diff --git a/.leptosfmt.toml b/.leptosfmt.toml deleted file mode 100644 index b6f799d6..00000000 --- a/.leptosfmt.toml +++ /dev/null @@ -1 +0,0 @@ -tab_spaces = 4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a1c9a0bf..056413dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -108,12 +108,6 @@ provided by default, but it can be overridden by adding an `AccordionTriggerIcon components display their defaults when they have no children, but override those defaults with arbitrary content provided as children. -### Avoid using other Singlestage components internally - -An example of this is the `DropdownMenuTrigger`. Instead of using a `Button`, it re-implements the -`Button` itself. This avoids a layer of complexity and prevents updates to `Button` breaking -`DropdownMenuTrigger`. Remember to add feature dependencies to `Cargo.toml` when using CSS classes -from other components. ### Prefix CSS classes with `singlestage-` to avoid name collisions diff --git a/Cargo.lock b/Cargo.lock index dcebf221..3c98c664 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,15 +8,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -25,9 +16,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] @@ -39,26 +30,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1384d3fe1eecb464229fcf6eebb72306591c56bf27b373561489458a7c73027d" dependencies = [ "futures", - "thiserror 2.0.17", + "thiserror 2.0.20", +] + +[[package]] +name = "any_spawner" +version = "0.3.0" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" +dependencies = [ + "futures", + "thiserror 2.0.20", "tokio", "wasm-bindgen-futures", ] [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] @@ -82,13 +81,13 @@ checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -108,7 +107,7 @@ dependencies = [ "manyhow", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -124,14 +123,37 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "aws-lc-rs" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", ] [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "base64", @@ -206,9 +228,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "block-buffer" @@ -221,9 +243,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.2" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -232,9 +254,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -242,27 +264,27 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "camino" -version = "1.2.2" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" [[package]] name = "cc" -version = "1.2.52" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -276,6 +298,32 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "codee" version = "0.3.5" @@ -284,7 +332,7 @@ checksum = "a9dbbdc4b4d349732bc6690de10a9de952bd39ba6a065c586e26600b6b0b91f5" dependencies = [ "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.20", ] [[package]] @@ -293,11 +341,21 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "brotli", "compression-core", @@ -309,24 +367,15 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "config" -version = "0.15.19" +version = "0.15.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +checksum = "b85f248a4de22d204ceabc6299d89d2c70fbd7f09fea53c06c852369652d8139" dependencies = [ "convert_case 0.6.0", "pathdiff", @@ -347,17 +396,18 @@ dependencies = [ [[package]] name = "const-str" -version = "0.7.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0664d2867b4a32697dfe655557f5c3b187e9b605b38612a748e5ec99811d160" +checksum = "18f12cc9948ed9604230cdddc7c86e270f9401ccbe3c2e98a4378c5e7632212f" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -374,8 +424,7 @@ dependencies = [ [[package]] name = "const_str_slice_concat" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" [[package]] name = "convert_case" @@ -388,20 +437,20 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" dependencies = [ "unicode-segmentation", ] [[package]] -name = "convert_case" -version = "0.10.0" +name = "convert_case_extras" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +checksum = "589c70f0faf8aa9d17787557d5eae854d7755cac50f5c3d12c81d3d57661cebb" dependencies = [ - "unicode-segmentation", + "convert_case 0.11.0", ] [[package]] @@ -414,6 +463,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -429,6 +488,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -438,11 +506,30 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crypto-common" @@ -454,44 +541,27 @@ dependencies = [ "typenum", ] -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", -] +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" [[package]] name = "derive-where" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -506,19 +576,20 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "docs_macro" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "rayon", "serde", "syntect", "toml", @@ -530,17 +601,22 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "either_of" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216d23e0ec69759a17f05e1c553f3a6870e5ec73420fbb07807a6f34d5d1d5a4" +version = "0.1.9" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "paste", "pin-project-lite", @@ -567,23 +643,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1731451909bde27714eacba19c2566362a7f35224f52b153d3f42cf60f72472" -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -598,23 +663,17 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -626,21 +685,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -650,11 +694,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -667,9 +717,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -677,56 +727,55 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -736,7 +785,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -752,13 +800,15 @@ 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", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -768,10 +818,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasm-bindgen", ] @@ -817,9 +879,9 @@ checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" [[package]] name = "h2" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27" dependencies = [ "atomic-waker", "bytes", @@ -836,21 +898,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "hermit-abi" -version = "0.5.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hex" @@ -860,18 +910,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "html-escape" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" -dependencies = [ - "utf8-width", -] +checksum = "c9356095b4b41197bba32173600e1582792cda618f65d12f68e2e77d273413c5" [[package]] name = "http" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -879,9 +926,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -889,9 +936,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -920,14 +967,12 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hydration_context" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8714ae4adeaa846d838f380fbd72f049197de629948f91bf045329e0cf0a283" +version = "0.3.1" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "futures", "js-sys", - "once_cell", - "or_poisoned", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", "pin-project-lite", "serde", "throw_error", @@ -936,9 +981,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -951,7 +996,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -959,46 +1003,28 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -1017,9 +1043,9 @@ dependencies = [ [[package]] name = "icondata" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278d66461d56cdaa2ff2a395e2e0b033330ed0c16171cc3f8f3f3857ea164832" +checksum = "412c95f764f89aad1a8505aa4273d931b66d5a26d1e6609a37243784a9128ad9" dependencies = [ "icondata_ai", "icondata_bi", @@ -1114,9 +1140,9 @@ dependencies = [ [[package]] name = "icondata_hi" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c70ef8303eb5e17445abd67260e057d3c94b793b120fca3809373c6b7195e38" +checksum = "1f8ba6ed6e155898d296fc5d4127eb651b667643368698dd02b1e9383eab3eef" dependencies = [ "icondata_core", ] @@ -1222,12 +1248,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1235,9 +1262,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1248,9 +1275,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1262,16 +1289,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -1282,15 +1310,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" dependencies = [ "displaydoc", "icu_locale_core", @@ -1314,9 +1342,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1324,12 +1352,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown", ] [[package]] @@ -1340,28 +1368,18 @@ checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" [[package]] name = "inventory" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" dependencies = [ "rustversion", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" -dependencies = [ - "memchr", - "serde", -] +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "itertools" @@ -1374,42 +1392,106 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] [[package]] -name = "leptos" -version = "0.8.15" +name = "konst" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9569fc37575a5d64c0512145af7630bf651007237ef67a8a77328199d315bb" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" dependencies = [ - "any_spawner", + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "leptos" +version = "0.8.20" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" +dependencies = [ + "any_spawner 0.3.0 (git+https://github.com/leptos-rs/leptos)", "base64", "cfg-if", "either_of", "futures", - "getrandom 0.3.4", + "getrandom 0.4.3", "hydration_context", "leptos_config", "leptos_dom", @@ -1417,10 +1499,10 @@ dependencies = [ "leptos_macro", "leptos_server", "oco_ref", - "or_poisoned", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", "paste", - "rand", - "reactive_graph", + "rand 0.9.5", + "reactive_graph 0.2.14 (git+https://github.com/leptos-rs/leptos)", "rustc-hash", "rustc_version", "send_wrapper", @@ -1430,10 +1512,10 @@ dependencies = [ "server_fn", "slotmap", "tachys", - "thiserror 2.0.17", + "thiserror 2.0.20", "throw_error", - "typed-builder 0.23.2", - "typed-builder-macro 0.23.2", + "typed-builder", + "typed-builder-macro", "wasm-bindgen", "wasm-bindgen-futures", "wasm_split_helpers", @@ -1442,13 +1524,11 @@ dependencies = [ [[package]] name = "leptos_axum" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0caa95760f87f3067e05025140becefdbdfd36cbc2adac4519f06e1f1edf4af" +version = "0.8.10" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ - "any_spawner", + "any_spawner 0.3.0 (git+https://github.com/leptos-rs/leptos)", "axum", - "dashmap", "futures", "hydration_context", "leptos", @@ -1456,36 +1536,33 @@ dependencies = [ "leptos_macro", "leptos_meta", "leptos_router", - "parking_lot", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", "server_fn", "tachys", "tokio", "tower", - "tower-http", + "tower-http 0.6.11", ] [[package]] name = "leptos_config" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071fc40aeb9fcab885965bad1887990477253ad51f926cd19068f45a44c59e89" +version = "0.8.10" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "config", - "regex", "serde", - "thiserror 2.0.17", - "typed-builder 0.21.2", + "thiserror 2.0.20", + "typed-builder", ] [[package]] name = "leptos_dom" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f4330c88694c5575e0bfe4eecf81b045d14e76a4f8b00d5fd2a63f8779f895" +version = "0.8.8" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "js-sys", - "or_poisoned", - "reactive_graph", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", + "reactive_graph 0.2.14 (git+https://github.com/leptos-rs/leptos)", "send_wrapper", "tachys", "wasm-bindgen", @@ -1494,27 +1571,25 @@ dependencies = [ [[package]] name = "leptos_hot_reload" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d61ec3e1ff8aaee8c5151688550c0363f85bc37845450764c31ff7584a33f38" +version = "0.8.6" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "anyhow", "camino", "indexmap", - "parking_lot", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", "proc-macro2", "quote", "rstml", "serde", - "syn", + "syn 2.0.119", "walkdir", ] [[package]] name = "leptos_integration_utils" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13cccc9305df53757bae61bf15641bfa6a667b5f78456ace4879dfe0591ae0e8" +version = "0.8.8" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "futures", "hydration_context", @@ -1522,18 +1597,18 @@ dependencies = [ "leptos_config", "leptos_meta", "leptos_router", - "reactive_graph", + "reactive_graph 0.2.14 (git+https://github.com/leptos-rs/leptos)", ] [[package]] name = "leptos_macro" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86ffd2e9cf3e264e9b3e16bdb086cefa26bd0fa7bc6a26b0cc5f6c1fd3178ed" +version = "0.8.17" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "attribute-derive", "cfg-if", - "convert_case 0.10.0", + "convert_case 0.11.0", + "convert_case_extras", "html-escape", "itertools", "leptos_hot_reload", @@ -1544,20 +1619,19 @@ dependencies = [ "rstml", "rustc_version", "server_fn_macro", - "syn", + "syn 2.0.119", "uuid", ] [[package]] name = "leptos_meta" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d489e38d3f541e9e43ecc2e3a815527840345a2afca629b3e23fcc1dd254578" +version = "0.8.6" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "futures", "indexmap", "leptos", - "or_poisoned", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", "send_wrapper", "wasm-bindgen", "web-sys", @@ -1565,24 +1639,23 @@ dependencies = [ [[package]] name = "leptos_router" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e573711f2fb9ab5d655ec38115220d359eaaf1dcb93cc0ea624543b6dba959" +version = "0.8.15" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ - "any_spawner", + "any_spawner 0.3.0 (git+https://github.com/leptos-rs/leptos)", "either_of", "futures", "gloo-net", "js-sys", "leptos", "leptos_router_macro", - "or_poisoned", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", "percent-encoding", - "reactive_graph", + "reactive_graph 0.2.14 (git+https://github.com/leptos-rs/leptos)", "rustc_version", "send_wrapper", "tachys", - "thiserror 2.0.17", + "thiserror 2.0.20", "url", "wasm-bindgen", "web-sys", @@ -1591,28 +1664,26 @@ dependencies = [ [[package]] name = "leptos_router_macro" version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409c0bd99f986c3cfa1a4db2443c835bc602ded1a12784e22ecb28c3ed5a2ae2" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "leptos_server" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf1045af93050bf3388d1c138426393fc131f6d9e46a65519da884c033ed730" +version = "0.8.7" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ - "any_spawner", + "any_spawner 0.3.0 (git+https://github.com/leptos-rs/leptos)", "base64", "codee", "futures", "hydration_context", - "or_poisoned", - "reactive_graph", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", + "reactive_graph 0.2.14 (git+https://github.com/leptos-rs/leptos)", "send_wrapper", "serde", "serde_json", @@ -1622,15 +1693,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "linear-map" -version = "1.2.0" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "linked-hash-map" @@ -1638,32 +1703,23 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] -name = "lock_api" -version = "0.4.14" +name = "log" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] -name = "log" -version = "0.4.29" +name = "lru-slab" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "manyhow" @@ -1674,7 +1730,7 @@ dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1696,9 +1752,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "mime" @@ -1728,9 +1784,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -1754,66 +1810,37 @@ dependencies = [ "version_check", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "next_tuple" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "oco_ref" version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0423ff9973dea4d6bd075934fdda86ebb8c05bdf9d6b0507067d4a1226371d" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "serde", - "thiserror 2.0.17", + "thiserror 2.0.20", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "onig" -version = "6.5.1" +version = "6.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" dependencies = [ "bitflags", "libc", @@ -1823,63 +1850,30 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.9.1" +version = "69.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" dependencies = [ "cc", "pkg-config", ] -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] -name = "openssl-sys" -version = "0.9.111" +name = "or_poisoned" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" [[package]] name = "or_poisoned" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" [[package]] name = "parking" @@ -1887,29 +1881,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - [[package]] name = "paste" version = "1.0.15" @@ -1930,47 +1901,41 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "plist" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" dependencies = [ "base64", "indexmap", @@ -1981,9 +1946,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -2010,7 +1975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.119", ] [[package]] @@ -2032,7 +1997,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2048,9 +2013,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -2063,25 +2028,82 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "version_check", "yansi", ] [[package]] name = "quick-xml" -version = "0.38.4" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" -version = "1.0.43" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -2105,7 +2127,7 @@ dependencies = [ "proc-macro-utils", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2114,14 +2136,31 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -2131,115 +2170,167 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", ] [[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", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "reactive_graph" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f0df355582937223ea403e52490201d65295bd6981383c69bfae5a1f8730c2" +checksum = "00c5a025366836190c7030e883cc2bcd9e384ff555336e3c7954741ca411b177" dependencies = [ - "any_spawner", + "any_spawner 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "async-lock", "futures", "guardian", - "hydration_context", "indexmap", - "or_poisoned", + "or_poisoned 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "paste", "pin-project-lite", "rustc-hash", "rustc_version", "send_wrapper", - "serde", "slotmap", - "thiserror 2.0.17", + "thiserror 2.0.20", "web-sys", ] [[package]] -name = "reactive_stores" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35372f05664a62a3dd389503371a15b8feb3396f99f6ec000de651fddb030942" +name = "reactive_graph" +version = "0.2.14" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ - "dashmap", + "any_spawner 0.3.0 (git+https://github.com/leptos-rs/leptos)", + "async-lock", + "futures", "guardian", - "itertools", - "or_poisoned", + "hydration_context", + "indexmap", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", "paste", - "reactive_graph", - "reactive_stores_macro", + "pin-project-lite", "rustc-hash", + "rustc_version", "send_wrapper", + "serde", + "slotmap", + "thiserror 2.0.20", + "web-sys", ] [[package]] -name = "reactive_stores_macro" -version = "0.2.6" +name = "reactive_stores" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa40919eb2975100283b2a70e68eafce1e8bcf81f0622ff168e4c2b3f8d46bb" +checksum = "c30fd35b7d299c591293bb69fed47a703eb2703b1cff0493e78b16ed007e5382" dependencies = [ - "convert_case 0.8.0", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", + "guardian", + "indexmap", + "itertools", + "or_poisoned 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "paste", + "reactive_graph 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "reactive_stores_macro 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hash", + "send_wrapper", ] [[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +name = "reactive_stores" +version = "0.4.3" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ - "bitflags", + "guardian", + "indexmap", + "itertools", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", + "paste", + "reactive_graph 0.2.14 (git+https://github.com/leptos-rs/leptos)", + "reactive_stores_macro 0.4.3 (git+https://github.com/leptos-rs/leptos)", + "rustc-hash", + "send_wrapper", ] [[package]] -name = "regex" -version = "1.12.2" +name = "reactive_stores_macro" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "68072edd607edd30b9ebf57d984ba45d8ab8809e598d0f6046278373fb76a5a0" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "convert_case 0.11.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +name = "reactive_stores_macro" +version = "0.4.3" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "convert_case 0.11.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", @@ -2253,23 +2344,21 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", + "rustls-platform-verifier", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower", - "tower-http", + "tower-http 0.6.11", "tower-service", "url", "wasm-bindgen", @@ -2285,7 +2374,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -2301,16 +2390,16 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.119", "syn_derive", - "thiserror 2.0.17", + "thiserror 2.0.20", ] [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -2321,25 +2410,13 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ + "aws-lc-rs", "once_cell", "rustls-pki-types", "rustls-webpki", @@ -2347,21 +2424,62 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2369,15 +2487,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -2390,27 +2508,21 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2418,9 +2530,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -2428,9 +2540,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "send_wrapper" @@ -2443,9 +2555,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2453,29 +2565,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -2503,14 +2615,14 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", - "thiserror 2.0.17", + "thiserror 2.0.20", ] [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -2529,16 +2641,14 @@ dependencies = [ [[package]] name = "server_fn" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353d02fa2886cd8dae0b8da0965289fa8f2ecc7df633d1ce965f62fdf9644d29" +version = "0.8.13" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "axum", "base64", "bytes", "const-str", "const_format", - "dashmap", "futures", "gloo-net", "http", @@ -2546,6 +2656,7 @@ dependencies = [ "hyper", "inventory", "js-sys", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", "pin-project-lite", "rustc_version", "rustversion", @@ -2554,7 +2665,7 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "thiserror 2.0.17", + "thiserror 2.0.20", "throw_error", "tokio", "tower", @@ -2569,37 +2680,35 @@ dependencies = [ [[package]] name = "server_fn_macro" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "950b8cfc9ff5f39ca879c5a7c5e640de2695a199e18e424c3289d0964cabe642" +version = "0.8.10" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "const_format", - "convert_case 0.8.0", + "convert_case 0.11.0", "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", "xxhash-rust", ] [[package]] name = "server_fn_macro_default" version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63eb08f80db903d3c42f64e60ebb3875e0305be502bdc064ec0a0eab42207f00" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "server_fn_macro", - "syn", + "syn 2.0.119", ] [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -2610,7 +2719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -2629,33 +2738,52 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "singlestage" -version = "0.4.0" +version = "0.5.0" dependencies = [ "leptos", "leptos_meta", - "reactive_stores", + "leptos_router", + "rayon", + "reactive_stores 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest", "serde", "sha256", "singlestage_macro 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "uuid", + "web-sys", ] [[package]] name = "singlestage_docs" -version = "0.4.0" +version = "0.5.0" dependencies = [ "axum", "console_error_panic_hook", @@ -2668,7 +2796,7 @@ dependencies = [ "singlestage", "tokio", "tower", - "tower-http", + "tower-http 0.7.0", "wasm-bindgen", "web-sys", ] @@ -2678,7 +2806,7 @@ name = "singlestage_macro" version = "0.1.1" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2688,14 +2816,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f833d819750ca73ed9658c83636b35fcac6bc8d008b26d4dfdc771ad050bc2d3" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slotmap" @@ -2708,25 +2836,25 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" [[package]] name = "stable_deref_trait" @@ -2742,9 +2870,20 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -2760,7 +2899,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2780,7 +2919,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2799,19 +2938,19 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.20", "walkdir", "yaml-rust", ] [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2827,11 +2966,10 @@ dependencies = [ [[package]] name = "tachys" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b2db11e455f7e84e2cc3e76f8a3f3843f7956096265d5ecff781eabe235077" +version = "0.2.18" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ - "any_spawner", + "any_spawner 0.3.0 (git+https://github.com/leptos-rs/leptos)", "async-trait", "const_str_slice_concat", "drain_filter_polyfill", @@ -2842,14 +2980,12 @@ dependencies = [ "indexmap", "itertools", "js-sys", - "linear-map", "next_tuple", "oco_ref", - "or_poisoned", - "parking_lot", + "or_poisoned 0.1.0 (git+https://github.com/leptos-rs/leptos)", "paste", - "reactive_graph", - "reactive_stores", + "reactive_graph 0.2.14 (git+https://github.com/leptos-rs/leptos)", + "reactive_stores 0.4.3 (git+https://github.com/leptos-rs/leptos)", "rustc-hash", "rustc_version", "send_wrapper", @@ -2859,19 +2995,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "tempfile" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -2883,11 +3006,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.20", ] [[package]] @@ -2898,55 +3021,53 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "throw_error" version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0ed6038fcbc0795aca7c92963ddda636573b956679204e044492d2b13c8f64" +source = "git+https://github.com/leptos-rs/leptos#0625dfd15230b05174284fd56642681b918460fb" dependencies = [ "pin-project-lite", ] [[package]] name = "time" -version = "0.3.44" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -2954,19 +3075,34 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" -version = "1.49.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -2979,23 +3115,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", + "syn 3.0.3", ] [[package]] @@ -3010,9 +3136,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ "futures-util", "log", @@ -3022,22 +3148,23 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap", "serde_core", @@ -3050,33 +3177,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[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", @@ -3090,11 +3217,10 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "async-compression", "bitflags", "bytes", "futures-core", @@ -3104,7 +3230,6 @@ dependencies = [ "http-body-util", "http-range-header", "httpdate", - "iri-string", "mime", "mime_guess", "percent-encoding", @@ -3114,7 +3239,28 @@ dependencies = [ "tower", "tower-layer", "tower-service", - "tracing", + "url", +] + +[[package]] +name = "tower-http" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", ] [[package]] @@ -3157,28 +3303,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ "bytes", "data-encoding", "http", "httparse", "log", - "rand", + "rand 0.9.5", "sha1", - "thiserror 2.0.17", - "utf-8", -] - -[[package]] -name = "typed-builder" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" -dependencies = [ - "typed-builder-macro 0.21.2", + "thiserror 2.0.20", ] [[package]] @@ -3187,18 +3323,7 @@ version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" dependencies = [ - "typed-builder-macro 0.23.2", -] - -[[package]] -name = "typed-builder-macro" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "typed-builder-macro", ] [[package]] @@ -3209,14 +3334,14 @@ checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicase" @@ -3226,15 +3351,15 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -3260,18 +3385,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3280,21 +3393,15 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.19.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -3328,18 +3435,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -3350,22 +3457,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3373,31 +3477,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -3408,9 +3512,9 @@ dependencies = [ [[package]] name = "wasm_split_helpers" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a114b3073258dd5de3d812cdd048cca6842342755e828a14dbf15f843f2d1b84" +checksum = "ab578aae2fe2916edaea06843187d50f87b0965622da0ceef648edca27b385ba" dependencies = [ "async-once-cell", "wasm_split_macros", @@ -3418,26 +3522,45 @@ dependencies = [ [[package]] name = "wasm_split_macros" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56481f8ed1a9f9ae97ea7b08a5e2b12e8adf9a7818a6ba952b918e09c7be8bf0" +checksum = "3e653af7ee4a9ef0fce481a9ec6f43cb78de20d0cdb4f4f5862e1dc6e407e6c8" dependencies = [ "base16", "quote", "sha2", - "syn", + "syn 2.0.119", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -3488,16 +3611,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -3515,31 +3629,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -3548,122 +3645,74 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "xxhash-rust" -version = "0.8.15" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" [[package]] name = "yaml-rust" @@ -3682,9 +3731,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -3693,68 +3742,68 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -3763,9 +3812,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" dependencies = [ "yoke", "zerofrom", @@ -3774,20 +3823,20 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 55e9639c..526c1bea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [workspace] resolver = "2" -members = ["singlestage", "docs", "singlestage_macro", "docs_macro"] +members = ["docs", "docs_macro", "singlestage", "singlestage_macro"] [workspace.package] -version = "0.4.0" +version = "0.5.0" authors = ["Adam Doyle "] edition = "2024" rust-version = "1.88" @@ -15,35 +15,49 @@ keywords = ["leptos", "ui", "components", "basecoat", "shadcn"] categories = ["web-programming", "gui", "wasm"] [workspace.dependencies] -leptos = "0.8" -leptos_meta = "0.8" -leptos_router = "0.8" -leptos_axum = "0.8" +# leptos = "0.8" +# leptos_axum = "0.8" +# leptos_meta = "0.8" +# leptos_router = "0.8" +# leptos = { path = "../leptos/leptos" } +# leptos_axum = { path = "../leptos/integrations/axum" } +# leptos_meta = { path = "../leptos/meta" } +# leptos_router = { path = "../leptos/router" } +leptos = { git = "https://github.com/leptos-rs/leptos" } +leptos_axum = { git = "https://github.com/leptos-rs/leptos" } +leptos_meta = { git = "https://github.com/leptos-rs/leptos" } +leptos_router = { git = "https://github.com/leptos-rs/leptos" } # Singlestage icondata_core = "0.1" +reactive_stores = "0.4" +singlestage_macro = "0" +web-sys = "0.3" # Singlestage build -reqwest = "0.12" +reqwest = "0.13" sha256 = "1.6" # Docs axum = "0.8" console_error_panic_hook = "0.1" -icondata = "0.6" +icondata = "0.7" tokio = "1" tower = "0.5" -tower-http = "0.6" +tower-http = "0.7" uuid = "1.17" wasm-bindgen = "0.2" # Macros serde = "1.0" syntect = "5" -toml = "0.9" +toml = "1.0" quote = "1.0" syn = "2.0" +# Docs macro +rayon = "1.11" + [profile.wasm-release] inherits = "release" opt-level = 'z' diff --git a/README.md b/README.md index 10f8780f..9e042169 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Or add singlestage to your Cargo.toml: [dependencies] leptos = "0.8" ... -singlestage = "0.4" +singlestage = "0.5" ``` ### Nightly @@ -51,7 +51,7 @@ singlestage = "0.4" [dependencies] leptos = { version = "0.8", features = ["nightly"] } ... -singlestage = { version = "0.4", features = ["nightly"] } +singlestage = { version = "0.5", features = ["nightly"] } ``` Add the nightly feature to singlestage just as you do with Leptos, if using nightly. @@ -66,7 +66,7 @@ Add the islands feature to singlestage just as you do with Leptos, if using isla [dependencies] leptos = { version = "0.8", features = ["islands"] } ... -singlestage = { version = "0.4", features = ["islands"] } +singlestage = { version = "0.5", features = ["islands"] } ``` ### SSR @@ -85,7 +85,7 @@ Add the stores feature to singlestage if you're using reactive stores. This will leptos = "0.8" ... reactive_stores = "0.3" -singlestage = { version = "0.4", features = ["stores"] } +singlestage = { version = "0.5", features = ["stores"] } ``` ### Tailwind BYOB diff --git a/deploy_demo b/deploy_demo index 3a702887..c7012a5a 100755 --- a/deploy_demo +++ b/deploy_demo @@ -6,7 +6,7 @@ cargo leptos build --release mkdir dist cp ./target/release/singlestage_docs ./dist/ -cp -r ./target/site ./dist/ +cp -r ./target/site ./dist/ rsync -avzp dist deez:singlestage/ ssh -l "$USER" deez -t "cd singlestage @@ -17,4 +17,3 @@ rmdir dist sudo -S systemctl restart singlestage" rm -rf dist - diff --git a/deploy_demo_beta b/deploy_demo_beta new file mode 100755 index 00000000..e0101d81 --- /dev/null +++ b/deploy_demo_beta @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +cargo leptos build --release + +mkdir dist +cp ./target/release/singlestage_docs ./dist/ +cp -r ./target/site ./dist/ + +rsync -avzp dist deez:singlestage/beta/ +ssh -l "$USER" deez -t "cd singlestage/beta +rm singlestage_docs +rm -rf site +mv dist/* . +rmdir dist +sudo -S systemctl restart beta.singlestage" + +rm -rf dist diff --git a/docs/Cargo.toml b/docs/Cargo.toml index fd83779e..48578ae8 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -78,9 +78,6 @@ end2end-dir = "end2end" # The browserlist query used for optimizing the CSS. browserquery = "defaults" -# The environment Leptos will run in, usually either "DEV" or "PROD" -env = "DEV" - # The features to use when compiling the bin target # # Optional. Can be over-ridden with the command line parameter --bin-features @@ -107,4 +104,3 @@ lib-default-features = false lib-profile-release = "wasm-release" tailwind-input-file = "style/tailwind.css" -LEPTOS_TAILWIND_VERSION = "v4.1.14" diff --git a/docs/src/app.rs b/docs/src/app.rs index 62cda16a..e6764df5 100644 --- a/docs/src/app.rs +++ b/docs/src/app.rs @@ -52,11 +52,6 @@ pub fn shell(options: LeptosOptions) -> impl IntoView { // Generates ComponentRoutes component generate_component_routes!(); -#[component] -pub fn NotFound() -> impl IntoView { - view! { "Page not found." } -} - #[component] pub fn App() -> impl IntoView { // Provides context that manages stylesheets, titles, meta tags, etc. @@ -64,31 +59,33 @@ pub fn App() -> impl IntoView { view! { - <Stylesheet id="leptos" href="/pkg/singlestage_docs.css" /> <ThemeProvider> + <Stylesheet id="leptos" href="/pkg/singlestage_docs.css" /> <Router> - <Routes fallback=NotFound> - <ParentRoute path=StaticSegment("") view=SidebarContainer> - <Route path=StaticSegment("/") view=LandingPage /> - <Route path=StaticSegment("/introduction") view=Introduction /> - <Route path=StaticSegment("/install") view=Installation /> - <Route path=StaticSegment("/theme-provider") view=ThemeProviderRoute /> - <Route path=StaticSegment("/icon-macro") view=IconMacroRoute /> - <Route - path=StaticSegment("/reactive-debug") - view=|| { - view! { - {if cfg!(debug_assertions) { - ReactiveDebug.into_any() - } else { - NotFound.into_any() - }} - } - } - /> - <ComponentRoutes /> - </ParentRoute> - </Routes> + <SidebarProvider> + <AppSidebar /> + <SidebarInset> + <header class="flex sticky inset-x-0 top-0 z-99 gap-2 items-center border-b bg-(--background) isolate shrink-0"> + <div class="flex gap-2 justify-between items-center px-4 w-full h-14"> + <SidebarTrigger /> + <ThemeSwitcher /> + </div> + </header> + <div class="my-8 mx-2 sm:mx-12 max-w-4xl"> + <Routes fallback=NotFound> + <Route path=StaticSegment("/") view=LandingPage /> + <Route path=StaticSegment("/introduction") view=Introduction /> + <Route path=StaticSegment("/install") view=Installation /> + <Route + path=StaticSegment("/theme-provider") + view=ThemeProviderRoute + /> + <Route path=StaticSegment("/icon-macro") view=IconMacroRoute /> + <ComponentRoutes /> + </Routes> + </div> + </SidebarInset> + </SidebarProvider> </Router> </ThemeProvider> } diff --git a/docs/src/components/component_template.rs b/docs/src/components/component_template.rs index 21b4efb7..904f5c3c 100644 --- a/docs/src/components/component_template.rs +++ b/docs/src/components/component_template.rs @@ -49,10 +49,8 @@ pub fn Example( #[prop(optional, into)] description: String, #[prop(into)] code: String, #[prop(into)] view: AnyView, + #[prop(into)] full_size: bool, ) -> impl IntoView { - // Special case for Empty Background example to make it display full-size - let empty_backround = name == "Background"; - view! { <div class="my-12"> {if !name.is_empty() { @@ -66,15 +64,26 @@ pub fn Example( } else { "".into_any() }} <Tabs class="my-4"> - <TabsList> - <TabsTrigger value="preview">"Preview"</TabsTrigger> - <TabsTrigger value="code">"Code"</TabsTrigger> + <TabsList variant="line"> + <TabsTrigger value="preview"> + {icon!(icondata::LuAppWindow)} "Preview" + </TabsTrigger> + <TabsTrigger value="code">{icon!(icondata::LuCode)} "Code"</TabsTrigger> </TabsList> - <TabsContent value="preview"> - {if empty_backround { + <TabsContent class="pt-2" value="preview"> + {if full_size { view! { - <div class="h-[450px] border border-(--muted) rounded-md">{view}</div> + <div class=r#"border border-(--muted) rounded-md + [&_*.singlestage-sidebar-wrapper]:max-h-[930px] + [&_*.singlestage-sidebar-wrapper]:min-h-[930px] + [&_*.singlestage-sidebar-container]:max-h-[930px] + [&_*.singlestage-sidebar-container]:min-h-[930px] + [&_*.singlestage-sidebar-side-left]:[&_*.singlestage-sidebar-inner]:rounded-l-md + [&_*.singlestage-sidebar-side-right]:[&_*.singlestage-sidebar-inner]:rounded-r-md + has-[&_*.singlestage-sidebar-variant-inset]:[&_*.singlestage-sidebar-wrapper]:rounded-md + not-has-[&_*.singlestage-sidebar-variant-inset]:[&_*.singlestage-sidebar-inset]:rounded-md + "#>{view}</div> } .into_any() } else { @@ -87,7 +96,7 @@ pub fn Example( }} </TabsContent> - <TabsContent value="code"> + <TabsContent class="pt-2" value="code"> <CodeBlock code=code /> </TabsContent> </Tabs> @@ -159,7 +168,7 @@ pub fn ComponentTemplate( #[prop(into)] description: String, ) -> impl IntoView { view! { - <div class="max-w-3xl m-auto"> + <div class="max-w-4xl m-auto"> <h1 class="text-4xl font-semibold">{name}</h1> <p class="my-5 text-(--muted-foreground)">{description}</p> diff --git a/docs/src/components/mod.rs b/docs/src/components/mod.rs index 0e6b7df0..89c4c153 100644 --- a/docs/src/components/mod.rs +++ b/docs/src/components/mod.rs @@ -6,3 +6,4 @@ mod theme_switcher; pub use code_block::*; pub use component_template::*; pub use sidebar::*; +pub use theme_switcher::*; diff --git a/docs/src/components/sidebar.rs b/docs/src/components/sidebar.rs deleted file mode 100644 index f6ad7023..00000000 --- a/docs/src/components/sidebar.rs +++ /dev/null @@ -1,217 +0,0 @@ -use super::theme_switcher::*; -use docs_macro::*; -use leptos::prelude::*; -use leptos_router::{components::A, nested_router::Outlet}; -use singlestage::*; -#[component] -pub fn SidebarButton() -> impl IntoView { - let sidebar = expect_context::<SidebarContext>(); - - view! { - <Tooltip side="bottom" align="start" value="Toggle sidebar"> - <Button variant="ghost" size="sm-icon"> - <Show - when=move || { sidebar.hidden.get() } - fallback=move || match sidebar.side.get().as_str() { - "right" => 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(), - }} - </Show> - </Button> - </Tooltip> - } -} - -#[component] -pub fn DebugMenu() -> impl IntoView { - { - if cfg!(debug_assertions) { - view! { - <SidebarGroup> - <SidebarGroupLabel>"Debug"</SidebarGroupLabel> - <SidebarGroupContent> - <SidebarMenu> - <SidebarMenuItem> - <SidebarMenuButton> - <A href="/reactive-debug"> - {icon!(icondata::BsJoystick)} <span>"Reactivity"</span> - </A> - </SidebarMenuButton> - </SidebarMenuItem> - <SidebarMenuItem> - <Collapsible> - <CollapsibleTrigger slot> - <SidebarMenuButton> - {icon!(icondata::LuSettings)} "Test Menu" - </SidebarMenuButton> - </CollapsibleTrigger> - <CollapsibleContent> - <SidebarMenuSub> - <SidebarMenuSubItem> - <a href="#"> - <span>"One"</span> - </a> - </SidebarMenuSubItem> - </SidebarMenuSub> - <SidebarMenuSub> - <SidebarMenuSubItem> - <a href="#"> - <span>"Two"</span> - </a> - </SidebarMenuSubItem> - </SidebarMenuSub> - <SidebarMenuSub> - <SidebarMenuSubItem> - <a href="#"> - <span>"Three"</span> - </a> - </SidebarMenuSubItem> - </SidebarMenuSub> - <SidebarMenuSub> - <SidebarMenuSubItem> - <a href="#"> - <span>"Four"</span> - </a> - </SidebarMenuSubItem> - </SidebarMenuSub> - </CollapsibleContent> - </Collapsible> - </SidebarMenuItem> - </SidebarMenu> - </SidebarGroupContent> - </SidebarGroup> - } - .into_any() - } else { - ().into_any() - } - } -} - -#[component] -pub fn SidebarContainer() -> impl IntoView { - view! { - <SidebarProvider> - <Sidebar class="inset-shadow-sm"> - <SidebarHeader> - <a href="/"> - <div class="flex m-2"> - <div class="flex justify-center items-center max-h-12 rounded-md shadow-sm bg-(--primary) text-(--primary-foreground) min-w-12 max-w-12 min-h-12"> - <svg - class="h-9 fill-current" - viewBox="0 0 44.238907 53.197109" - version="1.1" - xml:space="preserve" - xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg" - > - <defs id="defs1" /> - <g id="layer1" transform="translate(-83.28999,-146.33474)"> - <path - id="path643" - d="m 111.70944,146.33474 -1.3503,0.0398 -1.35031,0.0398 -0.50539,0.19534 -0.5054,0.19534 -0.42788,0.43149 -0.42788,0.4315 -0.95188,1.54151 -0.9524,1.54099 -0.0879,0.0543 -0.0879,0.0543 -0.21807,-0.0548 -0.10284,-0.0258 h -5.1e-4 c -0.0255,0.002 -0.0512,4.1e-4 -0.0765,-0.004 -0.001,-1.9e-4 -0.002,-3.2e-4 -0.004,-5.2e-4 -0.002,-2.8e-4 -0.004,-2.2e-4 -0.005,-5.1e-4 -0.002,-1.9e-4 -0.003,-3.9e-4 -0.004,-5.2e-4 0.001,-1e-5 0.004,-1.4e-4 0.006,0 -0.0297,-0.002 -0.006,3.5e-4 -0.046,-0.006 -0.007,-0.002 -0.0139,-0.003 -0.0207,-0.005 -0.003,-7.2e-4 -0.005,-10e-4 -0.008,-0.002 -7.9e-4,-2.2e-4 -0.003,-5e-4 -0.003,-5.2e-4 -0.0105,-0.003 -0.0208,-0.006 -0.031,-0.01 h -5.2e-4 -5.2e-4 c -2.1e-4,-4e-5 -0.001,6e-5 -0.002,0 l -5.1e-4,-5.1e-4 h -5.2e-4 c -0.0126,-0.001 -0.0248,-0.003 -0.0372,-0.006 -0.004,-0.001 -0.0126,-0.003 -0.0196,-0.005 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 c -0.002,-7.1e-4 -0.005,-0.001 -0.007,-0.002 -0.002,-3.6e-4 -0.003,-0.001 -0.005,-0.002 -6.9e-4,-1.6e-4 -0.001,-3.5e-4 -0.002,-5.2e-4 -0.0219,-0.005 -0.0437,-0.0111 -0.0641,-0.0212 v -5.2e-4 h -5.2e-4 c -0.002,-8.6e-4 -0.004,-0.001 -0.006,-0.003 l -5.1e-4,-5.2e-4 -5.2e-4,-5.2e-4 h -5.2e-4 c -0.004,-0.002 -0.009,-0.004 -0.0129,-0.006 -0.004,-0.002 -0.009,-0.005 -0.0129,-0.007 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 l -5.2e-4,-5.2e-4 v -5.1e-4 h -5.2e-4 -5.1e-4 c -8e-4,-4.4e-4 -0.002,-9.2e-4 -0.003,-0.002 -0.001,-7.1e-4 -0.003,-0.002 -0.004,-0.002 -0.007,-0.003 -0.002,-0.001 -0.0269,-0.0134 -0.005,-0.003 -0.0106,-0.006 -0.016,-0.009 -0.002,-0.001 -0.004,-0.002 -0.006,-0.004 h -5.1e-4 l -5.2e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.1e-4 v -5.2e-4 c -0.005,-0.003 -0.002,-0.001 -0.0191,-0.0108 -0.0169,-0.0104 -0.0335,-0.0207 -0.0501,-0.0315 -0.014,-0.009 -0.028,-0.018 -0.0419,-0.0269 -0.007,-0.004 -0.0135,-0.008 -0.0202,-0.0124 -0.003,-0.002 -0.006,-0.003 -0.008,-0.005 v -5.1e-4 c -0.006,-0.004 -0.0127,-0.007 -0.0186,-0.0114 -0.009,-0.006 -0.009,-0.007 -0.0124,-0.009 -8.3e-4,-6.2e-4 -0.002,-5.9e-4 -0.003,-0.002 h -5.1e-4 c -4.3e-4,-3.1e-4 -0.001,-6.8e-4 -0.002,-0.001 h -5.2e-4 v -5.1e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 -5.2e-4 c -0.004,-0.002 -0.008,-0.004 -0.0114,-0.006 h -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 c -5.8e-4,-2.8e-4 -0.001,-7.5e-4 -0.002,-0.001 -0.0159,-0.008 -0.0333,-0.0162 -0.0558,-0.0284 -3.4e-4,-1.8e-4 -6.9e-4,-3.4e-4 -0.001,-5.2e-4 -0.002,-9e-4 -0.005,-0.003 -0.007,-0.004 -0.0108,-0.006 -0.0216,-0.0118 -0.032,-0.0181 -0.002,-0.001 -0.006,-0.003 -0.007,-0.004 -0.0188,-0.0102 0.007,0.004 -0.0238,-0.0129 0,0 -0.001,-9.3e-4 -0.002,-0.001 -0.001,-7.3e-4 -0.002,-9.8e-4 -0.003,-0.002 -0.001,-7.8e-4 -0.002,-0.001 -0.004,-0.002 -0.001,-9.1e-4 -0.002,-0.001 -0.003,-0.002 -0.002,-10e-4 -0.005,-0.003 -0.007,-0.005 -4.1e-4,-1.5e-4 -10e-4,-4.6e-4 -0.002,-5.2e-4 -0.002,-9.3e-4 -0.006,-0.003 -0.007,-0.004 v -5.2e-4 c -0.0101,-0.006 -0.0221,-0.0114 -0.0315,-0.0186 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 l -5.2e-4,-5.2e-4 v -5.1e-4 h -5.1e-4 v -5.2e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 v 5.2e-4 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 v -5.1e-4 c -0.002,-9.2e-4 -0.004,-0.001 -0.004,-0.002 h -5.1e-4 c -2.8e-4,-9e-5 -0.001,-3.5e-4 -0.003,-0.001 -0.0114,-0.004 -0.0226,-0.008 -0.0341,-0.0124 -0.0178,-0.005 -0.0281,-0.009 -0.0444,-0.0145 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 c -0.002,-6.9e-4 -0.006,-0.002 -0.0114,-0.004 -0.003,-9.6e-4 -0.003,-0.001 -0.005,-0.002 0.003,9.7e-4 0.003,10e-4 -0.0139,-0.005 -0.002,-6.7e-4 -0.004,-0.001 -0.006,-0.002 -0.0296,-0.0104 -0.0585,-0.0217 -0.0853,-0.0382 v -5.2e-4 h -5.2e-4 c -0.002,-8.3e-4 -0.004,-0.001 -0.006,-0.002 l -5.2e-4,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.2e-4 -5.2e-4,-5.1e-4 v -5.2e-4 -5.2e-4 c -0.003,-0.002 -0.005,-0.003 -0.008,-0.005 -0.0133,-0.007 -0.0261,-0.0156 -0.0388,-0.0238 -5.8e-4,-2.9e-4 -10e-4,-6.3e-4 -0.002,-0.001 -3.2e-4,-1.7e-4 -7.4e-4,-3.7e-4 -0.001,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 v -5.1e-4 h -5.2e-4 c -0.002,-0.001 -0.005,-0.003 -0.009,-0.005 h -5.2e-4 c -2.2e-4,-1.4e-4 -8.1e-4,-8.8e-4 -10e-4,-10e-4 h -5.2e-4 v -5.1e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 l -5.2e-4,-5.2e-4 c -0.004,-0.002 -0.007,-0.003 -0.008,-0.003 5.8e-4,8e-5 0.002,3.5e-4 0.003,5.2e-4 h 5.2e-4 5.2e-4 c 3.8e-4,-2e-5 0.001,-2e-5 0.002,0 -10e-4,-6.6e-4 -0.005,-0.002 -0.014,-0.006 -0.0263,-0.0133 -0.0407,-0.0223 -0.0517,-0.0289 -0.001,-7.3e-4 -0.004,-0.002 -0.005,-0.003 -3.4e-4,-1.7e-4 -6.9e-4,-3.4e-4 -10e-4,-5.1e-4 -0.004,-0.002 -0.008,-0.005 -0.0103,-0.006 -0.003,-0.002 -0.006,-0.003 -0.009,-0.005 -4.2e-4,-2.4e-4 -6.3e-4,-2.9e-4 -0.001,-5.2e-4 -2e-4,-1.1e-4 -7.9e-4,-3.8e-4 -0.001,-5.1e-4 h -5.1e-4 c -0.002,-0.001 -0.006,-0.004 -0.007,-0.005 v -5.1e-4 h -5.2e-4 -5.1e-4 c -0.01,-0.005 -0.004,-0.002 -0.0181,-0.0109 -0.002,-0.001 -0.004,-0.002 -0.006,-0.004 2.9e-4,2.2e-4 -0.002,-9.2e-4 -0.003,-0.002 l -5.2e-4,-5.2e-4 -5.1e-4,-5.2e-4 h -5.2e-4 l -5.2e-4,-5.1e-4 c -0.002,-0.001 -0.002,-0.001 -0.003,-0.002 -3.5e-4,-2.3e-4 -0.001,-8.9e-4 -0.002,-0.001 l -5.2e-4,-5.2e-4 h -5.1e-4 c -0.012,-0.007 -0.01,-0.006 -0.0233,-0.0145 h -5.1e-4 v -5.2e-4 c -0.003,-0.002 -0.006,-0.003 -0.008,-0.005 v -5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 -10e-4,-5.2e-4 c -5.3e-4,-3.2e-4 -9.8e-4,-6.8e-4 -0.002,-0.001 -8.7e-4,-3.8e-4 -0.002,-7.1e-4 -0.003,-0.001 -9.4e-4,-4.8e-4 -0.002,-6.6e-4 -0.005,-0.002 -0.007,-0.003 -0.005,-0.002 -0.0191,-0.009 -10e-4,-6.9e-4 -0.004,-0.002 -0.006,-0.003 h -5.2e-4 v -5.1e-4 c -0.019,-0.008 -0.019,-0.008 -0.0362,-0.0165 -1.7e-4,-1.1e-4 -8.3e-4,-4e-4 -0.001,-5.2e-4 h -5.2e-4 l -5.2e-4,-5.1e-4 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 v -5.2e-4 h -5.1e-4 c -0.003,-0.002 -0.007,-0.004 -0.0114,-0.006 -4e-4,-2e-4 -6.4e-4,-3.1e-4 -0.001,-5.1e-4 -5.9e-4,-2.9e-4 -4.7e-4,-2.6e-4 -10e-4,-5.2e-4 -0.018,-0.009 -0.0219,-0.0107 -0.032,-0.0165 7e-5,5e-5 -0.002,-0.001 -0.004,-0.002 -0.001,-7.9e-4 -0.003,-0.001 -0.004,-0.002 h -5.2e-4 c -3.7e-4,-2.1e-4 -7.3e-4,-3.2e-4 -0.001,-5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.1e-4 c -0.002,-0.001 -0.003,-0.002 -0.004,-0.003 -0.0212,-0.0115 -0.0267,-0.0154 -0.0429,-0.0253 -0.003,-0.002 -0.007,-0.005 -0.01,-0.007 -0.007,-0.005 -0.0139,-0.01 -0.0207,-0.0145 -0.0111,-0.008 -0.023,-0.0161 -0.0331,-0.0253 -0.006,-0.004 -0.0125,-0.008 -0.0181,-0.0119 -0.001,-8.4e-4 -0.004,-0.003 -0.005,-0.004 -0.009,-0.005 -0.008,-0.005 -0.0222,-0.0129 l -5.1e-4,-5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.2e-4 h -5.2e-4 c -0.002,-9.4e-4 -0.005,-0.002 -0.008,-0.003 -0.005,-0.002 -0.009,-0.004 -0.014,-0.007 l -5.2e-4,-5.2e-4 h -5.2e-4 c -0.002,-6.2e-4 -0.004,-0.002 -0.005,-0.002 -4e-4,-2.1e-4 -8e-4,-3.9e-4 -10e-4,-5.1e-4 h -5.2e-4 c -0.003,-10e-4 -0.002,-6.8e-4 -0.0176,-0.008 -0.002,-0.001 -0.004,-0.002 -0.007,-0.004 -7.6e-4,-3.3e-4 -0.002,-6.8e-4 -0.003,-0.001 -8.8e-4,-2.9e-4 -0.002,-6.9e-4 -0.003,-0.001 -0.003,-0.001 -0.007,-0.003 -0.009,-0.004 h -5.1e-4 v -5.2e-4 c -0.0113,-0.004 -0.0224,-0.009 -0.0331,-0.0145 0.007,0.003 0.0267,0.0121 0.0202,0.009 -0.0116,-0.006 -0.0244,-0.0104 -0.0357,-0.0171 -0.004,-0.002 -0.007,-0.004 -0.0109,-0.007 l -5.2e-4,-5.1e-4 h -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 c -2.2e-4,-1.8e-4 -8.3e-4,-8.4e-4 -0.001,-10e-4 l -5.1e-4,-5.2e-4 v -5.2e-4 h -5.2e-4 c -0.003,-0.001 -0.005,-0.003 -0.008,-0.005 -8.9e-4,-4.7e-4 -0.002,-0.001 -0.003,-0.002 l -0.002,-10e-4 -0.31833,-0.12868 -0.12299,-0.0501 c -0.01,-0.003 -0.0192,-0.006 -0.0284,-0.01 -0.005,-0.001 -0.0102,-0.003 -0.0202,-0.006 h -5.1e-4 l -5.2e-4,-5.2e-4 -5.2e-4,-5.2e-4 h -5.1e-4 c -10e-4,-3.6e-4 -0.002,-6.8e-4 -0.003,-10e-4 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 -5.2e-4,-5.1e-4 h -5.2e-4 -5.1e-4 c -0.0128,-0.002 -0.0258,-0.005 -0.0382,-0.009 -0.0194,-0.007 -0.0302,-0.008 -0.0651,-0.0269 -0.004,-0.001 -0.007,-0.003 -0.0109,-0.004 -0.007,-0.002 -0.0141,-0.004 -0.0212,-0.007 0.005,0.002 0.0132,0.004 0.01,0.003 -0.004,-10e-4 -0.008,-0.002 -0.0119,-0.004 h -5.2e-4 -5.2e-4 c -0.002,-6.1e-4 -0.004,-0.001 -0.006,-0.002 -0.002,-7e-4 -0.009,-0.003 -0.016,-0.006 -0.004,-9.2e-4 -0.006,-0.002 -0.007,-0.002 h -5.2e-4 c -0.001,-2.2e-4 -0.003,-2.9e-4 -0.004,-5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 v -5.2e-4 c -0.0258,-0.007 -0.0352,-0.0104 -0.061,-0.0202 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.2e-4 -5.1e-4 c -0.009,-0.002 -0.018,-0.005 -0.0269,-0.008 -7.7e-4,-2.6e-4 -8.3e-4,-2.8e-4 -0.002,-5.2e-4 -5.1e-4,-1.7e-4 -10e-4,-3.4e-4 -0.002,-5.1e-4 -0.015,-0.005 -0.0179,-0.006 -0.0346,-0.0129 h -5.2e-4 v -5.2e-4 c -0.003,-10e-4 -0.005,-0.002 -0.008,-0.003 -0.01,-0.003 -0.0201,-0.007 -0.03,-0.0103 1.7e-4,8e-5 -10e-4,-3.1e-4 -0.002,-5.1e-4 -0.001,-3.2e-4 -0.002,-7.4e-4 -0.003,-0.001 -4.7e-4,-1.5e-4 -0.001,-3.6e-4 -0.002,-5.1e-4 h -5.1e-4 c -0.014,-0.004 -0.018,-0.006 -0.0336,-0.0114 -0.0148,-0.006 -0.0136,-0.005 -0.0248,-0.0103 -0.003,-0.001 -0.005,-0.002 -0.008,-0.004 -0.003,-0.001 -0.005,-0.002 -0.007,-0.003 -8.3e-4,-3.8e-4 -0.002,-7e-4 -0.003,-0.001 v -5.1e-4 h -5.2e-4 c -0.01,-0.004 -0.0191,-0.008 -0.0269,-0.0109 h -5.1e-4 v -5.1e-4 h -5.2e-4 -5.2e-4 c -0.002,-6.3e-4 -0.006,-0.002 -0.0108,-0.004 -0.004,-0.001 -0.008,-0.003 -0.0114,-0.005 l -5.2e-4,-5.2e-4 h -5.1e-4 c -0.004,-0.001 -0.007,-0.002 -0.0109,-0.004 -0.007,-0.003 -0.0105,-0.004 -0.0186,-0.008 h -5.1e-4 c -0.001,-5.3e-4 -0.004,-0.001 -0.006,-0.002 h -5.2e-4 -5.1e-4 v -5.2e-4 c -0.003,-0.001 -0.004,-0.002 -0.006,-0.003 -5e-4,-1.9e-4 -0.001,-3.2e-4 -0.002,-5.1e-4 -2.3e-4,-9e-5 -8.6e-4,-4.5e-4 -10e-4,-5.2e-4 -0.003,-0.001 -0.006,-0.002 -0.009,-0.004 -5.1e-4,-2e-4 -0.001,-3.1e-4 -0.002,-5.1e-4 h -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 c -0.002,-4.5e-4 -0.006,-0.002 -0.009,-0.003 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 v -5.1e-4 h -5.1e-4 c -0.003,-8.6e-4 -0.005,-0.001 -0.006,-0.002 h -5.1e-4 c -0.004,-10e-4 -0.005,-0.002 -0.0129,-0.004 -6.8e-4,-2.2e-4 -0.0118,-0.004 -0.0202,-0.007 -0.001,-4.4e-4 -0.003,-6.6e-4 -0.004,-0.001 h -5.2e-4 v -5.1e-4 h -5.2e-4 c -0.001,-3.5e-4 -0.004,-0.001 -0.004,-0.002 -0.0172,-0.005 -0.0267,-0.008 -0.045,-0.014 -0.0254,-0.009 -0.0393,-0.0149 -0.0522,-0.0202 -0.0169,-0.006 -0.0326,-0.0125 -0.0476,-0.0176 -0.0152,-0.006 -0.0302,-0.0106 -0.0455,-0.016 -0.004,-0.001 -0.008,-0.003 -0.0124,-0.004 -10e-4,-3.5e-4 -0.003,-0.001 -0.005,-0.002 -10e-4,-4.4e-4 -0.002,-7.7e-4 -0.003,-0.001 l -5.2e-4,-5.1e-4 c -0.006,-0.002 -0.0127,-0.004 -0.0191,-0.006 -0.01,-0.003 -0.0209,-0.007 -0.032,-0.0109 -0.002,-6e-4 -0.003,-9.4e-4 -0.005,-0.002 l -5.2e-4,-5.1e-4 c -5.3e-4,-1.9e-4 -10e-4,-3.4e-4 -0.002,-5.2e-4 -0.0146,-0.005 -0.0215,-0.007 -0.0367,-0.0129 -0.005,-0.002 -0.0103,-0.005 -0.0155,-0.007 h -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 -5.2e-4 -5.1e-4 v -5.2e-4 -5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 l -5.2e-4,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 -5.2e-4 -5.1e-4 l -5.2e-4,-5.2e-4 -5.2e-4,-5.2e-4 h -5.1e-4 c -0.002,-9.4e-4 -0.005,-0.002 -0.009,-0.004 -0.002,-8e-4 -0.006,-0.002 -0.0114,-0.004 0.002,5.8e-4 0.005,0.001 0.009,0.002 -0.004,-0.001 -0.005,-0.001 -0.007,-0.002 h -5.2e-4 c -7e-4,-2e-4 -0.002,-3.5e-4 -0.003,-5.1e-4 h -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 c -6.4e-4,-1.8e-4 -7.5e-4,-4.1e-4 -10e-4,-5.2e-4 -0.001,-3.7e-4 -0.004,-10e-4 -0.005,-0.002 -0.002,-6.3e-4 -0.003,-6.4e-4 -0.008,-0.002 -6.8e-4,-2.5e-4 -0.002,-7.9e-4 -0.003,-0.001 -0.003,-9.3e-4 -0.006,-0.002 -0.0108,-0.004 -0.005,-0.001 -0.009,-0.002 -0.014,-0.004 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 c -3.7e-4,1e-5 -0.001,3e-5 -0.002,0 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 v -5.1e-4 -5.2e-4 c -0.0217,-0.004 -0.0435,-0.008 -0.0651,-0.0124 -0.0169,-0.004 -0.0362,-0.008 -0.0517,-0.0124 -0.0149,-0.004 -0.0298,-0.008 -0.0444,-0.0124 -2.1e-4,-8e-5 -0.001,-4.3e-4 -0.002,-5.1e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 c -0.0193,-0.006 -0.0306,-0.01 -0.0455,-0.0155 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 c -2.7e-4,-9e-5 -7.8e-4,8e-5 -10e-4,0 v -5.1e-4 h -5.2e-4 v -5.2e-4 h -5.1e-4 -5.2e-4 c -0.002,-6.1e-4 -0.004,-0.001 -0.006,-0.002 h -5.2e-4 c -9.4e-4,-1.2e-4 -0.003,-4.1e-4 -0.004,-5.1e-4 h -5.1e-4 -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 v -5.2e-4 -5.1e-4 c -0.011,-0.003 -0.0194,-0.006 -0.0305,-0.009 -0.002,-7.9e-4 -0.004,-0.001 -0.007,-0.002 h -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 -5.2e-4 c -0.007,-0.002 -0.0134,-0.003 -0.015,-0.004 h -5.1e-4 v -5.2e-4 h -5.2e-4 c -8.1e-4,-1.7e-4 -0.005,-0.002 -0.009,-0.003 -4.4e-4,-1.4e-4 -0.002,-3.5e-4 -0.002,-5.2e-4 -0.007,-0.002 -0.0131,-0.003 -0.017,-0.005 -0.0199,-0.006 -0.0375,-0.0108 -0.0553,-0.0176 -0.013,-0.005 -0.0154,-0.007 -0.0202,-0.009 7.7e-4,2.9e-4 -9e-5,-1.7e-4 -0.005,-0.002 -9.2e-4,-3.4e-4 -0.001,-3.5e-4 -0.002,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.2e-4 c -0.002,-6.5e-4 -0.008,-0.003 -0.0134,-0.005 -0.002,-7.8e-4 -0.005,-0.002 -0.006,-0.002 h -5.1e-4 v -5.1e-4 h -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 -5.2e-4 c -6.8e-4,-1.3e-4 -0.001,-3.9e-4 -0.002,-5.2e-4 h -5.2e-4 c -0.002,-4e-4 -0.004,-0.001 -0.006,-0.002 -0.002,-2.4e-4 -0.004,-7.8e-4 -0.006,-10e-4 h -5.1e-4 -5.2e-4 -5.2e-4 -5.1e-4 v -5.2e-4 h -5.2e-4 c -0.009,-0.001 -0.0112,-0.001 -0.0217,-0.004 -0.003,-6.3e-4 -0.006,-0.001 -0.009,-0.002 -8.8e-4,-2.1e-4 -0.003,-10e-4 -0.003,-10e-4 -10e-4,-2.4e-4 -0.003,-2.8e-4 -0.004,-5.2e-4 -0.0122,-0.002 -0.0103,-0.002 -0.0212,-0.004 h -5.1e-4 v -5.2e-4 h -5.2e-4 -5.2e-4 l -5.2e-4,-5.1e-4 h -5.1e-4 c -0.002,-1.7e-4 -0.005,-7.5e-4 -0.007,-10e-4 h -5.1e-4 c -0.0165,-0.002 -0.0331,-0.006 -0.0491,-0.0103 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 c -0.0141,-0.002 -0.028,-0.003 -0.0418,-0.006 -0.0139,-0.003 -0.0184,-0.004 -0.0284,-0.006 -3.1e-4,-1.3e-4 -0.004,-7.4e-4 -0.008,-0.002 -5.7e-4,-1.2e-4 -4.2e-4,1.3e-4 -0.001,0 -0.008,-0.002 -0.0199,-0.004 -0.0243,-0.005 -0.004,-6.5e-4 -0.008,-0.002 -0.0114,-0.003 -2.9e-4,-1e-5 -7.8e-4,4e-5 -0.001,0 -4.9e-4,-1.1e-4 -0.002,-4.5e-4 -0.002,-5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 -5.1e-4 v -5.1e-4 h -5.2e-4 c -0.001,-1.8e-4 -0.002,-3.4e-4 -0.004,-5.2e-4 h -5.1e-4 c -2.3e-4,1e-5 -0.001,0 -0.002,0 h -5.2e-4 c -0.0115,-0.002 -0.0153,-0.002 -0.0269,-0.005 -0.004,-7.7e-4 -0.008,-0.002 -0.0114,-0.003 -8.3e-4,-1.7e-4 -0.002,-3.6e-4 -0.003,-5.2e-4 h -5.1e-4 c -9.4e-4,-2e-4 -0.003,-2.6e-4 -0.004,-5.1e-4 h -5.1e-4 c -0.002,-4.2e-4 -0.004,-0.001 -0.006,-0.002 -0.001,-3.2e-4 -0.003,-7e-4 -0.004,-10e-4 -0.004,-6.8e-4 -0.006,-0.001 -0.01,-0.002 -0.005,-9e-4 -0.01,-0.002 -0.0119,-0.002 0.001,1.6e-4 0.004,2.5e-4 0.006,5.1e-4 -0.002,-2.8e-4 -0.005,-7.2e-4 -0.007,-0.001 h -5.1e-4 -5.2e-4 -5.2e-4 c -0.0198,-0.003 -0.0397,-0.007 -0.0589,-0.0129 -2.1e-4,-4e-5 -0.001,3e-5 -0.002,0 v -5.2e-4 c -10e-4,-2.2e-4 -0.003,-6.6e-4 -0.004,-0.001 -7.6e-4,-2.1e-4 -0.001,-3e-4 -0.002,-5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.1e-4 c -0.002,-4.9e-4 -0.005,-10e-4 -0.0108,-0.003 -0.0112,-0.003 -0.0291,-0.006 -0.045,-0.0114 h -5.1e-4 -5.2e-4 c -0.0189,-0.005 -0.03,-0.009 -0.0377,-0.0108 -0.003,-8.5e-4 -0.005,-0.001 -0.016,-0.005 -0.004,-0.001 -0.009,-0.003 -0.0129,-0.004 0.003,8.2e-4 0.006,0.001 0.008,0.002 -0.0202,-0.006 -0.0398,-0.013 -0.0589,-0.0222 -0.002,-7.9e-4 -0.005,-0.003 -0.007,-0.004 -0.001,-5e-4 -0.002,-0.001 -0.003,-0.002 -0.001,-5e-4 -0.003,-0.001 -0.005,-0.002 -3.6e-4,-1.7e-4 -6.8e-4,-3.5e-4 -10e-4,-5.2e-4 -3.6e-4,-1.6e-4 -6.7e-4,-3.5e-4 -0.001,-5.1e-4 -1.5e-4,-6e-5 -6.5e-4,-3.1e-4 -0.001,-5.2e-4 h -5.1e-4 l -5.2e-4,-5.2e-4 h -5.2e-4 c -3.3e-4,-1.5e-4 -6.6e-4,-3.4e-4 -0.001,-5.1e-4 v -5.2e-4 h -5.2e-4 c -0.003,-0.001 -0.007,-0.002 -0.0109,-0.004 -7.3e-4,-2.9e-4 -10e-4,-4.2e-4 -0.002,-5.1e-4 v -5.2e-4 h -5.2e-4 c -0.004,-0.002 -0.007,-0.003 -0.008,-0.003 l -5.2e-4,-5.2e-4 h -5.2e-4 c -0.001,-3.2e-4 -0.002,-6.4e-4 -0.003,-10e-4 h -5.1e-4 l -5.2e-4,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.2e-4 -5.1e-4 c -8.2e-4,-3.1e-4 -0.002,-6.7e-4 -0.003,-0.001 l -5.1e-4,-5.2e-4 h -5.2e-4 c -0.004,-0.001 -0.008,-0.003 -0.0119,-0.004 v -5.2e-4 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 c -0.003,-4.7e-4 -0.006,-0.001 -0.009,-0.002 -0.005,-10e-4 -0.0111,-0.003 -0.0165,-0.005 -0.002,-4e-4 -0.008,-0.002 -0.0145,-0.004 h -5.2e-4 c -2e-4,-2e-5 -10e-4,-4.7e-4 -0.002,-5.1e-4 h -5.2e-4 c -0.003,-8.5e-4 -0.006,-0.002 -0.008,-0.003 h -5.2e-4 c -0.0114,-0.001 -0.0229,-0.004 -0.0341,-0.007 -5e-4,-1.3e-4 -0.001,7e-5 -0.002,0 l -5.1e-4,-5.1e-4 h -5.2e-4 c -0.0168,-0.003 -0.0335,-0.006 -0.0496,-0.0119 -0.002,-6.3e-4 -0.003,-0.001 -0.005,-0.002 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 c -4.9e-4,-1.4e-4 -0.001,-3.7e-4 -0.002,-5.2e-4 -0.004,-0.001 -0.007,-0.002 -0.0114,-0.004 -0.002,-5.4e-4 -0.003,-0.001 -0.005,-0.002 -5e-4,-1.9e-4 -10e-4,-3.3e-4 -0.002,-5.2e-4 -5e-4,-1.9e-4 -0.001,-3.2e-4 -0.002,-5.2e-4 -7.6e-4,-2.1e-4 -0.001,-2.9e-4 -0.002,-5.1e-4 -0.0107,-0.003 -0.0213,-0.007 -0.0315,-0.0109 0.009,0.004 0.0143,0.006 -0.0202,-0.009 -0.002,-9.9e-4 -0.004,-0.003 -0.007,-0.004 -3.4e-4,-1.6e-4 -7e-4,-3.5e-4 -0.001,-5.1e-4 h -5.1e-4 c -0.0167,-0.001 -0.0333,-0.004 -0.0496,-0.008 -10e-4,-3.1e-4 -0.002,-7.1e-4 -0.004,-0.001 -0.001,-2.2e-4 -0.002,-2.7e-4 -0.003,-5.1e-4 -0.002,-3.7e-4 -0.003,-0.001 -0.005,-0.002 -3e-4,-9e-5 -6.5e-4,-4e-4 -10e-4,-5.2e-4 h -5.2e-4 c -0.003,-8.3e-4 -0.006,-0.002 -0.009,-0.003 -6.8e-4,-2.1e-4 -10e-4,-3.2e-4 -0.002,-5.2e-4 -0.002,-4.5e-4 -0.003,-6e-4 -0.005,-10e-4 -8.7e-4,-1.3e-4 -0.002,-3.9e-4 -0.003,-5.2e-4 -6.1e-4,-5e-5 -0.002,-4.2e-4 -0.003,-5.2e-4 -0.003,-3.1e-4 -0.006,-7.2e-4 -0.009,-0.001 h -5.2e-4 c -0.0108,-10e-4 -0.0219,-0.003 -0.0326,-0.005 h -5.2e-4 c -2.2e-4,-5e-5 -0.001,-4.7e-4 -0.002,-5.2e-4 h -5.2e-4 c -0.0113,-0.001 -0.0225,-0.004 -0.0336,-0.005 -3.2e-4,-8e-5 -6.7e-4,-4.6e-4 -0.001,-5.2e-4 -0.005,-8e-4 -0.0136,5.9e-4 -0.0181,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 v -5.2e-4 l -5.2e-4,-5.1e-4 v -5.2e-4 -5.2e-4 -5.1e-4 c -0.0141,-0.003 -0.0278,-0.007 -0.0413,-0.0124 -0.004,-0.001 -0.006,-0.002 -0.008,-0.003 -8.6e-4,-2.8e-4 -0.0029,-0.001 -0.0041,-0.002 -9.04e-4,-1.7e-4 -0.0018,-3.2e-4 -0.0026,-5.2e-4 h -5.17e-4 v -5.1e-4 h -5.17e-4 c -0.0014,-5.1e-4 -0.0026,-8.3e-4 -0.0047,-0.002 h -5.17e-4 c -7.97e-4,-2.8e-4 -0.0017,-7.2e-4 -0.0026,-0.001 -0.0037,-9.1e-4 -0.0072,-0.002 -0.01085,-0.003 -0.0042,-0.001 -0.007,-0.002 -0.01033,-0.004 h -5.17e-4 c -8.85e-4,-3.9e-4 -0.0035,-0.001 -0.0052,-0.002 -5.97e-4,-1.7e-4 -9.57e-4,-3.4e-4 -0.0016,-5.1e-4 h -5.16e-4 -5.17e-4 c -8.58e-4,-3e-4 -7.57e-4,-2.5e-4 -0.0015,-5.2e-4 l -5.17e-4,-5.2e-4 h -5.17e-4 -5.17e-4 -5.16e-4 -5.17e-4 v -5.1e-4 h -5.17e-4 c -1.95e-4,-6e-5 -7.04e-4,-4.1e-4 -0.001,-5.2e-4 h -5.17e-4 -5.17e-4 c -0.0045,1.5e-4 -0.0089,5.2e-4 -0.01344,5.2e-4 -0.02156,0.002 -0.04278,-0.003 -0.06408,-0.004 -0.003,2e-5 -0.0058,-4.3e-4 -0.0088,-5.1e-4 -0.0088,-2e-4 -0.01756,5e-5 -0.02635,0 -0.0022,-1e-5 -0.0045,1e-5 -0.0067,0 -0.0047,5.9e-4 -0.0097,6.1e-4 -0.01447,0.001 h -5.17e-4 l -5.16e-4,5.2e-4 h -5.17e-4 -5.17e-4 -5.17e-4 -5.17e-4 -5.16e-4 c -0.002,1.9e-4 -0.0037,9.3e-4 -0.0057,0.001 -0.0092,0.002 -0.01815,0.003 -0.02739,0.004 -0.01904,0.003 -0.03808,0.006 -0.05736,0.007 -6.56e-4,4e-5 -0.0014,-5e-5 -0.0021,0 h -5.17e-4 -5.17e-4 c -0.0018,2.8e-4 -0.0038,8.4e-4 -0.0057,0.001 -0.0094,0.001 -0.0088,0.001 -0.01809,0.002 -0.0016,1.3e-4 -0.007,6.7e-4 -0.01033,10e-4 -0.0039,4.7e-4 -0.0071,0.001 -0.0098,0.002 h -5.16e-4 -5.17e-4 -5.17e-4 c -0.01683,0.002 -0.01204,0.001 -0.03462,0.003 -0.0151,3.7e-4 -0.03038,2.6e-4 -0.04547,0 -0.0099,-1.6e-4 -0.01956,-4.7e-4 -0.02946,-5.1e-4 -0.0022,-1e-4 -0.0059,-2e-5 -0.0098,0 -4.3e-4,0 -6.68e-4,-1e-5 -10e-4,0 -0.0097,0.001 -0.01967,0.002 -0.02946,0.003 -0.0051,3.5e-4 -0.0075,3.4e-4 -0.01033,5.1e-4 h -5.17e-4 -5.16e-4 -5.17e-4 -5.17e-4 -5.17e-4 l -5.16e-4,5.2e-4 c -4.42e-4,2e-5 -0.0011,-2e-5 -0.0016,0 -0.01062,0.002 -0.02134,0.003 -0.03204,0.004 -0.03273,0.009 -0.06879,0.0144 -0.108521,0.0171 l -5.16e-4,5.2e-4 c -0.0058,0.004 -0.0116,0.009 -0.01757,0.0124 -0.0031,0.002 -0.0061,0.004 -0.0093,0.006 h -5.17e-4 l -5.17e-4,5.2e-4 -5.16e-4,5.2e-4 c -0.0018,10e-4 -0.004,0.002 -0.0057,0.003 -0.01016,0.007 -0.02137,0.0129 -0.02997,0.017 -0.0022,0.002 -0.0045,0.003 -0.0067,0.005 -0.01927,0.0103 -0.02753,0.015 -0.03101,0.017 -0.0053,0.003 -0.0105,0.006 -0.01602,0.009 -0.0041,0.002 -0.0079,0.003 -0.01137,0.005 h -5.17e-4 -5.17e-4 l -5.17e-4,0.001 h -5.17e-4 -5.16e-4 -5.17e-4 -5.17e-4 v 5.1e-4 h -5.17e-4 c -3e-4,1.3e-4 -0.0012,-1.3e-4 -0.0015,0 l -5.17e-4,0.001 h -5.16e-4 v 5.1e-4 h -5.17e-4 -5.17e-4 -5.17e-4 -5.16e-4 l -5.17e-4,10e-4 h -5.17e-4 c -0.0043,0.002 -0.0086,0.004 -0.01292,0.006 -0.0085,0.003 -0.01286,0.005 -0.01705,0.006 h -5.17e-4 l -5.17e-4,5.2e-4 -0.134875,0.0909 -0.207223,0.13849 -0.154512,0.12713 -0.154513,0.12712 -0.257865,0.67748 -0.257866,0.67696 -0.835608,2.40192 -0.835608,2.40192 -0.187585,0.19947 -0.187069,0.19896 -0.262516,0.0801 -0.263033,0.0796 -0.685746,0.10542 -0.686263,0.1049 -0.482658,-0.19999 -0.482658,-0.19999 -1.645378,0.13746 -1.645377,0.13798 -0.487826,0.27802 -0.487825,0.2775 -0.446485,0.42013 -0.445967,0.42065 -0.245463,0.43046 -0.245463,0.43047 -0.349333,0.88883 -0.349849,0.88884 -0.077,0.18603 -0.07648,0.18604 -0.216525,0.14211 -0.216524,0.14211 -1.30793,0.14521 -1.307414,0.14572 -0.130742,0.10852 -0.130741,0.10801 v 0.0827 0.0822 l 0.158647,0.20102 0.15813,0.20154 0.0093,0.42426 0.0098,0.42375 -0.178801,0.16691 -0.1788,0.1664 0.05116,0.13281 0.05116,0.13281 0.192753,0.0667 0.192753,0.0667 1.312065,-0.0517 1.312581,-0.0512 0.14056,0.0672 0.141077,0.0672 0.269234,0.80771 0.268718,0.8077 0.450618,0.69608 0.450619,0.69608 0.443383,0.53847 0.443384,0.53847 0.433049,0.27698 0.433048,0.27699 0.418063,0.14211 0.418062,0.14159 0.512113,0.0481 0.512114,0.0476 1.076937,-0.13901 1.076936,-0.13901 0.404627,-0.17364 0.404626,-0.17311 0.274919,-0.21033 0.275435,-0.2098 v -0.13436 -0.13488 l -0.486792,0.0548 -0.486792,0.0548 -0.550354,0.0475 -0.550354,0.047 0.127124,-0.0801 0.126608,-0.0796 0.889351,-0.10129 0.888835,-0.10129 0.42323,-0.11834 0.42323,-0.11782 0.749825,-0.59531 0.749308,-0.59531 0.817004,-0.46147 0.817005,-0.46147 0.973589,-0.46561 0.97358,-0.4656 0.71934,-0.20568 0.71985,-0.20567 0.27078,-0.0584 0.27027,-0.0579 0.22945,0.36535 0.22944,0.36536 0.31626,0.36121 0.31626,0.36122 0.21291,0.57464 0.2129,0.57413 0.14728,0.8816 0.1478,0.8816 -0.0522,1.82366 -0.0512,1.82366 -0.13384,1.16324 -0.13332,1.16375 -0.16485,0.64337 -0.16433,0.64337 -0.27492,0.56379 -0.27492,0.56379 -0.70022,0.70435 -0.70021,0.70435 v 0.11938 0.11885 l 0.19068,0.12248 0.19017,0.12195 0.52555,0.1018 0.52503,0.10129 0.78704,-0.0398 0.78703,-0.0403 0.25425,-0.11524 0.25373,-0.11575 -0.53072,-0.045 -0.5302,-0.045 -0.005,-0.0687 -0.005,-0.0682 0.59325,-0.74311 0.59324,-0.74311 0.56328,-0.41341 0.56327,-0.41341 0.19689,-0.34158 0.19637,-0.34158 0.13281,-0.63459 0.13229,-0.6351 0.0868,-0.8046 0.0868,-0.80409 0.0832,-1.27021 0.0832,-1.26969 0.006,-0.6351 0.006,-0.6351 0.10129,-0.29611 0.10077,-0.29662 0.18552,-0.0264 0.185,-0.0269 0.14366,0.21911 0.14366,0.21963 -0.0579,0.99218 -0.0579,0.99271 -0.12712,1.43919 -0.12712,1.43918 -0.12661,0.97359 -0.12661,0.97358 -0.0434,0.21188 -0.0439,0.21187 0.0889,-0.13901 0.0894,-0.13901 0.13384,-0.57723 0.13385,-0.57722 0.16174,-1.56993 0.16175,-1.56993 0.0496,-1.14308 0.0496,-1.14257 0.2036,-0.76223 0.20413,-0.76171 0.17156,-1.00614 0.17157,-1.00562 v -0.31729 -0.3173 l 0.10542,-0.0842 0.10593,-0.0832 0.43047,-0.0548 0.43046,-0.0548 0.13126,0.14882 0.13178,0.14883 0.20929,0.42427 0.2098,0.42323 0.21963,0.67696 0.22014,0.67748 0.20981,0.84697 0.2098,0.84646 0.13126,0.71985 0.13074,0.71882 0.12868,1.01596 0.12815,1.01648 0.20929,2.49804 0.20878,2.497 0.0517,0.25787 0.0522,0.25631 -1.21388,0.005 -1.21337,0.005 -0.84646,0.1912 -0.84697,0.19121 -0.29508,0.12247 -0.29507,0.12247 -0.19172,0.18035 -0.19172,0.17984 0.003,0.1757 0.003,0.1757 0.18759,0.22841 0.18707,0.22892 0.31522,0.13126 0.31471,0.13126 0.87024,-0.006 0.87074,-0.006 0.76171,-0.0682 0.76223,-0.0687 0.061,0.0351 0.0594,0.0346 -0.0904,0.19947 -0.091,0.19895 0.26097,1.85105 0.26045,1.85053 0.17053,1.31258 0.17053,1.31207 0.37931,3.43597 0.37982,3.43544 0.046,0.12041 0.0465,0.12041 0.24856,0.16691 0.24857,0.16692 0.0496,0.1974 0.0496,0.1974 0.29352,0.17312 0.29352,0.17312 v 0.0579 0.0579 l -0.16691,0.14315 -0.16692,0.14262 -0.0568,0.22583 -0.0563,0.22582 0.0853,1.13068 0.0858,1.13017 0.10387,0.12506 0.10387,0.12505 -0.0987,0.29869 -0.0987,0.29869 0.10336,0.48783 0.10283,0.48782 0.10904,0.1788 0.10904,0.1788 0.28112,0.0667 0.2806,0.0672 0.15089,0.0181 0.1509,0.0176 0.0563,0.0563 0.0553,0.0558 0.51315,-0.0491 0.51418,-0.0501 0.21187,-0.0517 0.21136,-0.0512 0.0801,0.0191 0.0801,0.0191 0.36586,-0.13953 0.36587,-0.13953 0.0718,-0.14107 0.0734,-0.14056 0.0563,-0.52607 0.0579,-0.52607 -0.13746,-0.26561 -0.13746,-0.26614 0.10129,-0.24494 0.1018,-0.24443 -0.0584,-0.74156 -0.0584,-0.74156 -0.0868,-0.52968 -0.0889,-0.5302 -0.24856,-0.1943 -0.24857,-0.19431 0.32091,-0.20722 0.32091,-0.20722 0.0269,-0.22738 0.0264,-0.22789 0.3638,-0.17622 0.36432,-0.17621 0.14315,0.0553 0.14314,0.0548 -0.0543,0.24649 -0.0548,0.2465 0.18759,0.9493 0.18707,0.94929 0.0713,0.1385 0.0718,0.13849 0.3421,0.17777 0.3421,0.17828 0.31677,0.0506 0.31678,0.0501 0.54312,-0.0982 0.54312,-0.0987 0.37879,-0.17777 0.3793,-0.17828 0.2837,-0.28422 0.28423,-0.2837 v -0.23978 -0.23926 l -0.17674,-0.87954 -0.17725,-0.87953 -0.12506,-0.19637 -0.12505,-0.19585 0.10025,-0.12093 0.0997,-0.1204 -0.0899,-0.5302 -0.0899,-0.52969 -0.0434,-1.39681 -0.0434,-1.39733 -0.13178,-1.26969 -0.13126,-1.27021 -0.17415,-1.22783 -0.17415,-1.22732 -0.29249,-1.55391 -0.293,-1.55442 -0.12713,-0.24598 -0.12712,-0.24598 -0.16691,-0.10439 -0.1664,-0.10387 v -0.0672 -0.0656 l 0.21135,-0.0806 0.21188,-0.0806 v -0.17001 -0.17002 l -0.10594,-0.39532 -0.10542,-0.39481 -0.31471,-0.25322 -0.31471,-0.25269 -0.12971,-0.43305 -0.1297,-0.43253 -0.76843,-3.81011 -0.76843,-3.8101 0.032,-0.84646 0.0315,-0.84646 0.17829,-0.339 0.17828,-0.33848 0.24029,-0.22324 0.24082,-0.22324 0.25424,-0.12713 0.25374,-0.12764 1.39681,-0.44803 1.39733,-0.44752 0.31729,-0.31368 0.31781,-0.31367 0.0517,-0.25787 0.0517,-0.25786 0.20205,-0.19017 0.20257,-0.18965 v -0.20258 -0.20257 l -0.0796,-0.14883 -0.0796,-0.14882 -0.32298,-0.22015 -0.32246,-0.22065 -0.52348,-0.10077 -0.52348,-0.10077 -1.04283,0.14159 -1.04283,0.14108 -0.19017,0.047 -0.19069,0.0465 v -0.12144 -0.12144 l 0.21963,-0.20515 0.21962,-0.20516 0.12144,-0.23306 0.12092,-0.23254 h 0.13074 0.13023 l 0.2775,-0.21188 0.2775,-0.21135 h 0.0889 0.0889 l 0.0548,0.14262 0.0553,0.14315 -0.13229,0.0739 -0.13229,0.0739 0.0491,0.0801 0.0491,0.0796 0.31265,-5.1e-4 0.31264,-5.2e-4 0.97358,-0.13384 0.97359,-0.13436 0.42323,-0.032 0.42323,-0.0326 0.50798,-0.14572 0.50797,-0.14573 0.19431,-0.12247 0.19378,-0.12248 0.20206,-0.34778 0.20154,-0.3483 0.0589,-0.31833 0.0584,-0.31832 -0.1049,-0.12609 -0.10439,-0.12609 0.10232,-0.12299 0.10232,-0.12351 -0.0434,-0.0703 -0.0429,-0.0703 -0.25114,-0.0191 -0.25115,-0.0186 -0.0501,0.13022 -0.0501,0.13074 -0.21239,-0.13901 -0.21239,-0.13952 -0.0362,0.11523 -0.0362,0.11524 -0.0496,0.22686 -0.0496,0.22738 -0.1478,0.0439 -0.14779,0.0444 -1.00666,0.1297 -1.00614,0.12971 -0.11213,0.0925 -0.11163,0.0925 -0.0889,-0.0548 -0.0889,-0.0548 v -0.2744 -0.27441 l -0.0889,-0.0889 -0.0884,-0.0884 -0.0398,-0.48162 -0.0398,-0.48162 0.25941,0.13435 0.25993,0.13436 1.03302,-0.10128 1.03301,-0.10077 0.1974,-0.12196 0.19792,-0.12195 0.0491,0.0491 0.0491,0.0491 0.015,0.19069 0.0145,0.19068 0.39843,-0.008 0.39791,-0.008 0.0951,-0.0956 0.0951,-0.0951 -0.10181,-0.34003 -0.1018,-0.34055 -0.31471,-0.30644 -0.31419,-0.30696 -0.19586,-0.0754 -0.19637,-0.0744 -0.69298,5.1e-4 -0.69298,0.001 -1.43919,0.16691 -1.43918,0.16692 -0.0636,10e-4 -0.0636,0.001 v 0.0848 0.0847 h 0.11059 0.1111 l 0.1478,0.10801 0.14779,0.108 -0.0558,0.14573 -0.0563,0.14624 h -0.21549 -0.21549 l -0.4315,-0.21755 -0.43149,-0.21705 -0.20464,-0.11523 -0.20464,-0.11524 -0.79065,-0.14883 -0.79013,-0.14883 -1.20613,0.10129 -1.20665,0.10128 -0.31729,-0.001 -0.31781,-5.2e-4 0.001,-0.14831 0.001,-0.14831 0.0465,-0.17363 0.0465,-0.17364 0.18551,-0.11782 0.18501,-0.11782 0.38085,-0.0579 0.38086,-0.0584 0.83509,-0.17932 0.83457,-0.17983 h 0.24495 0.24494 l 0.31626,-0.1695 0.31575,-0.16898 h 0.18035 0.18035 l 0.23151,-0.18242 0.23151,-0.1819 0.15348,-0.35812 0.15348,-0.3576 -0.0186,-0.37001 -0.0186,-0.37052 -0.25476,-0.0253 -0.25477,-0.0253 -0.10697,0.23461 -0.10697,0.23461 -0.15555,-0.20618 -0.15554,-0.20619 -0.0372,-0.29973 -0.0377,-0.2992 0.0243,-0.0289 0.0238,-0.0284 0.17105,0.0553 0.17156,0.0543 0.0548,0.17208 0.0548,0.17208 0.22479,-0.0558 0.22479,-0.0563 0.031,-0.0315 0.0315,-0.0315 -0.0574,-0.30541 -0.0574,-0.3054 -0.29714,-0.32867 -0.29714,-0.32918 -0.26975,-0.0853 -0.26924,-0.0863 -0.67747,0.076 -0.67748,0.0754 -1.10071,0.20257 -1.10019,0.20309 -0.1416,0.0631 -0.14107,0.063 -0.19741,-0.12712 -0.1974,-0.12764 -0.71985,0.0367 -0.71934,0.0362 -1.60869,0.25993 -1.60868,0.25994 -0.31781,0.0816 -0.3173,0.0822 -0.007,0.14469 -0.007,0.14521 -0.0894,-0.25424 -0.0889,-0.25373 -0.12919,-0.14832 -0.12868,-0.14831 h -0.19895 -0.19844 v 0.0506 0.0501 l 0.25425,0.23771 0.25373,0.23771 v 0.29094 0.29197 l -0.16175,0.14625 -0.16174,0.14624 -0.23823,-0.0274 -0.23823,-0.0274 -0.13126,-0.38034 -0.13177,-0.38034 -0.1571,-0.0305 -0.15658,-0.0305 -0.0723,0.11421 -0.0724,0.1142 -0.001,0.27027 -5.1e-4,0.27027 0.19844,0.23099 0.19843,0.23048 0.40153,-0.0155 0.40204,-0.015 0.16847,-0.0904 0.16898,-0.0899 0.12402,-0.2682 0.12454,-0.26872 -0.0501,0.8046 -0.0501,0.80409 -0.20516,1.65106 -0.20567,1.65106 -0.12816,0.93121 -0.12764,0.93121 10e-4,0.4656 0.001,0.46613 0.12661,0.67696 0.12661,0.67747 0.048,0.25787 0.0481,0.25787 -1.25728,0.0915 -1.25729,0.0904 -0.0372,-0.0336 -0.0372,-0.0336 0.14883,-1.45211 0.14831,-1.45211 0.11834,-0.22893 0.11834,-0.22841 -0.0977,-0.25889 -0.0982,-0.2589 0.23978,-2.39779 0.23926,-2.39727 0.0724,-0.88315 0.0734,-0.88367 0.2098,-0.30179 0.20981,-0.3023 0.19327,-0.12196 0.19275,-0.12247 0.33435,-0.0465 0.33434,-0.047 0.10749,-0.76171 0.10749,-0.76223 0.10077,-0.38085 0.10077,-0.38086 0.15606,-0.31471 0.15658,-0.31471 0.1633,-0.0884 0.16329,-0.0868 h 0.20826 0.20826 l 0.0517,0.13384 0.0512,0.13385 v 0.23409 0.23409 l 0.1757,0.15917 0.1757,0.15864 1.12707,0.0336 1.12706,0.0331 0.18862,-0.18862 0.18913,-0.18914 -0.0543,-0.47697 -0.0543,-0.47697 -0.11885,-0.44959 -0.11886,-0.4501 -0.13746,-0.21032 -0.13797,-0.21033 -0.26355,-0.16019 -0.26304,-0.15968 -0.56172,-0.10077 z m -11.63701,2.70061 c 2.9e-4,7e-5 7.2e-4,-6e-5 0.001,0 -3.1e-4,-6e-5 -7.3e-4,-4.3e-4 -0.001,-5.2e-4 z m 4.07158,1.51929 h 5.2e-4 v -5.2e-4 h -5.2e-4 z m -4.46794,-0.90744 -0.04806,0.23874 -0.04754,0.23823 -0.08888,0.23306 -0.08888,0.23306 -0.192237,-0.001 -0.192753,-0.002 -0.116272,-0.0739 -0.116272,-0.0734 0.12299,-0.36949 0.122473,-0.37 0.322978,-0.0269 z m 1.74305,0.56224 0.26406,0.0222 0.26407,0.0217 0.71778,0.28061 0.71727,0.2806 0.23048,0.17208 0.22996,0.17208 -0.11007,0.41445 -0.11059,0.41444 -0.0584,0.0568 -0.0584,0.0574 -0.21342,-0.11369 -0.21394,-0.11317 -0.59066,-0.18655 -0.59066,-0.18604 -0.35967,-0.20567 -0.35967,-0.20567 10e-4,-0.10232 0.001,-0.1018 0.11989,-0.339 z m 2.89026,0.55035 h 0.0424 0.0424 v 0.0424 0.0424 h -0.0424 -0.0424 v -0.0424 z m 15.25436,1.96112 0.11214,0.2682 0.11214,0.2682 -0.045,0.23307 -0.0444,0.23254 h -0.14883 -0.14934 l -0.10542,0.12712 -0.10542,0.12713 h -0.28061 -0.28112 l -0.0465,0.0749 -0.0465,0.0755 -0.27596,0.0429 -0.27595,0.0429 -0.17776,0.0579 -0.17777,0.0574 -0.10439,-0.40876 -0.1049,-0.40876 -0.0775,-0.19431 -0.0775,-0.1943 v -0.12816 -0.12816 l 0.37827,0.0563 0.37776,0.0568 0.40514,-0.0884 0.40514,-0.0884 0.36742,-0.0408 z m -25.33695,4.22197 0.130742,0.0501 0.130224,0.0501 v 0.0651 0.0651 l -0.249597,0.0496 -0.250114,0.0501 -0.892969,0.0579 -0.892968,0.0579 v -0.0915 -0.0915 l 0.147794,-0.045 0.148311,-0.045 0.864547,-0.0863 z m -3.929993,0.13281 0.381373,0.17311 0.380855,0.1726 0.287838,0.2434 0.287837,0.24339 0.628386,0.75913 0.628902,0.75964 0.156579,0.30696 0.15658,0.30696 0.129708,0.50281 0.130225,0.50281 0.05788,0.69867 0.05788,0.69866 h -0.04806 -0.04754 l -0.08578,-0.27491 -0.08578,-0.27544 -0.224793,-0.84646 -0.225309,-0.84646 -0.276986,-0.601 -0.276986,-0.60099 -0.321944,-0.35605 -0.321944,-0.35657 -0.345715,-0.19947 -0.345716,-0.19999 -0.436665,-0.12712 -0.436149,-0.12765 -0.622185,0.078 -0.621667,0.0775 -0.402043,0.19896 -0.402042,0.19843 v -0.1049 -0.10542 l 0.298689,-0.26252 0.29869,-0.262 0.357084,-0.18293 0.357601,-0.18294 0.465605,-0.004 z m -1.311547,1.12138 0.274918,0.0594 0.274919,0.0594 0.298173,0.1509 0.29869,0.15089 0.386539,0.41341 0.386023,0.4129 0.319877,0.64647 0.319361,0.64699 0.04393,0.38757 0.04341,0.38757 -0.116789,-0.016 -0.116789,-0.0155 -0.465604,-0.0264 -0.465605,-0.0264 -1.003039,-0.0863 -1.002523,-0.0863 -0.151412,-0.0806 -0.151412,-0.0811 0.02635,-1.23507 0.02584,-1.23507 0.09767,-0.11782 0.09767,-0.11782 0.289905,-0.0956 z m 33.63206,0.14986 0.11678,0.15916 0.11679,0.15968 -0.001,0.17467 -0.001,0.17415 -0.0625,0.0848 -0.062,0.0847 -0.13695,0.10698 -0.13746,0.10645 -0.0951,-0.11472 -0.0956,-0.11472 -0.0522,-0.25942 -0.0517,-0.25942 0.23099,-0.15141 z m 4.05401,0.54053 h 0.12196 0.12195 v 0.0847 0.0848 h -0.17415 -0.17415 l 0.0522,-0.0848 z m -33.693549,0.25425 h 0.245463 0.245463 l 0.530717,0.50798 0.530717,0.50746 0.296623,-0.007 0.296105,-0.007 0.495577,-0.062 0.495061,-0.0615 0.133842,0.20412 0.133842,0.20413 -0.100253,0.16019 -0.09974,0.15968 v 0.0434 0.0434 h 0.116789 0.116788 l 0.09457,0.11472 0.09508,0.11421 -0.07338,0.39067 -0.07286,0.39119 0.163298,0.10852 0.163298,0.10852 -0.05374,0.14005 -0.05323,0.13952 -0.185001,0.06 -0.184485,0.0594 0.148311,0.0398 0.147795,0.0393 v 0.1173 0.11679 l -0.274919,0.0476 -0.275435,0.0475 -0.592212,0.0837 -0.592729,0.0842 -0.338997,0.0501 -0.338998,0.0507 -0.104386,-0.36846 -0.104386,-0.36845 -0.08992,-0.41858 -0.08992,-0.41858 -0.412378,-1.21129 z m 13.283939,0.10904 0.0496,0.0796 0.0491,0.0801 -0.0961,0.0961 -0.0961,0.0961 -0.29869,-0.0258 -0.29869,-0.0253 -0.11265,0.0935 -0.11318,0.0935 -0.0444,-0.11575 -0.0444,-0.11576 0.0558,-0.0863 0.0558,-0.0863 0.447,-0.0419 z m -2.33319,0.22789 0.21136,0.0222 0.21187,0.0217 v 0.25269 0.25322 l -0.13074,0.0253 -0.13074,0.0248 -0.20567,-0.21859 -0.20567,-0.21911 0.12454,-0.0811 z m -12.009083,0.97565 0.131775,0.10801 0.132292,0.10852 0.195854,0.44183 0.195853,0.44183 0.0036,0.7705 0.0041,0.76998 -0.113172,0.13642 -0.113171,0.13643 -1.495516,0.0248 -1.495516,0.0248 -0.141593,0.0703 -0.14211,0.0698 -0.077,-0.36638 -0.077,-0.36639 0.03359,-0.0636 0.03307,-0.0636 h 0.241329 0.241329 l 1.044898,-0.13023 1.044897,-0.13074 0.185002,-0.0925 0.185001,-0.0925 0.101286,-0.24495 0.101286,-0.24494 -0.05013,-0.35761 -0.05013,-0.3576 -0.05994,-0.2961 z m 12.284003,0.20257 0.0367,0.19482 0.0367,0.19482 -0.0475,0.46612 -0.047,0.46561 -0.1943,0.0274 -0.19379,0.0274 0.0248,-0.6625 0.0248,-0.66249 0.17984,-0.0258 z m -16.226398,0.85525 h 0.08062 0.0801 v 0.14366 0.14366 l 0.227893,0.23719 0.227376,0.23772 h -0.2651 -0.264583 l -0.05219,0.0847 -0.05271,0.0847 -0.269751,-0.006 -0.270268,-0.006 -0.127124,-0.0579 -0.127124,-0.0579 -0.01757,-0.31109 -0.01809,-0.31109 0.424264,-0.092 z m 16.089978,0.82527 0.23978,0.28577 0.23977,0.28629 0.2589,-0.19069 0.2589,-0.19068 h 0.0811 0.0811 l 0.2744,0.34675 0.2744,0.34674 0.24081,-0.0481 0.2403,-0.0481 v 0.11782 0.11782 l -0.43564,0.47697 -0.43563,0.47646 -0.23771,0.12299 -0.23719,0.12299 h -0.28061 -0.2806 l -0.25373,-0.18035 -0.25373,-0.18035 -0.2744,-0.35708 -0.27389,-0.35761 5.2e-4,-0.16071 5.1e-4,-0.16123 0.14935,-0.22634 0.14934,-0.22686 0.2372,-0.18655 z m -16.6486,0.48266 0.211357,0.032 0.211873,0.032 0.190686,0.29352 0.190169,0.29352 v 0.2434 0.24288 l -0.06356,0.0398 -0.06356,0.0393 -0.523482,-0.24546 -0.523482,-0.24495 -0.04703,-0.076 -0.04651,-0.0755 0.08992,-0.2589 0.09043,-0.2589 0.141594,-0.0284 z m 1.143083,0.0331 v 0.64182 0.64182 h -0.08733 -0.08733 l -0.04444,-0.11523 -0.04444,-0.11524 0.02584,-0.49196 0.02584,-0.49145 0.105937,-0.0351 z m 4.233333,3.49643 v 0.0646 0.0646 l -0.31781,0.45475 -0.31781,0.45476 -0.361735,0.27595 -0.361735,0.27595 -0.315226,0.0878 -0.315743,0.0873 -0.338481,5.2e-4 h -0.33848 l -0.519349,-0.24546 -0.518831,-0.24547 -0.06305,-0.1018 -0.06305,-0.10232 0.28577,0.0724 0.285771,0.0729 0.804085,-5.1e-4 h 0.804602 l 0.417029,-0.21136 0.417029,-0.21136 0.408244,-0.39687 z m 12.621964,0.0734 h 0.14987 0.14986 l 0.19068,0.17777 0.19069,0.17725 0.0956,0.22479 0.0961,0.22479 0.0341,2.11667 0.0336,2.11666 -0.0977,1.21337 -0.0977,1.21336 -0.16382,0.53175 -0.16433,0.53175 -0.17725,0.22479 -0.17673,0.22531 -0.24236,0.12506 -0.24236,0.12557 h -0.13385 -0.13384 l -0.045,-0.11782 -0.0455,-0.11782 0.17156,-0.9617 0.17157,-0.9617 0.0868,-0.84697 0.0868,-0.84646 0.0894,-1.60869 0.0894,-1.60869 -0.0553,-0.71985 -0.0548,-0.71934 0.0966,-0.36018 z m -9.540498,0.25425 -0.0801,0.077 -0.08062,0.0775 -0.76171,0.0915 -0.762228,0.092 -0.289388,5.2e-4 -0.289388,5.1e-4 0.04961,-0.0496 0.04961,-0.0496 0.874882,-0.11679 0.874882,-0.11731 0.207222,-0.003 z m 26.551868,25.99273 h 0.16175 0.16174 l 0.21549,0.25579 0.2155,0.25632 -0.046,0.11989 -0.046,0.11989 0.13436,0.64905 0.13436,0.64906 v 0.20257 0.20257 h -0.16382 -0.16329 l -0.13695,-0.65629 -0.13745,-0.65629 -0.0677,-0.12712 -0.0677,-0.12661 -0.26665,-0.0269 -0.26665,-0.0264 v -0.0868 -0.0873 l 0.1695,-0.21549 0.1695,-0.21497 v -0.11576 z m -1.77819,0.59221 h 0.11679 0.11627 l 0.10232,0.1235 0.10232,0.12299 -0.0543,0.0889 -0.0548,0.0884 h -0.10801 l -0.108,-5.1e-4 -0.0563,-0.0563 -0.0563,-0.0563 v -0.15503 z m -3.29023,0.74052 0.094,0.094 0.094,0.0941 0.0501,0.36174 0.0501,0.36173 0.27337,-0.0217 0.27337,-0.0217 0.002,0.10645 0.002,0.10594 0.047,1.03714 0.047,1.03715 h -0.0827 -0.0806 l -0.0558,-0.74053 -0.0563,-0.74104 -0.0486,-0.24701 -0.0486,-0.24701 -0.0584,0.11989 -0.0584,0.1204 0.0486,0.9586 0.047,0.95912 0.14779,0.0822 0.14728,0.0827 v 0.24082 0.24029 l -0.12712,0.0491 -0.12713,0.0486 v 0.12971 0.1297 l -0.0641,-0.007 -0.063,-0.006 -0.339,-0.10439 -0.33848,-0.10439 0.26459,-0.14004 0.26458,-0.13953 0.0439,-0.22996 0.0439,-0.22996 -0.0832,-0.93741 -0.0832,-0.93741 -0.20929,-0.22169 -0.20877,-0.22169 -0.0491,-0.47491 -0.0491,-0.47542 0.14418,-0.0553 z m -1.02785,0.094 0.14831,0.0279 0.14831,0.0279 0.0269,0.28526 0.0274,0.28525 -0.0739,0.0739 -0.0739,0.0739 -0.10129,-0.10129 -0.1018,-0.1018 v -0.28577 z m -0.43305,0.85886 0.0413,0.0672 0.0419,0.0677 0.0264,1.20354 0.0264,1.20355 0.14108,0.0444 0.14056,0.045 0.0527,0.13797 0.0532,0.13798 -0.0667,0.0574 -0.0656,0.0579 -0.21187,-0.0543 -0.21136,-0.0548 -0.0155,-0.25011 -0.016,-0.25012 -0.0873,-0.88883 -0.0873,-0.88935 -0.003,-0.26872 -0.003,-0.26923 -0.14831,-0.0382 -0.14831,-0.0388 0.27026,-0.0103 z m 0.26355,0.0749 0.0847,0.0522 0.0847,0.0522 v 0.0796 0.0801 h -0.0847 -0.0847 v -0.13229 z m -0.25373,3.0241 0.092,0.0915 0.0915,0.092 -0.0279,0.18707 -0.0284,0.18759 -0.0636,-0.0646 -0.0636,-0.0646 v -0.21445 z m 2.37092,0.006 0.0563,0.0558 0.0563,0.0553 h -0.11317 -0.11266 l 0.0563,-0.0553 z m -2.28617,0.84646 0.0563,0.0558 0.0563,0.0553 -0.0563,0.0563 -0.0563,0.0563 v -0.11317 z m 9.94825,-40.78642 -0.59273,0.0558 -0.59273,0.0558 -0.38086,0.0687 -0.38085,0.0682 0.84646,-0.047 0.84646,-0.047 0.12712,-0.077 z m 0.0527,0.74931 -0.11111,0.0444 -0.1111,0.0444 0.16226,0.007 0.16227,0.007 -0.0512,-0.0512 z" - /> - </g> - </svg> - </div> - <div class="content-center ml-2"> - <p class="font-semibold text-center">"Singlestage UI"</p> - <p class="text-sm text-center">"v"{env!("CARGO_PKG_VERSION")}</p> - </div> - </div> - </a> - </SidebarHeader> - <SidebarContent> - <SidebarGroup> - <SidebarGroupLabel>"Getting Started"</SidebarGroupLabel> - <SidebarGroupContent> - <SidebarMenu> - <SidebarMenuItem> - <SidebarMenuButton> - <A href="/introduction"> - {icon!(icondata::LuInfo)} <span>"Introduction"</span> - </A> - </SidebarMenuButton> - </SidebarMenuItem> - <SidebarMenuItem> - <SidebarMenuButton> - <A href="/install"> - {icon!(icondata::LuComputer)} <span>"Install"</span> - </A> - </SidebarMenuButton> - </SidebarMenuItem> - <SidebarMenuItem> - <SidebarMenuButton> - <A href="/theme-provider"> - {icon!(icondata::LuPaintBucket)} - <span>"Theme Provider"</span> - </A> - </SidebarMenuButton> - </SidebarMenuItem> - <SidebarMenuItem> - <SidebarMenuButton> - <A href="/icon-macro"> - {icon!(icondata::LuImage)} <span>"Icon Macro"</span> - </A> - </SidebarMenuButton> - </SidebarMenuItem> - <SidebarMenuItem> - <SidebarMenuButton> - <A href="https://github.com/adoyle0/singlestage-ui"> - {icon!(icondata::LuGithub)} <span>"GitHub"</span> - </A> - </SidebarMenuButton> - </SidebarMenuItem> - <SidebarMenuItem> - <SidebarMenuButton> - <A href="https://docs.rs/singlestage/latest/singlestage/index.html"> - {icon!(icondata::SiDocsdotrs, stroke_width=0)} - <span>"Docs.rs"</span> - </A> - </SidebarMenuButton> - </SidebarMenuItem> - </SidebarMenu> - </SidebarGroupContent> - </SidebarGroup> - - <DebugMenu /> - - <SidebarGroup> - <SidebarGroupLabel>"Components"</SidebarGroupLabel> - <SidebarGroupContent> - <SidebarMenu>{generate_component_links!()}</SidebarMenu> - </SidebarGroupContent> - </SidebarGroup> - </SidebarContent> - <SidebarSeparator /> - <SidebarFooter> - <a class="kofi-btn" href="https://ko-fi.com/K3K41INGJM" target="_blank"> - <Button class="w-full" variant="secondary"> - <img src="/kofi_symbol.svg" /> - "Buy me a coffee" - </Button> - </a> - </SidebarFooter> - </Sidebar> - - <main> - <header class="flex sticky inset-x-0 top-0 z-10 gap-2 items-center border-b bg-(--background) isolate shrink-0"> - <div class="flex gap-2 justify-between items-center px-4 w-full h-14"> - <SidebarTrigger> - <SidebarButton /> - </SidebarTrigger> - <ThemeSwitcher /> - </div> - </header> - <div class="my-8 mx-2 sm:mx-12 max-w-4xl"> - <Outlet /> - </div> - </main> - </SidebarProvider> - } -} diff --git a/docs/src/components/sidebar/mod.rs b/docs/src/components/sidebar/mod.rs new file mode 100644 index 00000000..e249744f --- /dev/null +++ b/docs/src/components/sidebar/mod.rs @@ -0,0 +1,123 @@ +use docs_macro::*; +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AppSidebar(#[prop(optional, into)] side: MaybeProp<String>) -> impl IntoView { + view! { + <Sidebar class="inset-shadow-sm" side> + <SidebarHeader> + <a + href="/" + on:click=move |_| { + if let Some(sheet) = use_context::<SheetContext>() { + sheet.open.set(false) + } + } + > + <div class="flex m-2"> + <div class="flex justify-center items-center max-h-12 rounded-md shadow-sm bg-(--primary) text-(--primary-foreground) min-w-12 max-w-12 min-h-12"> + <svg + class="h-9 fill-current" + viewBox="0 0 44.238907 53.197109" + version="1.1" + xml:space="preserve" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + > + <defs id="defs1" /> + <g id="layer1" transform="translate(-83.28999,-146.33474)"> + <path + id="path643" + d="m 111.70944,146.33474 -1.3503,0.0398 -1.35031,0.0398 -0.50539,0.19534 -0.5054,0.19534 -0.42788,0.43149 -0.42788,0.4315 -0.95188,1.54151 -0.9524,1.54099 -0.0879,0.0543 -0.0879,0.0543 -0.21807,-0.0548 -0.10284,-0.0258 h -5.1e-4 c -0.0255,0.002 -0.0512,4.1e-4 -0.0765,-0.004 -0.001,-1.9e-4 -0.002,-3.2e-4 -0.004,-5.2e-4 -0.002,-2.8e-4 -0.004,-2.2e-4 -0.005,-5.1e-4 -0.002,-1.9e-4 -0.003,-3.9e-4 -0.004,-5.2e-4 0.001,-1e-5 0.004,-1.4e-4 0.006,0 -0.0297,-0.002 -0.006,3.5e-4 -0.046,-0.006 -0.007,-0.002 -0.0139,-0.003 -0.0207,-0.005 -0.003,-7.2e-4 -0.005,-10e-4 -0.008,-0.002 -7.9e-4,-2.2e-4 -0.003,-5e-4 -0.003,-5.2e-4 -0.0105,-0.003 -0.0208,-0.006 -0.031,-0.01 h -5.2e-4 -5.2e-4 c -2.1e-4,-4e-5 -0.001,6e-5 -0.002,0 l -5.1e-4,-5.1e-4 h -5.2e-4 c -0.0126,-0.001 -0.0248,-0.003 -0.0372,-0.006 -0.004,-0.001 -0.0126,-0.003 -0.0196,-0.005 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 c -0.002,-7.1e-4 -0.005,-0.001 -0.007,-0.002 -0.002,-3.6e-4 -0.003,-0.001 -0.005,-0.002 -6.9e-4,-1.6e-4 -0.001,-3.5e-4 -0.002,-5.2e-4 -0.0219,-0.005 -0.0437,-0.0111 -0.0641,-0.0212 v -5.2e-4 h -5.2e-4 c -0.002,-8.6e-4 -0.004,-0.001 -0.006,-0.003 l -5.1e-4,-5.2e-4 -5.2e-4,-5.2e-4 h -5.2e-4 c -0.004,-0.002 -0.009,-0.004 -0.0129,-0.006 -0.004,-0.002 -0.009,-0.005 -0.0129,-0.007 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 l -5.2e-4,-5.2e-4 v -5.1e-4 h -5.2e-4 -5.1e-4 c -8e-4,-4.4e-4 -0.002,-9.2e-4 -0.003,-0.002 -0.001,-7.1e-4 -0.003,-0.002 -0.004,-0.002 -0.007,-0.003 -0.002,-0.001 -0.0269,-0.0134 -0.005,-0.003 -0.0106,-0.006 -0.016,-0.009 -0.002,-0.001 -0.004,-0.002 -0.006,-0.004 h -5.1e-4 l -5.2e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.1e-4 v -5.2e-4 c -0.005,-0.003 -0.002,-0.001 -0.0191,-0.0108 -0.0169,-0.0104 -0.0335,-0.0207 -0.0501,-0.0315 -0.014,-0.009 -0.028,-0.018 -0.0419,-0.0269 -0.007,-0.004 -0.0135,-0.008 -0.0202,-0.0124 -0.003,-0.002 -0.006,-0.003 -0.008,-0.005 v -5.1e-4 c -0.006,-0.004 -0.0127,-0.007 -0.0186,-0.0114 -0.009,-0.006 -0.009,-0.007 -0.0124,-0.009 -8.3e-4,-6.2e-4 -0.002,-5.9e-4 -0.003,-0.002 h -5.1e-4 c -4.3e-4,-3.1e-4 -0.001,-6.8e-4 -0.002,-0.001 h -5.2e-4 v -5.1e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 -5.2e-4 c -0.004,-0.002 -0.008,-0.004 -0.0114,-0.006 h -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 c -5.8e-4,-2.8e-4 -0.001,-7.5e-4 -0.002,-0.001 -0.0159,-0.008 -0.0333,-0.0162 -0.0558,-0.0284 -3.4e-4,-1.8e-4 -6.9e-4,-3.4e-4 -0.001,-5.2e-4 -0.002,-9e-4 -0.005,-0.003 -0.007,-0.004 -0.0108,-0.006 -0.0216,-0.0118 -0.032,-0.0181 -0.002,-0.001 -0.006,-0.003 -0.007,-0.004 -0.0188,-0.0102 0.007,0.004 -0.0238,-0.0129 0,0 -0.001,-9.3e-4 -0.002,-0.001 -0.001,-7.3e-4 -0.002,-9.8e-4 -0.003,-0.002 -0.001,-7.8e-4 -0.002,-0.001 -0.004,-0.002 -0.001,-9.1e-4 -0.002,-0.001 -0.003,-0.002 -0.002,-10e-4 -0.005,-0.003 -0.007,-0.005 -4.1e-4,-1.5e-4 -10e-4,-4.6e-4 -0.002,-5.2e-4 -0.002,-9.3e-4 -0.006,-0.003 -0.007,-0.004 v -5.2e-4 c -0.0101,-0.006 -0.0221,-0.0114 -0.0315,-0.0186 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 l -5.2e-4,-5.2e-4 v -5.1e-4 h -5.1e-4 v -5.2e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 v 5.2e-4 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 v -5.1e-4 c -0.002,-9.2e-4 -0.004,-0.001 -0.004,-0.002 h -5.1e-4 c -2.8e-4,-9e-5 -0.001,-3.5e-4 -0.003,-0.001 -0.0114,-0.004 -0.0226,-0.008 -0.0341,-0.0124 -0.0178,-0.005 -0.0281,-0.009 -0.0444,-0.0145 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 c -0.002,-6.9e-4 -0.006,-0.002 -0.0114,-0.004 -0.003,-9.6e-4 -0.003,-0.001 -0.005,-0.002 0.003,9.7e-4 0.003,10e-4 -0.0139,-0.005 -0.002,-6.7e-4 -0.004,-0.001 -0.006,-0.002 -0.0296,-0.0104 -0.0585,-0.0217 -0.0853,-0.0382 v -5.2e-4 h -5.2e-4 c -0.002,-8.3e-4 -0.004,-0.001 -0.006,-0.002 l -5.2e-4,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.2e-4 -5.2e-4,-5.1e-4 v -5.2e-4 -5.2e-4 c -0.003,-0.002 -0.005,-0.003 -0.008,-0.005 -0.0133,-0.007 -0.0261,-0.0156 -0.0388,-0.0238 -5.8e-4,-2.9e-4 -10e-4,-6.3e-4 -0.002,-0.001 -3.2e-4,-1.7e-4 -7.4e-4,-3.7e-4 -0.001,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 v -5.1e-4 h -5.2e-4 c -0.002,-0.001 -0.005,-0.003 -0.009,-0.005 h -5.2e-4 c -2.2e-4,-1.4e-4 -8.1e-4,-8.8e-4 -10e-4,-10e-4 h -5.2e-4 v -5.1e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 l -5.2e-4,-5.2e-4 c -0.004,-0.002 -0.007,-0.003 -0.008,-0.003 5.8e-4,8e-5 0.002,3.5e-4 0.003,5.2e-4 h 5.2e-4 5.2e-4 c 3.8e-4,-2e-5 0.001,-2e-5 0.002,0 -10e-4,-6.6e-4 -0.005,-0.002 -0.014,-0.006 -0.0263,-0.0133 -0.0407,-0.0223 -0.0517,-0.0289 -0.001,-7.3e-4 -0.004,-0.002 -0.005,-0.003 -3.4e-4,-1.7e-4 -6.9e-4,-3.4e-4 -10e-4,-5.1e-4 -0.004,-0.002 -0.008,-0.005 -0.0103,-0.006 -0.003,-0.002 -0.006,-0.003 -0.009,-0.005 -4.2e-4,-2.4e-4 -6.3e-4,-2.9e-4 -0.001,-5.2e-4 -2e-4,-1.1e-4 -7.9e-4,-3.8e-4 -0.001,-5.1e-4 h -5.1e-4 c -0.002,-0.001 -0.006,-0.004 -0.007,-0.005 v -5.1e-4 h -5.2e-4 -5.1e-4 c -0.01,-0.005 -0.004,-0.002 -0.0181,-0.0109 -0.002,-0.001 -0.004,-0.002 -0.006,-0.004 2.9e-4,2.2e-4 -0.002,-9.2e-4 -0.003,-0.002 l -5.2e-4,-5.2e-4 -5.1e-4,-5.2e-4 h -5.2e-4 l -5.2e-4,-5.1e-4 c -0.002,-0.001 -0.002,-0.001 -0.003,-0.002 -3.5e-4,-2.3e-4 -0.001,-8.9e-4 -0.002,-0.001 l -5.2e-4,-5.2e-4 h -5.1e-4 c -0.012,-0.007 -0.01,-0.006 -0.0233,-0.0145 h -5.1e-4 v -5.2e-4 c -0.003,-0.002 -0.006,-0.003 -0.008,-0.005 v -5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 -10e-4,-5.2e-4 c -5.3e-4,-3.2e-4 -9.8e-4,-6.8e-4 -0.002,-0.001 -8.7e-4,-3.8e-4 -0.002,-7.1e-4 -0.003,-0.001 -9.4e-4,-4.8e-4 -0.002,-6.6e-4 -0.005,-0.002 -0.007,-0.003 -0.005,-0.002 -0.0191,-0.009 -10e-4,-6.9e-4 -0.004,-0.002 -0.006,-0.003 h -5.2e-4 v -5.1e-4 c -0.019,-0.008 -0.019,-0.008 -0.0362,-0.0165 -1.7e-4,-1.1e-4 -8.3e-4,-4e-4 -0.001,-5.2e-4 h -5.2e-4 l -5.2e-4,-5.1e-4 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 v -5.2e-4 h -5.1e-4 c -0.003,-0.002 -0.007,-0.004 -0.0114,-0.006 -4e-4,-2e-4 -6.4e-4,-3.1e-4 -0.001,-5.1e-4 -5.9e-4,-2.9e-4 -4.7e-4,-2.6e-4 -10e-4,-5.2e-4 -0.018,-0.009 -0.0219,-0.0107 -0.032,-0.0165 7e-5,5e-5 -0.002,-0.001 -0.004,-0.002 -0.001,-7.9e-4 -0.003,-0.001 -0.004,-0.002 h -5.2e-4 c -3.7e-4,-2.1e-4 -7.3e-4,-3.2e-4 -0.001,-5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.1e-4 c -0.002,-0.001 -0.003,-0.002 -0.004,-0.003 -0.0212,-0.0115 -0.0267,-0.0154 -0.0429,-0.0253 -0.003,-0.002 -0.007,-0.005 -0.01,-0.007 -0.007,-0.005 -0.0139,-0.01 -0.0207,-0.0145 -0.0111,-0.008 -0.023,-0.0161 -0.0331,-0.0253 -0.006,-0.004 -0.0125,-0.008 -0.0181,-0.0119 -0.001,-8.4e-4 -0.004,-0.003 -0.005,-0.004 -0.009,-0.005 -0.008,-0.005 -0.0222,-0.0129 l -5.1e-4,-5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.2e-4 h -5.2e-4 c -0.002,-9.4e-4 -0.005,-0.002 -0.008,-0.003 -0.005,-0.002 -0.009,-0.004 -0.014,-0.007 l -5.2e-4,-5.2e-4 h -5.2e-4 c -0.002,-6.2e-4 -0.004,-0.002 -0.005,-0.002 -4e-4,-2.1e-4 -8e-4,-3.9e-4 -10e-4,-5.1e-4 h -5.2e-4 c -0.003,-10e-4 -0.002,-6.8e-4 -0.0176,-0.008 -0.002,-0.001 -0.004,-0.002 -0.007,-0.004 -7.6e-4,-3.3e-4 -0.002,-6.8e-4 -0.003,-0.001 -8.8e-4,-2.9e-4 -0.002,-6.9e-4 -0.003,-0.001 -0.003,-0.001 -0.007,-0.003 -0.009,-0.004 h -5.1e-4 v -5.2e-4 c -0.0113,-0.004 -0.0224,-0.009 -0.0331,-0.0145 0.007,0.003 0.0267,0.0121 0.0202,0.009 -0.0116,-0.006 -0.0244,-0.0104 -0.0357,-0.0171 -0.004,-0.002 -0.007,-0.004 -0.0109,-0.007 l -5.2e-4,-5.1e-4 h -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 c -2.2e-4,-1.8e-4 -8.3e-4,-8.4e-4 -0.001,-10e-4 l -5.1e-4,-5.2e-4 v -5.2e-4 h -5.2e-4 c -0.003,-0.001 -0.005,-0.003 -0.008,-0.005 -8.9e-4,-4.7e-4 -0.002,-0.001 -0.003,-0.002 l -0.002,-10e-4 -0.31833,-0.12868 -0.12299,-0.0501 c -0.01,-0.003 -0.0192,-0.006 -0.0284,-0.01 -0.005,-0.001 -0.0102,-0.003 -0.0202,-0.006 h -5.1e-4 l -5.2e-4,-5.2e-4 -5.2e-4,-5.2e-4 h -5.1e-4 c -10e-4,-3.6e-4 -0.002,-6.8e-4 -0.003,-10e-4 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 -5.2e-4,-5.1e-4 h -5.2e-4 -5.1e-4 c -0.0128,-0.002 -0.0258,-0.005 -0.0382,-0.009 -0.0194,-0.007 -0.0302,-0.008 -0.0651,-0.0269 -0.004,-0.001 -0.007,-0.003 -0.0109,-0.004 -0.007,-0.002 -0.0141,-0.004 -0.0212,-0.007 0.005,0.002 0.0132,0.004 0.01,0.003 -0.004,-10e-4 -0.008,-0.002 -0.0119,-0.004 h -5.2e-4 -5.2e-4 c -0.002,-6.1e-4 -0.004,-0.001 -0.006,-0.002 -0.002,-7e-4 -0.009,-0.003 -0.016,-0.006 -0.004,-9.2e-4 -0.006,-0.002 -0.007,-0.002 h -5.2e-4 c -0.001,-2.2e-4 -0.003,-2.9e-4 -0.004,-5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 v -5.2e-4 c -0.0258,-0.007 -0.0352,-0.0104 -0.061,-0.0202 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.2e-4 -5.1e-4 c -0.009,-0.002 -0.018,-0.005 -0.0269,-0.008 -7.7e-4,-2.6e-4 -8.3e-4,-2.8e-4 -0.002,-5.2e-4 -5.1e-4,-1.7e-4 -10e-4,-3.4e-4 -0.002,-5.1e-4 -0.015,-0.005 -0.0179,-0.006 -0.0346,-0.0129 h -5.2e-4 v -5.2e-4 c -0.003,-10e-4 -0.005,-0.002 -0.008,-0.003 -0.01,-0.003 -0.0201,-0.007 -0.03,-0.0103 1.7e-4,8e-5 -10e-4,-3.1e-4 -0.002,-5.1e-4 -0.001,-3.2e-4 -0.002,-7.4e-4 -0.003,-0.001 -4.7e-4,-1.5e-4 -0.001,-3.6e-4 -0.002,-5.1e-4 h -5.1e-4 c -0.014,-0.004 -0.018,-0.006 -0.0336,-0.0114 -0.0148,-0.006 -0.0136,-0.005 -0.0248,-0.0103 -0.003,-0.001 -0.005,-0.002 -0.008,-0.004 -0.003,-0.001 -0.005,-0.002 -0.007,-0.003 -8.3e-4,-3.8e-4 -0.002,-7e-4 -0.003,-0.001 v -5.1e-4 h -5.2e-4 c -0.01,-0.004 -0.0191,-0.008 -0.0269,-0.0109 h -5.1e-4 v -5.1e-4 h -5.2e-4 -5.2e-4 c -0.002,-6.3e-4 -0.006,-0.002 -0.0108,-0.004 -0.004,-0.001 -0.008,-0.003 -0.0114,-0.005 l -5.2e-4,-5.2e-4 h -5.1e-4 c -0.004,-0.001 -0.007,-0.002 -0.0109,-0.004 -0.007,-0.003 -0.0105,-0.004 -0.0186,-0.008 h -5.1e-4 c -0.001,-5.3e-4 -0.004,-0.001 -0.006,-0.002 h -5.2e-4 -5.1e-4 v -5.2e-4 c -0.003,-0.001 -0.004,-0.002 -0.006,-0.003 -5e-4,-1.9e-4 -0.001,-3.2e-4 -0.002,-5.1e-4 -2.3e-4,-9e-5 -8.6e-4,-4.5e-4 -10e-4,-5.2e-4 -0.003,-0.001 -0.006,-0.002 -0.009,-0.004 -5.1e-4,-2e-4 -0.001,-3.1e-4 -0.002,-5.1e-4 h -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 c -0.002,-4.5e-4 -0.006,-0.002 -0.009,-0.003 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 v -5.1e-4 h -5.1e-4 c -0.003,-8.6e-4 -0.005,-0.001 -0.006,-0.002 h -5.1e-4 c -0.004,-10e-4 -0.005,-0.002 -0.0129,-0.004 -6.8e-4,-2.2e-4 -0.0118,-0.004 -0.0202,-0.007 -0.001,-4.4e-4 -0.003,-6.6e-4 -0.004,-0.001 h -5.2e-4 v -5.1e-4 h -5.2e-4 c -0.001,-3.5e-4 -0.004,-0.001 -0.004,-0.002 -0.0172,-0.005 -0.0267,-0.008 -0.045,-0.014 -0.0254,-0.009 -0.0393,-0.0149 -0.0522,-0.0202 -0.0169,-0.006 -0.0326,-0.0125 -0.0476,-0.0176 -0.0152,-0.006 -0.0302,-0.0106 -0.0455,-0.016 -0.004,-0.001 -0.008,-0.003 -0.0124,-0.004 -10e-4,-3.5e-4 -0.003,-0.001 -0.005,-0.002 -10e-4,-4.4e-4 -0.002,-7.7e-4 -0.003,-0.001 l -5.2e-4,-5.1e-4 c -0.006,-0.002 -0.0127,-0.004 -0.0191,-0.006 -0.01,-0.003 -0.0209,-0.007 -0.032,-0.0109 -0.002,-6e-4 -0.003,-9.4e-4 -0.005,-0.002 l -5.2e-4,-5.1e-4 c -5.3e-4,-1.9e-4 -10e-4,-3.4e-4 -0.002,-5.2e-4 -0.0146,-0.005 -0.0215,-0.007 -0.0367,-0.0129 -0.005,-0.002 -0.0103,-0.005 -0.0155,-0.007 h -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 -5.2e-4 -5.1e-4 v -5.2e-4 -5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 l -5.2e-4,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 -5.2e-4 -5.1e-4 l -5.2e-4,-5.2e-4 -5.2e-4,-5.2e-4 h -5.1e-4 c -0.002,-9.4e-4 -0.005,-0.002 -0.009,-0.004 -0.002,-8e-4 -0.006,-0.002 -0.0114,-0.004 0.002,5.8e-4 0.005,0.001 0.009,0.002 -0.004,-0.001 -0.005,-0.001 -0.007,-0.002 h -5.2e-4 c -7e-4,-2e-4 -0.002,-3.5e-4 -0.003,-5.1e-4 h -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 c -6.4e-4,-1.8e-4 -7.5e-4,-4.1e-4 -10e-4,-5.2e-4 -0.001,-3.7e-4 -0.004,-10e-4 -0.005,-0.002 -0.002,-6.3e-4 -0.003,-6.4e-4 -0.008,-0.002 -6.8e-4,-2.5e-4 -0.002,-7.9e-4 -0.003,-0.001 -0.003,-9.3e-4 -0.006,-0.002 -0.0108,-0.004 -0.005,-0.001 -0.009,-0.002 -0.014,-0.004 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 c -3.7e-4,1e-5 -0.001,3e-5 -0.002,0 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 v -5.1e-4 -5.2e-4 c -0.0217,-0.004 -0.0435,-0.008 -0.0651,-0.0124 -0.0169,-0.004 -0.0362,-0.008 -0.0517,-0.0124 -0.0149,-0.004 -0.0298,-0.008 -0.0444,-0.0124 -2.1e-4,-8e-5 -0.001,-4.3e-4 -0.002,-5.1e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 c -0.0193,-0.006 -0.0306,-0.01 -0.0455,-0.0155 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 c -2.7e-4,-9e-5 -7.8e-4,8e-5 -10e-4,0 v -5.1e-4 h -5.2e-4 v -5.2e-4 h -5.1e-4 -5.2e-4 c -0.002,-6.1e-4 -0.004,-0.001 -0.006,-0.002 h -5.2e-4 c -9.4e-4,-1.2e-4 -0.003,-4.1e-4 -0.004,-5.1e-4 h -5.1e-4 -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 v -5.2e-4 -5.1e-4 c -0.011,-0.003 -0.0194,-0.006 -0.0305,-0.009 -0.002,-7.9e-4 -0.004,-0.001 -0.007,-0.002 h -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 -5.2e-4 c -0.007,-0.002 -0.0134,-0.003 -0.015,-0.004 h -5.1e-4 v -5.2e-4 h -5.2e-4 c -8.1e-4,-1.7e-4 -0.005,-0.002 -0.009,-0.003 -4.4e-4,-1.4e-4 -0.002,-3.5e-4 -0.002,-5.2e-4 -0.007,-0.002 -0.0131,-0.003 -0.017,-0.005 -0.0199,-0.006 -0.0375,-0.0108 -0.0553,-0.0176 -0.013,-0.005 -0.0154,-0.007 -0.0202,-0.009 7.7e-4,2.9e-4 -9e-5,-1.7e-4 -0.005,-0.002 -9.2e-4,-3.4e-4 -0.001,-3.5e-4 -0.002,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.2e-4 c -0.002,-6.5e-4 -0.008,-0.003 -0.0134,-0.005 -0.002,-7.8e-4 -0.005,-0.002 -0.006,-0.002 h -5.1e-4 v -5.1e-4 h -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 -5.2e-4 c -6.8e-4,-1.3e-4 -0.001,-3.9e-4 -0.002,-5.2e-4 h -5.2e-4 c -0.002,-4e-4 -0.004,-0.001 -0.006,-0.002 -0.002,-2.4e-4 -0.004,-7.8e-4 -0.006,-10e-4 h -5.1e-4 -5.2e-4 -5.2e-4 -5.1e-4 v -5.2e-4 h -5.2e-4 c -0.009,-0.001 -0.0112,-0.001 -0.0217,-0.004 -0.003,-6.3e-4 -0.006,-0.001 -0.009,-0.002 -8.8e-4,-2.1e-4 -0.003,-10e-4 -0.003,-10e-4 -10e-4,-2.4e-4 -0.003,-2.8e-4 -0.004,-5.2e-4 -0.0122,-0.002 -0.0103,-0.002 -0.0212,-0.004 h -5.1e-4 v -5.2e-4 h -5.2e-4 -5.2e-4 l -5.2e-4,-5.1e-4 h -5.1e-4 c -0.002,-1.7e-4 -0.005,-7.5e-4 -0.007,-10e-4 h -5.1e-4 c -0.0165,-0.002 -0.0331,-0.006 -0.0491,-0.0103 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 c -0.0141,-0.002 -0.028,-0.003 -0.0418,-0.006 -0.0139,-0.003 -0.0184,-0.004 -0.0284,-0.006 -3.1e-4,-1.3e-4 -0.004,-7.4e-4 -0.008,-0.002 -5.7e-4,-1.2e-4 -4.2e-4,1.3e-4 -0.001,0 -0.008,-0.002 -0.0199,-0.004 -0.0243,-0.005 -0.004,-6.5e-4 -0.008,-0.002 -0.0114,-0.003 -2.9e-4,-1e-5 -7.8e-4,4e-5 -0.001,0 -4.9e-4,-1.1e-4 -0.002,-4.5e-4 -0.002,-5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 -5.1e-4 v -5.1e-4 h -5.2e-4 c -0.001,-1.8e-4 -0.002,-3.4e-4 -0.004,-5.2e-4 h -5.1e-4 c -2.3e-4,1e-5 -0.001,0 -0.002,0 h -5.2e-4 c -0.0115,-0.002 -0.0153,-0.002 -0.0269,-0.005 -0.004,-7.7e-4 -0.008,-0.002 -0.0114,-0.003 -8.3e-4,-1.7e-4 -0.002,-3.6e-4 -0.003,-5.2e-4 h -5.1e-4 c -9.4e-4,-2e-4 -0.003,-2.6e-4 -0.004,-5.1e-4 h -5.1e-4 c -0.002,-4.2e-4 -0.004,-0.001 -0.006,-0.002 -0.001,-3.2e-4 -0.003,-7e-4 -0.004,-10e-4 -0.004,-6.8e-4 -0.006,-0.001 -0.01,-0.002 -0.005,-9e-4 -0.01,-0.002 -0.0119,-0.002 0.001,1.6e-4 0.004,2.5e-4 0.006,5.1e-4 -0.002,-2.8e-4 -0.005,-7.2e-4 -0.007,-0.001 h -5.1e-4 -5.2e-4 -5.2e-4 c -0.0198,-0.003 -0.0397,-0.007 -0.0589,-0.0129 -2.1e-4,-4e-5 -0.001,3e-5 -0.002,0 v -5.2e-4 c -10e-4,-2.2e-4 -0.003,-6.6e-4 -0.004,-0.001 -7.6e-4,-2.1e-4 -0.001,-3e-4 -0.002,-5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.1e-4 c -0.002,-4.9e-4 -0.005,-10e-4 -0.0108,-0.003 -0.0112,-0.003 -0.0291,-0.006 -0.045,-0.0114 h -5.1e-4 -5.2e-4 c -0.0189,-0.005 -0.03,-0.009 -0.0377,-0.0108 -0.003,-8.5e-4 -0.005,-0.001 -0.016,-0.005 -0.004,-0.001 -0.009,-0.003 -0.0129,-0.004 0.003,8.2e-4 0.006,0.001 0.008,0.002 -0.0202,-0.006 -0.0398,-0.013 -0.0589,-0.0222 -0.002,-7.9e-4 -0.005,-0.003 -0.007,-0.004 -0.001,-5e-4 -0.002,-0.001 -0.003,-0.002 -0.001,-5e-4 -0.003,-0.001 -0.005,-0.002 -3.6e-4,-1.7e-4 -6.8e-4,-3.5e-4 -10e-4,-5.2e-4 -3.6e-4,-1.6e-4 -6.7e-4,-3.5e-4 -0.001,-5.1e-4 -1.5e-4,-6e-5 -6.5e-4,-3.1e-4 -0.001,-5.2e-4 h -5.1e-4 l -5.2e-4,-5.2e-4 h -5.2e-4 c -3.3e-4,-1.5e-4 -6.6e-4,-3.4e-4 -0.001,-5.1e-4 v -5.2e-4 h -5.2e-4 c -0.003,-0.001 -0.007,-0.002 -0.0109,-0.004 -7.3e-4,-2.9e-4 -10e-4,-4.2e-4 -0.002,-5.1e-4 v -5.2e-4 h -5.2e-4 c -0.004,-0.002 -0.007,-0.003 -0.008,-0.003 l -5.2e-4,-5.2e-4 h -5.2e-4 c -0.001,-3.2e-4 -0.002,-6.4e-4 -0.003,-10e-4 h -5.1e-4 l -5.2e-4,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.2e-4 -5.1e-4 c -8.2e-4,-3.1e-4 -0.002,-6.7e-4 -0.003,-0.001 l -5.1e-4,-5.2e-4 h -5.2e-4 c -0.004,-0.001 -0.008,-0.003 -0.0119,-0.004 v -5.2e-4 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 c -0.003,-4.7e-4 -0.006,-0.001 -0.009,-0.002 -0.005,-10e-4 -0.0111,-0.003 -0.0165,-0.005 -0.002,-4e-4 -0.008,-0.002 -0.0145,-0.004 h -5.2e-4 c -2e-4,-2e-5 -10e-4,-4.7e-4 -0.002,-5.1e-4 h -5.2e-4 c -0.003,-8.5e-4 -0.006,-0.002 -0.008,-0.003 h -5.2e-4 c -0.0114,-0.001 -0.0229,-0.004 -0.0341,-0.007 -5e-4,-1.3e-4 -0.001,7e-5 -0.002,0 l -5.1e-4,-5.1e-4 h -5.2e-4 c -0.0168,-0.003 -0.0335,-0.006 -0.0496,-0.0119 -0.002,-6.3e-4 -0.003,-0.001 -0.005,-0.002 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 c -4.9e-4,-1.4e-4 -0.001,-3.7e-4 -0.002,-5.2e-4 -0.004,-0.001 -0.007,-0.002 -0.0114,-0.004 -0.002,-5.4e-4 -0.003,-0.001 -0.005,-0.002 -5e-4,-1.9e-4 -10e-4,-3.3e-4 -0.002,-5.2e-4 -5e-4,-1.9e-4 -0.001,-3.2e-4 -0.002,-5.2e-4 -7.6e-4,-2.1e-4 -0.001,-2.9e-4 -0.002,-5.1e-4 -0.0107,-0.003 -0.0213,-0.007 -0.0315,-0.0109 0.009,0.004 0.0143,0.006 -0.0202,-0.009 -0.002,-9.9e-4 -0.004,-0.003 -0.007,-0.004 -3.4e-4,-1.6e-4 -7e-4,-3.5e-4 -0.001,-5.1e-4 h -5.1e-4 c -0.0167,-0.001 -0.0333,-0.004 -0.0496,-0.008 -10e-4,-3.1e-4 -0.002,-7.1e-4 -0.004,-0.001 -0.001,-2.2e-4 -0.002,-2.7e-4 -0.003,-5.1e-4 -0.002,-3.7e-4 -0.003,-0.001 -0.005,-0.002 -3e-4,-9e-5 -6.5e-4,-4e-4 -10e-4,-5.2e-4 h -5.2e-4 c -0.003,-8.3e-4 -0.006,-0.002 -0.009,-0.003 -6.8e-4,-2.1e-4 -10e-4,-3.2e-4 -0.002,-5.2e-4 -0.002,-4.5e-4 -0.003,-6e-4 -0.005,-10e-4 -8.7e-4,-1.3e-4 -0.002,-3.9e-4 -0.003,-5.2e-4 -6.1e-4,-5e-5 -0.002,-4.2e-4 -0.003,-5.2e-4 -0.003,-3.1e-4 -0.006,-7.2e-4 -0.009,-0.001 h -5.2e-4 c -0.0108,-10e-4 -0.0219,-0.003 -0.0326,-0.005 h -5.2e-4 c -2.2e-4,-5e-5 -0.001,-4.7e-4 -0.002,-5.2e-4 h -5.2e-4 c -0.0113,-0.001 -0.0225,-0.004 -0.0336,-0.005 -3.2e-4,-8e-5 -6.7e-4,-4.6e-4 -0.001,-5.2e-4 -0.005,-8e-4 -0.0136,5.9e-4 -0.0181,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 v -5.2e-4 l -5.2e-4,-5.1e-4 v -5.2e-4 -5.2e-4 -5.1e-4 c -0.0141,-0.003 -0.0278,-0.007 -0.0413,-0.0124 -0.004,-0.001 -0.006,-0.002 -0.008,-0.003 -8.6e-4,-2.8e-4 -0.0029,-0.001 -0.0041,-0.002 -9.04e-4,-1.7e-4 -0.0018,-3.2e-4 -0.0026,-5.2e-4 h -5.17e-4 v -5.1e-4 h -5.17e-4 c -0.0014,-5.1e-4 -0.0026,-8.3e-4 -0.0047,-0.002 h -5.17e-4 c -7.97e-4,-2.8e-4 -0.0017,-7.2e-4 -0.0026,-0.001 -0.0037,-9.1e-4 -0.0072,-0.002 -0.01085,-0.003 -0.0042,-0.001 -0.007,-0.002 -0.01033,-0.004 h -5.17e-4 c -8.85e-4,-3.9e-4 -0.0035,-0.001 -0.0052,-0.002 -5.97e-4,-1.7e-4 -9.57e-4,-3.4e-4 -0.0016,-5.1e-4 h -5.16e-4 -5.17e-4 c -8.58e-4,-3e-4 -7.57e-4,-2.5e-4 -0.0015,-5.2e-4 l -5.17e-4,-5.2e-4 h -5.17e-4 -5.17e-4 -5.16e-4 -5.17e-4 v -5.1e-4 h -5.17e-4 c -1.95e-4,-6e-5 -7.04e-4,-4.1e-4 -0.001,-5.2e-4 h -5.17e-4 -5.17e-4 c -0.0045,1.5e-4 -0.0089,5.2e-4 -0.01344,5.2e-4 -0.02156,0.002 -0.04278,-0.003 -0.06408,-0.004 -0.003,2e-5 -0.0058,-4.3e-4 -0.0088,-5.1e-4 -0.0088,-2e-4 -0.01756,5e-5 -0.02635,0 -0.0022,-1e-5 -0.0045,1e-5 -0.0067,0 -0.0047,5.9e-4 -0.0097,6.1e-4 -0.01447,0.001 h -5.17e-4 l -5.16e-4,5.2e-4 h -5.17e-4 -5.17e-4 -5.17e-4 -5.17e-4 -5.16e-4 c -0.002,1.9e-4 -0.0037,9.3e-4 -0.0057,0.001 -0.0092,0.002 -0.01815,0.003 -0.02739,0.004 -0.01904,0.003 -0.03808,0.006 -0.05736,0.007 -6.56e-4,4e-5 -0.0014,-5e-5 -0.0021,0 h -5.17e-4 -5.17e-4 c -0.0018,2.8e-4 -0.0038,8.4e-4 -0.0057,0.001 -0.0094,0.001 -0.0088,0.001 -0.01809,0.002 -0.0016,1.3e-4 -0.007,6.7e-4 -0.01033,10e-4 -0.0039,4.7e-4 -0.0071,0.001 -0.0098,0.002 h -5.16e-4 -5.17e-4 -5.17e-4 c -0.01683,0.002 -0.01204,0.001 -0.03462,0.003 -0.0151,3.7e-4 -0.03038,2.6e-4 -0.04547,0 -0.0099,-1.6e-4 -0.01956,-4.7e-4 -0.02946,-5.1e-4 -0.0022,-1e-4 -0.0059,-2e-5 -0.0098,0 -4.3e-4,0 -6.68e-4,-1e-5 -10e-4,0 -0.0097,0.001 -0.01967,0.002 -0.02946,0.003 -0.0051,3.5e-4 -0.0075,3.4e-4 -0.01033,5.1e-4 h -5.17e-4 -5.16e-4 -5.17e-4 -5.17e-4 -5.17e-4 l -5.16e-4,5.2e-4 c -4.42e-4,2e-5 -0.0011,-2e-5 -0.0016,0 -0.01062,0.002 -0.02134,0.003 -0.03204,0.004 -0.03273,0.009 -0.06879,0.0144 -0.108521,0.0171 l -5.16e-4,5.2e-4 c -0.0058,0.004 -0.0116,0.009 -0.01757,0.0124 -0.0031,0.002 -0.0061,0.004 -0.0093,0.006 h -5.17e-4 l -5.17e-4,5.2e-4 -5.16e-4,5.2e-4 c -0.0018,10e-4 -0.004,0.002 -0.0057,0.003 -0.01016,0.007 -0.02137,0.0129 -0.02997,0.017 -0.0022,0.002 -0.0045,0.003 -0.0067,0.005 -0.01927,0.0103 -0.02753,0.015 -0.03101,0.017 -0.0053,0.003 -0.0105,0.006 -0.01602,0.009 -0.0041,0.002 -0.0079,0.003 -0.01137,0.005 h -5.17e-4 -5.17e-4 l -5.17e-4,0.001 h -5.17e-4 -5.16e-4 -5.17e-4 -5.17e-4 v 5.1e-4 h -5.17e-4 c -3e-4,1.3e-4 -0.0012,-1.3e-4 -0.0015,0 l -5.17e-4,0.001 h -5.16e-4 v 5.1e-4 h -5.17e-4 -5.17e-4 -5.17e-4 -5.16e-4 l -5.17e-4,10e-4 h -5.17e-4 c -0.0043,0.002 -0.0086,0.004 -0.01292,0.006 -0.0085,0.003 -0.01286,0.005 -0.01705,0.006 h -5.17e-4 l -5.17e-4,5.2e-4 -0.134875,0.0909 -0.207223,0.13849 -0.154512,0.12713 -0.154513,0.12712 -0.257865,0.67748 -0.257866,0.67696 -0.835608,2.40192 -0.835608,2.40192 -0.187585,0.19947 -0.187069,0.19896 -0.262516,0.0801 -0.263033,0.0796 -0.685746,0.10542 -0.686263,0.1049 -0.482658,-0.19999 -0.482658,-0.19999 -1.645378,0.13746 -1.645377,0.13798 -0.487826,0.27802 -0.487825,0.2775 -0.446485,0.42013 -0.445967,0.42065 -0.245463,0.43046 -0.245463,0.43047 -0.349333,0.88883 -0.349849,0.88884 -0.077,0.18603 -0.07648,0.18604 -0.216525,0.14211 -0.216524,0.14211 -1.30793,0.14521 -1.307414,0.14572 -0.130742,0.10852 -0.130741,0.10801 v 0.0827 0.0822 l 0.158647,0.20102 0.15813,0.20154 0.0093,0.42426 0.0098,0.42375 -0.178801,0.16691 -0.1788,0.1664 0.05116,0.13281 0.05116,0.13281 0.192753,0.0667 0.192753,0.0667 1.312065,-0.0517 1.312581,-0.0512 0.14056,0.0672 0.141077,0.0672 0.269234,0.80771 0.268718,0.8077 0.450618,0.69608 0.450619,0.69608 0.443383,0.53847 0.443384,0.53847 0.433049,0.27698 0.433048,0.27699 0.418063,0.14211 0.418062,0.14159 0.512113,0.0481 0.512114,0.0476 1.076937,-0.13901 1.076936,-0.13901 0.404627,-0.17364 0.404626,-0.17311 0.274919,-0.21033 0.275435,-0.2098 v -0.13436 -0.13488 l -0.486792,0.0548 -0.486792,0.0548 -0.550354,0.0475 -0.550354,0.047 0.127124,-0.0801 0.126608,-0.0796 0.889351,-0.10129 0.888835,-0.10129 0.42323,-0.11834 0.42323,-0.11782 0.749825,-0.59531 0.749308,-0.59531 0.817004,-0.46147 0.817005,-0.46147 0.973589,-0.46561 0.97358,-0.4656 0.71934,-0.20568 0.71985,-0.20567 0.27078,-0.0584 0.27027,-0.0579 0.22945,0.36535 0.22944,0.36536 0.31626,0.36121 0.31626,0.36122 0.21291,0.57464 0.2129,0.57413 0.14728,0.8816 0.1478,0.8816 -0.0522,1.82366 -0.0512,1.82366 -0.13384,1.16324 -0.13332,1.16375 -0.16485,0.64337 -0.16433,0.64337 -0.27492,0.56379 -0.27492,0.56379 -0.70022,0.70435 -0.70021,0.70435 v 0.11938 0.11885 l 0.19068,0.12248 0.19017,0.12195 0.52555,0.1018 0.52503,0.10129 0.78704,-0.0398 0.78703,-0.0403 0.25425,-0.11524 0.25373,-0.11575 -0.53072,-0.045 -0.5302,-0.045 -0.005,-0.0687 -0.005,-0.0682 0.59325,-0.74311 0.59324,-0.74311 0.56328,-0.41341 0.56327,-0.41341 0.19689,-0.34158 0.19637,-0.34158 0.13281,-0.63459 0.13229,-0.6351 0.0868,-0.8046 0.0868,-0.80409 0.0832,-1.27021 0.0832,-1.26969 0.006,-0.6351 0.006,-0.6351 0.10129,-0.29611 0.10077,-0.29662 0.18552,-0.0264 0.185,-0.0269 0.14366,0.21911 0.14366,0.21963 -0.0579,0.99218 -0.0579,0.99271 -0.12712,1.43919 -0.12712,1.43918 -0.12661,0.97359 -0.12661,0.97358 -0.0434,0.21188 -0.0439,0.21187 0.0889,-0.13901 0.0894,-0.13901 0.13384,-0.57723 0.13385,-0.57722 0.16174,-1.56993 0.16175,-1.56993 0.0496,-1.14308 0.0496,-1.14257 0.2036,-0.76223 0.20413,-0.76171 0.17156,-1.00614 0.17157,-1.00562 v -0.31729 -0.3173 l 0.10542,-0.0842 0.10593,-0.0832 0.43047,-0.0548 0.43046,-0.0548 0.13126,0.14882 0.13178,0.14883 0.20929,0.42427 0.2098,0.42323 0.21963,0.67696 0.22014,0.67748 0.20981,0.84697 0.2098,0.84646 0.13126,0.71985 0.13074,0.71882 0.12868,1.01596 0.12815,1.01648 0.20929,2.49804 0.20878,2.497 0.0517,0.25787 0.0522,0.25631 -1.21388,0.005 -1.21337,0.005 -0.84646,0.1912 -0.84697,0.19121 -0.29508,0.12247 -0.29507,0.12247 -0.19172,0.18035 -0.19172,0.17984 0.003,0.1757 0.003,0.1757 0.18759,0.22841 0.18707,0.22892 0.31522,0.13126 0.31471,0.13126 0.87024,-0.006 0.87074,-0.006 0.76171,-0.0682 0.76223,-0.0687 0.061,0.0351 0.0594,0.0346 -0.0904,0.19947 -0.091,0.19895 0.26097,1.85105 0.26045,1.85053 0.17053,1.31258 0.17053,1.31207 0.37931,3.43597 0.37982,3.43544 0.046,0.12041 0.0465,0.12041 0.24856,0.16691 0.24857,0.16692 0.0496,0.1974 0.0496,0.1974 0.29352,0.17312 0.29352,0.17312 v 0.0579 0.0579 l -0.16691,0.14315 -0.16692,0.14262 -0.0568,0.22583 -0.0563,0.22582 0.0853,1.13068 0.0858,1.13017 0.10387,0.12506 0.10387,0.12505 -0.0987,0.29869 -0.0987,0.29869 0.10336,0.48783 0.10283,0.48782 0.10904,0.1788 0.10904,0.1788 0.28112,0.0667 0.2806,0.0672 0.15089,0.0181 0.1509,0.0176 0.0563,0.0563 0.0553,0.0558 0.51315,-0.0491 0.51418,-0.0501 0.21187,-0.0517 0.21136,-0.0512 0.0801,0.0191 0.0801,0.0191 0.36586,-0.13953 0.36587,-0.13953 0.0718,-0.14107 0.0734,-0.14056 0.0563,-0.52607 0.0579,-0.52607 -0.13746,-0.26561 -0.13746,-0.26614 0.10129,-0.24494 0.1018,-0.24443 -0.0584,-0.74156 -0.0584,-0.74156 -0.0868,-0.52968 -0.0889,-0.5302 -0.24856,-0.1943 -0.24857,-0.19431 0.32091,-0.20722 0.32091,-0.20722 0.0269,-0.22738 0.0264,-0.22789 0.3638,-0.17622 0.36432,-0.17621 0.14315,0.0553 0.14314,0.0548 -0.0543,0.24649 -0.0548,0.2465 0.18759,0.9493 0.18707,0.94929 0.0713,0.1385 0.0718,0.13849 0.3421,0.17777 0.3421,0.17828 0.31677,0.0506 0.31678,0.0501 0.54312,-0.0982 0.54312,-0.0987 0.37879,-0.17777 0.3793,-0.17828 0.2837,-0.28422 0.28423,-0.2837 v -0.23978 -0.23926 l -0.17674,-0.87954 -0.17725,-0.87953 -0.12506,-0.19637 -0.12505,-0.19585 0.10025,-0.12093 0.0997,-0.1204 -0.0899,-0.5302 -0.0899,-0.52969 -0.0434,-1.39681 -0.0434,-1.39733 -0.13178,-1.26969 -0.13126,-1.27021 -0.17415,-1.22783 -0.17415,-1.22732 -0.29249,-1.55391 -0.293,-1.55442 -0.12713,-0.24598 -0.12712,-0.24598 -0.16691,-0.10439 -0.1664,-0.10387 v -0.0672 -0.0656 l 0.21135,-0.0806 0.21188,-0.0806 v -0.17001 -0.17002 l -0.10594,-0.39532 -0.10542,-0.39481 -0.31471,-0.25322 -0.31471,-0.25269 -0.12971,-0.43305 -0.1297,-0.43253 -0.76843,-3.81011 -0.76843,-3.8101 0.032,-0.84646 0.0315,-0.84646 0.17829,-0.339 0.17828,-0.33848 0.24029,-0.22324 0.24082,-0.22324 0.25424,-0.12713 0.25374,-0.12764 1.39681,-0.44803 1.39733,-0.44752 0.31729,-0.31368 0.31781,-0.31367 0.0517,-0.25787 0.0517,-0.25786 0.20205,-0.19017 0.20257,-0.18965 v -0.20258 -0.20257 l -0.0796,-0.14883 -0.0796,-0.14882 -0.32298,-0.22015 -0.32246,-0.22065 -0.52348,-0.10077 -0.52348,-0.10077 -1.04283,0.14159 -1.04283,0.14108 -0.19017,0.047 -0.19069,0.0465 v -0.12144 -0.12144 l 0.21963,-0.20515 0.21962,-0.20516 0.12144,-0.23306 0.12092,-0.23254 h 0.13074 0.13023 l 0.2775,-0.21188 0.2775,-0.21135 h 0.0889 0.0889 l 0.0548,0.14262 0.0553,0.14315 -0.13229,0.0739 -0.13229,0.0739 0.0491,0.0801 0.0491,0.0796 0.31265,-5.1e-4 0.31264,-5.2e-4 0.97358,-0.13384 0.97359,-0.13436 0.42323,-0.032 0.42323,-0.0326 0.50798,-0.14572 0.50797,-0.14573 0.19431,-0.12247 0.19378,-0.12248 0.20206,-0.34778 0.20154,-0.3483 0.0589,-0.31833 0.0584,-0.31832 -0.1049,-0.12609 -0.10439,-0.12609 0.10232,-0.12299 0.10232,-0.12351 -0.0434,-0.0703 -0.0429,-0.0703 -0.25114,-0.0191 -0.25115,-0.0186 -0.0501,0.13022 -0.0501,0.13074 -0.21239,-0.13901 -0.21239,-0.13952 -0.0362,0.11523 -0.0362,0.11524 -0.0496,0.22686 -0.0496,0.22738 -0.1478,0.0439 -0.14779,0.0444 -1.00666,0.1297 -1.00614,0.12971 -0.11213,0.0925 -0.11163,0.0925 -0.0889,-0.0548 -0.0889,-0.0548 v -0.2744 -0.27441 l -0.0889,-0.0889 -0.0884,-0.0884 -0.0398,-0.48162 -0.0398,-0.48162 0.25941,0.13435 0.25993,0.13436 1.03302,-0.10128 1.03301,-0.10077 0.1974,-0.12196 0.19792,-0.12195 0.0491,0.0491 0.0491,0.0491 0.015,0.19069 0.0145,0.19068 0.39843,-0.008 0.39791,-0.008 0.0951,-0.0956 0.0951,-0.0951 -0.10181,-0.34003 -0.1018,-0.34055 -0.31471,-0.30644 -0.31419,-0.30696 -0.19586,-0.0754 -0.19637,-0.0744 -0.69298,5.1e-4 -0.69298,0.001 -1.43919,0.16691 -1.43918,0.16692 -0.0636,10e-4 -0.0636,0.001 v 0.0848 0.0847 h 0.11059 0.1111 l 0.1478,0.10801 0.14779,0.108 -0.0558,0.14573 -0.0563,0.14624 h -0.21549 -0.21549 l -0.4315,-0.21755 -0.43149,-0.21705 -0.20464,-0.11523 -0.20464,-0.11524 -0.79065,-0.14883 -0.79013,-0.14883 -1.20613,0.10129 -1.20665,0.10128 -0.31729,-0.001 -0.31781,-5.2e-4 0.001,-0.14831 0.001,-0.14831 0.0465,-0.17363 0.0465,-0.17364 0.18551,-0.11782 0.18501,-0.11782 0.38085,-0.0579 0.38086,-0.0584 0.83509,-0.17932 0.83457,-0.17983 h 0.24495 0.24494 l 0.31626,-0.1695 0.31575,-0.16898 h 0.18035 0.18035 l 0.23151,-0.18242 0.23151,-0.1819 0.15348,-0.35812 0.15348,-0.3576 -0.0186,-0.37001 -0.0186,-0.37052 -0.25476,-0.0253 -0.25477,-0.0253 -0.10697,0.23461 -0.10697,0.23461 -0.15555,-0.20618 -0.15554,-0.20619 -0.0372,-0.29973 -0.0377,-0.2992 0.0243,-0.0289 0.0238,-0.0284 0.17105,0.0553 0.17156,0.0543 0.0548,0.17208 0.0548,0.17208 0.22479,-0.0558 0.22479,-0.0563 0.031,-0.0315 0.0315,-0.0315 -0.0574,-0.30541 -0.0574,-0.3054 -0.29714,-0.32867 -0.29714,-0.32918 -0.26975,-0.0853 -0.26924,-0.0863 -0.67747,0.076 -0.67748,0.0754 -1.10071,0.20257 -1.10019,0.20309 -0.1416,0.0631 -0.14107,0.063 -0.19741,-0.12712 -0.1974,-0.12764 -0.71985,0.0367 -0.71934,0.0362 -1.60869,0.25993 -1.60868,0.25994 -0.31781,0.0816 -0.3173,0.0822 -0.007,0.14469 -0.007,0.14521 -0.0894,-0.25424 -0.0889,-0.25373 -0.12919,-0.14832 -0.12868,-0.14831 h -0.19895 -0.19844 v 0.0506 0.0501 l 0.25425,0.23771 0.25373,0.23771 v 0.29094 0.29197 l -0.16175,0.14625 -0.16174,0.14624 -0.23823,-0.0274 -0.23823,-0.0274 -0.13126,-0.38034 -0.13177,-0.38034 -0.1571,-0.0305 -0.15658,-0.0305 -0.0723,0.11421 -0.0724,0.1142 -0.001,0.27027 -5.1e-4,0.27027 0.19844,0.23099 0.19843,0.23048 0.40153,-0.0155 0.40204,-0.015 0.16847,-0.0904 0.16898,-0.0899 0.12402,-0.2682 0.12454,-0.26872 -0.0501,0.8046 -0.0501,0.80409 -0.20516,1.65106 -0.20567,1.65106 -0.12816,0.93121 -0.12764,0.93121 10e-4,0.4656 0.001,0.46613 0.12661,0.67696 0.12661,0.67747 0.048,0.25787 0.0481,0.25787 -1.25728,0.0915 -1.25729,0.0904 -0.0372,-0.0336 -0.0372,-0.0336 0.14883,-1.45211 0.14831,-1.45211 0.11834,-0.22893 0.11834,-0.22841 -0.0977,-0.25889 -0.0982,-0.2589 0.23978,-2.39779 0.23926,-2.39727 0.0724,-0.88315 0.0734,-0.88367 0.2098,-0.30179 0.20981,-0.3023 0.19327,-0.12196 0.19275,-0.12247 0.33435,-0.0465 0.33434,-0.047 0.10749,-0.76171 0.10749,-0.76223 0.10077,-0.38085 0.10077,-0.38086 0.15606,-0.31471 0.15658,-0.31471 0.1633,-0.0884 0.16329,-0.0868 h 0.20826 0.20826 l 0.0517,0.13384 0.0512,0.13385 v 0.23409 0.23409 l 0.1757,0.15917 0.1757,0.15864 1.12707,0.0336 1.12706,0.0331 0.18862,-0.18862 0.18913,-0.18914 -0.0543,-0.47697 -0.0543,-0.47697 -0.11885,-0.44959 -0.11886,-0.4501 -0.13746,-0.21032 -0.13797,-0.21033 -0.26355,-0.16019 -0.26304,-0.15968 -0.56172,-0.10077 z m -11.63701,2.70061 c 2.9e-4,7e-5 7.2e-4,-6e-5 0.001,0 -3.1e-4,-6e-5 -7.3e-4,-4.3e-4 -0.001,-5.2e-4 z m 4.07158,1.51929 h 5.2e-4 v -5.2e-4 h -5.2e-4 z m -4.46794,-0.90744 -0.04806,0.23874 -0.04754,0.23823 -0.08888,0.23306 -0.08888,0.23306 -0.192237,-0.001 -0.192753,-0.002 -0.116272,-0.0739 -0.116272,-0.0734 0.12299,-0.36949 0.122473,-0.37 0.322978,-0.0269 z m 1.74305,0.56224 0.26406,0.0222 0.26407,0.0217 0.71778,0.28061 0.71727,0.2806 0.23048,0.17208 0.22996,0.17208 -0.11007,0.41445 -0.11059,0.41444 -0.0584,0.0568 -0.0584,0.0574 -0.21342,-0.11369 -0.21394,-0.11317 -0.59066,-0.18655 -0.59066,-0.18604 -0.35967,-0.20567 -0.35967,-0.20567 10e-4,-0.10232 0.001,-0.1018 0.11989,-0.339 z m 2.89026,0.55035 h 0.0424 0.0424 v 0.0424 0.0424 h -0.0424 -0.0424 v -0.0424 z m 15.25436,1.96112 0.11214,0.2682 0.11214,0.2682 -0.045,0.23307 -0.0444,0.23254 h -0.14883 -0.14934 l -0.10542,0.12712 -0.10542,0.12713 h -0.28061 -0.28112 l -0.0465,0.0749 -0.0465,0.0755 -0.27596,0.0429 -0.27595,0.0429 -0.17776,0.0579 -0.17777,0.0574 -0.10439,-0.40876 -0.1049,-0.40876 -0.0775,-0.19431 -0.0775,-0.1943 v -0.12816 -0.12816 l 0.37827,0.0563 0.37776,0.0568 0.40514,-0.0884 0.40514,-0.0884 0.36742,-0.0408 z m -25.33695,4.22197 0.130742,0.0501 0.130224,0.0501 v 0.0651 0.0651 l -0.249597,0.0496 -0.250114,0.0501 -0.892969,0.0579 -0.892968,0.0579 v -0.0915 -0.0915 l 0.147794,-0.045 0.148311,-0.045 0.864547,-0.0863 z m -3.929993,0.13281 0.381373,0.17311 0.380855,0.1726 0.287838,0.2434 0.287837,0.24339 0.628386,0.75913 0.628902,0.75964 0.156579,0.30696 0.15658,0.30696 0.129708,0.50281 0.130225,0.50281 0.05788,0.69867 0.05788,0.69866 h -0.04806 -0.04754 l -0.08578,-0.27491 -0.08578,-0.27544 -0.224793,-0.84646 -0.225309,-0.84646 -0.276986,-0.601 -0.276986,-0.60099 -0.321944,-0.35605 -0.321944,-0.35657 -0.345715,-0.19947 -0.345716,-0.19999 -0.436665,-0.12712 -0.436149,-0.12765 -0.622185,0.078 -0.621667,0.0775 -0.402043,0.19896 -0.402042,0.19843 v -0.1049 -0.10542 l 0.298689,-0.26252 0.29869,-0.262 0.357084,-0.18293 0.357601,-0.18294 0.465605,-0.004 z m -1.311547,1.12138 0.274918,0.0594 0.274919,0.0594 0.298173,0.1509 0.29869,0.15089 0.386539,0.41341 0.386023,0.4129 0.319877,0.64647 0.319361,0.64699 0.04393,0.38757 0.04341,0.38757 -0.116789,-0.016 -0.116789,-0.0155 -0.465604,-0.0264 -0.465605,-0.0264 -1.003039,-0.0863 -1.002523,-0.0863 -0.151412,-0.0806 -0.151412,-0.0811 0.02635,-1.23507 0.02584,-1.23507 0.09767,-0.11782 0.09767,-0.11782 0.289905,-0.0956 z m 33.63206,0.14986 0.11678,0.15916 0.11679,0.15968 -0.001,0.17467 -0.001,0.17415 -0.0625,0.0848 -0.062,0.0847 -0.13695,0.10698 -0.13746,0.10645 -0.0951,-0.11472 -0.0956,-0.11472 -0.0522,-0.25942 -0.0517,-0.25942 0.23099,-0.15141 z m 4.05401,0.54053 h 0.12196 0.12195 v 0.0847 0.0848 h -0.17415 -0.17415 l 0.0522,-0.0848 z m -33.693549,0.25425 h 0.245463 0.245463 l 0.530717,0.50798 0.530717,0.50746 0.296623,-0.007 0.296105,-0.007 0.495577,-0.062 0.495061,-0.0615 0.133842,0.20412 0.133842,0.20413 -0.100253,0.16019 -0.09974,0.15968 v 0.0434 0.0434 h 0.116789 0.116788 l 0.09457,0.11472 0.09508,0.11421 -0.07338,0.39067 -0.07286,0.39119 0.163298,0.10852 0.163298,0.10852 -0.05374,0.14005 -0.05323,0.13952 -0.185001,0.06 -0.184485,0.0594 0.148311,0.0398 0.147795,0.0393 v 0.1173 0.11679 l -0.274919,0.0476 -0.275435,0.0475 -0.592212,0.0837 -0.592729,0.0842 -0.338997,0.0501 -0.338998,0.0507 -0.104386,-0.36846 -0.104386,-0.36845 -0.08992,-0.41858 -0.08992,-0.41858 -0.412378,-1.21129 z m 13.283939,0.10904 0.0496,0.0796 0.0491,0.0801 -0.0961,0.0961 -0.0961,0.0961 -0.29869,-0.0258 -0.29869,-0.0253 -0.11265,0.0935 -0.11318,0.0935 -0.0444,-0.11575 -0.0444,-0.11576 0.0558,-0.0863 0.0558,-0.0863 0.447,-0.0419 z m -2.33319,0.22789 0.21136,0.0222 0.21187,0.0217 v 0.25269 0.25322 l -0.13074,0.0253 -0.13074,0.0248 -0.20567,-0.21859 -0.20567,-0.21911 0.12454,-0.0811 z m -12.009083,0.97565 0.131775,0.10801 0.132292,0.10852 0.195854,0.44183 0.195853,0.44183 0.0036,0.7705 0.0041,0.76998 -0.113172,0.13642 -0.113171,0.13643 -1.495516,0.0248 -1.495516,0.0248 -0.141593,0.0703 -0.14211,0.0698 -0.077,-0.36638 -0.077,-0.36639 0.03359,-0.0636 0.03307,-0.0636 h 0.241329 0.241329 l 1.044898,-0.13023 1.044897,-0.13074 0.185002,-0.0925 0.185001,-0.0925 0.101286,-0.24495 0.101286,-0.24494 -0.05013,-0.35761 -0.05013,-0.3576 -0.05994,-0.2961 z m 12.284003,0.20257 0.0367,0.19482 0.0367,0.19482 -0.0475,0.46612 -0.047,0.46561 -0.1943,0.0274 -0.19379,0.0274 0.0248,-0.6625 0.0248,-0.66249 0.17984,-0.0258 z m -16.226398,0.85525 h 0.08062 0.0801 v 0.14366 0.14366 l 0.227893,0.23719 0.227376,0.23772 h -0.2651 -0.264583 l -0.05219,0.0847 -0.05271,0.0847 -0.269751,-0.006 -0.270268,-0.006 -0.127124,-0.0579 -0.127124,-0.0579 -0.01757,-0.31109 -0.01809,-0.31109 0.424264,-0.092 z m 16.089978,0.82527 0.23978,0.28577 0.23977,0.28629 0.2589,-0.19069 0.2589,-0.19068 h 0.0811 0.0811 l 0.2744,0.34675 0.2744,0.34674 0.24081,-0.0481 0.2403,-0.0481 v 0.11782 0.11782 l -0.43564,0.47697 -0.43563,0.47646 -0.23771,0.12299 -0.23719,0.12299 h -0.28061 -0.2806 l -0.25373,-0.18035 -0.25373,-0.18035 -0.2744,-0.35708 -0.27389,-0.35761 5.2e-4,-0.16071 5.1e-4,-0.16123 0.14935,-0.22634 0.14934,-0.22686 0.2372,-0.18655 z m -16.6486,0.48266 0.211357,0.032 0.211873,0.032 0.190686,0.29352 0.190169,0.29352 v 0.2434 0.24288 l -0.06356,0.0398 -0.06356,0.0393 -0.523482,-0.24546 -0.523482,-0.24495 -0.04703,-0.076 -0.04651,-0.0755 0.08992,-0.2589 0.09043,-0.2589 0.141594,-0.0284 z m 1.143083,0.0331 v 0.64182 0.64182 h -0.08733 -0.08733 l -0.04444,-0.11523 -0.04444,-0.11524 0.02584,-0.49196 0.02584,-0.49145 0.105937,-0.0351 z m 4.233333,3.49643 v 0.0646 0.0646 l -0.31781,0.45475 -0.31781,0.45476 -0.361735,0.27595 -0.361735,0.27595 -0.315226,0.0878 -0.315743,0.0873 -0.338481,5.2e-4 h -0.33848 l -0.519349,-0.24546 -0.518831,-0.24547 -0.06305,-0.1018 -0.06305,-0.10232 0.28577,0.0724 0.285771,0.0729 0.804085,-5.1e-4 h 0.804602 l 0.417029,-0.21136 0.417029,-0.21136 0.408244,-0.39687 z m 12.621964,0.0734 h 0.14987 0.14986 l 0.19068,0.17777 0.19069,0.17725 0.0956,0.22479 0.0961,0.22479 0.0341,2.11667 0.0336,2.11666 -0.0977,1.21337 -0.0977,1.21336 -0.16382,0.53175 -0.16433,0.53175 -0.17725,0.22479 -0.17673,0.22531 -0.24236,0.12506 -0.24236,0.12557 h -0.13385 -0.13384 l -0.045,-0.11782 -0.0455,-0.11782 0.17156,-0.9617 0.17157,-0.9617 0.0868,-0.84697 0.0868,-0.84646 0.0894,-1.60869 0.0894,-1.60869 -0.0553,-0.71985 -0.0548,-0.71934 0.0966,-0.36018 z m -9.540498,0.25425 -0.0801,0.077 -0.08062,0.0775 -0.76171,0.0915 -0.762228,0.092 -0.289388,5.2e-4 -0.289388,5.1e-4 0.04961,-0.0496 0.04961,-0.0496 0.874882,-0.11679 0.874882,-0.11731 0.207222,-0.003 z m 26.551868,25.99273 h 0.16175 0.16174 l 0.21549,0.25579 0.2155,0.25632 -0.046,0.11989 -0.046,0.11989 0.13436,0.64905 0.13436,0.64906 v 0.20257 0.20257 h -0.16382 -0.16329 l -0.13695,-0.65629 -0.13745,-0.65629 -0.0677,-0.12712 -0.0677,-0.12661 -0.26665,-0.0269 -0.26665,-0.0264 v -0.0868 -0.0873 l 0.1695,-0.21549 0.1695,-0.21497 v -0.11576 z m -1.77819,0.59221 h 0.11679 0.11627 l 0.10232,0.1235 0.10232,0.12299 -0.0543,0.0889 -0.0548,0.0884 h -0.10801 l -0.108,-5.1e-4 -0.0563,-0.0563 -0.0563,-0.0563 v -0.15503 z m -3.29023,0.74052 0.094,0.094 0.094,0.0941 0.0501,0.36174 0.0501,0.36173 0.27337,-0.0217 0.27337,-0.0217 0.002,0.10645 0.002,0.10594 0.047,1.03714 0.047,1.03715 h -0.0827 -0.0806 l -0.0558,-0.74053 -0.0563,-0.74104 -0.0486,-0.24701 -0.0486,-0.24701 -0.0584,0.11989 -0.0584,0.1204 0.0486,0.9586 0.047,0.95912 0.14779,0.0822 0.14728,0.0827 v 0.24082 0.24029 l -0.12712,0.0491 -0.12713,0.0486 v 0.12971 0.1297 l -0.0641,-0.007 -0.063,-0.006 -0.339,-0.10439 -0.33848,-0.10439 0.26459,-0.14004 0.26458,-0.13953 0.0439,-0.22996 0.0439,-0.22996 -0.0832,-0.93741 -0.0832,-0.93741 -0.20929,-0.22169 -0.20877,-0.22169 -0.0491,-0.47491 -0.0491,-0.47542 0.14418,-0.0553 z m -1.02785,0.094 0.14831,0.0279 0.14831,0.0279 0.0269,0.28526 0.0274,0.28525 -0.0739,0.0739 -0.0739,0.0739 -0.10129,-0.10129 -0.1018,-0.1018 v -0.28577 z m -0.43305,0.85886 0.0413,0.0672 0.0419,0.0677 0.0264,1.20354 0.0264,1.20355 0.14108,0.0444 0.14056,0.045 0.0527,0.13797 0.0532,0.13798 -0.0667,0.0574 -0.0656,0.0579 -0.21187,-0.0543 -0.21136,-0.0548 -0.0155,-0.25011 -0.016,-0.25012 -0.0873,-0.88883 -0.0873,-0.88935 -0.003,-0.26872 -0.003,-0.26923 -0.14831,-0.0382 -0.14831,-0.0388 0.27026,-0.0103 z m 0.26355,0.0749 0.0847,0.0522 0.0847,0.0522 v 0.0796 0.0801 h -0.0847 -0.0847 v -0.13229 z m -0.25373,3.0241 0.092,0.0915 0.0915,0.092 -0.0279,0.18707 -0.0284,0.18759 -0.0636,-0.0646 -0.0636,-0.0646 v -0.21445 z m 2.37092,0.006 0.0563,0.0558 0.0563,0.0553 h -0.11317 -0.11266 l 0.0563,-0.0553 z m -2.28617,0.84646 0.0563,0.0558 0.0563,0.0553 -0.0563,0.0563 -0.0563,0.0563 v -0.11317 z m 9.94825,-40.78642 -0.59273,0.0558 -0.59273,0.0558 -0.38086,0.0687 -0.38085,0.0682 0.84646,-0.047 0.84646,-0.047 0.12712,-0.077 z m 0.0527,0.74931 -0.11111,0.0444 -0.1111,0.0444 0.16226,0.007 0.16227,0.007 -0.0512,-0.0512 z" + /> + </g> + </svg> + </div> + <div class="content-center ml-2"> + <p class="font-semibold text-center">"Singlestage UI"</p> + <p class="text-sm text-center">"v"{env!("CARGO_PKG_VERSION")}</p> + </div> + </div> + </a> + </SidebarHeader> + <SidebarContent> + <SidebarGroup> + <SidebarGroupLabel>"Getting Started"</SidebarGroupLabel> + <SidebarGroupContent> + <SidebarMenu> + <SidebarMenuItem> + <SidebarMenuButton> + <Link href="/introduction"> + {icon!(icondata::LuInfo)} <span>"Introduction"</span> + </Link> + </SidebarMenuButton> + </SidebarMenuItem> + <SidebarMenuItem> + <SidebarMenuButton> + <Link href="/install"> + {icon!(icondata::LuComputer)} <span>"Install"</span> + </Link> + </SidebarMenuButton> + </SidebarMenuItem> + <SidebarMenuItem> + <SidebarMenuButton> + <Link href="/theme-provider"> + {icon!(icondata::LuPaintBucket)} + <span>"Theme Provider"</span> + </Link> + </SidebarMenuButton> + </SidebarMenuItem> + <SidebarMenuItem> + <SidebarMenuButton> + <Link href="/icon-macro"> + {icon!(icondata::LuImage)} <span>"Icon Macro"</span> + </Link> + </SidebarMenuButton> + </SidebarMenuItem> + <SidebarMenuItem> + <SidebarMenuButton> + <Link href="https://github.com/adoyle0/singlestage-ui"> + {icon!(icondata::LuGithub)} <span>"GitHub"</span> + </Link> + </SidebarMenuButton> + </SidebarMenuItem> + <SidebarMenuItem> + <SidebarMenuButton> + <Link href="https://docs.rs/singlestage/latest/singlestage/index.html"> + {icon!(icondata::SiDocsdotrs, stroke_width=0)} + <span>"Docs.rs"</span> + </Link> + </SidebarMenuButton> + </SidebarMenuItem> + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + <SidebarGroup> + <SidebarGroupLabel>"Components"</SidebarGroupLabel> + <SidebarGroupContent> + <SidebarMenu>{generate_component_links!()}</SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + </SidebarContent> + <SidebarSeparator /> + <SidebarFooter> + <Link + render_as="button" + variant="secondary" + class="kofi-btn w-full" + href="https://ko-fi.com/K3K41INGJM" + on:click=move |_| { + if let Some(sheet) = use_context::<SheetContext>() { + sheet.open.set(false) + } + } + target="_blank" + > + <img src="/kofi_symbol.svg" /> + "Buy me a coffee" + </Link> + </SidebarFooter> + </Sidebar> + } +} diff --git a/docs/src/components/theme_switcher.rs b/docs/src/components/theme_switcher.rs index cb7e5f3d..2997eeac 100644 --- a/docs/src/components/theme_switcher.rs +++ b/docs/src/components/theme_switcher.rs @@ -6,7 +6,8 @@ use singlestage::*; pub fn ThemeSwitcher() -> impl IntoView { let theme_context = expect_context::<ThemeProviderContext>(); let prefers_dark = RwSignal::new(false); - let selected_theme = RwSignal::new("default".to_string()); + let selected_theme = RwSignal::new("neutral".to_string()); + let selected_base = RwSignal::new("vega".to_string()); // TODO: Make reactive Effect::new(move || { @@ -81,9 +82,9 @@ pub fn ThemeSwitcher() -> impl IntoView { .set(match selected_theme.get().as_str() { "amber" => Theme::Amber, "blue" => Theme::Blue, - "default" => Theme::Default, "lime" => Theme::Lime, "mono" => Theme::Mono, + "neutral" => Theme::Neutral, "orange" => Theme::Orange, "purple" => Theme::Purple, "red" => Theme::Red, @@ -92,52 +93,110 @@ pub fn ThemeSwitcher() -> impl IntoView { "teal" => Theme::Teal, "violet" => Theme::Violet, "yellow" => Theme::Yellow, - _ => Theme::Default, + _ => Theme::Neutral, }) }); + Effect::new(move || { + // let _ = document() + // .unchecked_ref::<web_sys::HtmlDocument>() + // .set_cookie(format!("theme={}; Path=/", selected_theme.get()).as_str()); + + theme_context.base.set(match selected_base.get().as_str() { + "luma" => ThemeBase::Luma, + "lyra" => ThemeBase::Lyra, + "maia" => ThemeBase::Maia, + "mira" => ThemeBase::Mira, + "nova" => ThemeBase::Nova, + "sera" => ThemeBase::Sera, + _ => ThemeBase::Vega, + }) + }); + view! { <span class="flex space-x-2"> - <Tooltip side="bottom" value="Select theme"> - <Select value=selected_theme class="h-8"> - <SelectContent label="Colors"> - <SelectItem value="default">"Default"</SelectItem> - <SelectItem value="amber">"Amber"</SelectItem> - <SelectItem value="blue">"Blue"</SelectItem> - <SelectItem value="lime">"Lime"</SelectItem> - <SelectItem value="orange">"Orange"</SelectItem> - <SelectItem value="purple">"Purple"</SelectItem> - <SelectItem value="red">"Red"</SelectItem> - <SelectItem value="rose">"Rose"</SelectItem> - <SelectItem value="teal">"Teal"</SelectItem> - <SelectItem value="violet">"Violet"</SelectItem> - <SelectItem value="yellow">"Yellow"</SelectItem> - </SelectContent> - <SelectContent label="Layout"> - <SelectItem value="mono">"Mono"</SelectItem> - <SelectItem value="scaled">"Scaled"</SelectItem> - </SelectContent> - </Select> + <Tooltip> + <TooltipTrigger> + <Select value=selected_base todo_name_me_size="sm"> + <SelectOption value="luma">"Luma"</SelectOption> + <SelectOption value="lyra">"Lyra"</SelectOption> + <SelectOption value="maia">"Maia"</SelectOption> + <SelectOption value="mira">"Mira"</SelectOption> + <SelectOption value="nova">"Nova"</SelectOption> + <SelectOption value="sera">"Sera"</SelectOption> + <SelectOption value="vega">"Vega"</SelectOption> + </Select> + </TooltipTrigger> + <TooltipContent side="bottom"> + <p>"Select base theme"</p> + </TooltipContent> </Tooltip> - <Tooltip side="bottom" value="Toggle dark mode"> - <Button variant="outline" size="sm-icon" on:click=swap_theme> - {move || match theme_context.mode.get() { - Mode::Light => view! { <span>{icon!(icondata::LuSun)}</span> }.into_any(), - Mode::Dark => view! { <span>{icon!(icondata::LuMoon)}</span> }.into_any(), - _ => { - view! { - <span class="block dark:hidden">{icon!(icondata::LuSun)}</span> - <span class="hidden dark:block">{icon!(icondata::LuMoon)}</span> + <Tooltip> + <TooltipTrigger> + <Select value=selected_theme todo_name_me_size="sm"> + <SelectOptGroup label="Colors"> + <SelectOption value="neutral">"Neutral"</SelectOption> + <SelectOption value="amber">"Amber"</SelectOption> + <SelectOption value="blue">"Blue"</SelectOption> + <SelectOption value="lime">"Lime"</SelectOption> + <SelectOption value="orange">"Orange"</SelectOption> + <SelectOption value="purple">"Purple"</SelectOption> + <SelectOption value="red">"Red"</SelectOption> + <SelectOption value="rose">"Rose"</SelectOption> + <SelectOption value="teal">"Teal"</SelectOption> + <SelectOption value="violet">"Violet"</SelectOption> + <SelectOption value="yellow">"Yellow"</SelectOption> + </SelectOptGroup> + <SelectOptGroup label="Layout"> + <SelectOption value="mono">"Mono"</SelectOption> + <SelectOption value="scaled">"Scaled"</SelectOption> + </SelectOptGroup> + </Select> + </TooltipTrigger> + <TooltipContent side="bottom"> + <p>"Select color scheme"</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button variant="outline" size="sm-icon" on:click=swap_theme> + {move || match theme_context.mode.get() { + Mode::Light => { + view! { <span>{icon!(icondata::LuSun)}</span> }.into_any() + } + Mode::Dark => { + view! { <span>{icon!(icondata::LuMoon)}</span> }.into_any() + } + _ => { + match prefers_dark.get() { + false => { + view! { <span>{icon!(icondata::LuSun)}</span> }.into_any() + } + true => { + view! { <span>{icon!(icondata::LuMoon)}</span> }.into_any() + } + } } - .into_any() - } - }} - </Button> + }} + </Button> + </TooltipTrigger> + <TooltipContent side="bottom"> + <p>"Toggle dark mode"</p> + </TooltipContent> </Tooltip> - <Tooltip side="bottom" align="end" value="GitHub repository"> - <a href="https://github.com/adoyle0/singlestage-ui"> - <Button size="sm-icon">{icon!(icondata::SiGithub)}</Button> - </a> + <Tooltip> + <TooltipTrigger> + <Link + href="https://github.com/adoyle0/singlestage-ui" + render_as="button" + size="sm-icon" + > + {icon!(icondata::SiGithub)} + </Link> + </TooltipTrigger> + <TooltipContent side="bottom"> + <p>"GitHub repository"</p> + </TooltipContent> </Tooltip> </span> } diff --git a/docs/src/routes/components/accordion/anatomy.rs b/docs/src/routes/components/accordion/anatomy.rs index 533498be..fc957c74 100644 --- a/docs/src/routes/components/accordion/anatomy.rs +++ b/docs/src/routes/components/accordion/anatomy.rs @@ -7,7 +7,7 @@ pub fn AccordionAnatomy() -> impl IntoView { <Accordion> <AccordionItem> <AccordionTrigger - <AccordionIcon /> + <AccordionIcon slot /> <AccordionTrigger/> <AccordionContent /> </AccordionItem> diff --git a/docs/src/routes/components/accordion/examples/accordion.rs b/docs/src/routes/components/accordion/examples/accordion.rs index c053587a..504e017f 100644 --- a/docs/src/routes/components/accordion/examples/accordion.rs +++ b/docs/src/routes/components/accordion/examples/accordion.rs @@ -4,8 +4,8 @@ use singlestage::accordion::*; #[component] pub fn AccordionExample() -> impl IntoView { view! { - <Accordion class="w-full sm:w-sm md:w-md"> - <AccordionItem name="demo" open=false> + <Accordion class="w-xs sm:w-sm md:w-md"> + <AccordionItem name="demo"> <AccordionTrigger>"Is it accessible?"</AccordionTrigger> <AccordionContent> <p>"Yes. It adheres to the WAI-ARIA design pattern."</p> diff --git a/docs/src/routes/components/alert/anatomy.rs b/docs/src/routes/components/alert/anatomy.rs index 2545100a..adb80d63 100644 --- a/docs/src/routes/components/alert/anatomy.rs +++ b/docs/src/routes/components/alert/anatomy.rs @@ -7,6 +7,7 @@ pub fn AlertAnatomy() -> impl IntoView { <Alert> <AlertTitle /> <AlertDescription /> + <AlertAction /> </Alert> } } diff --git a/docs/src/routes/components/alert/component.toml b/docs/src/routes/components/alert/component.toml index 4e0fc0bd..9354bfdb 100644 --- a/docs/src/routes/components/alert/component.toml +++ b/docs/src/routes/components/alert/component.toml @@ -4,11 +4,21 @@ description = "Displays a callout for user attention." [[examples]] name = "Alert" +[[examples]] +name = "Alert basic" +title = "Basic" +description = "A basic alert with an icon, title, and description." + [[examples]] name = "Alert Destructive" title = "Destructive" description = "Alerts have a destructive themed variant." +[[examples]] +name = "Alert action" +title = "Action" +description = "Use AlertAction to add a button or other action element to the alert." + [[references]] name = "Alert" description = "Contains the contents of the alert." @@ -16,10 +26,18 @@ extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [ - { attr = "variant", attr_type = "String", default = """"alert"""", description = """Specifies \ - the style of alert to display.""" }, + { attr = "variant", attr_type = "String", default = """""""", description = """Specifies \ + the style of alert to display. Accepted values: "destructive"""" }, ] +[[references]] +name = "AlertAction" +description = "Displays actionable inputs in the top right corner of the alert." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + [[references]] name = "AlertDescription" description = "Contains the description of the alert." diff --git a/docs/src/routes/components/alert/examples/alert.rs b/docs/src/routes/components/alert/examples/alert.rs index 7d3dae0c..c8e62b51 100644 --- a/docs/src/routes/components/alert/examples/alert.rs +++ b/docs/src/routes/components/alert/examples/alert.rs @@ -1,15 +1,24 @@ use leptos::prelude::*; -use singlestage::{alert::*, icon}; +use singlestage::*; #[component] pub fn AlertExample() -> impl IntoView { view! { - <Alert> - {icon!(icondata::LuCircleCheck)} - <AlertTitle>"Success! Your changes have been saved"</AlertTitle> - <AlertDescription> - "This is an alert with icon, title and description." - </AlertDescription> - </Alert> + <div class="max-w-md space-y-4"> + <Alert> + {icon!(icondata::LuCircleCheck)} <AlertTitle>"Payment successful"</AlertTitle> + <AlertDescription> + "Your payment of $29.99 has been processed. A receipt has been sent to + your email address." + </AlertDescription> + </Alert> + <Alert> + {icon!(icondata::LuInfo)} <AlertTitle>"New feature available"</AlertTitle> + <AlertDescription> + "We've added dark mode support. You can enable it in your account + settings." + </AlertDescription> + </Alert> + </div> } } diff --git a/docs/src/routes/components/alert/examples/alert_action.rs b/docs/src/routes/components/alert/examples/alert_action.rs new file mode 100644 index 00000000..df8528e6 --- /dev/null +++ b/docs/src/routes/components/alert/examples/alert_action.rs @@ -0,0 +1,19 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AlertActionExample() -> impl IntoView { + view! { + <Alert class="max-w-md"> + <AlertTitle>"Dark mode is now available"</AlertTitle> + <AlertDescription> + "Enable it under your profile settings to get started." + </AlertDescription> + <AlertAction> + <Button size="xs" variant="default"> + "Enable" + </Button> + </AlertAction> + </Alert> + } +} diff --git a/docs/src/routes/components/alert/examples/alert_basic.rs b/docs/src/routes/components/alert/examples/alert_basic.rs new file mode 100644 index 00000000..ddc79a6b --- /dev/null +++ b/docs/src/routes/components/alert/examples/alert_basic.rs @@ -0,0 +1,15 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AlertBasicExample() -> impl IntoView { + view! { + <Alert class="max-w-md"> + {icon!(icondata::BsCheckCircle)} <AlertTitle>"Account updated successfully"</AlertTitle> + <AlertDescription> + "Your profile information has been saved. Changes will be reflected + immediately." + </AlertDescription> + </Alert> + } +} diff --git a/docs/src/routes/components/alert/examples/alert_destructive.rs b/docs/src/routes/components/alert/examples/alert_destructive.rs index d5400e2c..1757613b 100644 --- a/docs/src/routes/components/alert/examples/alert_destructive.rs +++ b/docs/src/routes/components/alert/examples/alert_destructive.rs @@ -1,12 +1,16 @@ use leptos::prelude::*; -use singlestage::{alert::*, icon}; +use singlestage::*; #[component] pub fn AlertDestructiveExample() -> impl IntoView { view! { - <Alert variant="destructive"> - {icon!(icondata::FiAlertCircle)} <AlertTitle>"Something went wrong!"</AlertTitle> - <AlertDescription>"Your session has expired. Please log in again."</AlertDescription> + <Alert class="max-w-md" variant="destructive"> + {icon!(icondata::FiAlertCircle)} + <AlertTitle>"Payment failed"</AlertTitle> + <AlertDescription> + "Your payment could not be processed. Please check your payment method + and try again." + </AlertDescription> </Alert> } } diff --git a/docs/src/routes/components/alert/examples/mod.rs b/docs/src/routes/components/alert/examples/mod.rs index 1ac7f37b..86e087ce 100644 --- a/docs/src/routes/components/alert/examples/mod.rs +++ b/docs/src/routes/components/alert/examples/mod.rs @@ -1,5 +1,9 @@ mod alert; +mod alert_action; +mod alert_basic; mod alert_destructive; pub use alert::*; +pub use alert_action::*; +pub use alert_basic::*; pub use alert_destructive::*; diff --git a/docs/src/routes/components/alert_dialog/anatomy.rs b/docs/src/routes/components/alert_dialog/anatomy.rs new file mode 100644 index 00000000..a4e28148 --- /dev/null +++ b/docs/src/routes/components/alert_dialog/anatomy.rs @@ -0,0 +1,24 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AlertDialogAnatomy -> impl IntoView { + view!{ + <AlertDialog> + <AlertDialogTrigger> + <Button /> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogMedia /> + <AlertDialogTitle /> + <AlertDialogDescription /> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel /> + <AlertDialogAction /> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + } +} diff --git a/docs/src/routes/components/alert_dialog/component.toml b/docs/src/routes/components/alert_dialog/component.toml new file mode 100644 index 00000000..5e8f62ce --- /dev/null +++ b/docs/src/routes/components/alert_dialog/component.toml @@ -0,0 +1,126 @@ +name = "Alert Dialog" +description = "A modal dialog that interrupts the user with important content and expects a response." + +[[examples]] +name = "Alert Dialog" + +[[examples]] +name = "alert dialog basic" +title = "Basic" +description = "A basic alert dialog with a title, description, and cancel and continue buttons." + +[[examples]] +name = "alert dialog small" +title = "Small" +description = "Use the size prop to make the alert dialog smaller." + +[[examples]] +name = "alert dialog media" +title = "Media" +description = """Use the DialogMedia component to add a media element such as an icon or image to \ +the alert dialog.""" + +[[examples]] +name = "alert dialog small with media" +title = "Small with Media" +description = """Use the size prop to make the alert dialog smaller and the AlertDialogMedia \ +component to add a media element such as an icon or image to the alert dialog.""" + + +[[examples]] +name = "alert dialog destructive" +title = "Destructive" +description = """Use the AlertDialogAction component to add a destructive action button to the \ +alert dialog.""" + +[[references]] +name = "AlertDialog" +description = "Contains all the parts of a Dialog component." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +and <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#attributes">dialog</a> \ +attributes.""" +attrs = [ + { attr = "open", attr_type = "bool", default = "false", description = """Reactive signal that \ + can remotely control the open state of the popover **but is not coupled to the actual open \ + state of the popover**""" }, +] + +[[references]] +name = "AlertDialogAction" +description = "Wraps a button that the user presses to acknowledge the dialog." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "AlertDialogCancel" +description = "Wraps a button that the user presses to dismiss the dialog." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "AlertDialogContent" +description = "Contains content to be rendered in the main body of the dialog." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [ + { attr = "close_button", attr_type = "bool", default = "false", description = """Toggles \ + whether or not a close button should appear in the top right corner of the dialog.""" }, + { attr = "size", attr_type = "String", default = """""""", description = """Set the size of \ + dialog to render. Accepted values: "sm"""" }, +] + +[[references]] +name = "AlertDialogDescription" +description = "A short description/subheading describing dialog content." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "AlertDialogFooter" +description = """Displays at the bottom of the dialog, contains calls to action.""" +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "AlertDialogHeader" +description = "Contains the dialog title and a description to be rendered in the open dialog." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "AlertDialogMedia" +description = """Contains a media element such as an icon or image to render in the header of the \ +dialog.""" +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [ + { attr = "variant", attr_type = "String", default = """""""", description = """Set the style \ + variant of the media element. Accepted values: "destructive"""" }, +] + +[[references]] +name = "AlertDialogTitle" +description = "A title describing dialog content." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "AlertDialogTrigger" +description = "Used to wrap other elements and trigger the dialog on click." +attrs = [] diff --git a/docs/src/routes/components/alert_dialog/examples/alert_dialog.rs b/docs/src/routes/components/alert_dialog/examples/alert_dialog.rs new file mode 100644 index 00000000..ea39783a --- /dev/null +++ b/docs/src/routes/components/alert_dialog/examples/alert_dialog.rs @@ -0,0 +1,30 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AlertDialogExample() -> impl IntoView { + view! { + <AlertDialog> + <AlertDialogTrigger> + <Button variant="outline">"Show Dialog"</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>"Are you absolutely sure?"</AlertDialogTitle> + <AlertDialogDescription> + "This action cannot be undone. This will permanently delete your + account from our servers." + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel> + <Button>"Cancel"</Button> + </AlertDialogCancel> + <AlertDialogAction> + <Button>"Continue"</Button> + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + } +} diff --git a/docs/src/routes/components/alert_dialog/examples/alert_dialog_basic.rs b/docs/src/routes/components/alert_dialog/examples/alert_dialog_basic.rs new file mode 100644 index 00000000..9fcfe1e4 --- /dev/null +++ b/docs/src/routes/components/alert_dialog/examples/alert_dialog_basic.rs @@ -0,0 +1,30 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AlertDialogBasicExample() -> impl IntoView { + view! { + <AlertDialog> + <AlertDialogTrigger> + <Button variant="outline">"Show Dialog"</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>"Are you absolutely sure?"</AlertDialogTitle> + <AlertDialogDescription> + "This action cannot be undone. This will permanently delete your + account and remove your data from our servers." + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel> + <Button>"Cancel"</Button> + </AlertDialogCancel> + <AlertDialogAction> + <Button>"Continue"</Button> + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + } +} diff --git a/docs/src/routes/components/alert_dialog/examples/alert_dialog_destructive.rs b/docs/src/routes/components/alert_dialog/examples/alert_dialog_destructive.rs new file mode 100644 index 00000000..2132f433 --- /dev/null +++ b/docs/src/routes/components/alert_dialog/examples/alert_dialog_destructive.rs @@ -0,0 +1,33 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AlertDialogDestructiveExample() -> impl IntoView { + view! { + <AlertDialog> + <AlertDialogTrigger> + <Button variant="destructive">"Delete Chat"</Button> + </AlertDialogTrigger> + <AlertDialogContent size="sm"> + <AlertDialogHeader> + <AlertDialogMedia variant="destructive"> + {icon!(icondata::LuTrash2)} + </AlertDialogMedia> + <AlertDialogTitle>"Delete chat?"</AlertDialogTitle> + <AlertDialogDescription> + "This will permanently delete this chat conversation. View " + <Link>"Settings"</Link> + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel> + <Button variant="ghost">"Cancel"</Button> + </AlertDialogCancel> + <AlertDialogAction> + <Button variant="destructive">"Delete"</Button> + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + } +} diff --git a/docs/src/routes/components/alert_dialog/examples/alert_dialog_media.rs b/docs/src/routes/components/alert_dialog/examples/alert_dialog_media.rs new file mode 100644 index 00000000..1efd5cd8 --- /dev/null +++ b/docs/src/routes/components/alert_dialog/examples/alert_dialog_media.rs @@ -0,0 +1,30 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AlertDialogMediaExample() -> impl IntoView { + view! { + <AlertDialog> + <AlertDialogTrigger> + <Button variant="outline">"Share Project"</Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogMedia>{icon!(icondata::LuCircleFadingPlus)}</AlertDialogMedia> + <AlertDialogTitle>"Share this project?"</AlertDialogTitle> + <AlertDialogDescription> + "Anyone with the link will be able to view and edit this project." + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel> + <Button>"Cancel"</Button> + </AlertDialogCancel> + <AlertDialogAction> + <Button>"Share"</Button> + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + } +} diff --git a/docs/src/routes/components/alert_dialog/examples/alert_dialog_small.rs b/docs/src/routes/components/alert_dialog/examples/alert_dialog_small.rs new file mode 100644 index 00000000..2fafc395 --- /dev/null +++ b/docs/src/routes/components/alert_dialog/examples/alert_dialog_small.rs @@ -0,0 +1,29 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AlertDialogSmallExample() -> impl IntoView { + view! { + <AlertDialog> + <AlertDialogTrigger> + <Button variant="outline">"Show Dialog"</Button> + </AlertDialogTrigger> + <AlertDialogContent size="sm"> + <AlertDialogHeader> + <AlertDialogTitle>"Allow accessory to connect?"</AlertDialogTitle> + <AlertDialogDescription> + "Do you want to allow the USB accessory to connect to this device?" + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel> + <Button>"Don't allow"</Button> + </AlertDialogCancel> + <AlertDialogAction> + <Button>"Allow"</Button> + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + } +} diff --git a/docs/src/routes/components/alert_dialog/examples/alert_dialog_small_with_media.rs b/docs/src/routes/components/alert_dialog/examples/alert_dialog_small_with_media.rs new file mode 100644 index 00000000..63ad464c --- /dev/null +++ b/docs/src/routes/components/alert_dialog/examples/alert_dialog_small_with_media.rs @@ -0,0 +1,30 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AlertDialogSmallWithMediaExample() -> impl IntoView { + view! { + <AlertDialog> + <AlertDialogTrigger> + <Button variant="outline">"Show Dialog"</Button> + </AlertDialogTrigger> + <AlertDialogContent size="sm"> + <AlertDialogHeader> + <AlertDialogMedia>{icon!(icondata::LuBluetooth)}</AlertDialogMedia> + <AlertDialogTitle>"Allow accessory to connect?"</AlertDialogTitle> + <AlertDialogDescription> + "Do you want to allow the USB accessory to connect to this device?" + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel> + <Button>"Don't allow"</Button> + </AlertDialogCancel> + <AlertDialogAction> + <Button>"Allow"</Button> + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + } +} diff --git a/docs/src/routes/components/alert_dialog/examples/mod.rs b/docs/src/routes/components/alert_dialog/examples/mod.rs new file mode 100644 index 00000000..8ff1e4e8 --- /dev/null +++ b/docs/src/routes/components/alert_dialog/examples/mod.rs @@ -0,0 +1,13 @@ +mod alert_dialog; +mod alert_dialog_basic; +mod alert_dialog_destructive; +mod alert_dialog_media; +mod alert_dialog_small; +mod alert_dialog_small_with_media; + +pub use alert_dialog::*; +pub use alert_dialog_basic::*; +pub use alert_dialog_destructive::*; +pub use alert_dialog_media::*; +pub use alert_dialog_small::*; +pub use alert_dialog_small_with_media::*; diff --git a/docs/src/routes/components/alert_dialog/mod.rs b/docs/src/routes/components/alert_dialog/mod.rs new file mode 100644 index 00000000..c7ceb8b6 --- /dev/null +++ b/docs/src/routes/components/alert_dialog/mod.rs @@ -0,0 +1,3 @@ +mod examples; + +pub use examples::*; diff --git a/docs/src/routes/components/aspect_ratio/component.toml b/docs/src/routes/components/aspect_ratio/component.toml index ef70953d..14982caf 100644 --- a/docs/src/routes/components/aspect_ratio/component.toml +++ b/docs/src/routes/components/aspect_ratio/component.toml @@ -4,13 +4,23 @@ description = "Displays content within a desired ratio." [[examples]] name = "Aspect Ratio" +[[examples]] +name = "Aspect Ratio square" +title = "Square" + +[[examples]] +name = "Aspect Ratio portrait" +title = "Portrait" + [[references]] name = "AspectRatio" -description = "Contains the contents of an aspect ratio component." -extra = """Implements <a class="singlestage-link" \ +description = "Displays content within a desired aspect ratio." +extra = """More aspect-ratio info <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/aspect-ratio">here</a>. \ +<br />Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [ - { attr = "ratio", attr_type = "f64", default = "1.0", description = """The aspect ratio to use \ - to display the child element.""" }, + { attr = "ratio", attr_type = "String", default = """"1"""", description = """The aspect ratio to \ + use to display the child element. Can be a decimal or a formula.""" }, ] diff --git a/docs/src/routes/components/aspect_ratio/examples/aspect_ratio.rs b/docs/src/routes/components/aspect_ratio/examples/aspect_ratio.rs index 715e1bc4..91fc4238 100644 --- a/docs/src/routes/components/aspect_ratio/examples/aspect_ratio.rs +++ b/docs/src/routes/components/aspect_ratio/examples/aspect_ratio.rs @@ -1,15 +1,26 @@ use leptos::prelude::*; -use singlestage::aspect_ratio::*; +use singlestage::*; #[component] pub fn AspectRatioExample() -> impl IntoView { + let theme_provider = expect_context::<ThemeProviderContext>(); + view! { - <div class="w-[300px]"> - <AspectRatio ratio=16. / 9.> + <div class="max-w-sm"> + <AspectRatio ratio="16 / 9"> <img - src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80" alt="Photo by Drew Beamer" - class="w-full h-full rounded-lg object-cover dark:brightness-[0.2] dark:grayscale" + class=move || { + format!( + "w-full h-full rounded-lg object-cover{}", + match theme_provider.mode.get() { + Mode::Auto => " dark:brightness-[0.2] dark:grayscale", + Mode::Dark => " brightness-[0.2] grayscale", + _ => "", + }, + ) + } + src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80" /> </AspectRatio> </div> diff --git a/docs/src/routes/components/aspect_ratio/examples/aspect_ratio_portrait.rs b/docs/src/routes/components/aspect_ratio/examples/aspect_ratio_portrait.rs new file mode 100644 index 00000000..9a60cdb6 --- /dev/null +++ b/docs/src/routes/components/aspect_ratio/examples/aspect_ratio_portrait.rs @@ -0,0 +1,28 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AspectRatioPortraitExample() -> impl IntoView { + let theme_provider = expect_context::<ThemeProviderContext>(); + + view! { + <div class="w-full max-w-[10rem]"> + <AspectRatio ratio="9 / 16"> + <img + alt="Photo by Drew Beamer" + class=move || { + format!( + "w-full h-full rounded-lg object-cover{}", + match theme_provider.mode.get() { + Mode::Auto => " dark:brightness-[0.2] dark:grayscale", + Mode::Dark => " brightness-[0.2] grayscale", + _ => "", + }, + ) + } + src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80" + /> + </AspectRatio> + </div> + } +} diff --git a/docs/src/routes/components/aspect_ratio/examples/aspect_ratio_square.rs b/docs/src/routes/components/aspect_ratio/examples/aspect_ratio_square.rs new file mode 100644 index 00000000..2cdd3490 --- /dev/null +++ b/docs/src/routes/components/aspect_ratio/examples/aspect_ratio_square.rs @@ -0,0 +1,28 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AspectRatioSquareExample() -> impl IntoView { + let theme_provider = expect_context::<ThemeProviderContext>(); + + view! { + <div class="w-full max-w-[12rem]"> + <AspectRatio ratio="1"> + <img + alt="Photo by Drew Beamer" + class=move || { + format!( + "w-full h-full rounded-lg object-cover{}", + match theme_provider.mode.get() { + Mode::Auto => " dark:brightness-[0.2] dark:grayscale", + Mode::Dark => " brightness-[0.2] grayscale", + _ => "", + }, + ) + } + src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80" + /> + </AspectRatio> + </div> + } +} diff --git a/docs/src/routes/components/aspect_ratio/examples/mod.rs b/docs/src/routes/components/aspect_ratio/examples/mod.rs index 76a2e154..07d8afa4 100644 --- a/docs/src/routes/components/aspect_ratio/examples/mod.rs +++ b/docs/src/routes/components/aspect_ratio/examples/mod.rs @@ -1,3 +1,7 @@ mod aspect_ratio; +mod aspect_ratio_portrait; +mod aspect_ratio_square; pub use aspect_ratio::*; +pub use aspect_ratio_portrait::*; +pub use aspect_ratio_square::*; diff --git a/docs/src/routes/components/avatar/anatomy.rs b/docs/src/routes/components/avatar/anatomy.rs index 177adafd..06bfc39b 100644 --- a/docs/src/routes/components/avatar/anatomy.rs +++ b/docs/src/routes/components/avatar/anatomy.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::avatar::*; +use singlestage::*; #[component] pub fn AvatarAnatomy() -> impl IntoView { @@ -7,6 +7,15 @@ pub fn AvatarAnatomy() -> impl IntoView { <Avatar> <AvatarImage /> <AvatarFallback /> + <AvatarBadge /> </Avatar> + + // Group + <AvatarGroup> + <Avatar /> + <Avatar /> + <Avatar /> + <AvatarGroupCount /> + </AvatarGroup> } } diff --git a/docs/src/routes/components/avatar/component.toml b/docs/src/routes/components/avatar/component.toml index 0eb2169e..78960a97 100644 --- a/docs/src/routes/components/avatar/component.toml +++ b/docs/src/routes/components/avatar/component.toml @@ -5,12 +5,45 @@ description = "An image element with a fallback for representing the user." name = "Avatar" [[examples]] -name = "Avatar Fallback" -title = "Fallback" +name = "avatar basic" +title = "Basic" +description = "A basic avatar component with an image and a fallback." [[examples]] -name = "Avatar group" +name = "avatar badge" +title = "Badge" +description = """Use the AvatarBadge component to add a badge to the avatar. The badge is \ +positioned at the bottom right of the avatar.""" + +[[examples]] +name = "avatar badge with icon" +title = "Badge with Icon" +description = "You can also use an icon inside a badge." + +[[examples]] +name = "avatar group" title = "Avatar Group" +description = "Use AvatarGroup to group avatars." + +[[examples]] +name = "avatar group count" +title = "Avatar Group Count" +description = "Use AvatarGroupCount to add a count to the group." + +[[examples]] +name = "avatar group with icon" +title = "Avatar Group with Icon" +description = "You can also use an icon inside <AvatarGroupCount>." + +[[examples]] +name = "avatar sizes" +title = "Sizes" +description = "Use the size prop to change the size of the avatar." + +[[examples]] +name = "avatar dropdown" +title = "Dropdown" +description = "You can use an Avatar component as a trigger for a dropdown menu." [[references]] name = "Avatar" @@ -18,6 +51,17 @@ description = "Contains the contents of an avatar." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" +attrs = [ + { attr = "size", attr_type = "String", default = """"default"""", description = """Set which \ + size avatar to display. Accepted values: \"sm\" | \"default\" | \"lg\".""" }, +] + +[[references]] +name = "AvatarBadge" +description = "Displays a badge overlay at the bottom right of an avatar." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" attrs = [] [[references]] @@ -36,6 +80,13 @@ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attribu attributes.""" attrs = [] +[[references]] +name = "AvatarGroupCount" +description = "Displays a count representing additional hidden avatars." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] [[references]] name = "AvatarImage" diff --git a/docs/src/routes/components/avatar/examples/avatar.rs b/docs/src/routes/components/avatar/examples/avatar.rs index ffd0f943..9b40f5ef 100644 --- a/docs/src/routes/components/avatar/examples/avatar.rs +++ b/docs/src/routes/components/avatar/examples/avatar.rs @@ -1,12 +1,42 @@ use leptos::prelude::*; -use singlestage::avatar::*; +use singlestage::*; #[component] pub fn AvatarExample() -> impl IntoView { + let theme_provider = expect_context::<ThemeProviderContext>(); + view! { - <Avatar class="size-12"> - <AvatarImage alt="@adoyle0" src="/avatar.jpg" /> - <AvatarFallback>"AD"</AvatarFallback> - </Avatar> + <div class="flex flex-row flex-wrap items-center gap-6 md:gap-12"> + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" class="grayscale" /> + <AvatarFallback>"CN"</AvatarFallback> + </Avatar> + <Avatar> + <AvatarImage src="https://github.com/evilrabbit.png" alt="@evilrabbit" /> + <AvatarFallback>"ER"</AvatarFallback> + {move || match theme_provider.mode.get() { + Mode::Auto => { + view! { <AvatarBadge class="bg-green-600 dark:bg-green-800" /> }.into_any() + } + Mode::Dark => view! { <AvatarBadge class="bg-green-800" /> }.into_any(), + _ => view! { <AvatarBadge class="bg-green-600" /> }.into_any(), + }} + </Avatar> + <AvatarGroup class="grayscale"> + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> + </Avatar> + <Avatar> + <AvatarImage src="https://github.com/maxleiter.png" alt="@maxleiter" /> + <AvatarFallback>"LR"</AvatarFallback> + </Avatar> + <Avatar> + <AvatarImage src="https://github.com/evilrabbit.png" alt="@evilrabbit" /> + <AvatarFallback>"ER"</AvatarFallback> + </Avatar> + <AvatarGroupCount>"+3"</AvatarGroupCount> + </AvatarGroup> + </div> } } diff --git a/docs/src/routes/components/avatar/examples/avatar_badge.rs b/docs/src/routes/components/avatar/examples/avatar_badge.rs new file mode 100644 index 00000000..940a19ba --- /dev/null +++ b/docs/src/routes/components/avatar/examples/avatar_badge.rs @@ -0,0 +1,21 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AvatarBadgeExample() -> impl IntoView { + let theme_provider = expect_context::<ThemeProviderContext>(); + + view! { + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> + {move || match theme_provider.mode.get() { + Mode::Auto => { + view! { <AvatarBadge class="bg-green-600 dark:bg-green-800" /> }.into_any() + } + Mode::Dark => view! { <AvatarBadge class="bg-green-800" /> }.into_any(), + _ => view! { <AvatarBadge class="bg-green-600" /> }.into_any(), + }} + </Avatar> + } +} diff --git a/docs/src/routes/components/avatar/examples/avatar_badge_with_icon.rs b/docs/src/routes/components/avatar/examples/avatar_badge_with_icon.rs new file mode 100644 index 00000000..55e5296c --- /dev/null +++ b/docs/src/routes/components/avatar/examples/avatar_badge_with_icon.rs @@ -0,0 +1,13 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AvatarBadgeWithIconExample() -> impl IntoView { + view! { + <Avatar class="grayscale"> + <AvatarImage src="https://github.com/pranathip.png" alt="@pranathip" /> + <AvatarFallback>"PP"</AvatarFallback> + <AvatarBadge>{icon!(icondata::LuPlus)}</AvatarBadge> + </Avatar> + } +} diff --git a/docs/src/routes/components/avatar/examples/avatar_basic.rs b/docs/src/routes/components/avatar/examples/avatar_basic.rs new file mode 100644 index 00000000..b0f92bc5 --- /dev/null +++ b/docs/src/routes/components/avatar/examples/avatar_basic.rs @@ -0,0 +1,12 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AvatarBasicExample() -> impl IntoView { + view! { + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" class="grayscale" /> + <AvatarFallback>"CN"</AvatarFallback> + </Avatar> + } +} diff --git a/docs/src/routes/components/avatar/examples/avatar_dropdown.rs b/docs/src/routes/components/avatar/examples/avatar_dropdown.rs new file mode 100644 index 00000000..7402af9e --- /dev/null +++ b/docs/src/routes/components/avatar/examples/avatar_dropdown.rs @@ -0,0 +1,29 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AvatarDropdownExample() -> impl IntoView { + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="ghost" size="icon" class="rounded-full"> + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> + </Avatar> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent class="w-32"> + <DropdownMenuGroup> + <DropdownMenuItem>"Profile"</DropdownMenuItem> + <DropdownMenuItem>"Billing"</DropdownMenuItem> + <DropdownMenuItem>"Settings"</DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem variant="destructive">"Log out"</DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/avatar/examples/avatar_fallback.rs b/docs/src/routes/components/avatar/examples/avatar_fallback.rs deleted file mode 100644 index 55bed332..00000000 --- a/docs/src/routes/components/avatar/examples/avatar_fallback.rs +++ /dev/null @@ -1,11 +0,0 @@ -use leptos::prelude::*; -use singlestage::avatar::*; - -#[component] -pub fn AvatarFallbackExample() -> impl IntoView { - view! { - <Avatar class="size-12"> - <AvatarFallback>"AD"</AvatarFallback> - </Avatar> - } -} diff --git a/docs/src/routes/components/avatar/examples/avatar_group.rs b/docs/src/routes/components/avatar/examples/avatar_group.rs index 84c2ac06..61b6276f 100644 --- a/docs/src/routes/components/avatar/examples/avatar_group.rs +++ b/docs/src/routes/components/avatar/examples/avatar_group.rs @@ -1,21 +1,21 @@ use leptos::prelude::*; -use singlestage::avatar::*; +use singlestage::*; #[component] pub fn AvatarGroupExample() -> impl IntoView { view! { - <AvatarGroup> - <Avatar class="size-12 grayscale"> - <AvatarImage src="/avatar-1.png" alt="Sofia Davis's Avatar" /> - <AvatarFallback>"SD"</AvatarFallback> + <AvatarGroup class="grayscale"> + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> </Avatar> - <Avatar class="size-12 grayscale"> - <AvatarImage src="/avatar-2.png" alt="Jackson Lee's Avatar" /> - <AvatarFallback>"JL"</AvatarFallback> + <Avatar> + <AvatarImage src="https://github.com/maxleiter.png" alt="@maxleiter" /> + <AvatarFallback>"LR"</AvatarFallback> </Avatar> - <Avatar class="size-12 grayscale"> - <AvatarImage src="/avatar-3.png" alt="Isabella Nguyen's Avatar" /> - <AvatarFallback>"IN"</AvatarFallback> + <Avatar> + <AvatarImage src="https://github.com/evilrabbit.png" alt="@evilrabbit" /> + <AvatarFallback>"ER"</AvatarFallback> </Avatar> </AvatarGroup> } diff --git a/docs/src/routes/components/avatar/examples/avatar_group_count.rs b/docs/src/routes/components/avatar/examples/avatar_group_count.rs new file mode 100644 index 00000000..e201c670 --- /dev/null +++ b/docs/src/routes/components/avatar/examples/avatar_group_count.rs @@ -0,0 +1,23 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AvatarGroupCountExample() -> impl IntoView { + view! { + <AvatarGroup class="grayscale"> + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> + </Avatar> + <Avatar> + <AvatarImage src="https://github.com/maxleiter.png" alt="@maxleiter" /> + <AvatarFallback>"LR"</AvatarFallback> + </Avatar> + <Avatar> + <AvatarImage src="https://github.com/evilrabbit.png" alt="@evilrabbit" /> + <AvatarFallback>"ER"</AvatarFallback> + </Avatar> + <AvatarGroupCount>"+3"</AvatarGroupCount> + </AvatarGroup> + } +} diff --git a/docs/src/routes/components/avatar/examples/avatar_group_with_icon.rs b/docs/src/routes/components/avatar/examples/avatar_group_with_icon.rs new file mode 100644 index 00000000..019db235 --- /dev/null +++ b/docs/src/routes/components/avatar/examples/avatar_group_with_icon.rs @@ -0,0 +1,23 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AvatarGroupWithIconExample() -> impl IntoView { + view! { + <AvatarGroup class="grayscale"> + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> + </Avatar> + <Avatar> + <AvatarImage src="https://github.com/maxleiter.png" alt="@maxleiter" /> + <AvatarFallback>"LR"</AvatarFallback> + </Avatar> + <Avatar> + <AvatarImage src="https://github.com/evilrabbit.png" alt="@evilrabbit" /> + <AvatarFallback>"ER"</AvatarFallback> + </Avatar> + <AvatarGroupCount>{icon!(icondata::LuPlus)}</AvatarGroupCount> + </AvatarGroup> + } +} diff --git a/docs/src/routes/components/avatar/examples/avatar_sizes.rs b/docs/src/routes/components/avatar/examples/avatar_sizes.rs new file mode 100644 index 00000000..c0e331c8 --- /dev/null +++ b/docs/src/routes/components/avatar/examples/avatar_sizes.rs @@ -0,0 +1,22 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn AvatarSizesExample() -> impl IntoView { + view! { + <div class="flex flex-wrap items-center gap-2 grayscale"> + <Avatar size="sm"> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> + </Avatar> + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> + </Avatar> + <Avatar size="lg"> + <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> + </Avatar> + </div> + } +} diff --git a/docs/src/routes/components/avatar/examples/mod.rs b/docs/src/routes/components/avatar/examples/mod.rs index 26f08648..4326a4e2 100644 --- a/docs/src/routes/components/avatar/examples/mod.rs +++ b/docs/src/routes/components/avatar/examples/mod.rs @@ -1,7 +1,19 @@ mod avatar; -mod avatar_fallback; +mod avatar_badge; +mod avatar_badge_with_icon; +mod avatar_basic; +mod avatar_dropdown; mod avatar_group; +mod avatar_group_count; +mod avatar_group_with_icon; +mod avatar_sizes; pub use avatar::*; -pub use avatar_fallback::*; +pub use avatar_badge::*; +pub use avatar_badge_with_icon::*; +pub use avatar_basic::*; +pub use avatar_dropdown::*; pub use avatar_group::*; +pub use avatar_group_count::*; +pub use avatar_group_with_icon::*; +pub use avatar_sizes::*; diff --git a/docs/src/routes/components/badge/anatomy.rs b/docs/src/routes/components/badge/anatomy.rs index b00363c3..ece48716 100644 --- a/docs/src/routes/components/badge/anatomy.rs +++ b/docs/src/routes/components/badge/anatomy.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::badge::*; +use singlestage::*; #[component] pub fn BadgeAnatomy() -> impl IntoView { diff --git a/docs/src/routes/components/badge/component.toml b/docs/src/routes/components/badge/component.toml index 4ced5a73..69b91121 100644 --- a/docs/src/routes/components/badge/component.toml +++ b/docs/src/routes/components/badge/component.toml @@ -1,20 +1,30 @@ name = "Badge" -description = "Displays a badge or a component that looks like a badge." +description = "Displays a badge component." [[examples]] name = "Badge" [[examples]] -name = "Badge Invalid" -title = "Invalid" +name = "Badge variants" +title = "Variants" +description = "Use the variant prop to change the variant of the badge." [[examples]] name = "Badge Icon" title = "With Icon" +desscription = """You can render an icon inside the badge. Wrap text in a <span> or another \ +element so the correct padding is applied.""" + +[[examples]] +name = "Badge spinner" +title = "With Spinner" +desscription = """You can render a spinner inside the badge. Wrap text in a <span> or another \ +element so the correct padding is applied.""" [[examples]] name = "Badge Link" title = "Link" +desscription = """Use the render_as prop with Link to render a Link as a Badge.""" [[references]] name = "Badge" @@ -25,6 +35,6 @@ attributes.""" attrs = [ { attr = "invalid", attr_type = "bool", default = "false", description = """Toggle whether or \ not this badge appears invalid.""" }, - { attr = "variant", attr_type = "String", default = """"primary"""", description = """Specifies \ - the style of badge to display.""" }, + { attr = "variant", attr_type = "String", default = """"primary"""", description = """Define \ + the badge variant. Accepted values: "primary" | "secondary" | "destructive" | "outline"""" }, ] diff --git a/docs/src/routes/components/badge/examples/badge.rs b/docs/src/routes/components/badge/examples/badge.rs index cd67d9a5..c72d00dd 100644 --- a/docs/src/routes/components/badge/examples/badge.rs +++ b/docs/src/routes/components/badge/examples/badge.rs @@ -1,11 +1,11 @@ use leptos::prelude::*; -use singlestage::badge::*; +use singlestage::*; #[component] pub fn BadgeExample() -> impl IntoView { view! { - <div class="space-x-2"> - <Badge>"Primary"</Badge> + <div class="flex flex-wrap gap-2"> + <Badge>"Badge"</Badge> <Badge variant="secondary">"Secondary"</Badge> <Badge variant="destructive">"Destructive"</Badge> <Badge variant="outline">"Outline"</Badge> diff --git a/docs/src/routes/components/badge/examples/badge_icon.rs b/docs/src/routes/components/badge/examples/badge_icon.rs index caea88b9..d30395b7 100644 --- a/docs/src/routes/components/badge/examples/badge_icon.rs +++ b/docs/src/routes/components/badge/examples/badge_icon.rs @@ -1,15 +1,15 @@ -use icondata::FiAlertCircle; use leptos::prelude::*; -use singlestage::{badge::*, icon}; +use singlestage::*; #[component] pub fn BadgeIconExample() -> impl IntoView { view! { - <div class="space-x-2"> - <Badge>{icon!(FiAlertCircle)} "Primary"</Badge> - <Badge variant="secondary">{icon!(FiAlertCircle)} "Secondary"</Badge> - <Badge variant="destructive">{icon!(FiAlertCircle)}"Destructive"</Badge> - <Badge variant="outline">{icon!(FiAlertCircle)} "Outline"</Badge> + <div class="flex flex-wrap gap-2"> + <Badge variant="secondary">{icon!(icondata::LuCheck)}<span>Verified</span></Badge> + <Badge variant="outline"> + <span>Bookmark</span> + {icon!(icondata::LuBookmark)} + </Badge> </div> } } diff --git a/docs/src/routes/components/badge/examples/badge_invalid.rs b/docs/src/routes/components/badge/examples/badge_invalid.rs deleted file mode 100644 index 9ab1c4a7..00000000 --- a/docs/src/routes/components/badge/examples/badge_invalid.rs +++ /dev/null @@ -1,20 +0,0 @@ -use leptos::prelude::*; -use singlestage::badge::*; - -#[component] -pub fn BadgeInvalidExample() -> impl IntoView { - view! { - <div class="space-x-2"> - <Badge invalid=true>"Primary"</Badge> - <Badge invalid=true variant="secondary"> - "Secondary" - </Badge> - <Badge invalid=true variant="destructive"> - "Destructive" - </Badge> - <Badge invalid=true variant="outline"> - "Outline" - </Badge> - </div> - } -} diff --git a/docs/src/routes/components/badge/examples/badge_link.rs b/docs/src/routes/components/badge/examples/badge_link.rs index 37f2f506..463b61de 100644 --- a/docs/src/routes/components/badge/examples/badge_link.rs +++ b/docs/src/routes/components/badge/examples/badge_link.rs @@ -1,23 +1,12 @@ -use icondata::LuArrowRight; use leptos::prelude::*; -use singlestage::{badge::*, icon}; +use singlestage::*; #[component] pub fn BadgeLinkExample() -> impl IntoView { view! { - <div class="space-x-2"> - <a href="#"> - <Badge>"Link" {icon!(LuArrowRight)}</Badge> - </a> - <a href="#"> - <Badge variant="secondary">"Link" {icon!(LuArrowRight)}</Badge> - </a> - <a href="#"> - <Badge variant="destructive">"Link" {icon!(LuArrowRight)}</Badge> - </a> - <a href="#"> - <Badge variant="outline">"Link" {icon!(LuArrowRight)}</Badge> - </a> - </div> + <Link render_as="badge" href="#link"> + <span>"Open Link "</span> + {icon!(icondata::LuArrowUpRight)} + </Link> } } diff --git a/docs/src/routes/components/badge/examples/badge_spinner.rs b/docs/src/routes/components/badge/examples/badge_spinner.rs new file mode 100644 index 00000000..1c172860 --- /dev/null +++ b/docs/src/routes/components/badge/examples/badge_spinner.rs @@ -0,0 +1,18 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn BadgeSpinnerExample() -> impl IntoView { + view! { + <div class="flex flex-wrap gap-2"> + <Badge variant="destructive"> + <Spinner /> + <span>"Deleting"</span> + </Badge> + <Badge variant="secondary"> + <span>"Generating"</span> + <Spinner /> + </Badge> + </div> + } +} diff --git a/docs/src/routes/components/badge/examples/badge_variants.rs b/docs/src/routes/components/badge/examples/badge_variants.rs new file mode 100644 index 00000000..e5b3b0fb --- /dev/null +++ b/docs/src/routes/components/badge/examples/badge_variants.rs @@ -0,0 +1,15 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn BadgeVariantsExample() -> impl IntoView { + view! { + <div class="flex flex-wrap gap-2"> + <Badge invalid=true>"Invalid"</Badge> + <Badge variant="secondary">"Secondary"</Badge> + <Badge variant="destructive">"Destructive"</Badge> + <Badge variant="outline">"Outline"</Badge> + <Badge variant="ghost">"Ghost"</Badge> + </div> + } +} diff --git a/docs/src/routes/components/badge/examples/mod.rs b/docs/src/routes/components/badge/examples/mod.rs index 0374fa99..9c47768b 100644 --- a/docs/src/routes/components/badge/examples/mod.rs +++ b/docs/src/routes/components/badge/examples/mod.rs @@ -1,9 +1,11 @@ mod badge; mod badge_icon; -mod badge_invalid; mod badge_link; +mod badge_spinner; +mod badge_variants; pub use badge::*; pub use badge_icon::*; -pub use badge_invalid::*; pub use badge_link::*; +pub use badge_spinner::*; +pub use badge_variants::*; diff --git a/docs/src/routes/components/breadcrumb/anatomy.rs b/docs/src/routes/components/breadcrumb/anatomy.rs index 9382ca5d..7ff83b84 100644 --- a/docs/src/routes/components/breadcrumb/anatomy.rs +++ b/docs/src/routes/components/breadcrumb/anatomy.rs @@ -1,12 +1,21 @@ use leptos::prelude::*; -use singlestage::breadcrumb::*; +use singlestage::*; #[component] pub fn BreadcrumbAnatomy() -> impl IntoView { view! { <Breadcrumb> - <BreadcrumbItem /> - <BreadcrumbSeparator /> + <BreadcrumbList> + <BreadcrumbItem> + <BreadcrumbLink /> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbEllipsis /> + </BreadcrumbItem> + <BreadcrumbSeparator>{icon!(icondata::LuDot)}</BreadcrumbSeparator> + <BreadcrumbPage /> + </BreadcrumbList> </Breadcrumb> } } diff --git a/docs/src/routes/components/breadcrumb/component.toml b/docs/src/routes/components/breadcrumb/component.toml index 5ca1a80f..b90a725e 100644 --- a/docs/src/routes/components/breadcrumb/component.toml +++ b/docs/src/routes/components/breadcrumb/component.toml @@ -4,9 +4,55 @@ description = "Displays the path to the current resource using a hierarchy of li [[examples]] name = "Breadcrumb" +[[examples]] +name = "Breadcrumb basic" +title = "Basic" +description = "A basic breadcrumb with a home link and a components link." + +[[examples]] +name = "Breadcrumb custom separator" +title = "Custom Separator" +description = "BreadcrumbSeparator can take children to create a custom separator." + +[[examples]] +name = "Breadcrumb dropdown" +title = "Dropdown" +description = "BreadcrumbItem and DropdownMenu can be composed." + +[[examples]] +name = "Breadcrumb collapsed" +title = "Collapsed" +description = "Use BreadcrumbEllipsis to show a collapsed state when the breadcrumb is too long." + [[references]] name = "Breadcrumb" -description = "Contains the contents of the breadcrumb." +description = "Contains all the items of the breadcrumb component." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "BreadcrumbEllipsis" +description = "Displays an ellipsis in place of collapsed breadcrumb items." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "BreadcrumbItem" +description = "An individual breadcrumb link." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +and <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#attributes">li</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "BreadcrumbList" +description = "Displays the ordered list of breadcrumb items." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ and <a class="singlestage-link" \ @@ -18,12 +64,10 @@ attrs = [ ] [[references]] -name = "BreadcrumbItem" -description = "An individual breadcrumb link." +name = "BreadcrumbPage" +description = "Displays the current page in the breadcrumb (non-clickable)." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ -and <a class="singlestage-link" \ -href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#attributes">li</a> \ attributes.""" attrs = [] diff --git a/docs/src/routes/components/breadcrumb/examples/breadcrumb.rs b/docs/src/routes/components/breadcrumb/examples/breadcrumb.rs index 3456a36a..42f44995 100644 --- a/docs/src/routes/components/breadcrumb/examples/breadcrumb.rs +++ b/docs/src/routes/components/breadcrumb/examples/breadcrumb.rs @@ -1,45 +1,45 @@ use leptos::prelude::*; -use singlestage::{Button, breadcrumb::*, dropdown::*, icon}; +use singlestage::*; #[component] pub fn BreadcrumbExample() -> impl IntoView { view! { <Breadcrumb> - <BreadcrumbItem> - <a href="#" class="hover:text-(--foreground) transition-colors"> - "Home" - </a> - </BreadcrumbItem> - <BreadcrumbSeparator /> - <BreadcrumbItem> - <DropdownMenu> - <DropdownMenuTrigger> - <Button - class="flex size-9 items-center justify-center h-4 w-4 hover:text-(--foreground) cursor-pointer" - variant="ghost" - > - {icon!(icondata::VsEllipsis)} - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent> - <DropdownMenuGroup> - <DropdownMenuItem>"Documentation"</DropdownMenuItem> - <DropdownMenuItem>"Themes"</DropdownMenuItem> - <DropdownMenuItem>"GitHub"</DropdownMenuItem> - </DropdownMenuGroup> - </DropdownMenuContent> - </DropdownMenu> - </BreadcrumbItem> - <BreadcrumbSeparator /> - <BreadcrumbItem> - <a href="#" class="hover:text-(--foreground) transition-colors"> - "Components" - </a> - </BreadcrumbItem> - <BreadcrumbSeparator /> - <BreadcrumbItem> - <span class="text-(--foreground) font-normal">"Breadcrumb"</span> - </BreadcrumbItem> + <BreadcrumbList> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="#">"Home"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <DropdownMenu> + <DropdownMenuTrigger> + <Button size="icon-sm" variant="ghost"> + <BreadcrumbEllipsis /> + <span class="sr-only">"Toggle menu"</span> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="start"> + <DropdownMenuGroup> + <DropdownMenuItem>"Documentation"</DropdownMenuItem> + <DropdownMenuItem>"Themes"</DropdownMenuItem> + <DropdownMenuItem>"GitHub"</DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="#">"Components"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbPage>"Breadcrumb"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> </Breadcrumb> } } diff --git a/docs/src/routes/components/breadcrumb/examples/breadcrumb_basic.rs b/docs/src/routes/components/breadcrumb/examples/breadcrumb_basic.rs new file mode 100644 index 00000000..ce4ee4d3 --- /dev/null +++ b/docs/src/routes/components/breadcrumb/examples/breadcrumb_basic.rs @@ -0,0 +1,27 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn BreadcrumbBasicExample() -> impl IntoView { + view! { + <Breadcrumb> + <BreadcrumbList> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="#">"Home"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="#">"Components"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbPage>"Breadcrumb"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + } +} diff --git a/docs/src/routes/components/breadcrumb/examples/breadcrumb_collapsed.rs b/docs/src/routes/components/breadcrumb/examples/breadcrumb_collapsed.rs new file mode 100644 index 00000000..1a13f5e3 --- /dev/null +++ b/docs/src/routes/components/breadcrumb/examples/breadcrumb_collapsed.rs @@ -0,0 +1,31 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn BreadcrumbCollapsedExample() -> impl IntoView { + view! { + <Breadcrumb> + <BreadcrumbList> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="/">"Home"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbEllipsis /> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="/docs/components">"Components"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbPage>"Breadcrumb"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + } +} diff --git a/docs/src/routes/components/breadcrumb/examples/breadcrumb_custom_separator.rs b/docs/src/routes/components/breadcrumb/examples/breadcrumb_custom_separator.rs new file mode 100644 index 00000000..0cedf6de --- /dev/null +++ b/docs/src/routes/components/breadcrumb/examples/breadcrumb_custom_separator.rs @@ -0,0 +1,27 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn BreadcrumbCustomSeparatorExample() -> impl IntoView { + view! { + <Breadcrumb> + <BreadcrumbList> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="/">"Home"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator>{icon!(icondata::LuDot)}</BreadcrumbSeparator> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="/components">"Components"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator>{icon!(icondata::LuDot)}</BreadcrumbSeparator> + <BreadcrumbItem> + <BreadcrumbPage>"Breadcrumb"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + } +} diff --git a/docs/src/routes/components/breadcrumb/examples/breadcrumb_dropdown.rs b/docs/src/routes/components/breadcrumb/examples/breadcrumb_dropdown.rs new file mode 100644 index 00000000..26350197 --- /dev/null +++ b/docs/src/routes/components/breadcrumb/examples/breadcrumb_dropdown.rs @@ -0,0 +1,41 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn BreadcrumbDropdownExample() -> impl IntoView { + view! { + <Breadcrumb> + <BreadcrumbList> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="/">Home</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator>{icon!(icondata::LuDot)}</BreadcrumbSeparator> + <BreadcrumbItem> + <DropdownMenu> + <DropdownMenuTrigger> + <BreadcrumbLink> + <Button class="flex items-center gap-1" as_child=true> + <span>"Components"</span> + {icon!(icondata::LuChevronDown, class="size-3.5")} + </Button> + </BreadcrumbLink> + </DropdownMenuTrigger> + <DropdownMenuContent align="start"> + <DropdownMenuGroup> + <DropdownMenuItem>"Documentation"</DropdownMenuItem> + <DropdownMenuItem>"Themes"</DropdownMenuItem> + <DropdownMenuItem>"GitHub"</DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + </BreadcrumbItem> + <BreadcrumbSeparator>{icon!(icondata::LuDot)}</BreadcrumbSeparator> + <BreadcrumbItem> + <BreadcrumbPage>"Breadcrumb"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + } +} diff --git a/docs/src/routes/components/breadcrumb/examples/mod.rs b/docs/src/routes/components/breadcrumb/examples/mod.rs index fa9f84ac..96c1e82d 100644 --- a/docs/src/routes/components/breadcrumb/examples/mod.rs +++ b/docs/src/routes/components/breadcrumb/examples/mod.rs @@ -1,2 +1,11 @@ mod breadcrumb; +mod breadcrumb_basic; +mod breadcrumb_collapsed; +mod breadcrumb_custom_separator; +mod breadcrumb_dropdown; + pub use breadcrumb::*; +pub use breadcrumb_basic::*; +pub use breadcrumb_collapsed::*; +pub use breadcrumb_custom_separator::*; +pub use breadcrumb_dropdown::*; diff --git a/docs/src/routes/components/button/component.toml b/docs/src/routes/components/button/component.toml index d1a67d8f..fd6cdfef 100644 --- a/docs/src/routes/components/button/component.toml +++ b/docs/src/routes/components/button/component.toml @@ -5,17 +5,58 @@ description = "Displays a button." name = "Button" [[examples]] -name = "Button Sizes" -title = "Button Sizes" +name = "Button Size" +title = "Button Size" +description = "Use the size prop to change the size of the button." + +[[examples]] +name = "Button default" +title = "Default" + +[[examples]] +name = "Button outline" +title = "Outline" + +[[examples]] +name = "Button secondary" +title = "Secondary" + +[[examples]] +name = "Button ghost" +title = "Ghost" + +[[examples]] +name = "Button destructive" +title = "Destructive" + +[[examples]] +name = "Button link" +title = "Link" [[examples]] name = "Button Icon" -title = "Button Icon" +title = "Icon" [[examples]] -name = "Button With Icon" +name = "Button with icon" title = "With Icon" +[[examples]] +name = "button rounded" +title = "Rounded" + +[[examples]] +name = "button spinner" +title = "Spinner" + +[[examples]] +name = "button button group" +title = "Button Group" + +[[examples]] +name = "button link as button" +title = "Link" + [[references]] name = "Button" description = "Creates a button." @@ -25,10 +66,17 @@ and <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#attributes">button</a> \ attributes.""" attrs = [ - { attr = "button_type", attr_type = "String", default = """"submit"""", description = """Renamed <button> \ + { attr = "as_child", attr_type = "bool", default = """false""", description = """This component will \ + render without styling.""" }, + { attr = "button_type", attr_type = "String", default = """"submit"""", description = """Renamed button \ `type` attribute to avoid name collisions.""" }, + { attr = "dismiss", attr_type = "bool", default = """false""", description = """Toggle whether clicking \ + this button dismisses its parent popover.""" }, + { attr = "invalid", attr_type = "bool", default = "false", description = """Toggle whether or not \ + the input is disabled.""" }, { attr = "size", attr_type = "String", default = """""""", description = """Specify the size to \ - render the button.""" }, + render the button. Accepted values: "small" | "large" | "icon" | "sm-icon" | "lg-icon"""" }, { attr = "variant", attr_type = "String", default = """"primary"""", description = """Specify \ - the style of button to render.""" }, + the style of button to render. Accepted values: "primary" | "secondary" | "outline" | "ghost" \ + | "link" | "destructive"""" }, ] diff --git a/docs/src/routes/components/button/examples/button.rs b/docs/src/routes/components/button/examples/button.rs index 36b366bb..9c3a8ac1 100644 --- a/docs/src/routes/components/button/examples/button.rs +++ b/docs/src/routes/components/button/examples/button.rs @@ -1,16 +1,14 @@ use leptos::prelude::*; -use singlestage::button::*; +use singlestage::*; #[component] pub fn ButtonExample() -> impl IntoView { view! { - <div class="space-x-2"> - <Button>"Primary"</Button> - <Button variant="secondary">"Secondary"</Button> - <Button variant="outline">"Outline"</Button> - <Button variant="ghost">"Ghost"</Button> - <Button variant="link">"Link"</Button> - <Button variant="destructive">"Destructive"</Button> + <div class="flex flex-wrap gap-2"> + <Button variant="outline">Button</Button> + <Button variant="outline" size="icon" aria_label="Submit"> + {icon!(icondata::LuArrowUp)} + </Button> </div> } } diff --git a/docs/src/routes/components/button/examples/button_button_group.rs b/docs/src/routes/components/button/examples/button_button_group.rs new file mode 100644 index 00000000..765a566b --- /dev/null +++ b/docs/src/routes/components/button/examples/button_button_group.rs @@ -0,0 +1,75 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonButtonGroupExample() -> impl IntoView { + let label = RwSignal::new("personal".to_string()); + + view! { + <ButtonGroup> + <ButtonGroup> + <Button variant="outline" size="icon" aria_label="Go Back"> + {icon!(icondata::LuArrowLeft)} + </Button> + </ButtonGroup> + <ButtonGroup> + <Button variant="outline">"Archive"</Button> + <Button variant="outline">"Report"</Button> + </ButtonGroup> + <ButtonGroup> + <Button variant="outline">"Snooze"</Button> + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline" size="icon" aria_label="More Options"> + {icon!(icondata::FiMoreHorizontal)} + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" class="w-40"> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::LuMailCheck)} "Mark as Read" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuArchive)} "Archive" + </DropdownMenuItem> + </DropdownMenuGroup> + <Separator /> + <DropdownMenuGroup> + <DropdownMenuItem>{icon!(icondata::LuClock)} "Snooze"</DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuCalendarPlus)} "Add to Calendar" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuListFilter)} "Add to List" + </DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger> + {icon!(icondata::LuTag)} "Label As..." + </DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuRadioGroup value=label> + <DropdownMenuRadioItem value="personal"> + "Personal" + </DropdownMenuRadioItem> + <DropdownMenuRadioItem value="work"> + "Work" + </DropdownMenuRadioItem> + <DropdownMenuRadioItem value="other"> + "Other" + </DropdownMenuRadioItem> + </DropdownMenuRadioGroup> + </DropdownMenuSubContent> + </DropdownMenuSub> + </DropdownMenuGroup> + <Separator /> + <DropdownMenuGroup> + <DropdownMenuItem variant="destructive"> + {icon!(icondata::LuTrash2)} "Trash" + </DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + </ButtonGroup> + </ButtonGroup> + } +} diff --git a/docs/src/routes/components/button/examples/button_default.rs b/docs/src/routes/components/button/examples/button_default.rs new file mode 100644 index 00000000..ef40f4b3 --- /dev/null +++ b/docs/src/routes/components/button/examples/button_default.rs @@ -0,0 +1,7 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonDefaultExample() -> impl IntoView { + view! { <Button>"Button"</Button> } +} diff --git a/docs/src/routes/components/button/examples/button_destructive.rs b/docs/src/routes/components/button/examples/button_destructive.rs new file mode 100644 index 00000000..95b6a795 --- /dev/null +++ b/docs/src/routes/components/button/examples/button_destructive.rs @@ -0,0 +1,7 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonDestructiveExample() -> impl IntoView { + view! { <Button variant="destructive">"Destructive"</Button> } +} diff --git a/docs/src/routes/components/button/examples/button_ghost.rs b/docs/src/routes/components/button/examples/button_ghost.rs new file mode 100644 index 00000000..27cea990 --- /dev/null +++ b/docs/src/routes/components/button/examples/button_ghost.rs @@ -0,0 +1,7 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonGhostExample() -> impl IntoView { + view! { <Button variant="ghost">"Ghost"</Button> } +} diff --git a/docs/src/routes/components/button/examples/button_icon.rs b/docs/src/routes/components/button/examples/button_icon.rs index 3b82e4df..6dc0ca6c 100644 --- a/docs/src/routes/components/button/examples/button_icon.rs +++ b/docs/src/routes/components/button/examples/button_icon.rs @@ -1,20 +1,11 @@ -use icondata::LuMenu; use leptos::prelude::*; -use singlestage::{icon, Button}; +use singlestage::*; #[component] pub fn ButtonIconExample() -> impl IntoView { view! { - <div class="space-x-2 sm:space-x-12"> - <Button size="sm-icon" variant="primary"> - {icon!(LuMenu)} - </Button> - <Button size="icon" variant="primary"> - {icon!(LuMenu)} - </Button> - <Button size="lg-icon" variant="primary"> - {icon!(LuMenu)} - </Button> - </div> + <Button variant="outline" size="icon"> + {icon!(icondata::LuCircleFadingArrowUp)} + </Button> } } diff --git a/docs/src/routes/components/button/examples/button_link.rs b/docs/src/routes/components/button/examples/button_link.rs new file mode 100644 index 00000000..d82d8b0f --- /dev/null +++ b/docs/src/routes/components/button/examples/button_link.rs @@ -0,0 +1,7 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonLinkExample() -> impl IntoView { + view! { <Button variant="link">"Link"</Button> } +} diff --git a/docs/src/routes/components/button/examples/button_link_as_button.rs b/docs/src/routes/components/button/examples/button_link_as_button.rs new file mode 100644 index 00000000..73d97d7d --- /dev/null +++ b/docs/src/routes/components/button/examples/button_link_as_button.rs @@ -0,0 +1,11 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonLinkAsButtonExample() -> impl IntoView { + view! { + <Link render_as="button" href="/login"> + "Log in" + </Link> + } +} diff --git a/docs/src/routes/components/button/examples/button_outline.rs b/docs/src/routes/components/button/examples/button_outline.rs new file mode 100644 index 00000000..f0b0ec1e --- /dev/null +++ b/docs/src/routes/components/button/examples/button_outline.rs @@ -0,0 +1,7 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonOutlineExample() -> impl IntoView { + view! { <Button variant="outline">"Outline"</Button> } +} diff --git a/docs/src/routes/components/button/examples/button_rounded.rs b/docs/src/routes/components/button/examples/button_rounded.rs new file mode 100644 index 00000000..23967ae3 --- /dev/null +++ b/docs/src/routes/components/button/examples/button_rounded.rs @@ -0,0 +1,11 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonRoundedExample() -> impl IntoView { + view! { + <Button variant="outline" size="icon" class="rounded-full"> + {icon!(icondata::LuArrowUp)} + </Button> + } +} diff --git a/docs/src/routes/components/button/examples/button_secondary.rs b/docs/src/routes/components/button/examples/button_secondary.rs new file mode 100644 index 00000000..fd6b0c00 --- /dev/null +++ b/docs/src/routes/components/button/examples/button_secondary.rs @@ -0,0 +1,7 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonSecondaryExample() -> impl IntoView { + view! { <Button variant="secondary">"Secondary"</Button> } +} diff --git a/docs/src/routes/components/button/examples/button_size.rs b/docs/src/routes/components/button/examples/button_size.rs new file mode 100644 index 00000000..a54a79bf --- /dev/null +++ b/docs/src/routes/components/button/examples/button_size.rs @@ -0,0 +1,40 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonSizeExample() -> impl IntoView { + view! { + <div class="flex flex-col items-start gap-8 sm:flex-row"> + <div class="flex items-start gap-2"> + <Button size="xs" variant="outline"> + "Extra Small" + </Button> + <Button size="icon-xs" aria_label="Submit" variant="outline"> + {icon!(icondata::LuArrowUpRight)} + </Button> + </div> + <div class="flex items-start gap-2"> + <Button size="sm" variant="outline"> + "Small" + </Button> + <Button size="icon-sm" aria_label="Submit" variant="outline"> + {icon!(icondata::LuArrowUpRight)} + </Button> + </div> + <div class="flex items-start gap-2"> + <Button variant="outline">Default</Button> + <Button size="icon" aria_label="Submit" variant="outline"> + {icon!(icondata::LuArrowUpRight)} + </Button> + </div> + <div class="flex items-start gap-2"> + <Button variant="outline" size="lg"> + "Large" + </Button> + <Button size="icon-lg" aria_label="Submit" variant="outline"> + {icon!(icondata::LuArrowUpRight)} + </Button> + </div> + </div> + } +} diff --git a/docs/src/routes/components/button/examples/button_sizes.rs b/docs/src/routes/components/button/examples/button_sizes.rs deleted file mode 100644 index 643561c6..00000000 --- a/docs/src/routes/components/button/examples/button_sizes.rs +++ /dev/null @@ -1,19 +0,0 @@ -use leptos::prelude::*; -use singlestage::*; - -#[component] -pub fn ButtonSizesExample() -> impl IntoView { - view! { - <div class="space-x-2 sm:space-x-12"> - <Button size="small" variant="primary"> - "Small" - </Button> - <Button size="normal" variant="primary"> - "Normal" - </Button> - <Button size="large" variant="primary"> - "Large" - </Button> - </div> - } -} diff --git a/docs/src/routes/components/button/examples/button_spinner.rs b/docs/src/routes/components/button/examples/button_spinner.rs new file mode 100644 index 00000000..8adee5ff --- /dev/null +++ b/docs/src/routes/components/button/examples/button_spinner.rs @@ -0,0 +1,18 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ButtonSpinnerExample() -> impl IntoView { + view! { + <div class="flex gap-2"> + <Button variant="outline" disabled=true> + <Spinner /> + "Generating" + </Button> + <Button variant="secondary" disabled=true> + "Downloading" + <Spinner /> + </Button> + </div> + } +} diff --git a/docs/src/routes/components/button/examples/button_with_icon.rs b/docs/src/routes/components/button/examples/button_with_icon.rs index d2302faf..fc368523 100644 --- a/docs/src/routes/components/button/examples/button_with_icon.rs +++ b/docs/src/routes/components/button/examples/button_with_icon.rs @@ -1,20 +1,12 @@ -use icondata::LuMenu; use leptos::prelude::*; -use singlestage::{icon, Button}; +use singlestage::*; #[component] pub fn ButtonWithIconExample() -> impl IntoView { view! { - <div class="space-x-2 sm:space-x-12"> - <Button size="small" variant="primary"> - {icon!(LuMenu)} - "Menu" - </Button> - <Button variant="primary">{icon!(LuMenu)} "Menu"</Button> - <Button size="large" variant="primary"> - {icon!(LuMenu)} - "Menu" - </Button> - </div> + <Button variant="outline" size="sm"> + {icon!(icondata::LuGitBranch)} + <span>"New Branch"</span> + </Button> } } diff --git a/docs/src/routes/components/button/examples/mod.rs b/docs/src/routes/components/button/examples/mod.rs index 3a01fdd3..64968c01 100644 --- a/docs/src/routes/components/button/examples/mod.rs +++ b/docs/src/routes/components/button/examples/mod.rs @@ -1,9 +1,29 @@ mod button; +mod button_button_group; +mod button_default; +mod button_destructive; +mod button_ghost; mod button_icon; -mod button_sizes; +mod button_link; +mod button_link_as_button; +mod button_outline; +mod button_rounded; +mod button_secondary; +mod button_size; +mod button_spinner; mod button_with_icon; pub use button::*; +pub use button_button_group::*; +pub use button_default::*; +pub use button_destructive::*; +pub use button_ghost::*; pub use button_icon::*; -pub use button_sizes::*; +pub use button_link::*; +pub use button_link_as_button::*; +pub use button_outline::*; +pub use button_rounded::*; +pub use button_secondary::*; +pub use button_size::*; +pub use button_spinner::*; pub use button_with_icon::*; diff --git a/docs/src/routes/components/button_group/component.toml b/docs/src/routes/components/button_group/component.toml index 2c098d56..b9934656 100644 --- a/docs/src/routes/components/button_group/component.toml +++ b/docs/src/routes/components/button_group/component.toml @@ -22,15 +22,13 @@ description = "Nest <ButtonGroup> components to create button groups with spacin [[examples]] name = "button group separator" title = "Separator" -description = """The ButtonGroupSeparator component visually divides buttons within a group. \ -\ -Buttons with variant outline do not need a separator since they have a border. For other variants, \ -a separator is recommended to improve the visual hierarchy.""" +description = """Some buttons don't need a separator since they have a border. For other variants \ +use a Separator to improve the visual hierarchy.""" [[examples]] name = "button group split" title = "Split" -description = "Create a split button group by adding two buttons separated by a ButtonGroupSeparator." +description = "Create a split button group using a Separator." [[examples]] name = "button group input" @@ -64,17 +62,20 @@ extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [ - { attr = "vertical", attr_type = "bool", default = "false", description = """Sets the display \ - direction of the ButtonGroup.""" }, + { attr = "orientation", attr_type = "String", default = """""""", description = """Sets the \ + display direction of the ButtonGroup.""" }, ] [[references]] name = "ButtonGroupSeparator" -description = "Visually separates elements in the ButtonGroup." +description = "Visually separates button group buttons." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" -attrs = [] +attrs = [ + { attr = "orientation", attr_type = "String", default = """""""", description = """Sets the \ + display direction of the separator.""" }, +] [[references]] name = "ButtonGroupText" 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..4456532f 100644 --- a/docs/src/routes/components/button_group/examples/button_group.rs +++ b/docs/src/routes/components/button_group/examples/button_group.rs @@ -3,11 +3,13 @@ use singlestage::*; #[component] pub fn ButtonGroupExample() -> impl IntoView { + let label = RwSignal::new("personal".to_string()); + view! { <ButtonGroup> <ButtonGroup class="flex"> <Button variant="outline" size="icon" aria_label="Go Back"> - {icon!(icondata::LuArrowLeft, width=24, height=24)} + {icon!(icondata::LuArrowLeft)} </Button> </ButtonGroup> <ButtonGroup> @@ -22,7 +24,7 @@ pub fn ButtonGroupExample() -> impl IntoView { {icon!(icondata::FiMoreHorizontal)} </Button> </DropdownMenuTrigger> - <DropdownMenuContent class="w-52"> + <DropdownMenuContent align="end"> <DropdownMenuGroup> <DropdownMenuItem> {icon!(icondata::LuMailCheck)} "Mark as Read" @@ -40,6 +42,24 @@ pub fn ButtonGroupExample() -> impl IntoView { <DropdownMenuItem> {icon!(icondata::LuListPlus)} "Add to List" </DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger> + {icon!(icondata::LuTag)} "Label As..." + </DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuRadioGroup value=label> + <DropdownMenuRadioItem value="personal"> + "Personal" + </DropdownMenuRadioItem> + <DropdownMenuRadioItem value="work"> + "Work" + </DropdownMenuRadioItem> + <DropdownMenuRadioItem value="other"> + "Other" + </DropdownMenuRadioItem> + </DropdownMenuRadioGroup> + </DropdownMenuSubContent> + </DropdownMenuSub> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> 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..c7c2efd9 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,11 @@ pub fn ButtonGroupDropdownExample() -> impl IntoView { <Button variant="outline">"Follow"</Button> <DropdownMenu> <DropdownMenuTrigger> - <Button variant="outline" class="!pl-2"> + <Button variant="outline" class="!px-2"> {icon!(icondata::LuChevronDown)} </Button> </DropdownMenuTrigger> - <DropdownMenuContent class="[--radius:1rem]"> + <DropdownMenuContent align="end"> <DropdownMenuGroup> <DropdownMenuItem> {icon!(icondata::BiVolumeMuteRegular)} "Mute Conversation" diff --git a/docs/src/routes/components/button_group/examples/button_group_input_group.rs b/docs/src/routes/components/button_group/examples/button_group_input_group.rs index c9c02b50..591364d1 100644 --- a/docs/src/routes/components/button_group/examples/button_group_input_group.rs +++ b/docs/src/routes/components/button_group/examples/button_group_input_group.rs @@ -10,12 +10,13 @@ pub fn ButtonGroupInputGroupExample() -> impl IntoView { Effect::new(move || { class.set(format!( - "rounded-full{}", + "rounded-full {}", if voice_enabled.get() { + // TODO: make a reactive signal for the current preferred mode match theme.mode.get() { - Mode::Dark => " bg-orange-800 text-orange-100", - Mode::Light => " bg-orange-100 text-orange-700", - _ => " bg-orange-100 text-orange-700 dark:bg-orange-800 dark:text-orange-100", + Mode::Dark => "bg-orange-800 text-orange-100", + Mode::Light => "bg-orange-100 text-orange-700", + _ => "bg-orange-100 text-orange-700 dark:bg-orange-800 dark:text-orange-100", } } else { "" @@ -45,14 +46,19 @@ pub fn ButtonGroupInputGroupExample() -> impl IntoView { <InputGroup class="rounded-full"> <Input placeholder disabled=voice_enabled /> <InputGroupAddon align="inline-end"> - <Tooltip value="Voice Mode"> - <Button - on:click=move |_| voice_enabled.set(!voice_enabled.get()) - size="icon-xs" - class - > - {icon!(icondata::LuAudioLines)} - </Button> + <Tooltip> + <TooltipTrigger> + <Button + on:click=move |_| voice_enabled.set(!voice_enabled.get_untracked()) + size="icon-xs" + class + > + {icon!(icondata::LuAudioLines)} + </Button> + </TooltipTrigger> + <TooltipContent> + <p>"Voice Mode"</p> + </TooltipContent> </Tooltip> </InputGroupAddon> </InputGroup> diff --git a/docs/src/routes/components/button_group/examples/button_group_orientation.rs b/docs/src/routes/components/button_group/examples/button_group_orientation.rs index 62486792..29e4983e 100644 --- a/docs/src/routes/components/button_group/examples/button_group_orientation.rs +++ b/docs/src/routes/components/button_group/examples/button_group_orientation.rs @@ -4,7 +4,7 @@ use singlestage::*; #[component] pub fn ButtonGroupOrientationExample() -> impl IntoView { view! { - <ButtonGroup vertical=true aria_label="Media controls" class="h-fit"> + <ButtonGroup orientation="vertical" aria_label="Media controls" class="h-fit"> <Button variant="outline" size="icon"> {icon!(icondata::LuPlus)} </Button> 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)} </Button> </PopoverTrigger> - <PopoverContent class="rounded-xl p-0 text-sm max-w-72"> - <p class="px-4 pt-3 text-sm font-medium">"Agent Tasks"</p> - <Separator /> - <div class="p-4 pt-0 text-sm *:[p:not(:last-child)]:mb-2"> - <Textarea - placeholder="Describe your task in natural language." - class="mb-4 resize-none" - /> - <p class="font-medium">"Start a new task with Copilot"</p> - <p class="text-(--muted-foreground)"> - "Describe your task in natural language. Copilot will work in the - background and open a pull request for your review." - </p> - </div> + <PopoverContent align="end"> + <PopoverHeader> + <PopoverTitle>"Start a new task with Copilot"</PopoverTitle> + <PopoverDescription> + "Describe your task in natural language." + </PopoverDescription> + </PopoverHeader> + <Field> + <FieldLabel label_for="task" class="sr-only"> + "Task Description" + </FieldLabel> + <Textarea id="task" placeholder="I need to..." class="resize-none" /> + <FieldDescription> + "Copilot will open a pull request for review." + </FieldDescription> + </Field> </PopoverContent> </Popover> </ButtonGroup> diff --git a/docs/src/routes/components/button_group/examples/button_group_select.rs b/docs/src/routes/components/button_group/examples/button_group_select.rs index c406d002..8d47e5ed 100644 --- a/docs/src/routes/components/button_group/examples/button_group_select.rs +++ b/docs/src/routes/components/button_group/examples/button_group_select.rs @@ -9,11 +9,11 @@ pub fn ButtonGroupSelectExample() -> impl IntoView { <ButtonGroup> <ButtonGroup> <Select value=currency> - <SelectContent label="Currency"> - <SelectItem value="$">"$"</SelectItem> - <SelectItem value="€">"€"</SelectItem> - <SelectItem value="£">"£"</SelectItem> - </SelectContent> + <SelectOptGroup label="Currency"> + <SelectOption value="$">"$"</SelectOption> + <SelectOption value="€">"€"</SelectOption> + <SelectOption value="£">"£"</SelectOption> + </SelectOptGroup> </Select> <Input placeholder="10.00" pattern="[0-9]*" /> </ButtonGroup> diff --git a/docs/src/routes/components/card/anatomy.rs b/docs/src/routes/components/card/anatomy.rs index a2e79a2e..6a10e330 100644 --- a/docs/src/routes/components/card/anatomy.rs +++ b/docs/src/routes/components/card/anatomy.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::card::*; +use singlestage::*; #[component] pub fn CardAnatomy() -> impl IntoView { @@ -8,6 +8,7 @@ pub fn CardAnatomy() -> impl IntoView { <CardHeader> <CardTitle /> <CardDescription /> + <CardAction /> </CardHeader> <CardContent /> <CardFooter /> diff --git a/docs/src/routes/components/card/component.toml b/docs/src/routes/components/card/component.toml index ef5c1034..90403829 100644 --- a/docs/src/routes/components/card/component.toml +++ b/docs/src/routes/components/card/component.toml @@ -4,12 +4,34 @@ description = "Displays a card with header, content, and footer." [[examples]] name = "card" +[[examples]] +name = "card size" +title = "Size" +description = """Use the size prop to change the size of the card. The small size variant uses \ +smaller spacing.""" + +[[examples]] +name = "card image" +title = "Image" +description = "Add an image before the card header to create a card with an image." + [[references]] name = "Card" description = "Contains all the parts of a card." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" +attrs = [ + { attr = "size", attr_type = "String", default = """""""", description = """Specify the size to \ + render the card. Accepted values: "sm"""" }, +] + +[[references]] +name = "CardAction" +description = "Renders content like a button or a badge in the top-right of the header." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" attrs = [] [[references]] diff --git a/docs/src/routes/components/card/examples/card.rs b/docs/src/routes/components/card/examples/card.rs index b15a796c..bb7eb3aa 100644 --- a/docs/src/routes/components/card/examples/card.rs +++ b/docs/src/routes/components/card/examples/card.rs @@ -1,48 +1,45 @@ use leptos::prelude::*; -use singlestage::{card::*, Button, Input, Label}; +use singlestage::*; #[component] pub fn CardExample() -> impl IntoView { view! { - <Card class="w-full sm:w-sm"> + <Card class="max-w-sm"> <CardHeader> <CardTitle>"Log in to your account"</CardTitle> <CardDescription> - "Enter your details below to log in to your account" + "Enter your email below to Log in to your account" </CardDescription> + <CardAction> + <Button variant="link">"Sign Up"</Button> + </CardAction> </CardHeader> <CardContent> - <form class="form grid gap-6"> - <div class="grid gap-2"> - <Label label_for="demo-card-form-email">"Email"</Label> - <Input input_type="email" id="demo-card-form-email" /> - </div> - <div class="grid gap-2"> - <div class="flex items-center gap-2"> - <Label label_for="demo-card-form-password">"Password"</Label> - <a + <FieldSet> + <Field> + <Input input_type="email" placeholder="m@example.com"> + "Email" + </Input> + </Field> + <Field> + <div class="flex"> + <FieldLabel>"Password"</FieldLabel> + <Link href="#" - class="ml-auto inline-block text-sm underline-offset-4 hover:underline" + class="ml-auto text-sm font-normal no-underline hover:underline" > "Forgot your password?" - </a> + </Link> </div> - <Input input_type="password" id="demo-card-form-password" /> - </div> - </form> + <Input input_type="password" /> + </Field> + </FieldSet> </CardContent> - <CardFooter class="flex flex-col items-center gap-2"> - <Button button_type="button" class="w-full"> - "Log in" - </Button> - <Button button_type="button" variant="outline" class="w-full"> + <CardFooter class="flex-col gap-2 border-t"> + <Button class="w-full">"Log in"</Button> + <Button variant="outline" class="w-full"> "Log in with Google" </Button> - <p class="mt-4 text-center text-sm"> - "Don't have an account? "<a href="#" class="underline-offset-4 hover:underline"> - "Sign up" - </a> - </p> </CardFooter> </Card> } diff --git a/docs/src/routes/components/card/examples/card_image.rs b/docs/src/routes/components/card/examples/card_image.rs new file mode 100644 index 00000000..4a709fe4 --- /dev/null +++ b/docs/src/routes/components/card/examples/card_image.rs @@ -0,0 +1,40 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CardImageExample() -> impl IntoView { + let theme_provider = expect_context::<ThemeProviderContext>(); + + view! { + <Card class="relative max-w-sm"> + <img + src="https://avatar.vercel.sh/shadcn1" + alt="Event cover" + class=move || { + format!( + "aspect-video w-full object-cover grayscale {}", + match theme_provider.mode.get() { + Mode::Auto => "brightness-60 dark:brightness-40", + Mode::Dark => "brightness-40", + _ => "brightness-60", + }, + ) + } + /> + <div class="absolute inset-0 aspect-video bg-black/35" /> + <CardHeader> + <CardAction> + <Badge variant="secondary">"Featured"</Badge> + </CardAction> + <CardTitle>"Design systems meetup"</CardTitle> + <CardDescription> + "A practical talk on component APIs, accessibility, and shipping + faster." + </CardDescription> + </CardHeader> + <CardFooter> + <Button class="w-full">"View Event"</Button> + </CardFooter> + </Card> + } +} diff --git a/docs/src/routes/components/card/examples/card_size.rs b/docs/src/routes/components/card/examples/card_size.rs new file mode 100644 index 00000000..ef1aa2b0 --- /dev/null +++ b/docs/src/routes/components/card/examples/card_size.rs @@ -0,0 +1,25 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CardSizeExample() -> impl IntoView { + view! { + <Card size="sm" class="max-w-sm"> + <CardHeader> + <CardTitle>"Small Card"</CardTitle> + <CardDescription>"This card uses the small size variant."</CardDescription> + </CardHeader> + <CardContent> + <p> + "The card component supports a size prop that can be set to + \"sm\" for a more compact appearance." + </p> + </CardContent> + <CardFooter> + <Button variant="outline" size="sm" class="w-full"> + "Action" + </Button> + </CardFooter> + </Card> + } +} diff --git a/docs/src/routes/components/card/examples/mod.rs b/docs/src/routes/components/card/examples/mod.rs index 01a0d482..1455ba89 100644 --- a/docs/src/routes/components/card/examples/mod.rs +++ b/docs/src/routes/components/card/examples/mod.rs @@ -1,2 +1,7 @@ mod card; +mod card_image; +mod card_size; + pub use card::*; +pub use card_image::*; +pub use card_size::*; diff --git a/docs/src/routes/components/carousel/examples/carousel.rs b/docs/src/routes/components/carousel/examples/carousel.rs index 563b3ca6..e754fec5 100644 --- a/docs/src/routes/components/carousel/examples/carousel.rs +++ b/docs/src/routes/components/carousel/examples/carousel.rs @@ -6,7 +6,7 @@ pub fn CarouselExample() -> impl IntoView { let carousel_items = RwSignal::new((1..6).collect::<Vec<usize>>()); view! { - <Carousel class="w-full max-w-xs"> + <Carousel class="max-w-xs"> <CarouselContent> <For each=move || carousel_items.get() key=|item| *item let(item)> <CarouselItem> diff --git a/docs/src/routes/components/checkbox/component.toml b/docs/src/routes/components/checkbox/component.toml index d40ae5dc..f6fc0c7a 100644 --- a/docs/src/routes/components/checkbox/component.toml +++ b/docs/src/routes/components/checkbox/component.toml @@ -5,14 +5,33 @@ description = "A control that allows the user to toggle between checked and not name = "checkbox" [[examples]] -name = "subtext" +name = "checkbox invalid" +title = "Invalid State" +description = "Set invalid on either the Checkbox or a Field wrapper to display an invalid state." [[examples]] -name = "form" +name = "checkbox basic" +title = "Basic" +description = "Pair the checkbox with Field and Label for proper layout and labeling." [[examples]] -name = "group" +name = "checkbox description" +title = "Description" +description = "Use FieldContent and FieldDescription for helper text." + +[[examples]] +name = "checkbox disabled" +title = "Disabled" +description = "Use the disabled prop to prevent interaction." + +[[examples]] +name = "checkbox group" title = "Checkbox Group" +description = "Use multiple fields to create a checkbox list." + +[[examples]] +name = "checkbox table" +title = "Table" [[references]] name = "Checkbox" @@ -25,8 +44,10 @@ and <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/checkbox#additional_attributes">checkbox</a> \ attributes.""" attrs = [ - { attr = "checked", attr_type = "Reactive<bool>", default = "false", description = """A \ - reactive signal coupled to the checkbox's checked value.""" }, + { attr = "checked", attr_type = "bool", default = "false", description = """A reactive signal \ + coupled to the checkbox's checked state.""" }, + { attr = "invalid", attr_type = "bool", default = "false", description = """A reactive signal \ + coupled to the checkbox's invalid state.""" }, ] [[references]] @@ -38,8 +59,8 @@ and <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/fieldset#attributes">fieldset</a> \ attributes.""" attrs = [ - { attr = "invalid", attr_type = "Reactive<bool>", default = "false", description = """A \ - reactive signal that toggles whether all the children Checkboxes appear invalid or not.""" }, - { attr = "value", attr_type = "Reactive<Vec<String>>", default = "[]", description = """A \ - dynamic list of all the values of checked children Checkboxes.""" }, + { attr = "invalid", attr_type = "bool", default = "false", description = """A reactive signal \ + that toggles whether all the children Checkboxes appear invalid or not.""" }, + { attr = "value", attr_type = "Vec<String>", default = "[]", description = """A dynamic list of \ + all the values of checked children Checkboxes.""" }, ] diff --git a/docs/src/routes/components/checkbox/examples/checkbox.rs b/docs/src/routes/components/checkbox/examples/checkbox.rs index 3cc99a4d..88818822 100644 --- a/docs/src/routes/components/checkbox/examples/checkbox.rs +++ b/docs/src/routes/components/checkbox/examples/checkbox.rs @@ -1,7 +1,39 @@ use leptos::prelude::*; -use singlestage::checkbox::*; +use singlestage::*; #[component] pub fn CheckboxExample() -> impl IntoView { - view! { <Checkbox>"Accept terms and conditions"</Checkbox> } + view! { + <FieldGroup class="max-w-sm"> + <Field orientation="horizontal"> + <Checkbox>"Accept terms and conditions"</Checkbox> + </Field> + + <Field orientation="horizontal"> + <Checkbox checked=true /> + <FieldContent> + <FieldLabel>"Accept terms and conditions"</FieldLabel> + <FieldDescription> + "By clicking this checkbox, you agree to the terms." + </FieldDescription> + </FieldContent> + </Field> + + <Field orientation="horizontal" disabled=true> + <Checkbox>"Enable notifications"</Checkbox> + </Field> + + <FieldLabel> + <Field orientation="horizontal"> + <Checkbox /> + <FieldContent> + <FieldTitle>"Enable notifications"</FieldTitle> + <FieldDescription> + "You can enable or disable notifications at any time." + </FieldDescription> + </FieldContent> + </Field> + </FieldLabel> + </FieldGroup> + } } diff --git a/docs/src/routes/components/checkbox/examples/checkbox_basic.rs b/docs/src/routes/components/checkbox/examples/checkbox_basic.rs new file mode 100644 index 00000000..2d2dffca --- /dev/null +++ b/docs/src/routes/components/checkbox/examples/checkbox_basic.rs @@ -0,0 +1,25 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CheckboxBasicExample() -> impl IntoView { + view! { + <FieldGroup class="w-56"> + <Field orientation="horizontal"> + <Checkbox>"Accept terms and conditions"</Checkbox> + </Field> + + <Field orientation="horizontal"> + <Checkbox /> + <FieldLabel>"Accept terms and conditions"</FieldLabel> + </Field> + + <Field orientation="horizontal"> + <Checkbox id="terms-checkbox-basic" /> + <FieldLabel label_for="terms-checkbox-basic"> + "Accept terms and conditions" + </FieldLabel> + </Field> + </FieldGroup> + } +} diff --git a/docs/src/routes/components/checkbox/examples/checkbox_description.rs b/docs/src/routes/components/checkbox/examples/checkbox_description.rs new file mode 100644 index 00000000..0a1f204f --- /dev/null +++ b/docs/src/routes/components/checkbox/examples/checkbox_description.rs @@ -0,0 +1,21 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CheckboxDescriptionExample() -> impl IntoView { + view! { + <FieldGroup class="w-72"> + <Field orientation="horizontal"> + <Checkbox id="terms-checkbox-desc" checked=true /> + <FieldContent> + <FieldLabel label_for="terms-checkbox-desc"> + "Accept terms and conditions" + </FieldLabel> + <FieldDescription> + "By clicking this checkbox, you agree to the terms and conditions." + </FieldDescription> + </FieldContent> + </Field> + </FieldGroup> + } +} diff --git a/docs/src/routes/components/checkbox/examples/checkbox_disabled.rs b/docs/src/routes/components/checkbox/examples/checkbox_disabled.rs new file mode 100644 index 00000000..d1ede439 --- /dev/null +++ b/docs/src/routes/components/checkbox/examples/checkbox_disabled.rs @@ -0,0 +1,24 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CheckboxDisabledExample() -> impl IntoView { + view! { + <FieldGroup class="mx-auto w-56"> + <Checkbox disabled=true>"Enable notifications"</Checkbox> + + // Checkboxes defer to their outer-most parent's disabled state + <Field orientation="horizontal" disabled=true> + <Checkbox>"Enable notifications"</Checkbox> + </Field> + + <CheckboxGroup disabled=true> + <Field orientation="horizontal"> + <Checkbox>"Enable notifications"</Checkbox> + </Field> + + <Checkbox>"Enable notifications"</Checkbox> + </CheckboxGroup> + </FieldGroup> + } +} diff --git a/docs/src/routes/components/checkbox/examples/checkbox_group.rs b/docs/src/routes/components/checkbox/examples/checkbox_group.rs new file mode 100644 index 00000000..7310a99e --- /dev/null +++ b/docs/src/routes/components/checkbox/examples/checkbox_group.rs @@ -0,0 +1,32 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CheckboxGroupExample() -> impl IntoView { + let value = RwSignal::new(vec!["hard_disks".to_string(), "external_disks".to_string()]); + + view! { + <FieldSet> + <FieldLegend variant="label">"Show these items on the desktop:"</FieldLegend> + <FieldDescription>"Select the items you want to show on the desktop."</FieldDescription> + + <CheckboxGroup class="gap-3" value> + <Field orientation="horizontal"> + <Checkbox value="hard_disks">"Hard disks"</Checkbox> + </Field> + + <Field orientation="horizontal"> + <Checkbox value="external_disks">"External disks"</Checkbox> + </Field> + + <Field orientation="horizontal"> + <Checkbox value="cds">"CDs, DVDs, and iPods"</Checkbox> + </Field> + + <Field orientation="horizontal"> + <Checkbox value="servers">"Connected servers"</Checkbox> + </Field> + </CheckboxGroup> + </FieldSet> + } +} diff --git a/docs/src/routes/components/checkbox/examples/checkbox_invalid.rs b/docs/src/routes/components/checkbox/examples/checkbox_invalid.rs new file mode 100644 index 00000000..05e784df --- /dev/null +++ b/docs/src/routes/components/checkbox/examples/checkbox_invalid.rs @@ -0,0 +1,24 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CheckboxInvalidExample() -> impl IntoView { + view! { + <FieldGroup class="mx-auto w-56"> + <Checkbox invalid=true>"Accept terms and conditions"</Checkbox> + + // Checkboxes defer to their outer-most parent's invalid state + <Field orientation="horizontal" invalid=true> + <Checkbox>"Accept terms and conditions"</Checkbox> + </Field> + + <CheckboxGroup invalid=true> + <Field orientation="horizontal"> + <Checkbox>"Accept terms and conditions"</Checkbox> + </Field> + + <Checkbox>"Accept terms and conditions"</Checkbox> + </CheckboxGroup> + </FieldGroup> + } +} diff --git a/docs/src/routes/components/checkbox/examples/checkbox_table.rs b/docs/src/routes/components/checkbox/examples/checkbox_table.rs new file mode 100644 index 00000000..64807a1f --- /dev/null +++ b/docs/src/routes/components/checkbox/examples/checkbox_table.rs @@ -0,0 +1,73 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CheckboxTableExample() -> impl IntoView { + #[derive(Clone)] + struct Employee { + id: String, + name: String, + email: String, + role: String, + } + + let employees = vec![ + Employee { + id: "1".to_string(), + name: "Sarah Chen".to_string(), + email: "sarah.chen@example.com".to_string(), + role: "Admin".to_string(), + }, + Employee { + id: "2".to_string(), + name: "Marcus Rodriguez".to_string(), + email: "marcus.rodriguez@example.com".to_string(), + role: "User".to_string(), + }, + Employee { + id: "3".to_string(), + name: "Priya Patel".to_string(), + email: "priya.patel@example.com".to_string(), + role: "User".to_string(), + }, + Employee { + id: "4".to_string(), + name: "David Kim".to_string(), + email: "david.kim@example.com".to_string(), + role: "Editor".to_string(), + }, + ]; + + let select_all = RwSignal::new(false); + + view! { + <Table> + <TableHeader> + <TableRow> + <TableHead class="w-8"> + <Checkbox checked=select_all /> + </TableHead> + <TableHead>"Name"</TableHead> + <TableHead>"Email"</TableHead> + <TableHead>"Role"</TableHead> + </TableRow> + </TableHeader> + <TableBody> + <For + each=move || employees.clone() + key=|employee| employee.id.clone() + let(employee) + > + <TableRow> + <TableCell> + <Checkbox value=employee.id /> + </TableCell> + <TableCell class="font-medium">{employee.name}</TableCell> + <TableCell>{employee.email}</TableCell> + <TableCell>{employee.role}</TableCell> + </TableRow> + </For> + </TableBody> + </Table> + } +} diff --git a/docs/src/routes/components/checkbox/examples/form.rs b/docs/src/routes/components/checkbox/examples/form.rs deleted file mode 100644 index 4db427b4..00000000 --- a/docs/src/routes/components/checkbox/examples/form.rs +++ /dev/null @@ -1,19 +0,0 @@ -use leptos::prelude::*; -use singlestage::{Label, checkbox::*}; - -#[component] -pub fn FormExample() -> impl IntoView { - view! { - <form class="form flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs"> - <Checkbox id="checkbox-form-1" /> - <div class="flex flex-col gap-1"> - <Label label_for="checkbox-form-1" class="leading-snug"> - "Use different settings for my mobile devices" - </Label> - <p class="text-(--muted-foreground) text-sm leading-snug"> - "You can manage your mobile notifications in the mobile settings page." - </p> - </div> - </form> - } -} diff --git a/docs/src/routes/components/checkbox/examples/group.rs b/docs/src/routes/components/checkbox/examples/group.rs deleted file mode 100644 index 7674924f..00000000 --- a/docs/src/routes/components/checkbox/examples/group.rs +++ /dev/null @@ -1,35 +0,0 @@ -use leptos::prelude::*; -use singlestage::{Button, checkbox::*}; - -#[component] -pub fn GroupExample() -> impl IntoView { - let value = RwSignal::new(vec![ - "recents".to_string(), - "home".to_string(), - "applications".to_string(), - ]); - - view! { - <form class="form flex flex-col gap-4"> - <p>"Value: "{move || format!("{:#?}", value.get())}</p> - - <CheckboxGroup value> - <legend> - "Sidebar" - <p class="text-(--muted-foreground) text-sm"> - "Select the items you want to display in the sidebar." - </p> - </legend> - - <Checkbox value="recents">"Recents"</Checkbox> - <Checkbox value="home">"Home"</Checkbox> - <Checkbox value="applications">"Applications"</Checkbox> - <Checkbox value="desktop">"Desktop"</Checkbox> - <Checkbox value="download">"Download"</Checkbox> - <Checkbox value="documents">"Documents"</Checkbox> - </CheckboxGroup> - - <Button class="w-fit">"Submit"</Button> - </form> - } -} diff --git a/docs/src/routes/components/checkbox/examples/mod.rs b/docs/src/routes/components/checkbox/examples/mod.rs index e8e64dd6..646077f7 100644 --- a/docs/src/routes/components/checkbox/examples/mod.rs +++ b/docs/src/routes/components/checkbox/examples/mod.rs @@ -1,9 +1,15 @@ mod checkbox; -mod form; -mod group; -mod subtext; +mod checkbox_basic; +mod checkbox_description; +mod checkbox_disabled; +mod checkbox_group; +mod checkbox_invalid; +mod checkbox_table; pub use checkbox::*; -pub use form::*; -pub use group::*; -pub use subtext::*; +pub use checkbox_basic::*; +pub use checkbox_description::*; +pub use checkbox_disabled::*; +pub use checkbox_group::*; +pub use checkbox_invalid::*; +pub use checkbox_table::*; diff --git a/docs/src/routes/components/checkbox/examples/subtext.rs b/docs/src/routes/components/checkbox/examples/subtext.rs deleted file mode 100644 index 1c6e8644..00000000 --- a/docs/src/routes/components/checkbox/examples/subtext.rs +++ /dev/null @@ -1,17 +0,0 @@ -use leptos::prelude::*; -use singlestage::{Label, checkbox::*}; - -#[component] -pub fn SubtextExample() -> impl IntoView { - view! { - <div class="flex items-start gap-3"> - <Checkbox id="checkbox-with-text" /> - <div class="grid gap-2"> - <Label label_for="checkbox-with-text">"Accept terms and conditions"</Label> - <p class="text-(--muted-foreground) text-sm"> - "By clicking this checkbox, you agree to the terms and conditions." - </p> - </div> - </div> - } -} diff --git a/docs/src/routes/components/collapsible/anatomy.rs b/docs/src/routes/components/collapsible/anatomy.rs new file mode 100644 index 00000000..2411fe24 --- /dev/null +++ b/docs/src/routes/components/collapsible/anatomy.rs @@ -0,0 +1,14 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CollapsibleAnatomy() -> impl IntoView { + view! { + <Collapsible> + <CollapsibleTrigger> + <Button /> + </CollapsibleTrigger> + <CollapsibleContent /> + </CollapsibleTrigger> + } +} diff --git a/docs/src/routes/components/collapsible/component.toml b/docs/src/routes/components/collapsible/component.toml new file mode 100644 index 00000000..3d9420f6 --- /dev/null +++ b/docs/src/routes/components/collapsible/component.toml @@ -0,0 +1,43 @@ +name = "Collapsible" +description = "An interactive component which expands/collapses a panel." + +[[examples]] +name = "collapsible" + +[[examples]] +name = "collapsible basic" +title = "Basic" + +[[examples]] +name = "collapsible settings panel" +title = "Settings Panel" +description = "Use a trigger button to reveal additional settings." + +[[examples]] +name = "collapsible file tree" +title = "File Tree" +description = "Use nested collapsibles to build a file tree." + +[[references]] +name = "Collapsible" +description = "Contains the collapsible trigger and content." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [ + { attr = "open", attr_type = "bool", default = "false", description = """A reactive signal that \ +is directly coupled to the open state of the collapsible.""" }, +] + +[[references]] +name = "CollapsibleContent" +description = "Contains the content of a collapsible menu." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "CollapsibleTrigger" +description = "A context provider that wraps a Button component that triggers the collapsible menu." +attrs = [] diff --git a/docs/src/routes/components/collapsible/examples/collapsible.rs b/docs/src/routes/components/collapsible/examples/collapsible.rs new file mode 100644 index 00000000..4e753c4c --- /dev/null +++ b/docs/src/routes/components/collapsible/examples/collapsible.rs @@ -0,0 +1,47 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CollapsibleExample() -> impl IntoView { + let open = RwSignal::new(false); + + view! { + <Collapsible open class="flex flex-col w-full max-w-xs gap-2"> + <Item size="sm"> + <ItemContent> + <ItemTitle>"Order #4189"</ItemTitle> + </ItemContent> + <ItemActions> + <CollapsibleTrigger> + <Button variant="ghost" size="icon" class="size-8"> + {icon!(icondata::LuChevronsUpDown)} + <span class="sr-only">"Toggle details"</span> + </Button> + </CollapsibleTrigger> + </ItemActions> + </Item> + <Item size="sm" variant="outline"> + <ItemContent> + <ItemDescription>"Status"</ItemDescription> + </ItemContent> + <ItemActions> + <span class="font-medium">"Shipped"</span> + </ItemActions> + </Item> + <CollapsibleContent class="flex flex-col gap-2"> + <Item size="sm" variant="outline"> + <ItemContent> + <ItemTitle>"Shipping Address"</ItemTitle> + <ItemDescription>"100 Market St, San Francisco"</ItemDescription> + </ItemContent> + </Item> + <Item size="sm" variant="outline"> + <ItemContent> + <ItemTitle>"Items"</ItemTitle> + <ItemDescription>"2x Studio Headphones"</ItemDescription> + </ItemContent> + </Item> + </CollapsibleContent> + </Collapsible> + } +} diff --git a/docs/src/routes/components/collapsible/examples/collapsible_basic.rs b/docs/src/routes/components/collapsible/examples/collapsible_basic.rs new file mode 100644 index 00000000..de22b0bf --- /dev/null +++ b/docs/src/routes/components/collapsible/examples/collapsible_basic.rs @@ -0,0 +1,50 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CollapsibleBasicExample() -> impl IntoView { + let open = RwSignal::new(false); + + view! { + <Card class="w-full max-w-sm"> + <CardContent> + <Collapsible + open + // attr:class is used here instead of class because class doesn't support + // closures yet in singlestage + attr:class=move || { + format!( + "rounded-md{}", + match open.get() { + true => " bg-(--muted)", + false => "", + }, + ) + } + > + <CollapsibleTrigger> + <Button variant="ghost" class="group w-full"> + "Product details" + <span class=move || { + format!( + "ml-auto{}", + match open.get() { + true => " rotate-180", + false => "", + }, + ) + }>{icon!(icondata::LuChevronDown)}</span> + </Button> + </CollapsibleTrigger> + <CollapsibleContent class="flex flex-col items-start gap-2 p-2.5 pt-0 text-sm"> + <p> + "This panel can be expanded or collapsed to reveal additional + content." + </p> + <Button size="xs">"Learn More"</Button> + </CollapsibleContent> + </Collapsible> + </CardContent> + </Card> + } +} diff --git a/docs/src/routes/components/collapsible/examples/collapsible_file_tree.rs b/docs/src/routes/components/collapsible/examples/collapsible_file_tree.rs new file mode 100644 index 00000000..73c019af --- /dev/null +++ b/docs/src/routes/components/collapsible/examples/collapsible_file_tree.rs @@ -0,0 +1,222 @@ +use leptos::prelude::*; +use singlestage::*; + +#[derive(Clone, Default)] +pub struct FileTreeItem { + name: String, + items: Vec<FileTreeItem>, +} + +#[component] +pub fn RenderItem(file_item: FileTreeItem) -> impl IntoView { + if !file_item.items.is_empty() { + let open = RwSignal::new(false); + + view! { + <Collapsible open> + <CollapsibleTrigger> + <Button + variant="ghost" + size="sm" + class="w-full justify-start transition-none hover:bg-accent hover:text-accent-foreground" + > + <span class=move || { + format!( + "p-0 transition-transform{}", + match open.get() { + true => " rotate-90", + false => "", + }, + ) + }>{icon!(icondata::LuChevronRight)}</span> + {icon!(icondata::LuFolder)} + {file_item.name} + </Button> + </CollapsibleTrigger> + <CollapsibleContent class="mt-1 ml-6"> + <div class="flex flex-col gap-1"> + <For + each=move || file_item.items.to_owned() + key=|file_item| file_item.name.clone() + let(file_item) + > + <RenderItem file_item /> + </For> + </div> + </CollapsibleContent> + </Collapsible> + }.into_any() + } else { + view! { + <Button variant="link" size="sm" class="w-full justify-start gap-2 text-foreground"> + {icon!(icondata::LuFile)} + <span>{file_item.name}</span> + </Button> + } + .into_any() + } +} + +#[component] +pub fn CollapsibleFileTreeExample() -> impl IntoView { + let file_tree: Vec<FileTreeItem> = vec![ + FileTreeItem { + name: "components".to_string(), + items: vec![ + FileTreeItem { + name: "ui".to_string(), + items: vec![ + FileTreeItem { + name: "button.tsx".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "card.tsx".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "dialog.tsx".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "input.tsx".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "select.tsx".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "table.tsx".to_string(), + ..Default::default() + }, + ], + }, + FileTreeItem { + name: "login-form.tsx".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "register-form.tsx".to_string(), + ..Default::default() + }, + ], + }, + FileTreeItem { + name: "lib".to_string(), + items: vec![ + FileTreeItem { + name: "utils.ts".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "cn.ts".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "api.ts".to_string(), + ..Default::default() + }, + ], + }, + FileTreeItem { + name: "hooks".to_string(), + items: vec![ + FileTreeItem { + name: "use-media-query.ts".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "use-debounce.ts".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "use-local-storage.ts".to_string(), + ..Default::default() + }, + ], + }, + FileTreeItem { + name: "types".to_string(), + items: vec![ + FileTreeItem { + name: "index.d.ts".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "api.d.ts".to_string(), + ..Default::default() + }, + ], + }, + FileTreeItem { + name: "public".to_string(), + items: vec![ + FileTreeItem { + name: "favicon.ico".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "logo.svg".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "images".to_string(), + ..Default::default() + }, + ], + }, + FileTreeItem { + name: "app.tsx".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "layout.tsx".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "globals.css".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "package.json".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "tsconfig.json".to_string(), + ..Default::default() + }, + FileTreeItem { + name: "README.md".to_string(), + ..Default::default() + }, + FileTreeItem { + name: ".gitignore".to_string(), + ..Default::default() + }, + ]; + + view! { + <Card class="mx-auto w-full max-w-[16rem] gap-2" size="sm"> + <CardHeader> + <Tabs value="explorer"> + <TabsList class="w-full"> + <TabsTrigger value="explorer">"Explorer"</TabsTrigger> + <TabsTrigger value="settings">"Outline"</TabsTrigger> + </TabsList> + </Tabs> + </CardHeader> + <CardContent> + <div class="flex flex-col gap-1"> + <For + each=move || file_tree.to_owned() + key=|file_item| file_item.name.clone() + let(file_item) + > + <RenderItem file_item /> + </For> + </div> + </CardContent> + </Card> + } +} diff --git a/docs/src/routes/components/collapsible/examples/collapsible_settings_panel.rs b/docs/src/routes/components/collapsible/examples/collapsible_settings_panel.rs new file mode 100644 index 00000000..598ca4c7 --- /dev/null +++ b/docs/src/routes/components/collapsible/examples/collapsible_settings_panel.rs @@ -0,0 +1,50 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn CollapsibleSettingsPanelExample() -> impl IntoView { + let open = RwSignal::new(false); + + view! { + <Card class="w-full max-w-xs" size="sm"> + <CardHeader> + <CardTitle>"Radius"</CardTitle> + <CardDescription>"Set the corner radius of the element."</CardDescription> + </CardHeader> + <CardContent> + <Collapsible open class="flex items-start gap-2"> + <FieldGroup class="grid w-full grid-cols-2 gap-2"> + <Field> + <FieldLabel class="sr-only">"Radius X"</FieldLabel> + <Input placeholder="0" /> + </Field> + <Field> + <FieldLabel class="sr-only">"Radius Y"</FieldLabel> + <Input placeholder="0" /> + </Field> + <CollapsibleContent class="col-span-full grid grid-cols-subgrid gap-2"> + <Field> + <FieldLabel class="sr-only">"Radius X"</FieldLabel> + <Input placeholder="0" /> + </Field> + <Field> + <FieldLabel class="sr-only">"Radius Y"</FieldLabel> + <Input placeholder="0" /> + </Field> + </CollapsibleContent> + </FieldGroup> + <CollapsibleTrigger> + <Button variant="outline" size="icon"> + <Show + when=move || open.get() + fallback=move || { icon!(icondata::LuMaximize) } + > + {icon!(icondata::LuMinimize)} + </Show> + </Button> + </CollapsibleTrigger> + </Collapsible> + </CardContent> + </Card> + } +} diff --git a/docs/src/routes/components/collapsible/examples/mod.rs b/docs/src/routes/components/collapsible/examples/mod.rs new file mode 100644 index 00000000..5ba97e9b --- /dev/null +++ b/docs/src/routes/components/collapsible/examples/mod.rs @@ -0,0 +1,9 @@ +mod collapsible; +mod collapsible_basic; +mod collapsible_file_tree; +mod collapsible_settings_panel; + +pub use collapsible::*; +pub use collapsible_basic::*; +pub use collapsible_file_tree::*; +pub use collapsible_settings_panel::*; diff --git a/docs/src/routes/components/collapsible/mod.rs b/docs/src/routes/components/collapsible/mod.rs new file mode 100644 index 00000000..c7ceb8b6 --- /dev/null +++ b/docs/src/routes/components/collapsible/mod.rs @@ -0,0 +1,3 @@ +mod examples; + +pub use examples::*; diff --git a/docs/src/routes/components/context_menu/anatomy.rs b/docs/src/routes/components/context_menu/anatomy.rs index dacb8377..a898b7bb 100644 --- a/docs/src/routes/components/context_menu/anatomy.rs +++ b/docs/src/routes/components/context_menu/anatomy.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::context_menu::*; +use singlestage::*; #[component] pub fn ContextMenuAnatomy() -> impl IntoView { @@ -14,6 +14,20 @@ pub fn ContextMenuAnatomy() -> impl IntoView { </ContextMenuItem> </ContextMenuGroup> <ContextMenuSeparator /> + <ContextMenuSub> + <ContextMenuSubTrigger /> + <ContextMenuSubContent> + <ContextMenuGroup> + <ContextMenuCheckboxItem /> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuRadioGroup> + <ContextMenuRadioItem /> + </ContextMenuRadioGroup> + </ContextMenuGroup> + </ContextMenuSubContent> + </ContextMenuSub> </ContextMenuContent> </ContextMenu> } diff --git a/docs/src/routes/components/context_menu/component.toml b/docs/src/routes/components/context_menu/component.toml index 771d18b8..fadbe709 100644 --- a/docs/src/routes/components/context_menu/component.toml +++ b/docs/src/routes/components/context_menu/component.toml @@ -1,10 +1,60 @@ name = "Context Menu" description = """Displays a menu to the user — such as a set of actions or functions — triggered \ -by a button.""" +by right click or long press.""" + +[info] +title = "This component uses newly available browser features" +description = """"<p>"This component uses CSS Anchor Positioning which has only been widely \ +supported since January 2026. If you're targeting older or less mainstream browsers then maybe \ +this component isn't for you, yet. "</p><p>" Alternatively you can use Singlestage 0.4 which \ +positions the component correctly but with limited flexibility for alignment, or roll your own \ +using Singlestage CSS classes for consistent styling. "</p><singlestage::Link style="color: \ +var(--foreground)" href="https://caniuse.com/css-anchor-positioning"> "More Info" \ +</singlestage::Link>"""" [[examples]] name = "Context Menu" +[[examples]] +name = "Context Basic" +title = "Basic" +description = "A simple context menu with a few actions." + +[[examples]] +name = "Context Sub" +title = "Submenu" +description = "Use MenuSub to nest secondary actions." + +[[examples]] +name = "Context Shortcuts" +title = "Shortcuts" +description = "Add MenuShortcut to show keyboard hints." + +[[examples]] +name = "Context Groups" +title = "Groups" +description = "Group related actions and separate them with dividers." + +[[examples]] +name = "Context Icons" +title = "Icons" +description = "Combine icons with labels for quick scanning." + +[[examples]] +name = "Context Checkboxes" +title = "Checkboxes" +description = "Checkboxes can be used in context menus." + +[[examples]] +name = "Context Radio" +title = "Radio" +description = "Radio buttons can be used in context menus." + +[[examples]] +name = "Context Destructive" +title = "Destructive" +description = "Use the destructive variant for irreversible actions." + [[references]] name = "ContextMenu" description = "Contains all the parts of a context menu." @@ -13,17 +63,44 @@ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attribu attributes.""" attrs = [] +[[references]] +name = "ContextMenuCheckboxItem" +description = "A checkbox item for context menus." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +and <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#attributes">li</a> \ +attributes.""" +attrs = [ + { attr = "checked", attr_type = "bool", default = "false", description = """A reactive signal \ + coupled to the checkbox's checked value.""" }, + { attr = "disabled", attr_type = "bool", default = "false", description = """Controls \ + whether the item appears disabled or not.""" }, + { attr = "dismiss", attr_type = "bool", default = "true", description = """Toggle whether \ + clicking this item dismisses the parent menu.""" }, + { attr = "inset", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display inset from its normal position.""" }, + { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ + variant of the item. Accepted values: \"destructive\".""" }, +] + [[references]] name = "ContextMenuContent" description = "The component that pops out when the context menu is open." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" -attrs = [] +attrs = [ + { attr = "align", attr_type = "String", default = """"start"""", description = """Set how to \ + align the popover. Accepted values: "start" | "center" | "end"""" }, + { attr = "side", attr_type = "String", default = """"bottom"""", description = """Set which \ + side the popover opens relative to the trigger point. Accepted values: "top" | "right" | \ + "bottom" | "left""""" }, +] [[references]] name = "ContextMenuGroup" -description = "Contains multiple items." +description = "Contains multiple menu items." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" @@ -31,26 +108,79 @@ attrs = [] [[references]] name = "ContextMenuItem" -description = "Contains a menu item." +description = "Contains a context menu item." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ and <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#attributes">li</a> \ attributes.""" attrs = [ + { attr = "as_child", attr_type = "bool", default = "false", description = """This component will \ + render without styling.""" }, + { attr = "disabled", attr_type = "bool", default = "false", description = """Controls whether \ + the item appears disabled and is clickable.""" }, + { attr = "dismiss", attr_type = "bool", default = "true", description = """Toggle whether \ + clicking this item dismisses the parent menu.""" }, + { attr = "inset", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display inset from its normal position.""" }, + { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ + variant of the item. Accepted values: \"destructive\".""" }, +] + +[[references]] +name = "ContextMenuLabel" +description = "Labels groups of menu items." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [ + { attr = "disabled", attr_type = "bool", default = "false", description = """Renders the label \ + with disabled styling.""" }, + { attr = "inset", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display inset from its normal position.""" }, + { attr = "invalid", attr_type = "bool", default = "false", description = """Renders the label \ + with invalid styling.""" }, + { attr = "label_for", attr_type = "String", default = """""""", description = """Use this to \ + explicitly set the id of the labeled element.""" }, +] + +[[references]] +name = "ContextMenuRadioItem" +description = "A radio item for context menus." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +and <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#attributes">li</a> \ +attributes.""" +attrs = [ + { attr = "checked", attr_type = "bool", default = "false", description = """A reactive signal \ + coupled to the radio's checked value.""" }, { attr = "disabled", attr_type = "bool", default = "false", description = """Controls \ whether the item appears disabled or not.""" }, + { attr = "dismiss", attr_type = "bool", default = "true", description = """Toggle whether \ + clicking this item dismisses the parent menu.""" }, + { attr = "inset", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display inset from its normal position.""" }, { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ variant of the item. Accepted values: \"destructive\".""" }, ] [[references]] -name = "ContextMenuLabel" -description = "Labels groups." +name = "ContextMenuRadioGroup" +description = "A radio group component for context menus." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +and <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/fieldset#attributes">fieldset</a> \ attributes.""" -attrs = [] +attrs = [ + { attr = "default", attr_type = "String", default = """""""", description = """Set or update \ + the default value.""" }, + { attr = "invalid", attr_type = "bool", default = "false", description = """Renders the group \ + with invalid styling.""" }, + { attr = "value", attr_type = "String", default = """""""", description = """A reactive signal \ + coupled to the current selected value of the radio group.""" }, +] [[references]] name = "ContextMenuSeparator" @@ -58,7 +188,10 @@ description = "Visually separates groups of menu items." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" -attrs = [] +attrs = [ + { attr = "orientation", attr_type = "String", default = """"horizontal"""", description = """Specify \ + the orientation of the separator. Accepted values: "horizontal" | "vertical".""" }, +] [[references]] name = "ContextMenuShortcut" @@ -75,3 +208,46 @@ extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [] + +[[references]] +name = "ContextSubMenu" +description = "Contains all the parts of a sub menu." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [ + { attr = "open", attr_type = "bool", default = "false", description = """Reactive signal that \ + can remotely control the open state of the popover but is not coupled to the actual state of \ + the popover.""" }, +] + +[[references]] +name = "ContextMenuSubContent" +description = "The component that pops out when the sub trigger is triggered." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [ + { attr = "align", attr_type = "String", default = """"start"""", description = """Set how to \ + align the popover. Accepted values: "start" | "center" | "end"""" }, + { attr = "side", attr_type = "String", default = """"bottom"""", description = """Set which \ + side the popover opens relative to the trigger point. Accepted values: "top" | "right" | \ + "bottom" | "left""""" }, +] + +[[references]] +name = "ContextMenuSubTrigger" +description = "A context menu item that triggers a sub menu." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +and <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#attributes">li</a> \ +attributes.""" +attrs = [ + { attr = "disabled", attr_type = "bool", default = "false", description = """Controls \ + whether the item appears disabled or not.""" }, + { attr = "inset", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display inset from its normal position.""" }, + { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ + variant of the item. Accepted values: \"destructive\".""" }, +] diff --git a/docs/src/routes/components/context_menu/examples/context_basic.rs b/docs/src/routes/components/context_menu/examples/context_basic.rs new file mode 100644 index 00000000..8644dc9d --- /dev/null +++ b/docs/src/routes/components/context_menu/examples/context_basic.rs @@ -0,0 +1,21 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ContextBasicExample() -> impl IntoView { + view! { + <ContextMenu> + <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-xl border border-dashed text-sm"> + <span class="pointer-fine:hidden">"Long press here"</span> + <span class="pointer-coarse:hidden">"Right click here"</span> + </ContextMenuTrigger> + <ContextMenuContent> + <ContextMenuGroup> + <ContextMenuItem>"Back"</ContextMenuItem> + <ContextMenuItem disabled=true>"Forward"</ContextMenuItem> + <ContextMenuItem>"Reload"</ContextMenuItem> + </ContextMenuGroup> + </ContextMenuContent> + </ContextMenu> + } +} diff --git a/docs/src/routes/components/context_menu/examples/context_checkboxes.rs b/docs/src/routes/components/context_menu/examples/context_checkboxes.rs new file mode 100644 index 00000000..1f7c17d2 --- /dev/null +++ b/docs/src/routes/components/context_menu/examples/context_checkboxes.rs @@ -0,0 +1,25 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ContextCheckboxesExample() -> impl IntoView { + view! { + <ContextMenu> + <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-xl border border-dashed text-sm"> + <span class="pointer-fine:hidden">"Long press here"</span> + <span class="pointer-coarse:hidden">"Right click here"</span> + </ContextMenuTrigger> + <ContextMenuContent> + <ContextMenuGroup> + <ContextMenuCheckboxItem checked=true> + "Show Bookmarks Bar" + </ContextMenuCheckboxItem> + <ContextMenuCheckboxItem>"Show Full URLs"</ContextMenuCheckboxItem> + <ContextMenuCheckboxItem checked=true> + "Show Developer Tools" + </ContextMenuCheckboxItem> + </ContextMenuGroup> + </ContextMenuContent> + </ContextMenu> + } +} diff --git a/docs/src/routes/components/context_menu/examples/context_destructive.rs b/docs/src/routes/components/context_menu/examples/context_destructive.rs new file mode 100644 index 00000000..0a7986fe --- /dev/null +++ b/docs/src/routes/components/context_menu/examples/context_destructive.rs @@ -0,0 +1,26 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ContextDestructiveExample() -> impl IntoView { + view! { + <ContextMenu> + <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-xl border border-dashed text-sm"> + <span class="pointer-fine:hidden">"Long press here"</span> + <span class="pointer-coarse:hidden">"Right click here"</span> + </ContextMenuTrigger> + <ContextMenuContent> + <ContextMenuGroup> + <ContextMenuItem>{icon!(icondata::LuPencil)} "Edit"</ContextMenuItem> + <ContextMenuItem>{icon!(icondata::LuShare)} "Share"</ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuItem variant="destructive"> + {icon!(icondata::LuTrash)} "Delete" + </ContextMenuItem> + </ContextMenuGroup> + </ContextMenuContent> + </ContextMenu> + } +} diff --git a/docs/src/routes/components/context_menu/examples/context_groups.rs b/docs/src/routes/components/context_menu/examples/context_groups.rs new file mode 100644 index 00000000..ee745ed7 --- /dev/null +++ b/docs/src/routes/components/context_menu/examples/context_groups.rs @@ -0,0 +1,56 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ContextGroupsExample() -> impl IntoView { + view! { + <ContextMenu> + <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-xl border border-dashed text-sm"> + <span class="pointer-fine:hidden">"Long press here"</span> + <span class="pointer-coarse:hidden">"Right click here"</span> + </ContextMenuTrigger> + <ContextMenuContent> + <ContextMenuGroup> + <ContextMenuLabel>"File"</ContextMenuLabel> + <ContextMenuItem> + "New File" <ContextMenuShortcut>"⌘N"</ContextMenuShortcut> + </ContextMenuItem> + <ContextMenuItem> + "Open File" <ContextMenuShortcut>"⌘O"</ContextMenuShortcut> + </ContextMenuItem> + <ContextMenuItem> + "Save" <ContextMenuShortcut>"⌘S"</ContextMenuShortcut> + </ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuLabel>"Edit"</ContextMenuLabel> + <ContextMenuItem> + "Undo" <ContextMenuShortcut>"⌘Z"</ContextMenuShortcut> + </ContextMenuItem> + <ContextMenuItem> + "Redo" <ContextMenuShortcut>"⇧⌘Z"</ContextMenuShortcut> + </ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuItem> + "Cut" <ContextMenuShortcut>"⌘X"</ContextMenuShortcut> + </ContextMenuItem> + <ContextMenuItem> + "Copy" <ContextMenuShortcut>"⌘C"</ContextMenuShortcut> + </ContextMenuItem> + <ContextMenuItem> + "Paste" <ContextMenuShortcut>"⌘V"</ContextMenuShortcut> + </ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuItem variant="destructive"> + "Delete" <ContextMenuShortcut>"⌫"</ContextMenuShortcut> + </ContextMenuItem> + </ContextMenuGroup> + </ContextMenuContent> + </ContextMenu> + } +} diff --git a/docs/src/routes/components/context_menu/examples/context_icons.rs b/docs/src/routes/components/context_menu/examples/context_icons.rs new file mode 100644 index 00000000..5a0b5244 --- /dev/null +++ b/docs/src/routes/components/context_menu/examples/context_icons.rs @@ -0,0 +1,27 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ContextIconsExample() -> impl IntoView { + view! { + <ContextMenu> + <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-xl border border-dashed text-sm"> + <span class="pointer-fine:hidden">"Long press here"</span> + <span class="pointer-coarse:hidden">"Right click here"</span> + </ContextMenuTrigger> + <ContextMenuContent> + <ContextMenuGroup> + <ContextMenuItem>{icon!(icondata::LuCopy)} "Copy"</ContextMenuItem> + <ContextMenuItem>{icon!(icondata::LuScissors)} "Cut"</ContextMenuItem> + <ContextMenuItem>{icon!(icondata::LuClipboardPaste)} "Paste"</ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuItem variant="destructive"> + {icon!(icondata::LuTrash)} "Delete" + </ContextMenuItem> + </ContextMenuGroup> + </ContextMenuContent> + </ContextMenu> + } +} diff --git a/docs/src/routes/components/context_menu/examples/context_menu.rs b/docs/src/routes/components/context_menu/examples/context_menu.rs index 06dd05f1..5398675c 100644 --- a/docs/src/routes/components/context_menu/examples/context_menu.rs +++ b/docs/src/routes/components/context_menu/examples/context_menu.rs @@ -5,40 +5,52 @@ use singlestage::*; pub fn ContextMenuExample() -> impl IntoView { view! { <ContextMenu> - <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm"> - "Right click here" + <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-xl border border-dashed text-sm"> + <span class="pointer-fine:hidden">"Long press here"</span> + <span class="pointer-coarse:hidden">"Right click here"</span> </ContextMenuTrigger> - <ContextMenuContent> + <ContextMenuContent class="w-48"> <ContextMenuGroup> - <ContextMenuLabel>"My Account"</ContextMenuLabel> <ContextMenuItem> - "Profile" <ContextMenuShortcut>"⇧⌘P"</ContextMenuShortcut> + "Back" <ContextMenuShortcut>"⌘["</ContextMenuShortcut> </ContextMenuItem> - <ContextMenuItem> - "Billing" <ContextMenuShortcut>"⌘B"</ContextMenuShortcut> - </ContextMenuItem> - <ContextMenuItem> - "Settings" <ContextMenuShortcut>"⌘S"</ContextMenuShortcut> + <ContextMenuItem disabled=true> + "Forward" <ContextMenuShortcut>"⌘]"</ContextMenuShortcut> </ContextMenuItem> <ContextMenuItem> - "Keyboard shortcuts" <ContextMenuShortcut>"⌘K"</ContextMenuShortcut> + "Reload" <ContextMenuShortcut>"⌘R"</ContextMenuShortcut> </ContextMenuItem> + <ContextMenuSub> + <ContextMenuSubTrigger>"More Tools"</ContextMenuSubTrigger> + <ContextMenuSubContent class="w-44"> + <ContextMenuGroup> + <ContextMenuItem>"Save Page..."</ContextMenuItem> + <ContextMenuItem>"Create Shortcut..."</ContextMenuItem> + <ContextMenuItem>"Name Window..."</ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuItem>"Developer Tools"</ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuItem variant="destructive">"Delete"</ContextMenuItem> + </ContextMenuGroup> + </ContextMenuSubContent> + </ContextMenuSub> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuGroup> - <ContextMenuItem>"GitHub"</ContextMenuItem> - <ContextMenuItem>"Support"</ContextMenuItem> - <ContextMenuItem disabled=true>"API"</ContextMenuItem> + <ContextMenuCheckboxItem checked=true>"Show Bookmarks"</ContextMenuCheckboxItem> + <ContextMenuCheckboxItem>"Show Full URLs"</ContextMenuCheckboxItem> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuGroup> - <ContextMenuItem variant="destructive"> - {icon!(icondata::LuTrash2)} "Delete Account" - </ContextMenuItem> - <ContextMenuItem> - {icon!(icondata::LuLogOut)} "Logout" - <ContextMenuShortcut>"⇧⌘Q"</ContextMenuShortcut> - </ContextMenuItem> + <ContextMenuRadioGroup value="pedro"> + <ContextMenuLabel>"People"</ContextMenuLabel> + <ContextMenuRadioItem value="pedro">"Pedro Duarte"</ContextMenuRadioItem> + <ContextMenuRadioItem value="colm">"Colm Tuite"</ContextMenuRadioItem> + </ContextMenuRadioGroup> </ContextMenuGroup> </ContextMenuContent> </ContextMenu> diff --git a/docs/src/routes/components/context_menu/examples/context_radio.rs b/docs/src/routes/components/context_menu/examples/context_radio.rs new file mode 100644 index 00000000..4e002804 --- /dev/null +++ b/docs/src/routes/components/context_menu/examples/context_radio.rs @@ -0,0 +1,35 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ContextRadioExample() -> impl IntoView { + let user = RwSignal::new("pedro".to_string()); + let theme = RwSignal::new("light".to_string()); + + view! { + <ContextMenu> + <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-xl border border-dashed text-sm"> + <span class="pointer-fine:hidden">"Long press here"</span> + <span class="pointer-coarse:hidden">"Right click here"</span> + </ContextMenuTrigger> + <ContextMenuContent> + <ContextMenuGroup> + <ContextMenuLabel>"People"</ContextMenuLabel> + <ContextMenuRadioGroup value=user> + <ContextMenuRadioItem value="pedro">"Pedro Duarte"</ContextMenuRadioItem> + <ContextMenuRadioItem value="colm">"Colm Tuite"</ContextMenuRadioItem> + </ContextMenuRadioGroup> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuLabel>"Theme"</ContextMenuLabel> + <ContextMenuRadioGroup value=theme> + <ContextMenuRadioItem value="light">"Light"</ContextMenuRadioItem> + <ContextMenuRadioItem value="dark">"Dark"</ContextMenuRadioItem> + <ContextMenuRadioItem value="system">"System"</ContextMenuRadioItem> + </ContextMenuRadioGroup> + </ContextMenuGroup> + </ContextMenuContent> + </ContextMenu> + } +} diff --git a/docs/src/routes/components/context_menu/examples/context_shortcuts.rs b/docs/src/routes/components/context_menu/examples/context_shortcuts.rs new file mode 100644 index 00000000..597568be --- /dev/null +++ b/docs/src/routes/components/context_menu/examples/context_shortcuts.rs @@ -0,0 +1,36 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ContextShortcutsExample() -> impl IntoView { + view! { + <ContextMenu> + <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-xl border border-dashed text-sm"> + <span class="pointer-fine:hidden">"Long press here"</span> + <span class="pointer-coarse:hidden">"Right click here"</span> + </ContextMenuTrigger> + <ContextMenuContent> + <ContextMenuGroup> + <ContextMenuItem> + "Back" <ContextMenuShortcut>"⌘["</ContextMenuShortcut> + </ContextMenuItem> + <ContextMenuItem disabled=true> + "Forward" <ContextMenuShortcut>"⌘]"</ContextMenuShortcut> + </ContextMenuItem> + <ContextMenuItem> + "Reload" <ContextMenuShortcut>"⌘R"</ContextMenuShortcut> + </ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuItem> + "Save" <ContextMenuShortcut>"⌘S"</ContextMenuShortcut> + </ContextMenuItem> + <ContextMenuItem> + "Save As..." <ContextMenuShortcut>"⇧⌘S"</ContextMenuShortcut> + </ContextMenuItem> + </ContextMenuGroup> + </ContextMenuContent> + </ContextMenu> + } +} diff --git a/docs/src/routes/components/context_menu/examples/context_sub.rs b/docs/src/routes/components/context_menu/examples/context_sub.rs new file mode 100644 index 00000000..7dece94b --- /dev/null +++ b/docs/src/routes/components/context_menu/examples/context_sub.rs @@ -0,0 +1,42 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ContextSubExample() -> impl IntoView { + view! { + <ContextMenu> + <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-xl border border-dashed text-sm"> + <span class="pointer-fine:hidden">"Long press here"</span> + <span class="pointer-coarse:hidden">"Right click here"</span> + </ContextMenuTrigger> + <ContextMenuContent> + <ContextMenuGroup> + <ContextMenuItem> + "Copy" <ContextMenuShortcut>"⌘C"</ContextMenuShortcut> + </ContextMenuItem> + <ContextMenuItem> + "Cut" <ContextMenuShortcut>"⌘X"</ContextMenuShortcut> + </ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSub> + <ContextMenuSubTrigger>"More Tools"</ContextMenuSubTrigger> + <ContextMenuSubContent> + <ContextMenuGroup> + <ContextMenuItem>"Save Page..."</ContextMenuItem> + <ContextMenuItem>"Create Shortcut..."</ContextMenuItem> + <ContextMenuItem>"Name Window..."</ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuItem>"Developer Tools"</ContextMenuItem> + </ContextMenuGroup> + <ContextMenuSeparator /> + <ContextMenuGroup> + <ContextMenuItem variant="destructive">"Delete"</ContextMenuItem> + </ContextMenuGroup> + </ContextMenuSubContent> + </ContextMenuSub> + </ContextMenuContent> + </ContextMenu> + } +} diff --git a/docs/src/routes/components/context_menu/examples/mod.rs b/docs/src/routes/components/context_menu/examples/mod.rs index bcfcfd04..a4d82079 100644 --- a/docs/src/routes/components/context_menu/examples/mod.rs +++ b/docs/src/routes/components/context_menu/examples/mod.rs @@ -1,3 +1,19 @@ +mod context_basic; +mod context_checkboxes; +mod context_destructive; +mod context_groups; +mod context_icons; mod context_menu; +mod context_radio; +mod context_shortcuts; +mod context_sub; +pub use context_basic::*; +pub use context_checkboxes::*; +pub use context_destructive::*; +pub use context_groups::*; +pub use context_icons::*; pub use context_menu::*; +pub use context_radio::*; +pub use context_shortcuts::*; +pub use context_sub::*; diff --git a/docs/src/routes/components/dialog/anatomy.rs b/docs/src/routes/components/dialog/anatomy.rs index 47261757..5ec27a84 100644 --- a/docs/src/routes/components/dialog/anatomy.rs +++ b/docs/src/routes/components/dialog/anatomy.rs @@ -1,18 +1,23 @@ use leptos::prelude::*; -use singlestage::dialog::*; +use singlestage::*; #[component] pub fn DialogAnatomy -> impl IntoView { view!{ <Dialog> - <DialogTrigger slot /> - <DialogHeader> - <DialogTitle /> - <DialogDescription /> - </DialogHeader> - <DialogContent/> - <DialogFooter /> - <DialogClose/> + <DialogTrigger> + <Button /> + </DialogTrigger> + <DialogContent> + <DialogHeader> + <DialogTitle /> + <DialogDescription /> + </DialogHeader> + <DialogFooter> + <DialogCancel /> + <DialogAction /> + </DialogFooter> + </DialogContent> </Dialog> } } diff --git a/docs/src/routes/components/dialog/component.toml b/docs/src/routes/components/dialog/component.toml index 21665f4d..e555d009 100644 --- a/docs/src/routes/components/dialog/component.toml +++ b/docs/src/routes/components/dialog/component.toml @@ -5,32 +5,52 @@ description = "A window overlaid on either the primary window or another dialog name = "Dialog" [[examples]] -name = "Dialog Alert" -title = "Alert Dialog" -description = "A modal dialog that interrupts the user with important content and expects a response." +name = "Dialog custom close button" +title = "Custom Close Button" +description = "Replace the default close control with your own button." + +[[examples]] +name = "Dialog no close button" +title = "No Close Button" +description = "Use close_button=false to hide the close button." + +[[examples]] +name = "Dialog sticky footer" +title = "Sticky Footer" +description = "Keep actions visible while the content scrolls." + +[[examples]] +name = "Dialog scrollable content" +title = "Scrollable Content" +description = "Long content can scroll while the header stays in view." [[references]] name = "Dialog" -description = "Contains all the parts of an alert dialog." +description = "Contains all the parts of a Dialog component." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ and <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#attributes">dialog</a> \ attributes.""" attrs = [ - { attr = "alert", attr_type = "bool", default = "false", description = """Specify whether or \ - not alert styling should be used.""" }, - { attr = "open", attr_type = "bool", default = "false", description = """Toggle whether or not \ - the dialog is open.""" }, + { attr = "open", attr_type = "bool", default = "false", description = """Reactive signal that \ + can remotely control the open state of the popover **but is not coupled to the actual open \ + state of the popover**""" }, ] [[references]] -name = "DialogClose" -description = "An optional button that renders in the top-right corner and closes the dialog when clicked." +name = "DialogAction" +description = "Wraps a button that the user presses to acknowledge the dialog." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "DialogCancel" +description = "Wraps a button that the user presses to dismiss the dialog." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ -and <a class="singlestage-link" \ -href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#attributes">button</a> \ attributes.""" attrs = [] @@ -40,7 +60,10 @@ description = "Contains content to be rendered in the main body of the dialog." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" -attrs = [] +attrs = [ + { attr = "close_button", attr_type = "bool", default = "true", description = """Toggles \ + whether or not a close button should appear in the top right corner of the dialog.""" }, +] [[references]] name = "DialogDescription" @@ -52,8 +75,7 @@ attrs = [] [[references]] name = "DialogFooter" -description = """Displays at the bottom of the dialog, contains calls to action. Submit events \ -such as button clicks in this area automatically trigger closing of the dialog.""" +description = """Displays at the bottom of the dialog, contains calls to action.""" extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" @@ -69,7 +91,7 @@ attrs = [] [[references]] name = "DialogTitle" -description = "The title of the dialog." +description = "A title describing dialog content." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" diff --git a/docs/src/routes/components/dialog/examples/dialog.rs b/docs/src/routes/components/dialog/examples/dialog.rs index e2f113b7..1d409de6 100644 --- a/docs/src/routes/components/dialog/examples/dialog.rs +++ b/docs/src/routes/components/dialog/examples/dialog.rs @@ -1,45 +1,42 @@ use leptos::prelude::*; -use singlestage::{button::*, dialog::*, input::*, label::*}; +use singlestage::*; #[component] pub fn DialogExample() -> impl IntoView { view! { - <Dialog> - <DialogTrigger slot> - <Button variant="outline">"Edit Profile"</Button> + <Dialog class="sm:max-w-sm"> + <DialogTrigger> + <Button variant="outline">"Open Dialog"</Button> </DialogTrigger> - - <DialogClose /> - - <DialogHeader> - <DialogTitle>"Edit profile"</DialogTitle> - <DialogDescription> - "Make changes to your profile here. Click save when you're done." - </DialogDescription> - </DialogHeader> - <DialogContent> - <form class="grid gap-4"> - <div class="grid gap-3"> - <Label label_for="demo-dialog-edit-profile-name">"Name"</Label> - <Input - value="Pedro Duarte" - id="demo-dialog-edit-profile-name" - autofocus=true - /> - </div> - <div class="grid gap-3"> - <Label label_for="demo-dialog-edit-profile-username">"Username"</Label> - <Input value="@peduarte" id="demo-dialog-edit-profile-username" /> - </div> + <form> + <DialogHeader> + <DialogTitle>"Edit profile"</DialogTitle> + <DialogDescription> + "Make changes to your profile here. Click save when you're + done." + </DialogDescription> + </DialogHeader> + <FieldGroup> + <Field> + <Input name="name" value="Pedro Duarte"> + "Name" + </Input> + </Field> + <Field> + <Input name="username" value="@peduarte"> + "Username" + </Input> + </Field> + </FieldGroup> + <DialogFooter> + <DialogClose> + <Button>"Cancel"</Button> + </DialogClose> + <Button button_type="submit">"Save changes"</Button> + </DialogFooter> </form> </DialogContent> - - <DialogFooter> - <Button variant="outline">"Cancel"</Button> - <Button>"Save changes"</Button> - </DialogFooter> - </Dialog> } } diff --git a/docs/src/routes/components/dialog/examples/dialog_alert.rs b/docs/src/routes/components/dialog/examples/dialog_alert.rs deleted file mode 100644 index 0d8efbe4..00000000 --- a/docs/src/routes/components/dialog/examples/dialog_alert.rs +++ /dev/null @@ -1,25 +0,0 @@ -use leptos::prelude::*; -use singlestage::{button::*, dialog::*}; - -#[component] -pub fn DialogAlertExample() -> impl IntoView { - view! { - <Dialog alert=true> - <DialogTrigger slot> - <Button variant="destructive">"Delete account"</Button> - </DialogTrigger> - <DialogHeader> - <DialogTitle>"Are you absolutely sure?"</DialogTitle> - <DialogDescription> - "This action cannot be undone. - This will permanently delete your account - and remove your data from our servers." - </DialogDescription> - </DialogHeader> - <DialogFooter> - <Button variant="outline">"Cancel"</Button> - <Button variant="destructive">"Yes, delete account"</Button> - </DialogFooter> - </Dialog> - } -} diff --git a/docs/src/routes/components/dialog/examples/dialog_custom_close_button.rs b/docs/src/routes/components/dialog/examples/dialog_custom_close_button.rs new file mode 100644 index 00000000..7ecd37f8 --- /dev/null +++ b/docs/src/routes/components/dialog/examples/dialog_custom_close_button.rs @@ -0,0 +1,38 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DialogCustomCloseButtonExample() -> impl IntoView { + view! { + <Dialog class="sm:max-w-md"> + <DialogTrigger> + <Button variant="outline">"Share"</Button> + </DialogTrigger> + <DialogContent> + <DialogHeader> + <DialogTitle>"Share link"</DialogTitle> + <DialogDescription> + "Anyone who has this link will be able to view this." + </DialogDescription> + </DialogHeader> + <div class="flex items-center gap-2"> + <div class="grid flex-1 gap-2"> + <Label label_for="link" class="sr-only"> + "Link" + </Label> + <Input + id="link" + value="https://singlestage.doordesk.net/install" + readonly=true + /> + </div> + </div> + <DialogFooter class="sm:justify-start"> + <DialogClose> + <Button button_type="button">"Close"</Button> + </DialogClose> + </DialogFooter> + </DialogContent> + </Dialog> + } +} diff --git a/docs/src/routes/components/dialog/examples/dialog_no_close_button.rs b/docs/src/routes/components/dialog/examples/dialog_no_close_button.rs new file mode 100644 index 00000000..80b34e9c --- /dev/null +++ b/docs/src/routes/components/dialog/examples/dialog_no_close_button.rs @@ -0,0 +1,22 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DialogNoCloseButtonExample() -> impl IntoView { + view! { + <Dialog> + <DialogTrigger> + <Button variant="outline">"No Close Button"</Button> + </DialogTrigger> + <DialogContent close_button=false> + <DialogHeader> + <DialogTitle>"No Close Button"</DialogTitle> + <DialogDescription> + "This dialog doesn't have a close button in the top-right + corner." + </DialogDescription> + </DialogHeader> + </DialogContent> + </Dialog> + } +} diff --git a/docs/src/routes/components/dialog/examples/dialog_scrollable_content.rs b/docs/src/routes/components/dialog/examples/dialog_scrollable_content.rs new file mode 100644 index 00000000..c047dc4b --- /dev/null +++ b/docs/src/routes/components/dialog/examples/dialog_scrollable_content.rs @@ -0,0 +1,34 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DialogScrollableContentExample() -> impl IntoView { + view! { + <Dialog> + <DialogTrigger> + <Button variant="outline">"Scrollable Content"</Button> + </DialogTrigger> + <DialogContent> + <DialogHeader> + <DialogTitle>"Scrollable Content"</DialogTitle> + <DialogDescription> + "This is a dialog with scrollable content." + </DialogDescription> + </DialogHeader> + <div class="no-scrollbar -mx-4 max-h-[50vh] overflow-y-auto px-4"> + <For each=move || 0..10 key=|i| i.to_string() let(_)> + <p class="mb-4 leading-normal"> + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut + enim ad minim veniam, quis nostrud exercitation ullamco laboris + nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat + nulla pariatur. Excepteur sint occaecat cupidatat non proident, + sunt in culpa qui officia deserunt mollit anim id est laborum." + </p> + </For> + </div> + </DialogContent> + </Dialog> + } +} diff --git a/docs/src/routes/components/dialog/examples/dialog_sticky_footer.rs b/docs/src/routes/components/dialog/examples/dialog_sticky_footer.rs new file mode 100644 index 00000000..99e08f44 --- /dev/null +++ b/docs/src/routes/components/dialog/examples/dialog_sticky_footer.rs @@ -0,0 +1,39 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DialogStickyFooterExample() -> impl IntoView { + view! { + <Dialog> + <DialogTrigger> + <Button variant="outline">"Sticky Footer"</Button> + </DialogTrigger> + <DialogContent> + <DialogHeader> + <DialogTitle>"Sticky Footer"</DialogTitle> + <DialogDescription> + "This dialog has a sticky footer that stays visible while the content + scrolls." + </DialogDescription> + </DialogHeader> + <div class="no-scrollbar -mx-4 max-h-[50vh] overflow-y-auto px-4"> + <For each=move || 0..10 key=|i| i.to_string() let(_)> + <p class="mb-4 leading-normal"></p> + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut + enim ad minim veniam, quis nostrud exercitation ullamco laboris + nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat + nulla pariatur. Excepteur sint occaecat cupidatat non proident, + sunt in culpa qui officia deserunt mollit anim id est laborum." + </For> + </div> + <DialogFooter> + <DialogClose> + <Button variant="outline">"Close"</Button> + </DialogClose> + </DialogFooter> + </DialogContent> + </Dialog> + } +} diff --git a/docs/src/routes/components/dialog/examples/mod.rs b/docs/src/routes/components/dialog/examples/mod.rs index ad3012aa..f9e550a2 100644 --- a/docs/src/routes/components/dialog/examples/mod.rs +++ b/docs/src/routes/components/dialog/examples/mod.rs @@ -1,5 +1,11 @@ mod dialog; -mod dialog_alert; +mod dialog_custom_close_button; +mod dialog_no_close_button; +mod dialog_scrollable_content; +mod dialog_sticky_footer; pub use dialog::*; -pub use dialog_alert::*; +pub use dialog_custom_close_button::*; +pub use dialog_no_close_button::*; +pub use dialog_scrollable_content::*; +pub use dialog_sticky_footer::*; diff --git a/docs/src/routes/components/dropdown/anatomy.rs b/docs/src/routes/components/dropdown/anatomy.rs index d9d237be..2158c09b 100644 --- a/docs/src/routes/components/dropdown/anatomy.rs +++ b/docs/src/routes/components/dropdown/anatomy.rs @@ -16,6 +16,23 @@ pub fn DropdownMenuAnatomy() -> impl IntoView { </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> + <DropdownMenuSub> + <DropdownMenuSubTrigger /> + <DropdownMenuSubContent> + <DropdownMenuGroup> + <DropdownMenuCheckboxItem /> + <DropdownMenuCheckboxItem /> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuRadioGroup> + <DropdownMenuRadioItem /> + <DropdownMenuRadioItem /> + <DropdownMenuRadioItem /> + </DropdownMenuRadioGroup> + </DropdownMenuGroup> + </DropdownMenuSubContent> + </DropdownMenuSub> </DropdownMenuContent> </DropdownMenu> } diff --git a/docs/src/routes/components/dropdown/component.toml b/docs/src/routes/components/dropdown/component.toml index b1cfc2e3..1ee50a78 100644 --- a/docs/src/routes/components/dropdown/component.toml +++ b/docs/src/routes/components/dropdown/component.toml @@ -3,36 +3,134 @@ description = """Displays a menu to the user — such as a set of actions or fun by a button.""" [info] -title = "This component could use some work" -description = """While this component does make use of the new popover browser API, proper \ -positioning is still not widely supported. There are hacks in place to try to work around this \ -issue while still taking advantage of the rest of the API. Until this is implemented (hopefully \ -soon - Aug 03, 2025) that puts this component in only a 'Good Enough' state. "<singlestage::Link \ -style="color: var(--foreground)" \ -href="https://developer.mozilla.org/en-US/docs/Web/CSS/anchor#browser_compatibility"> "More \ -Info"</singlestage::Link>"""" +title = "This component uses newly available browser features" +description = """"<p>"This component uses CSS Anchor Positioning which has only been widely \ +supported since January 2026. If you're targeting older or less mainstream browsers then maybe \ +this component isn't for you, yet. "</p><p>" Alternatively you can use Singlestage 0.4 which \ +positions the component correctly but with limited flexibility for alignment, or roll your own \ +using Singlestage CSS classes for consistent styling. "</p><singlestage::Link style="color: \ +var(--foreground)" href="https://caniuse.com/css-anchor-positioning"> "More Info" \ +</singlestage::Link>"""" [[examples]] name = "Dropdown Menu" +[[examples]] +name = "Dropdown Basic" +title = "Basic" +description = "A basic dropdown menu with labels and separators." + +[[examples]] +name = "Dropdown Sub" +title = "Submenu" +description = "Use MenuSub to nest secondary actions." + +[[examples]] +name = "Dropdown Shortcuts" +title = "Shortcuts" +description = "Add MenuShortcut to show keyboard hints." + +[[examples]] +name = "Dropdown Icons" +title = "Icons" +description = "Combine icons with labels for quick scanning." + +[[examples]] +name = "Dropdown Checkboxes" +title = "Checkboxes" +description = "Dropdown menus can use Checkboxes." + +[[examples]] +name = "Dropdown Checkbox Icons" +title = "Checkbox Icons" +description = "Icons can be added to checkbox items." + +[[examples]] +name = "Dropdown Radio Group" +title = "Radio Group" +description = "Dropdown menus can use RadioGroups." + +[[examples]] +name = "Dropdown Radio Icons" +title = "Radio Icons" +description = "Icons can be added to radio items.." + +[[examples]] +name = "Dropdown Destructive" +title = "Destructive" +description = "Use the destructive variant for irreversible actions." + +[[examples]] +name = "Dropdown Avatar" +title = "Avatar" +description = "An account switcher dropdown triggered by an avatar." + +[[examples]] +name = "Dropdown Complex" +title = "Complex" +description = "A richer example combining groups, icons, and submenus." + +[[references]] +name = "CheckboxItem" +description = "A checkbox item for popover menus." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +and <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#attributes">li</a> \ +attributes.""" +attrs = [ + { attr = "disabled", attr_type = "bool", default = "false", description = """Controls \ + whether the item appears disabled or not.""" }, + { attr = "dismiss", attr_type = "bool", default = "true", description = """Toggle whether \ + clicking this item dismisses the parent menu.""" }, + { attr = "inset", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display inset from its normal position.""" }, + { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ + variant of the item. Accepted values: \"destructive\".""" }, +] + [[references]] name = "DropdownMenu" description = "Contains all the parts of a dropdown menu." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" -attrs = [] +attrs = [ + { attr = "modal", attr_type = "bool", default = "false", description = """ Set whether or not \ + this popover should be modal meaning it can't be light dismissed Use this along with the \ + `open` signal for a manually managed popover""" }, + { attr = "open", attr_type = "bool", default = "false", description = """Reactive signal that \ + can remotely control the open state of the popover **but is not coupled to the actual open \ + state of the popover** unless `modal` is set to `true`""" }, +] [[references]] -name = "DropdownMenuContent" +name = "Label" +description = "Labels groups." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [ + { attr = "invalid", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display as invalid.""" }, +] + +[[references]] +name = "MenuContent" description = "The component that pops out when the dropdown menu is open." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" -attrs = [] +attrs = [ + { attr = "align", attr_type = "String", default = """"start"""", description = """Set how to \ + align the popover. Accepted values are "start" | "center" | "end"""" }, + { attr = "side", attr_type = "String", default = """"bottom"""", description = """Set which \ + side the popover opens relative to the trigger. Accepted values are "top" | "right" | \ + "bottom" | "left". """ }, +] [[references]] -name = "DropdownMenuGroup" +name = "MenuGroup" description = "Contains multiple items." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ @@ -40,7 +138,7 @@ attributes.""" attrs = [] [[references]] -name = "DropdownMenuItem" +name = "MenuItem" description = "Contains a menu item." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ @@ -50,38 +148,86 @@ attributes.""" attrs = [ { attr = "disabled", attr_type = "bool", default = "false", description = """Controls \ whether the item appears disabled or not.""" }, + { attr = "dismiss", attr_type = "bool", default = "true", description = """Toggle whether \ + clicking this item dismisses the parent menu.""" }, + { attr = "inset", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display inset from its normal position.""" }, { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ - variant of the item.""" }, + variant of the item. Accepted values are "destructive"""" }, ] [[references]] -name = "DropdownMenuLabel" -description = "Labels groups." +name = "MenuShortcut" +description = "Displays next to the item content and indicates keyboard shortcuts." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [] [[references]] -name = "DropdownMenuSeparator" -description = "Visually separates groups of menu items." +name = "MenuSub" +description = "Contains all the parts of a sub menu." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [] [[references]] -name = "DropdownMenuShortcut" -description = "Displays next to the item content and indicates keyboard shortcuts." +name = "MenuSubContent" +description = "The component that pops out when the sub trigger is triggered." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [] [[references]] -name = "DropdownMenuTrigger" +name = "MenuSubTrigger" +description = "Defines the area where a sub menu can be triggered." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +and <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#attributes">li</a> \ +attributes.""" +attrs = [ + { attr = "disabled", attr_type = "bool", default = "false", description = """Controls \ + whether the item appears disabled or not.""" }, + { attr = "inset", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display inset from its normal position.""" }, + { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ + variant of the item. Accepted values: \"destructive\".""" }, +] + +[[references]] +name = "Trigger" description = "Provides context to a Button that triggers a DropdownMenu." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [] + +[[references]] +name = "RadioItem" +description = "A radio item for popover menus." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +and <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/li#attributes">li</a> \ +attributes.""" +attrs = [ + { attr = "disabled", attr_type = "bool", default = "false", description = """Controls \ + whether the item appears disabled or not.""" }, + { attr = "dismiss", attr_type = "bool", default = "true", description = """Toggle whether \ + clicking this item dismisses the parent menu.""" }, + { attr = "inset", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display inset from its normal position.""" }, + { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ + variant of the item. Accepted values: \"destructive\".""" }, +] + +[[references]] +name = "Separator" +description = "Visually separates groups of menu items." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] diff --git a/docs/src/routes/components/dropdown/examples/dropdown_avatar.rs b/docs/src/routes/components/dropdown/examples/dropdown_avatar.rs new file mode 100644 index 00000000..9b0140e4 --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_avatar.rs @@ -0,0 +1,27 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownAvatarExample() -> impl IntoView { + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="ghost" size="icon" class="rounded-full"> + <Avatar> + <AvatarImage src="https://github.com/shadcn.png" alt="shadcn" /> + <AvatarFallback>"LR"</AvatarFallback> + </Avatar> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <DropdownMenuGroup> + <DropdownMenuItem>{icon!(icondata::LuBadgeCheck)} "Account"</DropdownMenuItem> + <DropdownMenuItem>{icon!(icondata::LuCreditCard)} "Billing"</DropdownMenuItem> + <DropdownMenuItem>{icon!(icondata::LuBell)} "Notifications"</DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuItem>{icon!(icondata::LuLogOut)} "Sign Out"</DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_basic.rs b/docs/src/routes/components/dropdown/examples/dropdown_basic.rs new file mode 100644 index 00000000..36f8ceb3 --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_basic.rs @@ -0,0 +1,25 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownBasicExample() -> impl IntoView { + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Open"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuGroup> + <DropdownMenuLabel>"My Account"</DropdownMenuLabel> + <DropdownMenuItem>"Profile"</DropdownMenuItem> + <DropdownMenuItem>"Billing"</DropdownMenuItem> + <DropdownMenuItem>"Settings"</DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuItem>"GitHub"</DropdownMenuItem> + <DropdownMenuItem>"Support"</DropdownMenuItem> + <DropdownMenuItem disabled=true>"API"</DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_checkbox_icons.rs b/docs/src/routes/components/dropdown/examples/dropdown_checkbox_icons.rs new file mode 100644 index 00000000..e6c0761b --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_checkbox_icons.rs @@ -0,0 +1,42 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownCheckboxIconsExample() -> impl IntoView { + struct Notifications { + email: RwSignal<bool>, + sms: RwSignal<bool>, + push: RwSignal<bool>, + } + + let notifications = Notifications { + email: RwSignal::new(true), + sms: RwSignal::new(false), + push: RwSignal::new(true), + }; + + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Notifications"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent class="w-48"> + <DropdownMenuGroup> + <DropdownMenuLabel>"Notification Preferences"</DropdownMenuLabel> + <DropdownMenuCheckboxItem checked=notifications + .email> + {icon!(icondata::LuMail)} "Email notifications" + </DropdownMenuCheckboxItem> + <DropdownMenuCheckboxItem checked=notifications + .sms> + {icon!(icondata::LuMessageSquare)} "SMS notifications" + </DropdownMenuCheckboxItem> + <DropdownMenuCheckboxItem checked=notifications + .push> + {icon!(icondata::LuBell)} "Push notifications" + </DropdownMenuCheckboxItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_checkboxes.rs b/docs/src/routes/components/dropdown/examples/dropdown_checkboxes.rs new file mode 100644 index 00000000..dcdfc89e --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_checkboxes.rs @@ -0,0 +1,29 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownCheckboxesExample() -> impl IntoView { + let show_status_bar = RwSignal::new(false); + let show_activity_bar = RwSignal::new(false); + let show_panel = RwSignal::new(false); + + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">Open</Button> + </DropdownMenuTrigger> + <DropdownMenuContent class="w-40"> + <DropdownMenuGroup> + <DropdownMenuLabel>"Appearance"</DropdownMenuLabel> + <DropdownMenuCheckboxItem checked=show_status_bar> + "Status Bar" + </DropdownMenuCheckboxItem> + <DropdownMenuCheckboxItem checked=show_activity_bar disabled=true> + "Activity Bar" + </DropdownMenuCheckboxItem> + <DropdownMenuCheckboxItem checked=show_panel>"Panel"</DropdownMenuCheckboxItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_complex.rs b/docs/src/routes/components/dropdown/examples/dropdown_complex.rs new file mode 100644 index 00000000..7207c6bf --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_complex.rs @@ -0,0 +1,178 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownComplexExample() -> impl IntoView { + let theme = RwSignal::new("light".to_string()); + + struct Notifications { + email: RwSignal<bool>, + push: RwSignal<bool>, + } + + let notifications = Notifications { + email: RwSignal::new(true), + push: RwSignal::new(true), + }; + + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Complex Menu"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent class="w-44"> + <DropdownMenuGroup> + <DropdownMenuLabel>"File"</DropdownMenuLabel> + <DropdownMenuItem> + {icon!(icondata::LuFile)} "New File" + <DropdownMenuShortcut>"⌘N"</DropdownMenuShortcut> + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuFolder)} "New Folder" + <DropdownMenuShortcut>"⇧⌘N"</DropdownMenuShortcut> + </DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger> + {icon!(icondata::LuFolderOpen)} "Open Recent" + </DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuGroup> + <DropdownMenuLabel>"Recent Projects"</DropdownMenuLabel> + <DropdownMenuItem> + {icon!(icondata::LuFileCode)} "Project Alpha" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuFileCode)} "Project Beta" + </DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger> + {icon!(icondata::FiMoreHorizontal)} "More Projects" + </DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuItem> + {icon!(icondata::LuFileCode)} "Project Gamma" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuFileCode)} "Project Delta" + </DropdownMenuItem> + </DropdownMenuSubContent> + </DropdownMenuSub> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::LuFolderSearch)} "Browse..." + </DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuSubContent> + </DropdownMenuSub> + <DropdownMenuSeparator /> + <DropdownMenuItem> + {icon!(icondata::LuSave)} "Save" + <DropdownMenuShortcut>"⌘S"</DropdownMenuShortcut> + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuDownload)} "Export" + <DropdownMenuShortcut>"⇧⌘E"</DropdownMenuShortcut> + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuLabel>"View"</DropdownMenuLabel> + <DropdownMenuCheckboxItem checked=true> + {icon!(icondata::LuEye)} "Show Sidebar" + </DropdownMenuCheckboxItem> + <DropdownMenuCheckboxItem> + {icon!(icondata::FiLayout)} "Show Status Bar" + </DropdownMenuCheckboxItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger> + {icon!(icondata::LuPalette)} "Theme" + </DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuGroup> + <DropdownMenuLabel>"Appearance"</DropdownMenuLabel> + <DropdownMenuRadioGroup value=theme> + <DropdownMenuRadioItem value="light"> + {icon!(icondata::LuSun)} "Light" + </DropdownMenuRadioItem> + <DropdownMenuRadioItem value="dark"> + {icon!(icondata::LuMoon)} "Dark" + </DropdownMenuRadioItem> + <DropdownMenuRadioItem value="system"> + {icon!(icondata::LuMonitor)} "System" + </DropdownMenuRadioItem> + </DropdownMenuRadioGroup> + </DropdownMenuGroup> + </DropdownMenuSubContent> + </DropdownMenuSub> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuLabel>"Account"</DropdownMenuLabel> + <DropdownMenuItem> + {icon!(icondata::LuUser)} "Profile" + <DropdownMenuShortcut>"⇧⌘P"</DropdownMenuShortcut> + </DropdownMenuItem> + <DropdownMenuItem>{icon!(icondata::LuCreditCard)} "Billing"</DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger> + {icon!(icondata::LuSettings)} "Settings" + </DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuGroup> + <DropdownMenuLabel>"Preferences"</DropdownMenuLabel> + <DropdownMenuItem> + {icon!(icondata::LuKeyboard)} "Keyboard Shortcuts" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuLanguages)} "Language" + </DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger> + {icon!(icondata::LuBell)} "Notifications" + </DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuGroup> + <DropdownMenuLabel>"Notification Types"</DropdownMenuLabel> + <DropdownMenuCheckboxItem checked=notifications + .push> + {icon!(icondata::LuBell)} "Push Notifications" + </DropdownMenuCheckboxItem> + <DropdownMenuCheckboxItem checked=notifications + .email> + {icon!(icondata::LuMail)} "Email Notifications" + </DropdownMenuCheckboxItem> + </DropdownMenuGroup> + </DropdownMenuSubContent> + </DropdownMenuSub> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::LuShield)} "Privacy & Security" + </DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuSubContent> + </DropdownMenuSub> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::FiHelpCircle)} "Help & Support" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuFileText)} "Documentation" + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem variant="destructive"> + {icon!(icondata::LuLogOut)} "Sign Out" + <DropdownMenuShortcut>"⇧⌘Q"</DropdownMenuShortcut> + </DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_destructive.rs b/docs/src/routes/components/dropdown/examples/dropdown_destructive.rs new file mode 100644 index 00000000..b28beda4 --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_destructive.rs @@ -0,0 +1,25 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownDestructiveExample() -> impl IntoView { + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Actions"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuGroup> + <DropdownMenuItem>{icon!(icondata::LuPencil)} "Edit"</DropdownMenuItem> + <DropdownMenuItem>{icon!(icondata::LuShare)} "Share"</DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem variant="destructive"> + {icon!(icondata::LuTrash)} "Delete" + </DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_icons.rs b/docs/src/routes/components/dropdown/examples/dropdown_icons.rs new file mode 100644 index 00000000..bc5f1382 --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_icons.rs @@ -0,0 +1,22 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownIconsExample() -> impl IntoView { + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Open"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuItem>{icon!(icondata::LuUser)} "Profile"</DropdownMenuItem> + <DropdownMenuItem>{icon!(icondata::LuCreditCard)} "Billing"</DropdownMenuItem> + <DropdownMenuItem>{icon!(icondata::LuSettings)} "Settings"</DropdownMenuItem> + <DropdownMenuSeparator /> + <DropdownMenuItem variant="destructive"> + {icon!(icondata::LuLogOut)} "Log out" + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_menu.rs b/docs/src/routes/components/dropdown/examples/dropdown_menu.rs index f3bdc72c..1f598230 100644 --- a/docs/src/routes/components/dropdown/examples/dropdown_menu.rs +++ b/docs/src/routes/components/dropdown/examples/dropdown_menu.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::{Button, dropdown::*, icon}; +use singlestage::*; #[component] pub fn DropdownMenuExample() -> impl IntoView { @@ -8,7 +8,7 @@ pub fn DropdownMenuExample() -> impl IntoView { <DropdownMenuTrigger> <Button variant="outline">"Open"</Button> </DropdownMenuTrigger> - <DropdownMenuContent> + <DropdownMenuContent class="w-40" align="start"> <DropdownMenuGroup> <DropdownMenuLabel>"My Account"</DropdownMenuLabel> <DropdownMenuItem> @@ -20,8 +20,21 @@ pub fn DropdownMenuExample() -> impl IntoView { <DropdownMenuItem> "Settings" <DropdownMenuShortcut>"⌘S"</DropdownMenuShortcut> </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem>"Team"</DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger>"Invite users"</DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuItem>"Email"</DropdownMenuItem> + <DropdownMenuItem>"Message"</DropdownMenuItem> + <DropdownMenuSeparator /> + <DropdownMenuItem>"More..."</DropdownMenuItem> + </DropdownMenuSubContent> + </DropdownMenuSub> <DropdownMenuItem> - "Keyboard shortcuts" <DropdownMenuShortcut>"⌘K"</DropdownMenuShortcut> + "New Team" <DropdownMenuShortcut>"⌘+T"</DropdownMenuShortcut> </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> @@ -32,12 +45,8 @@ pub fn DropdownMenuExample() -> impl IntoView { </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> - <DropdownMenuItem variant="destructive"> - {icon!(icondata::LuTrash2)} "Delete Account" - </DropdownMenuItem> <DropdownMenuItem> - {icon!(icondata::LuLogOut)} "Logout" - <DropdownMenuShortcut>"⇧⌘Q"</DropdownMenuShortcut> + "Log out" <DropdownMenuShortcut>"⇧⌘Q"</DropdownMenuShortcut> </DropdownMenuItem> </DropdownMenuGroup> </DropdownMenuContent> diff --git a/docs/src/routes/components/dropdown/examples/dropdown_radio_group.rs b/docs/src/routes/components/dropdown/examples/dropdown_radio_group.rs new file mode 100644 index 00000000..d248bcbb --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_radio_group.rs @@ -0,0 +1,25 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownRadioGroupExample() -> impl IntoView { + let position = RwSignal::new("bottom".to_string()); + + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Open"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent class="w-32"> + <DropdownMenuGroup> + <DropdownMenuLabel>"Panel Position"</DropdownMenuLabel> + <DropdownMenuRadioGroup value=position> + <DropdownMenuRadioItem value="top">"Top"</DropdownMenuRadioItem> + <DropdownMenuRadioItem value="bottom">"Bottom"</DropdownMenuRadioItem> + <DropdownMenuRadioItem value="right">"Right"</DropdownMenuRadioItem> + </DropdownMenuRadioGroup> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_radio_icons.rs b/docs/src/routes/components/dropdown/examples/dropdown_radio_icons.rs new file mode 100644 index 00000000..a15a1484 --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_radio_icons.rs @@ -0,0 +1,30 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownRadioIconsExample() -> impl IntoView { + let payment_method = RwSignal::new("card".to_string()); + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Payment Method"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-56"> + <DropdownMenuGroup> + <DropdownMenuLabel>"Select Payment Method"</DropdownMenuLabel> + <DropdownMenuRadioGroup value=payment_method> + <DropdownMenuRadioItem value="card"> + {icon!(icondata::LuCreditCard)} "Credit Card" + </DropdownMenuRadioItem> + <DropdownMenuRadioItem value="paypal"> + {icon!(icondata::LuWallet)} "PayPal" + </DropdownMenuRadioItem> + <DropdownMenuRadioItem value="bank"> + {icon!(icondata::LuBuilding2)} "Bank Transfer" + </DropdownMenuRadioItem> + </DropdownMenuRadioGroup> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_shortcuts.rs b/docs/src/routes/components/dropdown/examples/dropdown_shortcuts.rs new file mode 100644 index 00000000..05b4ceec --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_shortcuts.rs @@ -0,0 +1,31 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownShortcutsExample() -> impl IntoView { + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Open"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuGroup> + <DropdownMenuLabel>"My Account"</DropdownMenuLabel> + <DropdownMenuItem> + "Profile" <DropdownMenuShortcut>"⇧⌘P"</DropdownMenuShortcut> + </DropdownMenuItem> + <DropdownMenuItem> + "Billing" <DropdownMenuShortcut>"⌘B"</DropdownMenuShortcut> + </DropdownMenuItem> + <DropdownMenuItem> + "Settings" <DropdownMenuShortcut>"⌘S"</DropdownMenuShortcut> + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuItem> + "Log out" <DropdownMenuShortcut>"⇧⌘Q"</DropdownMenuShortcut> + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/dropdown_sub.rs b/docs/src/routes/components/dropdown/examples/dropdown_sub.rs new file mode 100644 index 00000000..ae0ff7b2 --- /dev/null +++ b/docs/src/routes/components/dropdown/examples/dropdown_sub.rs @@ -0,0 +1,39 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DropdownSubExample() -> impl IntoView { + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Open"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuGroup> + <DropdownMenuItem>"Team"</DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger>"Invite users"</DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuItem>"Email"</DropdownMenuItem> + <DropdownMenuItem>"Message"</DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger>"More options"</DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <DropdownMenuItem>"Calendly"</DropdownMenuItem> + <DropdownMenuItem>"Slack"</DropdownMenuItem> + <DropdownMenuSeparator /> + <DropdownMenuItem>"Webhook"</DropdownMenuItem> + </DropdownMenuSubContent> + </DropdownMenuSub> + <DropdownMenuSeparator /> + <DropdownMenuItem>"Advanced..."</DropdownMenuItem> + </DropdownMenuSubContent> + </DropdownMenuSub> + <DropdownMenuItem> + "New Team" <DropdownMenuShortcut>"⌘+T"</DropdownMenuShortcut> + </DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + } +} diff --git a/docs/src/routes/components/dropdown/examples/mod.rs b/docs/src/routes/components/dropdown/examples/mod.rs index e00de10b..2ab01805 100644 --- a/docs/src/routes/components/dropdown/examples/mod.rs +++ b/docs/src/routes/components/dropdown/examples/mod.rs @@ -1,3 +1,25 @@ +mod dropdown_avatar; +mod dropdown_basic; +mod dropdown_checkbox_icons; +mod dropdown_checkboxes; +mod dropdown_complex; +mod dropdown_destructive; +mod dropdown_icons; mod dropdown_menu; +mod dropdown_radio_group; +mod dropdown_radio_icons; +mod dropdown_shortcuts; +mod dropdown_sub; +pub use dropdown_avatar::*; +pub use dropdown_basic::*; +pub use dropdown_checkbox_icons::*; +pub use dropdown_checkboxes::*; +pub use dropdown_complex::*; +pub use dropdown_destructive::*; +pub use dropdown_icons::*; pub use dropdown_menu::*; +pub use dropdown_radio_group::*; +pub use dropdown_radio_icons::*; +pub use dropdown_shortcuts::*; +pub use dropdown_sub::*; diff --git a/docs/src/routes/components/empty/examples/empty.rs b/docs/src/routes/components/empty/examples/empty.rs index ac9ceaa4..666ea5cd 100644 --- a/docs/src/routes/components/empty/examples/empty.rs +++ b/docs/src/routes/components/empty/examples/empty.rs @@ -13,13 +13,17 @@ pub fn EmptyExample() -> impl IntoView { your first project." </EmptyDescription> </EmptyHeader> - <EmptyContent> - <div class="flex gap-2"> - <Button>"Create Project"</Button> - <Button variant="outline">"Import Project"</Button> - </div> + <EmptyContent class="flex-row justify-center gap-2"> + <Button>"Create Project"</Button> + <Button variant="outline">"Import Project"</Button> </EmptyContent> - <Link as_button=true size="sm" class="text-(--muted-foreground)" href="#"> + <Link + render_as="button" + variant="link" + size="sm" + class="text-(--muted-foreground)" + href="#" + > "Learn More" {icon!(icondata::LuArrowUpRight)} </Link> diff --git a/docs/src/routes/components/empty/examples/empty_background.rs b/docs/src/routes/components/empty/examples/empty_background.rs index 7f5999c3..82a371a0 100644 --- a/docs/src/routes/components/empty/examples/empty_background.rs +++ b/docs/src/routes/components/empty/examples/empty_background.rs @@ -4,7 +4,7 @@ use singlestage::*; #[component] pub fn EmptyBackgroundExample() -> impl IntoView { view! { - <Empty class="from-(--muted)/50 to-(--background) h-full bg-gradient-to-b from-30% rounded-md"> + <Empty class="min-h-[450px] from-(--muted)/50 to-(--background) h-full bg-gradient-to-b from-30%"> <EmptyHeader> <EmptyMedia variant="icon">{icon!(icondata::LuBell)}</EmptyMedia> <EmptyTitle>"No Notifications"</EmptyTitle> diff --git a/docs/src/routes/components/field/component.toml b/docs/src/routes/components/field/component.toml index 03f2dedc..1eab4ad2 100644 --- a/docs/src/routes/components/field/component.toml +++ b/docs/src/routes/components/field/component.toml @@ -46,7 +46,7 @@ Radio, Checkbox and Switch components.""" [[examples]] name = "field group" title = "Field Group" -description = "Stack Field components with FieldGroup. Add FieldSeparator to divide them." +description = "Stack Field components with FieldGroup. Add Separator to divide them." [[examples]] name = "field responsive layout" @@ -94,8 +94,8 @@ extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [ - { attr = "errors", attr_type = "Reactive<Vec<String>>", default = "[]", description = """A \ - reactive list of errors to display.""" }, + { attr = "errors", attr_type = "Vec<String>", default = "[]", description = """A reactive list \ + of errors to display.""" }, ] @@ -132,7 +132,7 @@ attrs = [ [[references]] name = "FieldSeparator" -description = """Visual divider to separate sections inside a FieldGroup. Accepts optional inline \ +description = """Visual divider to separate sections inside a FieldSet. Accepts optional inline \ content.""" extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ diff --git a/docs/src/routes/components/field/examples/field.rs b/docs/src/routes/components/field/examples/field.rs index 77aab21b..eb1da0d6 100644 --- a/docs/src/routes/components/field/examples/field.rs +++ b/docs/src/routes/components/field/examples/field.rs @@ -14,61 +14,63 @@ pub fn FieldExample() -> impl IntoView { </FieldDescription> <FieldGroup> <Field> - <Input placeholder="Evil Rabbit" required=true> + <FieldLabel label_for="checkout-7j9-card-name-43j"> "Name on Card" - </Input> + </FieldLabel> + <Input id="checkout-7j9-card-name-43j" placeholder="Evil Rabbit" /> + </Field> <Field> + <FieldLabel label_for="checkout-7j9-card-number-uw1"> + "Card Number" + </FieldLabel> <Input + id="checkout-7j9-card-number-uw1" placeholder="1234 5678 9012 3456" - inputmode="numeric" - required=true - > - "Card Number" - </Input> + /> + <FieldDescription> "Enter your 16-digit card number" </FieldDescription> </Field> - <FieldGroup class="grid grid-cols-3"> + <div class="grid grid-cols-3 gap-4"> <Field> - <FieldLabel>"Month"</FieldLabel> - <Select placeholder="MM"> - <SelectContent> - <SelectItem value="01">"01"</SelectItem> - <SelectItem value="02">"02"</SelectItem> - <SelectItem value="03">"03"</SelectItem> - <SelectItem value="04">"04"</SelectItem> - <SelectItem value="05">"05"</SelectItem> - <SelectItem value="06">"06"</SelectItem> - <SelectItem value="07">"07"</SelectItem> - <SelectItem value="08">"08"</SelectItem> - <SelectItem value="09">"09"</SelectItem> - <SelectItem value="10">"10"</SelectItem> - <SelectItem value="11">"11"</SelectItem> - <SelectItem value="12">"12"</SelectItem> - </SelectContent> + <FieldLabel label_for="checkout-exp-month-ts6"> + "Month" + </FieldLabel> + <Select placeholder="MM" id="checkout-exp-month-ts6"> + <SelectOption value="01">"01"</SelectOption> + <SelectOption value="02">"02"</SelectOption> + <SelectOption value="03">"03"</SelectOption> + <SelectOption value="04">"04"</SelectOption> + <SelectOption value="05">"05"</SelectOption> + <SelectOption value="06">"06"</SelectOption> + <SelectOption value="07">"07"</SelectOption> + <SelectOption value="08">"08"</SelectOption> + <SelectOption value="09">"09"</SelectOption> + <SelectOption value="10">"10"</SelectOption> + <SelectOption value="11">"11"</SelectOption> + <SelectOption value="12">"12"</SelectOption> </Select> </Field> <Field> - <FieldLabel>"Year"</FieldLabel> - <Select placeholder="YYYY"> - <SelectContent> - <SelectItem value="2024">"2024"</SelectItem> - <SelectItem value="2025">"2025"</SelectItem> - <SelectItem value="2026">"2026"</SelectItem> - <SelectItem value="2027">"2027"</SelectItem> - <SelectItem value="2028">"2028"</SelectItem> - <SelectItem value="2029">"2029"</SelectItem> - </SelectContent> + <FieldLabel label_for="checkout-7j9-exp-year-f59"> + "Year" + </FieldLabel> + <Select id="checkout-7j9-exp-year-f59" placeholder="YYYY"> + <SelectOption value="2024">"2024"</SelectOption> + <SelectOption value="2025">"2025"</SelectOption> + <SelectOption value="2026">"2026"</SelectOption> + <SelectOption value="2027">"2027"</SelectOption> + <SelectOption value="2028">"2028"</SelectOption> + <SelectOption value="2029">"2029"</SelectOption> </Select> </Field> <Field> - <Input inputmode="numeric" placeholder="123" required=true> - "CVV" - </Input> + <FieldLabel label_for="checkout-7j9-cvv">"CVV"</FieldLabel> + <Input id="checkout-7j9-cvv" placeholder="123" /> </Field> - </FieldGroup> + </div> </FieldGroup> </FieldSet> <FieldSeparator /> @@ -77,18 +79,31 @@ pub fn FieldExample() -> impl IntoView { <FieldDescription> "The billing address associated with your payment method" </FieldDescription> - <Field orientation="horizontal"> - <Checkbox class="font-normal" checked=true> - "Same as shipping address" - </Checkbox> - </Field> + <FieldGroup> + <Field orientation="horizontal"> + <Checkbox id="checkout-7j9-same-as-shipping-wgm" checked=true /> + <FieldLabel + label_for="checkout-7j9-same-as-shipping-wgm" + class="font-normal" + > + "Same as shipping address" + </FieldLabel> + </Field> + </FieldGroup> </FieldSet> <FieldSet> - <Field> - <Textarea placeholder="Add any additional comments" class="resize-none"> - "Comments" - </Textarea> - </Field> + <FieldGroup> + <Field> + <FieldLabel label_for="checkout-7j9-optional-comments"> + "Comments" + </FieldLabel> + <Textarea + id="checkout-7j9-optional-comments" + placeholder="Add any additional comments" + class="resize-none" + /> + </Field> + </FieldGroup> </FieldSet> <Field orientation="horizontal"> <Button button_type="submit">"Submit"</Button> diff --git a/docs/src/routes/components/field/examples/field_checkbox.rs b/docs/src/routes/components/field/examples/field_checkbox.rs index 12cdf887..9c2c4c08 100644 --- a/docs/src/routes/components/field/examples/field_checkbox.rs +++ b/docs/src/routes/components/field/examples/field_checkbox.rs @@ -4,44 +4,58 @@ use singlestage::*; #[component] pub fn FieldCheckboxExample() -> impl IntoView { view! { - <div class="w-full max-w-md"> - <FieldGroup> - <FieldSet> - <FieldLegend variant="label">"Show these items on the desktop"</FieldLegend> + <FieldGroup class="w-full max-w-xs"> + <FieldSet> + <FieldLegend variant="label">"Show these items on the desktop"</FieldLegend> + <FieldDescription> + "Select the items you want to show on the desktop." + </FieldDescription> + <FieldGroup class="gap-3"> + <Field orientation="horizontal"> + <Checkbox id="finder-pref-9k2-hard-disks-ljj" checked=true /> + <FieldLabel label_for="finder-pref-9k2-hard-disks-ljj" class="font-normal"> + "Hard disks" + </FieldLabel> + </Field> + <Field orientation="horizontal"> + <Checkbox id="finder-pref-9k2-external-disks-1yg" /> + <FieldLabel + label_for="finder-pref-9k2-external-disks-1yg" + class="font-normal" + > + "External disks" + </FieldLabel> + </Field> + <Field orientation="horizontal"> + <Checkbox id="finder-pref-9k2-cds-dvds-fzt" /> + <FieldLabel label_for="finder-pref-9k2-cds-dvds-fzt" class="font-normal"> + "CDs, DVDs, and iPods" + </FieldLabel> + </Field> + <Field orientation="horizontal"> + <Checkbox id="finder-pref-9k2-connected-servers-6l2" /> + <FieldLabel + label_for="finder-pref-9k2-connected-servers-6l2" + class="font-normal" + > + "Connected servers" + </FieldLabel> + </Field> + </FieldGroup> + </FieldSet> + <FieldSeparator /> + <Field orientation="horizontal"> + <Checkbox id="finder-pref-9k2-sync-folders-nep" checked=true /> + <FieldContent> + <FieldLabel label_for="finder-pref-9k2-sync-folders-nep"> + "Sync Desktop & Documents folders" + </FieldLabel> <FieldDescription> - "Select the items you want to show on the desktop." + "Your Desktop & Documents folders are being synced with iCloud Drive. + You can access them from other devices." </FieldDescription> - <FieldGroup> - <CheckboxGroup> - <Field orientation="horizontal"> - <Checkbox class="font-normal">"Hard disks"</Checkbox> - </Field> - <Field orientation="horizontal"> - <Checkbox class="font-normal">"External disks"</Checkbox> - </Field> - <Field orientation="horizontal"> - <Checkbox class="font-normal">"CDs, DVDs, and iPods"</Checkbox> - </Field> - <Field orientation="horizontal"> - <Checkbox class="font-normal">"Connected servers"</Checkbox> - </Field> - </CheckboxGroup> - </FieldGroup> - </FieldSet> - <FieldSeparator /> - <Field orientation="horizontal"> - <Checkbox checked=true /> - <FieldLabel> - <FieldContent> - <FieldTitle>"Sync Desktop & Documents folders"</FieldTitle> - <FieldDescription> - "Your Desktop & Documents folders are being synced with iCloud - Drive. You can access them from other devices." - </FieldDescription> - </FieldContent> - </FieldLabel> - </Field> - </FieldGroup> - </div> + </FieldContent> + </Field> + </FieldGroup> } } diff --git a/docs/src/routes/components/field/examples/field_choice_card.rs b/docs/src/routes/components/field/examples/field_choice_card.rs index c5da6f72..2eb225e1 100644 --- a/docs/src/routes/components/field/examples/field_choice_card.rs +++ b/docs/src/routes/components/field/examples/field_choice_card.rs @@ -4,39 +4,33 @@ use singlestage::*; #[component] pub fn FieldChoiceCardExample() -> impl IntoView { view! { - <div class="w-full max-w-md"> - <FieldSet> - <FieldLegend variant="label">"Compute Environment"</FieldLegend> - <FieldDescription> - "Select the compute environment for your cluster." - </FieldDescription> - <FieldGroup> - <RadioGroup default="kubernetes"> - <Field orientation="horizontal" variant="button"> - <FieldLabel> - <FieldContent> - <FieldTitle>"Kubernetes"</FieldTitle> - <FieldDescription> - "Run GPU workloads on a K8s configured cluster." - </FieldDescription> - </FieldContent> - </FieldLabel> - <Radio value="kubernetes" /> - </Field> - <Field orientation="horizontal" variant="button"> - <FieldLabel> - <FieldContent> - <FieldTitle>"Virtual Machine"</FieldTitle> - <FieldDescription> - "Access a VM configured cluster to run GPU workloads." - </FieldDescription> - </FieldContent> - </FieldLabel> - <Radio value="vm" /> - </Field> - </RadioGroup> - </FieldGroup> - </FieldSet> - </div> + <FieldSet class="w-full max-w-xs"> + <FieldLegend variant="label">"Compute Environment"</FieldLegend> + <FieldDescription>"Select the compute environment for your cluster."</FieldDescription> + <RadioGroup value="kubernetes"> + <FieldLabel label_for="kubernetes-r2h"> + <Field orientation="horizontal"> + <FieldContent> + <FieldTitle>"Kubernetes"</FieldTitle> + <FieldDescription> + "Run GPU workloads on a K8s cluster." + </FieldDescription> + </FieldContent> + <Radio value="kubernetes" id="kubernetes-r2h" /> + </Field> + </FieldLabel> + <FieldLabel label_for="vm-z4k"> + <Field orientation="horizontal"> + <FieldContent> + <FieldTitle>"Virtual Machine"</FieldTitle> + <FieldDescription> + "Access a cluster to run GPU workloads." + </FieldDescription> + </FieldContent> + <Radio value="vm" id="vm-z4k" /> + </Field> + </FieldLabel> + </RadioGroup> + </FieldSet> } } diff --git a/docs/src/routes/components/field/examples/field_errors.rs b/docs/src/routes/components/field/examples/field_errors.rs index 0f512e4b..31e3487d 100644 --- a/docs/src/routes/components/field/examples/field_errors.rs +++ b/docs/src/routes/components/field/examples/field_errors.rs @@ -55,19 +55,20 @@ pub fn FieldErrorsExample() -> impl IntoView { <FieldSet> <FieldGroup> <Field> - <FieldLabel>"Username *"</FieldLabel> + <FieldLabel>"Username" <Show when=move || username_invalid.get()><span class="text-(--destructive)">" *"</span></Show></FieldLabel> <Show when=move || username_invalid.get()> <FieldError>"Username is required"</FieldError> </Show> - <Input invalid=username_invalid on:input=username_validate /> + <Input invalid=username_invalid on:input=username_validate required=true /> </Field> <Field> - <FieldLabel>"Password *"</FieldLabel> + <FieldLabel>"Password" <Show when=move || password_invalid.get()><span class="text-(--destructive)">" *"</span></Show></FieldLabel> <FieldError errors=password_errors /> <Input input_type="password" invalid=password_invalid on:input=password_validate + required=true /> </Field> </FieldGroup> diff --git a/docs/src/routes/components/field/examples/field_group.rs b/docs/src/routes/components/field/examples/field_group.rs index 6538cf7a..4919e154 100644 --- a/docs/src/routes/components/field/examples/field_group.rs +++ b/docs/src/routes/components/field/examples/field_group.rs @@ -4,39 +4,48 @@ use singlestage::*; #[component] pub fn FieldGroupExample() -> impl IntoView { view! { - <div class="w-full max-w-md"> - <FieldGroup> - <FieldSet class="gap-3"> - <FieldLegend variant="label">"Responses"</FieldLegend> - <FieldDescription> - "Get notified when ChatGPT responds to requests that take time, like - research or image generation." - </FieldDescription> - <Field orientation="horizontal"> - <Checkbox checked=true class="font-normal" disabled=true> - "Push notifications" - </Checkbox> - </Field> - </FieldSet> - <FieldSeparator /> - <FieldSet> - <FieldLegend variant="label">"Tasks"</FieldLegend> - <FieldDescription> - "Get notified when tasks you've created have updates. " - <a href="#">"Manage tasks"</a> - </FieldDescription> - <FieldGroup> - <CheckboxGroup> - <Field orientation="horizontal"> - <Checkbox class="font-normal">"Push notifications"</Checkbox> - </Field> - <Field orientation="horizontal"> - <Checkbox class="font-normal">"Email notifications"</Checkbox> - </Field> - </CheckboxGroup> - </FieldGroup> - </FieldSet> - </FieldGroup> - </div> + <FieldGroup class="w-full max-w-xs"> + <FieldSet> + <FieldLabel>"Responses"</FieldLabel> + <FieldDescription> + "Get notified when ChatGPT responds to requests that take time, like + research or image generation." + </FieldDescription> + <FieldGroup> + <CheckboxGroup> + <Field orientation="horizontal"> + <Checkbox id="push" checked=true disabled=true /> + <FieldLabel label_for="push" class="font-normal"> + "Push notifications" + </FieldLabel> + </Field> + </CheckboxGroup> + </FieldGroup> + </FieldSet> + <FieldSeparator /> + <FieldSet> + <FieldLabel>"Tasks"</FieldLabel> + <FieldDescription> + "Get notified when tasks you've created have updates. " + <a href="#">"Manage tasks"</a> + </FieldDescription> + <FieldGroup> + <CheckboxGroup> + <Field orientation="horizontal"> + <Checkbox id="push-tasks" /> + <FieldLabel label_for="push-tasks" class="font-normal"> + "Push notifications" + </FieldLabel> + </Field> + <Field orientation="horizontal"> + <Checkbox id="email-tasks" /> + <FieldLabel label_for="email-tasks" class="font-normal"> + "Email notifications" + </FieldLabel> + </Field> + </CheckboxGroup> + </FieldGroup> + </FieldSet> + </FieldGroup> } } diff --git a/docs/src/routes/components/field/examples/field_input.rs b/docs/src/routes/components/field/examples/field_input.rs index 6cf00d1c..c943d4f5 100644 --- a/docs/src/routes/components/field/examples/field_input.rs +++ b/docs/src/routes/components/field/examples/field_input.rs @@ -4,22 +4,25 @@ use singlestage::*; #[component] pub fn FieldInputExample() -> impl IntoView { view! { - <div class="w-full max-w-md"> - <FieldSet> - <FieldGroup> - <Field> - <Input placeholder="Max Leiter">"Username"</Input> - <FieldDescription> - "Choose a unique username for your account." - </FieldDescription> - </Field> - <Field> - <FieldLabel>"Password"</FieldLabel> - <FieldDescription>"Must be at least 8 characters long."</FieldDescription> - <Input input_type="password" placeholder="••••••••" /> - </Field> - </FieldGroup> - </FieldSet> - </div> + <FieldSet class="w-full max-w-xs"> + <FieldGroup> + <Field> + <FieldLabel label_for="username">"Username"</FieldLabel> + <Input id="username" input_type="text" placeholder="Max Leiter" /> + <FieldDescription> + "Choose a unique username for your account." + </FieldDescription> + </Field> + <Field> + <FieldLabel label_for="password">"Password"</FieldLabel> + <FieldDescription>"Must be at least 8 characters long."</FieldDescription> + <Input + id="password" + input_type="password" + placeholder="••••••••" + /> + </Field> + </FieldGroup> + </FieldSet> } } diff --git a/docs/src/routes/components/field/examples/field_radio.rs b/docs/src/routes/components/field/examples/field_radio.rs index c0601e0b..d7d6d099 100644 --- a/docs/src/routes/components/field/examples/field_radio.rs +++ b/docs/src/routes/components/field/examples/field_radio.rs @@ -4,32 +4,31 @@ use singlestage::*; #[component] pub fn FieldRadioExample() -> impl IntoView { view! { - <div class="w-full max-w-md"> - <FieldSet> - <FieldTitle>"Subscription Plan"</FieldTitle> - <FieldDescription> - "Yearly and lifetime plans offer significant savings." - </FieldDescription> - <FieldGroup> - <RadioGroup default="monthly"> - <Field orientation="horizontal"> - <Radio class="font-normal" value="monthly"> - "Monthly ($9.99/month)" - </Radio> - </Field> - <Field orientation="horizontal"> - <Radio class="font-normal" value="yearly"> - "Yearly ($99.99/year)" - </Radio> - </Field> - <Field orientation="horizontal"> - <Radio class="font-normal" value="lifetime"> - "Lifetime ($299.99)" - </Radio> - </Field> - </RadioGroup> - </FieldGroup> - </FieldSet> - </div> + <FieldSet class="w-full max-w-xs"> + <FieldLegend variant="label">"Subscription Plan"</FieldLegend> + <FieldDescription> + "Yearly and lifetime plans offer significant savings." + </FieldDescription> + <RadioGroup value="monthly"> + <Field orientation="horizontal"> + <Radio value="monthly" id="plan-monthly" /> + <FieldLabel label_for="plan-monthly" class="font-normal"> + "Monthly ($9.99/month)" + </FieldLabel> + </Field> + <Field orientation="horizontal"> + <Radio value="yearly" id="plan-yearly" /> + <FieldLabel label_for="plan-yearly" class="font-normal"> + "Yearly ($99.99/year)" + </FieldLabel> + </Field> + <Field orientation="horizontal"> + <Radio value="lifetime" id="plan-lifetime" /> + <FieldLabel label_for="plan-lifetime" class="font-normal"> + "Lifetime ($299.99)" + </FieldLabel> + </Field> + </RadioGroup> + </FieldSet> } } diff --git a/docs/src/routes/components/field/examples/field_responsive_layout.rs b/docs/src/routes/components/field/examples/field_responsive_layout.rs index 31de8d78..e66a28ba 100644 --- a/docs/src/routes/components/field/examples/field_responsive_layout.rs +++ b/docs/src/routes/components/field/examples/field_responsive_layout.rs @@ -4,23 +4,21 @@ use singlestage::*; #[component] pub fn FieldResponsiveLayoutExample() -> impl IntoView { view! { - <div class="w-full max-w-4xl"> + <div class="w-full max-w-lg"> <form> <FieldSet> <FieldLegend>"Profile"</FieldLegend> <FieldDescription>"Fill in your profile information."</FieldDescription> - <FieldSeparator /> <FieldGroup> <Field orientation="responsive"> <FieldContent> - <FieldLabel>"Name"</FieldLabel> + <FieldLabel label_for="name">"Name"</FieldLabel> <FieldDescription> "Provide your full name for identification" </FieldDescription> </FieldContent> - <Input placeholder="Evil Rabbit" /> + <Input id="name" placeholder="Evil Rabbit" invalid=true /> </Field> - <FieldSeparator /> <Field orientation="responsive"> <FieldContent> <FieldLabel>"Message"</FieldLabel> @@ -34,7 +32,6 @@ pub fn FieldResponsiveLayoutExample() -> impl IntoView { class="min-h-[100px] resize-none sm:min-w-[300px]" /> </Field> - <FieldSeparator /> <Field orientation="responsive"> <Button button_type="submit">"Submit"</Button> <Button button_type="button" variant="outline"> diff --git a/docs/src/routes/components/field/examples/field_select.rs b/docs/src/routes/components/field/examples/field_select.rs index 1be0bc4e..f6d28a4a 100644 --- a/docs/src/routes/components/field/examples/field_select.rs +++ b/docs/src/routes/components/field/examples/field_select.rs @@ -4,29 +4,19 @@ use singlestage::*; #[component] pub fn FieldSelectExample() -> impl IntoView { view! { - <div class="w-full max-w-md"> - <FieldSet> - <FieldGroup> - <Field> - <FieldLabel>"Department"</FieldLabel> - <Select placeholder="Choose department"> - <SelectContent> - <SelectItem value="engineering">"Engineering"</SelectItem> - <SelectItem value="design">"Design"</SelectItem> - <SelectItem value="marketing">"Marketing"</SelectItem> - <SelectItem value="sales">"Sales"</SelectItem> - <SelectItem value="support">"Customer Support"</SelectItem> - <SelectItem value="hr">"Human Resources"</SelectItem> - <SelectItem value="finance">"Finance"</SelectItem> - <SelectItem value="operations">"Operations"</SelectItem> - </SelectContent> - </Select> - <FieldDescription> - "Select your department or area of work." - </FieldDescription> - </Field> - </FieldGroup> - </FieldSet> - </div> + <Field class="w-full max-w-xs"> + <FieldLabel>"Department"</FieldLabel> + <Select placeholder="Choose department"> + <SelectOption value="engineering">"Engineering"</SelectOption> + <SelectOption value="design">"Design"</SelectOption> + <SelectOption value="marketing">"Marketing"</SelectOption> + <SelectOption value="sales">"Sales"</SelectOption> + <SelectOption value="support">"Customer Support"</SelectOption> + <SelectOption value="hr">"Human Resources"</SelectOption> + <SelectOption value="finance">"Finance"</SelectOption> + <SelectOption value="operations">"Operations"</SelectOption> + </Select> + <FieldDescription>"Select your department or area of work."</FieldDescription> + </Field> } } diff --git a/docs/src/routes/components/field/examples/field_set.rs b/docs/src/routes/components/field/examples/field_set.rs index bf1a529a..fb5ee59b 100644 --- a/docs/src/routes/components/field/examples/field_set.rs +++ b/docs/src/routes/components/field/examples/field_set.rs @@ -4,24 +4,25 @@ use singlestage::*; #[component] pub fn FieldSetExample() -> impl IntoView { view! { - <div class="w-full max-w-md"> - <FieldSet> - <FieldLegend>"Address Information"</FieldLegend> - <FieldDescription>"We need your address to deliver your order."</FieldDescription> - <FieldGroup> + <FieldSet class="w-full max-w-sm"> + <FieldLegend>"Address Information"</FieldLegend> + <FieldDescription>"We need your address to deliver your order."</FieldDescription> + <FieldGroup> + <Field> + <FieldLabel label_for="street">"Street Address"</FieldLabel> + <Input id="street" input_type="text" placeholder="123 Main St" /> + </Field> + <div class="grid grid-cols-2 gap-4"> <Field> - <Input placeholder="123 Main St">"Street Address"</Input> + <FieldLabel label_for="city">City</FieldLabel> + <Input id="city" input_type="text" placeholder="New York" /> </Field> - <FieldGroup class="grid grid-cols-2"> - <Field> - <Input placeholder="Torrance">"City"</Input> - </Field> - <Field> - <Input placeholder="90502">"Postal Code"</Input> - </Field> - </FieldGroup> - </FieldGroup> - </FieldSet> - </div> + <Field> + <FieldLabel label_for="zip">Postal Code</FieldLabel> + <Input id="zip" input_type="text" placeholder="90502" /> + </Field> + </div> + </FieldGroup> + </FieldSet> } } diff --git a/docs/src/routes/components/field/examples/field_slider.rs b/docs/src/routes/components/field/examples/field_slider.rs index 3227d740..957c8e01 100644 --- a/docs/src/routes/components/field/examples/field_slider.rs +++ b/docs/src/routes/components/field/examples/field_slider.rs @@ -6,20 +6,13 @@ pub fn FieldSliderExample() -> impl IntoView { let value = RwSignal::new(0.); view! { - <div class="w-full max-w-md"> - <FieldSet> - <FieldGroup> - <Field> - <FieldLabel>"Price Range"</FieldLabel> - <FieldDescription> - "Set your budget range ($" - <span class="font-medium tabular-nums">"0"</span> " - $" - <span class="font-medium tabular-nums">{move || value.get()}</span>")." - </FieldDescription> - <Slider class="mt-2 w-full" max=1000. min=0. step=10. value /> - </Field> - </FieldGroup> - </FieldSet> - </div> + <Field class="w-full max-w-xs"> + <FieldTitle>"Price Range"</FieldTitle> + <FieldDescription> + "Set your budget range ($" <span class="font-medium tabular-nums">"0"</span> " - $" + <span class="font-medium tabular-nums">{move || value.get()}</span>")." + </FieldDescription> + <Slider class="mt-2 w-full" max=1000. min=0. step=10. value /> + </Field> } } diff --git a/docs/src/routes/components/field/examples/field_switch.rs b/docs/src/routes/components/field/examples/field_switch.rs index 6026fee9..382f0983 100644 --- a/docs/src/routes/components/field/examples/field_switch.rs +++ b/docs/src/routes/components/field/examples/field_switch.rs @@ -4,23 +4,9 @@ use singlestage::*; #[component] pub fn FieldSwitchExample() -> impl IntoView { view! { - <div class="w-full max-w-md"> - <FieldSet> - <FieldGroup> - <Field orientation="horizontal"> - <FieldLabel> - <FieldContent> - <FieldTitle>"Multi-factor authentication"</FieldTitle> - <FieldDescription> - "Enable multi-factor authentication. If you do not have a two-factor - device, you can use a one-time code sent to your email." - </FieldDescription> - </FieldContent> - </FieldLabel> - <Switch /> - </Field> - </FieldGroup> - </FieldSet> - </div> + <Field orientation="horizontal" class="w-fit"> + <FieldLabel label_for="2fa">"Multi-factor authentication"</FieldLabel> + <Switch id="2fa" /> + </Field> } } diff --git a/docs/src/routes/components/field/examples/field_textarea.rs b/docs/src/routes/components/field/examples/field_textarea.rs index 782251bb..6921c283 100644 --- a/docs/src/routes/components/field/examples/field_textarea.rs +++ b/docs/src/routes/components/field/examples/field_textarea.rs @@ -4,19 +4,18 @@ use singlestage::*; #[component] pub fn FieldTextareaExample() -> impl IntoView { view! { - <div class="w-full max-w-md"> - <FieldSet> - <FieldGroup> - <Field> - <Textarea placeholder="Your feedback helps us improve..." rows=4> - "Feedback" - </Textarea> - <FieldDescription> - "Share your thoughts about our service." - </FieldDescription> - </Field> - </FieldGroup> - </FieldSet> - </div> + <FieldSet class="w-full max-w-xs"> + <FieldGroup> + <Field> + <FieldLabel label_for="feedback">"Feedback"</FieldLabel> + <Textarea + id="feedback" + placeholder="Your feedback helps us improve..." + rows=4 + /> + <FieldDescription>"Share your thoughts about our service."</FieldDescription> + </Field> + </FieldGroup> + </FieldSet> } } diff --git a/docs/src/routes/components/input/component.toml b/docs/src/routes/components/input/component.toml index 8ca8f505..87a8c011 100644 --- a/docs/src/routes/components/input/component.toml +++ b/docs/src/routes/components/input/component.toml @@ -20,6 +20,6 @@ attrs = [ <input> `type` attribute to avoid name collisions.""" }, { attr = "invalid", attr_type = "bool", default = "false", description = """Toggle whether or not \ the input is disabled.""" }, - { attr = "value", attr_type = "Reactive<String>", default = """""""", description = """A \ - reactive signal coupled to the input's value.""" }, + { attr = "value", attr_type = "String", default = """""""", description = """A reactive signal \ + coupled to the input's value.""" }, ] diff --git a/docs/src/routes/components/input/examples/form.rs b/docs/src/routes/components/input/examples/form.rs index 3bf3beea..4aa06b72 100644 --- a/docs/src/routes/components/input/examples/form.rs +++ b/docs/src/routes/components/input/examples/form.rs @@ -14,9 +14,9 @@ pub fn FormExample() -> impl IntoView { <div class="grid gap-2"> <Label label_for="demo-form-select">"Email"</Label> <Select id="demo-form-select"> - <SelectItem value="bob@example.com">"bob@example.com"</SelectItem> - <SelectItem value="alice@example.com">"alice@example.com"</SelectItem> - <SelectItem value="john@example.com">"john@example.com"</SelectItem> + <SelectOption value="bob@example.com">"bob@example.com"</SelectOption> + <SelectOption value="alice@example.com">"alice@example.com"</SelectOption> + <SelectOption value="john@example.com">"john@example.com"</SelectOption> </Select> <p class="text-muted-foreground text-sm"> "You can manage email addresses in your email settings." diff --git a/docs/src/routes/components/input/examples/input.rs b/docs/src/routes/components/input/examples/input.rs index 65bf43cc..cc337737 100644 --- a/docs/src/routes/components/input/examples/input.rs +++ b/docs/src/routes/components/input/examples/input.rs @@ -1,32 +1,75 @@ use leptos::prelude::*; -use singlestage::input::*; +use singlestage::*; #[component] pub fn InputExample() -> impl IntoView { view! { - <div> - <Input input_type="color" default="#F00"> - "Color:" - </Input> - <Input input_type="date">"Date:"</Input> - <Input input_type="datetime-local">"Datetime:"</Input> - <Input input_type="email">"Email:"</Input> - <Input input_type="file">"File:"</Input> - <Input input_type="hidden" name="John Cena" /> - <Input input_type="month">"Month:"</Input> - <Input input_type="number">"Number:"</Input> - <Input input_type="password">"Password:"</Input> - <Input input_type="search">"Search:"</Input> - <Input input_type="tel">"Phone Number:"</Input> - <Input input_type="text" disabled=true> - "Disabled:" - </Input> - <Input input_type="text" invalid=true> - "Invalid:" - </Input> - <Input input_type="time">"Time:"</Input> - <Input input_type="url">"URL:"</Input> - <Input input_type="week">"Week:"</Input> - </div> + <FieldSet> + <Field> + <Input input_type="color" default="#F00"> + "Color:" + </Input> + </Field> + + <Field> + <Input input_type="date">"Date:"</Input> + </Field> + + <Field> + <Input input_type="datetime-local">"Datetime:"</Input> + </Field> + + <Field> + <Input input_type="email">"Email:"</Input> + </Field> + + <Field> + <Input input_type="file">"File:"</Input> + </Field> + + <Field> + <Input input_type="hidden" name="John Cena" /> + </Field> + + <Field> + <Input input_type="month">"Month:"</Input> + </Field> + + <Field> + <Input input_type="number">"Number:"</Input> + </Field> + + <Field> + <Input input_type="password">"Password:"</Input> + </Field> + + <Field> + <Input input_type="search">"Search:"</Input> + </Field> + + <Field> + <Input input_type="tel">"Phone Number:"</Input> + </Field> + + <Field disabled=true> + <Input input_type="text">"Disabled:"</Input> + </Field> + + <Field invalid=true> + <Input input_type="text">"Invalid:"</Input> + </Field> + + <Field> + <Input input_type="time">"Time:"</Input> + </Field> + + <Field> + <Input input_type="url">"URL:"</Input> + </Field> + + <Field> + <Input input_type="week">"Week:"</Input> + </Field> + </FieldSet> } } diff --git a/docs/src/routes/components/input_group/component.toml b/docs/src/routes/components/input_group/component.toml index 599d8387..fb92029d 100644 --- a/docs/src/routes/components/input_group/component.toml +++ b/docs/src/routes/components/input_group/component.toml @@ -4,9 +4,30 @@ description = """Add additional information or actions to an input or textarea." [[examples]] name = "input group" +[[examples]] +name = "input group inline start" +title = "Inline Start" +descruotion = "Position the addon at the start of the input. This is the default." + +[[examples]] +name = "input group inline end" +title = "Inline End" +descruotion = "Position the addon at the end of the input." + +[[examples]] +name = "input group block start" +title = "Block Start" +descruotion = "Position the addon above the input." + +[[examples]] +name = "input group block end" +title = "Block End" +descruotion = "Position the addon below the input." + [[examples]] name = "input group icon" title = "Icon" +description = "Display icons within input groups." [[examples]] name = "input group text" @@ -19,9 +40,19 @@ title = "Button" description = "Add buttons to perform actions within the input group." [[examples]] -name = "input group tooltip" -title = "Tooltip" -description = "Add Add tooltips to provide additional context or help." +name = "input group kbd" +title = "Kbd" +description = "Display a keyboard shortcut associated with the input." + +[[examples]] +name = "input group dropdown" +title = "Dropdown" +description = "Pair input groups with dropdown menus for complex interactions." + +[[examples]] +name = "input group spinner" +title = "Spinner" +description = "Show loading indicators while processing input." [[examples]] name = "input group textarea" @@ -30,20 +61,15 @@ description = """Input groups also work with textarea components. Use block-star for alignment.""" [[examples]] -name = "input group spinner" -title = "Spinner" -description = "Show loading indicators while processing input." +name = "input group tooltip" +title = "Tooltip" +description = "Add Add tooltips to provide additional context or help." [[examples]] name = "input group label" title = "Label" description = "Add labels within input groups to improve accessibility." -[[examples]] -name = "input group dropdown" -title = "Dropdown" -description = "Pair input groups with dropdown menus for complex interactions." - [[examples]] name = "input group button group" title = "Button Group" @@ -72,24 +98,6 @@ attrs = [ | block-end.""" }, ] -[[references]] -name = "Button" -description = "InputGroup uses a regular Button with slightly different behavior." -extra = """Implements <a class="singlestage-link" \ -href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ -and <a class="singlestage-link" \ -href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#attributes">button</a> \ -attributes.""" -attrs = [ - { attr = "button_type", attr_type = "String", default = """"button"""", description = """Renamed <button> \ - `type` attribute to avoid name collisions.""" }, - { attr = "size", attr_type = "String", default = """"xs"""", description = """Specify the size to \ - render the button. Sizes: xs | icon-xs | sm | icon-sm.""" }, - { attr = "variant", attr_type = "String", default = """"ghost"""", description = """Specify \ - the style of button to render. Variants: primary | secondary | outline | ghost | link | \ - destructive""" }, -] - [[references]] name = "InputGroupText" description = "A styled wrapper for displaying text and icons in an InputGroup." diff --git a/docs/src/routes/components/input_group/examples/input_group.rs b/docs/src/routes/components/input_group/examples/input_group.rs index 8d2fca36..9ccae937 100644 --- a/docs/src/routes/components/input_group/examples/input_group.rs +++ b/docs/src/routes/components/input_group/examples/input_group.rs @@ -4,57 +4,10 @@ use singlestage::*; #[component] pub fn InputGroupExample() -> impl IntoView { view! { - <div class="grid w-full max-w-sm gap-6"> - <InputGroup> - <Input placeholder="Search..." /> - <InputGroupAddon>{icon!(icondata::LuSearch)}</InputGroupAddon> - <InputGroupAddon align="inline-end">"12 results"</InputGroupAddon> - </InputGroup> - <InputGroup> - <Input placeholder="example.com" class="!pl-1" /> - <InputGroupAddon> - <InputGroupText>"https://"</InputGroupText> - </InputGroupAddon> - <InputGroupAddon align="inline-end"> - <Tooltip value="This is content in a tooltip."> - <Button class="rounded-full" size="icon-xs"> - {icon!(icondata::LuInfo)} - </Button> - </Tooltip> - </InputGroupAddon> - </InputGroup> - <InputGroup> - <Textarea placeholder="Ask, Search or Chat..." /> - <InputGroupAddon align="block-end" class="max-h-[42px]"> - <Button variant="outline" class="rounded-full" size="icon-xs"> - {icon!(icondata::LuPlus)} - </Button> - <DropdownMenu> - <DropdownMenuTrigger> - <Button>"Auto"</Button> - </DropdownMenuTrigger> - <DropdownMenuContent class="[--radius:0.95rem]"> - <DropdownMenuItem>"Auto"</DropdownMenuItem> - <DropdownMenuItem>"Agent"</DropdownMenuItem> - <DropdownMenuItem>"Manual"</DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - <InputGroupText class="ml-auto">"52% used"</InputGroupText> - <Separator vertical=true class="!h-4" /> - <Button variant="default" class="rounded-full" size="icon-xs" disabled=true> - {icon!(icondata::LuArrowUp)} - <span class="sr-only">"Send"</span> - </Button> - </InputGroupAddon> - </InputGroup> - <InputGroup> - <Input placeholder="@shadcn" /> - <InputGroupAddon align="inline-end"> - <div class="bg-(--primary) text-(--primary-foreground) flex size-4 items-center justify-center rounded-full"> - {icon!(icondata::LuCheck, class="size-3")} - </div> - </InputGroupAddon> - </InputGroup> - </div> + <InputGroup class="max-w-xs"> + <Input placeholder="Search..." /> + <InputGroupAddon>{icon!(icondata::LuSearch)}</InputGroupAddon> + <InputGroupAddon align="inline-end">"12 results"</InputGroupAddon> + </InputGroup> } } diff --git a/docs/src/routes/components/input_group/examples/input_group_block_end.rs b/docs/src/routes/components/input_group/examples/input_group_block_end.rs new file mode 100644 index 00000000..697a2d34 --- /dev/null +++ b/docs/src/routes/components/input_group/examples/input_group_block_end.rs @@ -0,0 +1,33 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn InputGroupBlockEndExample() -> impl IntoView { + view! { + <FieldGroup class="max-w-sm"> + <Field> + <FieldLabel label_for="block-end-input">"Input"</FieldLabel> + <InputGroup class="h-auto"> + <Input id="block-end-input" placeholder="Enter amount" /> + <InputGroupAddon align="block-end"> + <InputGroupText>"USD"</InputGroupText> + </InputGroupAddon> + </InputGroup> + <FieldDescription>"Footer positioned below the input."</FieldDescription> + </Field> + <Field> + <FieldLabel label_for="block-end-textarea">"Textarea"</FieldLabel> + <InputGroup> + <Textarea id="block-end-textarea" placeholder="Write a comment..." /> + <InputGroupAddon align="block-end"> + <InputGroupText>"0/280"</InputGroupText> + <Button variant="default" size="sm" class="ml-auto"> + "Post" + </Button> + </InputGroupAddon> + </InputGroup> + <FieldDescription>"Footer positioned below the textarea."</FieldDescription> + </Field> + </FieldGroup> + } +} diff --git a/docs/src/routes/components/input_group/examples/input_group_block_start.rs b/docs/src/routes/components/input_group/examples/input_group_block_start.rs new file mode 100644 index 00000000..be3ef2c9 --- /dev/null +++ b/docs/src/routes/components/input_group/examples/input_group_block_start.rs @@ -0,0 +1,39 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn InputGroupBlockStartExample() -> impl IntoView { + view! { + <FieldGroup class="max-w-sm"> + <Field> + <FieldLabel label_for="block-start-input">"Input"</FieldLabel> + <InputGroup class="h-auto"> + <Input id="block-start-input" placeholder="Enter your name" /> + <InputGroupAddon align="block-start"> + <InputGroupText>"Full Name"</InputGroupText> + </InputGroupAddon> + </InputGroup> + <FieldDescription>"Header positioned above the input."</FieldDescription> + </Field> + <Field> + <FieldLabel label_for="block-start-textarea">"Textarea"</FieldLabel> + <InputGroup> + <Textarea + id="block-start-textarea" + placeholder="console.log('Hello, world!');" + class="font-mono text-sm" + /> + <InputGroupAddon align="block-start"> + {icon!(icondata::LuFileCode, class="text-muted-foreground")} + <InputGroupText class="font-mono">"script.js"</InputGroupText> + <Button size="icon-xs" class="ml-auto"> + {icon!(icondata::LuCopy)} + <span class="sr-only">"Copy"</span> + </Button> + </InputGroupAddon> + </InputGroup> + <FieldDescription>"Header positioned above the textarea."</FieldDescription> + </Field> + </FieldGroup> + } +} diff --git a/docs/src/routes/components/input_group/examples/input_group_button.rs b/docs/src/routes/components/input_group/examples/input_group_button.rs index 7f500df9..e0ebf40e 100644 --- a/docs/src/routes/components/input_group/examples/input_group_button.rs +++ b/docs/src/routes/components/input_group/examples/input_group_button.rs @@ -14,66 +14,76 @@ pub fn InputGroupButtonExample() -> impl IntoView { }); view! { - <div class="grid w-full max-w-sm gap-6"> - <InputGroup> - <Input placeholder="https://x.com/shadcn" readonly=true /> - <InputGroupAddon align="inline-end"> - <Button - aria_label="Copy" - title="Copy" - size="icon-xs" - on:click=move |_| is_copied.set(true) - > - <Show - when=move || is_copied.get() - fallback=move || { icon!(icondata::LuCopy) } + <FieldGroup class="max-w-sm"> + <Field> + <InputGroup> + <Input placeholder="https://x.com/shadcn" readonly=true /> + <InputGroupAddon align="inline-end"> + <Button + aria_label="Copy" + title="Copy" + size="icon-xs" + on:click=move |_| is_copied.set(true) > - {icon!(icondata::LuCheck)} - </Show> - </Button> - </InputGroupAddon> - </InputGroup> - <InputGroup class="rounded-full"> - <InputGroupAddon> - <Popover> - <PopoverTrigger> - <Button class="rounded-full" variant="secondary" size="icon-xs"> - {icon!(icondata::LuInfo)} - </Button> - </PopoverTrigger> - <PopoverContent class="gap-1 text-sm max-w-2xs"> - <p class="font-medium">"Your connection is not secure."</p> - <p>"You should not enter any sensitive information on this site."</p> - </PopoverContent> - </Popover> - </InputGroupAddon> - <InputGroupAddon class="text-muted-foreground pl-1.5">"https://"</InputGroupAddon> - <Input id="input-secure-19" /> - <InputGroupAddon align="inline-end"> - <Button - aria_label="Bookmark this page" - title="Bookmark this page" - class="rounded-full" - on:click=move |_| is_favorite.set(!is_favorite.get()) - size="icon-xs" - > - <Show - when=move || is_favorite.get() - fallback=move || { { icon!(icondata::LuStar) } } + <Show + when=move || is_copied.get() + fallback=move || { icon!(icondata::LuCopy) } + > + {icon!(icondata::LuCheck)} + </Show> + </Button> + </InputGroupAddon> + </InputGroup> + </Field> + <Field> + <InputGroup class="rounded-full"> + <InputGroupAddon> + <Popover> + <PopoverTrigger> + <Button class="rounded-full" variant="secondary" size="icon-xs"> + {icon!(icondata::LuInfo)} + </Button> + </PopoverTrigger> + <PopoverContent class="gap-1 text-sm max-w-2xs"> + <p class="font-medium">"Your connection is not secure."</p> + <p> + "You should not enter any sensitive information on this site." + </p> + </PopoverContent> + </Popover> + </InputGroupAddon> + <InputGroupAddon class="text-muted-foreground pl-1.5"> + "https://" + </InputGroupAddon> + <Input id="input-secure-19" /> + <InputGroupAddon align="inline-end"> + <Button + aria_label="Bookmark this page" + title="Bookmark this page" + class="rounded-full" + on:click=move |_| is_favorite.set(!is_favorite.get()) + size="icon-xs" > - {icon!(icondata::LuStar, class="fill-blue-600 stroke-blue-600")} - </Show> - </Button> - </InputGroupAddon> - </InputGroup> - <InputGroup> - <Input placeholder="Type to search..." /> - <InputGroupAddon align="inline-end"> - <Button aria_label="Search" title="Search" variant="secondary"> - "Search" - </Button> - </InputGroupAddon> - </InputGroup> - </div> + <Show + when=move || is_favorite.get() + fallback=move || { { icon!(icondata::LuStar) } } + > + {icon!(icondata::LuStar, class="fill-blue-600 stroke-blue-600")} + </Show> + </Button> + </InputGroupAddon> + </InputGroup> + </Field> + <Field> + <InputGroup> + <Input placeholder="Type to search..." /> + <InputGroupAddon align="inline-end"> + <Button aria_label="Search" title="Search" variant="secondary"> + "Search" + </Button> + </InputGroupAddon> + </InputGroup> + </Field> + </FieldGroup> } } diff --git a/docs/src/routes/components/input_group/examples/input_group_dropdown.rs b/docs/src/routes/components/input_group/examples/input_group_dropdown.rs index 88cbd402..3b0ee758 100644 --- a/docs/src/routes/components/input_group/examples/input_group_dropdown.rs +++ b/docs/src/routes/components/input_group/examples/input_group_dropdown.rs @@ -14,7 +14,7 @@ pub fn InputGroupDropdownExample() -> impl IntoView { {icon!(icondata::FiMoreHorizontal)} </Button> </DropdownMenuTrigger> - <DropdownMenuContent> + <DropdownMenuContent align="end"> <DropdownMenuItem>"Settings"</DropdownMenuItem> <DropdownMenuItem>"Copy path"</DropdownMenuItem> <DropdownMenuItem>"Open location"</DropdownMenuItem> @@ -31,7 +31,7 @@ pub fn InputGroupDropdownExample() -> impl IntoView { "Search In..." {icon!(icondata::LuChevronDown, class="size-3")} </Button> </DropdownMenuTrigger> - <DropdownMenuContent class="[--radius:0.95rem]"> + <DropdownMenuContent align="end" class="[--radius:0.95rem]"> <DropdownMenuItem>"Documentation"</DropdownMenuItem> <DropdownMenuItem>"Blog Posts"</DropdownMenuItem> <DropdownMenuItem>"Changelog"</DropdownMenuItem> diff --git a/docs/src/routes/components/input_group/examples/input_group_inline_end.rs b/docs/src/routes/components/input_group/examples/input_group_inline_end.rs new file mode 100644 index 00000000..8fcf3a0c --- /dev/null +++ b/docs/src/routes/components/input_group/examples/input_group_inline_end.rs @@ -0,0 +1,16 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn InputGroupInlineEndExample() -> impl IntoView { + view! { + <Field class="max-w-sm"> + <Label label_for="inline-end-input">"Input"</Label> + <InputGroup> + <Input id="inline-end-input" input_type="password" placeholder="Enter password" /> + <InputGroupAddon align="inline-end">{icon!(icondata::LuEyeOff)}</InputGroupAddon> + </InputGroup> + <FieldDescription>"Icon positioned at the end."</FieldDescription> + </Field> + } +} diff --git a/docs/src/routes/components/input_group/examples/input_group_inline_start.rs b/docs/src/routes/components/input_group/examples/input_group_inline_start.rs new file mode 100644 index 00000000..a820648a --- /dev/null +++ b/docs/src/routes/components/input_group/examples/input_group_inline_start.rs @@ -0,0 +1,18 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn InputGroupInlineStartExample() -> impl IntoView { + view! { + <Field class="max-w-sm"> + <FieldLabel label_for="inline-start-input">"Input"</FieldLabel> + <InputGroup> + <Input id="inline-start-input" placeholder="Search..." /> + <InputGroupAddon align="inline-start"> + {icon!(icondata::LuSearch, class="text-muted-foreground")} + </InputGroupAddon> + </InputGroup> + <FieldDescription>"Icon positioned at the start."</FieldDescription> + </Field> + } +} diff --git a/docs/src/routes/components/input_group/examples/input_group_kbd.rs b/docs/src/routes/components/input_group/examples/input_group_kbd.rs new file mode 100644 index 00000000..6864ffc2 --- /dev/null +++ b/docs/src/routes/components/input_group/examples/input_group_kbd.rs @@ -0,0 +1,17 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn InputGroupKbdExample() -> impl IntoView { + view! { + <InputGroup class="max-w-sm"> + <Input placeholder="Search..." /> + <InputGroupAddon> + {icon!(icondata::LuSearch, class="text-muted-foreground")} + </InputGroupAddon> + <InputGroupAddon align="inline-end"> + <Kbd>"⌘K"</Kbd> + </InputGroupAddon> + </InputGroup> + } +} diff --git a/docs/src/routes/components/input_group/examples/input_group_label.rs b/docs/src/routes/components/input_group/examples/input_group_label.rs index 4e5411bd..0aa48a84 100644 --- a/docs/src/routes/components/input_group/examples/input_group_label.rs +++ b/docs/src/routes/components/input_group/examples/input_group_label.rs @@ -17,15 +17,20 @@ pub fn InputGroupLabelExample() -> impl IntoView { <Label label_for="email-2" class="text-(--foreground)"> "Email" </Label> - <Tooltip class="ml-auto" value="We'll use this to send you notifications"> - <Button - variant="ghost" - aria_label="Help" - class="rounded-full" - size="icon-xs" - > - {icon!(icondata::LuInfo)} - </Button> + <Tooltip class="ml-auto"> + <TooltipTrigger> + <Button + variant="ghost" + aria_label="Help" + class="rounded-full" + size="icon-xs" + > + {icon!(icondata::LuInfo)} + </Button> + </TooltipTrigger> + <TooltipContent> + <p>"We'll use this to send you notifications"</p> + </TooltipContent> </Tooltip> </InputGroupAddon> </InputGroup> diff --git a/docs/src/routes/components/input_group/examples/input_group_spinner.rs b/docs/src/routes/components/input_group/examples/input_group_spinner.rs index f1ac2974..e24448ec 100644 --- a/docs/src/routes/components/input_group/examples/input_group_spinner.rs +++ b/docs/src/routes/components/input_group/examples/input_group_spinner.rs @@ -5,27 +5,27 @@ use singlestage::*; pub fn InputGroupSpinnerExample() -> impl IntoView { view! { <div class="grid w-full max-w-sm gap-4"> - <InputGroup disabled=true> - <Input placeholder="Searching..." disabled=true /> + <InputGroup> + <Input placeholder="Searching..." /> <InputGroupAddon align="inline-end"> <Spinner /> </InputGroupAddon> </InputGroup> - <InputGroup disabled=true> - <Input placeholder="Processing..." disabled=true /> + <InputGroup> + <Input placeholder="Processing..." /> <InputGroupAddon> <Spinner /> </InputGroupAddon> </InputGroup> - <InputGroup disabled=true> - <Input placeholder="Saving changes..." disabled=true /> + <InputGroup> + <Input placeholder="Saving changes..." /> <InputGroupAddon align="inline-end"> <InputGroupText>"Saving..."</InputGroupText> <Spinner /> </InputGroupAddon> </InputGroup> - <InputGroup disabled=true> - <Input placeholder="Refreshing data..." disabled=true /> + <InputGroup> + <Input placeholder="Refreshing data..." /> <InputGroupAddon> <Spinner>{icon!(icondata::LuLoader)}</Spinner> </InputGroupAddon> diff --git a/docs/src/routes/components/input_group/examples/input_group_tooltip.rs b/docs/src/routes/components/input_group/examples/input_group_tooltip.rs index e47cf6fc..8ad96d74 100644 --- a/docs/src/routes/components/input_group/examples/input_group_tooltip.rs +++ b/docs/src/routes/components/input_group/examples/input_group_tooltip.rs @@ -8,31 +8,46 @@ pub fn InputGroupTooltipExample() -> impl IntoView { <InputGroup> <Input placeholder="Enter password" input_type="password" /> <InputGroupAddon align="inline-end"> - <Tooltip value="Password must be at least 8 characters"> - <Button variant="ghost" aria_label="Info" size="icon-xs"> - {icon!(icondata::LuInfo)} - </Button> + <Tooltip> + <TooltipTrigger> + <Button variant="ghost" aria_label="Info" size="icon-xs"> + {icon!(icondata::LuInfo)} + </Button> + </TooltipTrigger> + <TooltipContent> + <p>"Password must be at least 8 characters"</p> + </TooltipContent> </Tooltip> </InputGroupAddon> </InputGroup> <InputGroup> <Input placeholder="Your email address" /> <InputGroupAddon align="inline-end"> - <Tooltip value="We'll use this to send you notifications"> - <Button variant="ghost" aria_label="Help" size="icon-xs"> - {icon!(icondata::FiHelpCircle)} - </Button> + <Tooltip> + <TooltipTrigger> + <Button variant="ghost" aria_label="Help" size="icon-xs"> + {icon!(icondata::FiHelpCircle)} + </Button> + </TooltipTrigger> + <TooltipContent> + <p>"We'll use this to send you notifications"</p> + </TooltipContent> </Tooltip> </InputGroupAddon> </InputGroup> <InputGroup> - <Tooltip side="left" value="Click for help with API keys"> - <InputGroupAddon> - <Button variant="ghost" aria_label="Help" size="icon-xs"> - {icon!(icondata::FiHelpCircle)} - </Button> - </InputGroupAddon> - </Tooltip> + <InputGroupAddon> + <Tooltip> + <TooltipTrigger> + <Button variant="ghost" aria_label="Help" size="icon-xs"> + {icon!(icondata::FiHelpCircle)} + </Button> + </TooltipTrigger> + <TooltipContent side="left"> + <p>"Click for help with API keys"</p> + </TooltipContent> + </Tooltip> + </InputGroupAddon> <Input placeholder="Enter API key" /> </InputGroup> </div> diff --git a/docs/src/routes/components/input_group/examples/mod.rs b/docs/src/routes/components/input_group/examples/mod.rs index 99d6efb1..5a5b2745 100644 --- a/docs/src/routes/components/input_group/examples/mod.rs +++ b/docs/src/routes/components/input_group/examples/mod.rs @@ -1,8 +1,13 @@ mod input_group; +mod input_group_block_end; +mod input_group_block_start; mod input_group_button; mod input_group_button_group; mod input_group_dropdown; mod input_group_icon; +mod input_group_inline_end; +mod input_group_inline_start; +mod input_group_kbd; mod input_group_label; mod input_group_spinner; mod input_group_text; @@ -10,10 +15,15 @@ mod input_group_textarea; mod input_group_tooltip; pub use input_group::*; +pub use input_group_block_end::*; +pub use input_group_block_start::*; pub use input_group_button::*; pub use input_group_button_group::*; pub use input_group_dropdown::*; pub use input_group_icon::*; +pub use input_group_inline_end::*; +pub use input_group_inline_start::*; +pub use input_group_kbd::*; pub use input_group_label::*; pub use input_group_spinner::*; pub use input_group_text::*; diff --git a/docs/src/routes/components/item/component.toml b/docs/src/routes/components/item/component.toml index 1ac81e8f..a8e74bfd 100644 --- a/docs/src/routes/components/item/component.toml +++ b/docs/src/routes/components/item/component.toml @@ -1,5 +1,6 @@ name = "Item" -description = "A versatile component used to display any content." +description = """A versatile component for displaying content with media, title, description, and \ +actions.""" [[examples]] name = "Item" @@ -7,10 +8,18 @@ name = "Item" [[examples]] name = "item variants" title = "Variants" +description = "Use the variant prop to change the visual style of the item." + +[[examples]] +name = "item size" +title = "Size" +description = """Use the size prop to change the size of the item. Available sizes are default, \ +sm, and xs.""" [[examples]] name = "item icon" title = "Icon" +description = "Use the ItemMedia icon variant to display an icon." [[examples]] name = "item avatar" @@ -19,18 +28,23 @@ title = "Avatar" [[examples]] name = "item image" title = "Image" +description = "Use the ItemMedia image variant to display an icon." [[examples]] name = "item group" title = "Group" +description = "Use ItemGroup to group related items together." [[examples]] name = "item header" title = "Header" +description = "Use ItemHeader to add a header above the item content." [[examples]] name = "item link" title = "Link" +description = """To make an Item a link, just wrap it with an anchor and it'll automatically \ +apply hover and focus styling.""" [[examples]] name = "item dropdown" @@ -43,10 +57,10 @@ extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [ - { attr = "size", attr_type = "String", default = """"default"""", description = """Set the \ - display size of the Item.""" }, - { attr = "variant", attr_type = "String", default = """"default"""", description = """Set the \ - display variant of the Item.""" }, + { attr = "size", attr_type = "String", default = """""""", description = """Set the display \ + size of the Item. Accepted values: "outline" | "muted".""" }, + { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ + variant of the Item. Accepted values: "outline" | "muted".""" }, ] [[references]] @@ -104,20 +118,18 @@ extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [ - { attr = "variant", attr_type = "String", default = """"default"""", description = """Set the \ - display variant of the ItemMedia.""" }, + { attr = "variant", attr_type = "String", default = """""""", description = """Set the display \ + variant of the ItemMedia. Accepted values: "icon" | "image".""" }, ] [[references]] name = "ItemSeparator" -description = "Separates Item content." +description = """Visual divider to separate sections inside an Item. Accepts optional inline \ +content.""" extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" -attrs = [ - { attr = "vertical", attr_type = "bool", default = "false", description = """Toggle whether or \ - not the separator should display vertically.""" }, -] +attrs = [] [[references]] name = "ItemTitle" diff --git a/docs/src/routes/components/item/examples/item_avatar.rs b/docs/src/routes/components/item/examples/item_avatar.rs index 237d7708..92bde3e0 100644 --- a/docs/src/routes/components/item/examples/item_avatar.rs +++ b/docs/src/routes/components/item/examples/item_avatar.rs @@ -9,6 +9,7 @@ pub fn ItemAvatarExample() -> impl IntoView { <ItemMedia> <Avatar class="size-10"> <AvatarImage src="https://github.com/evilrabbit.png" /> + <AvatarFallback>"ER"</AvatarFallback> </Avatar> </ItemMedia> <ItemContent> @@ -29,17 +30,20 @@ pub fn ItemAvatarExample() -> impl IntoView { <Item variant="outline"> <ItemMedia> <div class="*:ring-(--background) flex -space-x-2 *:ring-2 *:grayscale"> - <Avatar class="flex"> + <Avatar> <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> + <AvatarFallback>"CN"</AvatarFallback> </Avatar> - <Avatar class="flex"> + <Avatar> <AvatarImage src="https://github.com/maxleiter.png" alt="@maxleiter" /> + <AvatarFallback>"LR"</AvatarFallback> </Avatar> <Avatar> <AvatarImage src="https://github.com/evilrabbit.png" alt="@evilrabbit" /> + <AvatarFallback>"ER"</AvatarFallback> </Avatar> </div> </ItemMedia> diff --git a/docs/src/routes/components/item/examples/item_dropdown.rs b/docs/src/routes/components/item/examples/item_dropdown.rs index 7e71a2f2..e124a1bd 100644 --- a/docs/src/routes/components/item/examples/item_dropdown.rs +++ b/docs/src/routes/components/item/examples/item_dropdown.rs @@ -29,32 +29,37 @@ pub fn ItemDropdownExample() -> impl IntoView { ]); view! { - <div class="flex min-h-64 w-full max-w-md flex-col items-center gap-6"> - <DropdownMenu> - <DropdownMenuTrigger> - <Button variant="outline" size="sm" class="w-fit"> - "Select" - {icon!(icondata::LuChevronDown)} - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent class="w-72 [--radius:0.65rem]"> - <For each=move || people.get() key=|person| person.username.clone() let(person)> - <DropdownMenuItem class="p-0"> - <Item size="sm" class="w-full p-2"> - <ItemMedia> - <Avatar class="size-8"> - <AvatarImage src=person.avatar class="grayscale" /> - </Avatar> - </ItemMedia> - <ItemContent class="gap-0.5"> - <ItemTitle>{person.username}</ItemTitle> - <ItemDescription>{person.email}</ItemDescription> - </ItemContent> - </Item> - </DropdownMenuItem> - </For> - </DropdownMenuContent> - </DropdownMenu> - </div> + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="outline">"Select" {icon!(icondata::LuChevronDown)}</Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="center"> + <For each=move || people.get() key=|person| person.username.clone() let(person)> + { + let username = person.username.clone(); + view! { + <DropdownMenuItem class="p-0"> + <Item size="xs" class="w-full"> + <ItemMedia> + <Avatar class="size-[--spacing(6.5)]"> + <AvatarImage src=person.avatar class="grayscale" /> + <AvatarFallback> + {username[..2].to_uppercase()} + </AvatarFallback> + </Avatar> + </ItemMedia> + <ItemContent class="gap-0"> + <ItemTitle>{person.username}</ItemTitle> + <ItemDescription class="leading-none"> + {person.email} + </ItemDescription> + </ItemContent> + </Item> + </DropdownMenuItem> + } + } + </For> + </DropdownMenuContent> + </DropdownMenu> } } diff --git a/docs/src/routes/components/item/examples/item_group.rs b/docs/src/routes/components/item/examples/item_group.rs index e893148d..974fa3e5 100644 --- a/docs/src/routes/components/item/examples/item_group.rs +++ b/docs/src/routes/components/item/examples/item_group.rs @@ -29,41 +29,32 @@ pub fn ItemGroupExample() -> impl IntoView { ]); view! { - <div class="flex w-full max-w-md flex-col gap-6"> - <ItemGroup> - <For each=move || people.get() key=|person| person.username.clone() let(person)> - { - let username = person.username.clone(); + <ItemGroup class="max-w-sm"> + <For each=move || people.get() key=|person| person.username.clone() let(person)> + { + let username = person.username.clone(); - view! { - <Item> - <ItemMedia> - <Avatar> - <AvatarImage src=person.avatar class="grayscale" /> - </Avatar> - </ItemMedia> - <ItemContent class="gap-1"> - <ItemTitle>{person.username}</ItemTitle> - <ItemDescription>{person.email}</ItemDescription> - </ItemContent> - <ItemActions> - <Button variant="ghost" size="icon" class="rounded-full"> - {icon!(icondata::LuPlus)} - </Button> - </ItemActions> - </Item> - - {if username.as_str() != "evilrabbit" { - - view! { <ItemSeparator /> } - .into_any() - } else { - "".into_any() - }} - } + view! { + <Item variant="outline"> + <ItemMedia> + <Avatar> + <AvatarImage src=person.avatar class="grayscale" /> + <AvatarFallback>{username[..2].to_uppercase()}</AvatarFallback> + </Avatar> + </ItemMedia> + <ItemContent class="gap-1"> + <ItemTitle>{person.username}</ItemTitle> + <ItemDescription>{person.email}</ItemDescription> + </ItemContent> + <ItemActions> + <Button variant="ghost" size="icon" class="rounded-full"> + {icon!(icondata::LuPlus)} + </Button> + </ItemActions> + </Item> } - </For> - </ItemGroup> - </div> + } + </For> + </ItemGroup> } } diff --git a/docs/src/routes/components/item/examples/item_header.rs b/docs/src/routes/components/item/examples/item_header.rs index c7e2d9ea..004aaf3f 100644 --- a/docs/src/routes/components/item/examples/item_header.rs +++ b/docs/src/routes/components/item/examples/item_header.rs @@ -42,14 +42,19 @@ pub fn ItemHeaderExample() -> impl IntoView { view! { <Item variant="outline"> <ItemHeader> - <Tooltip value=model.credit> - <img - src=model.image - alt=model.name - width=128 - height=128 - class="aspect-square w-full rounded-sm object-cover" - /> + <Tooltip> + <TooltipTrigger> + <img + src=model.image + alt=model.name + width=128 + height=128 + class="aspect-square w-full rounded-sm object-cover" + /> + </TooltipTrigger> + <TooltipContent> + <p>{model.credit}</p> + </TooltipContent> </Tooltip> </ItemHeader> <ItemContent> diff --git a/docs/src/routes/components/item/examples/item_image.rs b/docs/src/routes/components/item/examples/item_image.rs index b82f2e10..f0e30325 100644 --- a/docs/src/routes/components/item/examples/item_image.rs +++ b/docs/src/routes/components/item/examples/item_image.rs @@ -53,7 +53,7 @@ pub fn ItemImageExample() -> impl IntoView { <ItemContent> <ItemTitle class="line-clamp-1"> {song_title} " - " - <span class="text-(--muted-foreground)">{song.album}</span> + <span class="text-muted-foreground">{song.album}</span> </ItemTitle> <ItemDescription>{song.artist}</ItemDescription> </ItemContent> diff --git a/docs/src/routes/components/item/examples/item_size.rs b/docs/src/routes/components/item/examples/item_size.rs new file mode 100644 index 00000000..201b2085 --- /dev/null +++ b/docs/src/routes/components/item/examples/item_size.rs @@ -0,0 +1,31 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ItemSizeExample() -> impl IntoView { + view! { + <div class="flex w-full max-w-md flex-col gap-6"> + <Item variant="outline"> + <ItemMedia variant="icon">{icon!(icondata::LuInbox)}</ItemMedia> + <ItemContent> + <ItemTitle>"Default Size"</ItemTitle> + <ItemDescription>"The standard size for most use cases."</ItemDescription> + </ItemContent> + </Item> + <Item variant="outline" size="sm"> + <ItemMedia variant="icon">{icon!(icondata::LuInbox)}</ItemMedia> + <ItemContent> + <ItemTitle>"Small Size"</ItemTitle> + <ItemDescription>"A compact size for dense layouts."</ItemDescription> + </ItemContent> + </Item> + <Item variant="outline" size="xs"> + <ItemMedia variant="icon">{icon!(icondata::LuInbox)}</ItemMedia> + <ItemContent> + <ItemTitle>"Extra Small Size"</ItemTitle> + <ItemDescription>"The most compact size available."</ItemDescription> + </ItemContent> + </Item> + </div> + } +} diff --git a/docs/src/routes/components/item/examples/mod.rs b/docs/src/routes/components/item/examples/mod.rs index c846a98a..cf9a0925 100644 --- a/docs/src/routes/components/item/examples/mod.rs +++ b/docs/src/routes/components/item/examples/mod.rs @@ -6,6 +6,7 @@ mod item_header; mod item_icon; mod item_image; mod item_link; +mod item_size; mod item_variants; pub use item::*; @@ -16,4 +17,5 @@ pub use item_header::*; pub use item_icon::*; pub use item_image::*; pub use item_link::*; +pub use item_size::*; pub use item_variants::*; diff --git a/docs/src/routes/components/kbd/component.toml b/docs/src/routes/components/kbd/component.toml index 93ff70e8..9f69851c 100644 --- a/docs/src/routes/components/kbd/component.toml +++ b/docs/src/routes/components/kbd/component.toml @@ -15,6 +15,12 @@ title = "Button" description = """Use the Kbd component inside a Button component to display a keyboard key inside \ a button.""" +[[examples]] +name = "Kbd input group" +title = "Input Group" +description = """You can use the Kbd component inside a InputGroupAddon component to display a \ +keyboard key inside an input group.""" + [[references]] name = "Kbd" description = "Contains a single styled input indication." diff --git a/docs/src/routes/components/kbd/examples/kbd.rs b/docs/src/routes/components/kbd/examples/kbd.rs index a5210b8d..d5015b9b 100644 --- a/docs/src/routes/components/kbd/examples/kbd.rs +++ b/docs/src/routes/components/kbd/examples/kbd.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::kbd::*; +use singlestage::*; #[component] pub fn KbdExample() -> impl IntoView { diff --git a/docs/src/routes/components/kbd/examples/kbd_button.rs b/docs/src/routes/components/kbd/examples/kbd_button.rs index fb6c67b0..2629cc08 100644 --- a/docs/src/routes/components/kbd/examples/kbd_button.rs +++ b/docs/src/routes/components/kbd/examples/kbd_button.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::{Button, Kbd}; +use singlestage::*; #[component] pub fn KbdButtonExample() -> impl IntoView { diff --git a/docs/src/routes/components/kbd/examples/kbd_group.rs b/docs/src/routes/components/kbd/examples/kbd_group.rs index 471f1549..aa238a8d 100644 --- a/docs/src/routes/components/kbd/examples/kbd_group.rs +++ b/docs/src/routes/components/kbd/examples/kbd_group.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::kbd::*; +use singlestage::*; #[component] pub fn KbdGroupExample() -> impl IntoView { diff --git a/docs/src/routes/components/kbd/examples/kbd_input_group.rs b/docs/src/routes/components/kbd/examples/kbd_input_group.rs new file mode 100644 index 00000000..dd7a3c3a --- /dev/null +++ b/docs/src/routes/components/kbd/examples/kbd_input_group.rs @@ -0,0 +1,18 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn KbdInputGroupExample() -> impl IntoView { + view! { + <div class="flex w-full max-w-xs flex-col gap-6"> + <InputGroup> + <Input placeholder="Search..." /> + <InputGroupAddon>{icon!(icondata::LuSearch)}</InputGroupAddon> + <InputGroupAddon align="inline-end"> + <Kbd>"⌘"</Kbd> + <Kbd>"K"</Kbd> + </InputGroupAddon> + </InputGroup> + </div> + } +} diff --git a/docs/src/routes/components/kbd/examples/mod.rs b/docs/src/routes/components/kbd/examples/mod.rs index 9f108760..a3d59892 100644 --- a/docs/src/routes/components/kbd/examples/mod.rs +++ b/docs/src/routes/components/kbd/examples/mod.rs @@ -1,7 +1,9 @@ mod kbd; mod kbd_button; mod kbd_group; +mod kbd_input_group; pub use kbd::*; pub use kbd_button::*; pub use kbd_group::*; +pub use kbd_input_group::*; diff --git a/docs/src/routes/components/label/component.toml b/docs/src/routes/components/label/component.toml index 25302bd1..d1a5e024 100644 --- a/docs/src/routes/components/label/component.toml +++ b/docs/src/routes/components/label/component.toml @@ -4,6 +4,16 @@ description = "Renders an accessible label associated with controls." [[examples]] name = "label" +[[examples]] +name = "labels implicit" +title = "Implicit Labeling" +description = "Input components use child text as their label and automatically handle association." + +[[examples]] +name = "labels in fields" +title = "Labels in Fields" +description = "Labels used in Fields automatically associate with Field inputs." + [[references]] name = "Label" description = "Renders an accessible label associated with controls." @@ -11,6 +21,10 @@ extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [ + { attr = "disabled", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display as disabled.""" }, + { attr = "invalid", attr_type = "bool", default = "false", description = """Set whether or not \ + this element should display as invalid.""" }, { attr = "label_for", attr_type = "String", default = """""""", description = """Renamed \ <label> `for` attribute to avoid name collisions.""" }, ] diff --git a/docs/src/routes/components/label/examples/label.rs b/docs/src/routes/components/label/examples/label.rs index 58b3ccaf..598abf27 100644 --- a/docs/src/routes/components/label/examples/label.rs +++ b/docs/src/routes/components/label/examples/label.rs @@ -1,21 +1,12 @@ use leptos::prelude::*; -use singlestage::{Checkbox, Input, Label}; +use singlestage::*; #[component] pub fn LabelExample() -> impl IntoView { view! { - <div class="space-y-4"> - <div class="grid gap-2"> - <Label label_for="email">"Email"</Label> - <Input id="email" input_type="email" /> - </div> - - <Label class="grid gap-2">"Password" <Input input_type="password" /></Label> - - <Label> - <Checkbox /> - "Accept terms and conditions" - </Label> + <div class="flex gap-2"> + <Checkbox id="terms" /> + <Label label_for="terms">"Accept terms and conditions"</Label> </div> } } diff --git a/docs/src/routes/components/label/examples/labels_implicit.rs b/docs/src/routes/components/label/examples/labels_implicit.rs new file mode 100644 index 00000000..0d69050f --- /dev/null +++ b/docs/src/routes/components/label/examples/labels_implicit.rs @@ -0,0 +1,15 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn LabelsImplicitExample() -> impl IntoView { + view! { + <div class="flex flex-col gap-4"> + <Input>"Label"</Input> + <Checkbox>"Label"</Checkbox> + <Radio>"Label"</Radio> + <Switch>"Label"</Switch> + <Textarea>"Label"</Textarea> + </div> + } +} 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! { + <div class="w-full max-w-md"> + <form> + <FieldGroup> + <FieldSet> + <FieldLegend>"Payment Method"</FieldLegend> + <FieldDescription> + "All transactions are secure and encrypted" + </FieldDescription> + <FieldGroup> + <Field> + <Label>"Name on Card"</Label> + <Input placeholder="Evil Rabbit" /> + </Field> + <Field> + <Label>"Card Number"</Label> + <Input placeholder="1234 5678 9012 3456" /> + <FieldDescription> + "Enter your 16-digit card number" + </FieldDescription> + </Field> + <div class="grid grid-cols-3 gap-4"> + <Field> + <Label>"Month"</Label> + <Select placeholder="MM"> + <SelectOption value="01">"01"</SelectOption> + <SelectOption value="02">"02"</SelectOption> + <SelectOption value="03">"03"</SelectOption> + <SelectOption value="04">"04"</SelectOption> + <SelectOption value="05">"05"</SelectOption> + <SelectOption value="06">"06"</SelectOption> + <SelectOption value="07">"07"</SelectOption> + <SelectOption value="08">"08"</SelectOption> + <SelectOption value="09">"09"</SelectOption> + <SelectOption value="10">"10"</SelectOption> + <SelectOption value="11">"11"</SelectOption> + <SelectOption value="12">"12"</SelectOption> + </Select> + </Field> + <Field> + <Label>"Year"</Label> + <Select placeholder="YYYY"> + <SelectOption value="2024">"2024"</SelectOption> + <SelectOption value="2025">"2025"</SelectOption> + <SelectOption value="2026">"2026"</SelectOption> + <SelectOption value="2027">"2027"</SelectOption> + <SelectOption value="2028">"2028"</SelectOption> + <SelectOption value="2029">"2029"</SelectOption> + </Select> + </Field> + <Field> + <Label>"CVV"</Label> + <Input placeholder="123" /> + </Field> + </div> + </FieldGroup> + </FieldSet> + <Separator /> + <FieldSet> + <FieldLegend>"Billing Address"</FieldLegend> + <FieldDescription> + "The billing address associated with your payment method" + </FieldDescription> + <FieldGroup> + <Field orientation="horizontal"> + <Checkbox checked=true /> + <Label class="font-normal">"Same as shipping address"</Label> + </Field> + </FieldGroup> + </FieldSet> + <FieldSet> + <FieldGroup> + <Field> + <Label>"Comments"</Label> + <Textarea + placeholder="Add any additional comments" + class="resize-none" + /> + </Field> + </FieldGroup> + </FieldSet> + <Field orientation="horizontal"> + <Button button_type="submit">"Submit"</Button> + <Button variant="outline" button_type="button"> + "Cancel" + </Button> + </Field> + </FieldGroup> + </form> + </div> + } +} diff --git a/docs/src/routes/components/label/examples/mod.rs b/docs/src/routes/components/label/examples/mod.rs index 8a0612c2..6a9c3cfe 100644 --- a/docs/src/routes/components/label/examples/mod.rs +++ b/docs/src/routes/components/label/examples/mod.rs @@ -1,2 +1,7 @@ mod label; +mod labels_implicit; +mod labels_in_fields; + pub use label::*; +pub use labels_implicit::*; +pub use labels_in_fields::*; diff --git a/docs/src/routes/components/link/component.toml b/docs/src/routes/components/link/component.toml index 500a89fa..53e3f698 100644 --- a/docs/src/routes/components/link/component.toml +++ b/docs/src/routes/components/link/component.toml @@ -7,7 +7,13 @@ name = "link" [[examples]] name = "link button" title = "As Button" -description = "Use as_button to render a Link component with Button styling." +description = "Use render_as to render a Link component with Button styling." + +[[examples]] +name = "link badge" +title = "As Badge" +description = "Use render_as to render a Link component with Badge styling." + [[references]] name = "Link" @@ -18,12 +24,13 @@ and <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#attributes">a</a> \ attributes.""" attrs = [ - { attr = "as_button", attr_type = "bool", default = "false", description = """Set whether or \ - not this Link should appear as a Button.""" }, + { attr = "render_as", attr_type = "String", default = """""""", description = """Set whether or \ + not this Link should appear as something else. This is similar to asChild. Accepted values \ + are "button" | "badge". """ }, { attr = "mimetype", attr_type = "String", default = """""""", description = """Renamed <a> \ `type` attribute to avoid name collisions.""" }, { attr = "size", attr_type = "String", default = """""""", description = """For use with \ - as_button, specify the size to render the button.""" }, + render_as, specify the size to render the element.""" }, { attr = "variant", attr_type = "String", default = """"link"""", description = """For use with \ - as_button, specify the style of button to render.""" }, + render_as, specify the variant of the element to render.""" }, ] diff --git a/docs/src/routes/components/link/examples/link_badge.rs b/docs/src/routes/components/link/examples/link_badge.rs new file mode 100644 index 00000000..0d83690d --- /dev/null +++ b/docs/src/routes/components/link/examples/link_badge.rs @@ -0,0 +1,12 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn LinkBadgeExample() -> impl IntoView { + view! { + <Link render_as="badge" href="#link"> + <span>"Open Link "</span> + {icon!(icondata::LuArrowUpRight)} + </Link> + } +} diff --git a/docs/src/routes/components/link/examples/link_button.rs b/docs/src/routes/components/link/examples/link_button.rs index 05355144..4d201f16 100644 --- a/docs/src/routes/components/link/examples/link_button.rs +++ b/docs/src/routes/components/link/examples/link_button.rs @@ -5,19 +5,19 @@ use singlestage::*; pub fn LinkButtonExample() -> impl IntoView { view! { <div class="flex flex-col space-y-6"> - <Link as_button=true size="sm" class="text-(--muted-foreground)" href="#"> + <Link render_as="button" size="sm" class="text-(--muted-foreground)" href="#"> "Learn More" {icon!(icondata::LuArrowUpRight)} </Link> - <Link as_button=true variant="primary" href="#"> + <Link render_as="button" variant="primary" href="#"> "Learn More" {icon!(icondata::LuArrowUpRight)} </Link> - <Link as_button=true variant="secondary" href="#"> + <Link render_as="button" variant="secondary" href="#"> "Learn More" {icon!(icondata::LuArrowUpRight)} </Link> - <Link as_button=true variant="outline" href="#"> + <Link render_as="button" variant="outline" href="#"> "Learn More" {icon!(icondata::LuArrowUpRight)} </Link> diff --git a/docs/src/routes/components/link/examples/mod.rs b/docs/src/routes/components/link/examples/mod.rs index 30975227..ff6e6f0b 100644 --- a/docs/src/routes/components/link/examples/mod.rs +++ b/docs/src/routes/components/link/examples/mod.rs @@ -1,5 +1,7 @@ mod link; +mod link_badge; mod link_button; pub use link::*; +pub use link_badge::*; pub use link_button::*; diff --git a/docs/src/routes/components/pagination/anatomy.rs b/docs/src/routes/components/pagination/anatomy.rs index d4400a67..e86f0a69 100644 --- a/docs/src/routes/components/pagination/anatomy.rs +++ b/docs/src/routes/components/pagination/anatomy.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::pagination::*; +use singlestage::*; #[component] pub fn PaginationAnatomy() -> impl IntoView { diff --git a/docs/src/routes/components/pagination/component.toml b/docs/src/routes/components/pagination/component.toml index 93881262..acd26f31 100644 --- a/docs/src/routes/components/pagination/component.toml +++ b/docs/src/routes/components/pagination/component.toml @@ -4,6 +4,17 @@ description = "Pagination with page navigation, next, and previous links." [[examples]] name = "pagination" +[[examples]] +name = "pagination simple" +title = "Simple" +description = "A simple pagination with only page numbers." + +[[examples]] +name = "pagination icons only" +title = "Icons only" +description = """You can also use just the previous and next buttons without page numbers. This \ +is useful for data tables with a rows per page selector.""" + [[references]] name = "Pagination" description = "Contains all the parts of the pagination component." diff --git a/docs/src/routes/components/pagination/examples/mod.rs b/docs/src/routes/components/pagination/examples/mod.rs index f7e18dcb..7fd79daf 100644 --- a/docs/src/routes/components/pagination/examples/mod.rs +++ b/docs/src/routes/components/pagination/examples/mod.rs @@ -1,2 +1,7 @@ mod pagination; +mod pagination_icons_only; +mod pagination_simple; + pub use pagination::*; +pub use pagination_icons_only::*; +pub use pagination_simple::*; diff --git a/docs/src/routes/components/pagination/examples/pagination.rs b/docs/src/routes/components/pagination/examples/pagination.rs index f56aacdd..aa17b221 100644 --- a/docs/src/routes/components/pagination/examples/pagination.rs +++ b/docs/src/routes/components/pagination/examples/pagination.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::pagination::*; +use singlestage::*; #[component] pub fn PaginationExample() -> impl IntoView { @@ -7,7 +7,7 @@ pub fn PaginationExample() -> impl IntoView { <Pagination> <PaginationContent> <PaginationItem> - <PaginationPrevious href="">"Previous"</PaginationPrevious> + <PaginationPrevious href="#" /> </PaginationItem> <PaginationItem> <PaginationLink href="#">"1"</PaginationLink> @@ -24,7 +24,7 @@ pub fn PaginationExample() -> impl IntoView { <PaginationEllipsis /> </PaginationItem> <PaginationItem> - <PaginationNext href="#">"Next"</PaginationNext> + <PaginationNext href="#" /> </PaginationItem> </PaginationContent> </Pagination> diff --git a/docs/src/routes/components/pagination/examples/pagination_icons_only.rs b/docs/src/routes/components/pagination/examples/pagination_icons_only.rs new file mode 100644 index 00000000..dbc6172b --- /dev/null +++ b/docs/src/routes/components/pagination/examples/pagination_icons_only.rs @@ -0,0 +1,29 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn PaginationIconsOnlyExample() -> impl IntoView { + view! { + <div class="flex items-center justify-between gap-4"> + <Field orientation="horizontal" class="w-fit"> + <Label>"Rows per page"</Label> + <Select class="w-20" value="25"> + <SelectOption value="10">"10"</SelectOption> + <SelectOption value="25">"25"</SelectOption> + <SelectOption value="50">"50"</SelectOption> + <SelectOption value="100">"100"</SelectOption> + </Select> + </Field> + <Pagination class="mx-0 w-auto"> + <PaginationContent> + <PaginationItem> + <PaginationPrevious href="#" /> + </PaginationItem> + <PaginationItem> + <PaginationNext href="#" /> + </PaginationItem> + </PaginationContent> + </Pagination> + </div> + } +} diff --git a/docs/src/routes/components/pagination/examples/pagination_simple.rs b/docs/src/routes/components/pagination/examples/pagination_simple.rs new file mode 100644 index 00000000..69be649b --- /dev/null +++ b/docs/src/routes/components/pagination/examples/pagination_simple.rs @@ -0,0 +1,29 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn PaginationSimpleExample() -> impl IntoView { + view! { + <Pagination> + <PaginationContent> + <PaginationItem> + <PaginationLink href="#">"1"</PaginationLink> + </PaginationItem> + <PaginationItem> + <PaginationLink href="#" active=true> + "2" + </PaginationLink> + </PaginationItem> + <PaginationItem> + <PaginationLink href="#">"3"</PaginationLink> + </PaginationItem> + <PaginationItem> + <PaginationLink href="#">"4"</PaginationLink> + </PaginationItem> + <PaginationItem> + <PaginationLink href="#">"5"</PaginationLink> + </PaginationItem> + </PaginationContent> + </Pagination> + } +} diff --git a/docs/src/routes/components/popover/anatomy.rs b/docs/src/routes/components/popover/anatomy.rs index 8892b3ba..dc52921a 100644 --- a/docs/src/routes/components/popover/anatomy.rs +++ b/docs/src/routes/components/popover/anatomy.rs @@ -8,7 +8,12 @@ pub fn PopoverAnatomy() -> impl IntoView { <PopoverTrigger> <Button /> </PopoverTrigger> - <PopoverContent /> + <PopoverContent> + <PopoverHeader> + <PopoverTitle /> + <PopoverDescription /> + </PopoverHeader> + </PopoverContent> </Popover> } } diff --git a/docs/src/routes/components/popover/component.toml b/docs/src/routes/components/popover/component.toml index 3a006026..49cf2b12 100644 --- a/docs/src/routes/components/popover/component.toml +++ b/docs/src/routes/components/popover/component.toml @@ -2,25 +2,47 @@ name = "Popover" description = "Displays rich content in a portal, triggered by a button." [info] -title = "This component could use some work" -description = """While this component does make use of the new popover browser API, proper \ -positioning is still not widely supported. There are hacks in place to try to work around this \ -issue while still taking advantage of the rest of the API. Until this is implemented (hopefully \ -soon - Aug 03, 2025) that puts this component in only a 'Good Enough' state. "<singlestage::Link \ -style="color: var(--foreground)" \ -href="https://developer.mozilla.org/en-US/docs/Web/CSS/anchor#browser_compatibility"> "More \ -Info"</singlestage::Link>"""" +title = "This component uses newly available browser features" +description = """"<p>"This component uses CSS Anchor Positioning which has only been widely \ +supported since January 2026. If you're targeting older or less mainstream browsers then maybe \ +this component isn't for you, yet. "</p><p>" Alternatively you can use Singlestage 0.4 which \ +positions the component correctly but with limited flexibility for alignment, or roll your own \ +using Singlestage CSS classes for consistent styling. "</p><singlestage::Link style="color: \ +var(--foreground)" href="https://caniuse.com/css-anchor-positioning"> "More Info" \ +</singlestage::Link>"""" [[examples]] name = "popover" +[[examples]] +name = "popover basic" +title = "Basic" +description = "A simple popover with a header, title, and description." + +[[examples]] +name = "popover align" +title = "Align" +description = "Use the align prop on PopoverContent to control the horizontal alignment." + +[[examples]] +name = "popover form" +title = "With Form" +description = "A popover with form fields inside." + [[references]] name = "Popover" description = "Contains all the parts of a popover." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" -attrs = [] +attrs = [ + { attr = "modal", attr_type = "bool", default = "false", description = """ Set whether or not \ + this popover should be modal meaning it can't be light dismissed Use this along with the \ + `open` signal for a manually managed popover""" }, + { attr = "open", attr_type = "bool", default = "false", description = """Reactive signal that \ + can remotely control the open state of the popover **but is not coupled to the actual open \ + state of the popover** unless `modal` is set to `true`""" }, +] [[references]] name = "PopoverContent" @@ -28,10 +50,40 @@ description = "The component that pops out when the popover is open." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" +attrs = [ + { attr = "align", attr_type = "String", default = """"start"""", description = """Set how to \ + align the popover. Accepted values are "start" | "center" | "end"""" }, + { attr = "side", attr_type = "String", default = """"bottom"""", description = """Set which \ + side the popover opens relative to the trigger. Accepted values are "top" | "right" | \ + "bottom" | "left". """ }, +] + +[[references]] +name = "PopoverDescription" +description = "Displays a description of popover content" +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "PopoverHeader" +description = "A styled header container for use inside a PopoverContent." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "PopoverTitle" +description = "Contains the title of the popover." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" attrs = [] [[references]] -name = "PopoverTrigger" +name = "Trigger" description = "Provides context to a `Button` that triggers a popover.." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ diff --git a/docs/src/routes/components/popover/examples/mod.rs b/docs/src/routes/components/popover/examples/mod.rs index 5d061cc1..e0e6b1e0 100644 --- a/docs/src/routes/components/popover/examples/mod.rs +++ b/docs/src/routes/components/popover/examples/mod.rs @@ -1,2 +1,9 @@ mod popover; +mod popover_align; +mod popover_basic; +mod popover_form; + pub use popover::*; +pub use popover_align::*; +pub use popover_basic::*; +pub use popover_form::*; diff --git a/docs/src/routes/components/popover/examples/popover.rs b/docs/src/routes/components/popover/examples/popover.rs index 1427b781..d3a0b2a4 100644 --- a/docs/src/routes/components/popover/examples/popover.rs +++ b/docs/src/routes/components/popover/examples/popover.rs @@ -10,23 +10,25 @@ pub fn PopoverExample() -> impl IntoView { </PopoverTrigger> <PopoverContent class="w-80"> <form> - <FieldSet> - <FieldLegend>"Dimensions"</FieldLegend> + <FieldSet class="gap-4"> + <FieldLegend variant="label">"Dimensions"</FieldLegend> <FieldDescription>"Set the dimensions for the layer."</FieldDescription> - <FieldGroup class="[&_input]:w-48 gap-2"> + <FieldGroup class="gap-2 [&_input]:max-w-48 [&_input]:h-8"> <Field orientation="horizontal"> - <Input value="100%" autofocus=true> - "Width" - </Input> + <FieldLabel>"Width"</FieldLabel> + <Input value="100%" autofocus=true /> </Field> <Field orientation="horizontal"> - <Input value="300px">"Max. width"</Input> + <FieldLabel>"Max. width"</FieldLabel> + <Input value="300px" /> </Field> <Field orientation="horizontal"> - <Input value="25px">"Height"</Input> + <FieldLabel>"Height"</FieldLabel> + <Input value="25px" /> </Field> <Field orientation="horizontal"> - <Input value="none">"Max. height"</Input> + <FieldLabel>"Max. height"</FieldLabel> + <Input value="none" /> </Field> </FieldGroup> </FieldSet> diff --git a/docs/src/routes/components/popover/examples/popover_align.rs b/docs/src/routes/components/popover/examples/popover_align.rs new file mode 100644 index 00000000..5a9c486e --- /dev/null +++ b/docs/src/routes/components/popover/examples/popover_align.rs @@ -0,0 +1,40 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn PopoverAlignExample() -> impl IntoView { + view! { + <div class="flex gap-6"> + <Popover> + <PopoverTrigger> + <Button variant="outline" size="sm"> + "Start" + </Button> + </PopoverTrigger> + <PopoverContent align="start" class="w-40"> + "Aligned to start" + </PopoverContent> + </Popover> + <Popover> + <PopoverTrigger> + <Button variant="outline" size="sm"> + "Center" + </Button> + </PopoverTrigger> + <PopoverContent align="center" class="w-40"> + "Aligned to center" + </PopoverContent> + </Popover> + <Popover> + <PopoverTrigger> + <Button variant="outline" size="sm"> + "End" + </Button> + </PopoverTrigger> + <PopoverContent align="end" class="w-40"> + "Aligned to end" + </PopoverContent> + </Popover> + </div> + } +} diff --git a/docs/src/routes/components/popover/examples/popover_basic.rs b/docs/src/routes/components/popover/examples/popover_basic.rs new file mode 100644 index 00000000..baab0978 --- /dev/null +++ b/docs/src/routes/components/popover/examples/popover_basic.rs @@ -0,0 +1,19 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn PopoverBasicExample() -> impl IntoView { + view! { + <Popover> + <PopoverTrigger> + <Button variant="outline">"Open Popover"</Button> + </PopoverTrigger> + <PopoverContent align="start"> + <PopoverHeader> + <PopoverTitle>"Dimensions"</PopoverTitle> + <PopoverDescription>"Set the dimensions for the layer."</PopoverDescription> + </PopoverHeader> + </PopoverContent> + </Popover> + } +} diff --git a/docs/src/routes/components/popover/examples/popover_form.rs b/docs/src/routes/components/popover/examples/popover_form.rs new file mode 100644 index 00000000..16835ff8 --- /dev/null +++ b/docs/src/routes/components/popover/examples/popover_form.rs @@ -0,0 +1,33 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn PopoverFormExample() -> impl IntoView { + view! { + <Popover> + <PopoverTrigger> + <Button variant="outline">"Open Popover"</Button> + </PopoverTrigger> + <PopoverContent class="w-64" align="start"> + <PopoverHeader> + <PopoverTitle>"Dimensions"</PopoverTitle> + <PopoverDescription>"Set the dimensions for the layer."</PopoverDescription> + </PopoverHeader> + <FieldGroup class="gap-4"> + <Field orientation="horizontal"> + <FieldLabel label_for="width" class="w-1/2"> + "Width" + </FieldLabel> + <Input id="width" value="100%" /> + </Field> + <Field orientation="horizontal"> + <FieldLabel label_for="height" class="w-1/2"> + "Height" + </FieldLabel> + <Input id="height" value="25px" /> + </Field> + </FieldGroup> + </PopoverContent> + </Popover> + } +} diff --git a/docs/src/routes/components/progress/component.toml b/docs/src/routes/components/progress/component.toml index 08098a51..d26f8f8f 100644 --- a/docs/src/routes/components/progress/component.toml +++ b/docs/src/routes/components/progress/component.toml @@ -5,6 +5,16 @@ displayed as a progress bar.""" [[examples]] name = "progress" +[[examples]] +name = "progress label" +title = "Label" +description = "Use a Field component to add a label to the progress bar." + +[[examples]] +name = "progress controlled" +title = "Controlled" +description = "A progress bar controlled by a slider." + [[references]] name = "Progress" description = "Displays progress." diff --git a/docs/src/routes/components/progress/examples/mod.rs b/docs/src/routes/components/progress/examples/mod.rs index 82f1ef42..82e6a91d 100644 --- a/docs/src/routes/components/progress/examples/mod.rs +++ b/docs/src/routes/components/progress/examples/mod.rs @@ -1,3 +1,7 @@ mod progress; +mod progress_controlled; +mod progress_label; pub use progress::*; +pub use progress_controlled::*; +pub use progress_label::*; diff --git a/docs/src/routes/components/progress/examples/progress.rs b/docs/src/routes/components/progress/examples/progress.rs index e5775914..b20eef07 100644 --- a/docs/src/routes/components/progress/examples/progress.rs +++ b/docs/src/routes/components/progress/examples/progress.rs @@ -4,10 +4,10 @@ use std::time::Duration; #[component] pub fn ProgressExample() -> impl IntoView { - let value = RwSignal::new(13); + let value = RwSignal::new(13.); Effect::new(move || { - set_timeout(move || value.set(66), Duration::from_millis(500)); + set_timeout(move || value.set(66.), Duration::from_millis(500)); }); view! { <Progress class="w-[60%]" value /> } diff --git a/docs/src/routes/components/progress/examples/progress_controlled.rs b/docs/src/routes/components/progress/examples/progress_controlled.rs new file mode 100644 index 00000000..469e6af4 --- /dev/null +++ b/docs/src/routes/components/progress/examples/progress_controlled.rs @@ -0,0 +1,14 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ProgressControlledExample() -> impl IntoView { + let value = RwSignal::new(0.); + + view! { + <div class="flex w-full max-w-sm flex-col gap-4"> + <Progress value /> + <Slider value /> + </div> + } +} diff --git a/docs/src/routes/components/progress/examples/progress_label.rs b/docs/src/routes/components/progress/examples/progress_label.rs new file mode 100644 index 00000000..5416fef1 --- /dev/null +++ b/docs/src/routes/components/progress/examples/progress_label.rs @@ -0,0 +1,15 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ProgressLabelExample() -> impl IntoView { + view! { + <Field class="w-full max-w-sm"> + <Label label_for="progress-upload"> + <span>"Upload progress"</span> + <span class="ml-auto">"66%"</span> + </Label> + <Progress value=66. id="progress-upload" /> + </Field> + } +} diff --git a/docs/src/routes/components/radio/component.toml b/docs/src/routes/components/radio/component.toml index 8813bb23..dba6f6a5 100644 --- a/docs/src/routes/components/radio/component.toml +++ b/docs/src/routes/components/radio/component.toml @@ -4,6 +4,26 @@ description = "A set of checkable buttons—known as radio buttons—where no mo [[examples]] name = "radio" +[[examples]] +name = "radio description" +title = "Description" + +[[examples]] +name = "radio choice card" +title = "Choice Card" + +[[examples]] +name = "radio fieldset" +title = "Fieldset" + +[[examples]] +name = "radio disabled" +title = "Disabled" + +[[examples]] +name = "radio invalid" +title = "Invalid" + [[references]] name = "Radio" description = "An item in the group that can be checked." @@ -15,8 +35,8 @@ and <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/radio#additional_attributes">radio</a> \ attributes.""" attrs = [ - { attr = "checked", attr_type = "Reactive<bool>", default = "false", description = """A \ - reactive signal coupled to the radio button's checked value.""" }, + { attr = "checked", attr_type = "bool", default = "false", description = """A reactive signal \ + coupled to the radio button's checked value.""" }, ] [[references]] @@ -32,6 +52,6 @@ attrs = [ or update the default selected value for the RadioGroup.""" }, { attr = "invalid", attr_type = "bool", default = "false", description = """A \ reactive signal that toggles whether all the children Radios appear invalid or not.""" }, - { attr = "value", attr_type = "Reactive<String>", default = """""""", description = """A \ - reactive value coupled to the currently selected Radio's value.""" }, + { attr = "value", attr_type = "String", default = """""""", description = """A reactive value \ + coupled to the currently selected Radio's value.""" }, ] diff --git a/docs/src/routes/components/radio/examples/mod.rs b/docs/src/routes/components/radio/examples/mod.rs index e40f35f8..a6cbde9a 100644 --- a/docs/src/routes/components/radio/examples/mod.rs +++ b/docs/src/routes/components/radio/examples/mod.rs @@ -1,2 +1,13 @@ mod radio; +mod radio_choice_card; +mod radio_description; +mod radio_disabled; +mod radio_fieldset; +mod radio_invalid; + pub use radio::*; +pub use radio_choice_card::*; +pub use radio_description::*; +pub use radio_disabled::*; +pub use radio_fieldset::*; +pub use radio_invalid::*; diff --git a/docs/src/routes/components/radio/examples/radio.rs b/docs/src/routes/components/radio/examples/radio.rs index 4a053152..6f452aa0 100644 --- a/docs/src/routes/components/radio/examples/radio.rs +++ b/docs/src/routes/components/radio/examples/radio.rs @@ -1,16 +1,11 @@ use leptos::prelude::*; -use singlestage::{radio::*, Label}; +use singlestage::*; #[component] pub fn RadioExample() -> impl IntoView { view! { - <RadioGroup default="default"> - <Label> - <Radio value="default" /> - "Default" - </Label> - - // Radios can create their own labels identical to the code above if provided children. + <RadioGroup default="comfortable" class="w-fit"> + <Radio value="default">"Default"</Radio> <Radio value="comfortable">"Comfortable"</Radio> <Radio value="compact">"Compact"</Radio> </RadioGroup> diff --git a/docs/src/routes/components/radio/examples/radio_choice_card.rs b/docs/src/routes/components/radio/examples/radio_choice_card.rs new file mode 100644 index 00000000..6d14f883 --- /dev/null +++ b/docs/src/routes/components/radio/examples/radio_choice_card.rs @@ -0,0 +1,37 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn RadioChoiceCardExample() -> impl IntoView { + view! { + <RadioGroup default="plus" class="max-w-sm"> + <FieldLabel label_for="plus-plan"> + <Field orientation="horizontal"> + <FieldContent> + <FieldTitle>"Plus"</FieldTitle> + <FieldDescription>"For individuals and small teams."</FieldDescription> + </FieldContent> + <Radio value="plus" id="plus-plan" /> + </Field> + </FieldLabel> + <FieldLabel label_for="pro-plan"> + <Field orientation="horizontal"> + <FieldContent> + <FieldTitle>"Pro"</FieldTitle> + <FieldDescription>"For growing businesses."</FieldDescription> + </FieldContent> + <Radio value="pro" id="pro-plan" /> + </Field> + </FieldLabel> + <FieldLabel label_for="enterprise-plan"> + <Field orientation="horizontal"> + <FieldContent> + <FieldTitle>"Enterprise"</FieldTitle> + <FieldDescription>"For large teams and enterprises."</FieldDescription> + </FieldContent> + <Radio value="enterprise" id="enterprise-plan" /> + </Field> + </FieldLabel> + </RadioGroup> + } +} diff --git a/docs/src/routes/components/radio/examples/radio_description.rs b/docs/src/routes/components/radio/examples/radio_description.rs new file mode 100644 index 00000000..8daf820e --- /dev/null +++ b/docs/src/routes/components/radio/examples/radio_description.rs @@ -0,0 +1,31 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn RadioDescriptionExample() -> impl IntoView { + view! { + <RadioGroup default="comfortable" class="w-fit"> + <Field orientation="horizontal"> + <Radio value="default" id="desc-r1" /> + <FieldContent> + <Label label_for="desc-r1">"Default"</Label> + <FieldDescription>"Standard spacing for most use cases."</FieldDescription> + </FieldContent> + </Field> + <Field orientation="horizontal"> + <Radio value="comfortable" id="desc-r2" /> + <FieldContent> + <Label label_for="desc-r2">"Comfortable"</Label> + <FieldDescription>"More space between elements."</FieldDescription> + </FieldContent> + </Field> + <Field orientation="horizontal"> + <Radio value="compact" id="desc-r3" /> + <FieldContent> + <Label label_for="desc-r3">"Compact"</Label> + <FieldDescription>"Minimal spacing for dense layouts."</FieldDescription> + </FieldContent> + </Field> + </RadioGroup> + } +} diff --git a/docs/src/routes/components/radio/examples/radio_disabled.rs b/docs/src/routes/components/radio/examples/radio_disabled.rs new file mode 100644 index 00000000..d990d704 --- /dev/null +++ b/docs/src/routes/components/radio/examples/radio_disabled.rs @@ -0,0 +1,25 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn RadioDisabledExample() -> impl IntoView { + view! { + <FieldSet class="w-full max-w-xs"> + <FieldLegend variant="label">"Notification Preferences"</FieldLegend> + <FieldDescription>"Choose how you want to receive notifications."</FieldDescription> + <RadioGroup default="email" disabled=true> + <Field orientation="horizontal" disabled=true> + <Radio value="email">"Email only"</Radio> + </Field> + <Field orientation="horizontal"> + <Radio value="sms" disabled=true> + "SMS only" + </Radio> + </Field> + <Field orientation="horizontal"> + <Radio value="both">"Both Email & SMS"</Radio> + </Field> + </RadioGroup> + </FieldSet> + } +} diff --git a/docs/src/routes/components/radio/examples/radio_fieldset.rs b/docs/src/routes/components/radio/examples/radio_fieldset.rs new file mode 100644 index 00000000..1006aa84 --- /dev/null +++ b/docs/src/routes/components/radio/examples/radio_fieldset.rs @@ -0,0 +1,34 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn RadioFieldsetExample() -> impl IntoView { + view! { + <FieldSet class="w-full max-w-xs"> + <FieldLegend variant="label">"Subscription Plan"</FieldLegend> + <FieldDescription> + "Yearly and lifetime plans offer significant savings." + </FieldDescription> + <RadioGroup default="monthly"> + <Field orientation="horizontal"> + <Radio value="monthly" id="plan-monthly" /> + <Label label_for="plan-monthly" class="font-normal"> + "Monthly ($9.99/month)" + </Label> + </Field> + <Field orientation="horizontal"> + <Radio value="yearly" id="plan-yearly" /> + <Label label_for="plan-yearly" class="font-normal"> + "Yearly ($99.99/year)" + </Label> + </Field> + <Field orientation="horizontal"> + <Radio value="lifetime" id="plan-lifetime" /> + <Label label_for="plan-lifetime" class="font-normal"> + "Lifetime ($299.99)" + </Label> + </Field> + </RadioGroup> + </FieldSet> + } +} diff --git a/docs/src/routes/components/radio/examples/radio_invalid.rs b/docs/src/routes/components/radio/examples/radio_invalid.rs new file mode 100644 index 00000000..9868b89b --- /dev/null +++ b/docs/src/routes/components/radio/examples/radio_invalid.rs @@ -0,0 +1,25 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn RadioInvalidExample() -> impl IntoView { + view! { + <FieldSet class="w-full max-w-xs"> + <FieldLegend variant="label">"Notification Preferences"</FieldLegend> + <FieldDescription>"Choose how you want to receive notifications."</FieldDescription> + <RadioGroup default="email" invalid=true> + <Field orientation="horizontal" invalid=true> + <Radio value="email">"Email only"</Radio> + </Field> + <Field orientation="horizontal"> + <Radio value="sms" invalid=true> + "SMS only" + </Radio> + </Field> + <Field orientation="horizontal"> + <Radio value="both">"Both Email & SMS"</Radio> + </Field> + </RadioGroup> + </FieldSet> + } +} diff --git a/docs/src/routes/components/select/anatomy.rs b/docs/src/routes/components/select/anatomy.rs index a7a533a2..e9d73ff7 100644 --- a/docs/src/routes/components/select/anatomy.rs +++ b/docs/src/routes/components/select/anatomy.rs @@ -5,9 +5,9 @@ use singlestage::select::*; pub fn SelectAnatomy() -> impl IntoView { view! { <Select> - <SelectContent> - <SelectItem /> - </SelectContent> + <SelectOptGroup> + <SelectOption /> + </SelectOptGroup> </Select> } } diff --git a/docs/src/routes/components/select/component.toml b/docs/src/routes/components/select/component.toml index 7e95c777..b837206d 100644 --- a/docs/src/routes/components/select/component.toml +++ b/docs/src/routes/components/select/component.toml @@ -14,6 +14,21 @@ name = "select default" title = "Default" description = "Set a default value to be pre-selected." +[[examples]] +name = "select groups" +title = "Groups" +description = "Use SelectOptGroup to organize options into categories." + +[[examples]] +name = "select disabled" +title = "Disabled" +description = "Set the disabled prop on the Select component to disable the select." + +[[examples]] +name = "select invalid" +title = "Invalid" +description = "Set the invalid prop on the Select component to toggle invalid styling." + [[references]] name = "Select" description = "Contains all the parts of the Select." @@ -29,12 +44,12 @@ attrs = [ appearance and behavior.""" }, { attr = "placeholder", attr_type = "String", default = """""""", description = """Sets a \ placeholder value displayed on initial load. Setting default will override this value.""" }, - { attr = "value", attr_type = "Reactive<String>", default = """""""", description = """A \ - reactive value coupled to the currently selected item's value.""" }, + { attr = "value", attr_type = "String", default = """""""", description = """A reactive value \ + coupled to the currently selected item's value.""" }, ] [[references]] -name = "SelectContent" +name = "SelectOptGroup" description = "Contains a group of items for the Select." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ @@ -44,7 +59,7 @@ attributes.""" attrs = [] [[references]] -name = "SelectItem" +name = "SelectOption" description = "Contains an item with a value to be selected." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ diff --git a/docs/src/routes/components/select/examples/mod.rs b/docs/src/routes/components/select/examples/mod.rs index 3aebb5eb..6862d21c 100644 --- a/docs/src/routes/components/select/examples/mod.rs +++ b/docs/src/routes/components/select/examples/mod.rs @@ -1,7 +1,13 @@ mod select; mod select_default; +mod select_disabled; +mod select_groups; +mod select_invalid; mod select_placeholder; pub use select::*; pub use select_default::*; +pub use select_disabled::*; +pub use select_groups::*; +pub use select_invalid::*; pub use select_placeholder::*; diff --git a/docs/src/routes/components/select/examples/select.rs b/docs/src/routes/components/select/examples/select.rs index 384ddbe5..8336173f 100644 --- a/docs/src/routes/components/select/examples/select.rs +++ b/docs/src/routes/components/select/examples/select.rs @@ -5,13 +5,11 @@ use singlestage::*; pub fn SelectExample() -> impl IntoView { view! { <Select> - <SelectContent label="Fruits"> - <SelectItem value="apple">"Apple"</SelectItem> - <SelectItem value="banana">"Banana"</SelectItem> - <SelectItem value="blueberry">"Blueberry"</SelectItem> - <SelectItem value="grapes">"Grapes"</SelectItem> - <SelectItem value="pineapple">"Pineapple"</SelectItem> - </SelectContent> + <SelectOption value="">"Select status"</SelectOption> + <SelectOption value="todo">"Todo"</SelectOption> + <SelectOption value="in-progress">"In Progress"</SelectOption> + <SelectOption value="done">"Done"</SelectOption> + <SelectOption value="cancelled">"Cancelled"</SelectOption> </Select> } } diff --git a/docs/src/routes/components/select/examples/select_default.rs b/docs/src/routes/components/select/examples/select_default.rs index 6060967c..169a8b79 100644 --- a/docs/src/routes/components/select/examples/select_default.rs +++ b/docs/src/routes/components/select/examples/select_default.rs @@ -5,13 +5,13 @@ use singlestage::*; pub fn SelectDefaultExample() -> impl IntoView { view! { <Select default="pineapple"> - <SelectContent label="Fruits"> - <SelectItem value="apple">"Apple"</SelectItem> - <SelectItem value="banana">"Banana"</SelectItem> - <SelectItem value="blueberry">"Blueberry"</SelectItem> - <SelectItem value="grapes">"Grapes"</SelectItem> - <SelectItem value="pineapple">"Pineapple"</SelectItem> - </SelectContent> + <SelectOptGroup label="Fruits"> + <SelectOption value="apple">"Apple"</SelectOption> + <SelectOption value="banana">"Banana"</SelectOption> + <SelectOption value="blueberry">"Blueberry"</SelectOption> + <SelectOption value="grapes">"Grapes"</SelectOption> + <SelectOption value="pineapple">"Pineapple"</SelectOption> + </SelectOptGroup> </Select> } } diff --git a/docs/src/routes/components/select/examples/select_disabled.rs b/docs/src/routes/components/select/examples/select_disabled.rs new file mode 100644 index 00000000..7f599279 --- /dev/null +++ b/docs/src/routes/components/select/examples/select_disabled.rs @@ -0,0 +1,14 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SelectDisabledExample() -> impl IntoView { + view! { + <Select disabled=true> + <SelectOption value="">"Disabled"</SelectOption> + <SelectOption value="apple">"Apple"</SelectOption> + <SelectOption value="banana">"Banana"</SelectOption> + <SelectOption value="blueberry">"Blueberry"</SelectOption> + </Select> + } +} diff --git a/docs/src/routes/components/select/examples/select_groups.rs b/docs/src/routes/components/select/examples/select_groups.rs new file mode 100644 index 00000000..f7c28c8e --- /dev/null +++ b/docs/src/routes/components/select/examples/select_groups.rs @@ -0,0 +1,26 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SelectGroupsExample() -> impl IntoView { + view! { + <Select> + <SelectOption value="">"Select department"</SelectOption> + <SelectOptGroup label="Engineering"> + <SelectOption value="frontend">"Frontend"</SelectOption> + <SelectOption value="backend">"Backend"</SelectOption> + <SelectOption value="devops">"DevOps"</SelectOption> + </SelectOptGroup> + <SelectOptGroup label="Sales"> + <SelectOption value="sales-rep">"Sales Rep"</SelectOption> + <SelectOption value="account-manager">"Account Manager"</SelectOption> + <SelectOption value="sales-director">"Sales Director"</SelectOption> + </SelectOptGroup> + <SelectOptGroup label="Operations"> + <SelectOption value="support">"Customer Support"</SelectOption> + <SelectOption value="product-manager">"Product Manager"</SelectOption> + <SelectOption value="ops-manager">"Operations Manager"</SelectOption> + </SelectOptGroup> + </Select> + } +} diff --git a/docs/src/routes/components/select/examples/select_invalid.rs b/docs/src/routes/components/select/examples/select_invalid.rs new file mode 100644 index 00000000..cf2ff429 --- /dev/null +++ b/docs/src/routes/components/select/examples/select_invalid.rs @@ -0,0 +1,14 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SelectInvalidExample() -> impl IntoView { + view! { + <Select invalid=true> + <SelectOption value="">"Error state"</SelectOption> + <SelectOption value="apple">"Apple"</SelectOption> + <SelectOption value="banana">"Banana"</SelectOption> + <SelectOption value="blueberry">"Blueberry"</SelectOption> + </Select> + } +} diff --git a/docs/src/routes/components/select/examples/select_placeholder.rs b/docs/src/routes/components/select/examples/select_placeholder.rs index 35d0c017..7aaab43d 100644 --- a/docs/src/routes/components/select/examples/select_placeholder.rs +++ b/docs/src/routes/components/select/examples/select_placeholder.rs @@ -5,13 +5,13 @@ use singlestage::*; pub fn SelectPlaceholderExample() -> impl IntoView { view! { <Select placeholder="Select your favorite fruit"> - <SelectContent label="Fruits"> - <SelectItem value="apple">"Apple"</SelectItem> - <SelectItem value="banana">"Banana"</SelectItem> - <SelectItem value="blueberry">"Blueberry"</SelectItem> - <SelectItem value="grapes">"Grapes"</SelectItem> - <SelectItem value="pineapple">"Pineapple"</SelectItem> - </SelectContent> + <SelectOptGroup label="Fruits"> + <SelectOption value="apple">"Apple"</SelectOption> + <SelectOption value="banana">"Banana"</SelectOption> + <SelectOption value="blueberry">"Blueberry"</SelectOption> + <SelectOption value="grapes">"Grapes"</SelectOption> + <SelectOption value="pineapple">"Pineapple"</SelectOption> + </SelectOptGroup> </Select> } } diff --git a/docs/src/routes/components/separator/component.toml b/docs/src/routes/components/separator/component.toml index 2c515672..a850ea07 100644 --- a/docs/src/routes/components/separator/component.toml +++ b/docs/src/routes/components/separator/component.toml @@ -11,6 +11,6 @@ extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [ - { attr = "vertical", attr_type = "bool", default = "false", description = """Toggle whether or \ - not the separator should display vertically.""" }, + { attr = "orientation", attr_type = "String", default = """"horizontal"""", description = """ \ + Specify the orientation of the separator. Accepted values: vertical | horizontal""" }, ] diff --git a/docs/src/routes/components/separator/examples/separator.rs b/docs/src/routes/components/separator/examples/separator.rs index e636cb06..5651220b 100644 --- a/docs/src/routes/components/separator/examples/separator.rs +++ b/docs/src/routes/components/separator/examples/separator.rs @@ -12,9 +12,9 @@ pub fn SeparatorExample() -> impl IntoView { <Separator class="my-4" /> <div class="flex h-5 items-center space-x-4 text-sm"> <div>"Blog"</div> - <Separator vertical=true /> + <Separator orientation="vertical" /> <div>"Docs"</div> - <Separator vertical=true /> + <Separator orientation="vertical" /> <div>"Source"</div> </div> </div> diff --git a/docs/src/routes/components/sheet/anatomy.rs b/docs/src/routes/components/sheet/anatomy.rs new file mode 100644 index 00000000..3efc1f17 --- /dev/null +++ b/docs/src/routes/components/sheet/anatomy.rs @@ -0,0 +1,24 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SheetAnatomy() -> impl IntoView { + view! { + <Sheet> + <SheetTrigger> + <Button /> + </SheetTrigger> + <SheetContent> + <SheetHeader> + <SheetTitle /> + <SheetDescription /> + </SheetHeader> + <SheetFooter> + <SheetClose> + <Button /> + </SheetClose> + </SheetFooter> + </SheetContent> + </Sheet> + } +} diff --git a/docs/src/routes/components/sheet/component.toml b/docs/src/routes/components/sheet/component.toml new file mode 100644 index 00000000..47a190a7 --- /dev/null +++ b/docs/src/routes/components/sheet/component.toml @@ -0,0 +1,73 @@ +name = "Sheet" +description = """Extends the Dialog component to display content that complements the main content +of the screen.""" + +[[examples]] +name = "sheet" + +[[examples]] +name = "sheet side" +title = "Side" +description = "Use the side prop to set which edge of the screen the sheet will appear from." + +[[examples]] +name = "sheet no close button" +title = "No Close Button" +description = "Use the show_close_button prop to control displaying a close button." + +[[references]] +name = "Sheet" +description = "Contains all the components of a sheet." +attrs = [] + +[[references]] +name = "SheetClose" +description = "Wraps a button to be used to trigger closing the sheet." +attrs = [] + +[[references]] +name = "SheetContent" +description = "Contains the main content section of a sheet." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "SheetDescription" +description = "A description of sheet content that renders in the sheet header." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "SheetFooter" +description = """A component that renders at the bottom of the sheet and typically contains calls \ +to action.""" +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "SheetHeader" +description = """A header group that contains a title and description and renders at the top of \ +the sheet.""" +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "SheetTitle" +description = "A title that quickly describes sheet content." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "SheetTrigger" +description = "Wraps a button to be used to trigger opening the sheet." +attrs = [] diff --git a/docs/src/routes/components/sheet/examples/mod.rs b/docs/src/routes/components/sheet/examples/mod.rs new file mode 100644 index 00000000..7a54662c --- /dev/null +++ b/docs/src/routes/components/sheet/examples/mod.rs @@ -0,0 +1,7 @@ +mod sheet; +mod sheet_no_close_button; +mod sheet_side; + +pub use sheet::*; +pub use sheet_no_close_button::*; +pub use sheet_side::*; diff --git a/docs/src/routes/components/sheet/examples/sheet.rs b/docs/src/routes/components/sheet/examples/sheet.rs new file mode 100644 index 00000000..df9b19ae --- /dev/null +++ b/docs/src/routes/components/sheet/examples/sheet.rs @@ -0,0 +1,37 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SheetExample() -> impl IntoView { + view! { + <Sheet> + <SheetTrigger> + <Button variant="outline">"Open"</Button> + </SheetTrigger> + <SheetContent> + <SheetHeader> + <SheetTitle>"Edit profile"</SheetTitle> + <SheetDescription> + "Make changes to your profile here. Click save when you're done." + </SheetDescription> + </SheetHeader> + <div class="grid flex-1 auto-rows-min gap-6 px-4"> + <div class="grid gap-3"> + <Label label_for="sheet-demo-name">"Name"</Label> + <Input id="sheet-demo-name" value="Pedro Duarte" /> + </div> + <div class="grid gap-3"> + <Label label_for="sheet-demo-username">"Username"</Label> + <Input id="sheet-demo-username" value="@peduarte" /> + </div> + </div> + <SheetFooter> + <Button>"Save changes"</Button> + <SheetClose> + <Button variant="outline">"Close"</Button> + </SheetClose> + </SheetFooter> + </SheetContent> + </Sheet> + } +} diff --git a/docs/src/routes/components/sheet/examples/sheet_no_close_button.rs b/docs/src/routes/components/sheet/examples/sheet_no_close_button.rs new file mode 100644 index 00000000..b44b4aad --- /dev/null +++ b/docs/src/routes/components/sheet/examples/sheet_no_close_button.rs @@ -0,0 +1,22 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SheetNoCloseButtonExample() -> impl IntoView { + view! { + <Sheet> + <SheetTrigger> + <Button variant="outline">"Open Sheet"</Button> + </SheetTrigger> + <SheetContent show_close_button=false> + <SheetHeader> + <SheetTitle>"No Close Button"</SheetTitle> + <SheetDescription> + "This sheet doesn't have a close button in the top-right corner. + Click outside to close." + </SheetDescription> + </SheetHeader> + </SheetContent> + </Sheet> + } +} diff --git a/docs/src/routes/components/sheet/examples/sheet_side.rs b/docs/src/routes/components/sheet/examples/sheet_side.rs new file mode 100644 index 00000000..21e6ccfa --- /dev/null +++ b/docs/src/routes/components/sheet/examples/sheet_side.rs @@ -0,0 +1,57 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SheetSideExample() -> impl IntoView { + let sheet_sides: [&'static str; 4] = ["top", "right", "bottom", "left"]; + + view! { + <div class="flex flex-wrap gap-2"> + <For each=move || sheet_sides.to_owned() key=|side| side.to_string() let(side)> + <Sheet> + <SheetTrigger> + <Button variant="outline" class="capitalize"> + {side} + </Button> + </SheetTrigger> + <SheetContent + side + class=match side { + "top" | "bottom" => "max-h-[50vh]", + _ => "", + } + > + <SheetHeader> + <SheetTitle>"Edit profile"</SheetTitle> + <SheetDescription> + "Make changes to your profile here. Click save when you're + done." + </SheetDescription> + </SheetHeader> + <div class="no-scrollbar overflow-y-auto px-4"> + + <For each=move || 0..10 key=|i| i.to_string() let(_)> + <p class="mb-2 leading-relaxed"> + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed + do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud exercitation ullamco + laboris nisi ut aliquip ex ea commodo consequat. Duis aute + irure dolor in reprehenderit in voluptate velit esse cillum + dolore eu fugiat nulla pariatur. Excepteur sint occaecat + cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est laborum." + </p> + </For> + </div> + <SheetFooter> + <Button>"Save changes"</Button> + <SheetClose> + <Button variant="outline">"Cancel"</Button> + </SheetClose> + </SheetFooter> + </SheetContent> + </Sheet> + </For> + </div> + } +} diff --git a/docs/src/routes/components/sheet/mod.rs b/docs/src/routes/components/sheet/mod.rs new file mode 100644 index 00000000..c7ceb8b6 --- /dev/null +++ b/docs/src/routes/components/sheet/mod.rs @@ -0,0 +1,3 @@ +mod examples; + +pub use examples::*; diff --git a/docs/src/routes/components/sidebar/anatomy.rs b/docs/src/routes/components/sidebar/anatomy.rs index 70781ce3..a43e1656 100644 --- a/docs/src/routes/components/sidebar/anatomy.rs +++ b/docs/src/routes/components/sidebar/anatomy.rs @@ -17,12 +17,14 @@ pub fn SidebarAnatomy() -> impl IntoView { </SidebarMenuItem> <SidebarMenuItem> <Collapsible> - <CollapsibleTrigger slot> + <CollapsibleTrigger> <SidebarMenuButton /> </CollapsibleTrigger> <CollapsibleContent> <SidebarMenuSub> - <SidebarMenuSubItem /> + <SidebarMenuSubItem> + <SidebarMenuSubButton /> + </SidebarMenuSubItem> </SidebarMenuSub> </CollapsibleContent> </Collapsible> @@ -32,6 +34,7 @@ pub fn SidebarAnatomy() -> impl IntoView { </SidebarGroup> </SidebarContent> <SidebarFooter /> + <SidebarRail /> </Sidebar> <main> <SidebarTrigger /> diff --git a/docs/src/routes/components/sidebar/component.toml b/docs/src/routes/components/sidebar/component.toml index 1c19d1a1..71270559 100644 --- a/docs/src/routes/components/sidebar/component.toml +++ b/docs/src/routes/components/sidebar/component.toml @@ -1,32 +1,53 @@ name = "Sidebar" description = "A composable, themeable, and customizable sidebar component." -# [[examples]] -# name = "sidebar" - -[[references]] -name = "Collapsible" -description = "Creates a collapsible menu." -extra = """Implements <a class="singlestage-link" \ -href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ -and <a class="singlestage-link" \ -href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/details#attributes">details</a> \ -attributes.""" -attrs = [] - -[[references]] -name = "CollapsibleContent" -description = "Contains the content of a collapsible menu." -extra = """Implements <a class="singlestage-link" \ -href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ -attributes.""" -attrs = [] - -[[references]] -name = "CollapsibleTrigger" -description = "A button that opens the collapsible menu." -extra = """Note: Slot is required.""" -attrs = [] +[[examples]] +name = "sidebar_icon" + +[[examples]] +name = "sidebar_sections" +title = "Sections" +description = "A simple sidebar with navigation grouped by section" + +[[examples]] +name = "sidebar_collapsible_sections" +title = "Collapsible Sections" +description = "A sidebar with collapsible sections" + +[[examples]] +name = "sidebar_submenus" +title = "Submenus" +description = "A sidebar with submenus" + +[[examples]] +name = "sidebar_floating_submenus" +title = "Floating Submenus" +description = "A floating sidebar with submenus" + +[[examples]] +name = "sidebar_collapsible_submenus" +title = "Collapsible Submenus" +description = "A floating sidebar with collapsible submenus" + +[[examples]] +name = "sidebar_submenu_dropdown" +title = "Submenu Dropdown" +description = "A sidebar with submenus as dropdowns" + +[[examples]] +name = "sidebar_icon" +title = "Icons" +description = "A sidebar that collapses to icons" + +[[examples]] +name = "sidebar_secondary" +title = "Secondary Navigation" +description = "An inset sidebar with secondary navigation" + +[[examples]] +name = "sidebar_nested" +title = "Nested" +description = "Collapsible nested sidebars" [[references]] name = "Sidebar" @@ -123,10 +144,10 @@ attrs = [] name = "SidebarProvider" description = "Takes in initial state and provides SidebarContext to its children." attrs = [ - { attr = "hidden", attr_type = "Reactive<bool>", default = "false", description = """Reactive \ - signal coupled to the sidebar's hidden state.""" }, - { attr = "side", attr_type = "Reactive<String>", default = """"left"""", description = """\ - Reactive signal coupled to which side of its container the sidebar renders on.""" }, + { attr = "hidden", attr_type = "bool", default = "false", description = """Reactive signal \ + coupled to the sidebar's hidden state.""" }, + { attr = "side", attr_type = "String", default = """"left"""", description = """Reactive signal \ + coupled to which side of its container the sidebar renders on.""" }, ] [[references]] diff --git a/docs/src/routes/components/sidebar/examples/mod.rs b/docs/src/routes/components/sidebar/examples/mod.rs index ac154d7d..35a9c932 100644 --- a/docs/src/routes/components/sidebar/examples/mod.rs +++ b/docs/src/routes/components/sidebar/examples/mod.rs @@ -1,3 +1,19 @@ -mod sidebar; +mod sidebar_collapsible_sections; +mod sidebar_collapsible_submenus; +mod sidebar_floating_submenus; +mod sidebar_icon; +mod sidebar_nested; +mod sidebar_secondary; +mod sidebar_sections; +mod sidebar_submenu_dropdown; +mod sidebar_submenus; -pub use sidebar::*; +pub use sidebar_collapsible_sections::*; +pub use sidebar_collapsible_submenus::*; +pub use sidebar_floating_submenus::*; +pub use sidebar_icon::*; +pub use sidebar_nested::*; +pub use sidebar_secondary::*; +pub use sidebar_sections::*; +pub use sidebar_submenu_dropdown::*; +pub use sidebar_submenus::*; diff --git a/docs/src/routes/components/sidebar/examples/sidebar.rs b/docs/src/routes/components/sidebar/examples/sidebar.rs deleted file mode 100644 index 78701481..00000000 --- a/docs/src/routes/components/sidebar/examples/sidebar.rs +++ /dev/null @@ -1,33 +0,0 @@ -use leptos::prelude::*; -use singlestage::*; - -#[component] -pub fn SidebarExample() -> impl IntoView { - view! { - <SidebarProvider> - <Sidebar> - <SidebarHeader>"Header"</SidebarHeader> - <SidebarContent> - <SidebarGroup> - <SidebarGroupLabel>"Links"</SidebarGroupLabel> - <SidebarGroupContent> - <a href="#">{icon!(icondata::LuInfo)} <span>"link"</span></a> - </SidebarGroupContent> - </SidebarGroup> - </SidebarContent> - <SidebarSeparator /> - <SidebarFooter>"Footer"</SidebarFooter> - </Sidebar> - - <main> - <header class="bg-(--background) sticky inset-x-0 top-0 isolate flex shrink-0 - items-center gap-2 border-b z-10"> - <div class="flex h-14 w-full items-center justify-between gap-2 px-4"> - "Main header" - </div> - </header> - <div class="mx-2 sm:mx-12 my-8">"Main content"</div> - </main> - </SidebarProvider> - } -} diff --git a/docs/src/routes/components/sidebar/examples/sidebar_collapsible_sections.rs b/docs/src/routes/components/sidebar/examples/sidebar_collapsible_sections.rs new file mode 100644 index 00000000..b683caa9 --- /dev/null +++ b/docs/src/routes/components/sidebar/examples/sidebar_collapsible_sections.rs @@ -0,0 +1,321 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +fn VersionSwitcher(versions: StoredValue<Vec<String>>) -> impl IntoView { + let selected_version = RwSignal::new(0); + + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground" + > + <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> + {icon!(icondata::LuGalleryVerticalEnd, class="size-4")} + </div> + <div class="flex flex-col gap-0.5 leading-none"> + <span class="font-medium">"Documentation"</span> + <span> + "v" + {move || { + versions.get_value()[selected_version.get()].to_owned() + }} + </span> + </div> + {icon!(icondata::LuChevronsUpDown, class="ml-auto")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58" align="center" side="bottom"> + <For + each=move || versions.get_value() + key=|version| version.clone() + children=move |version: String| { + let index = versions + .get_value() + .iter() + .position(|v| v == &version) + .unwrap_or_default(); + + view! { + <DropdownMenuItem on:click=move |_| { + selected_version.set(index) + }> + "v"{version.clone()}" " + <Show when=move || { + version == versions.get_value()[selected_version.get()] + }>{icon!(icondata::LuCheck, class="ml-auto")}</Show> + </DropdownMenuItem> + } + } + /> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[component] +fn SearchForm() -> impl IntoView { + view! { + <form> + <SidebarGroup class="py-0"> + <SidebarGroupContent class="relative"> + <Label label_for="search" class="sr-only"> + "Search" + </Label> + <InputGroup> + <Input id="search" placeholder="Search the docs..." class="pl-8" /> + <InputGroupAddon> + {icon!( + icondata::LuSearch, class="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" + )} + </InputGroupAddon> + </InputGroup> + </SidebarGroupContent> + </SidebarGroup> + </form> + } +} + +#[derive(Clone)] +struct NavSubItem { + title: String, + url: String, +} + +#[derive(Clone)] +struct NavItem { + title: String, + items: Vec<NavSubItem>, +} + +#[component] +fn AppSidebar() -> impl IntoView { + let versions = StoredValue::new(vec![ + "1.0.1".to_string(), + "1.1.0-alpha".to_string(), + "2.0.0-beta1".to_string(), + ]); + + let nav_main = StoredValue::new(vec![ + NavItem { + title: "Getting Started".to_string(), + items: vec![ + NavSubItem { + title: "Installation".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Project Structure".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Build Your Application".to_string(), + items: vec![ + NavSubItem { + title: "Routing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Data Fetching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Rendering".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Caching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Styling".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Optimizing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Configuring".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Testing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Authentication".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Deploying".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Upgrading".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Examples".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "API Reference".to_string(), + items: vec![ + NavSubItem { + title: "Components".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "File Conventions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Functions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Leptos Options".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "CLI".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Edge Runtime".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Architecture".to_string(), + items: vec![ + NavSubItem { + title: "Accessibility".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Fast Refresh".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Cargo Leptos".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Supported Browsers".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Wasm Bindgen".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Community".to_string(), + items: vec![NavSubItem { + title: "Contribution Guide".to_string(), + url: "url".to_string(), + }], + }, + ]); + + view! { + <Sidebar> + <SidebarHeader> + <VersionSwitcher versions /> + <SearchForm /> + </SidebarHeader> + <SidebarContent> + <For each=move || nav_main.get_value() key=|item| item.title.clone() let(item)> + <Collapsible open=true title=item.title.clone()> + <SidebarGroup> + <CollapsibleTrigger> + <SidebarMenuButton> + <Button class="aria-expanded:[&_svg:last-child]:rotate-90"> + {item.title} " " + {icon!( + icondata::LuChevronRight, class="ml-auto transition-transform" + )} + </Button> + </SidebarMenuButton> + </CollapsibleTrigger> + <CollapsibleContent> + <SidebarGroupContent> + <SidebarMenu> + <For + each=move || item.items.to_owned() + key=|item| item.title.clone() + let(item) + > + <SidebarMenuItem> + <SidebarMenuButton> + <Link href={item.url}>{item.title}</Link> + </SidebarMenuButton> + </SidebarMenuItem> + </For> + </SidebarMenu> + </SidebarGroupContent> + </CollapsibleContent> + </SidebarGroup> + </Collapsible> + </For> + </SidebarContent> + <SidebarRail /> + </Sidebar> + } +} + +#[component] +pub fn SidebarCollapsibleSectionsExample() -> impl IntoView { + let is_mobile = RwSignal::new(false); + + view! { + <SidebarProvider is_mobile> + <AppSidebar /> + <SidebarInset> + <header class="flex h-16 shrink-0 items-center gap-2 border-b px-2"> + <SidebarTrigger /> + <Separator orientation="vertical" class="mr-2 h-4" /> + <Breadcrumb> + <BreadcrumbList> + <Show when=move || !is_mobile.get()> + <BreadcrumbItem> + <BreadcrumbLink> + <Link>"Build Your Application"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + </Show> + <BreadcrumbItem> + <BreadcrumbPage>"Data Fetching"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + </header> + <div class="flex flex-1 flex-col gap-4 p-4"> + <div class="grid auto-rows-min gap-4 md:grid-cols-3"> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + </div> + <Skeleton class="flex-1 md:min-h-min" /> + </div> + </SidebarInset> + </SidebarProvider> + } +} diff --git a/docs/src/routes/components/sidebar/examples/sidebar_collapsible_submenus.rs b/docs/src/routes/components/sidebar/examples/sidebar_collapsible_submenus.rs new file mode 100644 index 00000000..48c123de --- /dev/null +++ b/docs/src/routes/components/sidebar/examples/sidebar_collapsible_submenus.rs @@ -0,0 +1,320 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +fn VersionSwitcher(versions: StoredValue<Vec<String>>) -> impl IntoView { + let selected_version = RwSignal::new(0); + + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground" + > + <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> + {icon!(icondata::LuGalleryVerticalEnd, class="size-4")} + </div> + <div class="flex flex-col gap-0.5 leading-none"> + <span class="font-medium">"Documentation"</span> + <span> + "v" + {move || { + versions.get_value()[selected_version.get()].to_owned() + }} + </span> + </div> + {icon!(icondata::LuChevronsUpDown, class="ml-auto")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58" align="center" side="bottom"> + <For + each=move || versions.get_value() + key=|version| version.clone() + children=move |version: String| { + let index = versions + .get_value() + .iter() + .position(|v| v == &version) + .unwrap_or_default(); + + view! { + <DropdownMenuItem on:click=move |_| { + selected_version.set(index) + }> + "v"{version.clone()}" " + <Show when=move || { + version == versions.get_value()[selected_version.get()] + }>{icon!(icondata::LuCheck, class="ml-auto")}</Show> + </DropdownMenuItem> + } + } + /> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[derive(Clone)] +struct NavSubItem { + title: String, + url: String, +} + +#[derive(Clone)] +struct NavItem { + title: String, + items: Vec<NavSubItem>, + open: bool, +} + +#[component] +fn AppSidebar() -> impl IntoView { + let versions = StoredValue::new(vec![ + "1.0.1".to_string(), + "1.1.0-alpha".to_string(), + "2.0.0-beta1".to_string(), + ]); + + let nav_main = StoredValue::new(vec![ + NavItem { + title: "Getting Started".to_string(), + open: false, + items: vec![ + NavSubItem { + title: "Installation".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Project Structure".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Build Your Application".to_string(), + open: true, + items: vec![ + NavSubItem { + title: "Routing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Data Fetching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Rendering".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Caching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Styling".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Optimizing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Configuring".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Testing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Authentication".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Deploying".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Upgrading".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Examples".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "API Reference".to_string(), + open: false, + items: vec![ + NavSubItem { + title: "Components".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "File Conventions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Functions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Leptos Options".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "CLI".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Edge Runtime".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Architecture".to_string(), + open: false, + items: vec![ + NavSubItem { + title: "Accessibility".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Fast Refresh".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Cargo Leptos".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Supported Browsers".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Wasm Bindgen".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Community".to_string(), + open: false, + items: vec![NavSubItem { + title: "Contribution Guide".to_string(), + url: "url".to_string(), + }], + }, + ]); + + view! { + <Sidebar> + <SidebarHeader> + <VersionSwitcher versions /> + </SidebarHeader> + <SidebarContent> + <SidebarGroup> + <SidebarMenu> + <For + each=move || nav_main.get_value() + key=|item| item.title.clone() + children=|item: NavItem| { + let open = RwSignal::new(item.open); + + view! { + <Collapsible open> + <CollapsibleTrigger> + <SidebarMenuItem> + <SidebarMenuButton> + <Button> + {item.title} " " + <Show + when=move || { open.get() } + fallback=move || { + view! { {icon!(icondata::LuPlus, class="ml-auto")} } + } + > + {icon!(icondata::LuMinus, class="ml-auto")} + </Show> + </Button> + </SidebarMenuButton> + </SidebarMenuItem> + </CollapsibleTrigger> + <CollapsibleContent> + <SidebarMenuSub> + <For + each=move || item.items.to_owned() + key=|item| item.title.clone() + let(sub_item) + > + <SidebarMenuSubItem> + <SidebarMenuSubButton> + <Link href={sub_item.url}> + <span>{sub_item.title}</span> + </Link> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + </For> + </SidebarMenuSub> + </CollapsibleContent> + </Collapsible> + } + } + /> + </SidebarMenu> + </SidebarGroup> + </SidebarContent> + <SidebarRail /> + </Sidebar> + } +} + +#[component] +pub fn SidebarCollapsibleSubmenusExample() -> impl IntoView { + let is_mobile = RwSignal::new(false); + + view! { + <SidebarProvider is_mobile> + <AppSidebar /> + <SidebarInset> + <header class="flex h-16 shrink-0 items-center gap-2 border-b px-2"> + <SidebarTrigger /> + <Separator orientation="vertical" class="mr-2 h-4" /> + <Breadcrumb> + <BreadcrumbList> + <Show when=move || !is_mobile.get()> + <BreadcrumbItem> + <BreadcrumbLink> + <Link>"Build Your Application"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + </Show> + <BreadcrumbItem> + <BreadcrumbPage>"Data Fetching"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + </header> + <div class="flex flex-1 flex-col gap-4 p-4"> + <div class="grid auto-rows-min gap-4 md:grid-cols-3"> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + </div> + <Skeleton class="flex-1 md:min-h-min" /> + </div> + </SidebarInset> + </SidebarProvider> + } +} diff --git a/docs/src/routes/components/sidebar/examples/sidebar_floating_submenus.rs b/docs/src/routes/components/sidebar/examples/sidebar_floating_submenus.rs new file mode 100644 index 00000000..223566ad --- /dev/null +++ b/docs/src/routes/components/sidebar/examples/sidebar_floating_submenus.rs @@ -0,0 +1,294 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +fn VersionSwitcher(versions: StoredValue<Vec<String>>) -> impl IntoView { + let selected_version = RwSignal::new(0); + + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground" + > + <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> + {icon!(icondata::LuGalleryVerticalEnd, class="size-4")} + </div> + <div class="flex flex-col gap-0.5 leading-none"> + <span class="font-medium">"Documentation"</span> + <span> + "v" + {move || { + versions.get_value()[selected_version.get()].to_owned() + }} + </span> + </div> + {icon!(icondata::LuChevronsUpDown, class="ml-auto")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58" align="center" side="bottom"> + <For + each=move || versions.get_value() + key=|version| version.clone() + children=move |version: String| { + let index = versions + .get_value() + .iter() + .position(|v| v == &version) + .unwrap_or_default(); + + view! { + <DropdownMenuItem on:click=move |_| { + selected_version.set(index) + }> + "v"{version.clone()}" " + <Show when=move || { + version == versions.get_value()[selected_version.get()] + }>{icon!(icondata::LuCheck, class="ml-auto")}</Show> + </DropdownMenuItem> + } + } + /> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[derive(Clone)] +struct NavSubItem { + title: String, + url: String, +} + +#[derive(Clone)] +struct NavItem { + title: String, + items: Vec<NavSubItem>, +} + +#[component] +fn AppSidebar() -> impl IntoView { + let versions = StoredValue::new(vec![ + "1.0.1".to_string(), + "1.1.0-alpha".to_string(), + "2.0.0-beta1".to_string(), + ]); + + let nav_main = StoredValue::new(vec![ + NavItem { + title: "Getting Started".to_string(), + items: vec![ + NavSubItem { + title: "Installation".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Project Structure".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Build Your Application".to_string(), + items: vec![ + NavSubItem { + title: "Routing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Data Fetching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Rendering".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Caching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Styling".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Optimizing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Configuring".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Testing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Authentication".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Deploying".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Upgrading".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Examples".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "API Reference".to_string(), + items: vec![ + NavSubItem { + title: "Components".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "File Conventions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Functions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Leptos Options".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "CLI".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Edge Runtime".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Architecture".to_string(), + items: vec![ + NavSubItem { + title: "Accessibility".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Fast Refresh".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Cargo Leptos".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Supported Browsers".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Wasm Bindgen".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Community".to_string(), + items: vec![NavSubItem { + title: "Contribution Guide".to_string(), + url: "url".to_string(), + }], + }, + ]); + + view! { + <Sidebar variant="floating"> + <SidebarHeader> + <VersionSwitcher versions /> + </SidebarHeader> + <SidebarContent> + <SidebarGroup> + <SidebarMenu class="gap-2"> + <For + each=move || nav_main.get_value() + key=|item| item.title.clone() + let(item) + > + <SidebarMenuItem> + <SidebarMenuButton> + <Button class="font-medium">{item.title}</Button> + </SidebarMenuButton> + </SidebarMenuItem> + <SidebarMenuSub class="ml-0 border-l-0 px-1.5"> + <For + each=move || item.items.to_owned() + key=|item| item.title.clone() + let(sub_item) + > + <SidebarMenuSubItem> + <SidebarMenuSubButton> + <Link href={sub_item.url}> + <span>{sub_item.title}</span> + </Link> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + </For> + </SidebarMenuSub> + </For> + </SidebarMenu> + </SidebarGroup> + </SidebarContent> + <SidebarRail /> + </Sidebar> + } +} + +#[component] +pub fn SidebarFloatingSubmenusExample() -> impl IntoView { + let is_mobile = RwSignal::new(false); + + view! { + <SidebarProvider is_mobile> + <AppSidebar /> + <SidebarInset> + <header class="flex h-16 shrink-0 items-center gap-2 px-2"> + <SidebarTrigger /> + <Separator orientation="vertical" class="mr-2 h-4" /> + <Breadcrumb> + <BreadcrumbList> + <Show when=move || !is_mobile.get()> + <BreadcrumbItem> + <BreadcrumbLink> + <Link>"Build Your Application"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + </Show> + <BreadcrumbItem> + <BreadcrumbPage>"Data Fetching"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + </header> + <div class="flex flex-1 flex-col gap-4 p-4"> + <div class="grid auto-rows-min gap-4 md:grid-cols-3"> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + </div> + <Skeleton class="flex-1 md:min-h-min" /> + </div> + </SidebarInset> + </SidebarProvider> + } +} diff --git a/docs/src/routes/components/sidebar/examples/sidebar_icon.rs b/docs/src/routes/components/sidebar/examples/sidebar_icon.rs new file mode 100644 index 00000000..5a2026d8 --- /dev/null +++ b/docs/src/routes/components/sidebar/examples/sidebar_icon.rs @@ -0,0 +1,504 @@ +use leptos::prelude::*; +use singlestage::*; + +#[derive(Clone, PartialEq)] +struct Team { + name: String, + logo: icondata::Icon, + plan: String, +} + +#[component] +fn TeamSwitcher(teams: StoredValue<Vec<Team>>) -> impl IntoView { + let active_team = RwSignal::new(0); + + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-(--sidebar-accent) aria-expanded:text-(--sidebar-accent-foreground)" + > + {move || { + let active_team = &teams.get_value()[active_team.get()]; + + view! { + <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-(--sidebar-primary) text-(--sidebar-primary-foreground)"> + { + let logo = active_team.logo; + icon!(logo, class="size-4") + } + </div> + <div class="grid flex-1 text-left text-sm leading-tight"> + <span class="truncate font-medium"> + {active_team.name.to_owned()} + </span> + <span class="truncate text-xs"> + {active_team.plan.to_owned()} + </span> + </div> + } + }} + {icon!(icondata::LuChevronsUpDown, class="ml-auto")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58 rounded-lg" side="bottom" align="center"> + <DropdownMenuLabel class="text-xs text-muted-foreground"> + "Teams" + </DropdownMenuLabel> + <For + each=move || teams.get_value() + key=|team| team.name.clone() + children=move |team: Team| { + let index = teams + .get_value() + .iter() + .position(|t| t == &team) + .unwrap_or_default(); + + view! { + <DropdownMenuItem + class="gap-2 p-2" + on:click={move |_| { active_team.set(index) }} + > + + <div class="flex size-6 items-center justify-center rounded-md border"> + { + let logo = team.logo; + icon!(logo, class="size-3.5 shrink-0") + } + </div> + {team.name} + </DropdownMenuItem> + } + } + /> + <DropdownMenuSeparator /> + <DropdownMenuItem class="gap-2 p-2"> + <div class="flex size-6 items-center justify-center rounded-md border bg-transparent"> + {icon!(icondata::LuPlus, class="size-4")} + </div> + <div class="font-medium text-muted-foreground">"Add team"</div> + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[derive(Clone)] +struct NavSubItem { + title: String, + url: String, +} + +#[derive(Clone)] +struct NavItem { + title: String, + icon: icondata::Icon, + is_active: bool, + items: Vec<NavSubItem>, +} + +#[component] +fn NavMain(items: StoredValue<Vec<NavItem>>) -> impl IntoView { + view! { + <SidebarGroup> + <SidebarGroupLabel>"Platform"</SidebarGroupLabel> + <SidebarGroupContent> + <SidebarMenu> + <For + each=move || items.get_value() + key=|item| item.title.clone() + children=|item: NavItem| { + let tooltip = item.title.clone(); + view! { + <Collapsible open={item.is_active}> + <SidebarMenuItem> + <CollapsibleTrigger> + <SidebarMenuButton tooltip> + <Button class="aria-expanded:[&>svg:last-child]:rotate-90"> + { + let icon = item.icon; + icon!(icon) + } <span>{item.title}</span> + {icon!( + icondata::LuChevronRight, class="ml-auto transition-transform duration-200" + )} + </Button> + </SidebarMenuButton> + </CollapsibleTrigger> + <CollapsibleContent> + <SidebarMenuSub> + <For + each=move || item.items.to_owned() + key=|item| item.title.clone() + let(sub_item) + > + <SidebarMenuSubItem> + <SidebarMenuSubButton> + <Link href={sub_item.url}> + <span>{sub_item.title}</span> + </Link> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + </For> + </SidebarMenuSub> + </CollapsibleContent> + </SidebarMenuItem> + </Collapsible> + } + } + /> + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + } +} + +#[derive(Clone)] +struct Project { + name: String, + icon: icondata::Icon, + url: String, +} + +#[component] +fn NavProjects(projects: StoredValue<Vec<Project>>) -> impl IntoView { + let sidebar = expect_context::<SidebarContext>(); + + view! { + <Show when=move || sidebar.open.get()> + <SidebarGroup class="group-data-[collapsible=icon]:hidden"> + <SidebarGroupLabel>"Projects"</SidebarGroupLabel> + <SidebarMenu> + <For + each=move || projects.get_value() + key=|project| project.name.clone() + let(project) + > + <SidebarMenuItem> + <SidebarMenuButton> + <Link href={project + .url}> + { + let icon = project.icon; + icon!(icon) + } <span>{project.name}</span> + </Link> + </SidebarMenuButton> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuAction show_on_hover=true> + {icon!(icondata::FiMoreHorizontal)} + <span class="sr-only">"More"</span> + </SidebarMenuAction> + </DropdownMenuTrigger> + <DropdownMenuContent + class="min-w-58 rounded-lg" + align="end" + side="bottom" + > + <DropdownMenuItem> + {icon!(icondata::LuFolder, class="text-muted-foreground")} + <span>"View Project"</span> + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuForward, class="text-muted-foreground")} + <span>"Share Project"</span> + </DropdownMenuItem> + <DropdownMenuSeparator /> + <DropdownMenuItem> + {icon!(icondata::LuTrash2, class="text-(muted-foreground)")} + <span>"Delete Project"</span> + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </For> + <SidebarMenuItem> + <SidebarMenuButton> + <Button class="text-sidebar-foreground/70"> + {icon!(icondata::FiMoreHorizontal)} <span>"More"</span> + </Button> + </SidebarMenuButton> + </SidebarMenuItem> + </SidebarMenu> + </SidebarGroup> + </Show> + } +} + +#[derive(Clone)] +struct User { + name: String, + email: String, + avatar: String, +} + +#[component] +fn NavUser(user: StoredValue<User>) -> impl IntoView { + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground" + > + <Avatar class="h-8 w-8 rounded-lg"> + <AvatarImage + src={user.get_value().avatar} + alt={user.get_value().name} + /> + <AvatarFallback class="rounded-lg">"SD"</AvatarFallback> + </Avatar> + <div class="grid flex-1 text-left text-sm leading-tight"> + <span class="truncate font-medium"> + {user.get_value().name} + </span> + <span class="truncate text-xs">{user.get_value().email}</span> + </div> + {icon!(icondata::LuChevronsUpDown, class="ml-auto size-4")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58 rounded-lg" align="center" side="top"> + <DropdownMenuLabel class="p-0 font-normal"> + <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> + <Avatar class="h-8 w-8 rounded-lg"> + <AvatarImage + src={user.get_value().avatar} + alt={user.get_value().name} + /> + <AvatarFallback class="rounded-lg">"CN"</AvatarFallback> + </Avatar> + <div class="grid flex-1 text-left text-sm leading-tight"> + <span class="truncate font-medium"> + {user.get_value().name} + </span> + <span class="truncate text-xs">{user.get_value().email}</span> + </div> + </div> + </DropdownMenuLabel> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::LuSparkles)} "Upgrade to Pro" + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::LuBadgeCheck)} "Account" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuCreditCard)} "Billing" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuBell)} "Notifications" + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuItem>{icon!(icondata::LuLogOut)} "Log out"</DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[component] +fn AppSidebar() -> impl IntoView { + let user = StoredValue::new(User { + name: "Sofia Davis".to_string(), + email: "m@example.com".to_string(), + avatar: "/avatar-1.png".to_string(), + }); + + let teams = StoredValue::new(vec![ + Team { + name: "Acme Inc".to_string(), + logo: icondata::LuGalleryVerticalEnd, + plan: "Enterprise".to_string(), + }, + Team { + name: "Acme Corp.".to_string(), + logo: icondata::LuAudioWaveform, + plan: "Startup".to_string(), + }, + Team { + name: "Evil Corp.".to_string(), + logo: icondata::LuCommand, + plan: "Free".to_string(), + }, + ]); + + let items = StoredValue::new(vec![ + NavItem { + title: "Playground".to_string(), + icon: icondata::LuSquareTerminal, + is_active: true, + items: vec![ + NavSubItem { + title: "History".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Starred".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Settings".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Models".to_string(), + icon: icondata::LuBot, + is_active: false, + items: vec![ + NavSubItem { + title: "Genesis".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Explorer".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Quantum".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Documentation".to_string(), + icon: icondata::LuBookOpen, + is_active: false, + items: vec![ + NavSubItem { + title: "Introduction".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Get Started".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Tutorials".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Changelog".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Settings".to_string(), + icon: icondata::LuSettings2, + is_active: false, + items: vec![ + NavSubItem { + title: "General".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Team".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Billing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Limits".to_string(), + url: "url".to_string(), + }, + ], + }, + ]); + + let projects = StoredValue::new(vec![ + Project { + name: "Design Engineering".to_string(), + url: "url".to_string(), + icon: icondata::LuFrame, + }, + Project { + name: "Sales & Marketing".to_string(), + url: "url".to_string(), + icon: icondata::FiPieChart, + }, + Project { + name: "Travel".to_string(), + url: "url".to_string(), + icon: icondata::LuMap, + }, + ]); + + view! { + <Sidebar collapsible="icon"> + <SidebarHeader> + <TeamSwitcher teams /> + </SidebarHeader> + <SidebarContent> + <NavMain items /> + <NavProjects projects /> + </SidebarContent> + <SidebarFooter> + <NavUser user /> + </SidebarFooter> + <SidebarRail /> + </Sidebar> + } +} + +#[component] +pub fn SidebarIconExample() -> impl IntoView { + let is_mobile = RwSignal::new(false); + + view! { + <SidebarProvider is_mobile> + <AppSidebar /> + <SidebarInset> + <header class="flex h-16 shrink-0 items-center gap-2 border-b px-2"> + <SidebarTrigger /> + <Separator orientation="vertical" class="mr-2 h-4" /> + <Breadcrumb> + <BreadcrumbList> + <Show when=move || !is_mobile.get()> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="#">"Build Your Application"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + </Show> + <BreadcrumbItem> + <BreadcrumbPage>"Data Fetching"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + </header> + <div class="flex flex-1 flex-col gap-4 p-4"> + <div class="grid auto-rows-min gap-4 md:grid-cols-3"> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + </div> + <Skeleton class="flex-1 md:min-h-min" /> + </div> + </SidebarInset> + </SidebarProvider> + } +} diff --git a/docs/src/routes/components/sidebar/examples/sidebar_nested.rs b/docs/src/routes/components/sidebar/examples/sidebar_nested.rs new file mode 100644 index 00000000..b751ac39 --- /dev/null +++ b/docs/src/routes/components/sidebar/examples/sidebar_nested.rs @@ -0,0 +1,361 @@ +use leptos::prelude::*; +use singlestage::*; + +#[derive(Clone)] +struct User { + name: String, + email: String, + avatar: String, +} + +#[component] +fn NavUser(user: StoredValue<User>) -> impl IntoView { + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground" + > + <Avatar class="h-8 w-8 rounded-lg"> + <AvatarImage + src={user.get_value().avatar} + alt={user.get_value().name} + /> + <AvatarFallback class="rounded-lg">"SD"</AvatarFallback> + </Avatar> + <div class="grid flex-1 text-left text-sm leading-tight"> + <span class="truncate font-medium"> + {user.get_value().name} + </span> + <span class="truncate text-xs">{user.get_value().email}</span> + </div> + {icon!(icondata::LuChevronsUpDown, class="ml-auto size-4")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" side="right-top"> + <DropdownMenuLabel class="p-0 font-normal"> + <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> + <Avatar class="h-8 w-8 rounded-lg"> + <AvatarImage + src={user.get_value().avatar} + alt={user.get_value().name} + /> + <AvatarFallback class="rounded-lg">"CN"</AvatarFallback> + </Avatar> + <div class="grid flex-1 text-left text-sm leading-tight"> + <span class="truncate font-medium"> + {user.get_value().name} + </span> + <span class="truncate text-xs">{user.get_value().email}</span> + </div> + </div> + </DropdownMenuLabel> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::LuSparkles)} "Upgrade to Pro" + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::LuBadgeCheck)} "Account" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuCreditCard)} "Billing" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuBell)} "Notifications" + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuItem>{icon!(icondata::LuLogOut)} "Log out"</DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[derive(Clone)] +struct NavItem { + title: String, + icon: icondata::Icon, +} + +#[component] +fn OuterSidebar( + active: RwSignal<String>, + items: StoredValue<Vec<NavItem>>, + user: StoredValue<User>, +) -> impl IntoView { + view! { + <Sidebar collapsible="icon" default_open=false> + <SidebarHeader> + <SidebarMenu> + <SidebarMenuItem> + <SidebarMenuButton> + <Link size="lg" href="url" class="md:h-8 md:p-0"> + <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-(--sidebar-primary) text-(--sidebar-primary-foreground)"> + {icon!(icondata::LuCommand, class="size-4")} + </div> + </Link> + </SidebarMenuButton> + </SidebarMenuItem> + </SidebarMenu> + </SidebarHeader> + <SidebarContent> + <SidebarGroup> + <SidebarGroupContent class="px-1.5 md:px-0"> + <SidebarMenu> + <For + each=move || items.get_value() + key=|item| item.title.clone() + children=move |item: NavItem| { + let is_active = &item.title == &active.get(); + + view! { + <SidebarMenuItem> + <SidebarMenuButton tooltip=item.title> + <Button class=format!( + "{}", + if is_active { + "bg-(--sidebar-accent) text-(--sidebar-accent-foreground)" + } else { + "" + }, + )> + { + let icon = item.icon; + icon!(icon) + } + </Button> + </SidebarMenuButton> + </SidebarMenuItem> + } + } + /> + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + </SidebarContent> + <SidebarFooter> + <NavUser user /> + </SidebarFooter> + </Sidebar> + } +} + +#[derive(Clone)] +struct Mail { + name: String, + email: String, + subject: String, + date: String, + teaser: String, +} + +#[component] +fn InnerSidebar(active: RwSignal<String>, mails: StoredValue<Vec<Mail>>) -> impl IntoView { + view! { + <Sidebar class="rounded-none!"> + <SidebarHeader class="gap-3.5 border-b p-4"> + <div class="flex w-full items-center justify-between"> + <div class="text-base font-medium text-foreground">{move || active.get()}</div> + <Label class="flex items-center gap-2 text-sm"> + <span>"Unreads"</span> + <Switch class="shadow-none" /> + </Label> + </div> + <Input placeholder="Type to search..." /> + </SidebarHeader> + <SidebarContent> + <SidebarGroup class="px-0"> + <SidebarGroupContent> + <For + each=move || mails.get_value() + key=|mail| mail.subject.clone() + children=move |mail: Mail| { + let initials = format!( + "{}{}", + &mail.name[0..1], + &mail.name.split(' ').last().unwrap_or_default()[0..1], + ); + + view! { + <Item variant="default" size="xs"> + <ItemMedia class="mb-auto"> + <Avatar size="xs"> + <AvatarFallback>{initials}</AvatarFallback> + </Avatar> + </ItemMedia> + <ItemContent> + <ItemTitle class="w-full"> + <span class="text-xs">{mail.name}</span> + <span class="ml-auto text-xs text-(--muted-foreground)"> + {mail.date} + </span> + </ItemTitle> + <div class="font-medium text-xs">{mail.subject}</div> + <div class="line-clamp-2 text-xs whitespace-break-spaces pt-1"> + {mail.teaser} + </div> + </ItemContent> + </Item> + <Separator /> + } + } + /> + </SidebarGroupContent> + </SidebarGroup> + </SidebarContent> + </Sidebar> + } +} + +#[component] +pub fn SidebarNestedExample() -> impl IntoView { + let user = StoredValue::new(User { + name: "Sofia Davis".to_string(), + email: "m@example.com".to_string(), + avatar: "/avatar-1.png".to_string(), + }); + + let items = StoredValue::new(vec![ + NavItem { + title: "Inbox".to_string(), + icon: icondata::LuInbox, + }, + NavItem { + title: "Drafts".to_string(), + icon: icondata::LuFile, + }, + NavItem { + title: "Sent".to_string(), + icon: icondata::LuSend, + }, + NavItem { + title: "Junk".to_string(), + icon: icondata::LuArchiveX, + }, + NavItem { + title: "Trash".to_string(), + icon: icondata::LuTrash2, + }, + ]); + + let mails = StoredValue::new(vec![ + Mail { + name: "William Smith".to_string(), + email: "williamsmith@example.com".to_string(), + subject: "Meeting Tomorrow".to_string(), + date: "09:34 AM".to_string(), + teaser: "Hi team, just a reminder about our meeting tomorrow at 10 AM.\nPlease come prepared with your project updates.".to_string(), + }, + Mail { + name: "Alice Smith".to_string(), + email: "alicesmith@example.com".to_string(), + subject: "Re: Project Update".to_string(), + date: "Yesterday".to_string(), + teaser: "Thanks for the update. The progress looks great so far.\nLet's schedule a call to discuss the next steps.".to_string(), + }, + Mail { + name: "Bob Johnson".to_string(), + email: "bobjohnson@example.com".to_string(), + subject: "Weekend Plans".to_string(), + date: "2 days ago".to_string(), + teaser: "Hey everyone! I'm thinking of organizing a team outing this weekend.\nWould you be interested in a hiking trip or a beach day?".to_string(), + }, + Mail { + name: "Emily Davis".to_string(), + email: "emilydavis@example.com".to_string(), + subject: "Re: Question about Budget".to_string(), + date: "2 days ago".to_string(), + teaser: "I've reviewed the budget numbers you sent over.\nCan we set up a quick call to discuss some potential adjustments?".to_string(), + }, + Mail { + name: "Michael Wilson".to_string(), + email: "michaelwilson@example.com".to_string(), + subject: "Important Announcement".to_string(), + date: "1 week ago".to_string(), + teaser: "Please join us for an all-hands meeting this Friday at 3 PM.\nWe have some exciting news to share about the company's future.".to_string(), + }, + Mail { + name: "Sarah Brown".to_string(), + email: "sarahbrown@example.com".to_string(), + subject: "Re: Feedback on Proposal".to_string(), + date: "1 week ago".to_string(), + teaser: "Thank you for sending over the proposal. I've reviewed it and have some thoughts.\nCould we schedule a meeting to discuss my feedback in detail?".to_string(), + }, + Mail { + name: "David Lee".to_string(), + email: "davidlee@example.com".to_string(), + subject: "New Project Idea".to_string(), + date: "1 week ago".to_string(), + teaser: "I've been brainstorming and came up with an interesting project concept.\nDo you have time this week to discuss its potential impact and feasibility?".to_string(), + }, + Mail { + name: "Olivia Wilson".to_string(), + email: "oliviawilson@example.com".to_string(), + subject: "Vacation Plans".to_string(), + date: "1 week ago".to_string(), + teaser: "Just a heads up that I'll be taking a two-week vacation next month.\nI'll make sure all my projects are up to date before I leave.".to_string(), + }, + Mail { + name: "James Martin".to_string(), + email: "jamesmartin@example.com".to_string(), + subject: "Re: Conference Registration".to_string(), + date: "1 week ago".to_string(), + teaser: "I've completed the registration for the upcoming tech conference.\nLet me know if you need any additional information from my end.".to_string(), + }, + Mail { + name: "Sophia White".to_string(), + email: "sophiawhite@example.com".to_string(), + subject: "Team Dinner".to_string(), + date: "1 week ago".to_string(), + teaser: "To celebrate our recent project success, I'd like to organize a team dinner.\nAre you available next Friday evening? Please let me know your preferences.".to_string(), + }, + ]); + + let is_mobile = RwSignal::new(false); + let active = RwSignal::new(items.get_value()[0].title.clone()); + + view! { + <SidebarProvider is_mobile> + <OuterSidebar active items user /> + <SidebarProvider style="--sidebar-width: 300px"> + <InnerSidebar active mails /> + <SidebarInset> + <header class="sticky top-0 flex shrink-0 items-center gap-2 border-b bg-background p-2"> + <SidebarTrigger /> + <Separator orientation="vertical" class="mr-2 h-4" /> + <Breadcrumb> + <BreadcrumbList> + <Show when=move || !is_mobile.get()> + <BreadcrumbItem> + <BreadcrumbLink> + <Link>"All Inboxes"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + </Show> + <BreadcrumbItem> + <BreadcrumbPage>"Inbox"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + </header> + <div class="flex flex-1 flex-col gap-4 p-4"> + <Skeleton class="h-24" /> + <Skeleton class="flex-1 md:min-h-min" /> + </div> + </SidebarInset> + </SidebarProvider> + </SidebarProvider> + } +} diff --git a/docs/src/routes/components/sidebar/examples/sidebar_secondary.rs b/docs/src/routes/components/sidebar/examples/sidebar_secondary.rs new file mode 100644 index 00000000..591aac22 --- /dev/null +++ b/docs/src/routes/components/sidebar/examples/sidebar_secondary.rs @@ -0,0 +1,550 @@ +use leptos::prelude::*; +use singlestage::*; + +#[derive(Clone, PartialEq)] +struct Team { + name: String, + logo: icondata::Icon, + plan: String, +} + +#[component] +fn TeamSwitcher(teams: StoredValue<Vec<Team>>) -> impl IntoView { + let active_team = RwSignal::new(0); + + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-(--sidebar-accent) aria-expanded:text-(--sidebar-accent-foreground)" + > + {move || { + let active_team = &teams.get_value()[active_team.get()]; + + view! { + <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-(--sidebar-primary) text-(--sidebar-primary-foreground)"> + { + let logo = active_team.logo; + icon!(logo, class="size-4") + } + </div> + <div class="grid flex-1 text-left text-sm leading-tight"> + <span class="truncate font-medium"> + {active_team.name.to_owned()} + </span> + <span class="truncate text-xs"> + {active_team.plan.to_owned()} + </span> + </div> + } + }} + {icon!(icondata::LuChevronsUpDown, class="ml-auto")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58 rounded-lg" side="bottom" align="center"> + <DropdownMenuLabel class="text-xs text-muted-foreground"> + "Teams" + </DropdownMenuLabel> + <For + each=move || teams.get_value() + key=|team| team.name.clone() + children=move |team: Team| { + let index = teams + .get_value() + .iter() + .position(|t| t == &team) + .unwrap_or_default(); + + view! { + <DropdownMenuItem + class="gap-2 p-2" + on:click={move |_| { active_team.set(index) }} + > + + <div class="flex size-6 items-center justify-center rounded-md border"> + { + let logo = team.logo; + icon!(logo, class="size-3.5 shrink-0") + } + </div> + {team.name} + </DropdownMenuItem> + } + } + /> + <DropdownMenuSeparator /> + <DropdownMenuItem class="gap-2 p-2"> + <div class="flex size-6 items-center justify-center rounded-md border bg-transparent"> + {icon!(icondata::LuPlus, class="size-4")} + </div> + <div class="font-medium text-muted-foreground">"Add team"</div> + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[derive(Clone)] +struct NavSubItem { + title: String, + url: String, +} + +#[derive(Clone)] +struct NavItem { + title: String, + icon: icondata::Icon, + is_active: bool, + items: Vec<NavSubItem>, +} + +#[component] +fn NavMain(items: StoredValue<Vec<NavItem>>) -> impl IntoView { + view! { + <SidebarGroup> + <SidebarGroupLabel>"Platform"</SidebarGroupLabel> + <SidebarGroupContent> + <SidebarMenu> + <For + each=move || items.get_value() + key=|item| item.title.clone() + children=|item: NavItem| { + let tooltip = item.title.clone(); + view! { + <Collapsible open={item.is_active}> + <SidebarMenuItem> + <CollapsibleTrigger> + <SidebarMenuButton tooltip> + <Button class="aria-expanded:[&>svg:last-child]:rotate-90"> + { + let icon = item.icon; + icon!(icon) + } <span>{item.title}</span> + {icon!( + icondata::LuChevronRight, class="ml-auto transition-transform duration-200" + )} + </Button> + </SidebarMenuButton> + </CollapsibleTrigger> + <CollapsibleContent> + <SidebarMenuSub> + <For + each=move || item.items.to_owned() + key=|item| item.title.clone() + let(sub_item) + > + <SidebarMenuSubItem> + <SidebarMenuSubButton> + <Link href={sub_item.url}> + <span>{sub_item.title}</span> + </Link> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + </For> + </SidebarMenuSub> + </CollapsibleContent> + </SidebarMenuItem> + </Collapsible> + } + } + /> + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + } +} + +#[derive(Clone)] +struct Project { + name: String, + icon: icondata::Icon, + url: String, +} + +#[component] +fn NavProjects(projects: StoredValue<Vec<Project>>) -> impl IntoView { + let sidebar = expect_context::<SidebarContext>(); + + view! { + <Show when=move || sidebar.open.get()> + <SidebarGroup class="group-data-[collapsible=icon]:hidden"> + <SidebarGroupLabel>"Projects"</SidebarGroupLabel> + <SidebarMenu> + <For + each=move || projects.get_value() + key=|project| project.name.clone() + let(project) + > + <SidebarMenuItem> + <SidebarMenuButton> + <Link href={project + .url}> + { + let icon = project.icon; + icon!(icon) + } <span>{project.name}</span> + </Link> + </SidebarMenuButton> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuAction show_on_hover=true> + {icon!(icondata::FiMoreHorizontal)} + <span class="sr-only">"More"</span> + </SidebarMenuAction> + </DropdownMenuTrigger> + <DropdownMenuContent + class="min-w-58 rounded-lg" + align="end" + side="bottom" + > + <DropdownMenuItem> + {icon!(icondata::LuFolder, class="text-muted-foreground")} + <span>"View Project"</span> + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuForward, class="text-muted-foreground")} + <span>"Share Project"</span> + </DropdownMenuItem> + <DropdownMenuSeparator /> + <DropdownMenuItem> + {icon!(icondata::LuTrash2, class="text-(muted-foreground)")} + <span>"Delete Project"</span> + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </For> + <SidebarMenuItem> + <SidebarMenuButton> + <Button class="text-sidebar-foreground/70"> + {icon!(icondata::FiMoreHorizontal)} <span>"More"</span> + </Button> + </SidebarMenuButton> + </SidebarMenuItem> + </SidebarMenu> + </SidebarGroup> + </Show> + } +} + +#[derive(Clone)] +struct NavSecondary { + title: String, + url: String, + icon: icondata::Icon, +} + +#[component] +fn NavSecondary(items: StoredValue<Vec<NavSecondary>>) -> impl IntoView { + view! { + <SidebarGroup class="mt-auto"> + <SidebarGroupContent> + <SidebarMenu> + <For each=move || items.get_value() key=|item| item.title.clone() let(item)> + <SidebarMenuItem> + <SidebarMenuButton> + <Link size="sm" href=item.url> + { + let icon = item.icon; + icon!(icon) + } + <span>{item.title}</span> + </Link> + </SidebarMenuButton> + </SidebarMenuItem> + </For> + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + } +} + +#[derive(Clone)] +struct User { + name: String, + email: String, + avatar: String, +} + +#[component] +fn NavUser(user: StoredValue<User>) -> impl IntoView { + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground" + > + <Avatar class="h-8 w-8 rounded-lg"> + <AvatarImage + src={user.get_value().avatar} + alt={user.get_value().name} + /> + <AvatarFallback class="rounded-lg">"SD"</AvatarFallback> + </Avatar> + <div class="grid flex-1 text-left text-sm leading-tight"> + <span class="truncate font-medium"> + {user.get_value().name} + </span> + <span class="truncate text-xs">{user.get_value().email}</span> + </div> + {icon!(icondata::LuChevronsUpDown, class="ml-auto size-4")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58 rounded-lg" align="center" side="top"> + <DropdownMenuLabel class="p-0 font-normal"> + <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> + <Avatar class="h-8 w-8 rounded-lg"> + <AvatarImage + src={user.get_value().avatar} + alt={user.get_value().name} + /> + <AvatarFallback class="rounded-lg">"CN"</AvatarFallback> + </Avatar> + <div class="grid flex-1 text-left text-sm leading-tight"> + <span class="truncate font-medium"> + {user.get_value().name} + </span> + <span class="truncate text-xs">{user.get_value().email}</span> + </div> + </div> + </DropdownMenuLabel> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::LuSparkles)} "Upgrade to Pro" + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem> + {icon!(icondata::LuBadgeCheck)} "Account" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuCreditCard)} "Billing" + </DropdownMenuItem> + <DropdownMenuItem> + {icon!(icondata::LuBell)} "Notifications" + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuItem>{icon!(icondata::LuLogOut)} "Log out"</DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[component] +fn AppSidebar() -> impl IntoView { + let user = StoredValue::new(User { + name: "Sofia Davis".to_string(), + email: "m@example.com".to_string(), + avatar: "/avatar-1.png".to_string(), + }); + + let teams = StoredValue::new(vec![ + Team { + name: "Acme Inc".to_string(), + logo: icondata::LuGalleryVerticalEnd, + plan: "Enterprise".to_string(), + }, + Team { + name: "Acme Corp.".to_string(), + logo: icondata::LuAudioWaveform, + plan: "Startup".to_string(), + }, + Team { + name: "Evil Corp.".to_string(), + logo: icondata::LuCommand, + plan: "Free".to_string(), + }, + ]); + + let items = StoredValue::new(vec![ + NavItem { + title: "Playground".to_string(), + icon: icondata::LuSquareTerminal, + is_active: true, + items: vec![ + NavSubItem { + title: "History".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Starred".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Settings".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Models".to_string(), + icon: icondata::LuBot, + is_active: false, + items: vec![ + NavSubItem { + title: "Genesis".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Explorer".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Quantum".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Documentation".to_string(), + icon: icondata::LuBookOpen, + is_active: false, + items: vec![ + NavSubItem { + title: "Introduction".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Get Started".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Tutorials".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Changelog".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Settings".to_string(), + icon: icondata::LuSettings2, + is_active: false, + items: vec![ + NavSubItem { + title: "General".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Team".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Billing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Limits".to_string(), + url: "url".to_string(), + }, + ], + }, + ]); + + let projects = StoredValue::new(vec![ + Project { + name: "Design Engineering".to_string(), + url: "url".to_string(), + icon: icondata::LuFrame, + }, + Project { + name: "Sales & Marketing".to_string(), + url: "url".to_string(), + icon: icondata::FiPieChart, + }, + Project { + name: "Travel".to_string(), + url: "url".to_string(), + icon: icondata::LuMap, + }, + ]); + + let secondary = StoredValue::new(vec![ + NavSecondary { + title: "Support".to_string(), + url: "url".to_string(), + icon: icondata::LuLifeBuoy, + }, + NavSecondary { + title: "Feedback".to_string(), + url: "url".to_string(), + icon: icondata::LuSend, + }, + ]); + + view! { + <Sidebar variant="inset"> + <SidebarHeader> + <TeamSwitcher teams /> + </SidebarHeader> + <SidebarContent> + <NavMain items /> + <NavProjects projects /> + <NavSecondary items=secondary /> + </SidebarContent> + <SidebarFooter> + <NavUser user /> + </SidebarFooter> + <SidebarRail /> + </Sidebar> + } +} + +#[component] +pub fn SidebarSecondaryExample() -> impl IntoView { + let is_mobile = RwSignal::new(false); + + view! { + <SidebarProvider is_mobile> + <AppSidebar /> + <SidebarInset> + <header class="flex h-16 shrink-0 items-center gap-2 border-b px-2"> + <SidebarTrigger /> + <Separator orientation="vertical" class="mr-2 h-4" /> + <Breadcrumb> + <BreadcrumbList> + <Show when=move || !is_mobile.get()> + <BreadcrumbItem> + <BreadcrumbLink> + <Link href="#">"Build Your Application"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + </Show> + <BreadcrumbItem> + <BreadcrumbPage>"Data Fetching"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + </header> + <div class="flex flex-1 flex-col gap-4 p-4"> + <div class="grid auto-rows-min gap-4 md:grid-cols-3"> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + </div> + <Skeleton class="flex-1 md:min-h-min" /> + </div> + </SidebarInset> + </SidebarProvider> + } +} diff --git a/docs/src/routes/components/sidebar/examples/sidebar_sections.rs b/docs/src/routes/components/sidebar/examples/sidebar_sections.rs new file mode 100644 index 00000000..7eca6658 --- /dev/null +++ b/docs/src/routes/components/sidebar/examples/sidebar_sections.rs @@ -0,0 +1,301 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +fn VersionSwitcher(versions: StoredValue<Vec<String>>) -> impl IntoView { + let selected_version = RwSignal::new(0); + + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground" + > + <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> + {icon!(icondata::LuGalleryVerticalEnd, class="size-4")} + </div> + <div class="flex flex-col gap-0.5 leading-none"> + <span class="font-medium">"Documentation"</span> + <span> + "v" + {move || { + versions.get_value()[selected_version.get()].to_owned() + }} + </span> + </div> + {icon!(icondata::LuChevronsUpDown, class="ml-auto")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58" align="center" side="bottom"> + <For + each=move || versions.get_value() + key=|version| version.clone() + children=move |version: String| { + let index = versions + .get_value() + .iter() + .position(|v| v == &version) + .unwrap_or_default(); + + view! { + <DropdownMenuItem on:click=move |_| { + selected_version.set(index) + }> + "v"{version.clone()}" " + <Show when=move || { + version == versions.get_value()[selected_version.get()] + }>{icon!(icondata::LuCheck, class="ml-auto")}</Show> + </DropdownMenuItem> + } + } + /> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[component] +fn SearchForm() -> impl IntoView { + view! { + <form> + <SidebarGroup class="py-0"> + <SidebarGroupContent class="relative"> + <Label label_for="search" class="sr-only"> + "Search" + </Label> + <InputGroup> + <Input id="search" placeholder="Search the docs..." class="pl-8" /> + <InputGroupAddon> + {icon!( + icondata::LuSearch, class="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" + )} + </InputGroupAddon> + </InputGroup> + </SidebarGroupContent> + </SidebarGroup> + </form> + } +} + +#[derive(Clone)] +struct NavSubItem { + title: String, + url: String, +} + +#[derive(Clone)] +struct NavItem { + title: String, + items: Vec<NavSubItem>, +} + +#[component] +fn AppSidebar() -> impl IntoView { + let versions = StoredValue::new(vec![ + "1.0.1".to_string(), + "1.1.0-alpha".to_string(), + "2.0.0-beta1".to_string(), + ]); + + let nav_main = StoredValue::new(vec![ + NavItem { + title: "Getting Started".to_string(), + items: vec![ + NavSubItem { + title: "Installation".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Project Structure".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Build Your Application".to_string(), + items: vec![ + NavSubItem { + title: "Routing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Data Fetching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Rendering".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Caching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Styling".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Optimizing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Configuring".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Testing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Authentication".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Deploying".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Upgrading".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Examples".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "API Reference".to_string(), + items: vec![ + NavSubItem { + title: "Components".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "File Conventions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Functions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Leptos Options".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "CLI".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Edge Runtime".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Architecture".to_string(), + items: vec![ + NavSubItem { + title: "Accessibility".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Fast Refresh".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Cargo Leptos".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Supported Browsers".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Wasm Bindgen".to_string(), + url: "url".to_string(), + }, + ], + }, + ]); + + view! { + <Sidebar> + <SidebarHeader> + <VersionSwitcher versions /> + <SearchForm /> + </SidebarHeader> + <SidebarContent> + <For each=move || nav_main.get_value() key=|item| item.title.clone() let(item)> + <SidebarGroup> + <SidebarGroupLabel>{item.title}</SidebarGroupLabel> + <SidebarGroupContent> + <SidebarMenu> + <For + each=move || item.items.to_owned() + key=|item| item.title.clone() + let(item) + > + <SidebarMenuItem> + <SidebarMenuButton> + <Link href={item.url}>{item.title}</Link> + </SidebarMenuButton> + </SidebarMenuItem> + </For> + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + </For> + </SidebarContent> + <SidebarRail /> + </Sidebar> + } +} + +#[component] +pub fn SidebarSectionsExample() -> impl IntoView { + let is_mobile = RwSignal::new(false); + + view! { + <SidebarProvider is_mobile> + <AppSidebar /> + <SidebarInset> + <header class="flex h-16 shrink-0 items-center gap-2 border-b px-2"> + <SidebarTrigger /> + <Separator orientation="vertical" class="mr-2 h-4" /> + <Breadcrumb> + <BreadcrumbList> + <Show when=move || !is_mobile.get()> + <BreadcrumbItem> + <BreadcrumbLink> + <Link>"Build Your Application"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + </Show> + <BreadcrumbItem> + <BreadcrumbPage>"Data Fetching"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + </header> + <div class="flex flex-1 flex-col gap-4 p-4"> + <div class="grid auto-rows-min gap-4 md:grid-cols-3"> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + </div> + <Skeleton class="flex-1 rounded-xl md:min-h-min" /> + </div> + </SidebarInset> + </SidebarProvider> + } +} diff --git a/docs/src/routes/components/sidebar/examples/sidebar_submenu_dropdown.rs b/docs/src/routes/components/sidebar/examples/sidebar_submenu_dropdown.rs new file mode 100644 index 00000000..c1afe15e --- /dev/null +++ b/docs/src/routes/components/sidebar/examples/sidebar_submenu_dropdown.rs @@ -0,0 +1,322 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +fn VersionSwitcher(versions: StoredValue<Vec<String>>) -> impl IntoView { + let selected_version = RwSignal::new(0); + + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground" + > + <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> + {icon!(icondata::LuGalleryVerticalEnd, class="size-4")} + </div> + <div class="flex flex-col gap-0.5 leading-none"> + <span class="font-medium">"Documentation"</span> + <span> + "v" + {move || { + versions.get_value()[selected_version.get()].to_owned() + }} + </span> + </div> + {icon!(icondata::LuChevronsUpDown, class="ml-auto")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58" align="center" side="bottom"> + <For + each=move || versions.get_value() + key=|version| version.clone() + children=move |version: String| { + let index = versions + .get_value() + .iter() + .position(|v| v == &version) + .unwrap_or_default(); + + view! { + <DropdownMenuItem on:click=move |_| { + selected_version.set(index) + }> + "v"{version.clone()}" " + <Show when=move || { + version == versions.get_value()[selected_version.get()] + }>{icon!(icondata::LuCheck, class="ml-auto")}</Show> + </DropdownMenuItem> + } + } + /> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[component] +fn SidebarOptInForm() -> impl IntoView { + view! { + <Card class="gap-2 py-4 shadow-none"> + <CardHeader class="px-4"> + <CardTitle class="text-sm">"Subscribe to our newsletter"</CardTitle> + <CardDescription> + "Opt-in to receive updates and news about the sidebar." + </CardDescription> + </CardHeader> + <CardContent class="px-4"> + <form> + <div class="grid gap-2.5"> + <Input input_type="email" placeholder="Email" /> + <Button + class="w-full bg-sidebar-primary text-sidebar-primary-foreground shadow-none" + size="sm" + > + "Subscribe" + </Button> + </div> + </form> + </CardContent> + </Card> + } +} + +#[derive(Clone)] +struct NavSubItem { + title: String, + url: String, +} + +#[derive(Clone)] +struct NavItem { + title: String, + items: Vec<NavSubItem>, +} + +#[component] +fn AppSidebar() -> impl IntoView { + let versions = StoredValue::new(vec![ + "1.0.1".to_string(), + "1.1.0-alpha".to_string(), + "2.0.0-beta1".to_string(), + ]); + + let nav_main = StoredValue::new(vec![ + NavItem { + title: "Getting Started".to_string(), + items: vec![ + NavSubItem { + title: "Installation".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Project Structure".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Build Your Application".to_string(), + items: vec![ + NavSubItem { + title: "Routing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Data Fetching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Rendering".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Caching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Styling".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Optimizing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Configuring".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Testing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Authentication".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Deploying".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Upgrading".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Examples".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "API Reference".to_string(), + items: vec![ + NavSubItem { + title: "Components".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "File Conventions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Functions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Leptos Options".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "CLI".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Edge Runtime".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Architecture".to_string(), + items: vec![ + NavSubItem { + title: "Accessibility".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Fast Refresh".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Cargo Leptos".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Supported Browsers".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Wasm Bindgen".to_string(), + url: "url".to_string(), + }, + ], + }, + ]); + + view! { + <Sidebar> + <SidebarHeader> + <VersionSwitcher versions /> + </SidebarHeader> + <SidebarContent> + <SidebarGroup> + <SidebarMenu> + <For + each=move || nav_main.get_value() + key=|item| item.title.clone() + let(item) + > + <DropdownMenu> + <SidebarMenuItem> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button> + {item.title} " " + {icon!(icondata::FiMoreHorizontal, class="ml-auto")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent align="start" side="right"> + <For + each=move || item.items.to_owned() + key=|item| item.title.clone() + let(item) + > + <DropdownMenuItem as_child=true> + <Link href={item.url}>{item.title}</Link> + </DropdownMenuItem> + </For> + </DropdownMenuContent> + </SidebarMenuItem> + </DropdownMenu> + </For> + </SidebarMenu> + </SidebarGroup> + </SidebarContent> + <SidebarFooter> + <div class="p-1"> + <SidebarOptInForm /> + </div> + </SidebarFooter> + <SidebarRail /> + </Sidebar> + } +} + +#[component] +pub fn SidebarSubmenuDropdownExample() -> impl IntoView { + let is_mobile = RwSignal::new(false); + + view! { + <SidebarProvider is_mobile> + <AppSidebar /> + <SidebarInset> + <header class="flex h-16 shrink-0 items-center gap-2 border-b px-2"> + <SidebarTrigger /> + <Separator orientation="vertical" class="mr-2 h-4" /> + <Breadcrumb> + <BreadcrumbList> + <Show when=move || !is_mobile.get()> + <BreadcrumbItem> + <BreadcrumbLink> + <Link>"Build Your Application"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + </Show> + <BreadcrumbItem> + <BreadcrumbPage>"Data Fetching"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + </header> + <div class="flex flex-1 flex-col gap-4 p-4"> + <div class="grid auto-rows-min gap-4 md:grid-cols-3"> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + </div> + <Skeleton class="flex-1 md:min-h-min" /> + </div> + </SidebarInset> + </SidebarProvider> + } +} diff --git a/docs/src/routes/components/sidebar/examples/sidebar_submenus.rs b/docs/src/routes/components/sidebar/examples/sidebar_submenus.rs new file mode 100644 index 00000000..9a9596d4 --- /dev/null +++ b/docs/src/routes/components/sidebar/examples/sidebar_submenus.rs @@ -0,0 +1,294 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +fn VersionSwitcher(versions: StoredValue<Vec<String>>) -> impl IntoView { + let selected_version = RwSignal::new(0); + + view! { + <SidebarMenu> + <SidebarMenuItem> + <DropdownMenu> + <DropdownMenuTrigger> + <SidebarMenuButton> + <Button + size="lg" + class="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground" + > + <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> + {icon!(icondata::LuGalleryVerticalEnd, class="size-4")} + </div> + <div class="flex flex-col gap-0.5 leading-none"> + <span class="font-medium">"Documentation"</span> + <span> + "v" + {move || { + versions.get_value()[selected_version.get()].to_owned() + }} + </span> + </div> + {icon!(icondata::LuChevronsUpDown, class="ml-auto")} + </Button> + </SidebarMenuButton> + </DropdownMenuTrigger> + <DropdownMenuContent class="min-w-58" align="center" side="bottom"> + <For + each=move || versions.get_value() + key=|version| version.clone() + children=move |version: String| { + let index = versions + .get_value() + .iter() + .position(|v| v == &version) + .unwrap_or_default(); + + view! { + <DropdownMenuItem on:click=move |_| { + selected_version.set(index) + }> + "v"{version.clone()}" " + <Show when=move || { + version == versions.get_value()[selected_version.get()] + }>{icon!(icondata::LuCheck, class="ml-auto")}</Show> + </DropdownMenuItem> + } + } + /> + </DropdownMenuContent> + </DropdownMenu> + </SidebarMenuItem> + </SidebarMenu> + } +} + +#[derive(Clone)] +struct NavSubItem { + title: String, + url: String, +} + +#[derive(Clone)] +struct NavItem { + title: String, + items: Vec<NavSubItem>, +} + +#[component] +fn AppSidebar() -> impl IntoView { + let versions = StoredValue::new(vec![ + "1.0.1".to_string(), + "1.1.0-alpha".to_string(), + "2.0.0-beta1".to_string(), + ]); + + let nav_main = StoredValue::new(vec![ + NavItem { + title: "Getting Started".to_string(), + items: vec![ + NavSubItem { + title: "Installation".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Project Structure".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Build Your Application".to_string(), + items: vec![ + NavSubItem { + title: "Routing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Data Fetching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Rendering".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Caching".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Styling".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Optimizing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Configuring".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Testing".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Authentication".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Deploying".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Upgrading".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Examples".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "API Reference".to_string(), + items: vec![ + NavSubItem { + title: "Components".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "File Conventions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Functions".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Leptos Options".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "CLI".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Edge Runtime".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Architecture".to_string(), + items: vec![ + NavSubItem { + title: "Accessibility".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Fast Refresh".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Cargo Leptos".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Supported Browsers".to_string(), + url: "url".to_string(), + }, + NavSubItem { + title: "Wasm Bindgen".to_string(), + url: "url".to_string(), + }, + ], + }, + NavItem { + title: "Community".to_string(), + items: vec![NavSubItem { + title: "Contribution Guide".to_string(), + url: "url".to_string(), + }], + }, + ]); + + view! { + <Sidebar> + <SidebarHeader> + <VersionSwitcher versions /> + </SidebarHeader> + <SidebarContent> + <SidebarGroup> + <SidebarMenu> + <For + each=move || nav_main.get_value() + key=|item| item.title.clone() + let(item) + > + <SidebarMenuItem> + <SidebarMenuButton> + <Button>{item.title}</Button> + </SidebarMenuButton> + </SidebarMenuItem> + <SidebarMenuSub> + <For + each=move || item.items.to_owned() + key=|item| item.title.clone() + let(sub_item) + > + <SidebarMenuSubItem> + <SidebarMenuSubButton> + <Link href={sub_item.url}> + <span>{sub_item.title}</span> + </Link> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + </For> + </SidebarMenuSub> + </For> + </SidebarMenu> + </SidebarGroup> + </SidebarContent> + <SidebarRail /> + </Sidebar> + } +} + +#[component] +pub fn SidebarSubmenusExample() -> impl IntoView { + let is_mobile = RwSignal::new(false); + + view! { + <SidebarProvider is_mobile> + <AppSidebar /> + <SidebarInset> + <header class="flex h-16 shrink-0 items-center gap-2 border-b px-2"> + <SidebarTrigger /> + <Separator orientation="vertical" class="mr-2 h-4" /> + <Breadcrumb> + <BreadcrumbList> + <Show when=move || !is_mobile.get()> + <BreadcrumbItem> + <BreadcrumbLink> + <Link>"Build Your Application"</Link> + </BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + </Show> + <BreadcrumbItem> + <BreadcrumbPage>"Data Fetching"</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + </header> + <div class="flex flex-1 flex-col gap-4 p-4"> + <div class="grid auto-rows-min gap-4 md:grid-cols-3"> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + <Skeleton class="aspect-video" /> + </div> + <Skeleton class="flex-1 md:min-h-min" /> + </div> + </SidebarInset> + </SidebarProvider> + } +} diff --git a/docs/src/routes/components/sidebar/mod.rs b/docs/src/routes/components/sidebar/mod.rs index e381f2ff..c7ceb8b6 100644 --- a/docs/src/routes/components/sidebar/mod.rs +++ b/docs/src/routes/components/sidebar/mod.rs @@ -1,2 +1,3 @@ -// mod examples; -// pub use examples::*; +mod examples; + +pub use examples::*; diff --git a/docs/src/routes/components/skeleton/component.toml b/docs/src/routes/components/skeleton/component.toml index d5a25b96..bd3b3905 100644 --- a/docs/src/routes/components/skeleton/component.toml +++ b/docs/src/routes/components/skeleton/component.toml @@ -4,10 +4,26 @@ description = "Use to show a placeholder while content is loading." [[examples]] name = "skeleton" +[[examples]] +name = "skeleton avatar" +title = "Avatar" + [[examples]] name = "skeleton card" title = "Card" +[[examples]] +name = "skeleton text" +title = "Text" + +[[examples]] +name = "skeleton form" +title = "Form" + +[[examples]] +name = "skeleton table" +title = "Table" + [[references]] name = "Skeleton" description = "Creates a skeleton used to show a placeholder while content is loading." diff --git a/docs/src/routes/components/skeleton/examples/mod.rs b/docs/src/routes/components/skeleton/examples/mod.rs index bd93d7c1..3bd1a8ba 100644 --- a/docs/src/routes/components/skeleton/examples/mod.rs +++ b/docs/src/routes/components/skeleton/examples/mod.rs @@ -1,5 +1,13 @@ mod skeleton; +mod skeleton_avatar; mod skeleton_card; +mod skeleton_form; +mod skeleton_table; +mod skeleton_text; pub use skeleton::*; +pub use skeleton_avatar::*; pub use skeleton_card::*; +pub use skeleton_form::*; +pub use skeleton_table::*; +pub use skeleton_text::*; diff --git a/docs/src/routes/components/skeleton/examples/skeleton.rs b/docs/src/routes/components/skeleton/examples/skeleton.rs index fc955521..8b622a29 100644 --- a/docs/src/routes/components/skeleton/examples/skeleton.rs +++ b/docs/src/routes/components/skeleton/examples/skeleton.rs @@ -4,11 +4,11 @@ use singlestage::*; #[component] pub fn SkeletonExample() -> impl IntoView { view! { - <div class="flex items-center space-x-4"> + <div class="flex items-center gap-4"> <Skeleton class="h-12 w-12 rounded-full" /> <div class="space-y-2"> - <Skeleton class="h-4 w-[250px] rounded-md" /> - <Skeleton class="h-4 w-[200px] rounded-md" /> + <Skeleton class="h-4 w-[250px]" /> + <Skeleton class="h-4 w-[200px]" /> </div> </div> } diff --git a/docs/src/routes/components/skeleton/examples/skeleton_avatar.rs b/docs/src/routes/components/skeleton/examples/skeleton_avatar.rs new file mode 100644 index 00000000..841c8deb --- /dev/null +++ b/docs/src/routes/components/skeleton/examples/skeleton_avatar.rs @@ -0,0 +1,15 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SkeletonAvatarExample() -> impl IntoView { + view! { + <div class="flex w-fit items-center gap-4"> + <Skeleton class="size-10 shrink-0 rounded-full" /> + <div class="grid gap-2"> + <Skeleton class="h-4 w-[150px]" /> + <Skeleton class="h-4 w-[100px]" /> + </div> + </div> + } +} diff --git a/docs/src/routes/components/skeleton/examples/skeleton_card.rs b/docs/src/routes/components/skeleton/examples/skeleton_card.rs index 437fefc0..ffaa6666 100644 --- a/docs/src/routes/components/skeleton/examples/skeleton_card.rs +++ b/docs/src/routes/components/skeleton/examples/skeleton_card.rs @@ -4,12 +4,14 @@ use singlestage::*; #[component] pub fn SkeletonCardExample() -> impl IntoView { view! { - <div class="flex flex-col space-y-3"> - <Skeleton class="h-[125px] w-[250px] rounded-xl" /> - <div class="space-y-2"> - <Skeleton class="h-4 w-[250px] rounded-md" /> - <Skeleton class="h-4 w-[200px] rounded-md" /> - </div> - </div> + <Card class="w-full max-w-xs"> + <CardHeader> + <Skeleton class="h-4 w-2/3" /> + <Skeleton class="h-4 w-1/2" /> + </CardHeader> + <CardContent> + <Skeleton class="aspect-video w-full" /> + </CardContent> + </Card> } } diff --git a/docs/src/routes/components/skeleton/examples/skeleton_form.rs b/docs/src/routes/components/skeleton/examples/skeleton_form.rs new file mode 100644 index 00000000..51d68e1f --- /dev/null +++ b/docs/src/routes/components/skeleton/examples/skeleton_form.rs @@ -0,0 +1,19 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SkeletonFormExample() -> impl IntoView { + view! { + <div class="flex w-full max-w-xs flex-col gap-7"> + <div class="flex flex-col gap-3"> + <Skeleton class="h-4 w-20" /> + <Skeleton class="h-8 w-full" /> + </div> + <div class="flex flex-col gap-3"> + <Skeleton class="h-4 w-24" /> + <Skeleton class="h-8 w-full" /> + </div> + <Skeleton class="h-8 w-24" /> + </div> + } +} diff --git a/docs/src/routes/components/skeleton/examples/skeleton_table.rs b/docs/src/routes/components/skeleton/examples/skeleton_table.rs new file mode 100644 index 00000000..5ad83a3e --- /dev/null +++ b/docs/src/routes/components/skeleton/examples/skeleton_table.rs @@ -0,0 +1,17 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SkeletonTableExample() -> impl IntoView { + view! { + <div class="flex w-full max-w-sm flex-col gap-2"> + <For each=move || 0..5 key=|i| i.to_string() let(_)> + <div class="flex gap-4"> + <Skeleton class="h-4 flex-1" /> + <Skeleton class="h-4 w-24" /> + <Skeleton class="h-4 w-20" /> + </div> + </For> + </div> + } +} diff --git a/docs/src/routes/components/skeleton/examples/skeleton_text.rs b/docs/src/routes/components/skeleton/examples/skeleton_text.rs new file mode 100644 index 00000000..69787731 --- /dev/null +++ b/docs/src/routes/components/skeleton/examples/skeleton_text.rs @@ -0,0 +1,13 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SkeletonTextExample() -> impl IntoView { + view! { + <div class="flex w-full max-w-xs flex-col gap-2"> + <Skeleton class="h-4 w-full" /> + <Skeleton class="h-4 w-full" /> + <Skeleton class="h-4 w-3/4" /> + </div> + } +} diff --git a/docs/src/routes/components/skeleton/mod.rs b/docs/src/routes/components/skeleton/mod.rs index 54d746b2..c7ceb8b6 100644 --- a/docs/src/routes/components/skeleton/mod.rs +++ b/docs/src/routes/components/skeleton/mod.rs @@ -1,2 +1,3 @@ mod examples; + pub use examples::*; diff --git a/docs/src/routes/components/slider/component.toml b/docs/src/routes/components/slider/component.toml index f028d89f..caf2fd68 100644 --- a/docs/src/routes/components/slider/component.toml +++ b/docs/src/routes/components/slider/component.toml @@ -4,6 +4,20 @@ description = "An input where the user selects a value from within a given range [[examples]] name = "slider" +[[examples]] +name = "slider vertical" +title = "Vertical" +description = "Use the orientation prop to change between horizontal and vertical orientations." + +[[examples]] +name = "slider controlled" +title = "Controlled" + +[[examples]] +name = "slider disabled" +title = "Disabled" +description = "Use the disabled prop to disable the slider." + [[references]] name = "Slider" description = "An input where the user selects a value from within a given range." @@ -20,8 +34,10 @@ attrs = [ range of permitted values.""" }, { attr = "min", attr_type = "f64", default = "0.0", description = """The lowest value in the \ range of permitted values.""" }, + { attr = "orientation", attr_type = "String", default = """"horizontal"""", description = """Set \ + the orientation to render the slider. Accepted values: \"vertical\", \"horizontal\".""" }, { attr = "step", attr_type = "f64", default = "1.0", description = """Define the granularity of \ the expected input value.""" }, - { attr = "value", attr_type = "Reactive<f64>", default = "0.0", description = """A \ - reactive signal coupled to the input's value.""" }, + { attr = "value", attr_type = "f64", default = "0.0", description = """A reactive signal coupled \ + to the input's value.""" }, ] diff --git a/docs/src/routes/components/slider/examples/mod.rs b/docs/src/routes/components/slider/examples/mod.rs index 53f8fc02..b1c3465c 100644 --- a/docs/src/routes/components/slider/examples/mod.rs +++ b/docs/src/routes/components/slider/examples/mod.rs @@ -1,2 +1,9 @@ mod slider; +mod slider_controlled; +mod slider_disabled; +mod slider_vertical; + pub use slider::*; +pub use slider_controlled::*; +pub use slider_disabled::*; +pub use slider_vertical::*; diff --git a/docs/src/routes/components/slider/examples/slider.rs b/docs/src/routes/components/slider/examples/slider.rs index 752e0830..7eb103ad 100644 --- a/docs/src/routes/components/slider/examples/slider.rs +++ b/docs/src/routes/components/slider/examples/slider.rs @@ -1,50 +1,7 @@ use leptos::prelude::*; -use singlestage::{Button, Checkbox, Label, Slider}; +use singlestage::*; #[component] pub fn SliderExample() -> impl IntoView { - let disabled = RwSignal::new(true); - let max = RwSignal::new(60.); - let min = RwSignal::new(40.); - let value = RwSignal::new(50.); - - view! { - <div> - <div class="flex justify-between text-(--muted-foreground)"> - <span class="w-4">{move || min.get()}</span> - <span class="w-fit mx-auto">{move || value.get()}</span> - <span class="w-4">{move || max.get()}</span> - </div> - - <div> - <Slider id="value" class="w-full" disabled min max value /> - - <Label class="my-4"> - <Checkbox checked=disabled /> - "Disabled" - </Label> - - <div class="flex justify-between"> - <Button variant="outline" on:click=move |_| value.set(10.)> - "10" - </Button> - <Button variant="outline" on:click=move |_| value.set(50.)> - "50" - </Button> - <Button variant="outline" on:click=move |_| value.set(70.)> - "70" - </Button> - </div> - </div> - - <div> - <Slider class="ml-auto" min=0. max=value value=min> - "Min:" - </Slider> - <Slider class="ml-auto" min=value max=100. value=max> - "Max:" - </Slider> - </div> - </div> - } + view! { <Slider default=75. max=100. step=1. class="mx-auto w-full max-w-xs" /> } } diff --git a/docs/src/routes/components/slider/examples/slider_controlled.rs b/docs/src/routes/components/slider/examples/slider_controlled.rs new file mode 100644 index 00000000..7bb4e310 --- /dev/null +++ b/docs/src/routes/components/slider/examples/slider_controlled.rs @@ -0,0 +1,17 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SliderControlledExample() -> impl IntoView { + let value = RwSignal::new(0.3); + + view! { + <div class="mx-auto grid w-full max-w-xs gap-3"> + <div class="flex items-center justify-between gap-2"> + <Label label_for="slider-demo-temperature">"Temperature"</Label> + <span class="text-sm text-muted-foreground">{move || value.get().to_string()}</span> + </div> + <Slider id="slider-demo-temperature" value min=0. max=1. step=0.1 /> + </div> + } +} diff --git a/docs/src/routes/components/slider/examples/slider_disabled.rs b/docs/src/routes/components/slider/examples/slider_disabled.rs new file mode 100644 index 00000000..f0c259eb --- /dev/null +++ b/docs/src/routes/components/slider/examples/slider_disabled.rs @@ -0,0 +1,7 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SliderDisabledExample() -> impl IntoView { + view! { <Slider default=50. max=100. step=1. disabled=true class="mx-auto w-full max-w-xs" /> } +} diff --git a/docs/src/routes/components/slider/examples/slider_vertical.rs b/docs/src/routes/components/slider/examples/slider_vertical.rs new file mode 100644 index 00000000..d6e390d6 --- /dev/null +++ b/docs/src/routes/components/slider/examples/slider_vertical.rs @@ -0,0 +1,12 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SliderVerticalExample() -> impl IntoView { + view! { + <div class="mx-auto flex w-full max-w-xs items-center justify-center gap-6"> + <Slider default=50. max=100. step=1. orientation="vertical" class="h-40" /> + <Slider default=25. max=100. step=1. orientation="vertical" class="h-40" /> + </div> + } +} diff --git a/docs/src/routes/components/slider/mod.rs b/docs/src/routes/components/slider/mod.rs index 54d746b2..c7ceb8b6 100644 --- a/docs/src/routes/components/slider/mod.rs +++ b/docs/src/routes/components/slider/mod.rs @@ -1,2 +1,3 @@ mod examples; + pub use examples::*; diff --git a/docs/src/routes/components/spinner/examples/spinner_item.rs b/docs/src/routes/components/spinner/examples/spinner_item.rs index 194c3fc5..640f6330 100644 --- a/docs/src/routes/components/spinner/examples/spinner_item.rs +++ b/docs/src/routes/components/spinner/examples/spinner_item.rs @@ -19,7 +19,7 @@ pub fn SpinnerItemExample() -> impl IntoView { </Button> </ItemActions> <ItemFooter> - <Progress max=1000 value=129 /> + <Progress max=1000. value=129. /> </ItemFooter> </Item> </div> diff --git a/docs/src/routes/components/switch/component.toml b/docs/src/routes/components/switch/component.toml index d3533f24..afb3e961 100644 --- a/docs/src/routes/components/switch/component.toml +++ b/docs/src/routes/components/switch/component.toml @@ -9,6 +9,31 @@ name = "switch_group" title = "Checkbox Group" description = "Since a Switch is a Checkbox underneath, it can also be used in a CheckboxGroup." +[[examples]] +name = "switch_description" +title = "Description" + +[[examples]] +name = "switch_choice_card" +title = "Choice Card" +description = """Card-style selection where FieldLabel wraps the entire Field for a clickable \ +card pattern.""" + +[[examples]] +name = "switch_disabled" +title = "Disabled" +description = "Set the disabled prop on either the Switch or the Field to render it disabled." + +[[examples]] +name = "switch_invalid" +title = "Invalid" +description = "Set the invalid prop on either the Switch or the Field to render it invalid." + +[[examples]] +name = "switch_size" +title = "Size" +description = "Set the size prop to change the size of the switch." + [[references]] name = "switch" description = "A control that allows the user to toggle between checked and not checked." @@ -20,6 +45,10 @@ and <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/checkbox#additional_attributes">checkbox</a> \ attributes.""" attrs = [ - { attr = "checked", attr_type = "Reactive<bool>", default = "false", description = """A \ - reactive signal coupled to the switch's checked value.""" }, + { attr = "checked", attr_type = "bool", default = "false", description = """A reactive signal \ + coupled to the Switch's checked value.""" }, + { attr = "invalid", attr_type = "bool", default = "false", description = """Set whether to \ + render the Switch as invalid.""" }, + { attr = "size", attr_type = "String", default = """"default"""", description = """ Set the \ + size to render the Switch.""" }, ] diff --git a/docs/src/routes/components/switch/examples/mod.rs b/docs/src/routes/components/switch/examples/mod.rs index a36060d5..bfe758ea 100644 --- a/docs/src/routes/components/switch/examples/mod.rs +++ b/docs/src/routes/components/switch/examples/mod.rs @@ -1,5 +1,15 @@ mod switch; +mod switch_choice_card; +mod switch_description; +mod switch_disabled; mod switch_group; +mod switch_invalid; +mod switch_size; pub use switch::*; +pub use switch_choice_card::*; +pub use switch_description::*; +pub use switch_disabled::*; pub use switch_group::*; +pub use switch_invalid::*; +pub use switch_size::*; diff --git a/docs/src/routes/components/switch/examples/switch_choice_card.rs b/docs/src/routes/components/switch/examples/switch_choice_card.rs new file mode 100644 index 00000000..4b3b8b67 --- /dev/null +++ b/docs/src/routes/components/switch/examples/switch_choice_card.rs @@ -0,0 +1,33 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SwitchChoiceCardExample() -> impl IntoView { + view! { + <FieldGroup class="w-full max-w-sm"> + <FieldLabel label_for="switch-share"> + <Field orientation="horizontal"> + <FieldContent> + <FieldTitle>"Share across devices"</FieldTitle> + <FieldDescription> + "Focus is shared across devices, and turns off when you leave the + app." + </FieldDescription> + </FieldContent> + <Switch id="switch-share" /> + </Field> + </FieldLabel> + <FieldLabel label_for="switch-notifications"> + <Field orientation="horizontal"> + <FieldContent> + <FieldTitle>"Enable notifications"</FieldTitle> + <FieldDescription> + "Receive notifications when focus mode is enabled or disabled." + </FieldDescription> + </FieldContent> + <Switch id="switch-notifications" checked=true /> + </Field> + </FieldLabel> + </FieldGroup> + } +} diff --git a/docs/src/routes/components/switch/examples/switch_description.rs b/docs/src/routes/components/switch/examples/switch_description.rs new file mode 100644 index 00000000..c88c1c81 --- /dev/null +++ b/docs/src/routes/components/switch/examples/switch_description.rs @@ -0,0 +1,17 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SwitchDescriptionExample() -> impl IntoView { + view! { + <Field orientation="horizontal" class="max-w-sm"> + <FieldContent> + <FieldLabel label_for="switch-focus-mode">"Share across devices"</FieldLabel> + <FieldDescription> + "Focus is shared across devices, and turns off when you leave the app." + </FieldDescription> + </FieldContent> + <Switch id="switch-focus-mode" /> + </Field> + } +} diff --git a/docs/src/routes/components/switch/examples/switch_disabled.rs b/docs/src/routes/components/switch/examples/switch_disabled.rs new file mode 100644 index 00000000..efde70c8 --- /dev/null +++ b/docs/src/routes/components/switch/examples/switch_disabled.rs @@ -0,0 +1,12 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SwitchDisabledExample() -> impl IntoView { + view! { + <Field orientation="horizontal" disabled=true class="w-fit"> + <Switch id="switch-disabled-unchecked" /> + <FieldLabel label_for="switch-disabled-unchecked">"Disabled"</FieldLabel> + </Field> + } +} diff --git a/docs/src/routes/components/switch/examples/switch_invalid.rs b/docs/src/routes/components/switch/examples/switch_invalid.rs new file mode 100644 index 00000000..59ab54d9 --- /dev/null +++ b/docs/src/routes/components/switch/examples/switch_invalid.rs @@ -0,0 +1,17 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SwitchInvalidExample() -> impl IntoView { + view! { + <Field orientation="horizontal" class="max-w-sm" invalid=true> + <FieldContent> + <FieldLabel label_for="switch-terms">"Accept terms and conditions"</FieldLabel> + <FieldDescription> + "You must accept the terms and conditions to continue." + </FieldDescription> + </FieldContent> + <Switch id="switch-terms" /> + </Field> + } +} diff --git a/docs/src/routes/components/switch/examples/switch_size.rs b/docs/src/routes/components/switch/examples/switch_size.rs new file mode 100644 index 00000000..010e8ccb --- /dev/null +++ b/docs/src/routes/components/switch/examples/switch_size.rs @@ -0,0 +1,18 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SwitchSizeExample() -> impl IntoView { + view! { + <FieldGroup class="w-full max-w-[10rem]"> + <Field orientation="horizontal"> + <Switch id="switch-size-sm" size="sm" /> + <FieldLabel label_for="switch-size-sm">"Small"</FieldLabel> + </Field> + <Field orientation="horizontal"> + <Switch id="switch-size-default" size="default" /> + <FieldLabel label_for="switch-size-default">"Default"</FieldLabel> + </Field> + </FieldGroup> + } +} diff --git a/docs/src/routes/components/table/component.toml b/docs/src/routes/components/table/component.toml index 9fa40452..8e504422 100644 --- a/docs/src/routes/components/table/component.toml +++ b/docs/src/routes/components/table/component.toml @@ -4,6 +4,11 @@ description = "A responsive table component." [[examples]] name = "table" +[[examples]] +name = "table actions" +title = "Actions" +description = "A table showing actions for each row using a DropdownMenu." + [[references]] name = "Table" description = "Contains all the part of a table component." diff --git a/docs/src/routes/components/table/examples/mod.rs b/docs/src/routes/components/table/examples/mod.rs index 03f88462..89a76bce 100644 --- a/docs/src/routes/components/table/examples/mod.rs +++ b/docs/src/routes/components/table/examples/mod.rs @@ -1,2 +1,5 @@ mod table; +mod table_actions; + pub use table::*; +pub use table_actions::*; diff --git a/docs/src/routes/components/table/examples/table.rs b/docs/src/routes/components/table/examples/table.rs index 56f23633..73760ae9 100644 --- a/docs/src/routes/components/table/examples/table.rs +++ b/docs/src/routes/components/table/examples/table.rs @@ -3,60 +3,83 @@ use singlestage::*; #[component] pub fn TableExample() -> impl IntoView { + #[derive(Clone)] + struct Invoice { + invoice: String, + payment_status: String, + total_amount: String, + payment_method: String, + } + + let invoices = [ + Invoice { + invoice: "INV001".to_string(), + payment_status: "Paid".to_string(), + total_amount: "$250.00".to_string(), + payment_method: "Credit Card".to_string(), + }, + Invoice { + invoice: "INV002".to_string(), + payment_status: "Pending".to_string(), + total_amount: "$150.00".to_string(), + payment_method: "PayPal".to_string(), + }, + Invoice { + invoice: "INV003".to_string(), + payment_status: "Unpaid".to_string(), + total_amount: "$350.00".to_string(), + payment_method: "Bank Transfer".to_string(), + }, + Invoice { + invoice: "INV004".to_string(), + payment_status: "Paid".to_string(), + total_amount: "$450.00".to_string(), + payment_method: "Credit Card".to_string(), + }, + Invoice { + invoice: "INV005".to_string(), + payment_status: "Paid".to_string(), + total_amount: "$550.00".to_string(), + payment_method: "PayPal".to_string(), + }, + Invoice { + invoice: "INV006".to_string(), + payment_status: "Pending".to_string(), + total_amount: "$200.00".to_string(), + payment_method: "Bank Transfer".to_string(), + }, + Invoice { + invoice: "INV007".to_string(), + payment_status: "Unpaid".to_string(), + total_amount: "$300.00".to_string(), + payment_method: "Credit Card".to_string(), + }, + ]; + view! { <Table class="overflow-x-auto w-full"> <TableCaption>"A list of your recent invoices."</TableCaption> <TableHeader> <TableRow> - <TableHead>"Invoice"</TableHead> + <TableHead class="w-[100px]">"Invoice"</TableHead> <TableHead>"Status"</TableHead> <TableHead>"Method"</TableHead> <TableHead class="text-right">"Amount"</TableHead> </TableRow> </TableHeader> <TableBody> - <TableRow> - <TableCell class="font-medium">"INV001"</TableCell> - <TableCell>"Paid"</TableCell> - <TableCell>"Credit Card"</TableCell> - <TableCell class="text-right">"$250.00"</TableCell> - </TableRow> - <TableRow> - <TableCell class="font-medium">"INV002"</TableCell> - <TableCell>"Pending"</TableCell> - <TableCell>"PayPal"</TableCell> - <TableCell class="text-right">"$150.00"</TableCell> - </TableRow> - <TableRow> - <TableCell class="font-medium">"INV003"</TableCell> - <TableCell>"Unpaid"</TableCell> - <TableCell>"Bank Transfer"</TableCell> - <TableCell class="text-right">"$350.00"</TableCell> - </TableRow> - <TableRow> - <TableCell class="font-medium">"INV004"</TableCell> - <TableCell>"Paid"</TableCell> - <TableCell>"Paypal"</TableCell> - <TableCell class="text-right">"$450.00"</TableCell> - </TableRow> - <TableRow> - <TableCell class="font-medium">"INV005"</TableCell> - <TableCell>"Paid"</TableCell> - <TableCell>"Credit Card"</TableCell> - <TableCell class="text-right">"$550.00"</TableCell> - </TableRow> - <TableRow> - <TableCell class="font-medium">"INV006"</TableCell> - <TableCell>"Pending"</TableCell> - <TableCell>"Bank Transfer"</TableCell> - <TableCell class="text-right">"$200.00"</TableCell> - </TableRow> - <TableRow> - <TableCell class="font-medium">"INV007"</TableCell> - <TableCell>"Unpaid"</TableCell> - <TableCell>"Credit Card"</TableCell> - <TableCell class="text-right">"$300.00"</TableCell> - </TableRow> + <For + each=move || invoices.to_owned() + key=|invoice| invoice.invoice.clone() + let(invoice) + > + <TableRow> + <TableCell class="font-medium">{invoice.invoice}</TableCell> + <TableCell>{invoice.payment_status}</TableCell> + <TableCell>{invoice.payment_method}</TableCell> + <TableCell class="text-right">{invoice.total_amount}</TableCell> + </TableRow> + </For> </TableBody> <TableFooter> <TableRow> diff --git a/docs/src/routes/components/table/examples/table_actions.rs b/docs/src/routes/components/table/examples/table_actions.rs new file mode 100644 index 00000000..ef660a87 --- /dev/null +++ b/docs/src/routes/components/table/examples/table_actions.rs @@ -0,0 +1,68 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TableActionsExample() -> impl IntoView { + #[derive(Clone)] + struct Product { + name: String, + price: String, + } + + let products = [ + Product { + name: "Wireless Mouse".to_string(), + price: "$29.99".to_string(), + }, + Product { + name: "Mechanical Keyboard".to_string(), + price: "$129.99".to_string(), + }, + Product { + name: "USB-C Hub".to_string(), + price: "$49.99".to_string(), + }, + ]; + + view! { + <Table> + <TableHeader> + <TableRow> + <TableHead>"Product"</TableHead> + <TableHead>"Price"</TableHead> + <TableHead class="text-right">"Actions"</TableHead> + </TableRow> + </TableHeader> + <TableBody> + <For + each=move || products.to_owned() + key=|product| product.name.clone() + let(product) + > + <TableRow> + <TableCell class="font-medium">{product.name}</TableCell> + <TableCell>{product.price}</TableCell> + <TableCell class="text-right"> + <DropdownMenu> + <DropdownMenuTrigger> + <Button variant="ghost" size="icon" class="size-8"> + {icon!(icondata::FiMoreHorizontal)} + <span class="sr-only">"Open menu"</span> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <DropdownMenuItem>"Edit"</DropdownMenuItem> + <DropdownMenuItem>"Duplicate"</DropdownMenuItem> + <DropdownMenuSeparator /> + <DropdownMenuItem variant="destructive"> + "Delete" + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </TableCell> + </TableRow> + </For> + </TableBody> + </Table> + } +} diff --git a/docs/src/routes/components/tabs/component.toml b/docs/src/routes/components/tabs/component.toml index 0ccb8b6f..af444ff6 100644 --- a/docs/src/routes/components/tabs/component.toml +++ b/docs/src/routes/components/tabs/component.toml @@ -4,6 +4,24 @@ description = "A set of layered sections of content—known as tab panels—that [[examples]] name = "tabs" +[[examples]] +name = "tabs line" +title = "Line" +description = "Use the line variant for a line style." + +[[examples]] +name = "tabs vertical" +title = "Vertical" +description = "Use the vertical orientation for vertical tabs." + +[[examples]] +name = "tabs disabled" +title = "Disabled" + +[[examples]] +name = "tabs icons" +title = "Icons" + [[references]] name = "Tabs" description = "Contains all the tabs component parts." @@ -11,8 +29,10 @@ extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" attrs = [ - { attr = "value", attr_type = "Reactive<String>", default = """""""", description = """Reactive \ - signal coupled to the `value` of the current selected tab.""" }, + { attr = "orientation", attr_type = "String", default = """"horizontal"""", description = """Set \ + which direction the tabs should flow. Accepted values: "horizontal" | "vertical".""" }, + { attr = "value", attr_type = "String", default = """""""", description = """Reactive signal \ + coupled to the `value` of the current selected tab.""" }, ] [[references]] @@ -32,7 +52,10 @@ description = "Contains the triggers that are aligned along the edge of the acti extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" -attrs = [] +attrs = [ + { attr = "variant", attr_type = "String", default = """"default"""", description = """Set the \ + display variant of the tab buttons. Accepted values: "default" | "line".""" }, +] [[references]] name = "TabsTrigger" diff --git a/docs/src/routes/components/tabs/examples/mod.rs b/docs/src/routes/components/tabs/examples/mod.rs index ea75c00b..c9114a29 100644 --- a/docs/src/routes/components/tabs/examples/mod.rs +++ b/docs/src/routes/components/tabs/examples/mod.rs @@ -1,2 +1,11 @@ mod tabs; +mod tabs_disabled; +mod tabs_icons; +mod tabs_line; +mod tabs_vertical; + pub use tabs::*; +pub use tabs_disabled::*; +pub use tabs_icons::*; +pub use tabs_line::*; +pub use tabs_vertical::*; diff --git a/docs/src/routes/components/tabs/examples/tabs.rs b/docs/src/routes/components/tabs/examples/tabs.rs index 5fee3ef5..382d81c9 100644 --- a/docs/src/routes/components/tabs/examples/tabs.rs +++ b/docs/src/routes/components/tabs/examples/tabs.rs @@ -4,71 +4,67 @@ use singlestage::*; #[component] pub fn TabsExample() -> impl IntoView { view! { - <Tabs id="demo-tabs-with-panels"> + <Tabs value="overview" class="w-[400px]"> <TabsList> - <TabsTrigger value="account">"Account"</TabsTrigger> - <TabsTrigger value="password">"Password"</TabsTrigger> + <TabsTrigger value="overview">"Overview"</TabsTrigger> + <TabsTrigger value="analytics">"Analytics"</TabsTrigger> + <TabsTrigger value="reports">"Reports"</TabsTrigger> + <TabsTrigger value="settings">"Settings"</TabsTrigger> </TabsList> - - <TabsContent value="account"> + <TabsContent value="overview"> <Card> <CardHeader> - <CardTitle>"Account"</CardTitle> + <CardTitle>"Overview"</CardTitle> <CardDescription> - "Make changes to your account here. Click save when you're done." + "View your key metrics and recent project activity. Track progress + across all your active projects." </CardDescription> </CardHeader> - <CardContent> - <form class="form grid gap-6"> - <div class="grid gap-3"> - <Label label_for="demo-tabs-account-name">"Name"</Label> - <Input - input_type="text" - id="demo-tabs-account-name" - value="Pedro Duarte" - /> - </div> - <div class="grid gap-3"> - <Label label_for="demo-tabs-account-username">"Username"</Label> - <Input - input_type="text" - id="demo-tabs-account-username" - value="@peduarte" - /> - </div> - </form> + <CardContent class="text-sm text-(--muted-foreground)"> + "You have 12 active projects and 3 pending tasks." </CardContent> - <CardFooter> - <Button button_type="button">"Save changes"</Button> - </CardFooter> </Card> </TabsContent> - - <TabsContent value="password"> + <TabsContent value="analytics"> + <Card> + <CardHeader> + <CardTitle>"Analytics"</CardTitle> + <CardDescription> + "Track performance and user engagement metrics. Monitor trends and + identify growth opportunities." + </CardDescription> + </CardHeader> + <CardContent class="text-sm text-(--muted-foreground)"> + "Page views are up 25% compared to last month." + </CardContent> + </Card> + </TabsContent> + <TabsContent value="reports"> + <Card> + <CardHeader> + <CardTitle>"Reports"</CardTitle> + <CardDescription> + "Generate and download your detailed reports. Export data in + multiple formats for analysis." + </CardDescription> + </CardHeader> + <CardContent class="text-sm text-(--muted-foreground)"> + "You have 5 reports ready and available to export." + </CardContent> + </Card> + </TabsContent> + <TabsContent value="settings"> <Card> <CardHeader> - <CardTitle>"Password"</CardTitle> + <CardTitle>"Settings"</CardTitle> <CardDescription> - "Change your password here. After saving, you'll be logged out." + "Manage your account preferences and options. Customize your + experience to fit your needs." </CardDescription> </CardHeader> - <CardContent> - <form class="form grid gap-6"> - <div class="grid gap-3"> - <Label label_for="demo-tabs-password-current"> - "Current password" - </Label> - <Input input_type="password" id="demo-tabs-password-current" /> - </div> - <div class="grid gap-3"> - <Label label_for="demo-tabs-password-new">"New password"</Label> - <Input input_type="password" id="demo-tabs-password-new" /> - </div> - </form> + <CardContent class="text-sm text-(--muted-foreground)"> + "Configure notifications, security, and themes." </CardContent> - <CardFooter> - <Button button_type="button">"Save Password"</Button> - </CardFooter> </Card> </TabsContent> </Tabs> 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! { + <Tabs value="home"> + <TabsList> + <TabsTrigger value="home">"Home"</TabsTrigger> + <TabsTrigger value="settings" disabled=true> + "Disabled" + </TabsTrigger> + </TabsList> + </Tabs> + } +} 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! { + <Tabs value="preview"> + <TabsList> + <TabsTrigger value="preview">{icon!(icondata::LuAppWindow)} "Preview"</TabsTrigger> + <TabsTrigger value="code">{icon!(icondata::LuCode)} "Code"</TabsTrigger> + </TabsList> + </Tabs> + } +} 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! { + <Tabs value="overview"> + <TabsList variant="line"> + <TabsTrigger value="overview">"Overview"</TabsTrigger> + <TabsTrigger value="analytics">"Analytics"</TabsTrigger> + <TabsTrigger value="reports">"Reports"</TabsTrigger> + </TabsList> + </Tabs> + } +} 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! { + <Tabs value="account" orientation="vertical"> + <TabsList> + <TabsTrigger value="account">"Account"</TabsTrigger> + <TabsTrigger value="password">"Password"</TabsTrigger> + <TabsTrigger value="notifications">"Notifications"</TabsTrigger> + </TabsList> + </Tabs> + } +} diff --git a/docs/src/routes/components/textarea/component.toml b/docs/src/routes/components/textarea/component.toml index 9dd0da7e..29442a05 100644 --- a/docs/src/routes/components/textarea/component.toml +++ b/docs/src/routes/components/textarea/component.toml @@ -4,6 +4,22 @@ description = "Displays a form textarea." [[examples]] name = "textarea" +[[examples]] +name = "textarea field" +title = "Field" + +[[examples]] +name = "textarea disabled" +title = "Disabled" + +[[examples]] +name = "textarea invalid" +title = "Invalid" + +[[examples]] +name = "textarea button" +title = "Button" + [[references]] name = "Textarea" description = "Creates a textarea that takes children as a default value." @@ -19,6 +35,6 @@ attrs = [ not the textarea is disabled.""" }, { attr = "invalid", attr_type = "bool", default = "false", description = """Toggles invalid \ appearance.""" }, - { attr = "value", attr_type = "Reactive<String>", default = """""""", description = """Reactive \ - signal coupled to the value of the textarea.""" }, + { attr = "value", attr_type = "String", default = """""""", description = """Reactive signal \ + coupled to the value of the textarea.""" }, ] diff --git a/docs/src/routes/components/textarea/examples/mod.rs b/docs/src/routes/components/textarea/examples/mod.rs index 4162e373..c95aff02 100644 --- a/docs/src/routes/components/textarea/examples/mod.rs +++ b/docs/src/routes/components/textarea/examples/mod.rs @@ -1,2 +1,11 @@ mod textarea; +mod textarea_button; +mod textarea_disabled; +mod textarea_field; +mod textarea_invalid; + pub use textarea::*; +pub use textarea_button::*; +pub use textarea_disabled::*; +pub use textarea_field::*; +pub use textarea_invalid::*; diff --git a/docs/src/routes/components/textarea/examples/textarea.rs b/docs/src/routes/components/textarea/examples/textarea.rs index ae23e3a3..4803a1d8 100644 --- a/docs/src/routes/components/textarea/examples/textarea.rs +++ b/docs/src/routes/components/textarea/examples/textarea.rs @@ -1,11 +1,11 @@ use leptos::prelude::*; -use singlestage::Textarea; +use singlestage::*; #[component] pub fn TextareaExample() -> impl IntoView { view! { - <form class="w-full"> - <Textarea placeholder="Type your message here">"Comments:"</Textarea> - </form> + <div class="w-xs"> + <Textarea placeholder="Type your message here" /> + </div> } } diff --git a/docs/src/routes/components/textarea/examples/textarea_button.rs b/docs/src/routes/components/textarea/examples/textarea_button.rs new file mode 100644 index 00000000..37573873 --- /dev/null +++ b/docs/src/routes/components/textarea/examples/textarea_button.rs @@ -0,0 +1,12 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TextareaButtonExample() -> impl IntoView { + view! { + <div class="grid w-xs gap-2"> + <Textarea placeholder="Type your message here." /> + <Button>"Send message"</Button> + </div> + } +} diff --git a/docs/src/routes/components/textarea/examples/textarea_disabled.rs b/docs/src/routes/components/textarea/examples/textarea_disabled.rs new file mode 100644 index 00000000..b40e7992 --- /dev/null +++ b/docs/src/routes/components/textarea/examples/textarea_disabled.rs @@ -0,0 +1,11 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TextareaDisabledExample() -> impl IntoView { + view! { + <Field class="max-w-xs" disabled=true> + <Textarea placeholder="Type your message here.">"Message"</Textarea> + </Field> + } +} diff --git a/docs/src/routes/components/textarea/examples/textarea_field.rs b/docs/src/routes/components/textarea/examples/textarea_field.rs new file mode 100644 index 00000000..91d47903 --- /dev/null +++ b/docs/src/routes/components/textarea/examples/textarea_field.rs @@ -0,0 +1,13 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TextareaFieldExample() -> impl IntoView { + view! { + <Field class="max-w-xs"> + <Label label_for="textarea-message">"Message"</Label> + <FieldDescription>"Enter your message below."</FieldDescription> + <Textarea id="textarea-message" placeholder="Type your message here." /> + </Field> + } +} diff --git a/docs/src/routes/components/textarea/examples/textarea_invalid.rs b/docs/src/routes/components/textarea/examples/textarea_invalid.rs new file mode 100644 index 00000000..baaa7cf0 --- /dev/null +++ b/docs/src/routes/components/textarea/examples/textarea_invalid.rs @@ -0,0 +1,12 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TextareaInvalidExample() -> impl IntoView { + view! { + <Field class="max-w-xs" invalid=true> + <Textarea placeholder="Type your message here.">"Message"</Textarea> + <FieldDescription>"Please enter a valid message."</FieldDescription> + </Field> + } +} diff --git a/docs/src/routes/components/toggle/anatomy.rs b/docs/src/routes/components/toggle/anatomy.rs index c3290a10..286ad355 100644 --- a/docs/src/routes/components/toggle/anatomy.rs +++ b/docs/src/routes/components/toggle/anatomy.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use singlestage::toggle::Toggle; +use singlestage::*; #[component] pub fn ToggleAnatomy() -> impl IntoView { diff --git a/docs/src/routes/components/toggle/component.toml b/docs/src/routes/components/toggle/component.toml index 37d0c9d4..520174a6 100644 --- a/docs/src/routes/components/toggle/component.toml +++ b/docs/src/routes/components/toggle/component.toml @@ -8,18 +8,16 @@ title = "Toggle" [[examples]] name = "Toggle Outline" title = "Outline" +description = "Use the outline variant for an outline style." [[examples]] name = "Toggle With Text" title = "With Text" [[examples]] -name = "Toggle Small" -title = "Small" - -[[examples]] -name = "Toggle Large" -title = "Large" +name = "toggle size" +title = "Size" +description = "Use the size prop to change the size of the toggle." [[examples]] name = "Toggle Disabled" @@ -34,10 +32,10 @@ and <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#attributes">button</a> \ attributes.""" attrs = [ + { attr = "pressed", attr_type = "bool", default = "false", description = """A reactive signal \ + coupled to the toggle's pressed state.""" }, { attr = "size", attr_type = "String", default = """""""", description = """Specify the size to \ render the toggle.""" }, - { attr = "pressed", attr_type = "Reactive<bool>", default = "false", description = """A \ - reactive signal coupled to the toggle's pressed state.""" }, { attr = "variant", attr_type = "String", default = """""""", description = """Specify \ the style of toggle to render.""" }, ] diff --git a/docs/src/routes/components/toggle/examples/mod.rs b/docs/src/routes/components/toggle/examples/mod.rs index 949ba8ac..a6bdb068 100644 --- a/docs/src/routes/components/toggle/examples/mod.rs +++ b/docs/src/routes/components/toggle/examples/mod.rs @@ -1,12 +1,11 @@ mod toggle; mod toggle_disabled; -mod toggle_large; mod toggle_outline; -mod toggle_small; +mod toggle_size; mod toggle_with_text; + pub use toggle::*; pub use toggle_disabled::*; -pub use toggle_large::*; pub use toggle_outline::*; -pub use toggle_small::*; +pub use toggle_size::*; pub use toggle_with_text::*; diff --git a/docs/src/routes/components/toggle/examples/toggle.rs b/docs/src/routes/components/toggle/examples/toggle.rs index 4acf32fc..ca7a860b 100644 --- a/docs/src/routes/components/toggle/examples/toggle.rs +++ b/docs/src/routes/components/toggle/examples/toggle.rs @@ -1,8 +1,19 @@ -use icondata::LuBold; use leptos::prelude::*; use singlestage::*; #[component] pub fn ToggleExample() -> impl IntoView { - view! { <Toggle aria_label="Toggle italic">{icon!(LuBold)}</Toggle> } + let pressed = RwSignal::new(false); + + view! { + <Toggle aria_label="Toggle bookmark" pressed size="sm" variant="outline"> + <Show + when=move || pressed.get() + fallback=move || view! { {icon!(icondata::LuBookmark)} } + > + {icon!(icondata::LuBookmark, class="fill-(--foreground)")} + </Show> + Bookmark + </Toggle> + } } diff --git a/docs/src/routes/components/toggle/examples/toggle_disabled.rs b/docs/src/routes/components/toggle/examples/toggle_disabled.rs index bff3ce32..fb5e56e0 100644 --- a/docs/src/routes/components/toggle/examples/toggle_disabled.rs +++ b/docs/src/routes/components/toggle/examples/toggle_disabled.rs @@ -1,12 +1,16 @@ -use icondata::LuUnderline; use leptos::prelude::*; use singlestage::*; #[component] pub fn ToggleDisabledExample() -> impl IntoView { view! { - <Toggle disabled=true aria_label="Toggle italic"> - {icon!(LuUnderline)} - </Toggle> + <div class="flex flex-wrap items-center gap-2"> + <Toggle aria_label="Toggle disabled" disabled=true> + "Disabled" + </Toggle> + <Toggle variant="outline" aria_label="Toggle disabled outline" disabled=true> + "Disabled" + </Toggle> + </div> } } diff --git a/docs/src/routes/components/toggle/examples/toggle_large.rs b/docs/src/routes/components/toggle/examples/toggle_large.rs deleted file mode 100644 index 81bcc073..00000000 --- a/docs/src/routes/components/toggle/examples/toggle_large.rs +++ /dev/null @@ -1,12 +0,0 @@ -use icondata::LuItalic; -use leptos::prelude::*; -use singlestage::*; - -#[component] -pub fn ToggleLargeExample() -> impl IntoView { - view! { - <Toggle size="lg" aria_label="Toggle italic"> - {icon!(LuItalic)} - </Toggle> - } -} diff --git a/docs/src/routes/components/toggle/examples/toggle_outline.rs b/docs/src/routes/components/toggle/examples/toggle_outline.rs index b36c9b89..d04f4219 100644 --- a/docs/src/routes/components/toggle/examples/toggle_outline.rs +++ b/docs/src/routes/components/toggle/examples/toggle_outline.rs @@ -1,12 +1,18 @@ -use icondata::LuItalic; use leptos::prelude::*; use singlestage::*; #[component] pub fn ToggleOutlineExample() -> impl IntoView { view! { - <Toggle variant="outline" aria_label="Toggle italic"> - {icon!(LuItalic)} - </Toggle> + <div class="flex flex-wrap items-center gap-2"> + <Toggle variant="outline" aria_label="Toggle italic"> + {icon!(icondata::LuItalic)} + <span>"Italic"</span> + </Toggle> + <Toggle variant="outline" aria_label="Toggle bold"> + {icon!(icondata::LuBold)} + <span>"Bold"</span> + </Toggle> + </div> } } diff --git a/docs/src/routes/components/toggle/examples/toggle_size.rs b/docs/src/routes/components/toggle/examples/toggle_size.rs new file mode 100644 index 00000000..87adf658 --- /dev/null +++ b/docs/src/routes/components/toggle/examples/toggle_size.rs @@ -0,0 +1,19 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn ToggleSizeExample() -> impl IntoView { + view! { + <div class="flex flex-wrap items-center gap-2"> + <Toggle variant="outline" aria_label="Toggle small" size="sm"> + "Small" + </Toggle> + <Toggle variant="outline" aria_label="Toggle default" size="default"> + "Default" + </Toggle> + <Toggle variant="outline" aria_label="Toggle large" size="lg"> + "Large" + </Toggle> + </div> + } +} diff --git a/docs/src/routes/components/toggle/examples/toggle_small.rs b/docs/src/routes/components/toggle/examples/toggle_small.rs deleted file mode 100644 index 20fb1b39..00000000 --- a/docs/src/routes/components/toggle/examples/toggle_small.rs +++ /dev/null @@ -1,12 +0,0 @@ -use icondata::LuItalic; -use leptos::prelude::*; -use singlestage::*; - -#[component] -pub fn ToggleSmallExample() -> impl IntoView { - view! { - <Toggle size="sm" aria_label="Toggle italic"> - {icon!(LuItalic)} - </Toggle> - } -} diff --git a/docs/src/routes/components/toggle/examples/toggle_with_text.rs b/docs/src/routes/components/toggle/examples/toggle_with_text.rs index 833265a5..a8e349e3 100644 --- a/docs/src/routes/components/toggle/examples/toggle_with_text.rs +++ b/docs/src/routes/components/toggle/examples/toggle_with_text.rs @@ -4,5 +4,5 @@ use singlestage::*; #[component] pub fn ToggleWithTextExample() -> impl IntoView { - view! { <Toggle aria_label="Toggle italic">{icon!(LuItalic)} "Italic"</Toggle> } + view! { <Toggle aria_label="Toggle italic">{icon!(LuItalic)} <span>"Italic"</span></Toggle> } } diff --git a/docs/src/routes/components/tooltip/anatomy.rs b/docs/src/routes/components/tooltip/anatomy.rs index 64e6429f..3a535c12 100644 --- a/docs/src/routes/components/tooltip/anatomy.rs +++ b/docs/src/routes/components/tooltip/anatomy.rs @@ -3,5 +3,10 @@ use singlestage::*; #[component] pub fn TooltipAnatomy() -> impl IntoView { - view! { <Tooltip /> } + view! { + <Tooltip> + <TooltipTrigger /> + <TooltipContent /> + </Tooltip> + } } diff --git a/docs/src/routes/components/tooltip/component.toml b/docs/src/routes/components/tooltip/component.toml index 1291d87f..3347af41 100644 --- a/docs/src/routes/components/tooltip/component.toml +++ b/docs/src/routes/components/tooltip/component.toml @@ -2,17 +2,44 @@ name = "tooltip" description = """A popup that displays information related to an element when the element \ receives keyboard focus or the mouse hovers over it.""" +[info] +title = "This component uses newly available browser features" +description = """"<p>"This component uses CSS Anchor Positioning which has only been widely \ +supported since January 2026. If you're targeting older or less mainstream browsers then maybe \ +this component isn't for you, yet. "</p><p>" Alternatively you can use Singlestage 0.4 which \ +positions the component correctly but with limited flexibility for alignment, or roll your own \ +using Singlestage CSS classes for consistent styling. "</p><singlestage::Link style="color: \ +var(--foreground)" href="https://caniuse.com/css-anchor-positioning"> "More Info" \ +</singlestage::Link>"""" + [[examples]] name = "tooltip" [[examples]] -name = "tooltip position" -title = "Positioning" -description = "Tooltips can be positioned." +name = "tooltip side" +title = "Side" +description = "Use the side prop to change the position of the tooltip." + +[[examples]] +name = "tooltip with keyboard shortcut" +title = "With Keyboard Shortcut" + +[[examples]] +name = "tooltip disabled button" +title = "Disabled Button" [[references]] -name = "tooltip" -description = "The tooltip." +name = "Tooltip" +description = """A popup that displays information related to an element when the element \ +receives keyboard focus or the mouse hovers over it. """ +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] + +[[references]] +name = "TooltipContent" +description = "." extra = """Implements <a class="singlestage-link" \ href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ attributes.""" @@ -22,3 +49,11 @@ attrs = [ { attr = "side", attr_type = "String", default = """"top"""", description = """Set which side of the \ triggering element that the tooltip will spawn from.""" }, ] + +[[references]] +name = "TooltipTrigger" +description = "Contains all the parts of a Tooltip component." +extra = """Implements <a class="singlestage-link" \ +href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes">global</a> \ +attributes.""" +attrs = [] diff --git a/docs/src/routes/components/tooltip/examples/mod.rs b/docs/src/routes/components/tooltip/examples/mod.rs index fbf529ca..1fb8d655 100644 --- a/docs/src/routes/components/tooltip/examples/mod.rs +++ b/docs/src/routes/components/tooltip/examples/mod.rs @@ -1,5 +1,9 @@ mod tooltip; -mod tooltip_position; +mod tooltip_disabled_button; +mod tooltip_side; +mod tooltip_with_keyboard_shortcut; pub use tooltip::*; -pub use tooltip_position::*; +pub use tooltip_disabled_button::*; +pub use tooltip_side::*; +pub use tooltip_with_keyboard_shortcut::*; diff --git a/docs/src/routes/components/tooltip/examples/tooltip.rs b/docs/src/routes/components/tooltip/examples/tooltip.rs index 922e9bdb..bf6c1ad5 100644 --- a/docs/src/routes/components/tooltip/examples/tooltip.rs +++ b/docs/src/routes/components/tooltip/examples/tooltip.rs @@ -4,8 +4,13 @@ use singlestage::*; #[component] pub fn TooltipExample() -> impl IntoView { view! { - <Tooltip value="Add to library"> - <Button variant="outline">"Hover"</Button> + <Tooltip> + <TooltipTrigger> + <Button variant="outline">"Hover"</Button> + </TooltipTrigger> + <TooltipContent> + <p>"Add to library"</p> + </TooltipContent> </Tooltip> } } diff --git a/docs/src/routes/components/tooltip/examples/tooltip_disabled_button.rs b/docs/src/routes/components/tooltip/examples/tooltip_disabled_button.rs new file mode 100644 index 00000000..a85c3297 --- /dev/null +++ b/docs/src/routes/components/tooltip/examples/tooltip_disabled_button.rs @@ -0,0 +1,18 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TooltipDisabledButtonExample() -> impl IntoView { + view! { + <Tooltip> + <TooltipTrigger> + <Button variant="outline" disabled=true> + "Disabled" + </Button> + </TooltipTrigger> + <TooltipContent> + <p>"This feature is currently unavailable"</p> + </TooltipContent> + </Tooltip> + } +} diff --git a/docs/src/routes/components/tooltip/examples/tooltip_position.rs b/docs/src/routes/components/tooltip/examples/tooltip_position.rs deleted file mode 100644 index 77005ad2..00000000 --- a/docs/src/routes/components/tooltip/examples/tooltip_position.rs +++ /dev/null @@ -1,82 +0,0 @@ -use leptos::prelude::*; -use singlestage::*; - -#[component] -pub fn TooltipPositionExample() -> impl IntoView { - view! { - <div> - <div class="flex"> - <Tooltip align="start" value="Add to library"> - <Button class="w-[4rem]" variant="outline"> - "Start" - </Button> - </Tooltip> - <Tooltip align="center" value="Add to library"> - <Button class="w-[4rem]" variant="outline"> - "Center" - </Button> - </Tooltip> - <Tooltip align="end" value="Add to library"> - <Button class="w-[4rem]" variant="outline"> - "End" - </Button> - </Tooltip> - </div> - - <div class="flex"> - <div> - <Tooltip side="left" align="start" value="Add to library"> - <Button class="w-[6rem] h-[4rem]" variant="outline"> - "Left-Start" - </Button> - </Tooltip> - <Tooltip side="left" align="center" value="Add to library"> - <Button class="w-[6rem] h-[4rem]" variant="outline"> - "Left-Center" - </Button> - </Tooltip> - <Tooltip side="left" align="end" value="Add to library"> - <Button class="w-[6rem] h-[4rem]" variant="outline"> - "Left-End" - </Button> - </Tooltip> - </div> - <div> - <Tooltip side="right" align="start" value="Add to library"> - <Button class="w-[6rem] h-[4rem]" variant="outline"> - "Right-Start" - </Button> - </Tooltip> - <Tooltip side="right" align="center" value="Add to library"> - <Button class="w-[6rem] h-[4rem]" variant="outline"> - "Left-Center" - </Button> - </Tooltip> - <Tooltip side="right" align="end" value="Add to library"> - <Button class="w-[6rem] h-[4rem]" variant="outline"> - "Right-End" - </Button> - </Tooltip> - </div> - </div> - - <div class="flex"> - <Tooltip side="bottom" align="start" value="Add to library"> - <Button class="w-[4rem]" variant="outline"> - "Start" - </Button> - </Tooltip> - <Tooltip side="bottom" align="center" value="Add to library"> - <Button class="w-[4rem]" variant="outline"> - "Center" - </Button> - </Tooltip> - <Tooltip side="bottom" align="end" value="Add to library"> - <Button class="w-[4rem]" variant="outline"> - "End" - </Button> - </Tooltip> - </div> - </div> - } -} diff --git a/docs/src/routes/components/tooltip/examples/tooltip_side.rs b/docs/src/routes/components/tooltip/examples/tooltip_side.rs new file mode 100644 index 00000000..4a7c33de --- /dev/null +++ b/docs/src/routes/components/tooltip/examples/tooltip_side.rs @@ -0,0 +1,26 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TooltipSideExample() -> impl IntoView { + view! { + <div class="flex flex-wrap gap-2"> + <For + each=move || ["left", "top", "bottom", "right"] + key=|side| side.to_string() + let(side) + > + <Tooltip> + <TooltipTrigger> + <Button variant="outline" class="w-fit capitalize"> + {side} + </Button> + </TooltipTrigger> + <TooltipContent side={side}> + <p>"Add to library"</p> + </TooltipContent> + </Tooltip> + </For> + </div> + } +} diff --git a/docs/src/routes/components/tooltip/examples/tooltip_with_keyboard_shortcut.rs b/docs/src/routes/components/tooltip/examples/tooltip_with_keyboard_shortcut.rs new file mode 100644 index 00000000..dd5a2635 --- /dev/null +++ b/docs/src/routes/components/tooltip/examples/tooltip_with_keyboard_shortcut.rs @@ -0,0 +1,16 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TooltipWithKeyboardShortcutExample() -> impl IntoView { + view! { + <Tooltip> + <TooltipTrigger> + <Button variant="outline" size="icon-sm"> + {icon!(icondata::LuSave)} + </Button> + </TooltipTrigger> + <TooltipContent>"Save Changes" <Kbd>"S"</Kbd></TooltipContent> + </Tooltip> + } +} diff --git a/docs/src/routes/debug/checkbox.rs b/docs/src/routes/debug/checkbox.rs deleted file mode 100644 index 23b4eab3..00000000 --- a/docs/src/routes/debug/checkbox.rs +++ /dev/null @@ -1,39 +0,0 @@ -use leptos::prelude::*; -use singlestage::*; - -#[component] -pub fn DebugCheckbox() -> impl IntoView { - let checked = RwSignal::new(true); - let disabled = RwSignal::new(true); - - view! { - <h1 class="text-4xl font-semibold">"Checkbox"</h1> - <ul class="singlestage-ulist text-(--muted-foreground)"> - <li>"Checked state should not flicker on load or reload"</li> - <li> - "Checked state should not lose sync with the signal when alternating clicking the - button and the checkbox" - </li> - <li>"Disabled state should not flicker on load or reload"</li> - </ul> - - <ul class="text-(--muted-foreground)"> - <li>"Checked: "{move || checked.get().to_string()}</li> - <li>"Disabled: "{move || disabled.get().to_string()}</li> - </ul> - - <Label> - <Checkbox disabled checked /> - "Accept terms and conditions" - </Label> - - <span class="space-x-2"> - <Button variant="outline" on:click=move |_| { disabled.set(!disabled.get_untracked()) }> - "Toggle Disabled" - </Button> - <Button variant="outline" on:click=move |_| { checked.set(!checked.get_untracked()) }> - "Toggle Checked" - </Button> - </span> - } -} diff --git a/docs/src/routes/debug/dropdown.rs b/docs/src/routes/debug/dropdown.rs deleted file mode 100644 index 4b39780e..00000000 --- a/docs/src/routes/debug/dropdown.rs +++ /dev/null @@ -1,28 +0,0 @@ -use leptos::prelude::*; -use singlestage::*; - -#[component] -pub fn DebugDropdown() -> impl IntoView { - view! { - <h1 class="text-4xl font-semibold">"Dropdown Menu"</h1> - <ul class="singlestage-ulist text-(--muted-foreground)"> - <li>"Console should log \"hey\" when clicked"</li> - <li>"Console should log \"hey\" when tapped"</li> - <li> - "Console should log \"hey\" when selected with keyboard and "<Kbd>"Enter"</Kbd> - " is pressed" - </li> - </ul> - - <DropdownMenu> - <DropdownMenuTrigger> - <Button>"Open"</Button> - </DropdownMenuTrigger> - <DropdownMenuContent> - <DropdownMenuItem on:click=move |_| { - leptos::logging::log!("hey") - }>"Click me"</DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - } -} diff --git a/docs/src/routes/getting_started/installation.rs b/docs/src/routes/getting_started/installation.rs index c0d0c429..3946db6c 100644 --- a/docs/src/routes/getting_started/installation.rs +++ b/docs/src/routes/getting_started/installation.rs @@ -16,7 +16,7 @@ pub fn Installation() -> impl IntoView { <span>[dependencies]</span> <span>leptos = "0.8"</span> <span>...</span> -<span>singlestage = "0.4"</span> +<span>singlestage = "0.5"</span> </pre>"# .to_string() /> @@ -30,7 +30,7 @@ pub fn Installation() -> impl IntoView { <span>[dependencies]</span> <span>leptos = { version = "0.8", features = ["nightly"] }</span> <span>...</span> -<span>singlestage = { version = "0.4", features = ["nightly"] }</span> +<span>singlestage = { version = "0.5", features = ["nightly"] }</span> </pre>"# .to_string() /> @@ -44,7 +44,7 @@ pub fn Installation() -> impl IntoView { <span>[dependencies]</span> <span>leptos = { version = "0.8", features = ["islands"] }</span> <span>...</span> -<span>singlestage = { version = "0.4", features = ["islands"] }</span> +<span>singlestage = { version = "0.5", features = ["islands"] }</span> </pre>"# .to_string() /> @@ -82,7 +82,7 @@ pub fn Installation() -> impl IntoView { <span>leptos = "0.8"</span> <span>...</span> <span>reactive_stores = "0.3"</span> -<span>singlestage = { version = "0.4", features = ["stores"] }</span> +<span>singlestage = { version = "0.5", features = ["stores"] }</span> </pre>"# .to_string() /> } diff --git a/docs/src/routes/getting_started/theme_provider.rs b/docs/src/routes/getting_started/theme_provider.rs index 9121e751..29c4dae3 100644 --- a/docs/src/routes/getting_started/theme_provider.rs +++ b/docs/src/routes/getting_started/theme_provider.rs @@ -10,8 +10,8 @@ pub fn ThemeProviderRoute() -> impl IntoView { <p class="my-4">"Wrap your app, probably at the router, with ThemeProvider:"</p> <CodeBlock code=r#"<pre> <span>...</span> -<span><</span><span>Stylesheet id="leptos" href="/pkg/my_app.css" </span><span>/></span> <span><</span>ThemeProvider</span><span>></span> +<span> <</span><span>Stylesheet id="leptos" href="/pkg/my_app.css" </span><span>/></span> <span> <</span><span>Router</span><span>></span> <span> ...</span> <span> <</span><span>/Router</span><span>></span> @@ -20,13 +20,13 @@ pub fn ThemeProviderRoute() -> impl IntoView { </pre>"# .to_string() /> - <p class="my-4">"Load your own stylesheets before ThemeProvider to avoid conflicts."</p> + <p class="my-4">"Load your own stylesheets after ThemeProvider to avoid conflicts."</p> <h2 class="mt-6 text-2xl font-semibold">"Setting the Default Theme and Mode"</h2> <p class="my-4">"By default, the theme provider expands to look like this:"</p> <CodeBlock code=r#"<pre> -<span><</span><span>ThemeProvider mode="auto" theme=Theme::Default></span> +<span><</span><span>ThemeProvider mode="auto" theme=Theme::Neutral></span> </pre>"# .to_string() /> @@ -38,7 +38,7 @@ pub fn ThemeProviderRoute() -> impl IntoView { </ul> <p class="my-4"> - "These modes correspond to parts of the theme, in this case Theme::Default. + "These modes correspond to parts of the theme, in this case Theme::Neutral. Each theme has three parts which are just a collection of CSS variables that override the main template:" </p> diff --git a/docs/src/routes/landing/demos/chat.rs b/docs/src/routes/landing/demos/chat.rs index d19cfc51..731f226f 100644 --- a/docs/src/routes/landing/demos/chat.rs +++ b/docs/src/routes/landing/demos/chat.rs @@ -5,23 +5,31 @@ use singlestage::*; pub fn ChatDemo() -> impl IntoView { view! { <Card> - <CardHeader class="flex items-center gap-4"> - <Avatar class="size-10"> - <AvatarImage src="/avatar-1.png" /> - </Avatar> + <CardHeader> + <div class="flex items-center gap-4"> + <Avatar class="size-10"> + <AvatarImage src="/avatar-1.png" /> + <AvatarFallback>"SD"</AvatarFallback> + </Avatar> - <div class="flex flex-col gap-1 mr-auto"> - <p class="text-sm font-semibold leading-none text-(--card-foreground)"> - "Sofia Davis" - </p> - <p class="text-sm">"m@example.com"</p> - </div> + <div class="flex flex-col gap-1 mr-auto"> + <p class="text-sm font-semibold leading-none text-(--card-foreground)"> + "Sofia Davis" + </p> + <p class="text-sm">"m@example.com"</p> + </div> - <Tooltip value="New message"> - <Button class="rounded-full" size="icon" variant="outline"> - {icon!(icondata::LuPlus)} - </Button> - </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button class="rounded-full" size="icon" variant="outline"> + {icon!(icondata::LuPlus)} + </Button> + </TooltipTrigger> + <TooltipContent> + <p>"New message"</p> + </TooltipContent> + </Tooltip> + </div> </CardHeader> <CardContent> diff --git a/docs/src/routes/landing/demos/payment_method.rs b/docs/src/routes/landing/demos/payment_method.rs index 2eb8aeae..cdbd2859 100644 --- a/docs/src/routes/landing/demos/payment_method.rs +++ b/docs/src/routes/landing/demos/payment_method.rs @@ -46,34 +46,34 @@ pub fn PaymentMethod() -> impl IntoView { <div class="grid flex-1 gap-2"> <Label label_for="payment-method-expiration-month">"Expires"</Label> <Select id="payment-method-expiration-month" class="w-full"> - <SelectItem value="01">"January"</SelectItem> - <SelectItem value="02">"February"</SelectItem> - <SelectItem value="03">"March"</SelectItem> - <SelectItem value="04">"April"</SelectItem> - <SelectItem value="05">"May"</SelectItem> - <SelectItem value="06">"June"</SelectItem> - <SelectItem value="07">"July"</SelectItem> - <SelectItem value="08">"August"</SelectItem> - <SelectItem value="09">"September"</SelectItem> - <SelectItem value="10">"October"</SelectItem> - <SelectItem value="11">"November"</SelectItem> - <SelectItem value="12">"December"</SelectItem> + <SelectOption value="01">"January"</SelectOption> + <SelectOption value="02">"February"</SelectOption> + <SelectOption value="03">"March"</SelectOption> + <SelectOption value="04">"April"</SelectOption> + <SelectOption value="05">"May"</SelectOption> + <SelectOption value="06">"June"</SelectOption> + <SelectOption value="07">"July"</SelectOption> + <SelectOption value="08">"August"</SelectOption> + <SelectOption value="09">"September"</SelectOption> + <SelectOption value="10">"October"</SelectOption> + <SelectOption value="11">"November"</SelectOption> + <SelectOption value="12">"December"</SelectOption> </Select> </div> <div class="grid flex-1 gap-2"> <Label label_for="payment-method-expiration-year">"Year"</Label> <Select id="payment-method-expiration-year" class="w-full"> - <SelectItem value="2024">"2024"</SelectItem> - <SelectItem value="2025">"2025"</SelectItem> - <SelectItem value="2026">"2026"</SelectItem> - <SelectItem value="2027">"2027"</SelectItem> - <SelectItem value="2028">"2028"</SelectItem> - <SelectItem value="2029">"2029"</SelectItem> - <SelectItem value="2030">"2030"</SelectItem> - <SelectItem value="2031">"2031"</SelectItem> - <SelectItem value="2032">"2032"</SelectItem> - <SelectItem value="2033">"2033"</SelectItem> - <SelectItem value="2034">"2034"</SelectItem> + <SelectOption value="2024">"2024"</SelectOption> + <SelectOption value="2025">"2025"</SelectOption> + <SelectOption value="2026">"2026"</SelectOption> + <SelectOption value="2027">"2027"</SelectOption> + <SelectOption value="2028">"2028"</SelectOption> + <SelectOption value="2029">"2029"</SelectOption> + <SelectOption value="2030">"2030"</SelectOption> + <SelectOption value="2031">"2031"</SelectOption> + <SelectOption value="2032">"2032"</SelectOption> + <SelectOption value="2033">"2033"</SelectOption> + <SelectOption value="2034">"2034"</SelectOption> </Select> </div> <div class="grid flex-1 gap-2"> diff --git a/docs/src/routes/landing/demos/report_issue.rs b/docs/src/routes/landing/demos/report_issue.rs index 68a33d1d..fc81b169 100644 --- a/docs/src/routes/landing/demos/report_issue.rs +++ b/docs/src/routes/landing/demos/report_issue.rs @@ -15,20 +15,20 @@ pub fn ReportIssue() -> impl IntoView { <div class="grid gap-2 flex-1"> <Label label_for="report-issue-area">"Area"</Label> <Select id="report-issue-area" class="w-full"> - <SelectItem value="team">"Team"</SelectItem> - <SelectItem value="billing">"Billing"</SelectItem> - <SelectItem value="account">"Account"</SelectItem> - <SelectItem value="deployments">"Deployments"</SelectItem> - <SelectItem value="support">"Support"</SelectItem> + <SelectOption value="team">"Team"</SelectOption> + <SelectOption value="billing">"Billing"</SelectOption> + <SelectOption value="account">"Account"</SelectOption> + <SelectOption value="deployments">"Deployments"</SelectOption> + <SelectOption value="support">"Support"</SelectOption> </Select> </div> <div class="grid gap-2 flex-1"> <Label label_for="report-issue-security-level">"Security Level"</Label> <Select id="report-issue-security-level" class="w-full"> - <SelectItem value="1">"Severity 1 (Highest)"</SelectItem> - <SelectItem value="2">"Severity 2"</SelectItem> - <SelectItem value="3">"Severity 3"</SelectItem> - <SelectItem value="4">"Severity 4 (Lowest)"</SelectItem> + <SelectOption value="1">"Severity 1 (Highest)"</SelectOption> + <SelectOption value="2">"Severity 2"</SelectOption> + <SelectOption value="3">"Severity 3"</SelectOption> + <SelectOption value="4">"Severity 4 (Lowest)"</SelectOption> </Select> </div> </div> @@ -46,7 +46,7 @@ pub fn ReportIssue() -> impl IntoView { id="report-issue-description" placeholder="Please include all information relevant to your issue." class="w-full" - ></Textarea> + /> </div> <CardFooter class="flex items-center gap-4 justify-between"> <Button variant="ghost" size="sm"> diff --git a/docs/src/routes/landing/demos/team_members.rs b/docs/src/routes/landing/demos/team_members.rs index 949408d8..3f1da0d8 100644 --- a/docs/src/routes/landing/demos/team_members.rs +++ b/docs/src/routes/landing/demos/team_members.rs @@ -22,11 +22,11 @@ pub fn TeamMembers() -> impl IntoView { <p class="text-sm text-(--muted-foreground)">"m@example.com"</p> </div> - <Select default="owner"> - <SelectItem value="viewer">"Viewer"</SelectItem> - <SelectItem value="developer">"Developer"</SelectItem> - <SelectItem value="billing">"Billing"</SelectItem> - <SelectItem value="owner">"Owner"</SelectItem> + <Select class="w-fit" default="owner"> + <SelectOption value="viewer">"Viewer"</SelectOption> + <SelectOption value="developer">"Developer"</SelectOption> + <SelectOption value="billing">"Billing"</SelectOption> + <SelectOption value="owner">"Owner"</SelectOption> </Select> </li> @@ -41,12 +41,12 @@ pub fn TeamMembers() -> impl IntoView { <p class="text-sm text-(--muted-foreground)">"p@example.com"</p> </div> - <Select> - <SelectItem value="empty">"Empty"</SelectItem> - <SelectItem value="viewer">"Viewer"</SelectItem> - <SelectItem value="developer">"Developer"</SelectItem> - <SelectItem value="billing">"Billing"</SelectItem> - <SelectItem value="owner">"Owner"</SelectItem> + <Select class="w-fit"> + <SelectOption value="empty">"Empty"</SelectOption> + <SelectOption value="viewer">"Viewer"</SelectOption> + <SelectOption value="developer">"Developer"</SelectOption> + <SelectOption value="billing">"Billing"</SelectOption> + <SelectOption value="owner">"Owner"</SelectOption> </Select> </li> @@ -61,11 +61,11 @@ pub fn TeamMembers() -> impl IntoView { <p class="text-sm text-(--muted-foreground)">"i@example.com"</p> </div> - <Select default="viewer"> - <SelectItem value="viewer">"Viewer"</SelectItem> - <SelectItem value="developer">"Developer"</SelectItem> - <SelectItem value="billing">"Billing"</SelectItem> - <SelectItem value="owner">"Owner"</SelectItem> + <Select class="w-fit" default="viewer"> + <SelectOption value="viewer">"Viewer"</SelectOption> + <SelectOption value="developer">"Developer"</SelectOption> + <SelectOption value="billing">"Billing"</SelectOption> + <SelectOption value="owner">"Owner"</SelectOption> </Select> </li> diff --git a/docs/src/routes/landing/mod.rs b/docs/src/routes/landing/mod.rs index fa7c0f27..37185e00 100644 --- a/docs/src/routes/landing/mod.rs +++ b/docs/src/routes/landing/mod.rs @@ -17,12 +17,12 @@ pub fn LandingPage() -> impl IntoView { </p> <div class="my-6 space-x-2"> - <a href="install"> - <Button>"Get Started"</Button> - </a> - <a href="introduction"> - <Button variant="outline">"Learn More"</Button> - </a> + <Link render_as="button" variant="primary" href="install"> + "Get Started" + </Link> + <Link href="introduction" render_as="button" variant="outline"> + "Learn More" + </Link> </div> </header> diff --git a/docs/src/routes/mod.rs b/docs/src/routes/mod.rs index 2149eea9..f9a68059 100644 --- a/docs/src/routes/mod.rs +++ b/docs/src/routes/mod.rs @@ -1,9 +1,9 @@ mod components; -mod debug; mod getting_started; mod landing; +mod not_found; pub use components::*; -pub use debug::*; pub use getting_started::*; pub use landing::*; +pub use not_found::*; diff --git a/docs/src/routes/not_found.rs b/docs/src/routes/not_found.rs new file mode 100644 index 00000000..f49c991c --- /dev/null +++ b/docs/src/routes/not_found.rs @@ -0,0 +1,34 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn NotFound() -> impl IntoView { + view! { + <Empty> + <EmptyHeader> + <EmptyMedia variant="icon"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + > + <path stroke="none" d="M0 0h24v24H0z" fill="none" /> + <path d="M3 8v3a1 1 0 0 0 1 1h3" /> + <path d="M7 8v8" /> + <path d="M17 8v3a1 1 0 0 0 1 1h3" /> + <path d="M21 8v8" /> + <path d="M10 10v4a2 2 0 1 0 4 0v-4a2 2 0 1 0 -4 0" /> + </svg> + </EmptyMedia> + <EmptyTitle>"Not Found"</EmptyTitle> + <EmptyDescription>"The page you're looking for doesn't exist."</EmptyDescription> + </EmptyHeader> + </Empty> + } +} diff --git a/docs/style/tailwind.css b/docs/style/tailwind.css index f1d8c73c..f5b85b87 100644 --- a/docs/style/tailwind.css +++ b/docs/style/tailwind.css @@ -1 +1,15 @@ @import "tailwindcss"; + +@layer components { + .singlestage-ulist { + @apply list-disc + ml-6 + my-6; + } + + .singlestage-olist { + @apply list-decimal + ml-6 + my-6; + } +} diff --git a/docs_macro/Cargo.toml b/docs_macro/Cargo.toml index 5d1bdf3a..93fdce7b 100644 --- a/docs_macro/Cargo.toml +++ b/docs_macro/Cargo.toml @@ -16,6 +16,7 @@ publish = false proc-macro = true [dependencies] +rayon = { workspace = true } serde = { workspace = true } syntect = { workspace = true } toml = { workspace = true } diff --git a/docs_macro/src/highlight.rs b/docs_macro/src/highlight.rs index 1691d0e4..93378411 100644 --- a/docs_macro/src/highlight.rs +++ b/docs_macro/src/highlight.rs @@ -1,20 +1,20 @@ +use std::sync::Arc; use std::{io::BufRead, path::Path}; +use syntect::highlighting::Color; use syntect::{ easy::HighlightFile, - highlighting::ThemeSet, - html::{ - IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet, - }, + highlighting::Theme, + html::{IncludeBackground, append_highlighted_html_for_styled_line}, parsing::SyntaxSet, }; -pub fn highlight_html_from_file<P: AsRef<Path>>(path: P) -> String { - let ss = SyntaxSet::load_defaults_newlines(); - let ts = ThemeSet::load_defaults(); - let theme = &ts.themes["Solarized (light)"]; - - let mut highlighter = HighlightFile::new(&path, &ss, theme).expect("Error highlighting file"); - let (_, bg) = start_highlighted_html_snippet(theme); +pub fn highlight_html_from_file<P: AsRef<Path>>( + path: P, + ss: Arc<SyntaxSet>, + theme: Arc<&Theme>, + bg: Arc<Color>, +) -> String { + let mut highlighter = HighlightFile::new(&path, &ss, &theme).unwrap(); let mut output = "<pre>".to_string(); let mut line = String::new(); @@ -24,14 +24,15 @@ pub fn highlight_html_from_file<P: AsRef<Path>>(path: P) -> String { .highlight_lines .highlight_line(&line, &ss) .unwrap(); + append_highlighted_html_for_styled_line( ®ions[..], - IncludeBackground::IfDifferent(bg), + IncludeBackground::IfDifferent(*bg), &mut output, ) .unwrap(); - } - line.clear(); + } // until NLL this scope is needed so we can clear the buffer after + line.clear(); // read_line appends so we need to clear between lines } output.push_str("</pre>\n"); output diff --git a/docs_macro/src/lib.rs b/docs_macro/src/lib.rs index 97eeab1c..f5c2a709 100644 --- a/docs_macro/src/lib.rs +++ b/docs_macro/src/lib.rs @@ -1,13 +1,15 @@ extern crate proc_macro; mod highlight; + use highlight::*; use proc_macro::TokenStream; +use rayon::prelude::*; use serde::Deserialize; use std::{ fs::*, io::{BufReader, Read}, - path::Path, + path::{Path, PathBuf}, }; #[derive(Deserialize)] @@ -48,242 +50,232 @@ struct ComponentPage { references: Vec<ComponentReference>, } -/// Converts string to kebab-case -fn string_to_kebab(input: &str) -> String { - input.to_lowercase().replace(" ", "-").replace("_", "-") -} +impl ComponentPage { + fn from_path(path: &PathBuf) -> Self { + let file = File::open(path.join("component.toml")).unwrap(); + let mut buf_reader = BufReader::new(file); + let mut contents = String::new(); -/// Converts word to Title case -fn word_to_title(input: &str) -> String { - if input.is_empty() { - String::new() - } else { - let (head, tail) = input.split_at(1); - let mut buf = head.to_uppercase(); - buf.push_str(tail.to_lowercase().as_str()); - buf + buf_reader + .read_to_string(&mut contents) + .expect("Error reading file"); + + toml::from_str(&contents).expect("Error decoding toml") } } +/// Converts string to kebab-case +fn string_to_kebab(input: &str) -> String { + input + .chars() + .map(|mut c| match c { + ' ' | '_' => '-', + _ => { + c.make_ascii_lowercase(); + c + } + }) + .collect() +} + /// Converts string To Title Case fn string_to_title(input: &str) -> String { - let buf = input - .split(" ") - .flat_map(|word| word.split("_")) - .flat_map(|word| word.split("-")) - .map(|word| format!("{} ", word_to_title(word))) - .collect::<String>(); + let mut buf = String::with_capacity(input.len()); + let mut input_iter = input.char_indices(); + + while let Some((index, mut char)) = input_iter.next() { + if index == 0 { + char.make_ascii_uppercase(); + buf.push(char); + continue; + } - buf.trim().to_string() + match char { + ' ' | '-' | '_' => { + buf.push(' '); + if let Some((_, mut char)) = input_iter.next() { + char.make_ascii_uppercase(); + buf.push(char) + } else { + break; + } + } + _ => { + char.make_ascii_lowercase(); + buf.push(char) + } + } + } + + buf } /// Converts string to PascalCase. fn string_to_pascal(input: &str) -> String { - input - .split(" ") - .flat_map(|word| word.split("_")) - .flat_map(|word| word.split("-")) - .map(word_to_title) - .collect::<String>() + let mut buf = String::with_capacity(input.len()); + let mut input_iter = input.char_indices(); + + while let Some((index, mut char)) = input_iter.next() { + if index == 0 { + char.make_ascii_uppercase(); + buf.push(char); + continue; + } + + buf.push(match char { + ' ' | '-' | '_' => { + if let Some((_, mut char)) = input_iter.next() { + char.make_ascii_uppercase(); + char + } else { + break; + } + } + _ => { + char.make_ascii_lowercase(); + char + } + }) + } + + buf } /// Converts string to snake_case fn string_to_snake(input: &str) -> String { - input.to_lowercase().replace(" ", "_") + input + .chars() + .map(|mut c| { + if c == ' ' { + '_' + } else { + c.make_ascii_lowercase(); + c + } + }) + .collect() } -#[proc_macro] -pub fn generate_component_links(_input: TokenStream) -> TokenStream { - let mut output = String::from("view!{"); +fn get_component_paths() -> Vec<PathBuf> { let comp_path = Path::new("./docs/src/routes/components"); - let mut names = vec![]; - for cur_dir in read_dir(comp_path).expect("Error reading dir") { - let path = cur_dir.expect("Error reading cur dir").path(); + let mut buf = vec![]; - if !path.is_dir() { - continue; - } - - // Skip directories marked with a leading "_" - let component_module_name = path - .to_str() - .unwrap() - .split("/") - .collect::<Vec<&str>>() - .pop() - .unwrap() - .to_owned(); - if component_module_name.chars().next().unwrap() == "_".chars().next().unwrap() { - continue; + read_dir(comp_path).unwrap().into_iter().for_each(|dir| { + let path = dir.unwrap().path(); + if path.is_dir() { + if exists(path.join("component.toml")).unwrap() { + buf.push(path) + } else { + println!( + "WARN: component.toml not found at {}", + path.to_str().unwrap() + ); + } } + }); - if let Ok(file) = File::open(path.join("component.toml")) { - let mut buf_reader = BufReader::new(file); - let mut contents = String::new(); + buf +} - buf_reader - .read_to_string(&mut contents) - .expect("Error reading file"); +#[proc_macro] +pub fn generate_component_links(_input: TokenStream) -> TokenStream { + let mut names = get_component_paths() + .par_iter() + .map(|path| { + let component = ComponentPage::from_path(path); - let decoded: ComponentPage = toml::from_str(&contents).expect("Error decoding toml"); - names.push(decoded.name); - } else { - println!( - "WARN: component.toml not found at {}", - path.to_str().unwrap() - ); - } - } + component.name.to_lowercase() + }) + .collect::<Vec<String>>(); names.sort(); - for name in names { - output.push_str( + + let links = names + .par_iter() + .map(|name| { format!( r#" - <SidebarMenuItem> <SidebarMenuButton> - <A href="/components/{}"> + <Link + href="/components/{}"> <span>"{}"</span> - </A> + </Link> </SidebarMenuButton> </SidebarMenuItem> "#, string_to_kebab(&name), string_to_title(&name) ) - .as_str(), - ) - } - - output.push('}'); + }) + .collect::<String>(); - output.parse().expect("Error parsing output") + format!("view!{{{}}}", links) + .parse() + .expect("Error parsing output") } #[proc_macro] pub fn generate_component_routes(_input: TokenStream) -> TokenStream { - let mut output = String::from( + let routes = get_component_paths() + .par_iter() + .map(|path| { + let component = ComponentPage::from_path(path); + + format!( + r#" + <Route path=StaticSegment("/{}") view={}Route /> +"#, + string_to_kebab(&component.name), + string_to_pascal(&component.name) + ) + }) + .collect::<String>(); + + format!( r#" use leptos_router::MatchNestedRoutes; #[component(transparent)] -pub fn ComponentRoutes() -> impl MatchNestedRoutes + Clone { - view! { +pub fn ComponentRoutes() -> impl MatchNestedRoutes + Clone {{ + view! {{ <ParentRoute path=StaticSegment("/components") - view=move || view! { <Outlet /> } - >"#, - ); - - let comp_path = Path::new("./docs/src/routes/components"); - - for cur_dir in read_dir(comp_path).unwrap() { - let path = cur_dir.unwrap().path(); - if !path.is_dir() { - continue; - } - - // Skip directories marked with a leading "_" - let component_module_name = path - .to_str() - .unwrap() - .split("/") - .collect::<Vec<&str>>() - .pop() - .unwrap() - .to_owned(); - if component_module_name.chars().next().unwrap() == "_".chars().next().unwrap() { - continue; - } - - if let Ok(file) = File::open(path.join("component.toml")) { - let mut buf_reader = BufReader::new(file); - let mut contents = String::new(); - - buf_reader.read_to_string(&mut contents).unwrap(); - - let decoded: ComponentPage = toml::from_str(&contents).unwrap(); - - output.push_str( - format!( - r#" - <Route path=StaticSegment("/{}") view={}Route />"#, - string_to_kebab(&decoded.name), - string_to_pascal(&decoded.name) - ) - .as_str(), - ) - } else { - println!( - "WARN: component.toml not found at {}", - path.to_str().unwrap() - ); - } - } - - output.push_str( - r#" + view=move || view! {{ <Outlet /> }} + >{} </ParentRoute> - } + }} .into_inner() -}"#, - ); - - output.parse().expect("Error parsing output") +}}"#, + &routes + ) + .parse() + .expect("Error parsing output") } #[proc_macro] pub fn generate_component_pages(_input: TokenStream) -> TokenStream { - let mut output = String::new(); - let comp_path = Path::new("./docs/src/routes/components"); - - for cur_dir in read_dir(comp_path) - .unwrap_or_else(|_| panic!("Error reading dir {}", &comp_path.to_str().unwrap())) - { - let path = cur_dir.unwrap().path(); - if !path.is_dir() { - continue; - } - - let component_module_name = path - .to_str() - .unwrap() - .split("/") - .collect::<Vec<&str>>() - .pop() - .unwrap() - .to_owned(); - - // Skip directories marked with a leading "_" - if component_module_name.chars().next().unwrap() == "_".chars().next().unwrap() { - continue; - } - - // Read component.toml - let component_toml = File::open(path.join("component.toml")); - if component_toml.is_err() { - println!( - "WARN: component.toml not found at {}", - path.to_str().unwrap() - ); - continue; - } - let component_toml = component_toml.unwrap(); - let mut contents = String::new(); - let mut buf_reader = BufReader::new(component_toml); - buf_reader - .read_to_string(&mut contents) - .expect("Error reading file"); - - let decoded: ComponentPage = toml::from_str(&contents).expect("Error reading toml"); - - // Generate info block - let mut info = String::new(); - - if let Some(special_info) = decoded.info { - info.push_str( + // Set up some syntax highlighting stuff up front so it's not done per thread + use std::sync::Arc; + use syntect::{ + highlighting::ThemeSet, html::start_highlighted_html_snippet, parsing::SyntaxSet, + }; + + let ss = Arc::new(SyntaxSet::load_defaults_newlines()); + let ts = ThemeSet::load_defaults(); + let theme = Arc::new(&ts.themes["Solarized (light)"]); + let (_, bg) = start_highlighted_html_snippet(&theme); + let bg = Arc::new(bg); + + get_component_paths() + .par_iter() + .map(|path| { + let component = ComponentPage::from_path(&path); + let component_module_name = path.iter().last().unwrap().to_str().unwrap(); + + // Generate info block + let info = if let Some(special_info) = component.info { format!( r#"<Alert> {{icon!(icondata::LuInfo)}} @@ -293,116 +285,114 @@ pub fn generate_component_pages(_input: TokenStream) -> TokenStream { "#, special_info.title, special_info.description ) - .as_str(), - ); - } - - // Generate examples - let mut examples = String::new(); - - if let Some(decoded_examples) = decoded.examples { - for example in decoded_examples { - let mut title = String::new(); - if let Some(example_title) = example.title { - title = format!(r#"name="{}" "#, example_title); - } - - let mut description = String::new(); - if let Some(example_description) = example.description { - description = format!(r#"description="{}" "#, example_description); - } - - let path = format!( - "./docs/src/routes/components/{}/examples/{}.rs", - component_module_name, - string_to_snake(&example.name) - ); - - let example_code = highlight_html_from_file(path); - - examples.push_str( - format!( - r##"<Example {}{}view={}Example.into_any() code=r#"{}"# /> + } else { + String::new() + }; + + // Generate examples + let examples = if let Some(component_examples) = component.examples { + Some( + component_examples + .par_iter() + .map(|example| { + let title = if let Some(example_title) = example.title.to_owned() { + Some(format!(r#"name="{}" "#, example_title)) + } else { + None + }; + + let description = + if let Some(example_description) = example.description.to_owned() { + Some(format!(r#"description="{}" "#, example_description)) + } else { + None + }; + + let path = format!( + "./docs/src/routes/components/{}/examples/{}.rs", + component_module_name, + string_to_snake(&example.name) + ); + + let example_code = highlight_html_from_file( + path, + ss.clone(), + theme.clone(), + bg.clone(), + ); + + format!( + r##"<Example {}{}view={}Example.into_any() code=r#"{}"# full_size={} /> "##, - title.as_str(), - description.as_str(), - string_to_pascal(&example.name), - example_code - ) - .as_str(), - ); - } - } - - // Generate API references - let mut references = String::new(); - for reference in decoded.references { - let mut attrs = String::new(); - - // HACK: do this right, don't duplicate - if reference.attrs.is_empty() { - references.push_str( - format!( - r###" - <Reference - name="{}" - description="{}" - extra=r##"{}"## - table=None - /> -"###, - reference.name, - reference.description, - reference.extra.unwrap_or_default(), - ) - .as_str(), + title.unwrap_or_default(), + description.unwrap_or_default(), + string_to_pascal(&example.name), + example_code, + example.name.starts_with("sidebar") + || example.name.contains("background") + ) + }) + .collect::<String>(), ) } else { - for attr in reference.attrs { - attrs.push_str( - format!( - r##" + None + }; + + // Generate API references + let references = component + .references + .par_iter() + .map(|reference| { + let attrs = if reference.attrs.is_empty() { + None + } else { + let mut buf = reference + .attrs + .par_iter() + .map(|attr| { + format!( + r##" (r#"{}"#,r#"{}"#,r#"{}"#,r#"{}"#),"##, - attr.attr, - attr.attr_type, - attr.default, - attr.description.unwrap_or_default() - ) - .as_str(), - ) - } - // Remove trailing comma - let _ = attrs.pop(); + attr.attr, + attr.attr_type, + attr.default, + attr.description.to_owned().unwrap_or_default() + ) + }) + .collect::<String>(); + // Remove trailing comma + let _ = buf.pop(); + Some(buf) + }; - references.push_str( format!( r###" <Reference name="{}" description="{}" extra=r##"{}"## - table=Some(attr_rows!({}) - .into_any()) + table={} /> "###, reference.name, reference.description, - reference.extra.unwrap_or_default(), - attrs, + reference.extra.to_owned().unwrap_or_default(), + if let Some(attrs) = attrs { + format!("Some(attr_rows!({}).into_any())", attrs) + } else { + "None".into() + }, ) - .as_str(), - ) - } - } + }) + .collect::<String>(); - // Generate anatomy snippet - let path = format!( - "./docs/src/routes/components/{}/anatomy.rs", - component_module_name - ); - let anatomy = highlight_html_from_file(path); + // Generate anatomy snippet + let path = format!( + "./docs/src/routes/components/{}/anatomy.rs", + component_module_name + ); + let anatomy = highlight_html_from_file(path, ss.clone(), theme.clone(), bg.clone()); - output.push_str( format!( r##" mod {}; @@ -423,16 +413,16 @@ pub fn {}Route() -> impl IntoView {{ "##, component_module_name, component_module_name, - string_to_pascal(&decoded.name), - string_to_title(&decoded.name), - decoded.description, + string_to_pascal(&component.name), + string_to_title(&component.name), + component.description, info, - examples, + examples.unwrap_or_default(), anatomy, references ) - .as_str(), - ); - } - output.parse().expect("Error parsing output") + }) + .collect::<String>() + .parse() + .expect("Error parsing output") } diff --git a/leptosfmt.toml b/leptosfmt.toml new file mode 100644 index 00000000..03a0b8b4 --- /dev/null +++ b/leptosfmt.toml @@ -0,0 +1,2 @@ +indentation_style = "Spaces" +attr_value_brace_style = "Preserve" diff --git a/singlestage/Cargo.toml b/singlestage/Cargo.toml index 275f1a60..d5ef6d6a 100644 --- a/singlestage/Cargo.toml +++ b/singlestage/Cargo.toml @@ -13,15 +13,18 @@ keywords.workspace = true categories.workspace = true [dependencies] -singlestage_macro = { version = "0", optional = true } -reactive_stores = { version = "0.3", optional = true } +singlestage_macro = { workspace = true, optional = true } +reactive_stores = { workspace = true, optional = true } leptos = { workspace = true } leptos_meta = { workspace = true } +leptos_router = { workspace = true } serde = { workspace = true, optional = true } 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 } @@ -31,10 +34,12 @@ ssr = ["leptos/ssr"] hydrate = ["leptos/hydrate"] islands = ["serde/derive", "leptos/islands"] nightly = ["leptos/nightly"] +nonce = ["leptos/nonce", "leptos_meta/nonce"] stores = ["dep:reactive_stores"] default = [ "accordion", "alert", + "alert_dialog", "aspect_ratio", "avatar", "badge", @@ -44,6 +49,7 @@ default = [ "card", "carousel", "checkbox", + "collapsible", "context_menu", "dialog", "dropdown", @@ -63,10 +69,18 @@ default = [ "scroll_area", "select", "separator", + "sheet", "sidebar", "skeleton", "slider", "spinner", + "style_luma", + "style_lyra", + "style_maia", + "style_mira", + "style_nova", + "style_sera", + "style_vega", "switch", "table", "tabs", @@ -78,18 +92,20 @@ default = [ accordion = [] alert = [] +alert_dialog = ["dialog"] aspect_ratio = [] avatar = [] badge = [] -breadcrumb = ["dropdown"] +breadcrumb = [] button = ["input_group"] button_group = [] card = [] carousel = [] checkbox = ["field", "label"] +collapsible = [] context_menu = [] dialog = [] -dropdown = ["button"] +dropdown = ["popover"] empty = [] icon = ["macro"] field = [] @@ -107,10 +123,18 @@ radio = ["field", "label"] scroll_area = [] select = ["field"] separator = [] +sheet = ["button"] 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 7f9e899e..57f8214c 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,36 +7,19 @@ use std::{ process::Command, }; -const TAILWIND_URL: &str = "https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.17/"; +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).unwrap_or_else(|_| panic!("Error opening {}", &input.display())); - let mut buf_reader = BufReader::new(file); - 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"); + if let Ok(output) = output { + output.status.success() + } else { + false + } } -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() @@ -65,39 +43,34 @@ fn download_file(download_url: &str, file_path: &PathBuf) { } fn run_tailwind( - tailwind_path: Option<&Path>, + tailwind_path: &PathBuf, bundle_path: &PathBuf, - singlestage_path: &PathBuf, -) -> Result<(), ()> { - let output; - - if let Some(tailwind_path) = tailwind_path { - output = Command::new(tailwind_path) - .arg("-i") - .arg(bundle_path) - .arg("-o") - .arg(singlestage_path) - .arg("-m") - .output() - } else { - output = Command::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() { - Ok(()) - } else { - let error = String::from_utf8(output.stderr).unwrap(); - panic!("{}", error); - } - } else { - Err(()) + let _cleanup = remove_file(&output_path); + + let output = Command::new(&tailwind_path) + .arg("-i") + .arg(&bundle_path) + .arg("-o") + .arg(output_path) + .arg("-m") + .output(); + + if let Ok(output) = output + && !output.status.success() + { + let error = String::from_utf8(output.stderr).unwrap(); + panic!("\nError in {:#?}\n\n{}", bundle_path, error); } } @@ -107,36 +80,145 @@ 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 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"); - // 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::<Vec<&str>>(); + 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 + let features = [ "accordion", "alert", + "alert_dialog", "aspect_ratio", "avatar", "badge", "breadcrumb", "button", - "button_group", "card", "carousel", "checkbox", - "context_menu", "dialog", "dropdown", "empty", - "field", "input", - "input_group", "item", "kbd", "label", @@ -148,6 +230,7 @@ fn main() { "scroll_area", "select", "separator", + "sheet", "sidebar", "skeleton", "slider", @@ -157,165 +240,130 @@ 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"); - - // Theme provider goes first - #[cfg(feature = "theme_provider")] - let main_css_path = Path::new("src") - .join("components") - .join("theme_provider") - .join("main.css"); - #[cfg(feature = "theme_provider")] - bundle_css(main_css_path, &bundle); - - // 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_css = Path::new("src") - .join("components") - .join(feature) - .join(format!("{}.css", &feature)); - bundle_css(feature_css, &bundle); - } - } - - // 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, - &singlestage_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, &singlestage_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") - } + "tooltip", + // Components that override + "input_group", + "button_group", + "field", + ]; - println!("Filename: {}", filename); + let base_styles = ["luma", "lyra", "maia", "mira", "nova", "sera", "vega"]; - // 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, - &singlestage_path, + let mut css_raw = String::new(); + let _ = File::open( + &Path::new("src") + .join("components") + .join("theme_provider") + .join("main.css"), ) - .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::<Vec<&str>>(); - 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, - &singlestage_path, - ); + .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"), + ) + .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::<String>() + ); + let bundle_path = Path::new(&out_dir).join(format!("bundle{}.css", mode)); + let output_path = Path::new(&out_dir).join(format!("singlestage{}.css", mode)); + println!("Bundling css{}...", mode); + run_tailwind(&tailwind_path, &bundle_path, &output_path, component_css); + // TODO: Reduce dark style bloat + + 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::<String>() + ); + 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)); + println!("Bundling base_{}{}...", base_style, mode); + run_tailwind(&tailwind_path, &bundle_path, &output_path, base_css); + // TODO: Reduce dark style bloat + // TODO: Reduce base style bloat + } + }); + }); + + println!("Finished!"); } diff --git a/singlestage/src/components/accordion/accordion.css b/singlestage/src/components/accordion/accordion.css deleted file mode 100644 index 83f378f1..00000000 --- a/singlestage/src/components/accordion/accordion.css +++ /dev/null @@ -1,73 +0,0 @@ -@layer components { - .singlestage-accordion { - /* AccordionItem */ - details { - @apply border-b - last:border-b-0; - - /* AccordionTrigger */ - &[open] > summary > h3 > svg { - @apply rotate-180; - } - - summary { - @apply focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - list-none - outline-none - rounded-md - transition-all - w-full; - - /* Safari */ - &::-webkit-details-marker { - @apply hidden; - } - - > h3 { - @apply flex - flex-1 - font-medium - gap-4 - hover:underline - items-start - justify-between - py-4 - text-left - text-sm; - - > svg { - @apply duration-200 - pointer-events-none - shrink-0 - size-4 - text-muted-foreground - transition-transform - translate-y-0.5; - } - } - } - - /* AccordionContent */ - &::details-content { - block-size: 0; - @apply block - opacity-0 - transition-all - transition-discrete; - } - - &[open]::details-content { - block-size: auto; - block-size: calc-size(auto, size); - @apply opacity-100; - } - - section { - @apply pb-4 - text-sm; - } - } - } -} diff --git a/singlestage/src/components/accordion/content.rs b/singlestage/src/components/accordion/content.rs index 633891fa..6de3a9a0 100644 --- a/singlestage/src/components/accordion/content.rs +++ b/singlestage/src/components/accordion/content.rs @@ -115,7 +115,6 @@ pub fn AccordionContent( 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() @@ -151,8 +150,14 @@ pub fn AccordionContent( }; view! { - <section {..global_attrs_1} {..global_attrs_2}> - {children()} + <section + class=move || { + format!("singlestage-accordion-content {} ", class.get().unwrap_or_default()) + } + {..global_attrs_1} + {..global_attrs_2} + > + <div class="singlestage-accordion-content-inner">{children()}</div> </section> } } diff --git a/singlestage/src/components/accordion/item.rs b/singlestage/src/components/accordion/item.rs index f71cfca6..73361009 100644 --- a/singlestage/src/components/accordion/item.rs +++ b/singlestage/src/components/accordion/item.rs @@ -125,7 +125,6 @@ pub fn AccordionItem( 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() @@ -162,6 +161,7 @@ pub fn AccordionItem( view! { <details + class=move || format!("singlestage-accordion-item {}", class.get().unwrap_or_default()) open=move || open.get() name=move || name.get() diff --git a/singlestage/src/components/accordion/style/accordion.css b/singlestage/src/components/accordion/style/accordion.css new file mode 100644 index 00000000..b188c3eb --- /dev/null +++ b/singlestage/src/components/accordion/style/accordion.css @@ -0,0 +1,81 @@ +@layer components { + .singlestage-accordion { + @apply flex + flex-col + w-full; + } + + .singlestage-accordion-item { + &::details-content { + block-size: 0; + @apply block + opacity-0 + transition-all + transition-discrete; + } + + &[open]::details-content { + block-size: auto; + block-size: calc-size(auto, size); + @apply opacity-100; + } + + &[open] { + svg { + @apply rotate-180; + } + } + } + + .singlestage-accordion-trigger { + @apply border + border-transparent + items-start + list-none + outline-none + relative + transition-all + transition-discrete; + + &:disabled { + @apply opacity-50 + pointer-events-none; + } + + /* Safari */ + &::-webkit-details-marker { + @apply hidden; + } + + > h3 { + @apply flex + flex-1 + justify-between + pointer-events-none; + + > svg { + @apply duration-150 + transition-transform; + } + } + } + + .singlestage-accordion-content { + @apply overflow-hidden; + } + + .singlestage-accordion-content-inner { + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + + p:not(:last-child) { + @apply mb-4; + } + } +} diff --git a/singlestage/src/components/accordion/style/base/luma/accordion.css b/singlestage/src/components/accordion/style/base/luma/accordion.css new file mode 100644 index 00000000..e3418d19 --- /dev/null +++ b/singlestage/src/components/accordion/style/base/luma/accordion.css @@ -0,0 +1,49 @@ +@layer components1 { + .singlestage-accordion { + @apply border + overflow-hidden + rounded-2xl; + } + + .singlestage-accordion-item { + &:not(&:last-child) { + @apply border-b; + } + + &[open] { + @apply bg-muted/50; + } + } + + .singlestage-accordion-trigger { + @apply font-medium + gap-6 + p-4 + text-left + text-sm; + + &:hover { + @apply underline; + } + + > h3 { + > svg { + @apply ml-auto + size-4 + text-muted-foreground; + } + } + } + + .singlestage-accordion-content { + @apply duration-150 + px-4 + text-sm + transition-all; + } + + .singlestage-accordion-content-inner { + @apply pb-4 + pt-0; + } +} diff --git a/singlestage/src/components/accordion/style/base/lyra/accordion.css b/singlestage/src/components/accordion/style/base/lyra/accordion.css new file mode 100644 index 00000000..137be706 --- /dev/null +++ b/singlestage/src/components/accordion/style/base/lyra/accordion.css @@ -0,0 +1,46 @@ +@layer components1 { + .singlestage-accordion-item:not(&:last-child) { + @apply border-b; + } + + .singlestage-accordion-trigger { + @apply font-medium + py-2.5 + rounded-none + text-left + text-xs; + + &:focus-visible { + @apply border-ring + ring-1 + ring-ring/50; + + &::after { + @appy border-ring; + } + } + + &:hover { + @apply underline; + } + + > h3 { + > svg { + @apply ml-auto + size-4 + text-muted-foreground; + } + } + } + + .singlestage-accordion-content { + @apply duration-150 + text-xs + transition-all; + } + + .singlestage-accordion-content-inner { + @apply pb-2.5 + pt-0; + } +} diff --git a/singlestage/src/components/accordion/style/base/maia/accordion.css b/singlestage/src/components/accordion/style/base/maia/accordion.css new file mode 100644 index 00000000..e3418d19 --- /dev/null +++ b/singlestage/src/components/accordion/style/base/maia/accordion.css @@ -0,0 +1,49 @@ +@layer components1 { + .singlestage-accordion { + @apply border + overflow-hidden + rounded-2xl; + } + + .singlestage-accordion-item { + &:not(&:last-child) { + @apply border-b; + } + + &[open] { + @apply bg-muted/50; + } + } + + .singlestage-accordion-trigger { + @apply font-medium + gap-6 + p-4 + text-left + text-sm; + + &:hover { + @apply underline; + } + + > h3 { + > svg { + @apply ml-auto + size-4 + text-muted-foreground; + } + } + } + + .singlestage-accordion-content { + @apply duration-150 + px-4 + text-sm + transition-all; + } + + .singlestage-accordion-content-inner { + @apply pb-4 + pt-0; + } +} diff --git a/singlestage/src/components/accordion/style/base/mira/accordion.css b/singlestage/src/components/accordion/style/base/mira/accordion.css new file mode 100644 index 00000000..1e41f54d --- /dev/null +++ b/singlestage/src/components/accordion/style/base/mira/accordion.css @@ -0,0 +1,49 @@ +@layer components1 { + .singlestage-accordion { + @apply border + overflow-hidden + rounded-md; + } + + .singlestage-accordion-item { + &:not(&:last-child) { + @apply border-b; + } + + #[open] { + @apply bg-muted/50; + } + } + + .singlestage-accordion-trigger { + @apply font-medium + gap-6 + p-2 + text-left + text-xs/relaxed; + + &:hover { + @apply underline; + } + + > h3 { + > svg { + @apply ml-auto + size-4 + text-muted-foreground; + } + } + } + + .singlestage-accordion-content { + @apply duration-150 + px-2 + text-xs/relaxed + transition-all; + } + + .singlestage-accordion-content-inner { + @apply pb-4 + pt-0; + } +} diff --git a/singlestage/src/components/accordion/style/base/nova/accordion.css b/singlestage/src/components/accordion/style/base/nova/accordion.css new file mode 100644 index 00000000..e7fa1a53 --- /dev/null +++ b/singlestage/src/components/accordion/style/base/nova/accordion.css @@ -0,0 +1,45 @@ +@layer components1 { + .singlestage-accordion-item { + &:not(&:last-child) { + @apply border-b; + } + } + + .singlestage-accordion-trigger { + @apply font-medium + py-2.5 + rounded-lg + text-left + text-sm; + + &:focus-visible { + @apply after:border-ring + border-ring + ring-3 + ring-ring/50; + } + + &:hover { + @apply underline; + } + + > h3 { + > svg { + @apply ml-auto + size-4 + text-muted-foreground; + } + } + } + + .singlestage-accordion-content { + @apply duration-150 + text-sm + transition-all; + } + + .singlestage-accordion-content-inner { + @apply pb-2.5 + pt-0; + } +} diff --git a/singlestage/src/components/accordion/style/base/sera/accordion.css b/singlestage/src/components/accordion/style/base/sera/accordion.css new file mode 100644 index 00000000..1ec75417 --- /dev/null +++ b/singlestage/src/components/accordion/style/base/sera/accordion.css @@ -0,0 +1,46 @@ +@layer components1 { + .singlestage-accordion-item { + &:not(&:last-child) { + @apply border-b; + } + } + + .singlestage-accordion-trigger { + @apply font-semibold + gap-6 + hover:underline + py-4 + rounded-none + text-left + text-sm; + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:hover { + @apply underline; + } + + > h3 { + > svg { + @apply ml-auto + size-3.5 + text-muted-foreground; + } + } + } + + .singlestage-accordion-content { + @apply duration-150 + text-sm + transition-all; + } + + .singlestage-accordion-content-inner { + @apply pb-4 + pt-0; + } +} diff --git a/singlestage/src/components/accordion/style/base/vega/accordion.css b/singlestage/src/components/accordion/style/base/vega/accordion.css new file mode 100644 index 00000000..1e96b2ac --- /dev/null +++ b/singlestage/src/components/accordion/style/base/vega/accordion.css @@ -0,0 +1,46 @@ +@layer components1 { + .singlestage-accordion-item:not(&:last-child) { + @apply border-b; + } + + .singlestage-accordion-trigger { + @apply font-medium + py-4 + rounded-md + text-left + text-sm; + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + + &::after { + @appy border-ring; + } + } + + &:hover { + @apply underline; + } + + > h3 { + > svg { + @apply ml-auto + size-4 + text-muted-foreground; + } + } + } + + .singlestage-accordion-content-inner { + @apply pb-4 + pt-0; + } + + .singlestage-accordion-content { + @apply duration-150 + text-sm + transition-all; + } +} diff --git a/singlestage/src/components/accordion/trigger.rs b/singlestage/src/components/accordion/trigger.rs index f4d14e56..de10ee58 100644 --- a/singlestage/src/components/accordion/trigger.rs +++ b/singlestage/src/components/accordion/trigger.rs @@ -161,11 +161,11 @@ pub fn AccordionTrigger( }; view! { - <summary> + <summary class="singlestage-accordion-trigger"> <h3 {..global_attrs_1} {..global_attrs_2}> {children()} - {if accordion_icon.is_some() { - (accordion_icon.unwrap().children)().into_any() + {if let Some(accordion_icon) = accordion_icon { + (accordion_icon.children)().into_any() } else { view! { <svg diff --git a/singlestage/src/components/alert/action.rs b/singlestage/src/components/alert/action.rs new file mode 100644 index 00000000..7a9eef43 --- /dev/null +++ b/singlestage/src/components/alert/action.rs @@ -0,0 +1,164 @@ +use leptos::prelude::*; + +/// Displays actionable inputs in the top right corner of the alert. +#[component] +pub fn AlertAction( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + 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! { + <div + class=move || { + format!("singlestage-alert-action {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + } +} diff --git a/singlestage/src/components/alert/alert.css b/singlestage/src/components/alert/alert.css deleted file mode 100644 index 79462de5..00000000 --- a/singlestage/src/components/alert/alert.css +++ /dev/null @@ -1,59 +0,0 @@ -@layer components { - .singlestage-alert, - .singlestage-alert-destructive { - @apply [&>svg]:size-4 - [&>svg]:text-current - [&>svg]:translate-y-0.5 - border - gap-y-0.5 - grid - grid-cols-[0_1fr] - has-[>svg]:gap-x-3 - has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] - items-start - px-4 - py-3 - relative - rounded-lg - text-sm - w-full; - - h2 { - @apply col-start-2 - font-medium - line-clamp-1 - min-h-4 - tracking-tight; - } - section { - @apply [&_p]:leading-relaxed - col-start-2 - gap-1 - grid - justify-items-start - text-muted-foreground - text-sm; - - ul { - @apply list-disc - list-inside - text-sm; - } - } - } - - .singlestage-alert { - @apply bg-card - text-card-foreground; - } - - .singlestage-alert-destructive { - @apply [&>svg]:text-current - bg-card - text-destructive; - - section { - @apply text-destructive; - } - } -} diff --git a/singlestage/src/components/alert/alert.rs b/singlestage/src/components/alert/alert.rs index 66158ed9..606c565f 100644 --- a/singlestage/src/components/alert/alert.rs +++ b/singlestage/src/components/alert/alert.rs @@ -5,8 +5,9 @@ use leptos::prelude::*; pub fn Alert( children: Children, - /// Set the variant of the alert. Leave empty for default. - /// Other variants: "destructive" + /// Set the variant of the alert. + /// + /// Accepted values: "destructive" #[prop(optional, into)] variant: MaybeProp<String>, @@ -158,15 +159,15 @@ pub fn Alert( <div class=move || { format!( - "{} {}", + "singlestage-alert {} {}", match variant.get().unwrap_or_default().as_str() { - "destructive" => "singlestage-alert-destructive", - "alert" => "singlestage-alert", - _ => "singlestage-alert", + "destructive" => "singlestage-alert-variant-destructive", + _ => "singlestage-alert-variant-default", }, class.get().unwrap_or_default(), ) } + {..global_attrs_1} {..global_attrs_2} > diff --git a/singlestage/src/components/alert/description.rs b/singlestage/src/components/alert/description.rs index bc3fa3c0..31ec48f8 100644 --- a/singlestage/src/components/alert/description.rs +++ b/singlestage/src/components/alert/description.rs @@ -115,7 +115,6 @@ pub fn AlertDescription( 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() @@ -151,7 +150,14 @@ pub fn AlertDescription( }; view! { - <section {..global_attrs_1} {..global_attrs_2}> + <section + class=move || { + format!("singlestage-alert-description {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > {children()} </section> } diff --git a/singlestage/src/components/alert/mod.rs b/singlestage/src/components/alert/mod.rs index 9e9457ab..4a0756c4 100644 --- a/singlestage/src/components/alert/mod.rs +++ b/singlestage/src/components/alert/mod.rs @@ -1,7 +1,9 @@ +mod action; mod alert; mod description; mod title; +pub use action::*; pub use alert::*; pub use description::*; pub use title::*; diff --git a/singlestage/src/components/alert/style/alert.css b/singlestage/src/components/alert/style/alert.css new file mode 100644 index 00000000..15063cc2 --- /dev/null +++ b/singlestage/src/components/alert/style/alert.css @@ -0,0 +1,32 @@ +@layer components { + .singlestage-alert { + @apply relative + w-full; + } + + .singlestage-alert-title { + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } + + .singlestage-alert-description { + @apply md:text-pretty + text-balance + text-muted-foreground; + + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } +} diff --git a/singlestage/src/components/alert/style/base/luma/alert.css b/singlestage/src/components/alert/style/base/luma/alert.css new file mode 100644 index 00000000..411b02dc --- /dev/null +++ b/singlestage/src/components/alert/style/base/luma/alert.css @@ -0,0 +1,75 @@ +@layer components1 { + .singlestage-alert { + @apply border + gap-0.5 + grid + px-4 + py-3 + rounded-2xl + text-left + text-sm; + + &:has(> svg) { + @apply gap-x-2.5 + grid-cols-[auto_1fr]; + + .singlestage-alert-title { + @apply col-start-2; + } + } + + &:has(.singlestage-alert-action) { + @apply pr-18 + relative; + } + + svg { + @apply row-span-2 + text-current + translate-y-0.5; + + &:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-alert-variant-default { + @apply bg-card + text-card-foreground; + } + + .singlestage-alert-variant-destructive { + @apply bg-card + text-destructive; + + .singlestage-alert-description { + @apply text-destructive/90; + } + + svg { + @apply text-current; + } + } + + .singlestage-alert-title { + @apply font-medium; + } + + .singlestage-alert-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-sm; + + p:not(:last-child) { + @apply mb-4; + } + } + + .singlestage-alert-action { + @apply absolute + right-3 + top-2.5; + } +} diff --git a/singlestage/src/components/alert/style/base/lyra/alert.css b/singlestage/src/components/alert/style/base/lyra/alert.css new file mode 100644 index 00000000..7bc63284 --- /dev/null +++ b/singlestage/src/components/alert/style/base/lyra/alert.css @@ -0,0 +1,73 @@ +@layer components1 { + .singlestage-alert { + @apply border + gap-0.5 + grid + px-2.5 + py-2 + rounded-none + text-left + text-xs; + &:has(> svg) { + @apply gap-x-2 + grid-cols-[auto_1fr]; + + .singlestage-alert-title { + @apply col-start-2; + } + } + + &:has(.singlestage-alert-action) { + @apply pr-18 + relative; + } + svg { + @apply row-span-2 + text-current + translate-y-0; + + &:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-alert-variant-default { + @apply bg-card + text-card-foreground; + } + + .singlestage-alert-variant-destructive { + @apply bg-card + text-destructive; + + .singlestage-alert-description { + @apply text-destructive/90; + } + + svg { + @apply text-current; + } + } + + .singlestage-alert-title { + @apply font-medium; + } + + .singlestage-alert-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-xs/relaxed; + + p:not(:last-child) { + @apply mb-4; + } + } + + .singlestage-alert-action { + @apply absolute + right-[calc(--spacing(1.25))] + top-[calc(--spacing(1.25))]; + } +} diff --git a/singlestage/src/components/alert/style/base/maia/alert.css b/singlestage/src/components/alert/style/base/maia/alert.css new file mode 100644 index 00000000..4b22f191 --- /dev/null +++ b/singlestage/src/components/alert/style/base/maia/alert.css @@ -0,0 +1,66 @@ +@layer components1 { + .singlestage-alert { + @apply *:[svg:not([class*='size-'])]:size-4 + *:[svg]:row-span-2 + *:[svg]:text-current + *:[svg]:translate-y-0.5 + border + gap-0.5 + grid + has-data-[slot=alert-action]:pr-18 + has-data-[slot=alert-action]:relative + px-4 + py-3 + rounded-lg + text-left + text-sm; + + &:has(> svg) { + @apply gap-x-2.5 + grid-cols-[auto_1fr]; + + .singlestage-alert-title { + @apply col-start-2; + } + } + } + + .singlestage-alert-variant-default { + @apply bg-card + text-card-foreground; + } + + .singlestage-alert-variant-destructive { + @apply bg-card + text-destructive; + + .singlestage-alert-description { + @apply text-destructive/90; + } + + svg { + @apply text-current; + } + } + + .singlestage-alert-title { + @apply font-medium; + } + + .singlestage-alert-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-sm; + + p:not(:last-child) { + @apply mb-4; + } + } + + .singlestage-alert-action { + @apply absolute + right-3 + top-2.5; + } +} diff --git a/singlestage/src/components/alert/style/base/mira/alert.css b/singlestage/src/components/alert/style/base/mira/alert.css new file mode 100644 index 00000000..ef8c74fb --- /dev/null +++ b/singlestage/src/components/alert/style/base/mira/alert.css @@ -0,0 +1,75 @@ +@layer components1 { + .singlestage-alert { + @apply border + gap-0.5 + grid + px-2 + py-1.5 + rounded-lg + text-left + text-xs/relaxed; + + &:has(> svg) { + @apply gap-x-1.5 + grid-cols-[auto_1fr]; + + .singlestage-alert-title { + @apply col-start-2; + } + } + + &:has(.singlestage-alert-action) { + @apply pr-18 + relative; + } + + svg { + @apply row-span-2 + text-current + translate-y-0.5; + + &:not([class*="size-"]) { + @apply size-3.5; + } + } + } + + .singlestage-alert-variant-default { + @apply bg-card + text-card-foreground; + } + + .singlestage-alert-variant-destructive { + @apply bg-card + text-destructive; + + .singlestage-alert-description { + @apply text-destructive/90; + } + + svg { + @apply text-current; + } + } + + .singlestage-alert-title { + @apply font-medium; + } + + .singlestage-alert-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-xs/relaxed; + + p:not(:last-child) { + @apply mb-4; + } + } + + .singlestage-alert-action { + @apply absolute + right-2 + top-1.5; + } +} diff --git a/singlestage/src/components/alert/style/base/nova/alert.css b/singlestage/src/components/alert/style/base/nova/alert.css new file mode 100644 index 00000000..ecae6b7f --- /dev/null +++ b/singlestage/src/components/alert/style/base/nova/alert.css @@ -0,0 +1,75 @@ +@layer components1 { + .singlestage-alert { + @apply border + gap-0.5 + grid + px-2.5 + py-2 + rounded-lg + text-left + text-sm; + + &:has(> svg) { + @apply gap-x-2 + grid-cols-[auto_1fr]; + + .singlestage-alert-title { + @apply col-start-2; + } + } + + &:has(.singlestage-alert-action) { + @apply pr-18 + relative; + } + + svg { + @apply row-span-2 + text-current + translate-y-0.5; + + &:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-alert-variant-default { + @apply bg-card + text-card-foreground; + } + + .singlestage-alert-variant-destructive { + @apply bg-card + text-destructive; + + .singlestage-alert-description { + @apply text-destructive/90; + } + + svg { + @apply text-current; + } + } + + .singlestage-alert-title { + @apply font-medium; + } + + .singlestage-alert-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-sm; + + p:not(:last-child) { + @apply mb-4; + } + } + + .singlestage-alert-action { + @apply absolute + right-2 + top-2; + } +} diff --git a/singlestage/src/components/alert/style/base/sera/alert.css b/singlestage/src/components/alert/style/base/sera/alert.css new file mode 100644 index 00000000..54135a32 --- /dev/null +++ b/singlestage/src/components/alert/style/base/sera/alert.css @@ -0,0 +1,83 @@ +@layer components1 { + .singlestage-alert { + @apply after:-inset-y-px + after:-left-px + after:absolute + after:w-0.5 + bg-background + border + gap-1 + grid + px-4 + py-3 + relative + text-left + text-sm; + + &:has(> svg) { + @apply gap-x-2.5 + grid-cols-[auto_1fr]; + + .singlestage-alert-title { + @apply col-start-2; + } + } + + &:has(.singlestage-alert-action) { + @apply pr-18 + relative; + } + + svg { + @apply row-span-2 + text-current + translate-y-0.5; + + &:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-alert-variant-default { + @apply after:bg-foreground + bg-card + text-card-foreground; + } + + .singlestage-alert-variant-destructive { + @apply after:bg-destructive + bg-card + text-destructive; + + .singlestage-alert-description { + @apply text-destructive/90; + } + + svg { + @apply text-current; + } + } + + .singlestage-alert-title { + @apply font-semibold + text-sm; + } + + .singlestage-alert-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-sm; + + p:not(:last-child) { + @apply mb-4; + } + } + + .singlestage-alert-action { + @apply absolute + right-3 + top-2.5; + } +} diff --git a/singlestage/src/components/alert/style/base/vega/alert.css b/singlestage/src/components/alert/style/base/vega/alert.css new file mode 100644 index 00000000..f2903afa --- /dev/null +++ b/singlestage/src/components/alert/style/base/vega/alert.css @@ -0,0 +1,75 @@ +@layer components1 { + .singlestage-alert { + @apply border + gap-0.5 + grid + px-4 + py-3 + rounded-lg + text-left + text-sm; + + &:has(> svg) { + @apply gap-x-2.5 + grid-cols-[auto_1fr]; + + .singlestage-alert-title { + @apply col-start-2; + } + } + + &:has(.singlestage-alert-action) { + @apply pr-18 + relative; + } + + svg { + @apply row-span-2 + text-current + translate-y-0.5; + + &:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-alert-variant-default { + @apply bg-card + text-card-foreground; + } + + .singlestage-alert-variant-destructive { + @apply bg-card + text-destructive; + + .singlestage-alert-description { + @apply text-destructive/90; + } + + svg { + @apply text-current; + } + } + + .singlestage-alert-title { + @apply font-medium; + } + + .singlestage-alert-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-sm; + + p:not(:last-child) { + @apply mb-4; + } + } + + .singlestage-alert-action { + @apply absolute + right-3 + top-2.5; + } +} diff --git a/singlestage/src/components/alert/title.rs b/singlestage/src/components/alert/title.rs index 411df66d..93ebf499 100644 --- a/singlestage/src/components/alert/title.rs +++ b/singlestage/src/components/alert/title.rs @@ -115,7 +115,6 @@ pub fn AlertTitle( 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() @@ -150,7 +149,12 @@ pub fn AlertTitle( /> }; view! { - <h2 {..global_attrs_1} {..global_attrs_2}> + <h2 + class=move || format!("singlestage-alert-title {}", class.get().unwrap_or_default()) + + {..global_attrs_1} + {..global_attrs_2} + > {children()} </h2> } diff --git a/singlestage/src/components/alert_dialog/action.rs b/singlestage/src/components/alert_dialog/action.rs new file mode 100644 index 00000000..658bbc91 --- /dev/null +++ b/singlestage/src/components/alert_dialog/action.rs @@ -0,0 +1,8 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// Wraps a Button that the user presses to acknowledge the dialog. +#[component] +pub fn AlertDialogAction(children: Children) -> impl IntoView { + view! { <DialogActionPrimitive>{children()}</DialogActionPrimitive> } +} diff --git a/singlestage/src/components/alert_dialog/cancel.rs b/singlestage/src/components/alert_dialog/cancel.rs new file mode 100644 index 00000000..042346a0 --- /dev/null +++ b/singlestage/src/components/alert_dialog/cancel.rs @@ -0,0 +1,8 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// Wraps Button that the user presses to dismiss the dialog. +#[component] +pub fn AlertDialogCancel(children: Children) -> impl IntoView { + view! { <DialogCancelPrimitive>{children()}</DialogCancelPrimitive> } +} diff --git a/singlestage/src/components/alert_dialog/content.rs b/singlestage/src/components/alert_dialog/content.rs new file mode 100644 index 00000000..ed452cf4 --- /dev/null +++ b/singlestage/src/components/alert_dialog/content.rs @@ -0,0 +1,174 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// Contains content to be rendered in the main body of the dialog. +#[component] +pub fn AlertDialogContent( + children: Children, + + /// Toggles whether or not a close button should appear in the top right corner of the dialog. + #[prop(optional, into)] + close_button: MaybeProp<bool>, + /// Set the size of dialog to render. + /// + /// Accepted values are "sm" + #[prop(optional, into)] + size: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <DialogContentPrimitive + class + close_button + size + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </DialogContentPrimitive> + } +} diff --git a/singlestage/src/components/alert_dialog/description.rs b/singlestage/src/components/alert_dialog/description.rs new file mode 100644 index 00000000..ccfe12ee --- /dev/null +++ b/singlestage/src/components/alert_dialog/description.rs @@ -0,0 +1,163 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// A short description/subheading describing dialog content. +#[component] +pub fn AlertDialogDescription( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <DialogDescriptionPrimitive + class + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </DialogDescriptionPrimitive> + } +} diff --git a/singlestage/src/components/alert_dialog/dialog.rs b/singlestage/src/components/alert_dialog/dialog.rs new file mode 100644 index 00000000..aa981612 --- /dev/null +++ b/singlestage/src/components/alert_dialog/dialog.rs @@ -0,0 +1,170 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +/// Contains all the parts of an AlertDialog component. +#[component] +pub fn AlertDialog( + children: Children, + + /// Reactive signal that can remotely control the open state of the popover **but is not + /// coupled to the actual open state of the popover** + #[prop(optional, into)] + open: Reactive<bool>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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! { + <DialogPrimitive + alert=true + open + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </DialogPrimitive> + } +} diff --git a/singlestage/src/components/alert_dialog/footer.rs b/singlestage/src/components/alert_dialog/footer.rs new file mode 100644 index 00000000..d491106e --- /dev/null +++ b/singlestage/src/components/alert_dialog/footer.rs @@ -0,0 +1,163 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// Displays at the bottom of the dialog, contains calls to action. +#[component] +pub fn AlertDialogFooter( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <DialogFooterPrimitive + class + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </DialogFooterPrimitive> + } +} diff --git a/singlestage/src/components/alert_dialog/header.rs b/singlestage/src/components/alert_dialog/header.rs new file mode 100644 index 00000000..466a6d9f --- /dev/null +++ b/singlestage/src/components/alert_dialog/header.rs @@ -0,0 +1,163 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// Contains the dialog title and a description to be rendered in the open dialog. +#[component] +pub fn AlertDialogHeader( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <DialogHeaderPrimitive + class + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </DialogHeaderPrimitive> + } +} diff --git a/singlestage/src/components/alert_dialog/media.rs b/singlestage/src/components/alert_dialog/media.rs new file mode 100644 index 00000000..5dd6b11c --- /dev/null +++ b/singlestage/src/components/alert_dialog/media.rs @@ -0,0 +1,170 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// Contains a media element such as an icon or image to render in the header of the dialog. +#[component] +pub fn AlertDialogMedia( + children: Children, + + /// Set the style variant of the media element. + /// + /// Accepted values: "destructive" + #[prop(optional, into)] + variant: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <DialogMediaPrimitive + class + variant + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </DialogMediaPrimitive> + } +} diff --git a/singlestage/src/components/alert_dialog/mod.rs b/singlestage/src/components/alert_dialog/mod.rs new file mode 100644 index 00000000..3755c655 --- /dev/null +++ b/singlestage/src/components/alert_dialog/mod.rs @@ -0,0 +1,21 @@ +mod action; +mod cancel; +mod content; +mod description; +mod dialog; +mod footer; +mod header; +mod media; +mod title; +mod trigger; + +pub use action::*; +pub use cancel::*; +pub use content::*; +pub use description::*; +pub use dialog::*; +pub use footer::*; +pub use header::*; +pub use media::*; +pub use title::*; +pub use trigger::*; diff --git a/singlestage/src/components/alert_dialog/style/alert_dialog.css b/singlestage/src/components/alert_dialog/style/alert_dialog.css new file mode 100644 index 00000000..d05454bf --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/alert_dialog.css @@ -0,0 +1,59 @@ +@layer components { + .singlestage-alert-dialog-overlay { + @apply -translate-x-1/2 + -translate-y-1/2 + fixed + outline-none + w-full + z-50; + + left: calc(1 / 2 * 100vw); + top: calc(1 / 2 * 100%); + + ::backdrop { + @apply fixed + inset-0 + isolate + z-50; + } + + &[open] { + @apply -translate-x-1/2 + -translate-y-1/2 + fixed + outline-none + w-full + z-50; + + left: calc(1 / 2 * 100vw); + top: calc(1 / 2 * 100%); + } + } + .singlestage-alert-dialog-size-sm { + .singlestage-alert-dialog-footer { + @apply grid + grid-cols-2; + } + } + + .singlestage-alert-dialog-content { + @apply grid; + } + + .singlestage-alert-dialog-footer { + @apply flex + flex-col-reverse + gap-2 + sm:flex-row + sm:justify-end; + } + + .singlestage-alert-dialog-media-variant-destructive { + @apply bg-destructive/10 + text-destructive; + } + + /* .singlestage-alert-dialog-title { */ + /* @apply font-heading; */ + /* } */ +} diff --git a/singlestage/src/components/alert_dialog/style/alert_dialog_dark.css b/singlestage/src/components/alert_dialog/style/alert_dialog_dark.css new file mode 100644 index 00000000..b128e4ac --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/alert_dialog_dark.css @@ -0,0 +1,6 @@ +@layer components { + .singlestage-alert-dialog-media-variant-destructive { + @apply bg-destructive/20 + text-destructive; + } +} diff --git a/singlestage/src/components/alert_dialog/style/base/luma/alert_dialog.css b/singlestage/src/components/alert_dialog/style/base/luma/alert_dialog.css new file mode 100644 index 00000000..5f59ea19 --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/luma/alert_dialog.css @@ -0,0 +1,102 @@ +@layer components1 { + .singlestage-alert-dialog-overlay { + @apply bg-popover + ring-1 + ring-foreground/5 + rounded-4xl + shadow-xl; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-sm; + } + + &[open]::backdrop { + @apply bg-black/30; + } + } + + .singlestage-alert-dialog-content { + @apply bg-popover + gap-6 + p-6 + rounded-4xl + text-popover-foreground; + } + + .singlestage-alert-dialog-header { + @apply gap-1.5 + grid + grid-rows-[auto_1fr] + place-items-center + text-center; + + &:has(.singlestage-alert-dialog-media) { + @apply gap-x-6 + grid-rows-[auto_auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply bg-muted + inline-flex + items-center + justify-center + mb-2 + rounded-full + size-16; + + svg:not([class*="size-"]) { + @apply size-8; + } + } + + .singlestage-alert-dialog-title { + @apply text-lg + font-medium; + } + + .singlestage-alert-dialog-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply font-normal + underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } + + .singlestage-alert-dialog-size-sm { + @apply max-w-xs; + } + + .singlestage-alert-dialog-size-default { + @apply max-w-xs + sm:max-w-md; + + &:has(.singlestage-alert-dialog-media) { + .singlestage-alert-dialog-title { + @apply sm:col-start-2; + } + } + + .singlestage-alert-dialog-header { + @apply sm:place-items-start + sm:text-left; + + &:has(.singlestage-alert-dialog-media) { + @apply sm:grid-rows-[auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply sm:row-span-2; + } + } +} diff --git a/singlestage/src/components/alert_dialog/style/base/luma/alert_dialog_dark.css b/singlestage/src/components/alert_dialog/style/base/luma/alert_dialog_dark.css new file mode 100644 index 00000000..1c810ca9 --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/luma/alert_dialog_dark.css @@ -0,0 +1,5 @@ +@layer components1 { + .singlestage-alert-dialog-content { + @apply ring-foreground/10; + } +} diff --git a/singlestage/src/components/alert_dialog/style/base/lyra/alert_dialog.css b/singlestage/src/components/alert_dialog/style/base/lyra/alert_dialog.css new file mode 100644 index 00000000..79aa083a --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/lyra/alert_dialog.css @@ -0,0 +1,101 @@ +@layer components1 { + .singlestage-alert-dialog-overlay { + @apply bg-popover + ring-1 + ring-foreground/10 + rounded-none; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/10; + } + } + + .singlestage-alert-dialog-content { + @apply bg-popover + gap-4 + p-4 + rounded-none + text-popover-foreground; + } + + .singlestage-alert-dialog-header { + @apply grid + grid-rows-[auto_1fr] + place-items-center + gap-1.5 + text-center; + + &:has(.singlestage-alert-dialog-media) { + @apply gap-x-4 + grid-rows-[auto_auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply bg-muted + inline-flex + items-center + justify-center + mb-2 + rounded-none + size-10; + + svg:not([class*="size-"]) { + @apply size-6; + } + } + + .singlestage-alert-dialog-title { + @apply text-sm + font-medium; + } + + .singlestage-alert-dialog-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-xs/relaxed; + + a:not(.singlestage-button) { + @apply font-normal + underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } + + .singlestage-alert-dialog-size-sm { + @apply max-w-xs; + } + + .singlestage-alert-dialog-size-default { + @apply max-w-xs + sm:max-w-sm; + + &:has(.singlestage-alert-dialog-media) { + .singlestage-alert-dialog-title { + @apply sm:col-start-2; + } + } + + .singlestage-alert-dialog-header { + @apply sm:place-items-start + sm:text-left; + + &:has(.singlestage-alert-dialog-media) { + @apply sm:grid-rows-[auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply sm:row-span-2; + } + } +} diff --git a/singlestage/src/components/alert_dialog/style/base/maia/alert_dialog.css b/singlestage/src/components/alert_dialog/style/base/maia/alert_dialog.css new file mode 100644 index 00000000..06e8147b --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/maia/alert_dialog.css @@ -0,0 +1,101 @@ +@layer components1 { + .singlestage-alert-dialog-overlay { + @apply bg-popover + ring-1 + ring-foreground/5 + rounded-4xl; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/80; + } + } + + .singlestage-alert-dialog-content { + @apply bg-popover + gap-6 + p-6 + rounded-4xl + text-popover-foreground; + } + + .singlestage-alert-dialog-header { + @apply gap-1.5 + grid + grid-rows-[auto_1fr] + place-items-center + text-center; + + &:has(.singlestage-alert-dialog-media) { + @apply gap-x-6 + grid-rows-[auto_auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply bg-muted + inline-flex + items-center + justify-center + mb-2 + rounded-full + size-16; + + svg:not([class*="size-"]) { + @apply size-8; + } + } + + .singlestage-alert-dialog-title { + @apply font-medium + text-lg; + } + + .singlestage-alert-dialog-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply font-normal + underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } + + .singlestage-alert-dialog-size-sm { + @apply max-w-xs; + } + + .singlestage-alert-dialog-size-default { + @apply max-w-xs + sm:max-w-md; + + &:has(.singlestage-alert-dialog-media) { + .singlestage-alert-dialog-title { + @apply sm:col-start-2; + } + } + + .singlestage-alert-dialog-header { + @apply sm:place-items-start + sm:text-left; + + &:has(.singlestage-alert-dialog-media) { + @apply sm:grid-rows-[auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply sm:row-span-2; + } + } +} diff --git a/singlestage/src/components/alert_dialog/style/base/mira/alert_dialog.css b/singlestage/src/components/alert_dialog/style/base/mira/alert_dialog.css new file mode 100644 index 00000000..68a771cf --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/mira/alert_dialog.css @@ -0,0 +1,101 @@ +@layer components1 { + .singlestage-alert-dialog-overlay { + @apply bg-popover + ring-1 + ring-foreground/10 + rounded-xl; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/80; + } + } + + .singlestage-alert-dialog-content { + @apply bg-popover + gap-3 + p-4 + rounded-xl + text-popover-foreground; + } + + .singlestage-alert-dialog-header { + @apply gap-1 + grid + grid-rows-[auto_1fr] + place-items-center + text-center; + + &:has(.singlestage-alert-dialog-media) { + @apply gap-x-4 + grid-rows-[auto_auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply bg-muted + inline-flex + items-center + justify-center + mb-2 + rounded-md + size-8; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-alert-dialog-title { + @apply font-medium + text-sm; + } + + .singlestage-alert-dialog-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-xs/relaxed; + + a:not(.singlestage-button) { + @apply font-normal + underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } + + .singlestage-alert-dialog-size-sm { + @apply max-w-64; + } + + .singlestage-alert-dialog-size-default { + @apply max-w-xs + sm:max-w-sm; + + &:has(.singlestage-alert-dialog-media) { + .singlestage-alert-dialog-title { + @apply sm:col-start-2; + } + } + + .singlestage-alert-dialog-header { + @apply sm:place-items-start + sm:text-left; + + &:has(.singlestage-alert-dialog-media) { + @apply sm:grid-rows-[auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply sm:row-span-2; + } + } +} diff --git a/singlestage/src/components/alert_dialog/style/base/nova/alert_dialog.css b/singlestage/src/components/alert_dialog/style/base/nova/alert_dialog.css new file mode 100644 index 00000000..5ce220b3 --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/nova/alert_dialog.css @@ -0,0 +1,111 @@ +@layer components1 { + .singlestage-alert-dialog-overlay { + @apply bg-popover + ring-1 + ring-foreground/10 + rounded-xl + text-popover-foreground; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/10; + } + } + + .singlestage-alert-dialog-content { + @apply bg-popover + gap-4 + p-4 + rounded-xl + text-popover-foreground; + } + + .singlestage-alert-dialog-header { + @apply gap-1.5 + grid + grid-rows-[auto_1fr] + place-items-center + text-center; + + &:has(.singlestage-alert-dialog-media) { + @apply gap-x-4 + grid-rows-[auto_auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply bg-muted + inline-flex + items-center + justify-center + mb-2 + rounded-md + size-10; + + svg:not([class*="size-"]) { + @apply size-6; + } + } + + .singlestage-alert-dialog-title { + @apply font-medium + text-base; + } + + .singlestage-alert-dialog-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply font-normal + underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } + + .singlestage-alert-dialog-footer { + @apply -mb-4 + -mx-4 + bg-muted/50 + border-t + p-4 + rounded-b-xl; + } + + .singlestage-alert-dialog-size-sm { + @apply max-w-xs; + } + + .singlestage-alert-dialog-size-default { + @apply max-w-xs + sm:max-w-sm; + + &:has(.singlestage-alert-dialog-media) { + .singlestage-alert-dialog-title { + @apply sm:col-start-2; + } + } + + .singlestage-alert-dialog-header { + @apply sm:place-items-start + sm:text-left; + + &:has(.singlestage-alert-dialog-media) { + @apply sm:grid-rows-[auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply sm:row-span-2; + } + } +} diff --git a/singlestage/src/components/alert_dialog/style/base/sera/alert_dialog.css b/singlestage/src/components/alert_dialog/style/base/sera/alert_dialog.css new file mode 100644 index 00000000..9115525d --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/sera/alert_dialog.css @@ -0,0 +1,106 @@ +@layer components1 { + .singlestage-alert-dialog-overlay { + @apply bg-popover + ring-1 + ring-foreground/10 + rounded-none + shadow-md; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-sm; + } + + &[open]::backdrop { + @apply bg-black/20; + } + } + + .singlestage-alert-dialog-content { + @apply bg-popover + gap-6 + p-6 + rounded-none + text-popover-foreground; + } + + .singlestage-alert-dialog-header { + @apply gap-2 + grid + grid-rows-[auto_1fr] + place-items-center + text-center; + + &:has(.singlestage-alert-dialog-media) { + @apply gap-x-6 + grid-rows-[auto_auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply bg-muted + inline-flex + items-center + justify-center + mb-2 + rounded-none + size-16; + + svg:not([class*="size-"]) { + @apply size-8; + } + } + + .singlestage-alert-dialog-title { + @apply font-semibold + text-lg + tracking-wider + uppercase; + } + + .singlestage-alert-dialog-description { + @apply leading-relaxed + md:text-pretty + mt-0.5 + text-balance + text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply font-normal + underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } + + .singlestage-alert-dialog-size-sm { + @apply max-w-xs; + } + + .singlestage-alert-dialog-size-default { + @apply max-w-xs + sm:max-w-md; + + &:has(.singlestage-alert-dialog-media) { + .singlestage-alert-dialog-title { + @apply sm:col-start-2; + } + } + + .singlestage-alert-dialog-header { + @apply sm:place-items-start + sm:text-left; + + &:has(.singlestage-alert-dialog-media) { + @apply sm:grid-rows-[auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply sm:row-span-2; + } + } +} diff --git a/singlestage/src/components/alert_dialog/style/base/vega/alert_dialog.css b/singlestage/src/components/alert_dialog/style/base/vega/alert_dialog.css new file mode 100644 index 00000000..e95bd7d1 --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/vega/alert_dialog.css @@ -0,0 +1,101 @@ +@layer components1 { + .singlestage-alert-dialog-overlay { + @apply bg-popover + ring-1 + ring-foreground/10 + rounded-xl; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/10; + } + } + + .singlestage-alert-dialog-content { + @apply bg-popover + gap-6 + p-6 + rounded-xl + text-popover-foreground; + } + + .singlestage-alert-dialog-header { + @apply gap-1.5 + grid + grid-rows-[auto_1fr] + place-items-center + text-center; + + &:has(.singlestage-alert-dialog-media) { + @apply gap-x-6 + grid-rows-[auto_auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply bg-muted + inline-flex + items-center + justify-center + mb-2 + rounded-md + size-16; + + svg:not([class*="size-"]) { + @apply size-8; + } + } + + .singlestage-alert-dialog-title { + @apply font-medium + text-lg; + } + + .singlestage-alert-dialog-description { + @apply md:text-pretty + text-balance + text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply font-normal + underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } + + .singlestage-alert-dialog-size-sm { + @apply max-w-xs; + } + + .singlestage-alert-dialog-size-default { + @apply max-w-xs + sm:max-w-lg; + + &:has(.singlestage-alert-dialog-media) { + .singlestage-alert-dialog-title { + @apply sm:col-start-2; + } + } + + .singlestage-alert-dialog-header { + @apply sm:place-items-start + sm:text-left; + + &:has(.singlestage-alert-dialog-media) { + @apply sm:grid-rows-[auto_1fr]; + } + } + + .singlestage-alert-dialog-media { + @apply sm:row-span-2; + } + } +} diff --git a/singlestage/src/components/alert_dialog/title.rs b/singlestage/src/components/alert_dialog/title.rs new file mode 100644 index 00000000..10a10e67 --- /dev/null +++ b/singlestage/src/components/alert_dialog/title.rs @@ -0,0 +1,163 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// A title describing dialog content. +#[component] +pub fn AlertDialogTitle( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <DialogTitlePrimitive + class + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </DialogTitlePrimitive> + } +} diff --git a/singlestage/src/components/alert_dialog/trigger.rs b/singlestage/src/components/alert_dialog/trigger.rs new file mode 100644 index 00000000..caf4f913 --- /dev/null +++ b/singlestage/src/components/alert_dialog/trigger.rs @@ -0,0 +1,7 @@ +use crate::primitives::*; +use leptos::prelude::*; + +#[component] +pub fn AlertDialogTrigger(children: Children) -> impl IntoView { + view! { <TriggerPrimitive>{children()}</TriggerPrimitive> } +} diff --git a/singlestage/src/components/aspect_ratio/aspect_ratio.css b/singlestage/src/components/aspect_ratio/aspect_ratio.css deleted file mode 100644 index 25f06fe9..00000000 --- a/singlestage/src/components/aspect_ratio/aspect_ratio.css +++ /dev/null @@ -1,15 +0,0 @@ -@layer components { - .singlestage-aspect-ratio-wrapper { - @apply overflow-hidden - relative - w-full; - - > div { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - } - } -} diff --git a/singlestage/src/components/aspect_ratio/mod.rs b/singlestage/src/components/aspect_ratio/mod.rs index bccf4751..cb152404 100644 --- a/singlestage/src/components/aspect_ratio/mod.rs +++ b/singlestage/src/components/aspect_ratio/mod.rs @@ -1,13 +1,15 @@ use leptos::prelude::*; -/// Contains the contents of an aspect ratio component. +/// Displays content within a desired aspect ratio. #[component] pub fn AspectRatio( children: Children, - /// The aspect ratio to use to display the child element. + /// The aspect ratio to use to display the child element. Can be a decimal or a formula. + /// + /// See https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/aspect-ratio #[prop(optional, into)] - ratio: MaybeProp<f64>, + ratio: MaybeProp<String>, // GLOBAL ATTRIBUTES // @@ -119,7 +121,7 @@ pub fn AspectRatio( 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() @@ -156,15 +158,13 @@ pub fn AspectRatio( view! { <div - class=move || { - format!("singlestage-aspect-ratio-wrapper {}", class.get().unwrap_or_default()) - } - style:padding-bottom=move || format!("{}%", (100. / ratio.get().unwrap_or(1.))) + style:position="relative" + style:aspect-ratio=move || ratio.get().unwrap_or("1".to_string()) {..global_attrs_1} {..global_attrs_2} > - <div>{children()}</div> + {children()} </div> } } diff --git a/singlestage/src/components/avatar/avatar.css b/singlestage/src/components/avatar/avatar.css deleted file mode 100644 index 8e3ebfc2..00000000 --- a/singlestage/src/components/avatar/avatar.css +++ /dev/null @@ -1,41 +0,0 @@ -@layer components { - .singlestage-avatar { - @apply flex - overflow-hidden - relative - rounded-full - shrink-0 - size-8; - } - - .singlestage-avatar-image { - @apply absolute - aspect-square - indent-[100%] - overflow-hidden - size-full - whitespace-nowrap - z-2; - } - - .singlestage-avatar-fallback { - @apply absolute - bg-muted - flex - items-center - justify-center - rounded-full - size-full - z-1; - } - - .singlestage-avatar-group { - @apply -space-x-2 - flex; - - .singlestage-avatar { - @apply ring-2 - ring-background; - } - } -} diff --git a/singlestage/src/components/avatar/avatar.rs b/singlestage/src/components/avatar/avatar.rs index 3467b5c6..a1829e38 100644 --- a/singlestage/src/components/avatar/avatar.rs +++ b/singlestage/src/components/avatar/avatar.rs @@ -1,10 +1,18 @@ -use leptos::prelude::*; +use super::AvatarContext; +use leptos::{context::Provider, prelude::*}; /// Contains the contents of an avatar. #[component] pub fn Avatar( children: Children, + /// Set which size avatar to display. + /// + /// Accepted values: "sm" | "default" | "lg" + /// Default value is "default" + #[prop(optional, into)] + size: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -110,6 +118,10 @@ pub fn Avatar( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { + let context = AvatarContext { + img_loaded: RwSignal::new(false), + }; + let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -150,13 +162,23 @@ pub fn Avatar( }; view! { - <div - class=move || { format!("singlestage-avatar {}", class.get().unwrap_or_default()) } + <span + class=move || { + format!( + "singlestage-avatar {} {}", + match size.get().unwrap_or_default().as_str() { + "sm" => "singlestage-avatar-size-sm", + "lg" => "singlestage-avatar-size-lg", + _ => "singlestage-avatar-size-default", + }, + class.get().unwrap_or_default(), + ) + } {..global_attrs_1} {..global_attrs_2} > - {children()} - </div> + <Provider value=context>{children()}</Provider> + </span> } } diff --git a/singlestage/src/components/avatar/badge.rs b/singlestage/src/components/avatar/badge.rs new file mode 100644 index 00000000..3e805ffb --- /dev/null +++ b/singlestage/src/components/avatar/badge.rs @@ -0,0 +1,164 @@ +use leptos::prelude::*; + +/// Displays a badge overlay at the bottom right of an avatar. +#[component] +pub fn AvatarBadge( + #[prop(optional)] children: Option<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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <span + class=move || { + format!("singlestage-avatar-badge {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children.map(|c| c())} + </span> + } +} diff --git a/singlestage/src/components/avatar/fallback.rs b/singlestage/src/components/avatar/fallback.rs index c96d5117..8e7373cf 100644 --- a/singlestage/src/components/avatar/fallback.rs +++ b/singlestage/src/components/avatar/fallback.rs @@ -1,3 +1,4 @@ +use super::AvatarContext; use leptos::prelude::*; /// Renders a text fallback inside the avatar. @@ -110,6 +111,8 @@ pub fn AvatarFallback( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { + let avatar = expect_context::<AvatarContext>(); + let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -151,15 +154,22 @@ pub fn AvatarFallback( }; view! { - <div + <span class=move || { format!("singlestage-avatar-fallback {}", class.get().unwrap_or_default()) } + style:display=move || { + match avatar.img_loaded.get() { + true => "none", + false => "flex", + } + .to_string() + } {..global_attrs_1} {..global_attrs_2} > {children()} - </div> + </span> } } diff --git a/singlestage/src/components/avatar/group_count.rs b/singlestage/src/components/avatar/group_count.rs new file mode 100644 index 00000000..ac82110f --- /dev/null +++ b/singlestage/src/components/avatar/group_count.rs @@ -0,0 +1,164 @@ +use leptos::prelude::*; + +/// Displays a count representing additional hidden avatars. +#[component] +pub fn AvatarGroupCount( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <div + class=move || { + format!("singlestage-avatar-group-count {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + } +} diff --git a/singlestage/src/components/avatar/image.rs b/singlestage/src/components/avatar/image.rs index 54632445..6681f16a 100644 --- a/singlestage/src/components/avatar/image.rs +++ b/singlestage/src/components/avatar/image.rs @@ -1,3 +1,4 @@ +use super::AvatarContext; use leptos::prelude::*; /// Renders an image inside the avatar. @@ -161,6 +162,16 @@ pub fn AvatarImage( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { + let avatar = expect_context::<AvatarContext>(); + let img_ref = NodeRef::<leptos::html::Img>::new(); + + // Check loaded state on wasm load + Effect::new(move || { + if let Some(img) = img_ref.get() { + avatar.img_loaded.set(img.complete()); + } + }); + let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -203,7 +214,9 @@ pub fn AvatarImage( view! { <img alt=move || alt.get() - class=move || format!("singlestage-avatar-image {}", class.get().unwrap_or_default()) + class=move || { + format!("singlestage-avatar-image {}", class.get().unwrap_or_default()) + } crossorigin=move || crossorigin.get() decoding=move || decoding.get() elementtiming=move || elementtiming.get() @@ -211,10 +224,19 @@ pub fn AvatarImage( height=move || height.get() ismap=move || ismap.get() loading=move || loading.get() + node_ref=img_ref + on:load=move |_| avatar.img_loaded.set(true) referrerpolicy=move || referrerpolicy.get() sizes=move || sizes.get() src=move || src.get() srcset=move || srcset.get() + style:display=move || { + match avatar.img_loaded.get() { + true => "block", + false => "none", + } + .to_string() + } usemap=move || usemap.get() width=move || width.get() diff --git a/singlestage/src/components/avatar/mod.rs b/singlestage/src/components/avatar/mod.rs index e8234ba9..b41e4145 100644 --- a/singlestage/src/components/avatar/mod.rs +++ b/singlestage/src/components/avatar/mod.rs @@ -1,9 +1,20 @@ mod avatar; +mod badge; mod fallback; mod group; +mod group_count; mod image; pub use avatar::*; +pub use badge::*; pub use fallback::*; pub use group::*; +pub use group_count::*; pub use image::*; + +use leptos::prelude::*; + +#[derive(Clone)] +struct AvatarContext { + img_loaded: RwSignal<bool>, +} diff --git a/singlestage/src/components/avatar/style/avatar.css b/singlestage/src/components/avatar/style/avatar.css new file mode 100644 index 00000000..c5d0c792 --- /dev/null +++ b/singlestage/src/components/avatar/style/avatar.css @@ -0,0 +1,96 @@ +@layer components { + .singlestage-avatar { + @apply flex + relative + select-none + shrink-0; + + &::after { + @apply absolute + border-border + inset-0 + mix-blend-darken; + } + } + + .singlestage-avatar-image { + @apply aspect-square + object-cover + size-full; + } + + .singlestage-avatar-fallback { + @apply flex + items-center + justify-center + size-full + text-sm; + } + + .singlestage-avatar-badge { + @apply absolute + bg-blend-color + bottom-0 + inline-flex + items-center + justify-center + right-0 + ring-2 + rounded-full + select-none + z-10; + } + + .singlestage-avatar-group { + @apply -space-x-2 + flex; + + .singlestage-avatar { + @apply ring-2 ring-background; + } + } + + .singlestage-avatar-group-count { + @apply flex + items-center + justify-center + relative + ring-2 + ring-background + shrink-0; + } + + .singlestage-avatar-size-sm { + .singlestage-avatar-fallback { + @apply text-xs; + } + + .singlestage-avatar-badge { + @apply size-2; + + & > svg { + @apply hidden; + } + } + } + + .singlestage-avatar-size-default { + .singlestage-avatar-badge { + @apply size-2.5; + + & > svg { + @apply size-2; + } + } + } + + .singlestage-avatar-size-lg { + .singlestage-avatar-badge { + @apply size-3; + + & > svg { + @apply size-2; + } + } + } +} diff --git a/singlestage/src/components/avatar/style/avatar_dark.css b/singlestage/src/components/avatar/style/avatar_dark.css new file mode 100644 index 00000000..8ad23279 --- /dev/null +++ b/singlestage/src/components/avatar/style/avatar_dark.css @@ -0,0 +1,5 @@ +@layer components { + .singlestage-avatar::after { + @apply mix-blend-lighten; + } +} diff --git a/singlestage/src/components/avatar/style/base/luma/avatar.css b/singlestage/src/components/avatar/style/base/luma/avatar.css new file mode 100644 index 00000000..559a1700 --- /dev/null +++ b/singlestage/src/components/avatar/style/base/luma/avatar.css @@ -0,0 +1,62 @@ +@layer components1 { + .singlestage-avatar { + @apply rounded-full + size-8; + + &::after { + @apply rounded-full; + } + } + + .singlestage-avatar-fallback { + @apply bg-muted + rounded-full + text-muted-foreground; + } + + .singlestage-avatar-image { + @apply rounded-full; + } + + .singlestage-avatar-badge { + @apply bg-primary + ring-background + text-primary-foreground; + } + + .singlestage-avatar-group-count { + @apply bg-muted + rounded-full + size-8 + text-muted-foreground + text-sm; + + & > svg { + @apply size-4; + } + } + + .singlestage-avatar-size-sm { + @apply size-6; + + .singlestage-avatar-group-count { + @apply size-6; + + & > svg { + @apply size-3; + } + } + } + + .singlestage-avatar-size-lg { + @apply size-10; + + .singlestage-avatar-group-count { + @apply size-10; + + & > svg { + @apply size-5; + } + } + } +} diff --git a/singlestage/src/components/avatar/style/base/lyra/avatar.css b/singlestage/src/components/avatar/style/base/lyra/avatar.css new file mode 100644 index 00000000..ece530a2 --- /dev/null +++ b/singlestage/src/components/avatar/style/base/lyra/avatar.css @@ -0,0 +1,62 @@ +@layer components1 { + .singlestage-avatar { + @apply rounded-full + size-8; + + &::after { + @apply rounded-full; + } + } + + .singlestage-avatar-fallback { + @apply bg-muted + rounded-full + text-muted-foreground; + } + + .singlestage-avatar-image { + @apply rounded-full; + } + + .singlestage-avatar-badge { + @apply bg-primary + ring-background + text-primary-foreground; + } + + .singlestage-avatar-group-count { + @apply bg-muted + rounded-full + size-8 + text-muted-foreground + text-xs; + + & > svg { + @apply size-4; + } + } + + .singlestage-avatar-size-sm { + @apply size-6; + + .singlestage-avatar-group-count { + @apply size-6; + + & > svg { + @apply size-3; + } + } + } + + .singlestage-avatar-size-lg { + @apply size-10; + + .singlestage-avatar-group-count { + @apply size-10; + + & > svg { + @apply size-5; + } + } + } +} diff --git a/singlestage/src/components/avatar/style/base/maia/avatar.css b/singlestage/src/components/avatar/style/base/maia/avatar.css new file mode 100644 index 00000000..3abfeb02 --- /dev/null +++ b/singlestage/src/components/avatar/style/base/maia/avatar.css @@ -0,0 +1,62 @@ +@layer components1 { + .singlestage-avatar { + @apply rounded-full + size-8; + + &::after { + @apply rounded-full; + } + } + + .singlestage-avatar-fallback { + @apply bg-muted + rounded-full + text-muted-foreground; + } + + .singlestage-avatar-image { + @apply rounded-full; + } + + .singlestage-avatar-badge { + @apply bg-primary + ring-background + text-primary-foreground; + } + + .singlestage-avatar-group-count { + @apply bg-muted + text-muted-foreground + size-8 + rounded-full + text-sm; + + & > svg { + @apply size-4; + } + } + + .singlestage-avatar-size-sm { + @apply size-6; + + .singlestage-avatar-group-count { + @apply size-6; + + & > svg { + @apply size-3; + } + } + } + + .singlestage-avatar-size-lg { + @apply size-10; + + .singlestage-avatar-group-count { + @apply size-10; + + & > svg { + @apply size-5; + } + } + } +} diff --git a/singlestage/src/components/avatar/style/base/mira/avatar.css b/singlestage/src/components/avatar/style/base/mira/avatar.css new file mode 100644 index 00000000..fd038671 --- /dev/null +++ b/singlestage/src/components/avatar/style/base/mira/avatar.css @@ -0,0 +1,62 @@ +@layer components1 { + .singlestage-avatar { + @apply rounded-full + size-8; + + &::after { + @apply rounded-full; + } + } + + .singlestage-avatar-fallback { + @apply bg-muted + rounded-full + text-muted-foreground; + } + + .singlestage-avatar-image { + @apply rounded-full; + } + + .singlestage-avatar-badge { + @apply bg-primary + ring-background + text-primary-foreground; + } + + .singlestage-avatar-group-count { + @apply bg-muted + rounded-full + size-8 + text-muted-foreground + text-xs/relaxed; + + & > svg { + @apply size-4; + } + } + + .singlestage-avatar-size-sm { + @apply size-6; + + .singlestage-avatar-group-count { + @apply size-6; + + & > svg { + @apply size-3; + } + } + } + + .singlestage-avatar-size-lg { + @apply size-10; + + .singlestage-avatar-group-count { + @apply size-10; + + & > svg { + @apply size-5; + } + } + } +} diff --git a/singlestage/src/components/avatar/style/base/nova/avatar.css b/singlestage/src/components/avatar/style/base/nova/avatar.css new file mode 100644 index 00000000..559a1700 --- /dev/null +++ b/singlestage/src/components/avatar/style/base/nova/avatar.css @@ -0,0 +1,62 @@ +@layer components1 { + .singlestage-avatar { + @apply rounded-full + size-8; + + &::after { + @apply rounded-full; + } + } + + .singlestage-avatar-fallback { + @apply bg-muted + rounded-full + text-muted-foreground; + } + + .singlestage-avatar-image { + @apply rounded-full; + } + + .singlestage-avatar-badge { + @apply bg-primary + ring-background + text-primary-foreground; + } + + .singlestage-avatar-group-count { + @apply bg-muted + rounded-full + size-8 + text-muted-foreground + text-sm; + + & > svg { + @apply size-4; + } + } + + .singlestage-avatar-size-sm { + @apply size-6; + + .singlestage-avatar-group-count { + @apply size-6; + + & > svg { + @apply size-3; + } + } + } + + .singlestage-avatar-size-lg { + @apply size-10; + + .singlestage-avatar-group-count { + @apply size-10; + + & > svg { + @apply size-5; + } + } + } +} diff --git a/singlestage/src/components/avatar/style/base/sera/avatar.css b/singlestage/src/components/avatar/style/base/sera/avatar.css new file mode 100644 index 00000000..559a1700 --- /dev/null +++ b/singlestage/src/components/avatar/style/base/sera/avatar.css @@ -0,0 +1,62 @@ +@layer components1 { + .singlestage-avatar { + @apply rounded-full + size-8; + + &::after { + @apply rounded-full; + } + } + + .singlestage-avatar-fallback { + @apply bg-muted + rounded-full + text-muted-foreground; + } + + .singlestage-avatar-image { + @apply rounded-full; + } + + .singlestage-avatar-badge { + @apply bg-primary + ring-background + text-primary-foreground; + } + + .singlestage-avatar-group-count { + @apply bg-muted + rounded-full + size-8 + text-muted-foreground + text-sm; + + & > svg { + @apply size-4; + } + } + + .singlestage-avatar-size-sm { + @apply size-6; + + .singlestage-avatar-group-count { + @apply size-6; + + & > svg { + @apply size-3; + } + } + } + + .singlestage-avatar-size-lg { + @apply size-10; + + .singlestage-avatar-group-count { + @apply size-10; + + & > svg { + @apply size-5; + } + } + } +} diff --git a/singlestage/src/components/avatar/style/base/vega/avatar.css b/singlestage/src/components/avatar/style/base/vega/avatar.css new file mode 100644 index 00000000..559a1700 --- /dev/null +++ b/singlestage/src/components/avatar/style/base/vega/avatar.css @@ -0,0 +1,62 @@ +@layer components1 { + .singlestage-avatar { + @apply rounded-full + size-8; + + &::after { + @apply rounded-full; + } + } + + .singlestage-avatar-fallback { + @apply bg-muted + rounded-full + text-muted-foreground; + } + + .singlestage-avatar-image { + @apply rounded-full; + } + + .singlestage-avatar-badge { + @apply bg-primary + ring-background + text-primary-foreground; + } + + .singlestage-avatar-group-count { + @apply bg-muted + rounded-full + size-8 + text-muted-foreground + text-sm; + + & > svg { + @apply size-4; + } + } + + .singlestage-avatar-size-sm { + @apply size-6; + + .singlestage-avatar-group-count { + @apply size-6; + + & > svg { + @apply size-3; + } + } + } + + .singlestage-avatar-size-lg { + @apply size-10; + + .singlestage-avatar-group-count { + @apply size-10; + + & > svg { + @apply size-5; + } + } + } +} diff --git a/singlestage/src/components/badge/badge.css b/singlestage/src/components/badge/badge.css deleted file mode 100644 index 527ad836..00000000 --- a/singlestage/src/components/badge/badge.css +++ /dev/null @@ -1,57 +0,0 @@ -@layer components { - .singlestage-badge-primary, - .singlestage-badge-secondary, - .singlestage-badge-destructive, - .singlestage-badge-outline { - @apply [&>svg]:pointer-events-none - [&>svg]:size-3 - aria-invalid:border-destructive - aria-invalid:ring-destructive/20 - border - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - font-medium - gap-1 - inline-flex - items-center - justify-center - overflow-hidden - px-2 - py-0.5 - rounded-md - shrink-0 - text-xs - transition-[color,box-shadow] - w-fit - whitespace-nowrap; - } - - .singlestage-badge-primary { - @apply [a&]:hover:bg-primary/90 - bg-primary - border-transparent - text-primary-foreground; - } - - .singlestage-badge-secondary { - @apply [a&]:hover:bg-secondary/90 - bg-secondary - border-transparent - text-secondary-foreground; - } - - .singlestage-badge-destructive { - @apply [a&]:hover:bg-destructive/90 - bg-destructive - border-transparent - focus-visible:ring-destructive/20 - text-white; - } - - .singlestage-badge-outline { - @apply text-foreground - [a&]:hover:bg-accent - [a&]:hover:text-accent-foreground; - } -} diff --git a/singlestage/src/components/badge/mod.rs b/singlestage/src/components/badge/mod.rs index d2a003b0..92de97c4 100644 --- a/singlestage/src/components/badge/mod.rs +++ b/singlestage/src/components/badge/mod.rs @@ -1,3 +1,4 @@ +use crate::Reactive; use leptos::prelude::*; /// Contains arbitrary badge content. @@ -7,9 +8,9 @@ pub fn Badge( /// Toggle whether or not this badge appears invalid. #[prop(optional, into)] - invalid: MaybeProp<bool>, + invalid: Reactive<bool>, /// Define the badge variant. Defaults to `"primary"`. - /// Variants: "primary" | "secondary" | "destructive" | "outline" + /// Accepted values: "primary" | "secondary" | "destructive" | "outline" #[prop(optional, into)] variant: MaybeProp<String>, @@ -160,16 +161,17 @@ pub fn Badge( view! { <span - aria_invalid=move || if invalid.get().unwrap_or_default() { Some("true") } else { None } + aria_invalid=move || if invalid.get() { Some("true") } else { None } class=move || { format!( - "{} {}", + "singlestage-badge {} {}", match variant.get().unwrap_or_default().as_str() { - "primary" => "singlestage-badge-primary", - "secondary" => "singlestage-badge-secondary", - "destructive" => "singlestage-badge-destructive", - "outline" => "singlestage-badge-outline", - _ => "singlestage-badge-primary", + "secondary" => "singlestage-badge-variant-secondary", + "destructive" => "singlestage-badge-variant-destructive", + "outline" => "singlestage-badge-variant-outline", + "ghost" => "singlestage-badge-variant-ghost", + "link" => "singlestage-badge-variant-link", + _ => "singlestage-badge-variant-default", }, class.get().unwrap_or_default(), ) diff --git a/singlestage/src/components/badge/style/badge.css b/singlestage/src/components/badge/style/badge.css new file mode 100644 index 00000000..548d094d --- /dev/null +++ b/singlestage/src/components/badge/style/badge.css @@ -0,0 +1,27 @@ +@layer components { + .singlestage-badge { + @apply inline-flex + items-center + justify-center + overflow-hidden + shrink-0 + w-fit + whitespace-nowrap; + + &::focus-visible { + @apply border-ring + ring-[3px] + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-destructive/20; + } + + & > svg { + @apply pointer-events-none; + } + } +} diff --git a/singlestage/src/components/badge/style/badge_dark.css b/singlestage/src/components/badge/style/badge_dark.css new file mode 100644 index 00000000..dfb8c281 --- /dev/null +++ b/singlestage/src/components/badge/style/badge_dark.css @@ -0,0 +1,6 @@ +@layer components { + .singlestage-badge:invalid, + .singlestage-badge[aria-invalid="true"] { + @apply ring-destructive/40; + } +} diff --git a/singlestage/src/components/badge/style/base/luma/badge.css b/singlestage/src/components/badge/style/base/luma/badge.css new file mode 100644 index 00000000..21bb6754 --- /dev/null +++ b/singlestage/src/components/badge/style/base/luma/badge.css @@ -0,0 +1,81 @@ +@layer components1 { + .singlestage-badge { + @apply border + border-transparent + font-medium + gap-1 + h-5 + px-2 + py-0.5 + rounded-3xl + text-xs + transition-all; + + & > svg { + @apply size-3!; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + } + + .singlestage-badge-variant-default { + @apply bg-primary + text-primary-foreground; + + a:hover { + @apply bg-primary/80; + } + } + + .singlestage-badge-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + a:hover { + @apply bg-secondary/80; + } + } + + .singlestage-badge-variant-outline { + @apply border-border + text-foreground; + + a:hover { + @apply bg-muted + text-muted-foreground; + } + } + + .singlestage-badge-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply ring-destructive/20; + } + + a:hover { + @apply bg-destructive/20; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted + text-muted-foreground; + } + + .singlestage-badge-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } +} diff --git a/singlestage/src/components/badge/style/base/luma/badge_dark.css b/singlestage/src/components/badge/style/base/luma/badge_dark.css new file mode 100644 index 00000000..24a9a485 --- /dev/null +++ b/singlestage/src/components/badge/style/base/luma/badge_dark.css @@ -0,0 +1,13 @@ +@layer components1 { + .singlestage-badge-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted/50; + } +} diff --git a/singlestage/src/components/badge/style/base/lyra/badge.css b/singlestage/src/components/badge/style/base/lyra/badge.css new file mode 100644 index 00000000..22cea04a --- /dev/null +++ b/singlestage/src/components/badge/style/base/lyra/badge.css @@ -0,0 +1,78 @@ +@layer components1 { + .singlestage-badge { + @apply border + border-transparent + font-medium + gap-1 + h-5 + px-2 + py-0.5 + rounded-none + text-xs + transition-all; + + & > svg { + @apply size-3!; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + } + + .singlestage-badge-variant-default { + @apply bg-primary + text-primary-foreground + [a]:hover:bg-primary/80; + } + + .singlestage-badge-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + a:hover { + @apply bg-primary/80; + } + } + + .singlestage-badge-variant-outline { + @apply border-border + text-foreground; + + a:hover { + @apply bg-muted + text-muted-foreground; + } + } + + .singlestage-badge-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply ring-destructive/20; + } + + a:hover { + @apply bg-destructive/20; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted + text-muted-foreground; + } + + .singlestage-badge-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } +} diff --git a/singlestage/src/components/badge/style/base/lyra/badge_dark.css b/singlestage/src/components/badge/style/base/lyra/badge_dark.css new file mode 100644 index 00000000..24a9a485 --- /dev/null +++ b/singlestage/src/components/badge/style/base/lyra/badge_dark.css @@ -0,0 +1,13 @@ +@layer components1 { + .singlestage-badge-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted/50; + } +} diff --git a/singlestage/src/components/badge/style/base/maia/badge.css b/singlestage/src/components/badge/style/base/maia/badge.css new file mode 100644 index 00000000..c66e9931 --- /dev/null +++ b/singlestage/src/components/badge/style/base/maia/badge.css @@ -0,0 +1,82 @@ +@layer components1 { + .singlestage-badge { + @apply border + border-transparent + font-medium + gap-1 + h-5 + px-2 + py-0.5 + rounded-4xl + text-xs + transition-all; + + & > svg { + @apply size-3!; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + } + + .singlestage-badge-variant-default { + @apply bg-primary + text-primary-foreground; + + a:hover { + @apply bg-primary/80; + } + } + + .singlestage-badge-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + a:hover { + @apply bg-secondary/80; + } + } + + .singlestage-badge-variant-outline { + @apply bg-input/30 + border-border + text-foreground; + + a:hover { + @apply bg-muted + text-muted-foreground; + } + } + + .singlestage-badge-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply ring-destructive/20; + } + + a:hover { + @apply bg-destructive/20; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted + text-muted-foreground; + } + + .singlestage-badge-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } +} diff --git a/singlestage/src/components/badge/style/base/maia/badge_dark.css b/singlestage/src/components/badge/style/base/maia/badge_dark.css new file mode 100644 index 00000000..24a9a485 --- /dev/null +++ b/singlestage/src/components/badge/style/base/maia/badge_dark.css @@ -0,0 +1,13 @@ +@layer components1 { + .singlestage-badge-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted/50; + } +} diff --git a/singlestage/src/components/badge/style/base/mira/badge.css b/singlestage/src/components/badge/style/base/mira/badge.css new file mode 100644 index 00000000..6978d456 --- /dev/null +++ b/singlestage/src/components/badge/style/base/mira/badge.css @@ -0,0 +1,82 @@ +@layer components1 { + .singlestage-badge { + @apply border + border-transparent + font-medium + gap-1 + h-5 + px-2 + py-0.5 + rounded-full + text-[0.625rem] + transition-all; + + & > svg { + @apply size-2.5!; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + } + + .singlestage-badge-variant-default { + @apply bg-primary + text-primary-foreground; + + a:hover { + @apply bg-primary/80; + } + } + + .singlestage-badge-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + a:hover { + @apply bg-secondary/80; + } + } + + .singlestage-badge-variant-outline { + @apply bg-input/20 + border-border + text-foreground; + + a:hover { + @apply bg-muted + text-muted-foreground; + } + } + + .singlestage-badge-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply ring-destructive/20; + } + + a:hover { + @apply bg-destructive/20; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted + text-muted-foreground; + } + + .singlestage-badge-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply hover:underline; + } + } +} diff --git a/singlestage/src/components/badge/style/base/mira/badge_dark.css b/singlestage/src/components/badge/style/base/mira/badge_dark.css new file mode 100644 index 00000000..bc36156c --- /dev/null +++ b/singlestage/src/components/badge/style/base/mira/badge_dark.css @@ -0,0 +1,17 @@ +@layer components1 { + .singlestage-badge-variant-outline { + @apply bg-input/30; + } + + .singlestage-badge-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted/50; + } +} diff --git a/singlestage/src/components/badge/style/base/nova/badge.css b/singlestage/src/components/badge/style/base/nova/badge.css new file mode 100644 index 00000000..a17320de --- /dev/null +++ b/singlestage/src/components/badge/style/base/nova/badge.css @@ -0,0 +1,81 @@ +@layer components1 { + .singlestage-badge { + @apply border + border-transparent + font-medium + gap-1 + h-5 + px-2 + py-0.5 + rounded-4xl + text-xs + transition-all; + + & > svg { + @apply size-3!; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + } + + .singlestage-badge-variant-default { + @apply bg-primary + text-primary-foreground; + + a:hover { + @apply bg-primary/80; + } + } + + .singlestage-badge-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + a:hover { + @apply bg-secondary/80; + } + } + + .singlestage-badge-variant-outline { + @apply border-border + text-foreground; + + a:hover { + @apply bg-muted + text-muted-foreground; + } + } + + .singlestage-badge-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply ring-destructive/20; + } + + a:hover { + @apply bg-destructive/20; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted + text-muted-foreground; + } + + .singlestage-badge-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } +} diff --git a/singlestage/src/components/badge/style/base/nova/badge_dark.css b/singlestage/src/components/badge/style/base/nova/badge_dark.css new file mode 100644 index 00000000..24a9a485 --- /dev/null +++ b/singlestage/src/components/badge/style/base/nova/badge_dark.css @@ -0,0 +1,13 @@ +@layer components1 { + .singlestage-badge-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted/50; + } +} diff --git a/singlestage/src/components/badge/style/base/sera/badge.css b/singlestage/src/components/badge/style/base/sera/badge.css new file mode 100644 index 00000000..6cbaabf0 --- /dev/null +++ b/singlestage/src/components/badge/style/base/sera/badge.css @@ -0,0 +1,80 @@ +@layer components1 { + .singlestage-badge { + @apply bg-transparent + border-0 + font-semibold + gap-1.5 + px-0 + py-0 + rounded-none + text-[0.625rem] + tracking-widest + transition-colors + uppercase; + + & > svg { + @apply size-3!; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-0; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-0; + } + } + + .singlestage-badge-variant-default { + @apply text-foreground; + + a:hover { + @apply text-foreground/70; + } + } + + .singlestage-badge-variant-secondary { + @apply text-muted-foreground; + + a:hover { + @apply text-foreground; + } + } + + .singlestage-badge-variant-outline { + @apply text-foreground; + + a:hover { + @apply text-foreground/70; + } + } + + .singlestage-badge-variant-destructive { + @apply text-destructive; + + &:focus-visible { + @apply ring-destructive/20; + } + + a:hover { + @apply text-destructive/70; + } + } + + .singlestage-badge-variant-ghost { + @apply text-muted-foreground; + + &:hover { + @apply text-foreground; + } + } + + .singlestage-badge-variant-link { + @apply text-foreground + underline-offset-4; + + &:hover { + @apply underline; + } + } +} diff --git a/singlestage/src/components/badge/style/base/sera/badge_dark.css b/singlestage/src/components/badge/style/base/sera/badge_dark.css new file mode 100644 index 00000000..427df276 --- /dev/null +++ b/singlestage/src/components/badge/style/base/sera/badge_dark.css @@ -0,0 +1,5 @@ +@layer components1 { + .singlestage-badge-variant-destructive:focus-visible { + @apply ring-destructive/40; + } +} diff --git a/singlestage/src/components/badge/style/base/vega/badge.css b/singlestage/src/components/badge/style/base/vega/badge.css new file mode 100644 index 00000000..a17320de --- /dev/null +++ b/singlestage/src/components/badge/style/base/vega/badge.css @@ -0,0 +1,81 @@ +@layer components1 { + .singlestage-badge { + @apply border + border-transparent + font-medium + gap-1 + h-5 + px-2 + py-0.5 + rounded-4xl + text-xs + transition-all; + + & > svg { + @apply size-3!; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + } + + .singlestage-badge-variant-default { + @apply bg-primary + text-primary-foreground; + + a:hover { + @apply bg-primary/80; + } + } + + .singlestage-badge-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + a:hover { + @apply bg-secondary/80; + } + } + + .singlestage-badge-variant-outline { + @apply border-border + text-foreground; + + a:hover { + @apply bg-muted + text-muted-foreground; + } + } + + .singlestage-badge-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply ring-destructive/20; + } + + a:hover { + @apply bg-destructive/20; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted + text-muted-foreground; + } + + .singlestage-badge-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } +} diff --git a/singlestage/src/components/badge/style/base/vega/badge_dark.css b/singlestage/src/components/badge/style/base/vega/badge_dark.css new file mode 100644 index 00000000..24a9a485 --- /dev/null +++ b/singlestage/src/components/badge/style/base/vega/badge_dark.css @@ -0,0 +1,13 @@ +@layer components1 { + .singlestage-badge-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + } + + .singlestage-badge-variant-ghost:hover { + @apply bg-muted/50; + } +} diff --git a/singlestage/src/components/breadcrumb/breadcrumb.css b/singlestage/src/components/breadcrumb/breadcrumb.css deleted file mode 100644 index 1a139775..00000000 --- a/singlestage/src/components/breadcrumb/breadcrumb.css +++ /dev/null @@ -1,17 +0,0 @@ -@layer components { - .singlestage-breadcrumb { - @apply break-words - flex - flex-wrap - gap-1.5 - items-center - sm:gap-2.5 - text-muted-foreground - text-sm; - } - .singlestage-breadcrumb-item { - @apply gap-1.5 - inline-flex - items-center; - } -} diff --git a/singlestage/src/components/breadcrumb/breadcrumb.rs b/singlestage/src/components/breadcrumb/breadcrumb.rs index 82adb1e1..e2a8c40b 100644 --- a/singlestage/src/components/breadcrumb/breadcrumb.rs +++ b/singlestage/src/components/breadcrumb/breadcrumb.rs @@ -1,28 +1,10 @@ use leptos::prelude::*; -/// Contains the contents of the breadcrumb. +/// Contains all the items of the breadcrumb component. #[component] pub fn Breadcrumb( children: Children, - // OL ATTRIBUTES - // - /// The numbering type. - /// - /// * `a` for lowercase letters - /// * `A` for uppercase letters - /// * `i` for lowercase Roman numerals - /// * `I` for uppercase Roman numerals - /// * `1` for numbers (default) - #[prop(optional, into)] - number_type: MaybeProp<usize>, - /// Toggle whether or not the list should be reversed. - #[prop(optional, into)] - reversed: MaybeProp<bool>, - /// Set which item to start counting from starting with 1. - #[prop(optional, into)] - start: MaybeProp<usize>, - // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -169,16 +151,14 @@ pub fn Breadcrumb( }; view! { - <ol + <nav + aria_label="breadcrumb" class=move || { format!("singlestage-breadcrumb {}", class.get().unwrap_or_default()) } - type=move || number_type.get() - reversed=move || reversed.get() - start=move || start.get() {..global_attrs_1} {..global_attrs_2} > {children()} - </ol> + </nav> } } diff --git a/singlestage/src/components/breadcrumb/ellipsis.rs b/singlestage/src/components/breadcrumb/ellipsis.rs new file mode 100644 index 00000000..8932476c --- /dev/null +++ b/singlestage/src/components/breadcrumb/ellipsis.rs @@ -0,0 +1,178 @@ +use leptos::prelude::*; + +/// Displays an ellipsis in place of collapsed breadcrumb items. +#[component] +pub fn BreadcrumbEllipsis( + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + popover=move || popover.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! { + <span + aria_current="page" + aria_hidden="true" + class=move || { + format!("singlestage-breadcrumb-ellipsis {}", class.get().unwrap_or_default()) + } + role="link" + + {..global_attrs_1} + {..global_attrs_2} + > + <svg + class="" + style="" + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + stroke="currentColor" + fill="none" + > + <circle cx="12" cy="12" r="1"></circle> + <circle cx="19" cy="12" r="1"></circle> + <circle cx="5" cy="12" r="1"></circle> + </svg> + <span class="sr-only">"More"</span> + </span> + } +} diff --git a/singlestage/src/components/breadcrumb/link.rs b/singlestage/src/components/breadcrumb/link.rs new file mode 100644 index 00000000..7a4a6f8a --- /dev/null +++ b/singlestage/src/components/breadcrumb/link.rs @@ -0,0 +1,13 @@ +// NOTE: This is intentionally a context provider and not another `Link` component +// to be more flexible with its children (`a` vs `A` vs `Link`, etc.) +// at the cost of being slightly more verbose than shadcn + +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone)] +pub(crate) struct BreadcrumbLinkContext {} + +#[component] +pub fn BreadcrumbLink(children: Children) -> impl IntoView { + view! { <Provider value=BreadcrumbLinkContext {}>{children()}</Provider> } +} diff --git a/singlestage/src/components/breadcrumb/list.rs b/singlestage/src/components/breadcrumb/list.rs new file mode 100644 index 00000000..f11f6708 --- /dev/null +++ b/singlestage/src/components/breadcrumb/list.rs @@ -0,0 +1,186 @@ +use leptos::prelude::*; + +/// Displays the ordered list of breadcrumb items. +#[component] +pub fn BreadcrumbList( + children: Children, + + // OL ATTRIBUTES + // + /// The numbering type. + /// + /// * `a` for lowercase letters + /// * `A` for uppercase letters + /// * `i` for lowercase Roman numerals + /// * `I` for uppercase Roman numerals + /// * `1` for numbers (default) + #[prop(optional, into)] + number_type: MaybeProp<usize>, + /// Toggle whether or not the list should be reversed. + #[prop(optional, into)] + reversed: MaybeProp<bool>, + /// Set which item to start counting from starting with 1. + #[prop(optional, into)] + start: MaybeProp<usize>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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! { + <ol + class=move || { + format!("singlestage-breadcrumb-list {}", class.get().unwrap_or_default()) + } + type=move || number_type.get() + reversed=move || reversed.get() + start=move || start.get() + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </ol> + } +} diff --git a/singlestage/src/components/breadcrumb/mod.rs b/singlestage/src/components/breadcrumb/mod.rs index 068ba412..7dd09619 100644 --- a/singlestage/src/components/breadcrumb/mod.rs +++ b/singlestage/src/components/breadcrumb/mod.rs @@ -1,7 +1,15 @@ mod breadcrumb; +mod ellipsis; mod item; +mod link; +mod list; +mod page; mod separator; pub use breadcrumb::*; +pub use ellipsis::*; pub use item::*; +pub use link::*; +pub use list::*; +pub use page::*; pub use separator::*; diff --git a/singlestage/src/components/breadcrumb/page.rs b/singlestage/src/components/breadcrumb/page.rs new file mode 100644 index 00000000..77b26de0 --- /dev/null +++ b/singlestage/src/components/breadcrumb/page.rs @@ -0,0 +1,163 @@ +use leptos::prelude::*; + +/// Displays the current page in the breadcrumb (non-clickable). +#[component] +pub fn BreadcrumbPage( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + popover=move || popover.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! { + <span + aria_current="page" + aria_disabled="true" + class=move || { + format!("singlestage-breadcrumb-page {}", class.get().unwrap_or_default()) + } + role="link" + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </span> + } +} diff --git a/singlestage/src/components/breadcrumb/separator.rs b/singlestage/src/components/breadcrumb/separator.rs index dbe21fa0..145b1065 100644 --- a/singlestage/src/components/breadcrumb/separator.rs +++ b/singlestage/src/components/breadcrumb/separator.rs @@ -92,9 +92,6 @@ pub fn BreadcrumbSeparator( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp<String>, - /// Define the semantic meaning of content. - #[prop(optional, into)] - role: MaybeProp<String>, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp<String>, @@ -121,7 +118,6 @@ pub fn BreadcrumbSeparator( 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() @@ -146,7 +142,6 @@ pub fn BreadcrumbSeparator( 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() @@ -155,43 +150,39 @@ pub fn BreadcrumbSeparator( translate=move || translate.get() /> }; + view! { + <li + aria_hidden="true" + class=move || { + format!("singlestage-breadcrumb-separator {}", class.get().unwrap_or_default()) + } + role="presentation" + value=move || value.get() - if let Some(children) = children { - view! { - <li - value=move || value.get() - - {..global_attrs_1} - {..global_attrs_2} - > - {children()} - </li> - } - .into_any() - } else { - view! { - <li - value=move || value.get() - - {..global_attrs_1} - {..global_attrs_2} - > - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" - class="size-3.5" - > - <path d="m9 18 6-6-6-6" /> - </svg> - </li> - } - .into_any() + {..global_attrs_1} + {..global_attrs_2} + > + {if let Some(children) = children { + children() + } else { + view! { + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + class="lucide lucide-chevron-right-icon lucide-chevron-right" + > + <path d="m9 18 6-6-6-6" /> + </svg> + } + .into_any() + }} + </li> } } diff --git a/singlestage/src/components/breadcrumb/style/base/luma/breadcrumb.css b/singlestage/src/components/breadcrumb/style/base/luma/breadcrumb.css new file mode 100644 index 00000000..6fdd0cf6 --- /dev/null +++ b/singlestage/src/components/breadcrumb/style/base/luma/breadcrumb.css @@ -0,0 +1,37 @@ +@layer components1 { + .singlestage-breadcrumb-list { + @apply gap-1.5 + sm:gap-2.5 + text-muted-foreground + text-sm; + } + + .singlestage-breadcrumb-item { + @apply gap-1.5; + } + + .singlestage-breadcrumb-link { + @apply transition-colors; + + &:hover { + @apply text-foreground; + } + } + + .singlestage-breadcrumb-page { + @apply font-normal + text-foreground; + } + + .singlestage-breadcrumb-separator > svg { + @apply size-3.5; + } + + .singlestage-breadcrumb-ellipsis { + @apply size-5; + + & > svg { + @apply size-4; + } + } +} diff --git a/singlestage/src/components/breadcrumb/style/base/lyra/breadcrumb.css b/singlestage/src/components/breadcrumb/style/base/lyra/breadcrumb.css new file mode 100644 index 00000000..f82135bf --- /dev/null +++ b/singlestage/src/components/breadcrumb/style/base/lyra/breadcrumb.css @@ -0,0 +1,35 @@ +@layer components1 { + .singlestage-breadcrumb-list { + @apply gap-1.5 + text-muted-foreground + text-xs; + } + + .singlestage-breadcrumb-item { + @apply gap-1; + } + + .singlestage-breadcrumb-link { + @apply transition-colors; + + &:hover { + @apply text-foreground; + } + } + + .singlestage-breadcrumb-page { + @apply text-foreground font-normal; + } + + .singlestage-breadcrumb-separator > svg { + @apply size-3.5; + } + + .singlestage-breadcrumb-ellipsis { + @apply size-5; + + & > svg { + @apply size-4; + } + } +} diff --git a/singlestage/src/components/breadcrumb/style/base/maia/breadcrumb.css b/singlestage/src/components/breadcrumb/style/base/maia/breadcrumb.css new file mode 100644 index 00000000..6fdd0cf6 --- /dev/null +++ b/singlestage/src/components/breadcrumb/style/base/maia/breadcrumb.css @@ -0,0 +1,37 @@ +@layer components1 { + .singlestage-breadcrumb-list { + @apply gap-1.5 + sm:gap-2.5 + text-muted-foreground + text-sm; + } + + .singlestage-breadcrumb-item { + @apply gap-1.5; + } + + .singlestage-breadcrumb-link { + @apply transition-colors; + + &:hover { + @apply text-foreground; + } + } + + .singlestage-breadcrumb-page { + @apply font-normal + text-foreground; + } + + .singlestage-breadcrumb-separator > svg { + @apply size-3.5; + } + + .singlestage-breadcrumb-ellipsis { + @apply size-5; + + & > svg { + @apply size-4; + } + } +} diff --git a/singlestage/src/components/breadcrumb/style/base/mira/breadcrumb.css b/singlestage/src/components/breadcrumb/style/base/mira/breadcrumb.css new file mode 100644 index 00000000..a4058fde --- /dev/null +++ b/singlestage/src/components/breadcrumb/style/base/mira/breadcrumb.css @@ -0,0 +1,36 @@ +@layer components1 { + .singlestage-breadcrumb-list { + @apply gap-1.5 + text-muted-foreground + text-xs/relaxed; + } + + .singlestage-breadcrumb-item { + @apply gap-1; + } + + .singlestage-breadcrumb-link { + @apply transition-colors; + + &:hover { + @apply text-foreground; + } + } + + .singlestage-breadcrumb-page { + @apply font-normal + text-foreground; + } + + .singlestage-breadcrumb-separator > svg { + @apply size-3.5; + } + + .singlestage-breadcrumb-ellipsis { + @apply size-4; + + & > svg { + @apply size-3.5; + } + } +} diff --git a/singlestage/src/components/breadcrumb/style/base/nova/breadcrumb.css b/singlestage/src/components/breadcrumb/style/base/nova/breadcrumb.css new file mode 100644 index 00000000..b47772f2 --- /dev/null +++ b/singlestage/src/components/breadcrumb/style/base/nova/breadcrumb.css @@ -0,0 +1,36 @@ +@layer components1 { + .singlestage-breadcrumb-list { + @apply gap-1.5 + text-muted-foreground + text-sm; + } + + .singlestage-breadcrumb-item { + @apply gap-1; + } + + .singlestage-breadcrumb-link { + @apply transition-colors; + + &:hover { + @apply text-foreground; + } + } + + .singlestage-breadcrumb-page { + @apply font-normal + text-foreground; + } + + .singlestage-breadcrumb-separator > svg { + @apply size-3.5; + } + + .singlestage-breadcrumb-ellipsis { + @apply size-5; + + & > svg { + @apply size-4; + } + } +} diff --git a/singlestage/src/components/breadcrumb/style/base/sera/breadcrumb.css b/singlestage/src/components/breadcrumb/style/base/sera/breadcrumb.css new file mode 100644 index 00000000..7be8fcaf --- /dev/null +++ b/singlestage/src/components/breadcrumb/style/base/sera/breadcrumb.css @@ -0,0 +1,39 @@ +@layer components1 { + .singlestage-breadcrumb-list { + @apply gap-1.5 + sm:gap-2.5 + text-muted-foreground + text-xs + tracking-wide + uppercase; + } + + .singlestage-breadcrumb-item { + @apply gap-1.5; + } + + .singlestage-breadcrumb-link { + @apply transition-colors; + + &:hover { + @apply text-foreground; + } + } + + .singlestage-breadcrumb-page { + @apply font-normal + text-foreground; + } + + .singlestage-breadcrumb-separator > svg { + @apply size-3.5; + } + + .singlestage-breadcrumb-ellipsis { + @apply size-5; + + & > svg { + @apply size-4; + } + } +} diff --git a/singlestage/src/components/breadcrumb/style/base/vega/breadcrumb.css b/singlestage/src/components/breadcrumb/style/base/vega/breadcrumb.css new file mode 100644 index 00000000..83c856da --- /dev/null +++ b/singlestage/src/components/breadcrumb/style/base/vega/breadcrumb.css @@ -0,0 +1,37 @@ +@layer components1 { + .singlestage-breadcrumb-list { + @apply text-muted-foreground + gap-1.5 + text-sm + sm:gap-2.5; + } + + .singlestage-breadcrumb-item { + @apply gap-1.5; + } + + .singlestage-breadcrumb-link { + @apply transition-colors; + + &:hover { + @apply text-foreground; + } + } + + .singlestage-breadcrumb-page { + @apply font-normal + text-foreground; + } + + .singlestage-breadcrumb-separator > svg { + @apply size-3.5; + } + + .singlestage-breadcrumb-ellipsis { + @apply size-5; + + & > svg { + @apply size-4; + } + } +} diff --git a/singlestage/src/components/breadcrumb/style/breadcrumb.css b/singlestage/src/components/breadcrumb/style/breadcrumb.css new file mode 100644 index 00000000..612892b1 --- /dev/null +++ b/singlestage/src/components/breadcrumb/style/breadcrumb.css @@ -0,0 +1,24 @@ +@layer components { + .singlestage-breadcrumb-list { + @apply flex + flex-wrap + items-center + wrap-break-word; + } + + .singlestage-breadcrumb-item { + @apply inline-flex + items-center; + } + + .singlestage-breadcrumb-page { + @apply font-normal + text-foreground; + } + + .singlestage-breadcrumb-ellipsis { + @apply flex + items-center + justify-center; + } +} diff --git a/singlestage/src/components/button/button.css b/singlestage/src/components/button/button.css deleted file mode 100644 index ae55b7ab..00000000 --- a/singlestage/src/components/button/button.css +++ /dev/null @@ -1,140 +0,0 @@ -@layer components { - .singlestage-btn-primary, - .singlestage-btn-secondary, - .singlestage-btn-outline, - .singlestage-btn-ghost, - .singlestage-btn-link, - .singlestage-btn-destructive, - .singlestage-btn-icon { - @apply [&_svg:not([class*='size-'])]:size-4 - [&_svg]:pointer-events-none - [&_svg]:shrink-0 - aria-invalid:border-destructive - aria-invalid:ring-destructive/20 - cursor-pointer - disabled:opacity-50 - disabled:pointer-events-none - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - font-medium - inline-flex - items-center - justify-center - outline-none - rounded-md - shrink-0 - text-sm - transition-all - whitespace-nowrap; - } - - .singlestage-btn-primary, - .singlestage-btn-secondary, - .singlestage-btn-outline, - .singlestage-btn-ghost, - .singlestage-btn-link, - .singlestage-btn-destructive { - @apply gap-2 - h-9 - has-[>svg]:px-3 - px-4 - py-2; - } - - .singlestage-btn-primary { - .singlestage-input { - @apply border-primary-foreground/30!; - } - } - - .singlestage-btn-icon { - @apply size-9; - } - - .singlestage-btn-sm { - @apply gap-1.5 - h-8 - has-[>svg]:px-2.5 - px-3; - } - - .singlestage-btn-sm-icon { - @apply size-8; - } - - .singlestage-btn-lg { - @apply gap-2 - h-10 - has-[>svg]:px-4 - px-6; - } - - .singlestage-btn-lg-icon { - @apply size-10; - } - - .singlestage-btn-primary { - @apply bg-primary - hover:bg-primary/90 - shadow-xs - text-primary-foreground; - - &[aria-pressed="true"] { - @apply bg-primary/90; - } - } - - .singlestage-btn-secondary { - @apply bg-secondary - shadow-xs - text-secondary-foreground; - - &:hover, - &[aria-pressed="true"] { - @apply bg-secondary/80; - } - } - - .singlestage-btn-outline { - @apply bg-background - border - shadow-xs; - - &:hover, - &[aria-pressed="true"] { - @apply bg-accent - text-accent-foreground; - } - } - - .singlestage-btn-ghost { - &:hover, - &[aria-pressed="true"] { - @apply bg-accent - text-accent-foreground; - } - } - - .singlestage-btn-link { - @apply text-primary - underline-offset-4; - - &:hover, - &[aria-pressed="true"] { - @apply hover:underline; - } - } - - .singlestage-btn-destructive { - @apply bg-destructive - text-white - shadow-xs - focus-visible:ring-destructive/20; - - &:hover, - &[aria-pressed="true"] { - @apply bg-destructive/90; - } - } -} diff --git a/singlestage/src/components/button/mod.rs b/singlestage/src/components/button/mod.rs index 14a137ac..659f1dd5 100644 --- a/singlestage/src/components/button/mod.rs +++ b/singlestage/src/components/button/mod.rs @@ -1,6 +1,7 @@ use crate::{ - DropdownMenuContext, DropdownTriggerContext, InputGroupContext, PopoverContext, - PopoverTriggerContext, + BreadcrumbLinkContext, CollapsibleContext, FieldContext, InputGroupContext, PopoverContext, + Reactive, SheetCloseContext, SheetContext, SidebarContext, SidebarMenuButtonContext, + primitives::*, }; use leptos::prelude::*; @@ -9,16 +10,24 @@ use leptos::prelude::*; pub fn Button( children: Children, - /// The type of the button. Defaults to `submit`: - /// Button types: submit | button | reset + /// This component will render without styling + #[prop(optional, into)] + as_child: MaybeProp<bool>, + /// Renamed `button` `type` attribute to avoid name collisions #[prop(optional, into)] button_type: MaybeProp<String>, + /// Toggle whether clicking this button dismisses its parent popover + #[prop(optional, into, default = Reactive::new(true))] + dismiss: Reactive<bool>, + /// Whether the input is invalid + #[prop(optional, into)] + invalid: Reactive<bool>, /// The size of the button. Leave this empty for the default size. /// Sizes: small | large | icon | sm-icon | lg-icon #[prop(optional, into)] size: MaybeProp<String>, /// The display variant of the button. Defaults to `primary` - /// Variants: primary | secondary | outline | ghost | link | destructive + /// Variants: "primary" | "secondary" | "outline" | "ghost" | "link" | "destructive" #[prop(optional, into)] variant: MaybeProp<String>, @@ -35,7 +44,7 @@ pub fn Button( commandfor: MaybeProp<String>, /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, @@ -82,6 +91,7 @@ pub fn Button( // ARIA ATTRIBUTES // + #[prop(optional, into)] aria_current: MaybeProp<String>, /// Provide a custom accessible name for this element. #[prop(optional, into)] aria_label: MaybeProp<String>, @@ -191,6 +201,41 @@ pub fn Button( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { + let update_disabled = move || { + if let Some(field) = use_context::<FieldContext>() { + let is_disabled = field.disabled.get(); + disabled.set(is_disabled); + is_disabled + } else { + disabled.get() + } + }; + + let update_invalid = move || { + if let Some(field) = use_context::<FieldContext>() { + let is_invalid = field.invalid.get(); + invalid.set(is_invalid); + is_invalid + } else { + invalid.get() + } + }; + + let button_attrs = view! { + <{..} + command=move || command.get() + commandfor=move || commandfor.get() + form=move || form.get() + formaction=move || formaction.get() + formenctype=move || formenctype.get() + formmethod=move || formmethod.get() + formnovalidate=move || formnovalidate.get() + formtarget=move || formtarget.get() + name=move || name.get() + value=move || value.get() + /> + }; + let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -206,11 +251,11 @@ pub fn Button( inputmode=move || inputmode.get() is=move || is.get() itemid=move || itemid.get() + itemprop=move || itemprop.get() /> }; let global_attrs_2 = view! { <{..} - itemprop=move || itemprop.get() itemref=move || itemref.get() itemscope=move || itemscope.get() itemtype=move || itemtype.get() @@ -228,127 +273,280 @@ pub fn Button( /> }; - let button_attrs = view! { - <{..} - command=move || command.get() - commandfor=move || commandfor.get() - form=move || form.get() - formaction=move || formaction.get() - formenctype=move || formenctype.get() - formmethod=move || formmethod.get() - formnovalidate=move || formnovalidate.get() - formtarget=move || formtarget.get() - name=move || name.get() - value=move || value.get() - /> - }; - - let button_is_trigger: bool = use_context::<DropdownTriggerContext>().is_some() - || use_context::<PopoverTriggerContext>().is_some(); + let in_breadcrumb_link: bool = use_context::<BreadcrumbLinkContext>().is_some(); + let in_input_group: bool = use_context::<InputGroupContext>().is_some(); + let in_popover_menu_item: bool = use_context::<MenuItemContext>().is_some(); + let in_sidebar_menu_button: bool = use_context::<SidebarMenuButtonContext>().is_some(); + let is_dialog_action: bool = use_context::<DialogActionContext>().is_some(); + let is_dialog_cancel: bool = use_context::<DialogCancelContext>().is_some(); + let is_dialog_close: bool = use_context::<DialogCloseContext>().is_some(); + let is_sheet_close: bool = use_context::<SheetCloseContext>().is_some(); + let is_trigger: bool = use_context::<TriggerContext>().is_some(); view! { <button aria_controls=move || { - if button_is_trigger { - if let Some(dropdown) = use_context::<DropdownMenuContext>() { + if is_trigger { + if let Some(dropdown) = use_context::<PopoverMenuContext>() + && !in_popover_menu_item + { Some(dropdown.menu_id.get()) - } else { use_context::<PopoverContext>().map(|popover| popover.menu_id.get()) } + } else if let Some(popover) = use_context::<PopoverContext>() { + Some(popover.menu_id.get()) + } else if let Some(sheet) = use_context::<SheetContext>() { + Some(sheet.content_id.get()) + } else { + None + } + } else if let Some(menu_item) = use_context::<MenuItemContext>() + && menu_item.dismiss.get() + { + if let Some(menu) = use_context::<PopoverMenuContext>() { + Some(menu.menu_id.get()) + } else { + None + } + } else { + None + } + } + aria_current=move || aria_current.get() + aria_disabled=move || { if update_disabled() { Some("true") } else { None } } + aria_expanded=move || { + if is_trigger { + if let Some(dropdown) = use_context::<PopoverMenuContext>() + && dropdown.open.get() + { + Some("true") + } else if let Some(collapsible) = use_context::<CollapsibleContext>() + && collapsible.open.get() + { + Some("true") + } else { + None + } } else { None } } - aria_haspopup=move || { if button_is_trigger { Some("menu") } else { None } } + aria_haspopup=move || { + if is_trigger || in_popover_menu_item { Some("menu") } else { None } + } + aria_invalid=move || { if update_invalid() { Some("true") } else { None } } aria_label=move || aria_label.get() class=move || { - format!( - "{}{} {} {} {}", - if button_is_trigger { "singlestage-trigger " } else { "" }, - match variant.get().unwrap_or_default().as_str() { - "primary" => "singlestage-btn-primary", - "secondary" => "singlestage-btn-secondary", - "outline" => "singlestage-btn-outline", - "ghost" => "singlestage-btn-ghost", - "link" => "singlestage-btn-link", - "destructive" => "singlestage-btn-destructive", - _ => { - if use_context::<InputGroupContext>().is_some() - && variant.get().is_none() - { - "singlestage-btn-ghost" - } else { - "singlestage-btn-primary" + if as_child.get().unwrap_or_default() { + if in_breadcrumb_link { + format!("singlestage-breadcrumb-link {}", class.get().unwrap_or_default()) + } else { + class.get().unwrap_or_default() + } + } else if in_popover_menu_item { + class.get().unwrap_or_default() + } else if in_sidebar_menu_button { + format!( + "singlestage-sidebar-menu-button {} {} {}", + match size.get().unwrap_or_default().as_str() { + "sm" | "small" => "singlestage-sidebar-menu-button-size-sm", + "lg" | "large" => "singlestage-sidebar-menu-button-size-lg", + _ => "singlestage-sidebar-menu-button-size-default", + }, + match variant.get().unwrap_or_default().as_str() { + "outline" => "singlestage-sidebar-menu-button-variant-outline", + _ => "singlestage-sidebar-menu-button-variant-default", + }, + class.get().unwrap_or_default(), + ) + } else { + format!( + "singlestage-button{}{}{} {} {} {}", + if in_input_group { " singlestage-input-group-button" } else { "" }, + if is_trigger { " singlestage-trigger" } else { "" }, + if is_dialog_action { + " singlestage-alert-dialog-action" + } else if is_dialog_cancel { + " singlestage-alert-dialog-cancel" + } else { + "" + }, + match variant.get().unwrap_or_default().as_str() { + "secondary" => "singlestage-button-variant-secondary", + "outline" => "singlestage-button-variant-outline", + "ghost" => "singlestage-button-variant-ghost", + "link" => "singlestage-button-variant-link", + "destructive" => "singlestage-button-variant-destructive", + _ => { + if is_dialog_cancel { + "singlestage-button-variant-outline" + } else if in_input_group && variant.get().is_none() { + "singlestage-button-variant-ghost" + } else { + "singlestage-button-variant-default" + } } - } - }, - match size.get().unwrap_or_default().as_str() { - "sm" => "singlestage-btn-sm", - "small" => "singlestage-btn-sm", - "lg" => "singlestage-btn-lg", - "large" => "singlestage-btn-lg", - "icon" => "singlestage-btn-icon", - "sm-icon" => "singlestage-btn-sm-icon", - "icon-sm" => "singlestage-btn-sm-icon", - "lg-icon" => "singlestage-btn-lg-icon", - "icon-lg" => "singlestage-btn-lg-icon", - _ => "", - }, - if use_context::<InputGroupContext>().is_some() { + }, format!( - "singlestage-input-group-button {}", + "{}{}", match size.get().unwrap_or_default().as_str() { - "sm" => "singlestage-input-group-button-sm", - "icon-xs" => "singlestage-input-group-button-icon-xs", - "icon-sm" => "singlestage-input-group-button-icon-sm", - _ => "singlestage-input-group-button-xs", + "xs" | "extra small" => "singlestage-button-size-xs", + "sm" | "small" => "singlestage-button-size-sm", + "lg" | "large" => "singlestage-button-size-lg", + "icon" => "singlestage-button-size-icon", + "xs-icon" | "icon-xs" | "icon extra small" | "extra small icon" => { + "singlestage-button-size-icon-xs" + } + "sm-icon" | "icon-sm" | "icon small" | "small icon" => { + "singlestage-button-size-icon-sm" + } + "lg-icon" | "icon-lg" | "icon large" | "large icon" => { + "singlestage-button-size-icon-lg" + } + _ => "singlestage-button-size-default", }, - ) - } else { - "".to_string() - }, - class.get().unwrap_or_default(), - ) + if in_input_group { + match size.get().unwrap_or_default().as_str() { + "sm" | "small" => " singlestage-input-group-button-sm", + "icon-xs" | "xs-icon" => { + " singlestage-input-group-button-icon-xs" + } + "icon-sm" | "sm-icon" => { + " singlestage-input-group-button-icon-sm" + } + _ => " singlestage-input-group-button-xs", + } + } else { + "" + }, + ), + class.get().unwrap_or_default(), + ) + } } - disabled=disabled.get_untracked() + disabled=update_disabled + prop:disabled=update_disabled id=move || { - if button_is_trigger { + if is_trigger { let trigger_id = id.get().unwrap_or(uuid::Uuid::new_v4().to_string()); - if let Some(dropdown) = use_context::<DropdownMenuContext>() { + if let Some(dropdown) = use_context::<PopoverMenuContext>() { dropdown.trigger_id.set(trigger_id.clone()); } else if let Some(popover) = use_context::<PopoverContext>() { popover.trigger_id.set(trigger_id.clone()); + } else if let Some(sheet) = use_context::<SheetContext>() { + sheet.trigger_id.set(trigger_id.clone()); } Some(trigger_id) } else { id.get() } } + on:click=move |ev| { + if is_trigger { + if let Some(dropdown) = use_context::<PopoverMenuContext>() { + if let Some(menu_item) = use_context::<MenuItemContext>() { + if !menu_item.dismiss.get_untracked() { + ev.prevent_default(); + } + } else { + if dropdown.modal.get_untracked() { + ev.prevent_default(); + dropdown.open.set(!dropdown.open.get_untracked()); + } + } + } else if let Some(dialog) = use_context::<DialogContext>() { + dialog.open.set(true); + } else if let Some(popover) = use_context::<PopoverContext>() + && popover.modal.get_untracked() + { + ev.prevent_default(); + popover.open.set(!popover.open.get_untracked()); + } else if let Some(collapsible) = use_context::<CollapsibleContext>() { + if let Some(sidebar) = use_context::<SidebarContext>() + && !sidebar.open.get() + { + return + } else { + collapsible.open.set(!collapsible.open.get_untracked()) + } + } else if let Some(sheet) = use_context::<SheetContext>() + && !in_sidebar_menu_button + { + sheet.open.set(true); + } + } else if let Some(dialog) = use_context::<DialogContext>() { + if is_dialog_action || is_dialog_cancel || is_dialog_close { + if dismiss.get() { + dialog.open.set(false); + } + } + } else if let Some(sheet) = use_context::<SheetContext>() { + if is_sheet_close || in_sidebar_menu_button { + if dismiss.get() { + sheet.open.set(false); + } + } + } else if let Some(menu_item) = use_context::<MenuItemContext>() { + if let Some(menu) = use_context::<PopoverMenuContext>() { + if !menu.modal.get() && menu_item.dismiss.get() && dismiss.get() { + ev.prevent_default(); + menu.open.set(false); + } + } + } + } popovertarget=move || { - if button_is_trigger { + if is_trigger { let mut target_id = None; if let Some(popovertarget) = popovertarget.get() { target_id = Some(popovertarget); - } else if let Some(dropdown) = use_context::<DropdownMenuContext>() { + } else if let Some(dropdown) = use_context::<PopoverMenuContext>() + && !in_popover_menu_item + { target_id = Some(dropdown.menu_id.get()) } else if let Some(popover) = use_context::<PopoverContext>() { target_id = Some(popover.menu_id.get()) + } else if let Some(sheet) = use_context::<SheetContext>() { + target_id = Some(sheet.content_id.get()) } target_id + } else if let Some(menu_item) = use_context::<MenuItemContext>() { + if let Some(menu) = use_context::<PopoverMenuContext>() + && menu_item.dismiss.get() + { + Some(menu.menu_id.get()) + } else { + None + } } else { popovertarget.get() } } popovertargetaction=move || { - if button_is_trigger { + if is_trigger { + Some("toggle".to_string()) + } else if let Some(menu_item) = use_context::<MenuItemContext>() + && menu_item.dismiss.get() + { Some("toggle".to_string()) } else { popovertargetaction.get() } } - prop:disabled=move || disabled.get() + style:anchor-name=move || { + if is_trigger { + if let Some(dropdown) = use_context::<PopoverMenuContext>() { + Some(format!("--{}", dropdown.trigger_id.get())) + } else { + use_context::<PopoverContext>() + .map(|popover| format!("--{}", popover.trigger_id.get())) + } + } else { + None + } + } type=move || { if let Some(button_type) = button_type.get() { Some(button_type) - } else if use_context::<InputGroupContext>().is_some() { + } else if is_trigger || in_popover_menu_item { Some("button".to_string()) } else { None diff --git a/singlestage/src/components/button/style/base/luma/button.css b/singlestage/src/components/button/style/base/luma/button.css new file mode 100644 index 00000000..05bc32df --- /dev/null +++ b/singlestage/src/components/button/style/base/luma/button.css @@ -0,0 +1,188 @@ +@layer components1 { + .singlestage-button { + @apply bg-clip-padding + border + border-transparent + font-medium + rounded-4xl + text-sm; + + &:active:not(&:has(+ .singlestage-popover)) { + @apply translate-y-px; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-variant-default { + @apply bg-primary + text-primary-foreground; + + &:hover { + @apply bg-primary/80; + } + } + + .singlestage-button-variant-outline { + @apply bg-background + border-border; + + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + &:hover { + @apply bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-secondary + text-secondary-foreground; + } + } + + .singlestage-button-variant-ghost { + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply border-destructive/40 + ring-destructive/20; + } + + &:hover { + @apply bg-destructive/20; + } + } + + .singlestage-button-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } + + .singlestage-button-size-xs { + @apply gap-1 + h-6 + px-2.5 + text-xs; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-sm { + @apply gap-1 + h-8 + px-3; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + } + + .singlestage-button-size-default { + @apply gap-1.5 + h-9 + px-3; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2.5; + } + } + + .singlestage-button-size-lg { + @apply gap-1.5 + h-10 + px-4; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-3; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-3; + } + } + + .singlestage-button-size-icon-xs { + @apply size-6; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-icon-sm { + @apply size-8; + } + + .singlestage-button-size-icon { + @apply size-9; + } + + .singlestage-button-size-icon-lg { + @apply size-10; + } +} diff --git a/singlestage/src/components/button/style/base/luma/button_dark.css b/singlestage/src/components/button/style/base/luma/button_dark.css new file mode 100644 index 00000000..9279667a --- /dev/null +++ b/singlestage/src/components/button/style/base/luma/button_dark.css @@ -0,0 +1,33 @@ +@layer components1 { + .singlestage-button { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } + + .singlestage-button-variant-outline { + @apply bg-transparent; + + &:hover { + @apply bg-input/30; + } + } + + .singlestage-button-variant-ghost:hover { + @apply bg-muted/50; + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + + &:hover { + @apply bg-destructive/30; + } + } +} diff --git a/singlestage/src/components/button/style/base/lyra/button.css b/singlestage/src/components/button/style/base/lyra/button.css new file mode 100644 index 00000000..cfd273fc --- /dev/null +++ b/singlestage/src/components/button/style/base/lyra/button.css @@ -0,0 +1,196 @@ +@layer components1 { + .singlestage-button { + @apply bg-clip-padding + border + border-transparent + font-medium + rounded-none + text-xs; + + &:active:not(&:has(+ .singlestage-popover)) { + @apply translate-y-px; + } + + &:focus-visible { + @apply border-ring + ring-1 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-1 + ring-destructive/20; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-variant-default { + @apply bg-primary + text-primary-foreground; + + a:hover { + @apply bg-primary/80; + } + } + + .singlestage-button-variant-outline { + @apply bg-background + border-border; + + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + &:hover { + @apply bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-secondary + text-secondary-foreground; + } + } + + .singlestage-button-variant-ghost { + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply border-destructive/40 + ring-destructive/20; + } + + &:hover { + @apply bg-destructive/20; + } + } + + .singlestage-button-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } + + .singlestage-button-size-xs { + @apply gap-1 + h-6 + px-2 + rounded-none + text-xs; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-sm { + @apply gap-1 + h-7 + px-2.5 + rounded-none; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-button-size-default { + @apply gap-1.5 + h-8 + px-2.5; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + } + + .singlestage-button-size-lg { + @apply gap-1.5 + h-9 + px-2.5; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + } + + .singlestage-button-size-icon-xs { + @apply rounded-none + size-6; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-icon-sm { + @apply rounded-none + size-7; + } + + .singlestage-button-size-icon { + @apply size-8; + } + + .singlestage-button-size-icon-lg { + @apply size-9; + } +} diff --git a/singlestage/src/components/button/style/base/lyra/button_dark.css b/singlestage/src/components/button/style/base/lyra/button_dark.css new file mode 100644 index 00000000..7fd656ed --- /dev/null +++ b/singlestage/src/components/button/style/base/lyra/button_dark.css @@ -0,0 +1,34 @@ +@layer components1 { + .singlestage-button { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } + + .singlestage-button-variant-outline { + @apply bg-input/30 + border-input; + + &:hover { + @apply bg-input/50; + } + } + + .singlestage-button-variant-ghost:hover { + @apply bg-muted/50; + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + + &:hover { + @apply bg-destructive/30; + } + } +} diff --git a/singlestage/src/components/button/style/base/maia/button.css b/singlestage/src/components/button/style/base/maia/button.css new file mode 100644 index 00000000..3742cf91 --- /dev/null +++ b/singlestage/src/components/button/style/base/maia/button.css @@ -0,0 +1,188 @@ +@layer components1 { + .singlestage-button { + @apply bg-clip-padding + border + border-transparent + font-medium + rounded-4xl + text-sm; + + &:active:not(&:has(+ .singlestage-popover)) { + @apply translate-y-px; + } + + &:focus-visible { + @apply border-ring + ring-[3px] + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-[3px] + ring-destructive/20; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-variant-default { + @apply bg-primary + text-primary-foreground; + + &:hover { + @apply bg-primary/80; + } + } + + .singlestage-button-variant-outline { + @apply bg-input/30 + border-border; + + &:hover { + @apply bg-input/50 + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + &:hover { + @apply bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-secondary + text-secondary-foreground; + } + } + + .singlestage-button-variant-ghost { + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply border-destructive/40 + ring-destructive/20; + } + + &:hover { + @apply bg-destructive/20; + } + } + + .singlestage-button-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } + + .singlestage-button-size-xs { + @apply gap-1 + h-6 + px-2.5 + text-xs; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-sm { + @apply gap-1 + h-8 + px-3; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + } + + .singlestage-button-size-default { + @apply gap-1.5 + h-9 + px-3; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2.5; + } + } + + .singlestage-button-size-lg { + @apply gap-1.5 + h-10 + px-4; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-3; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-3; + } + } + + .singlestage-button-size-icon-xs { + @apply size-6; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-icon-sm { + @apply size-8; + } + + .singlestage-button-size-icon { + @apply size-9; + } + + .singlestage-button-size-icon-lg { + @apply size-10; + } +} diff --git a/singlestage/src/components/button/style/base/maia/button_dark.css b/singlestage/src/components/button/style/base/maia/button_dark.css new file mode 100644 index 00000000..74124982 --- /dev/null +++ b/singlestage/src/components/button/style/base/maia/button_dark.css @@ -0,0 +1,25 @@ +@layer components1 { + .singlestage-button { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } + + .singlestage-button-variant-ghost:hover { + @apply bg-muted/50; + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + + &:hover { + @apply bg-destructive/30; + } + } +} diff --git a/singlestage/src/components/button/style/base/mira/button.css b/singlestage/src/components/button/style/base/mira/button.css new file mode 100644 index 00000000..926c4847 --- /dev/null +++ b/singlestage/src/components/button/style/base/mira/button.css @@ -0,0 +1,216 @@ +@layer components1 { + .singlestage-button { + @apply bg-clip-padding + border + border-transparent + font-medium + rounded-md + text-xs/relaxed; + + &:active:not(&:has(+ .singlestage-popover)) { + @apply translate-y-px; + } + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-variant-default { + @apply bg-primary + text-primary-foreground; + + &:hover { + @apply bg-primary/80; + } + } + + .singlestage-button-variant-outline { + @apply border-border; + + &:hover { + @apply bg-input/50 + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + &:hover { + @apply bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-secondary + text-secondary-foreground; + } + } + + .singlestage-button-variant-ghost { + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply border-destructive/40 + ring-destructive/20; + } + + &:hover { + @apply bg-destructive/20; + } + } + + .singlestage-button-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } + + .singlestage-button-size-xs { + @apply gap-1 + h-5 + px-2 + rounded-sm + text-[0.625rem]; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + svg:not([class*="size-"]) { + @apply size-2.5; + } + } + + .singlestage-button-size-sm { + @apply gap-1 + h-6 + px-2 + text-xs/relaxed; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-default { + @apply gap-1 + h-7 + px-2 + text-xs/relaxed; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-button-size-lg { + @apply gap-1 + h-8 + px-2.5 + text-xs/relaxed; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-size-icon-xs { + @apply size-5 + rounded-sm; + + svg:not([class*="size-"]) { + @apply size-2.5; + } + } + + .singlestage-button-size-icon-sm { + @apply size-6; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-icon { + @apply size-7; + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-button-size-icon-lg { + @apply size-8; + + svg:not([class*="size-"]) { + @apply size-4; + } + } +} diff --git a/singlestage/src/components/button/style/base/mira/button_dark.css b/singlestage/src/components/button/style/base/mira/button_dark.css new file mode 100644 index 00000000..62b690b3 --- /dev/null +++ b/singlestage/src/components/button/style/base/mira/button_dark.css @@ -0,0 +1,29 @@ +@layer components1 { + .singlestage-button { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } + + .singlestage-button-variant-outline { + @apply bg-input/30; + } + + .singlestage-button-variant-ghost:hover { + @apply bg-muted/50; + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + + &:hover { + @apply bg-destructive/30; + } + } +} diff --git a/singlestage/src/components/button/style/base/nova/button.css b/singlestage/src/components/button/style/base/nova/button.css new file mode 100644 index 00000000..81cfbabf --- /dev/null +++ b/singlestage/src/components/button/style/base/nova/button.css @@ -0,0 +1,206 @@ +@layer components1 { + .singlestage-button { + @apply bg-clip-padding + border + border-transparent + font-medium + rounded-lg + text-sm; + + &:active:not(&:has(+ .singlestage-popover)) { + @apply translate-y-px; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-variant-default { + @apply bg-primary + text-primary-foreground; + + a:hover { + @apply bg-primary/80; + } + } + + .singlestage-button-variant-outline { + @apply bg-background + border-border; + + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + &:hover { + @apply bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-secondary + text-secondary-foreground; + } + } + + .singlestage-button-variant-ghost { + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply border-destructive/40 + ring-destructive/20; + } + + &:hover { + @apply bg-destructive/20; + } + } + + .singlestage-button-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } + + .singlestage-button-size-xs { + @apply gap-1 + h-6 + px-2 + rounded-[min(var(--radius-md),10px)] + text-xs; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-sm { + @apply gap-1 + h-7 + px-2.5 + rounded-[min(var(--radius-md),12px)] + text-[0.8rem]; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-button-size-default { + @apply gap-1.5 + h-8 + px-2.5; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + } + + .singlestage-button-size-lg { + @apply gap-1.5 + h-9 + px-2.5; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + } + + .singlestage-button-size-icon-xs { + @apply rounded-[min(var(--radius-md),10px)] + size-6; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-icon-sm { + @apply rounded-[min(var(--radius-md),12px)] + size-7; + } + + .singlestage-button-size-icon { + @apply size-8; + } + + .singlestage-button-size-icon-lg { + @apply size-9; + } + + .singlestage-button-group { + .singlestage-button-size-sm, + .singlestage-button-size-xs, + .singlestage-button-size-icon-sm, + .singlestage-button-size-icon-xs { + @apply rounded-lg; + } + } +} diff --git a/singlestage/src/components/button/style/base/nova/button_dark.css b/singlestage/src/components/button/style/base/nova/button_dark.css new file mode 100644 index 00000000..7fd656ed --- /dev/null +++ b/singlestage/src/components/button/style/base/nova/button_dark.css @@ -0,0 +1,34 @@ +@layer components1 { + .singlestage-button { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } + + .singlestage-button-variant-outline { + @apply bg-input/30 + border-input; + + &:hover { + @apply bg-input/50; + } + } + + .singlestage-button-variant-ghost:hover { + @apply bg-muted/50; + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + + &:hover { + @apply bg-destructive/30; + } + } +} diff --git a/singlestage/src/components/button/style/base/sera/button.css b/singlestage/src/components/button/style/base/sera/button.css new file mode 100644 index 00000000..1420dc69 --- /dev/null +++ b/singlestage/src/components/button/style/base/sera/button.css @@ -0,0 +1,190 @@ +@layer components1 { + .singlestage-button { + @apply bg-clip-padding + border + border-transparent + font-semibold + rounded-none + text-xs + tracking-widest + uppercase; + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + } + + &:active:not(&:has(+ .singlestage-popover)) { + @apply translate-y-px; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-button-variant-default { + @apply bg-primary + text-primary-foreground; + + &:hover { + @apply bg-primary/80; + } + } + + .singlestage-button-variant-outline { + @apply bg-transparent + border-border; + + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + &:hover { + @apply bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-secondary + text-secondary-foreground; + } + } + + .singlestage-button-variant-ghost { + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply border-destructive/40 + ring-destructive/20; + } + + &:hover { + @apply bg-destructive/20; + } + } + + .singlestage-button-variant-link { + @apply text-primary + underline + underline-offset-4; + + &:hover { + @apply underline; + } + } + + .singlestage-button-size-xs { + @apply gap-1 + h-7 + px-3; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-sm { + @apply gap-1 + h-9 + px-4; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-3; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-3; + } + } + + .singlestage-button-size-default { + @apply gap-1.5 + h-10 + px-6; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-4; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-4; + } + } + + .singlestage-button-size-lg { + @apply gap-1.5 + h-11 + px-8; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-5; + } + } + + .singlestage-button-size-icon-xs { + @apply size-7; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-icon-sm { + @apply size-9; + } + + .singlestage-button-size-icon { + @apply size-10; + } + + .singlestage-button-size-icon-lg { + @apply size-11; + } +} diff --git a/singlestage/src/components/button/style/base/sera/button_dark.css b/singlestage/src/components/button/style/base/sera/button_dark.css new file mode 100644 index 00000000..9b39ceff --- /dev/null +++ b/singlestage/src/components/button/style/base/sera/button_dark.css @@ -0,0 +1,29 @@ +@layer components1 { + .singlestage-button { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } + + .singlestage-button-variant-outline:hover { + @apply bg-input/30; + } + + .singlestage-button-variant-ghost:hover { + @apply bg-muted/50; + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + + &:hover { + @apply bg-destructive/30; + } + } +} diff --git a/singlestage/src/components/button/style/base/vega/button.css b/singlestage/src/components/button/style/base/vega/button.css new file mode 100644 index 00000000..9284f314 --- /dev/null +++ b/singlestage/src/components/button/style/base/vega/button.css @@ -0,0 +1,203 @@ +@layer components1 { + .singlestage-button { + @apply bg-clip-padding + border + border-transparent + font-medium + rounded-md + text-sm; + + &:active:not(&:has(+ .singlestage-popover)) { + @apply translate-y-px; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-variant-default { + @apply bg-primary + text-primary-foreground; + + &:hover { + @apply bg-primary/80; + } + } + + .singlestage-button-variant-outline { + @apply border-border + bg-background + shadow-xs; + + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-secondary { + @apply bg-secondary + text-secondary-foreground; + + &:hover { + @apply bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-secondary + text-secondary-foreground; + } + } + + .singlestage-button-variant-ghost { + &:hover { + @apply bg-muted + text-foreground; + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + @apply bg-muted + text-foreground; + } + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/10 + text-destructive; + + &:focus-visible { + @apply border-destructive/40 + ring-destructive/20; + } + + &:hover { + @apply bg-destructive/20; + } + } + + .singlestage-button-variant-link { + @apply text-primary + underline-offset-4; + + &:hover { + @apply underline; + } + } + + .singlestage-button-size-xs { + @apply gap-1 + h-6 + px-2 + rounded-[min(var(--radius-md),8px)] + text-xs; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-sm { + @apply gap-1 + h-8 + px-2.5 + rounded-[min(var(--radius-md),10px)]; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + } + + .singlestage-button-size-default { + @apply gap-1.5 + h-9 + px-2.5; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + } + + .singlestage-button-size-lg { + @apply gap-1.5 + h-10 + px-2.5; + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + } + + .singlestage-button-size-icon-xs { + @apply rounded-[min(var(--radius-md),8px)] + size-6; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-button-size-icon-sm { + @apply rounded-[min(var(--radius-md),10px)] + size-8; + } + + .singlestage-button-size-icon { + @apply size-9; + } + + .singlestage-button-size-icon-lg { + @apply size-10; + } + + .singlestage-button-group { + .singlestage-button-size-default, + .singlestage-button-size-icon-sm, + .singlestage-button-size-icon-xs, + .singlestage-button-size-sm, + .singlestage-button-size-xs { + @apply rounded-md; + } + } +} diff --git a/singlestage/src/components/button/style/base/vega/button_dark.css b/singlestage/src/components/button/style/base/vega/button_dark.css new file mode 100644 index 00000000..7fd656ed --- /dev/null +++ b/singlestage/src/components/button/style/base/vega/button_dark.css @@ -0,0 +1,34 @@ +@layer components1 { + .singlestage-button { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } + + .singlestage-button-variant-outline { + @apply bg-input/30 + border-input; + + &:hover { + @apply bg-input/50; + } + } + + .singlestage-button-variant-ghost:hover { + @apply bg-muted/50; + } + + .singlestage-button-variant-destructive { + @apply bg-destructive/20; + + &:focus-visible { + @apply ring-destructive/40; + } + + &:hover { + @apply bg-destructive/30; + } + } +} diff --git a/singlestage/src/components/button/style/button.css b/singlestage/src/components/button/style/button.css new file mode 100644 index 00000000..22473313 --- /dev/null +++ b/singlestage/src/components/button/style/button.css @@ -0,0 +1,22 @@ +@layer components { + .singlestage-button { + @apply inline-flex + items-center + justify-center + outline-none + select-none + shrink-0 + transition-all + whitespace-nowrap; + + &:disabled { + @apply opacity-50 + pointer-events-none; + } + + svg { + @apply pointer-events-none + shrink-0; + } + } +} diff --git a/singlestage/src/components/button_group/button_group.css b/singlestage/src/components/button_group/button_group.css deleted file mode 100644 index 2b80f796..00000000 --- a/singlestage/src/components/button_group/button_group.css +++ /dev/null @@ -1,75 +0,0 @@ -@layer components { - .singlestage-button-group { - @apply [&>input]:flex-1 - flex - has-[>.singlestage-button-group]:gap-2 - items-stretch - w-fit; - - & > * { - @apply focus-visible:relative - focus-visible:z-10; - } - } - - .singlestage-button-group-horizontal { - > *:not(:first-child), - > *:not(:first-child) > .singlestage-trigger { - @apply !border-l-0 - !rounded-l-none; - } - - > *:not(:last-child), - > *:not(:last-child) > .singlestage-trigger { - @apply !rounded-r-none; - } - - .singlestage-button-group-separator { - @apply h-full - w-px; - } - } - - .singlestage-button-group-vertical { - @apply flex-col; - - > *:not(:first-child), - > *:not(:first-child) > .singlestage-trigger { - @apply !border-t-0 - !rounded-t-none; - } - - > *:not(:last-child), - > *:not(:last-child) > .singlestage-trigger { - @apply !rounded-b-none; - } - - .singlestage-button-group-separator { - @apply h-px - w-full; - } - } - - .singlestage-button-group-text { - @apply [&_svg:not([class*='size-'])]:size-4 - [&_svg]:pointer-events-none - bg-muted - border - flex - font-medium - gap-2 - items-center - px-4 - rounded-md - shadow-xs - text-sm; - } - - .singlestage-button-group-separator { - @apply bg-input - m-0 - relative - self-stretch - shrink-0; - } -} diff --git a/singlestage/src/components/button_group/group.rs b/singlestage/src/components/button_group/group.rs index 3ef323cd..3c799302 100644 --- a/singlestage/src/components/button_group/group.rs +++ b/singlestage/src/components/button_group/group.rs @@ -7,7 +7,7 @@ pub fn ButtonGroup( /// Sets the display direction of the ButtonGroup. #[prop(optional, into)] - vertical: MaybeProp<bool>, + orientation: MaybeProp<String>, // ARIA ATTRIBUTES // @@ -161,9 +161,9 @@ pub fn ButtonGroup( class=move || { format!( "singlestage-button-group {} {}", - match vertical.get().unwrap_or_default() { - true => "singlestage-button-group-vertical", - false => "singlestage-button-group-horizontal", + match orientation.get().unwrap_or_default().as_str() { + "vertical" => "singlestage-button-group-orientation-vertical", + _ => "singlestage-button-group-orientation-horizontal", }, class.get().unwrap_or_default(), ) diff --git a/singlestage/src/components/button_group/separator.rs b/singlestage/src/components/button_group/separator.rs index 896bbea2..b0c5a19a 100644 --- a/singlestage/src/components/button_group/separator.rs +++ b/singlestage/src/components/button_group/separator.rs @@ -1,8 +1,16 @@ +use crate::Separator; use leptos::prelude::*; -/// Visually separates elements in the ButtonGroup. +/// Visually separates button group buttons. #[component] pub fn ButtonGroupSeparator( + #[prop(optional)] children: Option<Children>, + + /// Specify the orientation of the separator + /// Accepted values: `"vertical"` | `"horizontal"` (default) + #[prop(optional, into)] + orientation: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -126,6 +134,7 @@ pub fn ButtonGroupSeparator( itemid=move || itemid.get() /> }; + let global_attrs_2 = view! { <{..} itemprop=move || itemprop.get() @@ -146,14 +155,35 @@ pub fn ButtonGroupSeparator( /> }; - view! { - <div - class=move || { - format!("singlestage-button-group-separator {}", class.get().unwrap_or_default()) - } + if let Some(children) = children { + view! { + <Separator + class=format!( + "singlestage-button-group-separator {}", + class.get().unwrap_or_default(), + ) + orientation + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </Separator> + } + .into_any() + } else { + view! { + <Separator + class=format!( + "singlestage-button-group-separator {}", + class.get().unwrap_or_default(), + ) + orientation - {..global_attrs_1} - {..global_attrs_2} - ></div> + {..global_attrs_1} + {..global_attrs_2} + /> + } + .into_any() } } diff --git a/singlestage/src/components/button_group/style/base/luma/button_group.css b/singlestage/src/components/button_group/style/base/luma/button_group.css new file mode 100644 index 00000000..e80c9d3e --- /dev/null +++ b/singlestage/src/components/button_group/style/base/luma/button_group.css @@ -0,0 +1,61 @@ +@layer components2 { + .singlestage-button-group { + /* TODO: revisit selects */ + /* has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-4xl */ + /* *:data-[slot=select-trigger]:border-border */ + /* [&>[data-slot=select-trigger]:focus-visible]:border-ring; */ + + &:has(.singlestage-button-variant-outline) { + & > .singlestage-input { + @apply border-border; + &:focus-visible { + @apply border-ring; + } + } + + .singlestage-input-group { + @apply border-border; + } + + & > .singlestage-input-group:has(:focus-visible) { + @apply border-ring; + } + } + + &:has(> .singlestage-button-group) { + @apply gap-2; + } + } + + /* Potentially useless */ + /* .singlestage-button-group-orientation-horizontal { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-4xl!; */ + /* } */ + /**/ + /* .singlestage-button-group-orientation-vertical { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-4xl!; */ + /* } */ + + .singlestage-button-group-text { + @apply bg-muted + border + font-medium + gap-2 + px-2.5 + rounded-4xl + text-sm; + + svg:not([class*="size-"]) { + @apply size-4; + } + + &:has(~ .singlestage-input-group), + .singlestage-input-group ~ & { + @apply border-muted; + } + } + + .singlestage-button-group-separator { + @apply bg-input; + } +} diff --git a/singlestage/src/components/button_group/style/base/lyra/button_group.css b/singlestage/src/components/button_group/style/base/lyra/button_group.css new file mode 100644 index 00000000..c69f216e --- /dev/null +++ b/singlestage/src/components/button_group/style/base/lyra/button_group.css @@ -0,0 +1,30 @@ +@layer components2 { + .singlestage-button-group { + @apply rounded-none; + + /* TODO: revisit selects */ + /* has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-none; */ + + &:has(> .singlestage-button-group) { + @apply gap-2; + } + } + + .singlestage-button-group-text { + @apply bg-muted + border + font-medium + gap-2 + px-2.5 + rounded-none + text-xs; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-group-separator { + @apply bg-input; + } +} diff --git a/singlestage/src/components/button_group/style/base/maia/button_group.css b/singlestage/src/components/button_group/style/base/maia/button_group.css new file mode 100644 index 00000000..11efc9f8 --- /dev/null +++ b/singlestage/src/components/button_group/style/base/maia/button_group.css @@ -0,0 +1,37 @@ +@layer components2 { + .singlestage-button-group { + /* TODO: revisit selects */ + /* @apply has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-4xl; */ + + &:has(> .singlestage-button-group) { + @apply gap-2; + } + } + + /* Potentially useless */ + /* .singlestage-button-group-orientation-horizontal { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-4xl!; */ + /* } */ + /**/ + /* .singlestage-button-group-orientation-vertical { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-4xl!; */ + /* } */ + + .singlestage-button-group-text { + @apply bg-muted + border + font-medium + gap-2 + px-2.5 + rounded-4xl + text-sm; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-group-separator { + @apply bg-input; + } +} diff --git a/singlestage/src/components/button_group/style/base/mira/button_group.css b/singlestage/src/components/button_group/style/base/mira/button_group.css new file mode 100644 index 00000000..0b22597e --- /dev/null +++ b/singlestage/src/components/button_group/style/base/mira/button_group.css @@ -0,0 +1,37 @@ +@layer components2 { + .singlestage-button-group { + /* TODO: revisit selects */ + /* @apply has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md; */ + + &:has(> .singlestage-button-group) { + @apply gap-2; + } + } + + /* Potentially useless */ + /* .singlestage-button-group-orientation-horizontal { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md!; */ + /* } */ + /**/ + /* .singlestage-button-group-orientation-vertical { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md!; */ + /* } */ + + .singlestage-button-group-text { + @apply bg-muted + border + font-medium + gap-2 + px-2.5 + rounded-md + text-xs/relaxed; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-group-separator { + @apply bg-input; + } +} diff --git a/singlestage/src/components/button_group/style/base/nova/button_group.css b/singlestage/src/components/button_group/style/base/nova/button_group.css new file mode 100644 index 00000000..82e063ca --- /dev/null +++ b/singlestage/src/components/button_group/style/base/nova/button_group.css @@ -0,0 +1,37 @@ +@layer components2 { + .singlestage-button-group { + /* TODO: revisit selects */ + /* @apply has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg; */ + + &:has(> .singlestage-button-group) { + @apply gap-2; + } + } + + /* Potentially useless */ + /* .singlestage-button-group-orientation-horizontal { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-lg!; */ + /* } */ + /**/ + /* .singlestage-button-group-orientation-vertical { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg!; */ + /* } */ + + .singlestage-button-group-text { + @apply bg-muted + border + font-medium + gap-2 + px-2.5 + rounded-lg + text-sm; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-group-separator { + @apply bg-input; + } +} diff --git a/singlestage/src/components/button_group/style/base/sera/button_group.css b/singlestage/src/components/button_group/style/base/sera/button_group.css new file mode 100644 index 00000000..2b2a16f0 --- /dev/null +++ b/singlestage/src/components/button_group/style/base/sera/button_group.css @@ -0,0 +1,76 @@ +@layer components2 { + .singlestage-button-group { + /* TODO: revisit selects */ + /* has-[>[data-variant=outline]]:*:data-[slot=select-trigger]:border-border */ + /* has-[>[data-variant=outline]]:[&>[data-slot=select-trigger]:focus-visible]:border-ring */ + /* has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-none */ + + /* TODO: revisit combobox */ + /* has-[>[data-variant=outline]]:*:[[role=combobox]]:px-2.5 */ + + .singlestage-input { + @apply px-4; + } + + &:has(> .singlestage-button-variant-outline) { + & > .singlestage-input { + @apply border-border; + + &:focus-visible { + @apply border-ring; + } + } + + & > .singlestage-input-group:has(:focus-visible) { + @apply border-ring; + } + + .singlestage-input-group { + @apply border-border + px-2.5; + } + + .singlestage-input-group-text { + @apply border-border; + } + } + + &:has(> .singlestage-button-group) { + @apply gap-2; + } + } + + /* Potentially useless */ + /* .singlestage-button-group-orientation-horizontal { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-none!; */ + /* } */ + /**/ + /* .singlestage-button-group-orientation-vertical { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-none!; */ + /* } */ + + .singlestage-button-group-text { + @apply bg-transparent + border + border-b-input + border-transparent + font-semibold + gap-2 + px-2.5 + text-xs + uppercase; + + svg:not([class*="size-"]) { + @apply size-3.5; + } + + + .singlestage-input-group { + @apply border-l-0! + border-r-0!; + } + } + + .singlestage-button-group-separator { + @apply bg-input; + } +} diff --git a/singlestage/src/components/button_group/style/base/vega/button_group.css b/singlestage/src/components/button_group/style/base/vega/button_group.css new file mode 100644 index 00000000..628da376 --- /dev/null +++ b/singlestage/src/components/button_group/style/base/vega/button_group.css @@ -0,0 +1,38 @@ +@layer components2 { + .singlestage-button-group { + /* TODO: revisit selects */ + /* @apply has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md; */ + + &:has(> .singlestage-button-group) { + @apply gap-2; + } + } + + /* Potentially useless */ + /* .singlestage-button-group-orientation-horizontal { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md!; */ + /* } */ + /**/ + /* .singlestage-button-group-orientation-vertical { */ + /* @apply [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md!; */ + /* } */ + + .singlestage-button-group-text { + @apply bg-muted + border + font-medium + gap-2 + px-2.5 + rounded-md + shadow-xs + text-sm; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-button-group-separator { + @apply bg-input; + } +} diff --git a/singlestage/src/components/button_group/style/button_group.css b/singlestage/src/components/button_group/style/button_group.css new file mode 100644 index 00000000..367a764a --- /dev/null +++ b/singlestage/src/components/button_group/style/button_group.css @@ -0,0 +1,122 @@ +@layer components2 { + .singlestage-button-group { + @apply flex + items-stretch + w-fit; + + > .singlestage-input { + @apply flex-1; + } + + > .singlestage-trigger:not([class*="w-"]) { + @apply w-fit; + } + + *:focus-visible { + @apply relative + z-10; + } + } + + .singlestage-button-group-text { + @apply flex + items-center; + + svg { + @apply pointer-events-none; + } + } + + .singlestage-button-group-separator { + @apply bg-input + relative + self-stretch; + } + + .singlestage-button-group-orientation-horizontal { + /* @apply *:data-slot:rounded-r-none */ + /* [&>[data-slot]~[data-slot]]:rounded-l-none */ + /* [&>[data-slot]~[data-slot]]:border-l-0; */ + + > *:not(:last-child), + > *:not(:last-child) > .singlestage-trigger, + > *:not(:last-child) > .singlestage-select, + > *:not(:last-child) > .singlestage-input-group-input, + > *:not(:last-child) > .singlestage-input-group-addon { + @apply rounded-r-none; + } + + > *:not(:first-child), + > *:not(:first-child) > .singlestage-trigger, + > *:not(:first-child) > .singlestage-select, + > *:not(:first-child) > .singlestage-input-group-input, + > *:not(:first-child) > .singlestage-input-group-addon { + @apply border-l-0 + rounded-l-none; + } + + .singlestage-button-group-separator { + @apply h-auto + my-px + w-px; + } + + .singlestage-button:has(+ .singlestage-button-group-separator) { + @apply border-r-0; + } + + .singlestage-button-group-text { + &:has(+ .singlestage-input-group) { + @apply border-r-0; + + + .singlestage-input-group { + @apply border-l-1; + } + } + } + } + + .singlestage-button-group-orientation-vertical { + @apply flex-col; + /* *:data-slot:rounded-b-none */ + /* [&>[data-slot]~[data-slot]]:rounded-t-none */ + /* [&>[data-slot]~[data-slot]]:border-t-0; */ + + > *:not(:last-child), + > *:not(:last-child) > .singlestage-trigger, + > *:not(:last-child) > .singlestage-select, + > *:not(:last-child) > .singlestage-input-group-input, + > *:not(:last-child) > .singlestage-input-group-addon { + @apply rounded-b-none; + } + + > *:not(:first-child), + > *:not(:first-child) > .singlestage-trigger, + > *:not(:first-child) > .singlestage-select, + > *:not(:first-child) > .singlestage-input-group-input, + > *:not(:first-child) > .singlestage-input-group-addon { + @apply border-t-0 + rounded-t-none; + } + + .singlestage-button-group-separator { + @apply h-px + mx-px + w-auto; + } + + .singlestage-button:has(+ .singlestage-button-group-separator) { + @apply border-b-0; + } + + .singlestage-button-group-text { + &:has(+ .singlestage-input-group) { + @apply border-b-0; + + + .singlestage-input-group { + @apply border-t-1; + } + } + } + } +} diff --git a/singlestage/src/components/card/action.rs b/singlestage/src/components/card/action.rs new file mode 100644 index 00000000..eb363b22 --- /dev/null +++ b/singlestage/src/components/card/action.rs @@ -0,0 +1,161 @@ +use leptos::prelude::*; + +/// Renders content like a button or a badge in the top-right of the header. +#[component] +pub fn CardAction( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <div + class=move || { format!("singlestage-card-action {}", class.get().unwrap_or_default()) } + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + } +} diff --git a/singlestage/src/components/card/card.css b/singlestage/src/components/card/card.css deleted file mode 100644 index 2577164b..00000000 --- a/singlestage/src/components/card/card.css +++ /dev/null @@ -1,46 +0,0 @@ -@layer components { - .singlestage-card { - @apply bg-card - border - flex - flex-col - gap-6 - py-6 - rounded-xl - shadow-sm - text-card-foreground; - - > header { - @apply @container/card-header - [.border-b]:pb-6 - auto-rows-min - gap-1.5 - grid - grid-rows-[auto_auto] - has-data-[slot=card-action]:grid-cols-[1fr_auto] - items-start - px-6; - - h2 { - @apply font-semibold - leading-none; - } - - p { - @apply text-muted-foreground - text-sm; - } - } - - > section { - @apply px-6; - } - - > footer { - @apply [.border-t]:pt-6 - flex - items-center - px-6; - } - } -} diff --git a/singlestage/src/components/card/card.rs b/singlestage/src/components/card/card.rs index f37579a4..85603517 100644 --- a/singlestage/src/components/card/card.rs +++ b/singlestage/src/components/card/card.rs @@ -5,6 +5,12 @@ use leptos::prelude::*; pub fn Card( children: Children, + /// Set the size of the card. + /// + /// Accepted values: "sm" + #[prop(optional, into)] + size: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -151,7 +157,16 @@ pub fn Card( }; view! { <div - class=move || { format!("singlestage-card {}", class.get().unwrap_or_default()) } + class=move || { + format!( + "singlestage-card{} {}", + match size.get().unwrap_or_default().as_str() { + "sm" | "small" => " singlestage-card-size-sm", + _ => "", + }, + class.get().unwrap_or_default(), + ) + } {..global_attrs_1} {..global_attrs_2} diff --git a/singlestage/src/components/card/content.rs b/singlestage/src/components/card/content.rs index 331d73e5..6ab90087 100644 --- a/singlestage/src/components/card/content.rs +++ b/singlestage/src/components/card/content.rs @@ -115,7 +115,6 @@ pub fn CardContent( 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() @@ -151,7 +150,12 @@ pub fn CardContent( }; view! { - <section {..global_attrs_1} {..global_attrs_2}> + <section + class=move || format!("singlestage-card-content {}", class.get().unwrap_or_default()) + + {..global_attrs_1} + {..global_attrs_2} + > {children()} </section> } diff --git a/singlestage/src/components/card/description.rs b/singlestage/src/components/card/description.rs index 2f486dcc..84e8843b 100644 --- a/singlestage/src/components/card/description.rs +++ b/singlestage/src/components/card/description.rs @@ -115,7 +115,6 @@ pub fn CardDescription( 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() @@ -151,7 +150,14 @@ pub fn CardDescription( }; view! { - <p {..global_attrs_1} {..global_attrs_2}> + <p + class=move || { + format!("singlestage-card-description {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > {children()} </p> } diff --git a/singlestage/src/components/card/footer.rs b/singlestage/src/components/card/footer.rs index 6d272991..146c0c45 100644 --- a/singlestage/src/components/card/footer.rs +++ b/singlestage/src/components/card/footer.rs @@ -115,7 +115,6 @@ pub fn CardFooter( 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() @@ -151,7 +150,12 @@ pub fn CardFooter( }; view! { - <footer {..global_attrs_1} {..global_attrs_2}> + <footer + class=move || { format!("singlestage-card-footer {}", class.get().unwrap_or_default()) } + + {..global_attrs_1} + {..global_attrs_2} + > {children()} </footer> } diff --git a/singlestage/src/components/card/header.rs b/singlestage/src/components/card/header.rs index 94aa2eaf..02bb6da9 100644 --- a/singlestage/src/components/card/header.rs +++ b/singlestage/src/components/card/header.rs @@ -115,7 +115,6 @@ pub fn CardHeader( 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() @@ -151,8 +150,12 @@ pub fn CardHeader( }; view! { - <header {..global_attrs_1} {..global_attrs_2}> + <hgroup + class=move || { format!("singlestage-card-header {}", class.get().unwrap_or_default()) } + {..global_attrs_1} + {..global_attrs_2} + > {children()} - </header> + </hgroup> } } diff --git a/singlestage/src/components/card/mod.rs b/singlestage/src/components/card/mod.rs index 75653f7e..6286c956 100644 --- a/singlestage/src/components/card/mod.rs +++ b/singlestage/src/components/card/mod.rs @@ -1,3 +1,4 @@ +mod action; mod card; mod content; mod description; @@ -5,6 +6,7 @@ mod footer; mod header; mod title; +pub use action::*; pub use card::*; pub use content::*; pub use description::*; diff --git a/singlestage/src/components/card/style/base/luma/card.css b/singlestage/src/components/card/style/base/luma/card.css new file mode 100644 index 00000000..cf545f7a --- /dev/null +++ b/singlestage/src/components/card/style/base/luma/card.css @@ -0,0 +1,86 @@ +@layer components1 { + .singlestage-card { + @apply bg-card + gap-6 + overflow-hidden + py-6 + ring-1 + ring-foreground/5 + rounded-4xl + shadow-md + text-card-foreground + text-sm; + + &:has(> img:first-child) { + @apply pt-0; + } + + > img:first-child { + @apply rounded-t-4xl; + } + + > img:last-child { + @apply rounded-b-4xl; + } + } + + .singlestage-card-header { + @apply gap-1.5 + px-6 + rounded-t-4xl; + + &.border-b { + @apply pb-6; + } + } + + .singlestage-card-title { + @apply font-medium + text-base; + } + + .singlestage-card-description { + @apply text-muted-foreground + text-sm; + } + + .singlestage-card-content { + @apply px-6; + } + + .singlestage-card-footer { + @apply px-6 + rounded-b-4xl; + + &.border-t] { + @apply pt-6; + } + } + + .singlestage-card-size-sm { + .singlestage-card { + @apply gap-4 + py-4; + } + + .singlestage-card-header { + @apply px-4; + + &.border-b { + @apply pb-4; + } + } + } + + .singlestage-card-content { + @apply px-4; + } + + .singlestage-card-footer { + @apply px-4; + + &.border-t { + @apply pt-4; + } + } +} diff --git a/singlestage/src/components/card/style/base/luma/card_dark.css b/singlestage/src/components/card/style/base/luma/card_dark.css new file mode 100644 index 00000000..4540fe06 --- /dev/null +++ b/singlestage/src/components/card/style/base/luma/card_dark.css @@ -0,0 +1,5 @@ +@layer components1 { + .singlestage-card { + @apply ring-foreground/10; + } +} diff --git a/singlestage/src/components/card/style/base/lyra/card.css b/singlestage/src/components/card/style/base/lyra/card.css new file mode 100644 index 00000000..a44a6f9d --- /dev/null +++ b/singlestage/src/components/card/style/base/lyra/card.css @@ -0,0 +1,87 @@ +@layer components1 { + .singlestage-card { + @apply bg-card + gap-4 + overflow-hidden + py-4 + ring-1 + ring-foreground/10 + rounded-none + text-card-foreground + text-xs/relaxed; + + &:has(> img:first-child) { + @apply pt-0; + } + + &:has(.singlestage-card-footer) { + @apply pb-0; + } + + > img:first-child, + > img:last-child { + @apply rounded-none; + } + } + + .singlestage-card-header { + @apply gap-1 + px-4 + rounded-none; + + &.border-b { + @apply pb-4; + } + } + + .singlestage-card-title { + @apply font-medium + text-sm; + } + + .singlestage-card-description { + @apply text-muted-foreground + text-xs/relaxed; + } + + .singlestage-card-content { + @apply px-4; + } + + .singlestage-card-footer { + @apply border-t + p-4 + rounded-none; + } + + .singlestage-card-size-sm { + .singlestage-card { + @apply gap-2 + py-3; + + &:has(.singlestage-card-footer) { + @apply pb-0; + } + } + + .singlestage-card-header { + @apply px-3; + + &.border-b { + @apply pb-3; + } + } + + .singlestage-card-title { + @apply text-sm; + } + + .singlestage-card-content { + @apply px-3; + } + + .singlestage-card-footer { + @apply p-3; + } + } +} diff --git a/singlestage/src/components/card/style/base/maia/card.css b/singlestage/src/components/card/style/base/maia/card.css new file mode 100644 index 00000000..aec2006b --- /dev/null +++ b/singlestage/src/components/card/style/base/maia/card.css @@ -0,0 +1,85 @@ +@layer components1 { + .singlestage-card { + @apply bg-card + gap-6 + overflow-hidden + py-6 + ring-1 + ring-foreground/10 + rounded-2xl + text-card-foreground + text-sm; + + &:has(> img:first-child) { + @apply pt-0; + } + + > img:first-child { + @apply rounded-t-xl; + } + + > img:last-child { + @apply rounded-b-xl; + } + } + + .singlestage-card-header { + @apply gap-2 + px-6 + rounded-t-xl; + + &.border-t { + @apply pb-6; + } + } + + .singlestage-card-title { + @apply font-medium + text-base; + } + + .singlestage-card-description { + @apply text-muted-foreground + text-sm; + } + + .singlestage-card-content { + @apply px-6; + } + + .singlestage-card-footer { + @apply px-6 + rounded-b-xl; + + &.border-t { + @apply pt-6; + } + } + + .singlestage-card-size-sm { + .singlestage-card { + @apply gap-4 + py-4; + } + + .singlestage-card-header { + @apply px-4; + + &.border-b { + @apply pb-4; + } + } + + .singlestage-card-content { + @apply px-4; + } + + .singlestage-card-footer { + @apply px-4; + + &.border-t { + @apply pt-4; + } + } + } +} diff --git a/singlestage/src/components/card/style/base/mira/card.css b/singlestage/src/components/card/style/base/mira/card.css new file mode 100644 index 00000000..40c74ce8 --- /dev/null +++ b/singlestage/src/components/card/style/base/mira/card.css @@ -0,0 +1,85 @@ +@layer components1 { + .singlestage-card { + @apply bg-card + gap-4 + overflow-hidden + py-4 + ring-1 + ring-foreground/10 + rounded-lg + text-card-foreground + text-xs/relaxed; + + &:has(> img:first-child) { + @apply pt-0; + } + + > img:first-child { + @apply rounded-t-lg; + } + + > img:last-child { + @apply rounded-b-lg; + } + } + + .singlestage-card-header { + @apply gap-1 + px-4 + rounded-t-lg; + + &.border-b { + @apply pb-4; + } + } + + .singlestage-card-title { + @apply font-medium + text-sm; + } + + .singlestage-card-description { + @apply text-muted-foreground + text-xs/relaxed; + } + + .singlestage-card-content { + @apply px-4; + } + + .singlestage-card-footer { + @apply px-4 + rounded-b-lg; + + &.border-t { + @apply pt-4; + } + } + + .singlestage-card-size-sm { + .singlestage-card { + @apply gap-3 + py-3; + } + + .singlestage-card-header { + @apply px-3; + + &.border-b { + @apply pb-3; + } + } + + .singlestage-card-content { + @apply px-3; + } + + .singlestage-card-footer { + @apply px-3; + + &.border-t { + @apply pt-3; + } + } + } +} diff --git a/singlestage/src/components/card/style/base/nova/card.css b/singlestage/src/components/card/style/base/nova/card.css new file mode 100644 index 00000000..9ab3bfb6 --- /dev/null +++ b/singlestage/src/components/card/style/base/nova/card.css @@ -0,0 +1,92 @@ +@layer components1 { + .singlestage-card { + @apply bg-card + gap-4 + overflow-hidden + py-4 + ring-1 + ring-foreground/10 + rounded-xl + text-card-foreground + text-sm; + + &:has(.singlestage-card-footer) { + @apply pb-0; + } + + &:has(> img:first-child) { + @apply pt-0; + } + + > img:first-child { + @apply rounded-t-xl; + } + + > img:last-child { + @apply rounded-b-xl; + } + } + + .singlestage-card-header { + @apply gap-1 + px-4 + rounded-t-xl; + + &.border-b { + @apply pb-4; + } + } + + .singlestage-card-title { + @apply font-medium + leading-snug + text-base; + } + + .singlestage-card-description { + @apply text-muted-foreground + text-sm; + } + + .singlestage-card-content { + @apply px-4; + } + + .singlestage-card-footer { + @apply bg-muted/50 + border-t + p-4 + rounded-b-xl; + } + + .singlestage-card-size-sm { + .singlestage-card { + @apply gap-3 + py-3; + + &:has(.singlestage-card-footer) { + @apply pb-0; + } + } + + .singlestage-card-header { + @apply px-3; + + &.border-b { + @apply pb-3; + } + } + + .singlestage-card-title { + @apply text-sm; + } + + .singlestage-card-content { + @apply px-3; + } + + .singlestage-card-footer { + @apply p-3; + } + } +} diff --git a/singlestage/src/components/card/style/base/sera/card.css b/singlestage/src/components/card/style/base/sera/card.css new file mode 100644 index 00000000..7e4f64a7 --- /dev/null +++ b/singlestage/src/components/card/style/base/sera/card.css @@ -0,0 +1,84 @@ +@layer components1 { + .singlestage-card { + @apply bg-card + gap-8 + overflow-hidden + py-8 + ring-1 + ring-foreground/5 + shadow-sm + text-card-foreground + text-sm; + + &:has(> img:first-child) { + @apply pt-0; + } + + > img:first-child, + > img:last-child { + @apply rounded-none; + } + } + + .singlestage-card-header { + @apply gap-1.5 + px-8 + rounded-none; + + &.border-b { + @apply pb-8; + } + } + + .singlestage-card-title { + @apply font-semibold + text-lg + tracking-wider + uppercase; + } + + .singlestage-card-description { + @apply leading-relaxed + text-muted-foreground + text-sm; + } + + .singlestage-card-content { + @apply px-8; + } + + .singlestage-card-footer { + @apply px-8; + + &.border-t { + @apply pt-8; + } + } + + .singlestage-card-size-sm { + .singlestage-card { + @apply gap-5 + py-5; + } + + .singlestage-card-header { + @apply px-5; + + &.border-b { + @apply pb-5; + } + } + + .singlestage-card-content { + @apply px-5; + } + + .singlestage-card-footer { + @apply px-5; + + &.border-t { + @apply pt-5; + } + } + } +} diff --git a/singlestage/src/components/card/style/base/vega/card.css b/singlestage/src/components/card/style/base/vega/card.css new file mode 100644 index 00000000..7b0380c8 --- /dev/null +++ b/singlestage/src/components/card/style/base/vega/card.css @@ -0,0 +1,91 @@ +@layer components1 { + .singlestage-card { + @apply bg-card + gap-6 + overflow-hidden + py-6 + ring-1 + ring-foreground/10 + rounded-xl + shadow-xs + text-card-foreground + text-sm; + + &:has(> img:first-child) { + @apply pt-0; + } + + > img:first-child { + @apply rounded-t-xl; + } + + > img:last-child { + @apply rounded-b-xl; + } + } + + .singlestage-card-header { + @apply gap-1 + px-6 + rounded-t-xl; + + &.border-b { + @apply pb-6; + } + } + + .singlestage-card-title { + @apply font-medium + leading-normal + text-base; + } + + .singlestage-card-description { + @apply text-muted-foreground + text-sm; + } + + .singlestage-card-content { + @apply px-6; + } + + .singlestage-card-footer { + @apply px-6 + rounded-b-xl; + + &.border-t { + @apply pt-6; + } + } + + .singlestage-card-size-sm { + .singlestage-card { + @apply gap-4 + py-4; + } + + .singlestage-card-header { + @apply px-4; + + &.border-b { + @apply pb-4; + } + } + + .singlestage-card-title { + @apply text-sm; + } + + .singlestage-card-content { + @apply px-4; + } + + .singlestage-card-footer { + @apply px-4; + + &.border-t { + @apply pt-4; + } + } + } +} diff --git a/singlestage/src/components/card/style/card.css b/singlestage/src/components/card/style/card.css new file mode 100644 index 00000000..41e1efd3 --- /dev/null +++ b/singlestage/src/components/card/style/card.css @@ -0,0 +1,108 @@ +@layer components { + .singlestage-card { + @apply bg-card + flex + flex-col + gap-8 + overflow-hidden + py-8 + ring-1 + ring-foreground/5 + shadow-sm + text-card-foreground + text-sm; + + > img:first-child, + > img:last-child { + @apply rounded-none; + } + + &:has(> img:first-child) { + @apply pt-0; + } + } + + .singlestage-card-header { + @apply auto-rows-min + gap-1.5 + grid + items-start + px-8 + rounded-none; + + &.border-b { + @apply pb-8; + } + + &:has(.singlestage-card-action) { + @apply grid-cols-[1fr_auto]; + } + + &:has(.singlestage-card-description) { + @apply grid-rows-[auto_auto]; + } + } + + .singlestage-card-title { + @apply /* font-heading */ + font-semibold + text-lg + tracking-wider; + } + + .singlestage-card-description { + @apply leading-relaxed + text-muted-foreground + text-sm; + } + + .singlestage-card-action { + @apply col-start-2 + justify-self-end + row-span-2 + row-start-1 + self-start; + } + + .singlestage-card-content { + @apply px-8; + } + + .singlestage-card-footer { + @apply flex + items-center; + + @apply flex + items-center + px-8; + + &.border-t { + @apply pt-8; + } + } + + .singlestage-card-size-sm { + @apply gap-5 + py-5; + + .singlestage-card-header { + @apply px-5; + + &.border-b { + @apply pb-5; + } + } + + .singlestage-card-content { + @apply px-5; + } + + .singlestage-card-footer { + @apply px-5; + + &.border-t { + @apply pt-5; + } + } + } +} diff --git a/singlestage/src/components/card/title.rs b/singlestage/src/components/card/title.rs index aab6d45f..3a6963f1 100644 --- a/singlestage/src/components/card/title.rs +++ b/singlestage/src/components/card/title.rs @@ -151,7 +151,12 @@ pub fn CardTitle( }; view! { - <h2 {..global_attrs_1} {..global_attrs_2}> + <h2 + class=move || format!("singlestage-card-title {}", class.get().unwrap_or_default()) + + {..global_attrs_1} + {..global_attrs_2} + > {children()} </h2> } diff --git a/singlestage/src/components/carousel/carousel.rs b/singlestage/src/components/carousel/carousel.rs index 2835328f..62f56b99 100644 --- a/singlestage/src/components/carousel/carousel.rs +++ b/singlestage/src/components/carousel/carousel.rs @@ -135,11 +135,11 @@ 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; - ul_ref.set_scroll_left(scroll_step * (cur - 1)); + ul_ref.set_scroll_left((scroll_step * (cur - 1)).into()); } }); diff --git a/singlestage/src/components/carousel/content.rs b/singlestage/src/components/carousel/content.rs index c0c16e38..acc7ac55 100644 --- a/singlestage/src/components/carousel/content.rs +++ b/singlestage/src/components/carousel/content.rs @@ -115,12 +115,20 @@ pub fn CarouselContent( let on_scroll_end = move |_ev| { if let Some(ul) = carousel.ul_ref.get_untracked() { - let scroll_left = ul.scroll_left(); + // Apparently this can either output i32 or f64 + let scroll_left: f64 = ul.scroll_left().into(); let width = ul.scroll_width(); - let scroll_step = width / carousel.num_items.get_untracked(); - let target = (scroll_left / scroll_step) + 1; + let scroll_step: f64 = (width / carousel.num_items.get_untracked()).into(); - carousel.current_item.update_untracked(|cur| *cur = target); + let target: f64 = (scroll_left / scroll_step) + 1.0; + + carousel.current_item.update_untracked(|cur| { + *cur = target + .round_ties_even() + .to_string() + .parse::<i32>() + .unwrap_or(i32::MAX) + }); } }; diff --git a/singlestage/src/components/carousel/mod.rs b/singlestage/src/components/carousel/mod.rs index 7c701672..2694460e 100644 --- a/singlestage/src/components/carousel/mod.rs +++ b/singlestage/src/components/carousel/mod.rs @@ -13,7 +13,7 @@ pub use previous::*; use leptos::prelude::*; #[derive(Clone)] -pub struct CarouselContext { +pub(crate) struct CarouselContext { pub current_item: RwSignal<i32>, pub num_items: RwSignal<i32>, pub ul_ref: NodeRef<leptos::html::Ul>, diff --git a/singlestage/src/components/carousel/next.rs b/singlestage/src/components/carousel/next.rs index f90727dc..e0c924ba 100644 --- a/singlestage/src/components/carousel/next.rs +++ b/singlestage/src/components/carousel/next.rs @@ -1,4 +1,5 @@ use super::CarouselContext; +use crate::Reactive; use leptos::prelude::*; /// A button that triggers the change to the next available carousel item. @@ -22,7 +23,7 @@ pub fn CarouselNext( commandfor: MaybeProp<String>, /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, diff --git a/singlestage/src/components/carousel/previous.rs b/singlestage/src/components/carousel/previous.rs index 9739254a..ce2620e4 100644 --- a/singlestage/src/components/carousel/previous.rs +++ b/singlestage/src/components/carousel/previous.rs @@ -1,5 +1,5 @@ use super::CarouselContext; - +use crate::Reactive; use leptos::prelude::*; /// A button that triggers the change to the previous available carousel item. @@ -23,7 +23,7 @@ pub fn CarouselPrevious( commandfor: MaybeProp<String>, /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, diff --git a/singlestage/src/components/carousel/carousel.css b/singlestage/src/components/carousel/style/carousel.css similarity index 97% rename from singlestage/src/components/carousel/carousel.css rename to singlestage/src/components/carousel/style/carousel.css index 0d032558..8b1bae7a 100644 --- a/singlestage/src/components/carousel/carousel.css +++ b/singlestage/src/components/carousel/style/carousel.css @@ -41,7 +41,7 @@ disabled:opacity-50 disabled:pointer-events-none focus-visible:border-ring - focus-visible:ring-[3px] + focus-visible:ring-3 focus-visible:ring-ring/50 font-medium gap-2 diff --git a/singlestage/src/components/checkbox/checkbox.css b/singlestage/src/components/checkbox/checkbox.css deleted file mode 100644 index 8349df7c..00000000 --- a/singlestage/src/components/checkbox/checkbox.css +++ /dev/null @@ -1,50 +0,0 @@ -@layer components { - .singlestage-checkbox.singlestage-input[type="checkbox"]:not( - [role="switch"] - ) { - @apply appearance-none - aria-invalid:border-destructive - aria-invalid:ring-destructive/20 - border - border-input - checked:bg-primary - checked:border-primary - disabled:cursor-not-allowed - disabled:opacity-50 - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - outline-none - rounded-[4px] - shadow-xs - shrink-0 - size-4 - transition-shadow; - - &:checked:after { - @apply bg-primary-foreground - block - content-[''] - size-3.5; - - @apply mask-[image:var(--check-icon)] - mask-center - mask-no-repeat - mask-size-[0.875rem]; - } - } - - .singlestage-checkbox-group { - @apply flex - flex-col - font-normal - gap-2 - leading-tight; - - > legend { - @apply font-medium - mb-3 - text-base; - } - } -} diff --git a/singlestage/src/components/checkbox/checkbox.rs b/singlestage/src/components/checkbox/checkbox.rs index 93692f51..8dd78cfd 100644 --- a/singlestage/src/components/checkbox/checkbox.rs +++ b/singlestage/src/components/checkbox/checkbox.rs @@ -1,4 +1,4 @@ -use crate::{CheckboxGroupContext, FieldContext, FieldLabel, Reactive}; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; /// A control that allows the user to toggle @@ -7,9 +7,13 @@ use leptos::prelude::*; pub fn Checkbox( #[prop(optional)] children: Option<Children>, + /// Reactive signal coupled to the checkbox's invalid state. + #[prop(optional, into)] + invalid: Reactive<bool>, + // CHECKBOX ATTRIBUTES // - /// Reactive signal coupled to the checkbox's checked value. + /// Reactive signal coupled to the checkbox's checked state. #[prop(optional, into)] checked: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. @@ -26,7 +30,7 @@ pub fn Checkbox( required: MaybeProp<bool>, /// Whether the form control is disabled #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// The value of the control. When specified in the HTML, corresponds to the initial value #[prop(optional, into)] value: MaybeProp<String>, @@ -141,67 +145,6 @@ pub fn Checkbox( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let checkbox_ref = { - if let Some(node_ref) = node_ref.get_untracked() { - node_ref - } else { - NodeRef::<leptos::html::Input>::new() - } - }; - - let on_change = move |ev| { - let checkbox_checked = event_target_checked(&ev); - - checked.set(checkbox_checked); - - if let Some(checkbox_value) = value.get_untracked() - && let Some(checkbox_group) = use_context::<CheckboxGroupContext>() - { - match checkbox_checked { - true => checkbox_group.value.update(|group_value| { - group_value.push(checkbox_value); - }), - false => checkbox_group.value.update(|group_value| { - if let Some(index) = group_value.iter().position(|el| *el == checkbox_value) { - group_value.swap_remove(index); - } - }), - } - } - }; - - if let Some(value) = value.get_untracked() - && let Some(checkbox_group) = use_context::<CheckboxGroupContext>() - && checkbox_group.value.get_untracked().contains(&value) - && let Some(checkbox) = checkbox_ref.get_untracked() - { - checkbox.set_checked(true) - } - - Effect::new(move || { - if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() - && let Some(value) = value.get_untracked() - { - if checkbox_group.value.get().contains(&value) { - checked.set(true); - } else { - checked.set(false); - } - } - }); - - Effect::new(move || { - if let Some(checkbox) = checkbox_ref.get_untracked() { - checkbox.set_checked(checked.get()); - } - }); - - Effect::new(move || { - if let Some(checkbox) = checkbox_ref.get_untracked() { - checkbox.set_disabled(disabled.get().unwrap_or_default()); - } - }); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -238,116 +181,49 @@ pub fn Checkbox( /> }; - let input_id = uuid::Uuid::new_v4(); - let label_id = uuid::Uuid::new_v4(); - let has_children = children.is_some(); - - let custom_attrs = view! { - <{..} - aria_describedby=move || { - if let Some(field) = use_context::<FieldContext>() { - let description_id = field.description_id.get(); - if description_id.is_empty() { None } else { Some(description_id) } - } else { - None - } - } - aria-invalid=move || { - if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() { - checkbox_group.invalid.get().to_string() - } else { - false.to_string() - } - } - aria_labelledby=move || { - if let Some(field) = use_context::<FieldContext>() { - Some(field.label_id.get()) - } else if has_children { - Some(label_id.to_string()) - } else { - None - } - } - checked=checked.get_untracked() - class=move || { - format!( - "singlestage-checkbox singlestage-input {}", - class.get().unwrap_or_default(), - ) - } - disabled=disabled.get_untracked() - form=move || form.get() - name=move || name.get() - node_ref=checkbox_ref - on:change=on_change - readonly=move || readonly.get() - required=move || required.get() - type="checkbox" - value=move || value.get() - /> - }; - if let Some(children) = children { view! { - {if use_context::<FieldContext>().is_some() { - view! { - <input - id=move || id.get().unwrap_or(input_id.to_string()) + <CheckboxPrimitive + primitive_type=CheckboxPrimitiveType::Checkbox + + checked + class + disabled + form + id + invalid + name + node_ref + readonly + required + value - {..global_attrs_1} - {..global_attrs_2} - {..custom_attrs} - /> - <FieldLabel - class=class.get_untracked() - label_for=id.get_untracked().unwrap_or(input_id.to_string()) - > - {children()} - </FieldLabel> - } - .into_any() - } else { - view! { - <label - class=move || { - format!("singlestage-label {}", class.get().unwrap_or_default()) - } - for=move || id.get().unwrap_or(input_id.to_string()) - id=label_id.to_string() - > - <input - class="singlestage-checkbox singlestage-input" - id=move || id.get().unwrap_or(input_id.to_string()) - - {..global_attrs_1} - {..global_attrs_2} - {..custom_attrs} - /> - {children()} - </label> - } - .into_any() - }} + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </CheckboxPrimitive> } .into_any() } else { view! { - <input - id={if let Some(field) = use_context::<FieldContext>() { - if let Some(id) = id.get_untracked() { - field.input_id.set(id.clone()); - Some(id) - } else { - field.input_id.set(input_id.to_string()); - Some(input_id.to_string()) - } - } else { - id.get_untracked() - }} + <CheckboxPrimitive + primitive_type=CheckboxPrimitiveType::Checkbox + + checked + class + disabled + form + id + invalid + name + node_ref + readonly + required + value {..global_attrs_1} {..global_attrs_2} - {..custom_attrs} /> } .into_any() diff --git a/singlestage/src/components/checkbox/group.rs b/singlestage/src/components/checkbox/group.rs index bd10abf5..66e6ebf6 100644 --- a/singlestage/src/components/checkbox/group.rs +++ b/singlestage/src/components/checkbox/group.rs @@ -17,7 +17,7 @@ pub fn CheckboxGroup( // /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, @@ -169,9 +169,23 @@ pub fn CheckboxGroup( /> }; - let fieldset_attrs = view! { <{..} disabled=move || disabled.get() form=move || form.get() name=move || name.get() /> }; + let fieldset_attrs = view! { + <{..} + aria_invalid=move || { if invalid.get() { Some("true") } else { None } } + disabled=move || disabled.get() + form=move || form.get() + name=move || name.get() + /> + }; - let context = CheckboxGroupContext { invalid, value }; + let context = CheckboxGroupContext { + name: name + .get_untracked() + .unwrap_or(uuid::Uuid::new_v4().to_string()), + disabled, + invalid, + value, + }; view! { <fieldset diff --git a/singlestage/src/components/checkbox/mod.rs b/singlestage/src/components/checkbox/mod.rs index d652d33e..54a0f981 100644 --- a/singlestage/src/components/checkbox/mod.rs +++ b/singlestage/src/components/checkbox/mod.rs @@ -7,7 +7,9 @@ pub use group::*; use crate::Reactive; #[derive(Clone)] -pub struct CheckboxGroupContext { +pub(crate) struct CheckboxGroupContext { + pub name: String, + pub disabled: Reactive<bool>, pub invalid: Reactive<bool>, pub value: Reactive<Vec<String>>, } diff --git a/singlestage/src/components/checkbox/style/base/luma/checkbox.css b/singlestage/src/components/checkbox/style/base/luma/checkbox.css new file mode 100644 index 00000000..f627698e --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/luma/checkbox.css @@ -0,0 +1,47 @@ +@layer components1 { + .singlestage-checkbox { + @apply bg-input/90 + border + border-transparent + flex + items-center + justify-center + rounded-[5px] + size-4 + transition-shadow; + + &:checked { + @apply bg-primary + border-primary; + + &::after { + @apply bg-primary-foreground + size-3.5; + } + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + + &:checked { + @apply border-primary; + } + } + } + + .singlestage-field:has(*:disabled), + .singlestage-field:has(*[aria-disabled="true"]) { + .singlestage-checkbox { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/luma/checkbox_dark.css b/singlestage/src/components/checkbox/style/base/luma/checkbox_dark.css new file mode 100644 index 00000000..40723a94 --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/luma/checkbox_dark.css @@ -0,0 +1,13 @@ +@layer components1 { + .singlestage-checkbox { + &:checked { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/lyra/checkbox.css b/singlestage/src/components/checkbox/style/base/lyra/checkbox.css new file mode 100644 index 00000000..e684c546 --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/lyra/checkbox.css @@ -0,0 +1,46 @@ +@layer components1 { + .singlestage-checkbox { + @apply border + border-input + flex + items-center + justify-center + rounded-none + size-4 + transition-colors; + + &:checked { + @apply bg-primary + border-primary; + + &::after { + @apply bg-primary-foreground + size-3.5; + } + } + + &:focus-visible { + @apply border-ring + ring-1 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-1 + ring-destructive/20; + + &:checked { + @apply border-primary; + } + } + } + + .singlestage-field:has(*:disabled), + .singlestage-field:has(*[aria-disabled="true"]) { + .singlestage-checkbox { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/lyra/checkbox_dark.css b/singlestage/src/components/checkbox/style/base/lyra/checkbox_dark.css new file mode 100644 index 00000000..7e526f1d --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/lyra/checkbox_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-checkbox { + @apply bg-input/30; + + &:checked { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/maia/checkbox.css b/singlestage/src/components/checkbox/style/base/maia/checkbox.css new file mode 100644 index 00000000..1ccf05c9 --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/maia/checkbox.css @@ -0,0 +1,46 @@ +@layer components1 { + .singlestage-checkbox { + @apply border + border-input + flex + items-center + justify-center + rounded-[6px] + size-4 + transition-shadow; + + &:checked { + @apply bg-primary + border-primary; + + &::after { + @apply bg-primary-foreground + size-3.5; + } + } + + &:focus-visible { + @apply border-ring + ring-[3px] + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-[3px] + ring-destructive/20; + + &:checked { + @apply border-primary; + } + } + } + + .singlestage-field:has(*:disabled), + .singlestage-field:has(*[aria-disabled="true"]) { + .singlestage-checkbox { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/maia/checkbox_dark.css b/singlestage/src/components/checkbox/style/base/maia/checkbox_dark.css new file mode 100644 index 00000000..7e526f1d --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/maia/checkbox_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-checkbox { + @apply bg-input/30; + + &:checked { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/mira/checkbox.css b/singlestage/src/components/checkbox/style/base/mira/checkbox.css new file mode 100644 index 00000000..adf412e6 --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/mira/checkbox.css @@ -0,0 +1,46 @@ +@layer components1 { + .singlestage-checkbox { + @apply border + border-input + flex + items-center + justify-center + rounded-[4px] + size-4 + transition-shadow; + + &:checked { + @apply bg-primary + border-primary; + + &::after { + @apply bg-primary-foreground + size-3.5; + } + } + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + + &:checked { + @apply border-primary; + } + } + } + + .singlestage-field:has(*:disabled), + .singlestage-field:has(*[aria-disabled="true"]) { + .singlestage-checkbox { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/mira/checkbox_dark.css b/singlestage/src/components/checkbox/style/base/mira/checkbox_dark.css new file mode 100644 index 00000000..7e526f1d --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/mira/checkbox_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-checkbox { + @apply bg-input/30; + + &:checked { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/nova/checkbox.css b/singlestage/src/components/checkbox/style/base/nova/checkbox.css new file mode 100644 index 00000000..fbeaa443 --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/nova/checkbox.css @@ -0,0 +1,46 @@ +@layer components1 { + .singlestage-checkbox { + @apply border + border-input + flex + items-center + justify-center + rounded-[4px] + size-4 + transition-colors; + + &:checked { + @apply bg-primary + border-primary; + + &::after { + @apply bg-primary-foreground + size-3.5; + } + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + + &:checked { + @apply border-primary; + } + } + } + + .singlestage-field:has(*:disabled), + .singlestage-field:has(*[aria-disabled="true"]) { + .singlestage-checkbox { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/nova/checkbox_dark.css b/singlestage/src/components/checkbox/style/base/nova/checkbox_dark.css new file mode 100644 index 00000000..7e526f1d --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/nova/checkbox_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-checkbox { + @apply bg-input/30; + + &:checked { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/sera/checkbox.css b/singlestage/src/components/checkbox/style/base/sera/checkbox.css new file mode 100644 index 00000000..ef8cbe12 --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/sera/checkbox.css @@ -0,0 +1,47 @@ +@layer components1 { + .singlestage-checkbox { + @apply bg-transparent + border + border-input + flex + items-center + justify-center + rounded-none + size-4.5 + transition-shadow; + + &:checked { + @apply bg-primary + border-primary; + + &::after { + @apply bg-primary-foreground + size-3.5; + } + } + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + + &:checked { + @apply border-primary; + } + } + } + + .singlestage-field:has(*:disabled), + .singlestage-field:has(*[aria-disabled="true"]) { + .singlestage-checkbox { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/sera/checkbox_dark.css b/singlestage/src/components/checkbox/style/base/sera/checkbox_dark.css new file mode 100644 index 00000000..40723a94 --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/sera/checkbox_dark.css @@ -0,0 +1,13 @@ +@layer components1 { + .singlestage-checkbox { + &:checked { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/vega/checkbox.css b/singlestage/src/components/checkbox/style/base/vega/checkbox.css new file mode 100644 index 00000000..8885c028 --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/vega/checkbox.css @@ -0,0 +1,47 @@ +@layer components1 { + .singlestage-checkbox { + @apply border + border-input + flex + items-center + justify-center + rounded-[4px] + shadow-xs + size-4 + transition-shadow; + + &:checked { + @apply bg-primary + border-primary; + + &::after { + @apply bg-primary-foreground + size-3.5; + } + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + + &:checked { + @apply border-primary; + } + } + } + + .singlestage-field:has(*:disabled), + .singlestage-field:has(*[aria-disabled="true"]) { + .singlestage-checkbox { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/checkbox/style/base/vega/checkbox_dark.css b/singlestage/src/components/checkbox/style/base/vega/checkbox_dark.css new file mode 100644 index 00000000..7e526f1d --- /dev/null +++ b/singlestage/src/components/checkbox/style/base/vega/checkbox_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-checkbox { + @apply bg-input/30; + + &:checked { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/checkbox/style/checkbox.css b/singlestage/src/components/checkbox/style/checkbox.css new file mode 100644 index 00000000..cd266f33 --- /dev/null +++ b/singlestage/src/components/checkbox/style/checkbox.css @@ -0,0 +1,22 @@ +@layer components { + .singlestage-checkbox { + @apply appearance-none + outline-none + shrink-0; + + &:checked:after { + @apply block + content-[''] + mask-(--check-icon) + mask-center + mask-no-repeat + mask-size-[0.875rem]; + } + + &:disabled, + &[aria-disabled="true"] { + @apply cursor-not-allowed + opacity-50; + } + } +} diff --git a/singlestage/src/components/checkbox/style/checkbox_dark.css b/singlestage/src/components/checkbox/style/checkbox_dark.css new file mode 100644 index 00000000..fb1050f8 --- /dev/null +++ b/singlestage/src/components/checkbox/style/checkbox_dark.css @@ -0,0 +1,13 @@ +@layer components { + .singlestage-checkbox { + &:checked { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/sidebar/collapsible/content.rs b/singlestage/src/components/collapsible/content.rs similarity index 94% rename from singlestage/src/components/sidebar/collapsible/content.rs rename to singlestage/src/components/collapsible/content.rs index 585d5a4b..22028a75 100644 --- a/singlestage/src/components/sidebar/collapsible/content.rs +++ b/singlestage/src/components/collapsible/content.rs @@ -47,9 +47,9 @@ pub fn CollapsibleContent( /// Controls hidden status of the element. #[prop(optional, into)] hidden: MaybeProp<String>, - // /// Set the id of this element. - // #[prop(optional, into)] - // id: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, /// Toggle if the browser reacts to input events from this element. #[prop(optional, into)] inert: MaybeProp<bool>, @@ -125,7 +125,7 @@ pub fn CollapsibleContent( enterkeyhint=move || enterkeyhint.get() exportparts=move || exportparts.get() hidden=move || hidden.get() - // id=move || id.get() + id=move || id.get() inert=move || inert.get() inputmode=move || inputmode.get() is=move || is.get() @@ -154,13 +154,16 @@ pub fn CollapsibleContent( }; view! { - <ul - id=format!("{}-content", collapsible.id) + <section + style:display=move || match collapsible.open.get() { + true => None, + false => Some("none"), + } {..global_attrs_1} {..global_attrs_2} > {children()} - </ul> + </section> } } diff --git a/singlestage/src/components/sidebar/collapsible/collapsible.rs b/singlestage/src/components/collapsible/mod.rs similarity index 84% rename from singlestage/src/components/sidebar/collapsible/collapsible.rs rename to singlestage/src/components/collapsible/mod.rs index 845c6208..d49dc4c4 100644 --- a/singlestage/src/components/sidebar/collapsible/collapsible.rs +++ b/singlestage/src/components/collapsible/mod.rs @@ -1,28 +1,25 @@ -use crate::CollapsibleContext; +mod content; +mod trigger; + +pub use content::*; +pub use trigger::*; + +use crate::Reactive; use leptos::{context::Provider, prelude::*}; -/// A button that opens the collapsible menu. -#[slot] -pub struct CollapsibleTrigger { - children: ChildrenFn, +#[derive(Clone)] +pub(crate) struct CollapsibleContext { + pub open: Reactive<bool>, } -/// Creates a collapsible menu. +/// An interactive component which expands/collapses a panel. #[component] pub fn Collapsible( children: Children, - /// A button that opens the collapsible menu. - collapsible_trigger: CollapsibleTrigger, - // DETAILS ATTRIBUTES - // /// Set whether the Item is open (`true`) or closed (`false`). Defaults to `false` #[prop(optional, into)] - open: MaybeProp<bool>, - /// Sets the name of the `details` element. - /// Set the same name for each `AccordionItem` to only allow one open at a time. - #[prop(optional, into)] - name: MaybeProp<String>, + open: Reactive<bool>, // GLOBAL ATTRIBUTES // @@ -129,9 +126,6 @@ pub fn Collapsible( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let uuid = uuid::Uuid::new_v4().to_string(); - let context = CollapsibleContext { id: uuid.clone() }; - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -173,17 +167,8 @@ pub fn Collapsible( }; view! { - <details id=uuid.clone() name=move || name.get() open=move || open.get()> - <summary - aria-controls=format!("{}-content", uuid) - on:click=move |ev| ev.stop_propagation() - - {..global_attrs_1} - {..global_attrs_2} - > - {(collapsible_trigger.children)().into_any()} - </summary> - <Provider value=context>{children()}</Provider> - </details> + <div {..global_attrs_1} {..global_attrs_2}> + <Provider value=CollapsibleContext { open }>{children()}</Provider> + </div> } } diff --git a/singlestage/src/components/collapsible/trigger.rs b/singlestage/src/components/collapsible/trigger.rs new file mode 100644 index 00000000..88cb7edb --- /dev/null +++ b/singlestage/src/components/collapsible/trigger.rs @@ -0,0 +1,8 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// A context provider that wraps a Button component that triggers the collapsible menu. +#[component] +pub fn CollapsibleTrigger(children: Children) -> impl IntoView { + view! { <TriggerPrimitive>{children()}</TriggerPrimitive> } +} diff --git a/singlestage/src/components/context_menu/checkbox.rs b/singlestage/src/components/context_menu/checkbox.rs new file mode 100644 index 00000000..8cb2e265 --- /dev/null +++ b/singlestage/src/components/context_menu/checkbox.rs @@ -0,0 +1,193 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +/// A checkbox item for context menus. +#[component] +pub fn ContextMenuCheckboxItem( + children: Children, + + /// A reactive signal coupled to the checkbox's checked value. + #[prop(optional, into)] + checked: Reactive<bool>, + /// Controls whether the item appears disabled and is clickable. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Toggle whether clicking this item dismisses its parent menu + #[prop(optional, into, default = Reactive::new(true))] + dismiss: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Set the display variant of the item. + /// + /// Accepted values: "destructive" + #[prop(optional, into)] + variant: MaybeProp<String>, + + // LI ATTRIBRUTES + // + /// The current ordinal value of the item. + #[prop(optional, into)] + value: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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() + 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! { + <MenuItemPrimitive + primitive_type=MenuItemPrimitiveType::Checkbox + + checked + class + disabled + dismiss + id + inset + variant + value + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuItemPrimitive> + } +} diff --git a/singlestage/src/components/context_menu/content.rs b/singlestage/src/components/context_menu/content.rs index 0d5b85eb..4cca2514 100644 --- a/singlestage/src/components/context_menu/content.rs +++ b/singlestage/src/components/context_menu/content.rs @@ -1,4 +1,4 @@ -use crate::ContextMenuContext; +use crate::primitives::*; use leptos::prelude::*; /// The component that pops out when the context menu is open. @@ -6,6 +6,19 @@ use leptos::prelude::*; pub fn ContextMenuContent( children: Children, + /// Set how to align the popover + /// + /// Accepted values: "start" | "center" | "end" + /// Default is "start" + #[prop(optional, into)] + align: MaybeProp<String>, + /// Set which side the popover opens relative to the trigger point + /// + /// Accepted values: "top" | "right" | "bottom" | "left" + /// Default is "bottom" + #[prop(optional, into)] + side: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -47,6 +60,9 @@ pub fn ContextMenuContent( /// Controls hidden status of the element. #[prop(optional, into)] hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, /// Toggle if the browser reacts to input events from this element. #[prop(optional, into)] inert: MaybeProp<bool>, @@ -102,13 +118,6 @@ pub fn ContextMenuContent( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let menu = expect_context::<ContextMenuContext>(); - let menu_ref = NodeRef::<leptos::html::Menu>::new(); - let uuid = uuid::Uuid::new_v4().to_string(); - - menu.menu_id.set(uuid.clone()); - menu.menu_ref.set(Some(menu_ref)); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -138,7 +147,6 @@ pub fn ContextMenuContent( part=move || part.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() @@ -146,23 +154,19 @@ pub fn ContextMenuContent( }; view! { - <menu - class=move || { - format!("singlestage-context-menu-content {}", class.get().unwrap_or_default()) - } - id=uuid - node_ref=menu_ref - on:contextmenu=move |ev| { - ev.prevent_default(); - } - popover="auto" - role="menu" - style=move || { format!("left: {}px; top: {}px", menu.x.get(), menu.y.get()) } + <MenuContentPrimitive + primitive_type=MenuContentPrimitiveType::Main + + align + class + id + side + style {..global_attrs_1} {..global_attrs_2} > {children()} - </menu> + </MenuContentPrimitive> } } diff --git a/singlestage/src/components/context_menu/context_menu.css b/singlestage/src/components/context_menu/context_menu.css deleted file mode 100644 index bd227068..00000000 --- a/singlestage/src/components/context_menu/context_menu.css +++ /dev/null @@ -1,116 +0,0 @@ -@layer components { - .singlestage-context-menu { - @apply transition-all; - - [popover] { - @apply fixed - bg-transparent; - } - - .singlestage-context-menu-content { - @apply bg-popover - border - min-w-[8rem] - overflow-x-hidden - overflow-y-auto - p-1 - rounded-md - shadow-md - text-nowrap - text-popover-foreground - z-50; - - .singlestage-radio-group { - @appply block - gap-0; - } - } - - .singlestage-context-menu-item { - > button { - @apply [&_svg:not([class*='size-'])]:size-4 - [&_svg:not([class*='text-'])]:text-muted-foreground - [&_svg]:pointer-events-none - [&_svg]:shrink-0 - content-center - cursor-default - data-[disabled]:opacity-50 - data-[disabled]:pointer-events-none - data-[inset]:pl-8 - data-[variant=destructive]:*:[svg]:!text-destructive - data-[variant=destructive]:focus:bg-destructive/10 - data-[variant=destructive]:focus:text-destructive - data-[variant=destructive]:text-destructive - focus:bg-accent - focus:text-accent-foreground - flex - gap-2 - h-full - hover:bg-accent - hover:text-accent-foreground - items-center - outline-hidden - px-2 - py-1.5 - relative - rounded-sm - select-none - text-sm - w-full; - } - - > .singlestage-label { - @apply h-full - w-full; - } - } - - .singlestage-context-menu-label { - @apply data-[inset]:pl-8 - font-medium - px-2 - py-1.5 - text-sm; - } - - .singlestage-context-menu-separator { - @apply -mx-1 - bg-border - h-px - my-1; - } - - .singlestage-context-menu-shortcut { - @apply ml-auto - text-muted-foreground - text-xs - tracking-widest; - } - - .singlestage-context-menu-sub-trigger { - @apply cursor-default - flex - focus:bg-accent - focus:text-accent-foreground - items-center - outline-hidden - px-2 - py-1.5 - rounded-sm - select-none - text-sm; - } - - .singlestage-context-menu-sub-content { - @apply bg-popover - border - min-w-[8rem] - overflow-hidden - p-1 - rounded-md - shadow-lg - text-popover-foreground - z-50; - } - } -} diff --git a/singlestage/src/components/context_menu/group.rs b/singlestage/src/components/context_menu/group.rs index 4df04ab9..d42f5393 100644 --- a/singlestage/src/components/context_menu/group.rs +++ b/singlestage/src/components/context_menu/group.rs @@ -1,7 +1,7 @@ -use crate::ContextMenuGroupContext; -use leptos::{context::Provider, prelude::*}; +use crate::primitives::*; +use leptos::prelude::*; -/// Contains multiple items. +/// Contains multiple menu items. #[component] pub fn ContextMenuGroup( children: Children, @@ -108,10 +108,6 @@ pub fn ContextMenuGroup( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let heading_id = RwSignal::new(String::new()); - - let context = ContextMenuGroupContext { heading_id }; - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -151,17 +147,13 @@ pub fn ContextMenuGroup( }; view! { - <div - aria-labelledby=move || heading_id.get() - class=move || { - format!("singlestage-context-menu-group {}", class.get().unwrap_or_default()) - } - role="group" + <MenuGroupPrimitive + class {..global_attrs_1} {..global_attrs_2} > - <Provider value=context>{children()}</Provider> - </div> + {children()} + </MenuGroupPrimitive> } } diff --git a/singlestage/src/components/context_menu/item.rs b/singlestage/src/components/context_menu/item.rs index 8008bbb1..776bc13f 100644 --- a/singlestage/src/components/context_menu/item.rs +++ b/singlestage/src/components/context_menu/item.rs @@ -1,14 +1,23 @@ -use crate::ContextMenuContext; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// Contains a menu item. +/// Contains a context menu item. #[component] pub fn ContextMenuItem( children: Children, + /// This component will render without styling + #[prop(optional, into)] + as_child: MaybeProp<bool>, /// Controls whether the item appears disabled and is clickable. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, + /// Toggle whether clicking this item dismisses its parent menu + #[prop(optional, into, default = Reactive::new(true))] + dismiss: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, /// Set the display variant of the item. /// /// Accepted values: "destructive" @@ -123,13 +132,12 @@ pub fn ContextMenuItem( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let menu = expect_context::<ContextMenuContext>(); - 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() @@ -164,27 +172,22 @@ pub fn ContextMenuItem( }; view! { - <li - class=move || { - format!("singlestage-context-menu-item {}", class.get().unwrap_or_default()) - } - role="menuitem" - value=move || value.get() + <MenuItemPrimitive + primitive_type=MenuItemPrimitiveType::Item + + as_child + class + disabled + dismiss + id + inset + variant + value {..global_attrs_1} {..global_attrs_2} > - <button - aria-controls=move || menu.menu_id.get() - aria-haspopup="menu" - data-disabled=move || disabled.get().unwrap_or_default() - data-variant=move || variant.get().unwrap_or_default() - popovertarget=move || menu.menu_id.get() - popovertargetaction="toggle" - type="button" - > - {children()} - </button> - </li> + {children()} + </MenuItemPrimitive> } } diff --git a/singlestage/src/components/context_menu/label.rs b/singlestage/src/components/context_menu/label.rs index afbf9880..050f14f0 100644 --- a/singlestage/src/components/context_menu/label.rs +++ b/singlestage/src/components/context_menu/label.rs @@ -1,11 +1,24 @@ -use crate::ContextMenuGroupContext; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// Labels groups. +/// Labels groups of menu items. #[component] pub fn ContextMenuLabel( children: Children, + /// Renders the label with disabled styling. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Renders the label with invalid styling. + #[prop(optional, into)] + invalid: Reactive<bool>, + /// Use this to explicitly set the id of the labeled element. + #[prop(optional, into)] + label_for: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -47,6 +60,9 @@ pub fn ContextMenuLabel( /// Controls hidden status of the element. #[prop(optional, into)] hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, /// Toggle if the browser reacts to input events from this element. #[prop(optional, into)] inert: MaybeProp<bool>, @@ -84,6 +100,9 @@ pub fn ContextMenuLabel( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp<String>, @@ -105,11 +124,6 @@ pub fn ContextMenuLabel( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let group = expect_context::<ContextMenuGroupContext>(); - - let uuid = uuid::Uuid::new_v4().to_string(); - group.heading_id.set(uuid.clone()); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -138,6 +152,7 @@ pub fn ContextMenuLabel( 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() @@ -148,16 +163,19 @@ pub fn ContextMenuLabel( }; view! { - <h6 - id=uuid - class=move || { - format!("singlestage-context-menu-label {}", class.get().unwrap_or_default()) - } + <LabelPrimitive + primitive_type=LabelPrimitiveType::MenuLabel + class + disabled + id + inset + invalid + label_for {..global_attrs_1} {..global_attrs_2} > {children()} - </h6> + </LabelPrimitive> } } diff --git a/singlestage/src/components/context_menu/menu.rs b/singlestage/src/components/context_menu/menu.rs index a17abaed..f69d1e7f 100644 --- a/singlestage/src/components/context_menu/menu.rs +++ b/singlestage/src/components/context_menu/menu.rs @@ -1,6 +1,12 @@ -use crate::ContextMenuContext; +use crate::{Reactive, primitives::*}; use leptos::{context::Provider, prelude::*}; +#[derive(Clone, Default)] +pub(crate) struct ContextMenuContext { + pub x: RwSignal<i32>, + pub y: RwSignal<i32>, +} + /// Contains all the parts of a context menu. #[component] pub fn ContextMenu( @@ -111,13 +117,12 @@ pub fn ContextMenu( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let menu_id = RwSignal::new(String::new()); - - let context = ContextMenuContext { - menu_id, - menu_ref: RwSignal::new(None), - x: RwSignal::new(i32::default()), - y: RwSignal::new(i32::default()), + let popover_context = PopoverMenuContext { + modal: Reactive::new(false), + ..Default::default() + }; + let menu_context = ContextMenuContext { + ..Default::default() }; let global_attrs_1 = view! { @@ -161,14 +166,14 @@ pub fn ContextMenu( view! { <div - class=move || { - format!("singlestage-context-menu {}", class.get().unwrap_or_default()) - } + class=move || format!("singlestage-context-menu {}", class.get().unwrap_or_default()) {..global_attrs_1} {..global_attrs_2} > - <Provider value=context>{children()}</Provider> + <Provider value=menu_context> + <Provider value=popover_context>{children()}</Provider> + </Provider> </div> } } diff --git a/singlestage/src/components/context_menu/mod.rs b/singlestage/src/components/context_menu/mod.rs index 178ba7e4..dc44231d 100644 --- a/singlestage/src/components/context_menu/mod.rs +++ b/singlestage/src/components/context_menu/mod.rs @@ -1,32 +1,25 @@ +mod checkbox; mod content; mod group; mod item; mod label; mod menu; +mod radio; +mod radio_group; mod separator; mod shortcut; +mod sub; mod trigger; +pub use checkbox::*; pub use content::*; pub use group::*; pub use item::*; pub use label::*; pub use menu::*; +pub use radio::*; +pub use radio_group::*; pub use separator::*; pub use shortcut::*; +pub use sub::*; pub use trigger::*; - -use leptos::{html::Menu, prelude::*}; - -#[derive(Clone)] -pub struct ContextMenuContext { - menu_id: RwSignal<String>, - menu_ref: RwSignal<Option<NodeRef<Menu>>>, - x: RwSignal<i32>, - y: RwSignal<i32>, -} - -#[derive(Clone)] -pub struct ContextMenuGroupContext { - heading_id: RwSignal<String>, -} diff --git a/singlestage/src/components/context_menu/radio.rs b/singlestage/src/components/context_menu/radio.rs new file mode 100644 index 00000000..a9cd12a5 --- /dev/null +++ b/singlestage/src/components/context_menu/radio.rs @@ -0,0 +1,193 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +/// A radio item for context menus. +#[component] +pub fn ContextMenuRadioItem( + children: Children, + + /// A reactive signal coupled to the radio's checked value. + #[prop(optional, into)] + checked: Reactive<bool>, + /// Controls whether the item appears disabled and is clickable. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Toggle whether clicking this item dismisses its parent menu + #[prop(optional, into, default = Reactive::new(true))] + dismiss: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Set the display variant of the item. + /// + /// Accepted values: "destructive" + #[prop(optional, into)] + variant: MaybeProp<String>, + + // LI ATTRIBRUTES + // + /// The current ordinal value of the item. + #[prop(optional, into)] + value: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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() + 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! { + <MenuItemPrimitive + primitive_type=MenuItemPrimitiveType::Radio + + checked + class + disabled + dismiss + id + inset + variant + value + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuItemPrimitive> + } +} diff --git a/singlestage/src/components/context_menu/radio_group.rs b/singlestage/src/components/context_menu/radio_group.rs new file mode 100644 index 00000000..4126aa6c --- /dev/null +++ b/singlestage/src/components/context_menu/radio_group.rs @@ -0,0 +1,191 @@ +use crate::{RadioGroup, Reactive}; +use leptos::prelude::*; + +/// A radio group component for context menus. +#[component] +pub fn ContextMenuRadioGroup( + children: Children, + + /// Set or update the default value. + #[prop(optional, into)] + default: MaybeProp<String>, + /// Renders the group with invalid styling. + #[prop(optional, into)] + invalid: Reactive<bool>, + /// Reactive signal coupled to the current selected value of the radio group. + #[prop(optional, into)] + value: Reactive<String>, + + // FIELDSET ATTRIBUTES + // + /// Toggle whether or not the input is disabled. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Associate this element with a form element that may not be its parent by its `id`. + #[prop(optional, into)] + form: MaybeProp<String>, + /// Name of this element. Submitted with the form as part of a name/value pair. + #[prop(optional, into)] + name: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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() + /> + }; + + let fieldset_attrs = view! { <{..} disabled=move || disabled.get() form=move || form.get() name=move || name.get() /> }; + + view! { + <RadioGroup + default + invalid + value + + {..global_attrs_1} + {..global_attrs_2} + {..fieldset_attrs} + > + {children()} + </RadioGroup> + } +} diff --git a/singlestage/src/components/context_menu/separator.rs b/singlestage/src/components/context_menu/separator.rs index ecf0e214..6d55edba 100644 --- a/singlestage/src/components/context_menu/separator.rs +++ b/singlestage/src/components/context_menu/separator.rs @@ -1,8 +1,16 @@ +use crate::Separator; use leptos::prelude::*; /// Visually separates groups of menu items. #[component] pub fn ContextMenuSeparator( + #[prop(optional)] children: Option<Children>, + + /// Specify the orientation of the separator + /// Accepted values: `"vertical"` | `"horizontal"` (default) + #[prop(optional, into)] + orientation: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -84,6 +92,9 @@ pub fn ContextMenuSeparator( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp<String>, @@ -110,6 +121,7 @@ pub fn ContextMenuSeparator( 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() @@ -134,6 +146,7 @@ pub fn ContextMenuSeparator( 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() @@ -143,15 +156,27 @@ pub fn ContextMenuSeparator( /> }; - view! { - <hr - class=move || { - format!("singlestage-context-menu-separator {}", class.get().unwrap_or_default()) - } - role="separator" + if let Some(children) = children { + view! { + <Separator + orientation - {..global_attrs_1} - {..global_attrs_2} - /> + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </Separator> + } + .into_any() + } else { + view! { + <Separator + orientation + + {..global_attrs_1} + {..global_attrs_2} + /> + } + .into_any() } } diff --git a/singlestage/src/components/context_menu/shortcut.rs b/singlestage/src/components/context_menu/shortcut.rs index ef148a54..f0a67724 100644 --- a/singlestage/src/components/context_menu/shortcut.rs +++ b/singlestage/src/components/context_menu/shortcut.rs @@ -1,3 +1,4 @@ +use crate::primitives::*; use leptos::prelude::*; /// Displays next to the item content and indicates keyboard shortcuts. @@ -150,15 +151,13 @@ pub fn ContextMenuShortcut( }; view! { - <span - class=move || { - format!("singlestage-context-menu-shortcut {}", class.get().unwrap_or_default()) - } + <MenuShortcutPrimitive + class {..global_attrs_1} {..global_attrs_2} > {children()} - </span> + </MenuShortcutPrimitive> } } diff --git a/singlestage/src/components/context_menu/sub/content.rs b/singlestage/src/components/context_menu/sub/content.rs new file mode 100644 index 00000000..e5e7a586 --- /dev/null +++ b/singlestage/src/components/context_menu/sub/content.rs @@ -0,0 +1,172 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// The component that pops out when the sub trigger is triggered. +#[component] +pub fn ContextMenuSubContent( + children: Children, + + /// Set how to align the popover + /// + /// Accepted values: "start" | "center" | "end" + /// Default is "start" + #[prop(optional, into)] + align: MaybeProp<String>, + /// Set which side the popover opens relative to the trigger + /// + /// Accepted values: "top" | "right" | "bottom" | "left" + /// Default is "bottom" + #[prop(optional, into)] + side: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + <MenuContentPrimitive + primitive_type=MenuContentPrimitiveType::Sub + + align + class + id + side + style + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuContentPrimitive> + } +} diff --git a/singlestage/src/components/context_menu/sub/menu.rs b/singlestage/src/components/context_menu/sub/menu.rs new file mode 100644 index 00000000..eeec4610 --- /dev/null +++ b/singlestage/src/components/context_menu/sub/menu.rs @@ -0,0 +1,169 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +/// Contains all the parts of a sub menu. +#[component] +pub fn ContextMenuSub( + children: Children, + + /// Reactive signal that can remotely control the open state of the popover **but is not + /// coupled to the actual open state of the popover** + #[prop(optional, into)] + open: Reactive<bool>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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! { + <MenuSubPrimitive + open + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuSubPrimitive> + } +} diff --git a/singlestage/src/components/context_menu/sub/mod.rs b/singlestage/src/components/context_menu/sub/mod.rs new file mode 100644 index 00000000..ee49e468 --- /dev/null +++ b/singlestage/src/components/context_menu/sub/mod.rs @@ -0,0 +1,7 @@ +mod content; +mod menu; +mod trigger; + +pub use content::*; +pub use menu::*; +pub use trigger::*; diff --git a/singlestage/src/components/context_menu/sub/trigger.rs b/singlestage/src/components/context_menu/sub/trigger.rs new file mode 100644 index 00000000..258398b6 --- /dev/null +++ b/singlestage/src/components/context_menu/sub/trigger.rs @@ -0,0 +1,186 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +/// A context menu item that triggers a sub menu. +#[component] +pub fn ContextMenuSubTrigger( + children: Children, + + /// Controls whether the item appears disabled and is clickable. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Set the display variant of the item. + /// + /// Accepted values: "destructive" + #[prop(optional, into)] + variant: MaybeProp<String>, + + // LI ATTRIBRUTES + // + /// The current ordinal value of the item. + #[prop(optional, into)] + value: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + class=move || class.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() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + value=move || value.get() + /> + }; + + view! { + <MenuItemPrimitive + primitive_type=MenuItemPrimitiveType::SubTrigger + + class + disabled + id + inset + value + variant + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuItemPrimitive> + } +} diff --git a/singlestage/src/components/context_menu/trigger.rs b/singlestage/src/components/context_menu/trigger.rs index efcdb819..b34dd07e 100644 --- a/singlestage/src/components/context_menu/trigger.rs +++ b/singlestage/src/components/context_menu/trigger.rs @@ -1,4 +1,4 @@ -use crate::ContextMenuContext; +use crate::{ContextMenuContext, primitives::*}; use leptos::prelude::*; /// Defines the area where the context menu can be triggered. @@ -105,8 +105,6 @@ pub fn ContextMenuTrigger( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let menu = expect_context::<ContextMenuContext>(); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -145,30 +143,50 @@ pub fn ContextMenuTrigger( /> }; + let context_menu = expect_context::<ContextMenuContext>(); + let menu = expect_context::<PopoverMenuContext>(); + + // Keep track of when the user triggers the menu + // So it doesn't flicker when tapping on the trigger area + // If the menu wasn't opened previously + let triggered = RwSignal::new(false); + view! { <div on:contextmenu=move |ev| { ev.prevent_default(); - menu.x.set(ev.x()); - menu.y.set(ev.y()); - if let Some(menu) = menu.menu_ref.get_untracked() - && let Some(menu) = menu.get_untracked() { - let _ = menu.show_popover(); - } + context_menu.x.set(ev.x()); + context_menu.y.set(ev.y()); + menu.open.set(true); + triggered.set(true); + } + + // Prevent the menu getting immediately soft dismissed after opening + on:mouseup=move |ev| { + if ev.button() == 2 { + menu.open.set(true); + } + } + on:touchend=move |_ev| { + if triggered.get() { + menu.open.set(true); + } } + + // Allow closing the menu if the trigger area is touched/clicked again on:mousedown=move |_ev| { - if let Some(menu) = menu.menu_ref.get_untracked() - && let Some(menu) = menu.get_untracked() { - let _ = menu.hide_popover(); - } + if menu.open.get_untracked() && !menu.modal.get_untracked() { + menu.open.set(false); + triggered.set(false); + } } - on:mouseup=move |ev| { - if ev.button() == 2 - && let Some(menu) = menu.menu_ref.get_untracked() - && let Some(menu) = menu.get_untracked() { - let _ = menu.show_popover(); - } + on:touchstart=move |_ev| { + if menu.open.get_untracked() && !menu.modal.get_untracked() { + menu.open.set(false); + triggered.set(false); + } } + {..global_attrs_1} {..global_attrs_2} > diff --git a/singlestage/src/components/dialog/action.rs b/singlestage/src/components/dialog/action.rs new file mode 100644 index 00000000..e204fda1 --- /dev/null +++ b/singlestage/src/components/dialog/action.rs @@ -0,0 +1,8 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// Wraps a Button that the user presses to acknowledge the dialog. +#[component] +pub fn DialogAction(children: Children) -> impl IntoView { + view! { <DialogActionPrimitive>{children()}</DialogActionPrimitive> } +} diff --git a/singlestage/src/components/dialog/cancel.rs b/singlestage/src/components/dialog/cancel.rs new file mode 100644 index 00000000..30e7916d --- /dev/null +++ b/singlestage/src/components/dialog/cancel.rs @@ -0,0 +1,8 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// Wraps Button that the user presses to dismiss the dialog. +#[component] +pub fn DialogCancel(children: Children) -> impl IntoView { + view! { <DialogCancelPrimitive>{children()}</DialogCancelPrimitive> } +} diff --git a/singlestage/src/components/dialog/close.rs b/singlestage/src/components/dialog/close.rs index 5de37110..a5ff63f1 100644 --- a/singlestage/src/components/dialog/close.rs +++ b/singlestage/src/components/dialog/close.rs @@ -1,260 +1,8 @@ +use crate::primitives::*; use leptos::prelude::*; -/// An optional button that renders in the top-right corner and closes the dialog when clicked. +/// Wraps Button that the user presses to dismiss the dialog. #[component] -pub fn DialogClose( - // BUTTON ATTRIBUTES - // - /// Set the default behavior of the button. - /// - /// Accepted values: "submit" | "reset" | "button" - #[prop(optional, into)] - button_type: MaybeProp<String>, - /// The action that's performed by the element this button controls. - /// - /// Accepted values: "show-modal" | "close" | "request-close" | "show-popover" | "hide-popover" - /// | "toggle-popover" | "--[custom value]" - #[prop(optional, into)] - command: MaybeProp<String>, - /// Turn this button into a command button for an element via id. - #[prop(optional, into)] - commandfor: MaybeProp<String>, - /// Toggle whether or not the input is disabled. - #[prop(optional, into)] - disabled: MaybeProp<bool>, - /// Associate this element with a form element that may not be its parent by its `id`. - #[prop(optional, into)] - form: MaybeProp<String>, - /// Defines the target for submitted form data. Overrides any parent `<form>` `action` values. - #[prop(optional, into)] - formaction: MaybeProp<String>, - /// Defines the encoding type for submitted form data. Overrides any parent `<form>` - /// `formenctype` values. - /// - /// Accepted values: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain". - #[prop(optional, into)] - formenctype: MaybeProp<String>, - /// Defines the HTTP method used to submit form data. Overrides any parent `<form>` `method` - /// values. - /// - /// Accepted values: "get" | "post" | "dialog". - #[prop(optional, into)] - formmethod: MaybeProp<String>, - /// Toggle whether the form data is validated or not before submission. Overrides any parent - /// `<form>` `novalidate` values. - #[prop(optional, into)] - formnovalidate: MaybeProp<bool>, - /// Defines where to display the response received after submission. Overrides any parent - /// `<form>` `target` values. - /// - /// Accepted values: "_self" | "_blank" | "_parent" | "_top", or the `name` of any tab, window, - /// or iframe - #[prop(optional, into)] - formtarget: MaybeProp<String>, - /// Name of this element. Submitted with the form as part of a name/value pair. - #[prop(optional, into)] - name: MaybeProp<String>, - /// Id of a popover to control. - #[prop(optional, into)] - popovertarget: MaybeProp<String>, - /// The action to perform on the target popover. - /// - /// Accepted values: "hide" | "show" | "toggle" - #[prop(optional, into)] - popovertargetaction: MaybeProp<String>, - /// The value associated with this button. - #[prop(optional, into)] - value: MaybeProp<String>, - - // 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<String>, - /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, - /// 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<bool>, - /// Apply classes to the element. - #[prop(optional, into)] - class: MaybeProp<String>, - /// Allows client-side editing of the element by the user. - /// - /// Accepted values: "true" | "false" | "plaintext-only" - #[prop(optional, into)] - contenteditable: MaybeProp<String>, - /// Indicate directionality of the element's text. - /// - /// Accepted values: "ltr" | "rtl" | "auto" - #[prop(optional, into)] - dir: MaybeProp<String>, - /// Toggle whether the element can be dragged. - #[prop(optional, into)] - draggable: MaybeProp<bool>, - /// Modifies the appearance of the enter key on virtual keyboards. - #[prop(optional, into)] - enterkeyhint: MaybeProp<String>, - /// Expose elements in the shadow DOM to be manipulated by the DOM. - #[prop(optional, into)] - exportparts: MaybeProp<String>, - /// Controls hidden status of the element. - #[prop(optional, into)] - hidden: MaybeProp<String>, - /// Set the id of this element. - #[prop(optional, into)] - id: MaybeProp<String>, - /// Toggle if the browser reacts to input events from this element. - #[prop(optional, into)] - inert: MaybeProp<bool>, - /// Hints to the browser of what type of virtual keyboard to display when editing this element - /// or its children. - #[prop(optional, into)] - inputmode: MaybeProp<String>, - /// Used to render a standard element as a custom element. - #[prop(optional, into)] - is: MaybeProp<String>, - /// Unique global identifier of an item. - #[prop(optional, into)] - itemid: MaybeProp<String>, - /// Used to add properties to an item. - #[prop(optional, into)] - itemprop: MaybeProp<String>, - /// Used to associate an item with a related non-parent element that's using `itemscope`. - #[prop(optional, into)] - itemref: MaybeProp<String>, - /// Used to declare that children elements are related to a particular item. - #[prop(optional, into)] - itemscope: MaybeProp<String>, - /// URL of data used to define `itemprops`. - #[prop(optional, into)] - itemtype: MaybeProp<String>, - /// Defines the language of an element. - #[prop(optional, into)] - lang: MaybeProp<String>, - /// Cryptographic "number used once". - #[prop(optional, into)] - nonce: MaybeProp<String>, - /// List of the part names of the element. - #[prop(optional, into)] - part: MaybeProp<String>, - /// Designate an element as a popover element. - #[prop(optional, into)] - popover: MaybeProp<String>, - /// Define the semantic meaning of content. - #[prop(optional, into)] - role: MaybeProp<String>, - /// Assigns a slot to an element. - #[prop(optional, into)] - slot: MaybeProp<String>, - /// Toggle spellcheck for this input. - /// - /// Accepted values: "default" | "true" | "false". - #[prop(optional, into)] - spellcheck: MaybeProp<String>, - /// Define CSS to be applied to the element. - #[prop(optional, into)] - style: MaybeProp<String>, - /// Controls how an element behaves when a user navigates using the tab key. - #[prop(optional, into)] - tabindex: MaybeProp<usize>, - /// Describes the content of the element to screen readers. - #[prop(optional, into)] - title: MaybeProp<String>, - /// Defines localization behavior for the element. - #[prop(optional, into)] - translate: MaybeProp<String>, -) -> 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() - /> - }; - - 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() - /> - }; - - let button_attrs = view! { - <{..} - command=move || command.get() - commandfor=move || commandfor.get() - disabled=move || disabled.get() - form=move || form.get() - formaction=move || formaction.get() - formenctype=move || formenctype.get() - formmethod=move || formmethod.get() - formnovalidate=move || formnovalidate.get() - formtarget=move || formtarget.get() - name=move || name.get() - popovertarget=move || popovertarget.get() - popovertargetaction=move || popovertargetaction.get() - type=move || button_type.get() - value=move || value.get() - /> - }; - - view! { - <form method="dialog"> - <button - aria-label="Close dialog" - - {..global_attrs_1} - {..global_attrs_2} - {..button_attrs} - > - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" - class="lucide lucide-x-icon lucide-x" - > - <path d="M18 6 6 18" /> - <path d="m6 6 12 12" /> - </svg> - </button> - </form> - } +pub fn DialogClose(children: Children) -> impl IntoView { + view! { <DialogCancelPrimitive>{children()}</DialogCancelPrimitive> } } diff --git a/singlestage/src/components/dialog/content.rs b/singlestage/src/components/dialog/content.rs index 36a6367a..a5b81926 100644 --- a/singlestage/src/components/dialog/content.rs +++ b/singlestage/src/components/dialog/content.rs @@ -1,3 +1,4 @@ +use crate::primitives::*; use leptos::prelude::*; /// Contains content to be rendered in the main body of the dialog. @@ -5,6 +6,12 @@ use leptos::prelude::*; pub fn DialogContent( children: Children, + /// Toggles whether or not a close button should appear in the top right corner of the dialog. + /// + /// Defaults to `true` for `Dialog` and `false` for `AlertDialog` + #[prop(optional, into)] + close_button: MaybeProp<bool>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -115,7 +122,6 @@ pub fn DialogContent( 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() @@ -151,8 +157,14 @@ pub fn DialogContent( }; view! { - <section {..global_attrs_1} {..global_attrs_2}> + <DialogContentPrimitive + class + close_button + + {..global_attrs_1} + {..global_attrs_2} + > {children()} - </section> + </DialogContentPrimitive> } } diff --git a/singlestage/src/components/dialog/description.rs b/singlestage/src/components/dialog/description.rs index b50463b0..e0cc1919 100644 --- a/singlestage/src/components/dialog/description.rs +++ b/singlestage/src/components/dialog/description.rs @@ -1,4 +1,4 @@ -use crate::DialogContext; +use crate::primitives::*; use leptos::prelude::*; /// A short description/subheading describing dialog content. @@ -47,9 +47,9 @@ pub fn DialogDescription( /// Controls hidden status of the element. #[prop(optional, into)] hidden: MaybeProp<String>, - // /// Set the id of this element. - // #[prop(optional, into)] - // id: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, /// Toggle if the browser reacts to input events from this element. #[prop(optional, into)] inert: MaybeProp<bool>, @@ -111,24 +111,18 @@ pub fn DialogDescription( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let dialog = expect_context::<DialogContext>(); - - let uuid: String = uuid::Uuid::new_v4().to_string(); - dialog.described_by.set(uuid.clone()); - 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() + id=move || id.get() inert=move || inert.get() inputmode=move || inputmode.get() is=move || is.get() @@ -155,9 +149,15 @@ pub fn DialogDescription( translate=move || translate.get() /> }; + view! { - <p {..global_attrs_1} {..global_attrs_2} id=uuid> + <DialogDescriptionPrimitive + class + + {..global_attrs_1} + {..global_attrs_2} + > {children()} - </p> + </DialogDescriptionPrimitive> } } diff --git a/singlestage/src/components/dialog/dialog.css b/singlestage/src/components/dialog/dialog.css deleted file mode 100644 index 32a23abb..00000000 --- a/singlestage/src/components/dialog/dialog.css +++ /dev/null @@ -1,123 +0,0 @@ -@layer components { - .singlestage-dialog { - @apply inset-y-0 - opacity-0 - text-foreground - transition-all - transition-discrete; - - &:is([open], :popover-open) { - @apply opacity-100; - - &::backdrop { - @apply opacity-100; - } - - > article { - @apply scale-100; - } - - @starting-style { - @apply opacity-0; - - &::backdrop { - @apply opacity-0; - } - - > article { - @apply scale-95; - } - } - } - - &::backdrop { - @apply bg-black/50 - opacity-0 - transition-all - transition-discrete; - } - - > article { - @apply -translate-x-1/2 - -translate-y-1/2 - bg-background - border - fixed - flex - flex-col - gap-4 - left-[50%] - max-h-[calc(100%_-_2rem)] - max-w-[calc(100%_-_2rem)] - p-6 - rounded-lg - shadow-lg - sm:max-w-lg - top-[50%] - w-full - z-50; - - @apply scale-95 - transition-all; - - > header { - @apply flex - flex-col - gap-2 - sm:text-left - text-center; - - > h2 { - @apply font-semibold - leading-none - text-lg; - } - - > p { - @apply text-muted-foreground - text-sm; - } - } - - > section { - @apply -mx-6 - flex-1 - px-6; - } - - > footer { - > form { - @apply flex - flex-col-reverse - gap-2 - sm:flex-row - sm:justify-end; - } - } - - > form[method="dialog"] { - @apply absolute - right-4 - top-4; - - > button { - @apply [&_svg:not([class*='size-'])]:size-4 - [&_svg]:pointer-events-none - [&_svg]:shrink-0 - data-[state=open]:bg-accent - data-[state=open]:text-muted-foreground - disabled:pointer-events-none - focus:outline-hidden - focus:ring-2 - focus:ring-offset-2 - focus:ring-ring - hover:opacity-100 - opacity-70 - ring-offset-background - rounded-xs - transition-opacity; - } - } - } - } -} diff --git a/singlestage/src/components/dialog/dialog.rs b/singlestage/src/components/dialog/dialog.rs index 21601fa3..b884f438 100644 --- a/singlestage/src/components/dialog/dialog.rs +++ b/singlestage/src/components/dialog/dialog.rs @@ -1,27 +1,15 @@ -use crate::{DialogContext, DialogTrigger}; -use leptos::{context::Provider, html, prelude::*}; +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; -/// Contains all the parts of an alert dialog. +/// Contains all the parts of a Dialog component. #[component] pub fn Dialog( children: Children, - /// A button that opens the dialog. - dialog_trigger: DialogTrigger, - // DIALOG ATTRIBUTES - // - /// Specify whether or not this dialog contains an alert - #[prop(optional, into)] - alert: MaybeProp<bool>, - // TODO:Check for safari support - // /// Specify what actions will close the dialog. - // /// - // /// Accepted values: "any" | "closerequest" | "none" - // #[prop(optional, into)] - // closedby: MaybeProp<String>, - /// Toggle whether or not the dialog is open. - #[prop(optional, into)] - open: MaybeProp<bool>, + /// Reactive signal that can remotely control the open state of the popover **but is not + /// coupled to the actual open state of the popover** + #[prop(optional, into)] + open: Reactive<bool>, // GLOBAL ATTRIBUTES // @@ -128,42 +116,6 @@ pub fn Dialog( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let dialog_ref = NodeRef::<html::Dialog>::new(); - let labeled_by = RwSignal::new(String::new()); - let described_by = RwSignal::new(String::new()); - - let context = DialogContext { - labeled_by, - described_by, - }; - - let open_dialog = move || { - if let Some(dialog) = dialog_ref.write().as_ref() - && dialog.show_modal().is_err() - {} - }; - - let close_dialog = move || { - if let Some(alert) = alert.get_untracked() - && alert - { - return; - } - - if let Some(dialog) = dialog_ref.write().as_ref() { - dialog.close(); - } - }; - - Effect::new(move || { - if let Some(open) = open.get() { - match open { - true => open_dialog(), - false => close_dialog(), - } - } - }); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -205,20 +157,14 @@ pub fn Dialog( }; view! { - <div on:click=move |_| open_dialog()>{(dialog_trigger.children)().into_any()}</div> - <dialog - aria-describedby=move || described_by.get() - aria-labelledby=move || labeled_by.get() - class="singlestage-dialog" - // TODO:Check for safari support - // closedby=closedby.get() - node_ref=dialog_ref - on:click=move |_| close_dialog() - open=move || open.get_untracked() + <DialogPrimitive + alert=false + open + + {..global_attrs_1} + {..global_attrs_2} > - <article {..global_attrs_1} {..global_attrs_2} on:click=move |ev| ev.stop_propagation()> - <Provider value=context>{children()}</Provider> - </article> - </dialog> + {children()} + </DialogPrimitive> } } diff --git a/singlestage/src/components/dialog/footer.rs b/singlestage/src/components/dialog/footer.rs index e4ddccbb..af09f7d2 100644 --- a/singlestage/src/components/dialog/footer.rs +++ b/singlestage/src/components/dialog/footer.rs @@ -1,7 +1,7 @@ +use crate::primitives::*; use leptos::prelude::*; -/// Displays at the bottom of the dialog, contains calls to action. Submit events such as button -/// clicks in this area automatically trigger closing of the dialog. +/// Displays at the bottom of the dialog, contains calls to action. #[component] pub fn DialogFooter( children: Children, @@ -116,7 +116,6 @@ pub fn DialogFooter( 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() @@ -152,10 +151,13 @@ pub fn DialogFooter( }; view! { - <footer> - <form method="dialog" {..global_attrs_1} {..global_attrs_2}> - {children()} - </form> - </footer> + <DialogFooterPrimitive + class + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </DialogFooterPrimitive> } } diff --git a/singlestage/src/components/dialog/header.rs b/singlestage/src/components/dialog/header.rs index 93f62d32..95330237 100644 --- a/singlestage/src/components/dialog/header.rs +++ b/singlestage/src/components/dialog/header.rs @@ -1,3 +1,4 @@ +use crate::primitives::*; use leptos::prelude::*; /// Contains the dialog title and a description to be rendered in the open dialog. @@ -115,7 +116,6 @@ pub fn DialogHeader( 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() @@ -151,8 +151,13 @@ pub fn DialogHeader( }; view! { - <header {..global_attrs_1} {..global_attrs_2}> + <DialogHeaderPrimitive + class + + {..global_attrs_1} + {..global_attrs_2} + > {children()} - </header> + </DialogHeaderPrimitive> } } diff --git a/singlestage/src/components/dialog/mod.rs b/singlestage/src/components/dialog/mod.rs index ae77ff28..4937414b 100644 --- a/singlestage/src/components/dialog/mod.rs +++ b/singlestage/src/components/dialog/mod.rs @@ -1,3 +1,5 @@ +mod action; +mod cancel; mod close; mod content; mod description; @@ -7,6 +9,8 @@ mod header; mod title; mod trigger; +pub use action::*; +pub use cancel::*; pub use close::*; pub use content::*; pub use description::*; @@ -15,11 +19,3 @@ pub use footer::*; pub use header::*; pub use title::*; pub use trigger::*; - -use leptos::prelude::*; - -#[derive(Clone)] -pub struct DialogContext { - pub labeled_by: RwSignal<String>, - pub described_by: RwSignal<String>, -} diff --git a/singlestage/src/components/dialog/style/base/luma/dialog.css b/singlestage/src/components/dialog/style/base/luma/dialog.css new file mode 100644 index 00000000..f8db6232 --- /dev/null +++ b/singlestage/src/components/dialog/style/base/luma/dialog.css @@ -0,0 +1,64 @@ +@layer components1 { + .singlestage-dialog-overlay { + @apply bg-popover + max-w-[calc(100%-2rem)] + ring-1 + ring-foreground/5 + rounded-4xl + shadow-xl + sm:max-w-md; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-sm; + } + + &[open]::backdrop { + @apply bg-black/30; + } + } + + .singlestage-dialog-content { + @apply bg-popover + gap-6 + grid + p-6 + rounded-4xl + text-popover-foreground + text-sm; + } + + .singlestage-dialog-close { + @apply absolute + bg-secondary + right-4 + top-4; + } + + .singlestage-dialog-header { + @apply gap-1.5; + } + + .singlestage-dialog-footer { + @apply gap-2; + } + + .singlestage-dialog-title { + @apply font-medium + leading-none + text-base; + } + + .singlestage-dialog-description { + @apply text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } +} diff --git a/singlestage/src/components/dialog/style/base/luma/dialog_dark.css b/singlestage/src/components/dialog/style/base/luma/dialog_dark.css new file mode 100644 index 00000000..38fb3184 --- /dev/null +++ b/singlestage/src/components/dialog/style/base/luma/dialog_dark.css @@ -0,0 +1,5 @@ +@layer components1 { + .singlestage-dialog-content { + @apply ring-foreground/10; + } +} diff --git a/singlestage/src/components/dialog/style/base/lyra/dialog.css b/singlestage/src/components/dialog/style/base/lyra/dialog.css new file mode 100644 index 00000000..22b0e46d --- /dev/null +++ b/singlestage/src/components/dialog/style/base/lyra/dialog.css @@ -0,0 +1,58 @@ +@layer components1 { + .singlestage-dialog-overlay { + @apply bg-popover + max-w-[calc(100%-2rem)] + ring-1 + ring-foreground/10 + rounded-none + sm:max-w-sm; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/10; + } + } + + .singlestage-dialog-content { + @apply bg-popover + gap-4 + grid + p-4 + rounded-none + text-popover-foreground + text-xs/relaxed; + } + + .singlestage-dialog-close { + @apply absolute + right-2 + top-2; + } + + .singlestage-dialog-header { + @apply gap-1 + text-left; + } + + .singlestage-dialog-title { + @apply font-medium + text-sm; + } + + .singlestage-dialog-description { + @apply text-muted-foreground + text-xs/relaxed; + + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } +} diff --git a/singlestage/src/components/dialog/style/base/maia/dialog.css b/singlestage/src/components/dialog/style/base/maia/dialog.css new file mode 100644 index 00000000..6cbb5901 --- /dev/null +++ b/singlestage/src/components/dialog/style/base/maia/dialog.css @@ -0,0 +1,62 @@ +@layer components1 { + .singlestage-dialog-overlay { + @apply bg-popover + max-w-[calc(100%-2rem)] + ring-1 + ring-foreground/5 + rounded-4xl + sm:max-w-md; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/80; + } + } + + .singlestage-dialog-content { + @apply bg-popover + gap-6 + grid + p-6 + rounded-4xl + text-popover-foreground + text-sm; + } + + .singlestage-dialog-close { + @apply absolute + right-4 + top-4; + } + + .singlestage-dialog-header { + @apply gap-2; + } + + .singlestage-dialog-footer { + @apply gap-2; + } + + .singlestage-dialog-title { + @apply font-medium + leading-none + text-base; + } + + .singlestage-dialog-description { + @apply text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } +} diff --git a/singlestage/src/components/dialog/style/base/mira/dialog.css b/singlestage/src/components/dialog/style/base/mira/dialog.css new file mode 100644 index 00000000..b729da14 --- /dev/null +++ b/singlestage/src/components/dialog/style/base/mira/dialog.css @@ -0,0 +1,61 @@ +@layer components1 { + .singlestage-dialog-overlay { + @apply bg-popover + max-w-[calc(100%-2rem)] + ring-1 + ring-foreground/10 + rounded-xl + sm:max-w-sm; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/80; + } + } + + .singlestage-dialog-content { + @apply bg-popover + gap-4 + grid + p-4 + rounded-xl + text-popover-foreground + text-xs/relaxed; + } + + .singlestage-dialog-close { + @apply absolute + right-2 + top-2; + } + + .singlestage-dialog-header { + @apply gap-1; + } + + .singlestage-dialog-footer { + @apply gap-2; + } + + .singlestage-dialog-title { + @apply font-medium + text-sm; + } + + .singlestage-dialog-description { + @apply text-muted-foreground + text-xs/relaxed; + + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } +} diff --git a/singlestage/src/components/dialog/style/base/nova/dialog.css b/singlestage/src/components/dialog/style/base/nova/dialog.css new file mode 100644 index 00000000..2f57e347 --- /dev/null +++ b/singlestage/src/components/dialog/style/base/nova/dialog.css @@ -0,0 +1,67 @@ +@layer components1 { + .singlestage-dialog-overlay { + @apply bg-popover + max-w-[calc(100%-2rem)] + ring-1 + ring-foreground/10 + rounded-xl + sm:max-w-sm; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/10; + } + } + + .singlestage-dialog-content { + @apply bg-popover + gap-4 + grid + p-4 + rounded-xl + text-popover-foreground + text-sm; + } + + .singlestage-dialog-close { + @apply absolute + right-2 + top-2; + } + + .singlestage-dialog-header { + @apply gap-2; + } + + .singlestage-dialog-footer { + @apply -mb-4 + -mx-4 + bg-muted/50 + border-t + p-4 + rounded-b-xl; + } + + .singlestage-dialog-title { + @apply font-medium + leading-none + text-base; + } + + .singlestage-dialog-description { + @apply text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } +} diff --git a/singlestage/src/components/dialog/style/base/sera/dialog.css b/singlestage/src/components/dialog/style/base/sera/dialog.css new file mode 100644 index 00000000..3cdb6d73 --- /dev/null +++ b/singlestage/src/components/dialog/style/base/sera/dialog.css @@ -0,0 +1,68 @@ +@layer components1 { + .singlestage-dialog-overlay { + @apply bg-popover + max-w-[calc(100%-2rem)] + ring-1 + ring-foreground/10 + rounded-none + shadow-md + sm:max-w-md; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-sm; + } + + &[open]::backdrop { + @apply bg-black/20; + } + } + + .singlestage-dialog-content { + @apply bg-popover + gap-6 + grid + p-6 + rounded-none + text-popover-foreground + text-sm; + } + + .singlestage-dialog-close { + @apply absolute + bg-secondary + right-5 + top-5; + } + + .singlestage-dialog-header { + @apply gap-2; + } + + .singlestage-dialog-footer { + @apply gap-2; + } + + .singlestage-dialog-title { + @apply font-semibold + leading-none + text-lg + tracking-wider + uppercase; + } + + .singlestage-dialog-description { + @apply leading-relaxed + mt-0.5 + text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } +} diff --git a/singlestage/src/components/dialog/style/base/vega/dialog.css b/singlestage/src/components/dialog/style/base/vega/dialog.css new file mode 100644 index 00000000..2f871834 --- /dev/null +++ b/singlestage/src/components/dialog/style/base/vega/dialog.css @@ -0,0 +1,61 @@ +@layer components1 { + .singlestage-dialog-overlay { + @apply bg-popover + max-w-[calc(100%-2rem)] + ring-1 + ring-foreground/10 + rounded-xl + sm:max-w-md; + + &::backdrop { + @apply supports-backdrop-filter:backdrop-blur-xs; + } + + &[open]::backdrop { + @apply bg-black/10; + } + } + + .singlestage-dialog-content { + @apply bg-popover + gap-6 + grid + p-6 + rounded-xl + text-popover-foreground + text-sm; + } + + .singlestage-dialog-close { + @apply absolute + right-4 + top-4; + } + + .singlestage-dialog-header { + @apply gap-2; + } + + .singlestage-dialog-footer { + @apply gap-2; + } + + .singlestage-dialog-title { + @apply font-medium + leading-none; + } + + .singlestage-dialog-description { + @apply text-muted-foreground + text-sm; + + a:not(.singlestage-button) { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } +} diff --git a/singlestage/src/components/dialog/style/dialog.css b/singlestage/src/components/dialog/style/dialog.css new file mode 100644 index 00000000..02246373 --- /dev/null +++ b/singlestage/src/components/dialog/style/dialog.css @@ -0,0 +1,105 @@ +@layer components { + @media (prefers-reduced-motion: no-preference) { + .singlestage-dialog-overlay, + .singlestage-alert-dialog-overlay { + transition: + opacity 0.1s cubic-bezier(0.4, 0, 0.2, 1), + scale 0.1s cubic-bezier(0.4, 0, 0.2, 1), + overlay 0.1s allow-discrete, + display 0.1s allow-discrete; + + opacity: 0; + scale: 0.95; + + ::webkit-scrollbar { + display: none; + } + + &::backdrop { + transition: + display 0.1s allow-discrete, + overlay 0.1s allow-discrete, + background-color 0.1s; + background-color: hsl(0 0 0 / 0); + } + + &[open] { + opacity: 1; + scale: 1; + } + } + + @starting-style { + .singlestage-dialog-overlay[open], + .singlestage-alert-dialog-overlay[open] { + opacity: 0; + scale: 0.95; + + &::backdrop { + background-color: hsl(0 0 0 / 0); + } + } + } + + /* Disable scrolling while popover is open */ + html:has(.singlestage-dialog-overlay[open]), + html:has(.singlestage-alert-dialog-overlay[open]) { + overflow: hidden; + } + } + + .singlestage-dialog-overlay { + @apply -translate-x-1/2 + -translate-y-1/2 + fixed + outline-none + w-full + z-50; + + left: calc(1 / 2 * 100vw); + top: calc(1 / 2 * 100%); + + ::backdrop { + @apply fixed + inset-0 + isolate + z-50; + } + + &[open] { + @apply -translate-x-1/2 + -translate-y-1/2 + fixed + outline-none + w-full + z-50; + + left: calc(1 / 2 * 100vw); + top: calc(1 / 2 * 100%); + } + } + + .singlestage-dialog-size-sm { + .singlestage-dialog-footer { + @apply grid + grid-cols-2; + } + } + + .singlestage-dialog-header { + @apply flex + flex-col; + } + + .singlestage-dialog-footer { + @apply flex + flex-col-reverse + gap-2 + sm:flex-row + sm:justify-end; + } + + /* .singlestage-dialog-title { */ + /* @apply font-heading; */ + /* } */ +} diff --git a/singlestage/src/components/dialog/title.rs b/singlestage/src/components/dialog/title.rs index 423e511f..2abff2a9 100644 --- a/singlestage/src/components/dialog/title.rs +++ b/singlestage/src/components/dialog/title.rs @@ -1,7 +1,7 @@ -use crate::DialogContext; +use crate::primitives::*; use leptos::prelude::*; -/// The title of the dialog. +/// A title describing dialog content. #[component] pub fn DialogTitle( children: Children, @@ -111,17 +111,11 @@ pub fn DialogTitle( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let dialog = expect_context::<DialogContext>(); - - let uuid: String = uuid::Uuid::new_v4().to_string(); - dialog.labeled_by.set(uuid.clone()); - 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() @@ -157,8 +151,13 @@ pub fn DialogTitle( }; view! { - <h2 {..global_attrs_1} {..global_attrs_2} id=uuid> + <DialogTitlePrimitive + class + + {..global_attrs_1} + {..global_attrs_2} + > {children()} - </h2> + </DialogTitlePrimitive> } } diff --git a/singlestage/src/components/dialog/trigger.rs b/singlestage/src/components/dialog/trigger.rs index aacbc350..f7538d3b 100644 --- a/singlestage/src/components/dialog/trigger.rs +++ b/singlestage/src/components/dialog/trigger.rs @@ -1,7 +1,7 @@ +use crate::primitives::*; use leptos::prelude::*; -/// Used to wrap other elements and trigger the dialog on click. -#[slot] -pub struct DialogTrigger { - children: ChildrenFn, +#[component] +pub fn DialogTrigger(children: Children) -> impl IntoView { + view! { <TriggerPrimitive>{children()}</TriggerPrimitive> } } diff --git a/singlestage/src/components/dropdown/checkbox.rs b/singlestage/src/components/dropdown/checkbox.rs new file mode 100644 index 00000000..6a1534a7 --- /dev/null +++ b/singlestage/src/components/dropdown/checkbox.rs @@ -0,0 +1,192 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +/// Renders a checkbox menu item. +#[component] +pub fn DropdownMenuCheckboxItem( + children: Children, + + #[prop(optional, into)] checked: Reactive<bool>, + + /// Controls whether the item appears disabled and is clickable. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Toggle whether clicking this item dismisses its parent menu + #[prop(optional, into, default = Reactive::new(true))] + dismiss: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Set the display variant of the item. + /// + /// Accepted values: "destructive" + #[prop(optional, into)] + variant: MaybeProp<String>, + + // LI ATTRIBRUTES + // + /// The current ordinal value of the item. + #[prop(optional, into)] + value: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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() + 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! { + <MenuItemPrimitive + primitive_type=MenuItemPrimitiveType::Checkbox + + checked + class + disabled + dismiss + id + inset + variant + value + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuItemPrimitive> + } +} diff --git a/singlestage/src/components/dropdown/content.rs b/singlestage/src/components/dropdown/content.rs index 47dd9051..ed462583 100644 --- a/singlestage/src/components/dropdown/content.rs +++ b/singlestage/src/components/dropdown/content.rs @@ -1,4 +1,4 @@ -use crate::DropdownMenuContext; +use crate::primitives::*; use leptos::prelude::*; /// The component that pops out when the dropdown menu is open. @@ -6,6 +6,19 @@ use leptos::prelude::*; pub fn DropdownMenuContent( children: Children, + /// Set how to align the popover + /// + /// Accepted values: "start" | "center" | "end" + /// Default is "start" + #[prop(optional, into)] + align: MaybeProp<String>, + /// Set which side the popover opens relative to the trigger + /// + /// Accepted values: "top" | "right" | "bottom" | "left" + /// Default is "bottom" + #[prop(optional, into)] + side: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -105,8 +118,6 @@ pub fn DropdownMenuContent( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let menu = expect_context::<DropdownMenuContext>(); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -136,7 +147,6 @@ pub fn DropdownMenuContent( part=move || part.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() @@ -144,22 +154,19 @@ pub fn DropdownMenuContent( }; view! { - <menu - class=move || { - format!("singlestage-dropdown-menu {}", class.get().unwrap_or_default()) - } - id={ - let menu_id = id.get().unwrap_or(uuid::Uuid::new_v4().to_string()); - menu.menu_id.set(menu_id.clone()); - menu_id - } - popover="auto" - role="menu" + <MenuContentPrimitive + primitive_type=MenuContentPrimitiveType::Main + + align + class + id + side + style {..global_attrs_1} {..global_attrs_2} > {children()} - </menu> + </MenuContentPrimitive> } } diff --git a/singlestage/src/components/dropdown/dropdown.css b/singlestage/src/components/dropdown/dropdown.css deleted file mode 100644 index 25f6895e..00000000 --- a/singlestage/src/components/dropdown/dropdown.css +++ /dev/null @@ -1,130 +0,0 @@ -@layer components { - /* Hacks to render properly until anchor support - https://developer.mozilla.org/en-US/docs/Web/CSS/anchor#browser_compatibility - */ - /* Webkit -- HAS TO BE FIRST */ - @media screen and (-webkit-min-device-pixel-ratio: 0) { - .singlestage-dropdown-menu { - @apply relative - bg-transparent; - position-area: bottom span-right; - inset: unset; - } - } - - /* Firefox */ - @-moz-document url-prefix() { - .singlestage-dropdown-menu { - @apply relative - bg-transparent; - inset: unset; - } - } - /* End hacks */ - - .singlestage-dropdown-menu { - @apply bg-popover - border - min-w-[8rem] - overflow-x-hidden - overflow-y-auto - p-1 - rounded-md - shadow-md - text-nowrap - text-popover-foreground - z-50; - - .singlestage-radio-group { - @appply block - gap-0; - } - } - - .singlestage-dropdown-menu-item { - > button { - @apply [&_svg:not([class*='size-'])]:size-4 - [&_svg:not([class*='text-'])]:text-muted-foreground - [&_svg]:pointer-events-none - [&_svg]:shrink-0 - content-center - cursor-default - data-[disabled]:opacity-50 - data-[disabled]:pointer-events-none - data-[inset]:pl-8 - data-[variant=destructive]:*:[svg]:!text-destructive - data-[variant=destructive]:focus:bg-destructive/10 - data-[variant=destructive]:focus:text-destructive - data-[variant=destructive]:text-destructive - focus:bg-accent - focus:text-accent-foreground - flex - gap-2 - h-full - hover:bg-accent - hover:text-accent-foreground - items-center - outline-hidden - px-2 - py-1.5 - relative - rounded-sm - select-none - text-sm - w-full; - } - - > .singlestage-label { - @apply h-full - w-full; - } - } - - .singlestage-dropdown-menu-label { - @apply data-[inset]:pl-8 - font-medium - px-2 - py-1.5 - text-sm; - } - - .singlestage-dropdown-menu-separator { - @apply -mx-1 - bg-border - h-px - my-1; - } - - .singlestage-dropdown-menu-shortcut { - @apply ml-auto - text-muted-foreground - text-xs - tracking-widest; - } - - .singlestage-dropdown-menu-sub-trigger { - @apply cursor-default - flex - focus:bg-accent - focus:text-accent-foreground - items-center - outline-hidden - px-2 - py-1.5 - rounded-sm - select-none - text-sm; - } - - .singlestage-dropdown-menu-sub-content { - @apply bg-popover - border - min-w-[8rem] - overflow-hidden - p-1 - rounded-md - shadow-lg - text-popover-foreground - z-50; - } -} diff --git a/singlestage/src/components/dropdown/group.rs b/singlestage/src/components/dropdown/group.rs index 83bd8827..4eefbed4 100644 --- a/singlestage/src/components/dropdown/group.rs +++ b/singlestage/src/components/dropdown/group.rs @@ -1,5 +1,5 @@ -use crate::DropdownMenuGroupContext; -use leptos::{context::Provider, prelude::*}; +use crate::primitives::*; +use leptos::prelude::*; /// Contains multiple items. #[component] @@ -108,10 +108,6 @@ pub fn DropdownMenuGroup( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let heading_id = RwSignal::new(String::new()); - - let context = DropdownMenuGroupContext { heading_id }; - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -151,17 +147,13 @@ pub fn DropdownMenuGroup( }; view! { - <div - aria-labelledby=move || heading_id.get() - class=move || { - format!("singlestage-dropdown-menu-group {}", class.get().unwrap_or_default()) - } - role="group" + <MenuGroupPrimitive + class {..global_attrs_1} {..global_attrs_2} > - <Provider value=context>{children()}</Provider> - </div> + {children()} + </MenuGroupPrimitive> } } diff --git a/singlestage/src/components/dropdown/item.rs b/singlestage/src/components/dropdown/item.rs index de47dc9d..a9d27880 100644 --- a/singlestage/src/components/dropdown/item.rs +++ b/singlestage/src/components/dropdown/item.rs @@ -1,14 +1,23 @@ -use crate::DropdownMenuContext; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// Contains a menu item. +/// Contains a dropdown menu item. #[component] pub fn DropdownMenuItem( children: Children, + /// This component will render without styling + #[prop(optional, into)] + as_child: MaybeProp<bool>, /// Controls whether the item appears disabled and is clickable. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, + /// Toggle whether clicking this item dismisses its parent menu + #[prop(optional, into, default = Reactive::new(true))] + dismiss: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, /// Set the display variant of the item. /// /// Accepted values: "destructive" @@ -123,13 +132,12 @@ pub fn DropdownMenuItem( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let menu = expect_context::<DropdownMenuContext>(); - 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() @@ -164,27 +172,22 @@ pub fn DropdownMenuItem( }; view! { - <li - class=move || { - format!("singlestage-dropdown-menu-item {}", class.get().unwrap_or_default()) - } - role="menuitem" - value=move || value.get() + <MenuItemPrimitive + primitive_type=MenuItemPrimitiveType::Item + + as_child + class + disabled + dismiss + id + inset + variant + value {..global_attrs_1} {..global_attrs_2} > - <button - aria-controls=move || menu.menu_id.get() - aria-haspopup="menu" - data-disabled=move || disabled.get().unwrap_or_default() - data-variant=move || variant.get().unwrap_or_default() - popovertarget=move || menu.menu_id.get() - popovertargetaction="toggle" - type="button" - > - {children()} - </button> - </li> + {children()} + </MenuItemPrimitive> } } diff --git a/singlestage/src/components/dropdown/label.rs b/singlestage/src/components/dropdown/label.rs index 8fbca94b..57f0f82c 100644 --- a/singlestage/src/components/dropdown/label.rs +++ b/singlestage/src/components/dropdown/label.rs @@ -1,11 +1,24 @@ -use crate::DropdownMenuGroupContext; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// Labels groups. #[component] pub fn DropdownMenuLabel( children: Children, + /// Whether the element renders as disabled + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + /// (For use in popover menus) + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Whether the element renders as invalid + #[prop(optional, into)] + invalid: Reactive<bool>, + /// The id of the labeled element if it's not a child + #[prop(optional, into)] + label_for: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -47,6 +60,9 @@ pub fn DropdownMenuLabel( /// Controls hidden status of the element. #[prop(optional, into)] hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, /// Toggle if the browser reacts to input events from this element. #[prop(optional, into)] inert: MaybeProp<bool>, @@ -84,6 +100,9 @@ pub fn DropdownMenuLabel( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp<String>, @@ -105,11 +124,6 @@ pub fn DropdownMenuLabel( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let group = expect_context::<DropdownMenuGroupContext>(); - - let uuid = uuid::Uuid::new_v4().to_string(); - group.heading_id.set(uuid.clone()); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -138,6 +152,7 @@ pub fn DropdownMenuLabel( 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() @@ -148,16 +163,19 @@ pub fn DropdownMenuLabel( }; view! { - <h6 - id=uuid - class=move || { - format!("singlestage-dropdown-menu-label {}", class.get().unwrap_or_default()) - } + <LabelPrimitive + primitive_type=LabelPrimitiveType::MenuLabel + class + disabled + id + inset + invalid + label_for {..global_attrs_1} {..global_attrs_2} > {children()} - </h6> + </LabelPrimitive> } } diff --git a/singlestage/src/components/dropdown/menu.rs b/singlestage/src/components/dropdown/menu.rs index 9637a81a..2399b42b 100644 --- a/singlestage/src/components/dropdown/menu.rs +++ b/singlestage/src/components/dropdown/menu.rs @@ -1,11 +1,23 @@ -use crate::DropdownMenuContext; +use crate::{Reactive, primitives::*}; use leptos::{context::Provider, prelude::*}; +#[derive(Clone)] +pub(crate) struct DropdownMenuContext {} + /// Contains all the parts of a dropdown menu. #[component] pub fn DropdownMenu( children: Children, + /// Set whether or not this popover should be modal meaning it can't be light dismissed + /// Use this along with the `open` signal for a manually managed popover + #[prop(optional, into, default = Reactive::new(false))] + modal: Reactive<bool>, + /// Reactive signal that can remotely control the open state of the popover **but is not + /// coupled to the actual open state of the popover** unless `modal` is set to `true` + #[prop(optional, into)] + open: Reactive<bool>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -114,8 +126,10 @@ pub fn DropdownMenu( let menu_id = RwSignal::new(String::new()); let trigger_id = RwSignal::new(String::new()); - let context = DropdownMenuContext { + let context = PopoverMenuContext { + modal, menu_id, + open, trigger_id, }; @@ -165,7 +179,9 @@ pub fn DropdownMenu( {..global_attrs_1} {..global_attrs_2} > - <Provider value=context>{children()}</Provider> + <Provider value=DropdownMenuContext {}> + <Provider value=context>{children()}</Provider> + </Provider> </div> } } diff --git a/singlestage/src/components/dropdown/mod.rs b/singlestage/src/components/dropdown/mod.rs index e419ae7a..dc44231d 100644 --- a/singlestage/src/components/dropdown/mod.rs +++ b/singlestage/src/components/dropdown/mod.rs @@ -1,33 +1,25 @@ +mod checkbox; mod content; mod group; mod item; mod label; mod menu; +mod radio; +mod radio_group; mod separator; mod shortcut; +mod sub; mod trigger; +pub use checkbox::*; pub use content::*; pub use group::*; pub use item::*; pub use label::*; pub use menu::*; +pub use radio::*; +pub use radio_group::*; pub use separator::*; pub use shortcut::*; +pub use sub::*; pub use trigger::*; - -use leptos::prelude::*; - -#[derive(Clone)] -pub struct DropdownMenuContext { - pub menu_id: RwSignal<String>, - pub trigger_id: RwSignal<String>, -} - -#[derive(Clone)] -pub struct DropdownTriggerContext {} - -#[derive(Clone)] -pub struct DropdownMenuGroupContext { - heading_id: RwSignal<String>, -} diff --git a/singlestage/src/components/dropdown/radio.rs b/singlestage/src/components/dropdown/radio.rs new file mode 100644 index 00000000..f1e8e7b9 --- /dev/null +++ b/singlestage/src/components/dropdown/radio.rs @@ -0,0 +1,192 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +/// Contains a radio menu item. +#[component] +pub fn DropdownMenuRadioItem( + children: Children, + + #[prop(optional, into)] checked: Reactive<bool>, + + /// Controls whether the item appears disabled and is clickable. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Toggle whether clicking this item dismisses its parent menu + #[prop(optional, into, default = Reactive::new(true))] + dismiss: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Set the display variant of the item. + /// + /// Accepted values: "destructive" + #[prop(optional, into)] + variant: MaybeProp<String>, + + // LI ATTRIBRUTES + // + /// The current ordinal value of the item. + #[prop(optional, into)] + value: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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() + 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! { + <MenuItemPrimitive + primitive_type=MenuItemPrimitiveType::Radio + + checked + class + disabled + dismiss + id + inset + variant + value + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuItemPrimitive> + } +} diff --git a/singlestage/src/components/dropdown/radio_group.rs b/singlestage/src/components/dropdown/radio_group.rs new file mode 100644 index 00000000..2836755f --- /dev/null +++ b/singlestage/src/components/dropdown/radio_group.rs @@ -0,0 +1,191 @@ +use crate::{RadioGroup, Reactive}; +use leptos::prelude::*; + +/// Contains all the parts of a radio group +#[component] +pub fn DropdownMenuRadioGroup( + children: Children, + + /// Set or update the default value. + #[prop(optional, into)] + default: MaybeProp<String>, + /// Set or update the invalid state of the radio group. + #[prop(optional, into)] + invalid: Reactive<bool>, + /// Reactive signal coupled to the current selected value of the radio group. + #[prop(optional, into)] + value: Reactive<String>, + + // FIELDSET ATTRIBUTES + // + /// Toggle whether or not the input is disabled. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Associate this element with a form element that may not be its parent by its `id`. + #[prop(optional, into)] + form: MaybeProp<String>, + /// Name of this element. Submitted with the form as part of a name/value pair. + #[prop(optional, into)] + name: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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() + /> + }; + + let fieldset_attrs = view! { <{..} disabled=move || disabled.get() form=move || form.get() name=move || name.get() /> }; + + view! { + <RadioGroup + default + invalid + value + + {..global_attrs_1} + {..global_attrs_2} + {..fieldset_attrs} + > + {children()} + </RadioGroup> + } +} diff --git a/singlestage/src/components/dropdown/separator.rs b/singlestage/src/components/dropdown/separator.rs index 5ee11c50..3ee4fb68 100644 --- a/singlestage/src/components/dropdown/separator.rs +++ b/singlestage/src/components/dropdown/separator.rs @@ -1,8 +1,16 @@ +use crate::Separator; use leptos::prelude::*; -/// Visually separates groups of menu items. +/// The separator. #[component] pub fn DropdownMenuSeparator( + #[prop(optional)] children: Option<Children>, + + /// Specify the orientation of the separator + /// Accepted values: `"vertical"` | `"horizontal"` (default) + #[prop(optional, into)] + orientation: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -84,6 +92,9 @@ pub fn DropdownMenuSeparator( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp<String>, @@ -110,6 +121,7 @@ pub fn DropdownMenuSeparator( 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() @@ -134,6 +146,7 @@ pub fn DropdownMenuSeparator( 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() @@ -143,15 +156,27 @@ pub fn DropdownMenuSeparator( /> }; - view! { - <hr - class=move || { - format!("singlestage-dropdown-menu-separator {}", class.get().unwrap_or_default()) - } - role="separator" + if let Some(children) = children { + view! { + <Separator + orientation - {..global_attrs_1} - {..global_attrs_2} - /> + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </Separator> + } + .into_any() + } else { + view! { + <Separator + orientation + + {..global_attrs_1} + {..global_attrs_2} + /> + } + .into_any() } } diff --git a/singlestage/src/components/dropdown/shortcut.rs b/singlestage/src/components/dropdown/shortcut.rs index dc1a26c4..f33dff92 100644 --- a/singlestage/src/components/dropdown/shortcut.rs +++ b/singlestage/src/components/dropdown/shortcut.rs @@ -1,3 +1,4 @@ +use crate::primitives::*; use leptos::prelude::*; /// Displays next to the item content and indicates keyboard shortcuts. @@ -150,15 +151,13 @@ pub fn DropdownMenuShortcut( }; view! { - <span - class=move || { - format!("singlestage-dropdown-menu-shortcut {}", class.get().unwrap_or_default()) - } + <MenuShortcutPrimitive + class {..global_attrs_1} {..global_attrs_2} > {children()} - </span> + </MenuShortcutPrimitive> } } diff --git a/singlestage/src/components/dropdown/style/base/luma/dropdown.css b/singlestage/src/components/dropdown/style/base/luma/dropdown.css new file mode 100644 index 00000000..51aa35ba --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/luma/dropdown.css @@ -0,0 +1,121 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply bg-popover + min-w-48 + p-1.5 + ring-1 + ring-foreground/5 + rounded-3xl + shadow-lg + text-popover-foreground; + } + + .singlestage-dropdown-menu-item { + > * { + @apply font-medium + gap-2.5 + px-3 + py-2 + rounded-2xl + text-sm; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-9.5; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-dropdown-menu-item-variant-destructive { + > * { + @apply text-destructive; + + &:focus, + &:hover { + @apply bg-destructive/10 + text-destructive; + } + + svg { + @apply text-destructive; + } + } + } + + .singlestage-dropdown-menu-label { + @apply px-3 + py-2.5 + text-muted-foreground + text-xs; + + &.singlestage-dropdown-menu-inset { + @apply pl-9.5; + } + } + + .singlestage-dropdown-menu-separator { + @apply -mx-1.5 + bg-border/50 + h-px + my-1.5; + } + + .singlestage-dropdown-menu-shortcut { + @apply ml-auto + text-muted-foreground + text-xs + tracking-widest; + } + + .singlestage-dropdown-menu-sub-trigger { + > * { + @apply font-medium + px-3 + py-2 + rounded-2xl + text-sm; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-9.5; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + > * { + @apply bg-accent + text-accent-foreground; + } + } + } + + .singlestage-dropdown-menu-sub-content { + @apply bg-popover + min-w-36 + p-1.5 + ring-1 + ring-foreground/5 + rounded-3xl + shadow-lg + text-popover-foreground; + } +} diff --git a/singlestage/src/components/dropdown/style/base/luma/dropdown_dark.css b/singlestage/src/components/dropdown/style/base/luma/dropdown_dark.css new file mode 100644 index 00000000..09c87057 --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/luma/dropdown_dark.css @@ -0,0 +1,18 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply ring-foreground/10; + } + + .singlestage-dropdown-menu-item-variant-destructive { + > * { + &:focus, + &:hover { + @apply focus:bg-destructive/20; + } + } + } + + .singlestage-dropdown-menu-sub-content { + @apply ring-foreground/10; + } +} diff --git a/singlestage/src/components/dropdown/style/base/lyra/dropdown.css b/singlestage/src/components/dropdown/style/base/lyra/dropdown.css new file mode 100644 index 00000000..539a8664 --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/lyra/dropdown.css @@ -0,0 +1,117 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply bg-popover + min-w-32 + ring-1 + ring-foreground/10 + rounded-none + shadow-md + text-popover-foreground; + } + + .singlestage-dropdown-menu-item { + > * { + @apply gap-2 + px-2 + py-2 + rounded-none + text-xs; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-7; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-dropdown-menu-item-variant-destructive { + > * { + @apply text-destructive; + + &:focus, + &:hover { + @apply bg-destructive/10 + text-destructive; + } + + svg { + @apply text-destructive; + } + } + } + + .singlestage-dropdown-menu-label { + @apply px-2 + py-2 + text-muted-foreground + text-xs; + + &.singlestage-dropdown-menu-inset { + @apply pl-7; + } + } + + .singlestage-dropdown-menu-separator { + @apply -mx-1 + bg-border + h-px; + } + + .singlestage-dropdown-menu-shortcut { + @apply ml-auto + text-muted-foreground + text-xs + tracking-widest; + } + + .singlestage-dropdown-menu-sub-trigger { + > * { + @apply gap-2 + px-2 + py-2 + rounded-none + text-xs; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-7; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + > * { + @apply bg-accent + text-accent-foreground; + } + } + } + + .singlestage-dropdown-menu-sub-content { + @apply bg-popover + min-w-[96px] + ring-1 + ring-foreground/10 + rounded-none + shadow-lg + text-popover-foreground; + } +} diff --git a/singlestage/src/components/dropdown/style/base/lyra/dropdown_dark.css b/singlestage/src/components/dropdown/style/base/lyra/dropdown_dark.css new file mode 100644 index 00000000..6da6488e --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/lyra/dropdown_dark.css @@ -0,0 +1,10 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > * { + &:focus, + &:hover { + @apply bg-destructive/20; + } + } + } +} diff --git a/singlestage/src/components/dropdown/style/base/maia/dropdown.css b/singlestage/src/components/dropdown/style/base/maia/dropdown.css new file mode 100644 index 00000000..93e1f1dc --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/maia/dropdown.css @@ -0,0 +1,120 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply bg-popover + min-w-48 + p-1 + ring-1 + ring-foreground/5 + rounded-2xl + shadow-2xl + text-popover-foreground; + } + + .singlestage-dropdown-menu-item { + > * { + @apply gap-2.5 + rounded-xl + px-3 + py-2 + text-sm; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-9.5; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-dropdown-menu-item-variant-destructive { + > * { + @apply text-destructive; + + &:focus, + &:hover { + @apply bg-destructive/10 + text-destructive; + } + + svg { + @apply text-destructive; + } + } + } + + .singlestage-dropdown-menu-label { + @apply px-3 + py-2.5 + text-muted-foreground + text-xs; + + &.singlestage-dropdown-menu-inset { + @apply pl-9.5; + } + } + + .singlestage-dropdown-menu-separator { + @apply -mx-1 + bg-border/50 + h-px + my-1; + } + + .singlestage-dropdown-menu-shortcut { + @apply ml-auto + text-muted-foreground + text-xs + tracking-widest; + } + + .singlestage-dropdown-menu-sub-trigger { + > * { + @apply gap-2 + px-3 + py-2 + rounded-xl + text-sm; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-9.5; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + > * { + @apply bg-accent + text-accent-foreground; + } + } + } + + .singlestage-dropdown-menu-sub-content { + @apply bg-popover + min-w-36 + p-1 + ring-1 + ring-foreground/5 + rounded-2xl + shadow-2xl + text-popover-foreground; + } +} diff --git a/singlestage/src/components/dropdown/style/base/maia/dropdown_dark.css b/singlestage/src/components/dropdown/style/base/maia/dropdown_dark.css new file mode 100644 index 00000000..bb091f7a --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/maia/dropdown_dark.css @@ -0,0 +1,14 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply ring-foreground/10; + } + + .singlestage-dropdown-menu-item-variant-destructive { + > * { + &:focus, + &:hover { + @apply bg-destructive/20; + } + } + } +} diff --git a/singlestage/src/components/dropdown/style/base/mira/dropdown.css b/singlestage/src/components/dropdown/style/base/mira/dropdown.css new file mode 100644 index 00000000..2d0361eb --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/mira/dropdown.css @@ -0,0 +1,122 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply bg-popover + min-w-32 + p-1 + ring-1 + ring-foreground/10 + rounded-lg + shadow-md + text-popover-foreground; + } + + .singlestage-dropdown-menu-item { + > * { + @apply gap-2 + min-h-7 + px-2 + py-1 + rounded-md + text-xs/relaxed; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-7.5; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + } + + .singlestage-dropdown-menu-item-variant-destructive { + > * { + @apply text-destructive; + + &:focus, + &:hover { + @apply bg-destructive/10 + text-destructive; + } + + svg { + @apply text-destructive; + } + } + } + + .singlestage-dropdown-menu-label { + @apply px-2 + py-1.5 + text-muted-foreground + text-xs; + + &.singlestage-dropdown-menu-inset { + @apply pl-7.5; + } + } + + .singlestage-dropdown-menu-separator { + @apply -mx-1 + bg-border/50 + h-px + my-1; + } + + .singlestage-dropdown-menu-shortcut { + @apply ml-auto + text-[0.625rem] + text-muted-foreground + tracking-widest; + } + + .singlestage-dropdown-menu-sub-trigger { + > * { + @apply gap-2 + min-h-7 + px-2 + py-1 + rounded-md + text-xs; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-7.5; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + > * { + @apply bg-accent + text-accent-foreground; + } + } + } + + .singlestage-dropdown-menu-sub-content { + @apply bg-popover + min-w-32 + p-1 + ring-1 + ring-foreground/10 + rounded-lg + shadow-md + text-popover-foreground; + } +} diff --git a/singlestage/src/components/dropdown/style/base/mira/dropdown_dark.css b/singlestage/src/components/dropdown/style/base/mira/dropdown_dark.css new file mode 100644 index 00000000..6da6488e --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/mira/dropdown_dark.css @@ -0,0 +1,10 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > * { + &:focus, + &:hover { + @apply bg-destructive/20; + } + } + } +} diff --git a/singlestage/src/components/dropdown/style/base/nova/dropdown.css b/singlestage/src/components/dropdown/style/base/nova/dropdown.css new file mode 100644 index 00000000..e4110e8e --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/nova/dropdown.css @@ -0,0 +1,121 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply bg-popover + min-w-32 + p-1 + ring-1 + ring-foreground/10 + rounded-lg + shadow-md + text-popover-foreground; + } + + .singlestage-dropdown-menu-item { + > * { + @apply gap-1.5 + px-1.5 + py-1 + rounded-md + text-sm; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-7; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-dropdown-menu-item-variant-destructive { + > * { + @apply text-destructive; + + &:focus, + &:hover { + @apply bg-destructive/10 + text-destructive; + } + + svg { + @apply text-destructive; + } + } + } + + .singlestage-dropdown-menu-label { + @apply font-medium + px-1.5 + py-1 + text-muted-foreground + text-xs; + + &.singlestage-dropdown-menu-inset { + @apply pl-7; + } + } + + .singlestage-dropdown-menu-separator { + @apply -mx-1 + bg-border + h-px + my-1; + } + + .singlestage-dropdown-menu-shortcut { + @apply ml-auto + text-muted-foreground + text-xs + tracking-widest; + } + + .singlestage-dropdown-menu-sub-trigger { + > * { + @apply gap-1.5 + px-1.5 + py-1 + rounded-md + text-sm; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-7; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + > * { + @apply bg-accent + text-accent-foreground; + } + } + } + + .singlestage-dropdown-menu-sub-content { + @apply bg-popover + min-w-[96px] + p-1 + ring-1 + ring-foreground/10 + rounded-lg + shadow-lg + text-popover-foreground; + } +} diff --git a/singlestage/src/components/dropdown/style/base/nova/dropdown_dark.css b/singlestage/src/components/dropdown/style/base/nova/dropdown_dark.css new file mode 100644 index 00000000..6da6488e --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/nova/dropdown_dark.css @@ -0,0 +1,10 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > * { + &:focus, + &:hover { + @apply bg-destructive/20; + } + } + } +} diff --git a/singlestage/src/components/dropdown/style/base/sera/dropdown.css b/singlestage/src/components/dropdown/style/base/sera/dropdown.css new file mode 100644 index 00000000..b774f7bb --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/sera/dropdown.css @@ -0,0 +1,129 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply bg-popover + min-w-48 + p-1.5 + ring-1 + ring-foreground/10 + rounded-none + shadow-md + text-popover-foreground; + } + + .singlestage-dropdown-menu-item { + > * { + @apply font-medium + gap-2.5 + px-3 + py-2 + rounded-none + text-xs + tracking-wider + uppercase; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-9.5; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + } + + .singlestage-dropdown-menu-item-variant-destructive { + > * { + @apply text-destructive; + + &:focus, + &:hover { + @apply bg-destructive/10 + text-destructive; + } + + svg { + @apply text-destructive; + } + } + } + + .singlestage-dropdown-menu-label { + @apply font-semibold + px-3 + py-2 + text-muted-foreground + text-xs + tracking-wider + uppercase; + + &.singlestage-dropdown-menu-inset { + @apply pl-9.5; + } + } + + .singlestage-dropdown-menu-separator { + @apply -mx-1.5 + bg-border/50 + h-px + my-1.5; + } + + .singlestage-dropdown-menu-shortcut { + @apply ml-auto + text-muted-foreground + text-xs + tracking-widest; + } + + .singlestage-dropdown-menu-sub-trigger { + > * { + @apply font-medium + gap-2 + px-3 + py-2 + rounded-none + text-xs + tracking-wider + uppercase; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-9.5; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + > * { + @apply bg-accent + text-accent-foreground; + } + } + } + + .singlestage-dropdown-menu-sub-content { + @apply bg-popover + min-w-36 + p-1.5 + ring-1 + ring-foreground/10 + rounded-none + shadow-md + text-popover-foreground; + } +} diff --git a/singlestage/src/components/dropdown/style/base/sera/dropdown_dark.css b/singlestage/src/components/dropdown/style/base/sera/dropdown_dark.css new file mode 100644 index 00000000..6da6488e --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/sera/dropdown_dark.css @@ -0,0 +1,10 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > * { + &:focus, + &:hover { + @apply bg-destructive/20; + } + } + } +} diff --git a/singlestage/src/components/dropdown/style/base/vega/dropdown.css b/singlestage/src/components/dropdown/style/base/vega/dropdown.css new file mode 100644 index 00000000..d9a26a66 --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/vega/dropdown.css @@ -0,0 +1,121 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply bg-popover + min-w-32 + p-1 + ring-1 + ring-foreground/10 + rounded-md + shadow-md + text-popover-foreground; + } + + .singlestage-dropdown-menu-item { + > * { + @apply gap-2 + px-2 + py-1.5 + rounded-sm + text-sm; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-8; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + } + + .singlestage-dropdown-menu-item-variant-destructive { + > * { + @apply text-destructive; + + &:focus, + &:hover { + @apply bg-destructive/10 + text-destructive; + } + + svg { + @apply text-destructive; + } + } + } + + .singlestage-dropdown-menu-label { + @apply font-medium + px-2 + py-1.5 + text-muted-foreground + text-xs; + + &.singlestage-dropdown-menu-inset { + @apply pl-8; + } + } + + .singlestage-dropdown-menu-separator { + @apply -mx-1 + bg-border + h-px + my-1; + } + + .singlestage-dropdown-menu-shortcut { + @apply ml-auto + text-muted-foreground + text-xs + tracking-widest; + } + + .singlestage-dropdown-menu-sub-trigger { + > * { + @apply gap-2 + px-2 + py-1.5 + rounded-sm + text-sm; + + &:focus, + &:hover { + @apply bg-accent + text-accent-foreground; + } + + &.singlestage-dropdown-menu-inset { + @apply pl-8; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + > * { + @apply bg-accent + text-accent-foreground; + } + } + } + + .singlestage-dropdown-menu-sub-content { + @apply bg-popover + min-w-[96px] + p-1 + ring-1 + ring-foreground/10 + rounded-md + shadow-lg + text-popover-foreground; + } +} diff --git a/singlestage/src/components/dropdown/style/base/vega/dropdown_dark.css b/singlestage/src/components/dropdown/style/base/vega/dropdown_dark.css new file mode 100644 index 00000000..6da6488e --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/vega/dropdown_dark.css @@ -0,0 +1,10 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > * { + &:focus, + &:hover { + @apply bg-destructive/20; + } + } + } +} diff --git a/singlestage/src/components/dropdown/style/dropdown.css b/singlestage/src/components/dropdown/style/dropdown.css new file mode 100644 index 00000000..32cbfd9a --- /dev/null +++ b/singlestage/src/components/dropdown/style/dropdown.css @@ -0,0 +1,117 @@ +@layer components { + .singlestage-dropdown-menu { + @apply isolate + outline-none + z-50; + } + + .singlestage-dropdown-menu-content { + @apply /* data-closed:overflow-hidden */ + flex + flex-col + /* max-h-(--available-height) */ + /* origin-(--transform-origin) */ + outline-none + overflow-x-hidden + overflow-y-auto + /* w-(--anchor-width) */ + z-50; + } + + .singlestage-dropdown-menu-item { + > * { + @apply content-center + cursor-default + flex + h-full + items-center + outline-hidden + relative + select-none + text-start + text-nowrap + w-full; + + &:disabled, + &[aria-disabled="true"] { + @apply opacity-50 + pointer-events-none; + } + + svg { + @apply pointer-events-none + shrink-0; + } + + .singlestage-dropdown-menu-checkbox-item, + .singlestage-dropdown-menu-radio-item { + @apply ml-auto; + } + } + } + + .singlestage-dropdown-menu-sub-trigger { + > * { + @apply content-center + cursor-default + flex + h-full + items-center + outline-hidden + relative + select-none + text-nowrap + w-full; + + svg { + @apply pointer-events-none + shrink-0; + } + } + + &:has(+ .singlestage-popover:popover-open), + &[aria-expanded="true"] { + > * { + @apply bg-accent + text-accent-foreground; + } + } + } + + .singlestage-dropdown-menu-sub-content { + @apply w-auto; + } + + .singlestage-dropdown-menu-checkbox-item, + .singlestage-dropdown-menu-radio-item { + @apply appearance-none + cursor-default + /* flex */ + /* items-center */ + outline-hidden + /* relative */ + select-none + shrink-0 + size-4 + transition-shadow; + + &:disabled, + &[aria-disabled="true"] { + @apply opacity-50 + pointer-events-none; + } + + &:checked:after { + @apply bg-accent-foreground + block + content-[''] + mask-[image:var(--check-icon)] + mask-center + mask-no-repeat + mask-size-[calc(4*var(--spacing))] + pointer-events-none + shrink-0 + size-4; + } + } +} diff --git a/singlestage/src/components/dropdown/sub/content.rs b/singlestage/src/components/dropdown/sub/content.rs new file mode 100644 index 00000000..1edc1cef --- /dev/null +++ b/singlestage/src/components/dropdown/sub/content.rs @@ -0,0 +1,172 @@ +use crate::primitives::*; +use leptos::prelude::*; + +/// The component that pops out when the sub trigger is triggered. +#[component] +pub fn DropdownMenuSubContent( + children: Children, + + /// Set how to align the popover + /// + /// Accepted values: "start" | "center" | "end" + /// Default is "start" + #[prop(optional, into)] + align: MaybeProp<String>, + /// Set which side the popover opens relative to the trigger + /// + /// Accepted values: "top" | "right" | "bottom" | "left" + /// Default is "bottom" + #[prop(optional, into)] + side: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + <MenuContentPrimitive + primitive_type=MenuContentPrimitiveType::Sub + + align + class + id + side + style + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuContentPrimitive> + } +} diff --git a/singlestage/src/components/dropdown/sub/menu.rs b/singlestage/src/components/dropdown/sub/menu.rs new file mode 100644 index 00000000..f2a1f022 --- /dev/null +++ b/singlestage/src/components/dropdown/sub/menu.rs @@ -0,0 +1,169 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +/// Contains all the parts of a sub menu. +#[component] +pub fn DropdownMenuSub( + children: Children, + + /// Reactive signal that can remotely control the open state of the popover **but is not + /// coupled to the actual open state of the popover** + #[prop(optional, into)] + open: Reactive<bool>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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! { + <MenuSubPrimitive + open + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuSubPrimitive> + } +} diff --git a/singlestage/src/components/dropdown/sub/mod.rs b/singlestage/src/components/dropdown/sub/mod.rs new file mode 100644 index 00000000..ee49e468 --- /dev/null +++ b/singlestage/src/components/dropdown/sub/mod.rs @@ -0,0 +1,7 @@ +mod content; +mod menu; +mod trigger; + +pub use content::*; +pub use menu::*; +pub use trigger::*; diff --git a/singlestage/src/components/dropdown/sub/trigger.rs b/singlestage/src/components/dropdown/sub/trigger.rs new file mode 100644 index 00000000..5d8a9a40 --- /dev/null +++ b/singlestage/src/components/dropdown/sub/trigger.rs @@ -0,0 +1,185 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +#[component] +pub fn DropdownMenuSubTrigger( + children: Children, + + /// Controls whether the item appears disabled and is clickable. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Set the display variant of the item. + /// + /// Accepted values: "destructive" + #[prop(optional, into)] + variant: MaybeProp<String>, + + // LI ATTRIBRUTES + // + /// The current ordinal value of the item. + #[prop(optional, into)] + value: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + class=move || class.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() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + value=move || value.get() + /> + }; + + view! { + <MenuItemPrimitive + primitive_type=MenuItemPrimitiveType::SubTrigger + + class + disabled + id + inset + value + variant + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </MenuItemPrimitive> + } +} diff --git a/singlestage/src/components/dropdown/trigger.rs b/singlestage/src/components/dropdown/trigger.rs index 864a1148..d52b3887 100644 --- a/singlestage/src/components/dropdown/trigger.rs +++ b/singlestage/src/components/dropdown/trigger.rs @@ -1,10 +1,7 @@ -use crate::DropdownTriggerContext; -use leptos::{context::Provider, prelude::*}; +use crate::primitives::*; +use leptos::prelude::*; -/// Provides context to a `Button` that triggers a dropdown menu. #[component] pub fn DropdownMenuTrigger(children: Children) -> impl IntoView { - let context = DropdownTriggerContext {}; - - view! { <Provider value=context>{children()}</Provider> } + view! { <TriggerPrimitive>{children()}</TriggerPrimitive> } } diff --git a/singlestage/src/components/empty/empty.css b/singlestage/src/components/empty/empty.css deleted file mode 100644 index efecb87e..00000000 --- a/singlestage/src/components/empty/empty.css +++ /dev/null @@ -1,74 +0,0 @@ -@layer components { - .singlestage-empty { - @apply border-dashed - flex - flex-1 - flex-col - gap-6 - items-center - justify-center - md:p-12 - min-w-0 - p-6 - rounded-lg - text-balance - text-center; - } - - .singlestage-empty-header { - @apply flex - flex-col - gap-2 - items-center - max-w-sm - text-center; - } - - .singlestage-empty-media { - @apply bg-transparent; - } - - .singlestage-empty-media-icon { - @apply bg-muted - flex - items-center - justify-center - rounded-lg - shrink-0 - size-10 - text-foreground - [&_svg:not([class*='size-'])]:size-6; - } - - .singlestage-empty-title { - @apply font-medium - text-lg - tracking-tight; - } - - .singlestage-empty-description { - @apply text-muted-foreground - text-sm/relaxed; - - > a { - @apply underline - underline-offset-4; - } - - > a:hover { - @apply text-primary; - } - } - - .singlestage-empty-content { - @apply flex - flex-col - gap-4 - items-center - max-w-sm - min-w-0 - text-balance - text-sm - w-full; - } -} diff --git a/singlestage/src/components/empty/media.rs b/singlestage/src/components/empty/media.rs index ed6b6656..dd319892 100644 --- a/singlestage/src/components/empty/media.rs +++ b/singlestage/src/components/empty/media.rs @@ -155,10 +155,10 @@ pub fn EmptyMedia( <div class=move || { format!( - "{} {}", + "singlestage-empty-media {} {}", match variant.get().unwrap_or_default().as_str() { "icon" => "singlestage-empty-media-icon", - _ => "singlestage-empty-media", + _ => "singlestage-empty-media-default", }, class.get().unwrap_or_default(), ) diff --git a/singlestage/src/components/empty/style/base/luma/empty.css b/singlestage/src/components/empty/style/base/luma/empty.css new file mode 100644 index 00000000..4b581c4e --- /dev/null +++ b/singlestage/src/components/empty/style/base/luma/empty.css @@ -0,0 +1,50 @@ +@layer components1 { + .singlestage-empty { + @apply border-dashed + gap-4 + p-12 + rounded-2xl; + } + + .singlestage-empty-header { + @apply gap-2; + } + + .singlestage-empty-media { + @apply mb-2; + } + + .singlestage-empty-media-default { + @apply bg-transparent; + } + + .singlestage-empty-media-icon { + @apply bg-muted + flex + items-center + justify-center + rounded-xl + shrink-0 + size-10 + text-foreground; + + svg:not([class*="size-"]) { + @apply size-5; + } + } + + .singlestage-empty-title { + @apply font-medium + text-lg + tracking-tight; + } + + .singlestage-empty-description { + @apply text-sm/relaxed; + } + + .singlestage-empty-content { + @apply gap-4 + text-sm; + } +} diff --git a/singlestage/src/components/empty/style/base/lyra/empty.css b/singlestage/src/components/empty/style/base/lyra/empty.css new file mode 100644 index 00000000..68b00590 --- /dev/null +++ b/singlestage/src/components/empty/style/base/lyra/empty.css @@ -0,0 +1,49 @@ +@layer components1 { + .singlestage-empty { + @apply border-dashed + gap-4 + p-6 + rounded-none; + } + + .singlestage-empty-header { + @apply gap-2; + } + + .singlestage-empty-media { + @apply mb-2; + } + + .singlestage-empty-media-default { + @apply bg-transparent; + } + + .singlestage-empty-media-icon { + @apply bg-muted + flex + items-center + justify-center + rounded-none + shrink-0 + size-8 + text-foreground; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-empty-title { + @apply font-medium + text-sm; + } + + .singlestage-empty-description { + @apply text-xs/relaxed; + } + + .singlestage-empty-content { + @apply gap-2.5 + text-xs; + } +} diff --git a/singlestage/src/components/empty/style/base/maia/empty.css b/singlestage/src/components/empty/style/base/maia/empty.css new file mode 100644 index 00000000..ce94acc9 --- /dev/null +++ b/singlestage/src/components/empty/style/base/maia/empty.css @@ -0,0 +1,50 @@ +@layer components1 { + .singlestage-empty { + @apply border-dashed + gap-4 + p-12 + rounded-lg; + } + + .singlestage-empty-header { + @apply gap-2; + } + + .singlestage-empty-media { + @apply mb-2; + } + + .singlestage-empty-media-default { + @apply bg-transparent; + } + + .singlestage-empty-media-icon { + @apply bg-muted + flex + items-center + justify-center + rounded-lg + shrink-0 + size-10 + text-foreground; + + svg:not([class*="size-"]) { + @apply size-6; + } + } + + .singlestage-empty-title { + @apply font-medium + text-lg + tracking-tight; + } + + .singlestage-empty-description { + @apply text-sm/relaxed; + } + + .singlestage-empty-content { + @apply gap-4 + text-sm; + } +} diff --git a/singlestage/src/components/empty/style/base/mira/empty.css b/singlestage/src/components/empty/style/base/mira/empty.css new file mode 100644 index 00000000..c69da421 --- /dev/null +++ b/singlestage/src/components/empty/style/base/mira/empty.css @@ -0,0 +1,50 @@ +@layer components1 { + .singlestage-empty { + @apply border-dashed + gap-4 + p-6 + rounded-xl; + } + + .singlestage-empty-header { + @apply gap-1; + } + + .singlestage-empty-media { + @apply mb-2; + } + + .singlestage-empty-media-default { + @apply bg-transparent; + } + + .singlestage-empty-media-icon { + @apply bg-muted + flex + items-center + justify-center + rounded-md + shrink-0 + size-8 + text-foreground; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-empty-title { + @apply font-medium + text-sm + tracking-tight; + } + + .singlestage-empty-description { + @apply text-xs/relaxed; + } + + .singlestage-empty-content { + @apply gap-2 + text-xs/relaxed; + } +} diff --git a/singlestage/src/components/empty/style/base/nova/empty.css b/singlestage/src/components/empty/style/base/nova/empty.css new file mode 100644 index 00000000..970bdfaf --- /dev/null +++ b/singlestage/src/components/empty/style/base/nova/empty.css @@ -0,0 +1,50 @@ +@layer components1 { + .singlestage-empty { + @apply border-dashed + gap-4 + p-6 + rounded-xl; + } + + .singlestage-empty-header { + @apply gap-2; + } + + .singlestage-empty-media { + @apply mb-2; + } + + .singlestage-empty-media-default { + @apply bg-transparent; + } + + .singlestage-empty-media-icon { + @apply bg-muted + flex + items-center + justify-center + rounded-lg + shrink-0 + size-8 + text-foreground; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-empty-title { + @apply font-medium + text-sm + tracking-tight; + } + + .singlestage-empty-description { + @apply text-sm/relaxed; + } + + .singlestage-empty-content { + @apply gap-2.5 + text-sm; + } +} diff --git a/singlestage/src/components/empty/style/base/sera/empty.css b/singlestage/src/components/empty/style/base/sera/empty.css new file mode 100644 index 00000000..5280ed0c --- /dev/null +++ b/singlestage/src/components/empty/style/base/sera/empty.css @@ -0,0 +1,51 @@ +@layer components1 { + .singlestage-empty { + @apply border-dashed + gap-4 + p-12; + } + + .singlestage-empty-header { + @apply gap-2; + } + + .singlestage-empty-media { + @apply mb-2; + } + + .singlestage-empty-media-default { + @apply bg-transparent; + } + + .singlestage-empty-media-icon { + @apply bg-muted + flex + items-center + justify-center + rounded-none + shrink-0 + size-10 + text-foreground; + + svg:not([class*="size-"]) { + @apply size-5; + } + } + + .singlestage-empty-title { + @apply font-semibold + text-lg + tracking-wider + uppercase; + } + + .singlestage-empty-description { + @apply mt-0.5 + text-sm/relaxed; + } + + .singlestage-empty-content { + @apply gap-4 + text-sm; + } +} diff --git a/singlestage/src/components/empty/style/base/vega/empty.css b/singlestage/src/components/empty/style/base/vega/empty.css new file mode 100644 index 00000000..ce94acc9 --- /dev/null +++ b/singlestage/src/components/empty/style/base/vega/empty.css @@ -0,0 +1,50 @@ +@layer components1 { + .singlestage-empty { + @apply border-dashed + gap-4 + p-12 + rounded-lg; + } + + .singlestage-empty-header { + @apply gap-2; + } + + .singlestage-empty-media { + @apply mb-2; + } + + .singlestage-empty-media-default { + @apply bg-transparent; + } + + .singlestage-empty-media-icon { + @apply bg-muted + flex + items-center + justify-center + rounded-lg + shrink-0 + size-10 + text-foreground; + + svg:not([class*="size-"]) { + @apply size-6; + } + } + + .singlestage-empty-title { + @apply font-medium + text-lg + tracking-tight; + } + + .singlestage-empty-description { + @apply text-sm/relaxed; + } + + .singlestage-empty-content { + @apply gap-4 + text-sm; + } +} diff --git a/singlestage/src/components/empty/style/empty.css b/singlestage/src/components/empty/style/empty.css new file mode 100644 index 00000000..12f7189f --- /dev/null +++ b/singlestage/src/components/empty/style/empty.css @@ -0,0 +1,55 @@ +@layer components { + .singlestage-empty { + @apply flex + flex-1 + flex-col + items-center + justify-center + min-w-0 + text-balance + text-center + w-full; + } + + .singlestage-empty-header { + @apply flex + flex-col + items-center + max-w-sm; + } + + .singlestage-empty-media { + @apply flex + items-center + justify-center + shrink-0; + + svg { + @apply pointer-events-none + shrink-0; + } + } + + .singlestage-empty-description { + @apply text-muted-foreground; + + > a:not(.singlestage-button) { + @apply underline + underline-offset-4; + + &:hover { + @apply text-primary; + } + } + } + + .singlestage-empty-content { + @apply flex + flex-col + items-center + max-w-sm + min-w-0 + text-balance + w-full; + } +} diff --git a/singlestage/src/components/field/field.css b/singlestage/src/components/field/field.css deleted file mode 100644 index 31aa6520..00000000 --- a/singlestage/src/components/field/field.css +++ /dev/null @@ -1,261 +0,0 @@ -@layer components { - /* Field */ - .singlestage-field { - @apply aria-invalid:text-destructive - flex - gap-3 - w-full; - } - - .singlestage-field:has(> *:disabled) { - .singlestage-field-label { - @apply opacity-50; - } - } - - .singlestage-field-vertical { - @apply flex-col; - - > * { - @apply w-full; - } - - > .sr-only { - @apply w-auto; - } - } - - .singlestage-field-horizontal { - @apply flex-row - items-center; - - .singlestage-field-description { - @apply text-balance; - } - - > .singlestage-field-label, - > .singlestage-field-title, - > .singlestage-field-legend-label { - @apply flex-auto; - } - } - - .singlestage-field-horizontal:has(.singlestage-field-content) { - @apply items-start; - - > input[type="checkbox"], - > input[type="radio"] { - @apply md:mt-px; - } - } - - .singlestage-field-responsive { - @apply flex-col; - - > * { - @apply w-full; - } - - > .sr-only { - @apply w-auto; - } - } - - .singlestage-field-responsive:has(> .singlestage-field-content) { - @apply md:items-start; - - > input[type="checkbox"], - > input[type="radio"] { - @apply md:mt-px; - } - } - - .singlestage-field-button { - @apply border - flex-auto - p-4 - rounded-md - w-full; - } - - .singlestage-field-button:has(input:checked) { - @apply bg-primary/5 - border-primary; - } - - /* Content */ - - .singlestage-field-content { - @apply flex - flex-1 - flex-col - gap-1.5 - leading-snug; - } - - /* Description */ - - .singlestage-field-description { - @apply font-normal - last:mt-0 - leading-normal - nth-last-2:-mt-1 - text-muted-foreground - text-sm; - - > a { - @apply underline - underline-offset-4; - } - - > a:hover { - @apply text-primary; - } - } - - /* Error */ - - .singlestage-field-error { - @apply font-normal - text-destructive - text-sm; - } - - .singlestage-field-error-list { - @apply flex - flex-col - gap-1 - list-disc - ml-4; - } - - /* Group */ - - .singlestage-field-group { - @apply flex - flex-col - gap-7 - w-full; - - > .singlestage-field-group { - @apply gap-4; - } - - > .singlestage-checkbox-group { - @apply gap-3; - } - - .singlestage-field-responsive { - @apply md:flex-row - md:items-center; - - > * { - @apply md:!w-auto; - } - - > .singlestage-field-label, - > .singlestage-field-title, - > .singlestage-field-legend-label { - @apply md:flex-auto; - } - } - - .singlestage-field-responsive:has(.singlestage-field-content) { - @apply md:items-start; - - > input[type="checkbox"], - > input[type="radio"] { - @apply md:mt-px; - } - } - } - - /* Label */ - - .singlestage-field-label { - @apply flex - w-fit - gap-2 - leading-snug - items-center - text-sm - font-medium - select-none - aria-disabled:pointer-events-none - aria-disabled:opacity-50; - } - - /* Legend */ - - .singlestage-field-legend { - @apply font-medium - mb-3; - } - - .singlestage-field-legend-legend { - @apply text-base; - } - - .singlestage-field-legend-legend + .singlestage-field-description { - @apply -mt-1.5; - } - - .singlestage-field-legend-label { - @apply text-sm; - } - - /* Set */ - - .singlestage-field-set { - @apply flex - flex-col - gap-6; - } - - .singlestage-field-set:has(.singlestage-checkbox-group), - .singlestage-field-set:has(.singlestage-radio-group) { - @apply gap-3; - } - - /* Separator */ - - .singlestage-field-separator { - @apply absolute - bg-border - h-px - inset-0 - shrink-0 - top-1/2 - w-full; - } - - .singlestage-field-separator-outer { - @apply -my-2 - h-5 - relative - text-sm; - } - - .singlestage-field-separator-content { - @apply bg-background - block - mx-auto - px-2 - relative - text-muted-foreground - w-fit; - } - - /* Title */ - - .singlestage-field-title { - @apply aria-disabled:opacity-50 - flex - font-medium - gap-2 - items-center - leading-snug - text-sm - w-fit; - } -} diff --git a/singlestage/src/components/field/field.rs b/singlestage/src/components/field/field.rs index 0cf002b9..80e5e54c 100644 --- a/singlestage/src/components/field/field.rs +++ b/singlestage/src/components/field/field.rs @@ -1,4 +1,4 @@ -use crate::FieldContext; +use crate::{CheckboxGroupContext, FieldContext, RadioGroupContext, Reactive}; use leptos::{context::Provider, prelude::*}; /// The core wrapper for a single field. Provides orientation control, invalid state styling, and @@ -7,16 +7,17 @@ use leptos::{context::Provider, prelude::*}; pub fn Field( children: Children, + /// Sets whether this Field is disabled + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Sets whether this Field is invalid + #[prop(optional, into)] + invalid: Reactive<bool>, /// Sets the display orientation. /// /// Accepted values: "vertical" | "horizontal" | "responsive". Defaults to "vertical". #[prop(optional, into)] orientation: MaybeProp<String>, - /// Sets the display variant of the `Field`. - /// - /// Accepted values: "button". - #[prop(optional, into)] - variant: MaybeProp<String>, // GLOBAL ATTRIBUTES // @@ -120,12 +121,6 @@ pub fn Field( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let context = FieldContext { - description_id: RwSignal::new(String::default()), - input_id: RwSignal::new(String::default()), - label_id: RwSignal::new(String::default()), - }; - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -164,19 +159,45 @@ pub fn Field( /> }; + let disabled: Reactive<bool> = { + if let Some(radio_group) = use_context::<RadioGroupContext>() { + radio_group.disabled + } else if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() { + checkbox_group.disabled + } else { + disabled + } + }; + + let invalid: Reactive<bool> = { + if let Some(radio_group) = use_context::<RadioGroupContext>() { + radio_group.invalid + } else if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() { + checkbox_group.invalid + } else { + invalid + } + }; + + let context = FieldContext { + description_id: RwSignal::new(String::default()), + disabled, + input_id: RwSignal::new(String::default()), + invalid, + label_id: RwSignal::new(String::default()), + }; + view! { <div + aria_disabled=move || if disabled.get() { Some("true".to_string()) } else { None } + aria_invalid=move || if invalid.get() { Some("true".to_string()) } else { None } class=move || { format!( - "singlestage-field{}{}{}", + "singlestage-field{} {}", match orientation.get().unwrap_or_default().as_str() { - "horizontal" => " singlestage-field-horizontal", - "responsive" => " singlestage-field-responsive", - _ => " singlestage-field-vertical", - }, - match variant.get().unwrap_or_default().as_str() { - "button" => " singlestage-field-button", - _ => "", + "horizontal" => " singlestage-field-orientation-horizontal", + "responsive" => " singlestage-field-orientation-responsive", + _ => " singlestage-field-orientation-vertical", }, class.get().unwrap_or_default(), ) diff --git a/singlestage/src/components/field/label.rs b/singlestage/src/components/field/label.rs index af67c57f..742c3957 100644 --- a/singlestage/src/components/field/label.rs +++ b/singlestage/src/components/field/label.rs @@ -1,11 +1,21 @@ -use crate::FieldContext; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// A styled label associated with the input for a Field. +/// Renders an accessible label associated with controls #[component] pub fn FieldLabel( children: Children, + /// Whether the element renders as disabled + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + /// (For use in popover menus) + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Whether the element renders as invalid + #[prop(optional, into)] + invalid: Reactive<bool>, /// The id of the labeled element if it's not a child #[prop(optional, into)] label_for: MaybeProp<String>, @@ -153,35 +163,20 @@ pub fn FieldLabel( /> }; - let uuid = uuid::Uuid::new_v4(); - view! { - <label - class=move || format!("singlestage-field-label {}", class.get().unwrap_or_default()) - for=move || { - if let Some(label_for) = label_for.get() { - Some(label_for) - } else { - use_context::<FieldContext>().map(|field| field.input_id.get()) - } - } - id={if let Some(field) = use_context::<FieldContext>() { - let label_id; - if let Some(id) = id.get_untracked() { - label_id = id; - } else { - label_id = uuid.to_string() - }; - field.label_id.set(label_id.clone()); - Some(label_id) - } else { - id.get_untracked() - }} + <LabelPrimitive + primitive_type=LabelPrimitiveType::FieldLabel + class + disabled + id + inset + invalid + label_for {..global_attrs_1} {..global_attrs_2} > {children()} - </label> + </LabelPrimitive> } } diff --git a/singlestage/src/components/field/legend.rs b/singlestage/src/components/field/legend.rs index fdeaba8b..4fe74d6a 100644 --- a/singlestage/src/components/field/legend.rs +++ b/singlestage/src/components/field/legend.rs @@ -161,8 +161,8 @@ pub fn FieldLegend( format!( "singlestage-field-legend {} {}", match variant.get().unwrap_or_default().as_str() { - "label" => "singlestage-field-legend-label", - _ => "singlestage-field-legend-legend", + "label" => "singlestage-field-legend-variant-label", + _ => "singlestage-field-legend-variant-legend", }, class.get().unwrap_or_default(), ) diff --git a/singlestage/src/components/field/mod.rs b/singlestage/src/components/field/mod.rs index 50547ac6..06bfa2cb 100644 --- a/singlestage/src/components/field/mod.rs +++ b/singlestage/src/components/field/mod.rs @@ -20,11 +20,17 @@ pub use separator::*; pub use set::*; pub use title::*; +use crate::Reactive; use leptos::prelude::RwSignal; #[derive(Clone)] -pub struct FieldContext { +pub(crate) struct FieldContext { pub description_id: RwSignal<String>, + pub disabled: Reactive<bool>, pub input_id: RwSignal<String>, + pub invalid: Reactive<bool>, pub label_id: RwSignal<String>, } + +#[derive(Clone)] +pub(crate) struct FieldSetContext {} diff --git a/singlestage/src/components/field/separator.rs b/singlestage/src/components/field/separator.rs index 6f3b6b7a..ef7eced9 100644 --- a/singlestage/src/components/field/separator.rs +++ b/singlestage/src/components/field/separator.rs @@ -1,10 +1,16 @@ +use crate::Separator; use leptos::prelude::*; -/// Visual divider to separate sections inside a FieldGroup. Accepts optional inline content. +/// The separator. #[component] pub fn FieldSeparator( #[prop(optional)] children: Option<Children>, + /// Specify the orientation of the separator + /// Accepted values: `"vertical"` | `"horizontal"` (default) + #[prop(optional, into)] + orientation: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -150,27 +156,39 @@ pub fn FieldSeparator( }; view! { - <div class="singlestage-field-separator-outer"> - <div class="singlestage-field-separator"></div> + <div class="singlestage-field-separator"> {if let Some(children) = children { view! { - <span - class=move || { - format!( - "singlestage-field-separator-content {}", - class.get().unwrap_or_default(), - ) - } + <Separator + class="singlestage-field-separator-inner" + orientation {..global_attrs_1} {..global_attrs_2} > - {children()} - </span> + <span class=format!( + "singlestage-field-separator-content {}", + class.get().unwrap_or_default(), + )>{children()}</span> + </Separator> } .into_any() } else { - "".into_any() + + view! { + <Separator + class=format!( + "singlestage-field-separator-inner {}", + class.get().unwrap_or_default(), + ) + + orientation + + {..global_attrs_1} + {..global_attrs_2} + /> + } + .into_any() }} </div> } diff --git a/singlestage/src/components/field/set.rs b/singlestage/src/components/field/set.rs index 90354298..d89febfb 100644 --- a/singlestage/src/components/field/set.rs +++ b/singlestage/src/components/field/set.rs @@ -1,4 +1,5 @@ -use leptos::prelude::*; +use crate::{FieldSetContext, Reactive}; +use leptos::{context::Provider, prelude::*}; /// Container that renders a semantic fieldset with spacing presets. #[component] @@ -9,7 +10,7 @@ pub fn FieldSet( // /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, @@ -171,7 +172,7 @@ pub fn FieldSet( {..global_attrs_2} {..fieldset_attrs} > - {children()} + <Provider value=FieldSetContext {}>{children()}</Provider> </fieldset> } } diff --git a/singlestage/src/components/field/style/base/luma/field.css b/singlestage/src/components/field/style/base/luma/field.css new file mode 100644 index 00000000..462e6223 --- /dev/null +++ b/singlestage/src/components/field/style/base/luma/field.css @@ -0,0 +1,112 @@ +@layer components2 { + .singlestage-field-set { + @apply gap-6; + + &:has(> .singlestage-checkbox-group, > .singlestage-radio-group) { + @apply gap-3; + } + } + + .singlestage-field-legend { + @apply font-medium + mb-3; + } + + .singlestage-field-content { + @apply gap-1; + } + + .singlestage-field-title { + @apply gap-2 + leading-snug + text-sm + font-medium; + } + + .singlestage-field-description { + @apply text-left + text-muted-foreground + text-sm; + } + + .singlestage-field-separator { + @apply -my-2 + h-5 + text-sm; + } + + .singlestage-field-separator-content { + @apply px-2 + text-muted-foreground; + } + + .singlestage-field-group { + @apply gap-7; + + &:has(.singlestage-checkbox-group) { + @apply gap-3; + } + + .singlestage-field-group { + @apply gap-4; + } + } + + .singlestage-field-error { + @apply text-destructive + text-sm; + } + + .singlestage-field-legend-variant-label { + @apply text-sm; + } + + .singlestage-field-legend-variant-legend { + @apply text-base; + + & + .singlestage-field-description { + @apply -mt-1.5; + } + } + + .singlestage-field-label { + @apply gap-2 + leading-snug; + + &:has(> .singlestage-field) { + @apply border + rounded-2xl; + } + + &:has(*:checked) { + @apply bg-input/30; + } + + .singlestage-field { + @apply p-4; + } + } + + .singlestage-field { + @apply gap-3; + + &:disabled, + &[aria-disabled="true"] { + .singlestage-label, + .singlestage-field-title { + @apply opacity-50; + } + } + + &:invalid, + &[aria-invalid="true"] { + @apply text-destructive; + } + } + + .singlestage-field-group-variant-outline { + .singlestage-field-separator { + @apply -mb-2; + } + } +} diff --git a/singlestage/src/components/field/style/base/lyra/field.css b/singlestage/src/components/field/style/base/lyra/field.css new file mode 100644 index 00000000..355e727b --- /dev/null +++ b/singlestage/src/components/field/style/base/lyra/field.css @@ -0,0 +1,113 @@ +@layer components2 { + .singlestage-field-set { + @apply gap-4; + + &:has(> .singlestage-checkbox-group, > .singlestage-radio-group) { + @apply gap-3; + } + } + + .singlestage-field-legend { + @apply font-medium + mb-2.5; + } + + .singlestage-field-group { + @apply gap-5; + + &:has(.singlestage-checkbox-group) { + @apply gap-3; + } + + .singlestage-field-group { + @apply gap-4; + } + } + + .singlestage-field-content { + @apply gap-0.5; + } + + .singlestage-field-label { + @apply gap-2 + leading-snug; + + &:has(> .singlestage-field) { + @apply border + rounded-none; + } + + &:has(*:checked) { + @apply bg-primary/5 + border-primary/30; + } + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + .singlestage-field { + @apply p-2; + } + } + + .singlestage-field { + @apply gap-2; + + &:invalid, + &[aria-invalid="true"] { + @apply text-destructive; + } + } + + .singlestage-field-title { + @apply gap-2 + text-xs/relaxed; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + } + + .singlestage-field-description { + @apply text-left + text-muted-foreground + text-xs/relaxed; + + .singlestage-field-legend-variant-legend + & { + @apply -mt-1.5; + } + } + + .singlestage-field-separator { + @apply -my-2 + h-5 + text-xs; + } + + .singlestage-field-separator-content { + @apply text-muted-foreground + px-2; + } + + .singlestage-field-error { + @apply text-destructive + text-xs; + } + + .singlestage-field-legend-variant-label { + @apply text-xs; + } + + .singlestage-field-legend-variant-legend { + @apply text-sm; + } + + .singlestage-field-group-variant-outline { + .singlestage-field-separator { + @apply -mb-2; + } + } +} diff --git a/singlestage/src/components/field/style/base/lyra/field_dark.css b/singlestage/src/components/field/style/base/lyra/field_dark.css new file mode 100644 index 00000000..f224da96 --- /dev/null +++ b/singlestage/src/components/field/style/base/lyra/field_dark.css @@ -0,0 +1,6 @@ +@layer components2 { + .singlestage-field-label:has(*:checked) { + @apply bg-primary/10 + border-primary/20; + } +} diff --git a/singlestage/src/components/field/style/base/maia/field.css b/singlestage/src/components/field/style/base/maia/field.css new file mode 100644 index 00000000..ce29e1f8 --- /dev/null +++ b/singlestage/src/components/field/style/base/maia/field.css @@ -0,0 +1,115 @@ +@layer components2 { + .singlestage-field-set { + @apply gap-6; + + &:has (> .singlestage-checkbox-group, > .singlestage-radio-group) { + @apply gap-3; + } + } + + .singlestage-field-legend { + @apply font-medium + mb-3; + } + + .singlestage-field-group { + @apply gap-7; + + &:has(.singlestage-checkbox-group) { + @apply gap-3; + } + + .singlestage-field-group { + @apply gap-4; + } + } + + .singlestage-field-content { + @apply gap-1; + } + + .singlestage-field-label { + @apply gap-2 + leading-snug; + + &:has(> .singlestage-field) { + @apply border + rounded-xl; + } + + &:has(*:checked) { + @apply bg-primary/5 + border-primary/30; + } + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + .singlestage-field { + @apply p-4; + } + } + + .singlestage-field { + @apply gap-3; + + &:invalid, + &[aria-invalid="true"] { + @apply text-destructive; + } + } + + .singlestage-field-title { + @apply font-medium + gap-2 + leading-snug + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + } + + .singlestage-field-description { + @apply text-left + text-muted-foreground + text-sm; + + .singlestage-field-legend-variant-legend + & { + @apply -mt-1.5; + } + } + + .singlestage-field-separator { + @apply -my-2 + h-5 + text-sm; + } + + .singlestage-field-separator-content { + @apply px-2 + text-muted-foreground; + } + + .singlestage-field-error { + @apply text-destructive + text-sm; + } + + .singlestage-field-legend-variant-label { + @apply text-sm; + } + + .singlestage-field-legend-variant-legend { + @apply text-base; + } + + .singlestage-field-group-variant-outline { + .singlestage-field-separator { + @apply -mb-2; + } + } +} diff --git a/singlestage/src/components/field/style/base/maia/field_dark.css b/singlestage/src/components/field/style/base/maia/field_dark.css new file mode 100644 index 00000000..f224da96 --- /dev/null +++ b/singlestage/src/components/field/style/base/maia/field_dark.css @@ -0,0 +1,6 @@ +@layer components2 { + .singlestage-field-label:has(*:checked) { + @apply bg-primary/10 + border-primary/20; + } +} diff --git a/singlestage/src/components/field/style/base/mira/field.css b/singlestage/src/components/field/style/base/mira/field.css new file mode 100644 index 00000000..3e04c1b2 --- /dev/null +++ b/singlestage/src/components/field/style/base/mira/field.css @@ -0,0 +1,113 @@ +@layer components2 { + .singlestage-field-set { + @apply gap-4; + + &:has(> .singlestage-checkbox-group, > .singlestage-radio-group) { + @apply gap-3; + } + } + + .singlestage-field-legend { + @apply font-medium + mb-2; + } + + .singlestage-field-group { + @apply gap-4; + + &:has(.singlestage-checkbox-group) { + @apply gap-3; + } + + .singlestage-field-group { + @apply gap-4; + } + } + + .singlestage-field { + @apply gap-2; + + &:invalid, + &[aria-invalid="true"] { + @apply text-destructive; + } + } + + .singlestage-field-content { + @apply gap-0.5; + } + + .singlestage-field-label { + @apply gap-2 + leading-snug; + + &:has(> .singlestage-field) { + @apply border + rounded-md; + } + + &:has(*:checked) { + @apply bg-primary/5; + } + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + .singlestage-field { + @apply p-2; + } + } + + .singlestage-field-title { + @apply font-medium + gap-2 + text-xs/relaxed; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + } + + .singlestage-field-description { + @apply text-left + text-muted-foreground + text-xs/relaxed; + + .singlestage-field-legend-variant-legend + & { + @apply -mt-1.5; + } + } + + .singlestage-field-separator { + @apply -my-2 + h-5 + text-xs/relaxed; + } + + .singlestage-field-separator-content { + @apply px-2 + text-muted-foreground; + } + + .singlestage-field-error { + @apply text-destructive + text-xs/relaxed; + } + + .singlestage-field-legend-variant-label { + @apply text-xs/relaxed; + } + + .singlestage-field-legend-variant-legend { + @apply text-sm; + } + + .singlestage-field-group-variant-outline { + .singlestage-field-separator { + @apply -mb-2; + } + } +} diff --git a/singlestage/src/components/field/style/base/mira/field_dark.css b/singlestage/src/components/field/style/base/mira/field_dark.css new file mode 100644 index 00000000..0cb4bd52 --- /dev/null +++ b/singlestage/src/components/field/style/base/mira/field_dark.css @@ -0,0 +1,5 @@ +@layer components2 { + .singlestage-field-label:has(*:checked) { + @apply has-data-checked:bg-primary/10; + } +} diff --git a/singlestage/src/components/field/style/base/nova/field.css b/singlestage/src/components/field/style/base/nova/field.css new file mode 100644 index 00000000..da993432 --- /dev/null +++ b/singlestage/src/components/field/style/base/nova/field.css @@ -0,0 +1,115 @@ +@layer components2 { + .singlestage-field-set { + @apply gap-4; + + &:has(> .singlestage-checkbox-group, > .singlestage-radio-group) { + @apply gap-3; + } + } + + .singlestage-field-legend { + @apply font-medium + mb-1.5; + } + + .singlestage-field-group { + @apply gap-5; + + &:has(.singlestage-checkbox-group) { + @apply gap-3; + } + + .singlestage-field-group { + @apply gap-4; + } + } + + .singlestage-field-content { + @apply gap-0.5; + } + + .singlestage-field-label { + @apply gap-2 + leading-snug; + + &:has(> .singlestage-field) { + @apply border + rounded-lg; + } + + &:has(*:checked) { + @apply bg-primary/5 + border-primary/30; + } + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + .singlestage-field { + @apply p-2.5; + } + } + + .singlestage-field { + @apply gap-2; + + &:invalid, + &[aria-invalid="true"] { + @apply text-destructive; + } + } + + .singlestage-field-title { + @apply font-medium + gap-2 + leading-snug + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + } + + .singlestage-field-description { + @apply text-left + text-muted-foreground + text-sm; + + .singlestage-field-legend-variant-legend + & { + @apply -mt-1.5; + } + } + + .singlestage-field-separator { + @apply -my-2 + h-5 + text-sm; + } + + .singlestage-field-separator-content { + @apply px-2 + text-muted-foreground; + } + + .singlestage-field-error { + @apply text-destructive + text-sm; + } + + .singlestage-field-legend-variant-label { + @apply text-sm; + } + + .singlestage-field-legend-variant-legend { + @apply text-base; + } + + .singlestage-field-group-variant-outline { + .singlestage-field-separator { + @apply -mb-2; + } + } +} diff --git a/singlestage/src/components/field/style/base/nova/field_dark.css b/singlestage/src/components/field/style/base/nova/field_dark.css new file mode 100644 index 00000000..f224da96 --- /dev/null +++ b/singlestage/src/components/field/style/base/nova/field_dark.css @@ -0,0 +1,6 @@ +@layer components2 { + .singlestage-field-label:has(*:checked) { + @apply bg-primary/10 + border-primary/20; + } +} diff --git a/singlestage/src/components/field/style/base/sera/field.css b/singlestage/src/components/field/style/base/sera/field.css new file mode 100644 index 00000000..2dd3500c --- /dev/null +++ b/singlestage/src/components/field/style/base/sera/field.css @@ -0,0 +1,123 @@ +@layer components2 { + .singlestage-field-set { + @apply gap-6; + + &:has(> .singlestage-checkbox-group, > .singlestage-radio-group) { + @apply gap-3; + } + } + + .singlestage-field-legend { + @apply font-semibold + mb-3 + uppercase; + } + + .singlestage-field-group { + @apply gap-10; + + &:has(.singlestage-checkbox-group) { + @apply gap-3; + } + + .singlestage-field-group { + @apply gap-4; + } + } + + .singlestage-field-content { + @apply gap-1; + } + + .singlestage-field-title { + @apply font-semibold + gap-2 + leading-normal + text-xs + uppercase; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + } + + .singlestage-field-label { + @apply gap-2 + leading-relaxed; + + &:has(> .singlestage-field) { + @apply border + rounded-none; + } + + &:has(*:checked) { + @apply bg-primary/5 + border-primary/30; + } + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + .singlestage-field-title { + @apply font-semibold; + } + + .singlestage-field { + @apply p-4; + } + } + + .singlestage-field { + @apply gap-3; + + &:invalid, + &[aria-invalid="true"] { + @apply text-destructive; + } + } + + .singlestage-field-description { + @apply normal-case + text-left + text-muted-foreground + text-sm + tracking-normal; + + .singlestage-field-legend-variant-legend + & { + @apply -mt-1.5; + } + } + + .singlestage-field-separator { + @apply -my-2 + h-5 + text-sm; + } + + .singlestage-field-separator-content { + @apply px-2 + text-muted-foreground; + } + + .singlestage-field-error { + @apply text-sm + text-destructive; + } + + .singlestage-field-legend-variant-label { + @apply text-xs; + } + + .singlestage-field-legend-variant-legend { + @apply text-xs; + } + + .singlestage-field-group-variant-outline { + .singlestage-field-separator { + @apply -mb-2; + } + } +} diff --git a/singlestage/src/components/field/style/base/sera/field_dark.css b/singlestage/src/components/field/style/base/sera/field_dark.css new file mode 100644 index 00000000..f224da96 --- /dev/null +++ b/singlestage/src/components/field/style/base/sera/field_dark.css @@ -0,0 +1,6 @@ +@layer components2 { + .singlestage-field-label:has(*:checked) { + @apply bg-primary/10 + border-primary/20; + } +} diff --git a/singlestage/src/components/field/style/base/vega/field.css b/singlestage/src/components/field/style/base/vega/field.css new file mode 100644 index 00000000..de9e5830 --- /dev/null +++ b/singlestage/src/components/field/style/base/vega/field.css @@ -0,0 +1,118 @@ +@layer components2 { + .singlestage-field-set { + @apply gap-6; + + &:has(> .singlestage-checkbox-group, > .singlestage-radio-group) { + @apply gap-3; + } + } + + .singlestage-field-legend { + @apply font-medium + mb-3; + } + + .singlestage-field-content { + @apply gap-1; + } + + .singlestage-field-title { + @apply font-medium + gap-2 + text-sm; + } + + .singlestage-field-description { + @apply text-left + text-muted-foreground + text-sm; + } + + .singlestage-field-separator { + @apply -my-2 + h-5 + text-sm; + } + + .singlestage-field-separator-separator { + @apply absolute + inset-0 + top-1/2; + } + + .singlestage-field-separator-content { + @apply px-2 + text-muted-foreground; + } + + .singlestage-field-error { + @apply text-destructive + text-sm; + } + + .singlestage-field-group { + @apply gap-7; + + .singlestage-field-group { + @apply gap-4; + } + + > .singlestage-checkbox-group { + @apply gap-3; + } + } + + .singlestage-field-label { + @apply gap-2 + leading-snug; + + &:has(> .singlestage-field) { + @apply border + rounded-md; + } + + &:has(*:checked) { + @apply bg-primary/5 + border-primary/30; + } + + .singlestage-field { + @apply p-3; + } + } + + .singlestage-field { + @apply gap-3; + + &:disabled, + &[aria-disabled="true"] { + .singlestage-field-label, + .singlestage-field-title { + @apply opacity-50; + } + } + + &:invalid, + &[aria-invalid="true"] { + @apply text-destructive; + } + } + + .singlestage-field-legend-variant-label { + @apply text-sm; + } + + .singlestage-field-legend-variant-legend { + @apply text-base; + + & + .singlestage-field-description { + @apply -mt-1.5; + } + } + + .singlestage-field-group-variant-outline { + .singlestage-field-separator { + @apply -mb-2; + } + } +} diff --git a/singlestage/src/components/field/style/base/vega/field_dark.css b/singlestage/src/components/field/style/base/vega/field_dark.css new file mode 100644 index 00000000..f224da96 --- /dev/null +++ b/singlestage/src/components/field/style/base/vega/field_dark.css @@ -0,0 +1,6 @@ +@layer components2 { + .singlestage-field-label:has(*:checked) { + @apply bg-primary/10 + border-primary/20; + } +} diff --git a/singlestage/src/components/field/style/field.css b/singlestage/src/components/field/style/field.css new file mode 100644 index 00000000..c5b003e9 --- /dev/null +++ b/singlestage/src/components/field/style/field.css @@ -0,0 +1,156 @@ +@layer components1 { + .singlestage-field-set { + @apply flex + flex-col; + } + + .singlestage-field-group { + @apply flex + flex-col + w-full; + } + + .singlestage-field { + @apply flex + w-full; + } + + .singlestage-field-content { + @apply flex + flex-1 + flex-col + leading-snug; + } + + .singlestage-field-label { + @apply flex + w-fit; + + &:has(> .singlestage-field) { + @apply flex-col + w-full; + } + } + + .singlestage-field-title { + @apply flex + items-center + w-fit; + } + + .singlestage-field-description { + @apply font-normal + last:mt-0 + leading-normal + nth-last-2:-mt-1; + + > a:not(.singlestage-button) { + @apply underline + underline-offset-4; + + &:hover { + @apply text-primary; + } + } + } + + .singlestage-field-separator { + @apply relative; + } + + .singlestage-field-separator-inner { + @apply absolute + inset-0 + top-1/2; + } + + .singlestage-field-separator-content { + @apply bg-background + block + mx-auto + relative + w-fit; + } + + .singlestage-field-error-list { + @apply flex + flex-col + gap-1 + list-disc + ml-4; + } + + .singlestage-field-error { + @apply font-normal; + } + + .singlestage-field-orientation-vertical { + @apply flex-col; + + > * { + @apply w-full; + } + + > .sr-only { + @apply w-auto; + } + } + + .singlestage-field-orientation-horizontal { + @apply flex-row + items-center; + + &:has(> .singlestage-field-content) { + @apply items-start; + + & > .singlestage-checkbox, + .singlestage-radio { + @apply mt-px; + } + } + + .singlestage-field-label, + .singlestage-field-title { + @apply flex-auto; + } + + .singlestage-field-description { + @apply text-balance; + } + } + + .singlestage-field-orientation-responsive { + @apply flex-col; + + > * { + @apply w-full; + } + + > .sr-only { + @apply w-auto; + } + + .singlestage-field-group { + @apply md:flex-row + md:items-center; + + > * { + @apply md:w-auto; + } + + > .singlestage-field-label, + > .singlestage-field-title { + @apply md:flex-auto; + } + + &:has(> .singlestage-field-content) { + @apply md:items-start; + + & > .singlestage-checkbox, + .singlestage-radio { + @apply md:mt-px; + } + } + } + } +} diff --git a/singlestage/src/components/field/title.rs b/singlestage/src/components/field/title.rs index 92e14818..03f09498 100644 --- a/singlestage/src/components/field/title.rs +++ b/singlestage/src/components/field/title.rs @@ -150,13 +150,13 @@ pub fn FieldTitle( }; view! { - <span + <div class=move || { format!("singlestage-field-title {}", class.get().unwrap_or_default()) } {..global_attrs_1} {..global_attrs_2} > {children()} - </span> + </div> } } diff --git a/singlestage/src/components/input/input.css b/singlestage/src/components/input/input.css deleted file mode 100644 index 98ce37db..00000000 --- a/singlestage/src/components/input/input.css +++ /dev/null @@ -1,75 +0,0 @@ -@layer components { - .singlestage-input[type="date"], - .singlestage-input[type="datetime-local"], - .singlestage-input[type="email"], - .singlestage-input[type="file"], - .singlestage-input[type="month"], - .singlestage-input[type="number"], - .singlestage-input[type="password"], - .singlestage-input[type="search"], - .singlestage-input[type="tel"], - .singlestage-input[type="text"], - .singlestage-input[type="time"], - .singlestage-input[type="url"], - .singlestage-input[type="week"] { - @apply appearance-none - aria-invalid:border-destructive - aria-invalid:ring-destructive/20 - bg-transparent - border - border-input - disabled:cursor-not-allowed - disabled:opacity-50 - disabled:pointer-events-none - file:bg-transparent - file:border-0 - file:font-medium - file:h-7 - file:inline-flex - file:text-foreground - file:text-sm - flex - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - h-9 - md:text-sm - min-w-0 - outline-none - placeholder:text-muted-foreground - px-3 - py-1 - rounded-md - selection:bg-primary - selection:text-primary-foreground - shadow-xs - text-base - transition-[color,box-shadow] - w-full; - } - .singlestage-input[type="color"] { - @apply appearance-none - aria-invalid:border-destructive - aria-invalid:ring-destructive/20 - bg-transparent - border - border-input - disabled:cursor-not-allowed - disabled:opacity-50 - disabled:pointer-events-none - flex - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - size-9 - outline-none - p-2 - rounded-md - shadow-xs - transition-[color,box-shadow]; - } - - .singlestage-input-label { - @apply my-3; - } -} diff --git a/singlestage/src/components/input/mod.rs b/singlestage/src/components/input/mod.rs index 5137eeeb..e5c38794 100644 --- a/singlestage/src/components/input/mod.rs +++ b/singlestage/src/components/input/mod.rs @@ -1,4 +1,4 @@ -use crate::{FieldContext, FieldLabel, Reactive}; +use crate::{FieldContext, FieldLabel, InputGroupContext, Label, Reactive, SidebarGroupContext}; use leptos::prelude::*; /// A form input field. @@ -17,7 +17,7 @@ pub fn Input( input_type: MaybeProp<String>, /// Toggle invalid appearance. #[prop(optional, into)] - invalid: MaybeProp<bool>, + invalid: Reactive<bool>, /// The reactive value signal of this input. Also sets initial `default` value, but doesn't /// update it. #[prop(optional, into)] @@ -58,7 +58,7 @@ pub fn Input( dirname: MaybeProp<String>, /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, @@ -269,41 +269,69 @@ pub fn Input( } }; - Effect::new(move || { - if let Some(input) = input_ref.get_untracked() - && let Some(default) = default.get() - { - input.set_default_value(&default); + let update_default = move || { + if let Some(val) = default.get() { + if let Some(input) = input_ref.get() { + let _ = input.set_default_value(&val); + } + + value.set(val); + } + + default.get() + }; + + let disabled: Reactive<bool> = { + if let Some(field) = use_context::<FieldContext>() { + field.disabled + } else { + disabled } - }); + }; - Effect::new(move || { - if let Some(input) = input_ref.get_untracked() - && let Some(disabled) = disabled.get() - { - input.set_disabled(disabled); + let invalid: Reactive<bool> = { + if let Some(field) = use_context::<FieldContext>() { + field.invalid + } else { + invalid } - }); + }; + + let on_input = move |ev: web_sys::Event| { + let input_value = event_target_value(&ev); + + let val = match input_type.get_untracked().unwrap_or_default().as_str() { + "number" => { + let decimal = '.'; - Effect::new( - move || match input_type.get_untracked().unwrap_or_default().as_str() { - "file" => { - if value.get().is_empty() - && let Some(input) = input_ref.get_untracked() + if step.get_untracked().unwrap_or_default().contains(decimal) + && let Some(input_precision) = input_value.split(decimal).last() + && let Some(step_precision) = step + .get_untracked() + .unwrap_or_default() + .split(decimal) + .last() { - input.set_value(""); - } - } - _ => { - if let Some(input) = input_ref.get_untracked() { - input.set_value(&value.get()); + if input_precision.len() < step_precision.len() + || !input_value.contains(decimal) + { + // User is not done typing + return; + } + + format!( + "{:.*}", + step_precision.len(), + input_value.parse::<f64>().unwrap() + ) + } else { + input_value } } - }, - ); + _ => input_value, + }; - let on_input = move |ev| { - value.set(event_target_value(&ev)); + value.set(val); }; let global_attrs_1 = view! { @@ -383,7 +411,11 @@ pub fn Input( let input_id = uuid::Uuid::new_v4(); let label_id = uuid::Uuid::new_v4(); - let has_children = children.is_some(); + + let has_children: bool = children.is_some(); + let in_field: bool = use_context::<FieldContext>().is_some(); + let in_input_group: bool = use_context::<InputGroupContext>().is_some(); + let in_sidebar_group: bool = use_context::<SidebarGroupContext>().is_some(); let custom_attrs = view! { <{..} @@ -395,12 +427,8 @@ pub fn Input( None } } - aria_invalid=move || { - match invalid.get() { - Some(true) => Some("true"), - _ => None, - } - } + aria_disabled=move || { if disabled.get() { Some("true") } else { None } } + aria_invalid=move || { if invalid.get() { Some("true") } else { None } } aria_label=move || aria_label.get() aria_labelledby=move || { if let Some(field) = use_context::<FieldContext>() { @@ -411,8 +439,35 @@ pub fn Input( None } } - class=move || { format!("singlestage-input {}", class.get().unwrap_or_default()) } + class=move || { + format!( + "singlestage-input{}{} {}", + if in_input_group { + " singlestage-input-group-input singlestage-input-group-control" + } else { + "" + }, + if in_sidebar_group { " singlestage-sidebar-input" } else { "" }, + class.get().unwrap_or_default(), + ) + } + default=update_default + prop:default=update_default disabled=move || disabled.get() + prop:disabled=move || disabled.get() + id={if let Some(field) = use_context::<FieldContext>() { + if let Some(id) = id.get_untracked() { + field.input_id.set(id.clone()); + Some(id) + } else { + field.input_id.set(input_id.to_string()); + Some(input_id.to_string()) + } + } else if let Some(id) = id.get_untracked() { + Some(id) + } else { + if has_children { Some(input_id.to_string()) } else { None } + }} node_ref=input_ref on:input=on_input type=move || { @@ -423,68 +478,59 @@ pub fn Input( "text".to_string() } } - value={ - let value = value.get_untracked(); - if value.is_empty() { None } else { Some(value) } - } + value=default.get_untracked().unwrap_or(value.get_untracked()) + prop:value=move || value.get() /> }; if let Some(children) = children { - view! { - {if use_context::<FieldContext>().is_some() { - view! { - <FieldLabel - class=class.get_untracked() - label_for=id.get_untracked().unwrap_or(input_id.to_string()) - > - {children()} - </FieldLabel> - } - .into_any() - } else { - view! { - <label - class=move || { - format!( - "singlestage-label singlestage-input-label {}", - class.get().unwrap_or_default(), - ) - } - for=move || id.get().unwrap_or(input_id.to_string()) - id=label_id.to_string() - > - {children()} - </label> - } - .into_any() - }} - <input - id=move || id.get().unwrap_or(input_id.to_string()) - - {..global_attrs_1} - {..global_attrs_2} - {..input_attrs_1} - {..input_attrs_2} - {..custom_attrs} - /> + if in_field { + view! { + <FieldLabel + class=format!("{}", class.get_untracked().unwrap_or_default()) + disabled + id=label_id.to_string() + invalid + label_for=id.get_untracked().unwrap_or(input_id.to_string()) + > + {children()} + </FieldLabel> + <input + {..global_attrs_1} + {..global_attrs_2} + {..input_attrs_1} + {..input_attrs_2} + {..custom_attrs} + /> + } + .into_any() + } else { + view! { + <Label + class=format!( + "singlestage-input-label {}", + class.get_untracked().unwrap_or_default(), + ) + disabled + id=label_id.to_string() + invalid + label_for=id.get_untracked().unwrap_or(input_id.to_string()) + > + {children()} + </Label> + <input + {..global_attrs_1} + {..global_attrs_2} + {..input_attrs_1} + {..input_attrs_2} + {..custom_attrs} + /> + } + .into_any() } - .into_any() } else { view! { <input - id={if let Some(field) = use_context::<FieldContext>() { - if let Some(id) = id.get_untracked() { - field.input_id.set(id.clone()); - Some(id) - } else { - field.input_id.set(input_id.to_string()); - Some(input_id.to_string()) - } - } else { - id.get_untracked() - }} - {..global_attrs_1} {..global_attrs_2} {..input_attrs_1} diff --git a/singlestage/src/components/input/style/base/luma/input.css b/singlestage/src/components/input/style/base/luma/input.css new file mode 100644 index 00000000..2840aa07 --- /dev/null +++ b/singlestage/src/components/input/style/base/luma/input.css @@ -0,0 +1,33 @@ +@layer components1 { + .singlestage-input { + @apply bg-input/50 + border + border-transparent + h-9 + md:text-sm + px-3 + py-1 + rounded-3xl + text-base + transition-[color,box-shadow,background-color]; + + &[type="file"] { + @apply font-medium + h-7 + text-sm; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/input/style/base/luma/input_dark.css b/singlestage/src/components/input/style/base/luma/input_dark.css new file mode 100644 index 00000000..353079c5 --- /dev/null +++ b/singlestage/src/components/input/style/base/luma/input_dark.css @@ -0,0 +1,9 @@ +@layer components1 { + .singlestage-input { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input/style/base/lyra/input.css b/singlestage/src/components/input/style/base/lyra/input.css new file mode 100644 index 00000000..26f497c2 --- /dev/null +++ b/singlestage/src/components/input/style/base/lyra/input.css @@ -0,0 +1,38 @@ +@layer components1 { + .singlestage-input { + @apply bg-transparent + border + border-input + h-8 + md:text-xs + px-2.5 + py-1 + rounded-none + text-xs + transition-colors; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-input/50; + } + + &[type="file"] { + @apply font-medium + h-6 + text-xs; + } + + &:focus-visible { + @apply border-ring + ring-1 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-1 + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/input/style/base/lyra/input_dark.css b/singlestage/src/components/input/style/base/lyra/input_dark.css new file mode 100644 index 00000000..d747f3e7 --- /dev/null +++ b/singlestage/src/components/input/style/base/lyra/input_dark.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-input { + @apply bg-input/30; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-input/80; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input/style/base/maia/input.css b/singlestage/src/components/input/style/base/maia/input.css new file mode 100644 index 00000000..5b11bc6e --- /dev/null +++ b/singlestage/src/components/input/style/base/maia/input.css @@ -0,0 +1,33 @@ +@layer components1 { + .singlestage-input { + @apply bg-input/30 + border + border-input + h-9 + md:text-sm + px-3 + py-1 + rounded-4xl + text-base + transition-colors; + + &[type="file"] { + @apply font-medium + h-7 + text-sm; + } + + &:focus-visible { + @apply border-ring + ring-[3px] + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-[3px] + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/input/style/base/maia/input_dark.css b/singlestage/src/components/input/style/base/maia/input_dark.css new file mode 100644 index 00000000..353079c5 --- /dev/null +++ b/singlestage/src/components/input/style/base/maia/input_dark.css @@ -0,0 +1,9 @@ +@layer components1 { + .singlestage-input { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input/style/base/mira/input.css b/singlestage/src/components/input/style/base/mira/input.css new file mode 100644 index 00000000..01b7d4e0 --- /dev/null +++ b/singlestage/src/components/input/style/base/mira/input.css @@ -0,0 +1,33 @@ +@layer components1 { + .singlestage-input { + @apply bg-input/20 + border + border-input + h-7 + md:text-xs/relaxed + px-2 + py-0.5 + rounded-md + text-sm + transition-colors; + } + + &[type="file"] { + @apply font-medium + h-6 + text-xs/relaxed; + } + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + } +} diff --git a/singlestage/src/components/input/style/base/mira/input_dark.css b/singlestage/src/components/input/style/base/mira/input_dark.css new file mode 100644 index 00000000..b40ea09f --- /dev/null +++ b/singlestage/src/components/input/style/base/mira/input_dark.css @@ -0,0 +1,11 @@ +@layer components1 { + .singlestage-input { + @apply bg-input/30; + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input/style/base/nova/input.css b/singlestage/src/components/input/style/base/nova/input.css new file mode 100644 index 00000000..b171753a --- /dev/null +++ b/singlestage/src/components/input/style/base/nova/input.css @@ -0,0 +1,38 @@ +@layer components1 { + .singlestage-input { + @apply bg-transparent + border + border-input + h-8 + md:text-sm + px-2.5 + py-1 + rounded-lg + text-base + transition-colors; + + &[type="file"] { + @apply font-medium + h-6 + text-sm; + } + + &:disabled, + &[aria-disabled="true"] { + @apply bg-input/50; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/input/style/base/nova/input_dark.css b/singlestage/src/components/input/style/base/nova/input_dark.css new file mode 100644 index 00000000..d747f3e7 --- /dev/null +++ b/singlestage/src/components/input/style/base/nova/input_dark.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-input { + @apply bg-input/30; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-input/80; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input/style/base/sera/input.css b/singlestage/src/components/input/style/base/sera/input.css new file mode 100644 index 00000000..b8cfbf87 --- /dev/null +++ b/singlestage/src/components/input/style/base/sera/input.css @@ -0,0 +1,29 @@ +@layer components1 { + .singlestage-input { + @apply bg-transparent + border + border-b-input + border-transparent + h-10 + md:text-sm + px-0 + py-1 + text-base + transition-[color,border-color]; + + &[type="file"] { + @apply font-medium + h-7 + text-sm; + } + + &:focus-visible { + @apply border-b-ring; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-b-destructive; + } + } +} diff --git a/singlestage/src/components/input/style/base/sera/input_dark.css b/singlestage/src/components/input/style/base/sera/input_dark.css new file mode 100644 index 00000000..0566aed3 --- /dev/null +++ b/singlestage/src/components/input/style/base/sera/input_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-input { + &:invalid, + &[aria-invalid="true"] { + @apply border-b-destructive/50; + } + } +} diff --git a/singlestage/src/components/input/style/base/vega/input.css b/singlestage/src/components/input/style/base/vega/input.css new file mode 100644 index 00000000..805e3934 --- /dev/null +++ b/singlestage/src/components/input/style/base/vega/input.css @@ -0,0 +1,34 @@ +@layer components1 { + .singlestage-input { + @apply bg-transparent + border + border-input + h-9 + md:text-sm + px-2.5 + py-1 + rounded-md + shadow-xs + text-base + transition-[color,box-shadow]; + + &[type="file"] { + @apply font-medium + h-7 + text-sm; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/input/style/base/vega/input_dark.css b/singlestage/src/components/input/style/base/vega/input_dark.css new file mode 100644 index 00000000..b40ea09f --- /dev/null +++ b/singlestage/src/components/input/style/base/vega/input_dark.css @@ -0,0 +1,11 @@ +@layer components1 { + .singlestage-input { + @apply bg-input/30; + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input/style/input.css b/singlestage/src/components/input/style/input.css new file mode 100644 index 00000000..d81db8ab --- /dev/null +++ b/singlestage/src/components/input/style/input.css @@ -0,0 +1,25 @@ +@layer components { + .singlestage-input { + @apply min-w-0 + outline-none + w-full; + + &[type="file"] { + @apply bg-transparent + border-0 + inline-flex + text-foreground; + } + + &:disabled, + &[aria-disabled="true"] { + @apply cursor-not-allowed + opacity-50 + pointer-events-none; + } + + &::placeholder { + @apply text-muted-foreground; + } + } +} diff --git a/singlestage/src/components/input_group/addon.rs b/singlestage/src/components/input_group/addon.rs index 5e990e0d..a57b5801 100644 --- a/singlestage/src/components/input_group/addon.rs +++ b/singlestage/src/components/input_group/addon.rs @@ -158,10 +158,10 @@ pub fn InputGroupAddon( format!( "singlestage-input-group-addon {} {}", match align.get().unwrap_or_default().as_str() { - "inline-end" => "singlestage-input-group-addon-inline-end", - "block-start" => "singlestage-input-group-addon-block-start", - "block-end" => "singlestage-input-group-addon-block-end", - _ => "singlestage-input-group-addon-inline-start", + "inline-end" => "singlestage-input-group-addon-align-inline-end", + "block-start" => "singlestage-input-group-addon-align-block-start", + "block-end" => "singlestage-input-group-addon-align-block-end", + _ => "singlestage-input-group-addon-align-inline-start", }, class.get().unwrap_or_default(), ) diff --git a/singlestage/src/components/input_group/group.rs b/singlestage/src/components/input_group/group.rs index e3ba835e..2dc5e3b1 100644 --- a/singlestage/src/components/input_group/group.rs +++ b/singlestage/src/components/input_group/group.rs @@ -1,4 +1,4 @@ -use crate::InputGroupContext; +use crate::{InputGroupContext, Reactive}; use leptos::{context::Provider, prelude::*}; /// The main component that wraps inputs and addons. @@ -8,7 +8,7 @@ pub fn InputGroup( /// Toggle whether or not this input group should appear disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, // GLOBAL ATTRIBUTES // @@ -154,10 +154,8 @@ pub fn InputGroup( view! { <div + aria_disabled=move || { if disabled.get() { Some("true".to_string()) } else { None } } class=move || { format!("singlestage-input-group {}", class.get().unwrap_or_default()) } - data-disabled=move || { - if disabled.get().unwrap_or_default() { Some("true".to_string()) } else { None } - } role="group" {..global_attrs_1} diff --git a/singlestage/src/components/input_group/input_group.css b/singlestage/src/components/input_group/input_group.css deleted file mode 100644 index 02e59534..00000000 --- a/singlestage/src/components/input_group/input_group.css +++ /dev/null @@ -1,232 +0,0 @@ -@layer components { - /* Group */ - - .singlestage-input-group { - @apply border - border-input - flex - h-9 - items-center - min-w-0 - outline-none - relative - rounded-md - shadow-xs - transition-[color,box-shadow] - w-full; - - &[data-disabled="true"] { - .singlestage-input-group-addon { - @apply opacity-50; - } - } - } - - .singlestage-input-group:has([aria-invalid="true"]), - .singlestage-input-group:has(*:invalid) { - @apply border-destructive - ring-destructive/20; - } - - .singlestage-input-group:has( - .singlestage-input:focus-visible, - .singlestage-textarea:focus-visible - ) { - @apply border-ring - ring-[3px] - ring-ring/50; - } - - .singlestage-input-group:has(> input, > textarea) { - .singlestage-input-group-addon-block-start { - @apply pt-2.5; - } - - .singlestage-input-group-addon-block-end { - @apply pb-2.5; - } - } - - .singlestage-input-group:has(> textarea) { - @apply h-auto; - } - - .singlestage-input-group:has(> .singlestage-input-group-addon-inline-start) { - > input { - @apply pl-2; - } - } - - .singlestage-input-group:has(> .singlestage-input-group-addon-inline-end) { - > input { - @apply pr-2; - } - } - - .singlestage-input-group:has(> .singlestage-input-group-addon-block-start) { - @apply flex-col - h-auto; - - > input { - @apply pb-3; - } - } - - .singlestage-input-group:has(> .singlestage-input-group-addon-block-end) { - @apply flex-col - h-auto; - - > input { - @apply pt-3; - } - } - - /* Addon */ - - .singlestage-input-group-addon { - @apply [&>kbd]:rounded-[calc(var(--radius)-5px)] - [&>svg:not([class*='size-'])]:size-4 - cursor-text - flex - font-medium - gap-2 - h-auto - items-center - justify-center - py-1.5 - select-none - text-muted-foreground - text-sm; - } - - .singlestage-input-group-addon-inline-start { - @apply order-first - pl-3; - } - - .singlestage-input-group-addon-inline-start:has(button) { - @apply ml-[-0.45rem]; - } - - .singlestage-input-group-addon-inline-start:has(kbd) { - @apply ml-[-0.35rem]; - } - - .singlestage-input-group-addon-inline-end { - @apply order-last - pr-3; - } - - .singlestage-input-group-addon-inline-end:has(button) { - @apply mr-[-0.45rem]; - } - - .singlestage-input-group-addon-inline-end:has(kbd) { - @apply mr-[-0.35rem]; - } - - .singlestage-input-group-addon-block-start { - @apply justify-start - order-first - pt-3 - px-3 - w-full; - } - - .singlestage-input-group-addon-block-start.border-b { - @apply pb-3; - } - - .singlestage-input-group-addon-block-end { - @apply justify-start - order-last - pb-3 - px-3 - w-full; - } - - .singlestage-input-group-addon-block-end.border-t { - @apply pt-3; - } - - /* Button */ - - .singlestage-input-group-button, - .singlestage-input-group .singlestage-popover button, - .singlestage-input-group-addon > .singlestage-dropdown-menu > button { - @apply !flex - !shadow-none; - } - - .singlestage-input-group-button-xs, - .singlestage-input-group .singlestage-popover button, - .singlestage-input-group-addon > .singlestage-dropdown-menu > button { - @apply !gap-1 - !h-6 - !px-2 - [&>svg:not([class*='size-'])]:!size-3.5 - has-[>svg]:!px-2 - rounded-[calc(var(--radius)-5px)]; - } - - .singlestage-input-group-button-sm { - @apply !gap-1.5 - !h-8 - !px-2.5 - has-[>svg]:!px-2.5 - rounded-md; - } - - .singlestage-input-group-button-icon-xs, - .singlestage-input-group-button-icon-sm { - @apply !p-0 - has-[>svg]:!p-0; - } - - .singlestage-input-group-button-icon-xs { - @apply !size-6 - rounded-[calc(var(--radius)-5px)]; - } - - .singlestage-input-group-button-icon-sm { - @apply !size-8; - } - - /* Input */ - - .singlestage-input-group { - .singlestage-input { - @apply !border-0 - !rounded-none - !shadow-none - flex-1 - focus-visible:!ring-0; - } - } - - /* Text */ - - .singlestage-input-group-text { - @apply [&_svg:not([class*='size-'])]:size-4 - [&_svg]:pointer-events-none - flex - gap-2 - items-center - text-muted-foreground - text-sm; - } - - /* Textarea */ - - .singlestage-input-group { - .singlestage-textarea { - @apply !border-0 - !py-3 - !rounded-none - !shadow-none - flex-1 - focus-visible:!ring-0 - resize-none; - } - } -} diff --git a/singlestage/src/components/input_group/mod.rs b/singlestage/src/components/input_group/mod.rs index c35eb534..5608ed42 100644 --- a/singlestage/src/components/input_group/mod.rs +++ b/singlestage/src/components/input_group/mod.rs @@ -7,4 +7,4 @@ pub use group::*; pub use text::*; #[derive(Clone)] -pub struct InputGroupContext {} +pub(crate) struct InputGroupContext {} diff --git a/singlestage/src/components/input_group/style/base/luma/input_group.css b/singlestage/src/components/input_group/style/base/luma/input_group.css new file mode 100644 index 00000000..4f894dbf --- /dev/null +++ b/singlestage/src/components/input_group/style/base/luma/input_group.css @@ -0,0 +1,213 @@ +@layer components2 { + .singlestage-input-group-addon { + @apply font-medium + gap-2 + h-auto + py-2 + text-muted-foreground + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + > kbd { + @apply bg-muted-foreground/10 + px-1.5 + rounded-3xl; + } + + > svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-addon-align-inline-start { + @apply pl-3; + + &:has(> button, > kbd) { + @apply -ml-1; + } + } + + .singlestage-input-group-addon-align-inline-end { + @apply pr-3; + + &:has(> button, > kbd) { + @apply -mr-1; + } + } + + .singlestage-input-group-addon-align-block-start { + @apply pt-3 + px-3; + + &.border-b { + @apply pb-3.5; + } + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-3 + px-3; + + &.border-t { + @apply pt-3.5; + } + } + + .singlestage-input-group-button { + @apply gap-2 + rounded-4xl + text-sm; + } + + .singlestage-input-group-button-size-xs { + @apply gap-1 + h-6 + px-1.5 + rounded-xl; + + > svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-input-group-button-size-icon-xs { + @apply p-0 + rounded-xl + size-6; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-button-size-icon-sm { + @apply p-0 + size-8; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-text { + @apply gap-2 + text-muted-foreground + text-sm; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-input { + @apply bg-transparent + border-0 + ring-0 + rounded-none + shadow-none; + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group-textarea { + @apply bg-transparent + border-0 + py-2.5 + ring-0 + rounded-none + shadow-none; + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group { + @apply bg-input/50 + border + border-transparent + h-9 + rounded-4xl + transition-[color,box-shadow,background-color]; + + &:has(> .singlestage-input-group-addon-align-block-end) { + @apply flex-col + h-auto; + + & > input { + @apply pt-3; + } + } + + &:has( + .singlestage-input-group-addon-align-block-end, + .singlestage-input-group-addon-align-block-start + ) { + @apply rounded-3xl; + } + + &:has(> .singlestage-input-group-addon-align-block-start) { + @apply flex-col + h-auto; + + & > input { + @apply pb-3; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-end) { + & > input { + @apply pr-1.5; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-start) { + & > input { + @apply pl-1.5; + } + } + + &:has(.singlestage-input-group-control:focus-visible) { + @apply border-ring + ring-3 + ring-ring/30; + } + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &:has(textarea) { + @apply rounded-2xl; + } + + &:has(> input) { + .singlestage-input-group-addon-align-block-start { + @apply pt-3.5; + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-3.5; + } + } + } + + .singlestage-combobox-content { + .singlestage-input-group:focus-within { + @apply border-inherit + ring-0; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/luma/input_group_dark.css b/singlestage/src/components/input_group/style/base/luma/input_group_dark.css new file mode 100644 index 00000000..25d7e723 --- /dev/null +++ b/singlestage/src/components/input_group/style/base/luma/input_group_dark.css @@ -0,0 +1,15 @@ +@layer components2 { + .singlestage-input-group-input { + @apply bg-transparent; + } + + .singlestage-input-group-textarea { + @apply bg-transparent; + } + + .singlestage-input-group { + &:has(*:invalid, *[aria-invalid="true"]) { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/lyra/input_group.css b/singlestage/src/components/input_group/style/base/lyra/input_group.css new file mode 100644 index 00000000..a2c80bcc --- /dev/null +++ b/singlestage/src/components/input_group/style/base/lyra/input_group.css @@ -0,0 +1,225 @@ +@layer components2 { + .singlestage-input-group-addon { + @apply font-medium + gap-2 + h-auto + py-1.5 + text-muted-foreground + text-xs; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + > kbd { + @apply rounded-none; + } + + > svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-addon-align-inline-start { + @apply pl-2; + + &:has(> button) { + @apply ml-[-0.3rem]; + } + + &:has(> kbd) { + @apply ml-[-0.15rem]; + } + } + + .singlestage-input-group-addon-align-inline-end { + @apply pr-2; + + &:has(> button) { + @apply mr-[-0.3rem]; + } + + &:has(> kbd) { + @apply mr-[-0.15rem]; + } + } + + .singlestage-input-group-addon-align-block-start { + @apply pt-2 + px-2.5; + + &.border-b { + @apply pb-2; + } + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-2 + px-2.5; + + &.border-t { + @apply pt-2; + } + } + + .singlestage-input-group-button { + @apply gap-2 + text-xs; + } + + .singlestage-input-group-button-size-xs { + @apply gap-1 + h-6 + px-1.5 + rounded-none; + + > svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-input-group-button-size-icon-xs { + @apply p-0 + rounded-none + size-6; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-button-size-sm { + @apply gap-1; + } + + .singlestage-input-group-button-size-icon-sm { + @apply p-0 + size-7; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-text { + @apply gap-2 + text-muted-foreground + text-xs; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-input { + @apply bg-transparent + border-0 + ring-0 + rounded-none + shadow-none; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-transparent; + } + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group-textarea { + @apply bg-transparent + border-0 + py-2 + ring-0 + rounded-none + shadow-none; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-transparent; + } + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group { + @apply border + border-input + h-8 + rounded-none + transition-colors; + + &:has(> .singlestage-input-group-addon-align-block-end) { + @apply flex-col + h-auto; + + & > input { + @apply pt-3; + } + } + + &:has(> .singlestage-input-group-addon-align-block-start) { + @apply flex-col + h-auto; + + & > input { + @apply pb-3; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-end) { + & > input { + @apply pr-1.5; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-start) { + & > input { + @apply pl-1.5; + } + } + + &:has(.singlestage-input-group-control:focus-visible) { + @apply border-ring + ring-1 + ring-ring/50; + } + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply border-destructive + ring-1 + ring-destructive/20; + } + + &:has(*:disabled, *[aria-disabled="true"]) { + @apply bg-input/50 + opacity-50; + } + + &:has(> input) { + .singlestage-input-group-addon-align-block-start { + @apply pt-2; + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-2; + } + } + } + + .singlestage-combobox-content { + .singlestage-input-group:focus-within { + @apply border-inherit + ring-0; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/lyra/input_group_dark.css b/singlestage/src/components/input_group/style/base/lyra/input_group_dark.css new file mode 100644 index 00000000..fb4abcbb --- /dev/null +++ b/singlestage/src/components/input_group/style/base/lyra/input_group_dark.css @@ -0,0 +1,31 @@ +@layer components2 { + .singlestage-input-group-input { + @apply bg-transparent; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-transparent; + } + } + + .singlestage-input-group-textarea { + @apply bg-transparent; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-transparent; + } + } + + .singlestage-input-group { + @apply bg-input/30; + + &:has(*:disabled, *[aria-disabled="true"]) { + @apply bg-input/80; + } + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/maia/input_group.css b/singlestage/src/components/input_group/style/base/maia/input_group.css new file mode 100644 index 00000000..1917292b --- /dev/null +++ b/singlestage/src/components/input_group/style/base/maia/input_group.css @@ -0,0 +1,219 @@ +@layer components2 { + .singlestage-input-group-addon { + @apply font-medium + gap-2 + h-auto + py-2 + text-muted-foreground + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + > kbd { + @apply bg-muted-foreground/10 + px-1.5 + rounded-4xl; + } + + > svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-addon-align-inline-start { + @apply pl-3; + + &:has(> button) { + @apply -ml-1; + } + + &:has(> kbd) { + @apply ml-[-0.15rem]; + } + } + + .singlestage-input-group-addon-align-inline-end { + @apply pr-3; + + &:has(> button) { + @apply -mr-1; + } + + &:has(> kbd) { + @apply mr-[-0.15rem]; + } + } + + .singlestage-input-group-addon-align-block-start { + @apply pt-3 + px-3; + + &.border-b { + @apply pb-3; + } + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-3 + px-3; + + &.border-t { + @apply pt-3; + } + } + + .singlestage-input-group-button { + @apply gap-2 + rounded-4xl + text-sm; + } + + .singlestage-input-group-button-size-xs { + @apply gap-1 + h-6 + px-1.5; + + > svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-input-group-button-size-icon-xs { + @apply p-0 + size-6; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-button-size-icon-sm { + @apply p-0 + size-8; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-text { + @apply gap-2 + text-muted-foreground + text-sm; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-input { + @apply bg-transparent + border-0 + ring-0 + rounded-none + shadow-none; + + &:focus-visible, + &:invalid, + & [aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group-textarea { + @apply bg-transparent + border-0 + py-2 + ring-0 + rounded-none + shadow-none; + + &:focus-visible, + &:invalid, + & [aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group { + @apply bg-input/30 + border + border-input + h-9 + rounded-4xl + transition-colors; + + &:has(> .singlestage-input-group-addon-align-block-end) { + @apply flex-col + h-auto; + + & > input { + @apply pt-3; + } + } + + &:has(> .singlestage-input-group-addon-align-block-start) { + @apply flex-col + h-auto; + + & > input { + @apply pb-3; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-end) { + & > input { + @apply pr-1.5; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-start) { + & > input { + @apply pl-1.5; + } + } + + &:has(.singlestage-input-group-control:focus-visible) { + @apply border-ring + ring-[3px] + ring-ring/50; + } + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply border-destructive + ring-[3px] + ring-destructive/20; + } + + &:has(textarea) { + @apply rounded-xl; + } + + &:has( + .singlestage-input-group-addon-align-block-end, + .singlestage-input-group-addon-align-block-start + ) { + @apply rounded-2xl; + } + + &:has(> input) { + .singlestage-input-group-addon-align-block-start { + @apply pt-3; + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-3; + } + } + } + + .singlestage-combobox-content { + .singlestage-input-group:focus-within { + @apply border-inherit + ring-0; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/maia/input_group_dark.css b/singlestage/src/components/input_group/style/base/maia/input_group_dark.css new file mode 100644 index 00000000..25d7e723 --- /dev/null +++ b/singlestage/src/components/input_group/style/base/maia/input_group_dark.css @@ -0,0 +1,15 @@ +@layer components2 { + .singlestage-input-group-input { + @apply bg-transparent; + } + + .singlestage-input-group-textarea { + @apply bg-transparent; + } + + .singlestage-input-group { + &:has(*:invalid, *[aria-invalid="true"]) { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/mira/input_group.css b/singlestage/src/components/input_group/style/base/mira/input_group.css new file mode 100644 index 00000000..67d0103b --- /dev/null +++ b/singlestage/src/components/input_group/style/base/mira/input_group.css @@ -0,0 +1,214 @@ +@layer components2 { + .singlestage-input-group-addon { + @apply font-medium + gap-1 + h-auto + py-2 + text-muted-foreground + text-xs/relaxed; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + > kbd { + @apply bg-muted-foreground/10 + px-1 + rounded-[calc(var(--radius-sm)-2px)] + text-[0.625rem]; + } + + > svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-input-group-addon-align-inline-start { + @apply pl-2; + + &:has(> button, > kbd) { + @apply ml-[-0.275rem]; + } + } + + .singlestage-input-group-addon-align-inline-end { + @apply pr-2; + + &:has(> button, > kbd) { + @apply mr-[-0.275rem]; + } + } + + .singlestage-input-group-addon-align-block-start { + @apply pt-2 + px-2; + + &.border-b { + @apply pb-2; + } + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-2 + px-2; + + &.border-t { + @apply pt-2; + } + } + + .singlestage-input-group-button { + @apply gap-2 + rounded-md + text-xs/relaxed; + } + + .singlestage-input-group-button-size-xs { + @apply gap-1 + h-5 + px-1 + rounded-[calc(var(--radius-sm)-2px)]; + + > svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-input-group-button-size-icon-xs { + @apply p-0 + size-6; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-button-size-sm { + @apply gap-1; + } + + .singlestage-input-group-button-size-icon-sm { + @apply p-0 + size-7; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-text { + @apply gap-2 + text-muted-foreground + text-xs/relaxed; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-input { + @apply bg-transparent + border-0 + ring-0 + rounded-none + shadow-none; + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group-textarea { + @apply bg-transparent + border-0 + py-2 + ring-0 + rounded-none + shadow-none; + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group { + @apply bg-input/20 + border + border-input + h-7 + rounded-md + transition-colors; + + &:has(> .singlestage-input-group-addon-align-block-end) { + @apply flex-col + h-auto; + + & > input { + @apply pt-3; + } + } + + &:has(> .singlestage-input-group-addon-align-block-start) { + @apply flex-col + h-auto; + + & > input { + @apply pb-3; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-end) { + & > input { + @apply pr-1.5; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-start) { + & > input { + @apply pl-1.5; + } + } + + &:has(.singlestage-input-group-control:focus-visible) { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply border-destructive + ring-2 + ring-destructive/20; + } + + &:has( + textarea, + .singlestage-input-group-align-block-end, + .singlestage-input-group-align-block-start + ) { + @apply rounded-md; + } + + &:has(> input) { + .singlestage-input-group-addon-align-block-start { + @apply pt-2; + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-2; + } + } + } + + .singlestage-combobox-content { + .singlestage-input-group:focus-within { + @apply border-inherit + ring-0; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/mira/input_group_dark.css b/singlestage/src/components/input_group/style/base/mira/input_group_dark.css new file mode 100644 index 00000000..59a8ca86 --- /dev/null +++ b/singlestage/src/components/input_group/style/base/mira/input_group_dark.css @@ -0,0 +1,17 @@ +@layer components2 { + .singlestage-input-group-input { + @apply bg-transparent; + } + + .singlestage-input-group-textarea { + @apply bg-transparent; + } + + .singlestage-input-group { + @apply bg-input/30; + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/nova/input_group.css b/singlestage/src/components/input_group/style/base/nova/input_group.css new file mode 100644 index 00000000..fa588456 --- /dev/null +++ b/singlestage/src/components/input_group/style/base/nova/input_group.css @@ -0,0 +1,221 @@ +@layer components2 { + .singlestage-input-group-addon { + @apply font-medium + gap-2 + h-auto + py-1.5 + text-muted-foreground + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + > kbd { + @apply rounded-[calc(var(--radius)-5px)]; + } + + > svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-addon-align-inline-start { + @apply pl-2; + + &:has(> button) { + @apply ml-[-0.3rem]; + } + + &:has(> kbd) { + @apply ml-[-0.15rem]; + } + } + + .singlestage-input-group-addon-align-inline-end { + @apply pr-2; + + &:has(> button) { + @apply mr-[-0.3rem]; + } + + &:has(> kbd) { + @apply mr-[-0.15rem]; + } + } + + .singlestage-input-group-addon-align-block-start { + @apply pt-2 + px-2.5; + + &.border-b { + @apply pb-2; + } + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-2 + px-2.5; + + &.border-t { + @apply pt-2; + } + } + + .singlestage-input-group-button { + @apply gap-2 + text-sm; + } + + .singlestage-input-group-button-size-xs { + @apply gap-1 + h-6 + px-1.5 + rounded-[calc(var(--radius)-3px)]; + + > svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-input-group-button-size-icon-xs { + @apply p-0 + rounded-[calc(var(--radius)-3px)] + size-6; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-button-size-icon-sm { + @apply p-0 + size-8; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-text { + @apply gap-2 + text-muted-foreground + text-sm; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-input { + @apply bg-transparent + border-0 + ring-0 + rounded-none + shadow-none; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-transparent; + } + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group-textarea { + @apply bg-transparent + border-0 + py-2 + ring-0 + rounded-none + shadow-none; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-transparent; + } + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group { + @apply border + border-input + h-8 + rounded-lg + transition-colors; + + &:has(> .singlestage-input-group-addon-align-block-end) { + @apply flex-col + h-auto; + + & > input { + @apply pt-3; + } + } + + &:has(> .singlestage-input-group-addon-align-block-start) { + @apply flex-col + h-auto; + + & > input { + @apply pb-3; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-end) { + & > input { + @apply pr-1.5; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-start) { + & > input { + @apply pl-1.5; + } + } + + &:has(.singlestage-input-group-control:focus-visible) { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &:has(*:disabled, *[aria-disabled="true"]) { + @apply bg-input/50 + opacity-50; + } + + &:has(> input) { + .singlestage-input-group-addon-align-block-start { + @apply pt-2; + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-2; + } + } + } + + .singlestage-combobox-content { + .singlestage-input-group:focus-within { + @apply border-inherit + ring-0; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/nova/input_group_dark.css b/singlestage/src/components/input_group/style/base/nova/input_group_dark.css new file mode 100644 index 00000000..fb4abcbb --- /dev/null +++ b/singlestage/src/components/input_group/style/base/nova/input_group_dark.css @@ -0,0 +1,31 @@ +@layer components2 { + .singlestage-input-group-input { + @apply bg-transparent; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-transparent; + } + } + + .singlestage-input-group-textarea { + @apply bg-transparent; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-transparent; + } + } + + .singlestage-input-group { + @apply bg-input/30; + + &:has(*:disabled, *[aria-disabled="true"]) { + @apply bg-input/80; + } + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/sera/input_group.css b/singlestage/src/components/input_group/style/base/sera/input_group.css new file mode 100644 index 00000000..35d1b49d --- /dev/null +++ b/singlestage/src/components/input_group/style/base/sera/input_group.css @@ -0,0 +1,189 @@ +@layer components2 { + .singlestage-input-group-addon { + @apply font-medium + gap-2 + h-auto + py-2 + text-muted-foreground + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + > kbd { + @apply bg-muted-foreground/10 + px-1.5 + rounded-none; + } + + > svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-input-group-addon-align-block-start { + @apply pt-3; + + &.border-b { + @apply pb-3.5; + } + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-3; + + &.border-t { + @apply pt-3.5; + } + } + + .singlestage-input-group-button { + @apply gap-2 + rounded-none + text-sm; + } + + .singlestage-input-group-button-size-xs { + @apply gap-1 + h-6 + px-1.5 + rounded-none + text-xs; + + > svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-input-group-button-size-icon-xs { + @apply p-0 + size-6 + text-xs; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-button-size-icon-sm { + @apply p-0 + size-8; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-text { + @apply gap-2 + text-muted-foreground + text-sm; + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-input-group-input { + @apply bg-transparent + border-0 + ring-0; + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group-textarea { + @apply bg-transparent + border-0 + py-2.5 + ring-0; + + &:focus-visible, + &:invalid, + &[aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group { + @apply bg-transparent + border + border-b-input + border-transparent + h-10 + rounded-none + transition-[color,border-color]; + + &:has(> .singlestage-input-group-addon-align-block-end) { + @apply flex-col + h-auto; + + & > input { + @apply pt-3; + } + } + + &:has(> .singlestage-input-group-addon-align-block-start) { + @apply flex-col + h-auto; + + & > input { + @apply pb-3; + } + } + + &:has( + .singlestage-input-group-addon-align-block-end, + .singlestage-input-group-addon-align-block-start + ) { + @apply rounded-none; + } + + &:has(> .singlestage-input-group-addon-align-inline-end) { + .singlestage-input-group-input { + @apply pr-2; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-start) { + .singlestage-input-group-input { + @apply pl-2; + } + } + + &:has(.singlestage-input-group-control:focus-visible) { + @apply border-b-ring; + } + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply border-b-destructive; + } + + &:has(textarea) { + @apply rounded-none; + } + + &:has(> input) { + .singlestage-input-group-addon-align-block-start { + @apply pt-3.5; + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-3.5; + } + } + } + + .singlestage-combobox-content { + .singlestage-input-group:focus-within { + @apply border-inherit + ring-0; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/sera/input_group_dark.css b/singlestage/src/components/input_group/style/base/sera/input_group_dark.css new file mode 100644 index 00000000..3800856f --- /dev/null +++ b/singlestage/src/components/input_group/style/base/sera/input_group_dark.css @@ -0,0 +1,15 @@ +@layer components2 { + .singlestage-input-group-input { + @apply bg-transparent; + } + + .singlestage-input-group-textarea { + @apply bg-transparent; + } + + .singlestage-input-group { + &:has(*:invalid, *[aria-invalid="true"]) { + @apply border-b-destructive/50; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/vega/input_group.css b/singlestage/src/components/input_group/style/base/vega/input_group.css new file mode 100644 index 00000000..c8c7d6ba --- /dev/null +++ b/singlestage/src/components/input_group/style/base/vega/input_group.css @@ -0,0 +1,210 @@ +@layer components2 { + .singlestage-input-group-addon { + @apply font-medium + gap-2 + h-auto + py-1.5 + text-muted-foreground + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + + > kbd { + @apply rounded-[calc(var(--radius)-5px)]; + } + + > svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-addon-align-inline-start { + @apply pl-2; + + &:has(> button) { + @apply -ml-1; + } + + &:has(> kbd) { + @apply ml-[-0.15rem]; + } + } + + .singlestage-input-group-addon-align-inline-end { + @apply pr-2; + + &:has(> button) { + @apply -mr-1; + } + + &:has(> kbd) { + @apply mr-[-0.15rem]; + } + } + + .singlestage-input-group-addon-align-block-start { + @apply pt-2 + px-2.5; + + &.border-b { + @apply pb-2; + } + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-2 + px-2.5; + + &.border-t { + @apply pt-2; + } + } + + .singlestage-input-group-button { + @apply gap-2 + text-sm; + } + + .singlestage-input-group-button-size-xs { + @apply gap-1 + h-6 + px-1.5 + rounded-[calc(var(--radius)-5px)]; + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-input-group-button-size-icon-xs { + @apply p-0 + rounded-[calc(var(--radius)-5px)] + size-6; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-button-size-icon-sm { + @apply p-0 + size-8; + + &:has(> svg) { + @apply p-0; + } + } + + .singlestage-input-group-text { + @apply gap-2 + text-muted-foreground + text-sm; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-input-group-input { + @apply bg-transparent + border-0 + ring-0 + rounded-none + shadow-none; + + &:focus-visible, + &:invalid, + & [aria-invalid="true"] { + @apply ring-0; + } + } + + .singlestage-input-group-textarea { + @apply bg-transparent + border-0 + py-2 + ring-0 + rounded-none + shadow-none; + + &:invalid, + & [aria-invalid="true"] { + @apply ring-0; + } + + &:focus-visible { + @apply ring-0; + } + } + + .singlestage-input-group { + @apply border + border-input + h-9 + rounded-md + shadow-xs + transition-[color,box-shadow]; + + &:has(> .singlestage-input-group-addon-align-block-end) { + @apply flex-col + h-auto; + + & > input { + @apply pt-3; + } + } + + &:has(> .singlestage-input-group-addon-align-block-start) { + @apply flex-col + h-auto; + + & > input { + @apply pb-3; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-end) { + & > input { + @apply pr-1.5; + } + } + + &:has(> .singlestage-input-group-addon-align-inline-start) { + & > input { + @apply pl-1.5; + } + } + + &:has(.singlestage-input-group-control:focus-visible) { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &:has(> input) { + .singlestage-input-group-addon-align-block-start { + @apply pt-2; + } + + .singlestage-input-group-addon-align-block-end { + @apply pb-2; + } + } + } + + .singlestage-combobox-content { + .singlestage-input-group:focus-within { + @apply border-inherit + ring-0; + } + } +} diff --git a/singlestage/src/components/input_group/style/base/vega/input_group_dark.css b/singlestage/src/components/input_group/style/base/vega/input_group_dark.css new file mode 100644 index 00000000..59a8ca86 --- /dev/null +++ b/singlestage/src/components/input_group/style/base/vega/input_group_dark.css @@ -0,0 +1,17 @@ +@layer components2 { + .singlestage-input-group-input { + @apply bg-transparent; + } + + .singlestage-input-group-textarea { + @apply bg-transparent; + } + + .singlestage-input-group { + @apply bg-input/30; + + &:has(*:invalid, *[aria-invalid="true"]) { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/input_group/style/input_group.css b/singlestage/src/components/input_group/style/input_group.css new file mode 100644 index 00000000..26cf3af7 --- /dev/null +++ b/singlestage/src/components/input_group/style/input_group.css @@ -0,0 +1,66 @@ +@layer components1 { + .singlestage-input-group-addon { + @apply cursor-text + flex + items-center + justify-center + select-none; + } + + .singlestage-input-group-addon-align-inline-start { + @apply order-first; + } + + .singlestage-input-group-addon-align-inline-end { + @apply order-last; + } + + .singlestage-input-group-addon-align-block-start { + @apply justify-start + order-first + w-full; + } + + .singlestage-input-group-addon-align-block-end { + @apply justify-start + order-last + w-full; + } + + .singlestage-input-group-button { + @apply flex + items-center + shadow-none; + } + + .singlestage-input-group-text { + @apply flex + items-center; + + svg { + @apply pointer-events-none; + } + } + + .singlestage-input-group-input { + @apply flex-1; + } + + .singlestage-input-group-textarea { + @apply flex-1 + resize-none; + } + + .singlestage-input-group { + @apply flex + items-center + min-w-0 + outline-none + relative + w-full; + + &:has(> .singlestage-textarea) { + @apply h-auto; + } + } +} diff --git a/singlestage/src/components/item/description.rs b/singlestage/src/components/item/description.rs index 71050a97..344a9eb5 100644 --- a/singlestage/src/components/item/description.rs +++ b/singlestage/src/components/item/description.rs @@ -5,6 +5,10 @@ use leptos::prelude::*; pub fn ItemDescription( children: Children, + /// This component will render without styling + #[prop(optional, into)] + as_child: MaybeProp<bool>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -150,16 +154,33 @@ pub fn ItemDescription( /> }; - view! { - <p - class=move || { - format!("singlestage-item-description {}", class.get().unwrap_or_default()) - } + if as_child.get().unwrap_or_default() { + view! { + <div + class=move || { + format!("singlestage-item-description {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + } + .into_any() + } else { + view! { + <p + class=move || { + format!("singlestage-item-description {}", class.get().unwrap_or_default()) + } - {..global_attrs_1} - {..global_attrs_2} - > - {children()} - </p> + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </p> + } + .into_any() } } diff --git a/singlestage/src/components/item/item.css b/singlestage/src/components/item/item.css deleted file mode 100644 index b36c217f..00000000 --- a/singlestage/src/components/item/item.css +++ /dev/null @@ -1,162 +0,0 @@ -@layer components { - a:has(> .singlestage-item) { - > .singlestage-item { - @apply hover:bg-accent/50 - transition-colors; - } - } - - .singlestage-item { - @apply border - border-transparent - duration-100 - flex - flex-wrap - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - items-center - outline-none - rounded-md - text-sm - transition-colors; - } - - .singlestage-item:has(.singlestage-item-description) { - .singlestage-item-media { - @apply self-start - translate-y-0.5; - } - } - - .singlestage-item-default { - @apply bg-transparent; - } - - .singlestage-item-outline { - @apply border-border; - } - - .singlestage-item-muted { - @apply bg-muted/50; - } - - .singlestage-item-size-default { - @apply gap-4 - p-4; - } - - .singlestage-item-size-small { - @apply gap-2.5 - px-4 - py-3; - } - - .singlestage-item-group { - @apply flex - flex-col; - } - - .singlestage-item-media { - @apply [&_svg]:pointer-events-none - flex - gap-2 - items-center - justify-center - shrink-0; - } - - .singlestage-item-media-default { - @apply bg-transparent; - } - - .singlestage-item-media-icon { - @apply [&_svg:not([class*='size-'])]:size-4 - bg-muted - border - rounded-sm - size-8; - } - - .singlestage-item-media-image { - @apply [&_img]:object-cover - [&_img]:size-full - overflow-hidden - rounded-sm - size-10; - } - - .singlestage-item-content { - @apply flex - flex-1 - flex-col - gap-1; - - & + .singlestage-item-content { - @apply flex-none; - } - } - - .singlestage-item-title { - @apply flex - font-medium - gap-2 - items-center - leading-snug - text-sm - w-fit; - } - - .singlestage-item-description { - @apply font-normal - leading-normal - line-clamp-2 - text-balance - text-muted-foreground - text-sm - w-fit; - - & > a { - @apply hover:text-primary - underline - underline-offset-4; - } - } - - .singlestage-item-actions { - @apply flex - gap-2 - items-center - ml-auto; - } - - .singlestage-item-header { - @apply basis-full - flex - gap-2 - items-center - justify-between; - } - - .singlestage-item-footer { - @apply basis-full - flex - gap-2 - items-center - justify-between; - } - - .singlestage-item-separator { - @apply bg-border - my-0 - shrink-0; - &[data-orientation="horizontal"] { - @apply h-px - w-full; - } - &[data-orientation="vertical"] { - @apply h-full - w-px; - } - } -} diff --git a/singlestage/src/components/item/item.rs b/singlestage/src/components/item/item.rs index 2e1db639..5fe48042 100644 --- a/singlestage/src/components/item/item.rs +++ b/singlestage/src/components/item/item.rs @@ -1,4 +1,5 @@ -use leptos::prelude::*; +use crate::ItemContext; +use leptos::{context::Provider, prelude::*}; /// Contains the contents of an Item. #[component] @@ -7,12 +8,12 @@ pub fn Item( /// Set the display size of the `Item`. /// - /// Accepted values: small | default + /// Accepted values: "xs" | "sm" | "default" #[prop(optional, into)] size: MaybeProp<String>, /// Set the display style of the `Item`. /// - /// Accepted values: outline | muted | default + /// Accepted values: "outline" | "muted" | "default" #[prop(optional, into)] variant: MaybeProp<String>, @@ -167,13 +168,13 @@ pub fn Item( format!( "singlestage-item {} {} {}", match variant.get().unwrap_or_default().as_str() { - "muted" => "singlestage-item-muted", - "outline" => "singlestage-item-outline", - _ => "singlestage-item-default", + "muted" => "singlestage-item-variant-muted", + "outline" => "singlestage-item-variant-outline", + _ => "singlestage-item-variant-default", }, match size.get().unwrap_or_default().as_str() { - "sm" => "singlestage-item-size-small", - "small" => "singlestage-item-size-small", + "xs" | "extra-small" => "singlestage-item-size-xs", + "sm" | "small" => "singlestage-item-size-sm", _ => "singlestage-item-size-default", }, class.get().unwrap_or_default(), @@ -183,7 +184,7 @@ pub fn Item( {..global_attrs_1} {..global_attrs_2} > - {children()} + <Provider value=ItemContext {}>{children()}</Provider> </div> } } diff --git a/singlestage/src/components/item/media.rs b/singlestage/src/components/item/media.rs index 6b292c7e..d8ff8161 100644 --- a/singlestage/src/components/item/media.rs +++ b/singlestage/src/components/item/media.rs @@ -7,7 +7,7 @@ pub fn ItemMedia( /// Set the display style of the `ItemMedia`. /// - /// Accepted values: icon | image + /// Accepted values: "icon" | "image" #[prop(optional, into)] variant: MaybeProp<String>, @@ -162,9 +162,9 @@ pub fn ItemMedia( format!( "singlestage-item-media {} {}", match variant.get().unwrap_or_default().as_str() { - "icon" => "singlestage-item-media-icon", - "image" => "singlestage-item-media-image", - _ => "singlestage-item-media-default", + "icon" => "singlestage-item-media-variant-icon", + "image" => "singlestage-item-media-variant-image", + _ => "singlestage-item-media-variant-default", }, class.get().unwrap_or_default(), ) diff --git a/singlestage/src/components/item/mod.rs b/singlestage/src/components/item/mod.rs index 6bf34327..f1fd2b6a 100644 --- a/singlestage/src/components/item/mod.rs +++ b/singlestage/src/components/item/mod.rs @@ -19,3 +19,6 @@ pub use item::*; pub use media::*; pub use separator::*; pub use title::*; + +#[derive(Clone)] +pub(crate) struct ItemContext {} diff --git a/singlestage/src/components/item/separator.rs b/singlestage/src/components/item/separator.rs index e7c162eb..fc1697f9 100644 --- a/singlestage/src/components/item/separator.rs +++ b/singlestage/src/components/item/separator.rs @@ -1,11 +1,15 @@ +use crate::Separator; use leptos::prelude::*; -/// Separates Item content. +/// The separator. #[component] pub fn ItemSeparator( - /// Toggle whether or not the separator should display vertically. + #[prop(optional)] children: Option<Children>, + + /// Specify the orientation of the separator + /// Accepted values: `"vertical"` | `"horizontal"` (default) #[prop(optional, into)] - vertical: MaybeProp<bool>, + orientation: MaybeProp<String>, // GLOBAL ATTRIBUTES // @@ -117,7 +121,7 @@ pub fn ItemSeparator( 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() @@ -152,17 +156,27 @@ pub fn ItemSeparator( /> }; - view! { - <div - class=move || format!("singlestage-item-separator {}", class.get().unwrap_or_default()) - data-orientation=match vertical.get().unwrap_or_default() { - false => "horizontal", - true => "vertical", - } - .to_string() + if let Some(children) = children { + view! { + <Separator + orientation + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </Separator> + } + .into_any() + } else { + view! { + <Separator + orientation - {..global_attrs_1} - {..global_attrs_2} - ></div> + {..global_attrs_1} + {..global_attrs_2} + /> + } + .into_any() } } diff --git a/singlestage/src/components/item/style/base/luma/item.css b/singlestage/src/components/item/style/base/luma/item.css new file mode 100644 index 00000000..6d455e06 --- /dev/null +++ b/singlestage/src/components/item/style/base/luma/item.css @@ -0,0 +1,148 @@ +@layer components1 { + .singlestage-item-variant-default { + @apply border-transparent; + } + + .singlestage-item-variant-outline { + @apply border-border; + } + + .singlestage-item-variant-muted { + @apply bg-muted/50 + border-transparent; + } + + .singlestage-item-size-default { + @apply gap-3.5 + px-4 + py-3.5; + } + + .singlestage-item-size-sm { + @apply gap-3.5 + px-3.5 + py-3; + } + + .singlestage-item-size-xs { + @apply gap-2.5 + px-3 + py-2.5; + } + + .singlestage-dropdown-menu-content { + .singlestage-item-size-xs { + @apply p-0; + } + } + + .singlestage-item-media { + @apply gap-2; + } + + .singlestage-item-media-variant-default { + @apply bg-transparent; + } + + .singlestage-item-media-variant-icon { + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-item-media-variant-image { + @apply overflow-hidden + rounded-xl + size-10; + + img { + @apply object-cover + size-full; + } + } + + .singlestage-item-content { + @apply gap-1; + } + + .singlestage-item-title { + @apply font-medium + gap-2 + leading-snug + text-sm + underline-offset-4; + } + + .singlestage-item-description { + @apply text-left + text-muted-foreground + text-sm; + } + + .singlestage-item-actions { + @apply gap-2; + } + + .singlestage-item-header { + @apply gap-2; + } + + .singlestage-item-footer { + @apply gap-2; + } + + .singlestage-item-group { + @apply gap-4; + + &:has(.singlestage-item-size-xs) { + @apply gap-2; + } + + &:has(.singlestage-item-size-sm) { + @apply gap-2.5; + } + } + + .singlestage-item-separator { + @apply my-2; + } + + .singlestage-item { + @apply border + rounded-2xl + text-sm; + + &:has(.singlestage-item-description) { + .singlestage-item-media { + @apply self-start + translate-y-0.5; + } + } + + &.singlestage-item-size-sm { + .singlestage-item-media-variant-image { + @apply size-8; + } + } + + &.singlestage-item-size-xs { + .singlestage-item-media-variant-image { + @apply rounded-lg + size-6; + } + + .singlestage-item-content { + @apply gap-0.5; + } + } + } + + a:has(> .singlestage-item) { + @apply rounded-2xl; + + &:hover { + @apply bg-muted + rounded-2xl; + } + } +} diff --git a/singlestage/src/components/item/style/base/lyra/item.css b/singlestage/src/components/item/style/base/lyra/item.css new file mode 100644 index 00000000..ab01c5ed --- /dev/null +++ b/singlestage/src/components/item/style/base/lyra/item.css @@ -0,0 +1,150 @@ +@layer components1 { + .singlestage-item-variant-default { + @apply border-transparent; + } + + .singlestage-item-variant-outline { + @apply border-border; + } + + .singlestage-item-variant-muted { + @apply bg-muted/50 + border-transparent; + } + + .singlestage-item-size-default { + @apply gap-2.5 + px-3 + py-2.5; + } + + .singlestage-item-size-sm { + @apply gap-2.5 + px-3 + py-2.5; + } + + .singlestage-item-size-xs { + @apply gap-2 + px-2.5 + py-2; + } + + .singlestage-dropdown-menu-content { + .singlestage-item-size-xs { + @apply p-0; + } + } + + .singlestage-item-media { + @apply gap-2; + } + + .singlestage-item-media-variant-default { + @apply bg-transparent; + } + + .singlestage-item-media-variant-icon { + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-item-media-variant-image { + @apply overflow-hidden + rounded-none + size-10; + + img { + @apply object-cover + size-full; + } + } + + .singlestage-item-content { + @apply gap-1; + } + + .singlestage-item-title { + @apply font-medium + gap-2 + text-xs + underline-offset-4; + } + + .singlestage-item-description { + @apply text-left + text-muted-foreground + text-xs/relaxed; + } + + .singlestage-item-actions { + @apply gap-2; + } + + .singlestage-item-header { + @apply gap-2; + } + + .singlestage-item-footer { + @apply gap-2; + } + + .singlestage-item-group { + @apply gap-4; + + &:has(.singlestage-item-size-xs) { + @apply gap-2; + } + + &:has(.singlestage-item-size-sm) { + @apply gap-2.5; + } + } + + .singlestage-item-separator { + @apply my-2; + } + + .singlestage-item { + @apply border + rounded-none + text-xs; + + &:has(.singlestage-item-description) { + .singlestage-item-media { + @apply self-start + translate-y-0.5; + } + } + + &.singlestage-item-size-sm { + .singlestage-item-media-variant-image { + @apply size-8; + } + } + + &.singlestage-item-size-xs { + .singlestage-item-media-variant-image { + @apply size-6; + } + + .singlestage-item-content { + @apply gap-0; + } + + .singlestage-item-description { + @apply text-xs/relaxed; + } + } + } + + a:has(> .singlestage-item) { + @apply rounded-none; + + &:hover { + @apply bg-muted + rounded-none; + } + } +} diff --git a/singlestage/src/components/item/style/base/maia/item.css b/singlestage/src/components/item/style/base/maia/item.css new file mode 100644 index 00000000..ee59e12f --- /dev/null +++ b/singlestage/src/components/item/style/base/maia/item.css @@ -0,0 +1,148 @@ +@layer components1 { + .singlestage-item-variant-default { + @apply border-transparent; + } + + .singlestage-item-variant-outline { + @apply border-border; + } + + .singlestage-item-variant-muted { + @apply bg-muted/50 + border-transparent; + } + + .singlestage-item-size-default { + @apply gap-3.5 + px-4 + py-3.5; + } + + .singlestage-item-size-sm { + @apply gap-3.5 + px-3.5 + py-3; + } + + .singlestage-item-size-xs { + @apply gap-2.5 + px-3 + py-2.5; + + .singlestage-dropdown-menu-content { + .singlestage-item-size-xs { + @apply p-0; + } + } + } + + .singlestage-item-media { + @apply gap-2; + } + + .singlestage-item-media-variant-default { + @apply bg-transparent; + } + + .singlestage-item-media-variant-icon { + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-item-media-variant-image { + @apply overflow-hidden + rounded-lg + size-10; + + img { + @apply object-cover + size-full; + } + } + + .singlestage-item-content { + @apply gap-1; + } + + .singlestage-item-title { + @apply font-medium + gap-2 + leading-snug + text-sm + underline-offset-4; + } + + .singlestage-item-description { + @apply text-left + text-muted-foreground + text-sm; + } + + .singlestage-item-actions { + @apply gap-2; + } + + .singlestage-item-header { + @apply gap-2; + } + + .singlestage-item-footer { + @apply gap-2; + } + + .singlestage-item-group { + @apply gap-4; + + &:has(.singlestage-item-size-xs) { + @apply gap-2; + } + + &:has(.singlestage-item-size-sm) { + @apply gap-2.5; + } + } + + .singlestage-item-separator { + @apply my-2; + } + + .singlestage-item { + @apply border + rounded-2xl + text-sm; + + &:has(.singlestage-item-description) { + .singlestage-item-media { + @apply self-start + translate-y-0.5; + } + } + + &.singlestage-item-size-sm { + .singlestage-item-media-variant-image { + @apply size-8; + } + } + + &.singlestage-item-size-xs { + .singlestage-item-media-variant-image { + @apply rounded-md + size-6; + } + + .singlestage-item-content { + @apply gap-0.5; + } + } + } + + a:has(> .singlestage-item) { + @apply rounded-2xl; + + &:hover { + @apply bg-muted + rounded-2xl; + } + } +} diff --git a/singlestage/src/components/item/style/base/mira/item.css b/singlestage/src/components/item/style/base/mira/item.css new file mode 100644 index 00000000..280c4e59 --- /dev/null +++ b/singlestage/src/components/item/style/base/mira/item.css @@ -0,0 +1,147 @@ +@layer components1 { + .singlestage-item-variant-default { + @apply border-transparent; + } + + .singlestage-item-variant-outline { + @apply border-border; + } + + .singlestage-item-variant-muted { + @apply bg-muted/50 + border-transparent; + } + + .singlestage-item-size-default { + @apply gap-2.5 + px-3 + py-2.5; + } + + .singlestage-item-size-sm { + @apply gap-2.5 + px-3 + py-2.5; + } + + .singlestage-item-size-xs { + @apply gap-2.5 + px-2.5 + py-2; + + .singlestage-dropdown-menu-content { + .singlestage-item-size-xs { + @apply p-0; + } + } + } + + .singlestage-item-media { + @apply gap-2; + } + + .singlestage-item-media-variant-default { + @apply bg-transparent; + } + + .singlestage-item-media-variant-icon { + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-item-media-variant-image { + @apply overflow-hidden + rounded-sm + size-8; + + img { + @apply object-cover + size-full; + } + } + + .singlestage-item-content { + @apply gap-1; + } + + .singlestage-item-title { + @apply font-medium + gap-2 + leading-snug + text-xs/relaxed + underline-offset-4; + } + + .singlestage-item-description { + @apply text-left + text-muted-foreground + text-xs/relaxed; + } + + .singlestage-item-actions { + @apply gap-2; + } + + .singlestage-item-header { + @apply gap-2; + } + + .singlestage-item-footer { + @apply gap-2; + } + + .singlestage-item-group { + @apply gap-4; + + &:has(.singlestage-item-size-xs) { + @apply gap-2; + } + + &:has(.singlestage-item-size-sm) { + @apply gap-2.5; + } + } + + .singlestage-item-separator { + @apply my-2; + } + + .singlestage-item { + @apply border + rounded-md + text-xs/relaxed; + + &:has(.singlestage-item-description) { + .singlestage-item-media { + @apply self-start + translate-y-0.5; + } + } + + &.singlestage-item-size-sm { + .singlestage-item-media-variant-image { + @apply size-8; + } + } + + &.singlestage-item-size-xs { + .singlestage-item-media-variant-image { + @apply size-6; + } + + .singlestage-item-content { + @apply gap-0.5; + } + } + } + + a:has(> .singlestage-item) { + @apply rounded-md; + + &:hover { + @apply bg-muted + rounded-md; + } + } +} diff --git a/singlestage/src/components/item/style/base/nova/item.css b/singlestage/src/components/item/style/base/nova/item.css new file mode 100644 index 00000000..408e23c3 --- /dev/null +++ b/singlestage/src/components/item/style/base/nova/item.css @@ -0,0 +1,152 @@ +@layer components1 { + .singlestage-item-variant-default { + @apply border-transparent; + } + + .singlestage-item-variant-outline { + @apply border-border; + } + + .singlestage-item-variant-muted { + @apply bg-muted/50 + border-transparent; + } + + .singlestage-item-size-default { + @apply gap-2.5 + px-3 + py-2.5; + } + + .singlestage-item-size-sm { + @apply gap-2.5 + px-3 + py-2.5; + } + + .singlestage-item-size-xs { + @apply gap-2 + px-2.5 + py-2; + } + + .singlestage-dropdown-menu-content { + .singlestage-item-size-xs { + @apply p-0; + } + } + + .singlestage-item-media { + @apply gap-2; + } + + .singlestage-item-media-variant-default { + @apply bg-transparent; + } + + .singlestage-item-media-variant-icon { + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-item-media-variant-image { + @apply overflow-hidden + rounded-sm + size-10; + + img { + @apply object-cover + size-full; + } + } + + .singlestage-item-content { + @apply gap-1; + } + + .singlestage-item-title { + @apply font-medium + gap-2 + leading-snug + text-sm + underline-offset-4; + } + + .singlestage-item-description { + @apply leading-normal + text-left + text-muted-foreground + text-sm; + } + + .singlestage-item-actions { + @apply gap-2; + } + + .singlestage-item-header { + @apply gap-2; + } + + .singlestage-item-footer { + @apply gap-2; + } + + .singlestage-item-group { + @apply gap-4; + + &:has(.singlestage-item-size-xs) { + @apply gap-2; + } + + &:has(.singlestage-item-size-sm) { + @apply gap-2.5; + } + } + + .singlestage-item-separator { + @apply my-2; + } + + .singlestage-item { + @apply border + rounded-lg + text-sm; + + &:has(.singlestage-item-description) { + .singlestage-item-media { + @apply self-start + translate-y-0.5; + } + } + + &.singlestage-item-size-sm { + .singlestage-item-media-variant-image { + @apply size-8; + } + } + + &.singlestage-item-size-xs { + .singlestage-item-media-variant-image { + @apply size-6; + } + + .singlestage-item-content { + @apply gap-0; + } + + .singlestage-item-description { + @apply text-xs; + } + } + } + + a:has(> .singlestage-item) { + @apply rounded-lg; + + &:hover { + @apply bg-muted + rounded-lg; + } + } +} diff --git a/singlestage/src/components/item/style/base/sera/item.css b/singlestage/src/components/item/style/base/sera/item.css new file mode 100644 index 00000000..467fe4e5 --- /dev/null +++ b/singlestage/src/components/item/style/base/sera/item.css @@ -0,0 +1,150 @@ +@layer components1 { + .singlestage-item-variant-default { + @apply border-transparent; + } + + .singlestage-item-variant-outline { + @apply border-border; + } + + .singlestage-item-variant-muted { + @apply bg-muted/50 + border-transparent; + } + + .singlestage-item-size-default { + @apply gap-3.5 + px-4 + py-3.5; + } + + .singlestage-item-size-sm { + @apply gap-3.5 + px-3.5 + py-3; + } + + .singlestage-item-size-xs { + @apply gap-2.5 + px-3 + py-2.5; + } + + .singlestage-dropdown-menu-content { + .singlestage-item-size-xs { + @apply p-0; + } + } + + .singlestage-item-media { + @apply gap-2; + } + + .singlestage-item-media-variant-default { + @apply bg-transparent; + } + + .singlestage-item-media-variant-icon { + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-item-media-variant-image { + @apply overflow-hidden + rounded-none + size-10; + + img { + @apply object-cover + size-full; + } + } + + .singlestage-item-content { + @apply gap-1; + } + + .singlestage-item-title { + @apply font-semibold + gap-2 + leading-snug + text-xs + underline-offset-4 + uppercase; + } + + .singlestage-item-description { + @apply leading-relaxed + text-left + text-muted-foreground + text-sm; + } + + .singlestage-item-actions { + @apply gap-2; + } + + .singlestage-item-header { + @apply gap-2; + } + + .singlestage-item-footer { + @apply gap-2; + } + + .singlestage-item-group { + @apply gap-4; + + &:has(.singlestage-item-size-xs) { + @apply gap-2; + } + + &:has(.singlestage-item-size-sm) { + @apply gap-2.5; + } + } + + .singlestage-item-separator { + @apply my-2; + } + + .singlestage-item { + @apply border + rounded-none + text-sm; + + &:has(.singlestage-item-description) { + .singlestage-item-media { + @apply self-start + translate-y-0.5; + } + } + + &.singlestage-item-size-sm { + .singlestage-item-media-variant-image { + @apply size-8; + } + } + + &.singlestage-item-size-xs { + .singlestage-item-media-variant-image { + @apply rounded-none + size-6; + } + + .singlestage-item-content { + @apply gap-0.5; + } + } + } + + a:has(> .singlestage-item) { + @apply rounded-none; + + &:hover { + @apply bg-muted + rounded-none; + } + } +} diff --git a/singlestage/src/components/item/style/base/vega/item.css b/singlestage/src/components/item/style/base/vega/item.css new file mode 100644 index 00000000..e77c187d --- /dev/null +++ b/singlestage/src/components/item/style/base/vega/item.css @@ -0,0 +1,152 @@ +@layer components1 { + .singlestage-item-variant-default { + @apply border-transparent; + } + + .singlestage-item-variant-outline { + @apply border-border; + } + + .singlestage-item-variant-muted { + @apply bg-muted/50 + border-transparent; + } + + .singlestage-item-size-default { + @apply gap-3.5 + px-4 + py-3.5; + } + + .singlestage-item-size-sm { + @apply gap-2.5 + px-3 + py-2.5; + } + + .singlestage-item-size-xs { + @apply gap-2 + px-2.5 + py-2; + } + + .singlestage-dropdown-menu-content { + .singlestage-item-size-xs { + @apply p-0; + } + } + + .singlestage-item-media { + @apply gap-2; + } + + .singlestage-item-media-variant-default { + @apply bg-transparent; + } + + .singlestage-item-media-variant-icon { + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-item-media-variant-image { + @apply overflow-hidden + rounded-sm + size-10; + + img { + @apply object-cover + size-full; + } + } + + .singlestage-item-content { + @apply gap-1; + } + + .singlestage-item-title { + @apply font-medium + gap-2 + leading-snug + text-sm + underline-offset-4; + } + + .singlestage-item-description { + @apply leading-normal + text-left + text-muted-foreground + text-sm; + } + + .singlestage-item-actions { + @apply gap-2; + } + + .singlestage-item-header { + @apply gap-2; + } + + .singlestage-item-footer { + @apply gap-2; + } + + .singlestage-item-group { + @apply gap-4; + + &:has(.singlestage-item-size-xs) { + @apply gap-2; + } + + &:has(.singlestage-item-size-sm) { + @apply gap-2.5; + } + } + + .singlestage-item-separator { + @apply my-2; + } + + .singlestage-item { + @apply border + rounded-md + text-sm; + + &:has(.singlestage-item-description) { + .singlestage-item-media { + @apply self-start + translate-y-0.5; + } + } + + &.singlestage-item-size-sm { + .singlestage-item-media-variant-image { + @apply size-8; + } + } + + &.singlestage-item-size-xs { + .singlestage-item-media-variant-image { + @apply size-6; + } + + .singlestage-item-content { + @apply gap-0; + } + + .singlestage-item-description { + @apply text-xs; + } + } + } + + a:has(> .singlestage-item) { + @apply rounded-md; + + &:hover { + @apply bg-muted + rounded-md; + } + } +} diff --git a/singlestage/src/components/item/style/item.css b/singlestage/src/components/item/style/item.css new file mode 100644 index 00000000..afe7b93b --- /dev/null +++ b/singlestage/src/components/item/style/item.css @@ -0,0 +1,88 @@ +@layer components { + .singlestage-item-group { + @apply flex + flex-col + w-full; + } + + .singlestage-item { + @apply duration-100 + flex + flex-wrap + items-center + outline-none + transition-colors + w-full; + + &:focus-visible { + @apply border-ring + ring-[3px] + ring-ring/50; + } + } + + a:has(> .singlestage-item) { + @apply transition-colors; + } + + .singlestage-item-media { + @apply flex + items-center + justify-center + shrink-0; + + svg { + @apply pointer-events-none; + } + } + + .singlestage-item-content { + @apply flex + flex-1 + flex-col; + + & + .singlestage-item-content { + @apply flex-none; + } + } + + .singlestage-item-title { + @apply flex + items-center + line-clamp-1 + w-fit; + } + + .singlestage-item-description { + @apply line-clamp-2 + font-normal; + + a:not(.singlestage-button) { + @apply underline + underline-offset-4; + + &:hover { + @apply text-primary; + } + } + } + + .singlestage-item-actions { + @apply flex + items-center; + } + + .singlestage-item-header { + @apply basis-full + flex + items-center + justify-between; + } + + .singlestage-item-footer { + @apply basis-full + flex + items-center + justify-between; + } +} diff --git a/singlestage/src/components/kbd/kbd.css b/singlestage/src/components/kbd/kbd.css deleted file mode 100644 index ecc99b4c..00000000 --- a/singlestage/src/components/kbd/kbd.css +++ /dev/null @@ -1,27 +0,0 @@ -@layer components { - .singlestage-kbd { - @apply [&_svg:not([class*='size-'])]:size-3 - bg-muted - font-medium - font-sans - gap-1 - h-5 - inline-flex - items-center - justify-center - min-w-5 - pointer-events-none - px-1 - rounded-sm - select-none - text-muted-foreground - text-xs - w-fit; - } - - .singlestage-kbd-group { - @apply gap-1 - inline-flex - items-center; - } -} diff --git a/singlestage/src/components/kbd/style/base/luma/kbd.css b/singlestage/src/components/kbd/style/base/luma/kbd.css new file mode 100644 index 00000000..5fc5ac41 --- /dev/null +++ b/singlestage/src/components/kbd/style/base/luma/kbd.css @@ -0,0 +1,36 @@ +@layer components1 { + .singlestage-kbd { + @apply bg-muted + font-medium + font-sans + gap-1 + h-5.5 + min-w-5.5 + px-1.5 + rounded-lg + text-muted-foreground + text-xs + w-fit; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-input-group { + .singlestage-kbd { + @apply bg-input; + } + } + + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/20 + text-background; + } + } + + .singlestage-kbd-group { + @apply gap-1; + } +} diff --git a/singlestage/src/components/kbd/style/base/luma/kbd_dark.css b/singlestage/src/components/kbd/style/base/luma/kbd_dark.css new file mode 100644 index 00000000..48636fdb --- /dev/null +++ b/singlestage/src/components/kbd/style/base/luma/kbd_dark.css @@ -0,0 +1,7 @@ +@layer components1 { + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/10; + } + } +} diff --git a/singlestage/src/components/kbd/style/base/lyra/kbd.css b/singlestage/src/components/kbd/style/base/lyra/kbd.css new file mode 100644 index 00000000..237328f8 --- /dev/null +++ b/singlestage/src/components/kbd/style/base/lyra/kbd.css @@ -0,0 +1,30 @@ +@layer components1 { + .singlestage-kbd { + @apply bg-muted + font-medium + font-sans + gap-1 + h-5 + min-w-5 + px-1 + rounded-none + text-muted-foreground + text-xs + w-fit; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/20 + text-background; + } + } + + .singlestage-kbd-group { + @apply gap-1; + } +} diff --git a/singlestage/src/components/kbd/style/base/lyra/kbd_dark.css b/singlestage/src/components/kbd/style/base/lyra/kbd_dark.css new file mode 100644 index 00000000..48636fdb --- /dev/null +++ b/singlestage/src/components/kbd/style/base/lyra/kbd_dark.css @@ -0,0 +1,7 @@ +@layer components1 { + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/10; + } + } +} diff --git a/singlestage/src/components/kbd/style/base/maia/kbd.css b/singlestage/src/components/kbd/style/base/maia/kbd.css new file mode 100644 index 00000000..7daa2e79 --- /dev/null +++ b/singlestage/src/components/kbd/style/base/maia/kbd.css @@ -0,0 +1,30 @@ +@layer components1 { + .singlestage-kbd { + @apply bg-muted + font-medium + font-sans + gap-1 + h-5 + min-w-5 + px-1 + rounded-sm + text-muted-foreground + text-xs + w-fit; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/20 + text-background; + } + } + + .singlestage-kbd-group { + @apply gap-1; + } +} diff --git a/singlestage/src/components/kbd/style/base/maia/kbd_dark.css b/singlestage/src/components/kbd/style/base/maia/kbd_dark.css new file mode 100644 index 00000000..48636fdb --- /dev/null +++ b/singlestage/src/components/kbd/style/base/maia/kbd_dark.css @@ -0,0 +1,7 @@ +@layer components1 { + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/10; + } + } +} diff --git a/singlestage/src/components/kbd/style/base/mira/kbd.css b/singlestage/src/components/kbd/style/base/mira/kbd.css new file mode 100644 index 00000000..81e98687 --- /dev/null +++ b/singlestage/src/components/kbd/style/base/mira/kbd.css @@ -0,0 +1,30 @@ +@layer components1 { + .singlestage-kbd { + @apply bg-muted + font-medium + font-sans + gap-1 + h-5 + min-w-5 + px-1 + rounded-xs + text-[0.625rem] + text-muted-foreground + w-fit; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/20 + text-background; + } + } + + .singlestage-kbd-group { + @apply gap-1; + } +} diff --git a/singlestage/src/components/kbd/style/base/mira/kbd_dark.css b/singlestage/src/components/kbd/style/base/mira/kbd_dark.css new file mode 100644 index 00000000..48636fdb --- /dev/null +++ b/singlestage/src/components/kbd/style/base/mira/kbd_dark.css @@ -0,0 +1,7 @@ +@layer components1 { + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/10; + } + } +} diff --git a/singlestage/src/components/kbd/style/base/nova/kbd.css b/singlestage/src/components/kbd/style/base/nova/kbd.css new file mode 100644 index 00000000..7daa2e79 --- /dev/null +++ b/singlestage/src/components/kbd/style/base/nova/kbd.css @@ -0,0 +1,30 @@ +@layer components1 { + .singlestage-kbd { + @apply bg-muted + font-medium + font-sans + gap-1 + h-5 + min-w-5 + px-1 + rounded-sm + text-muted-foreground + text-xs + w-fit; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/20 + text-background; + } + } + + .singlestage-kbd-group { + @apply gap-1; + } +} diff --git a/singlestage/src/components/kbd/style/base/nova/kbd_dark.css b/singlestage/src/components/kbd/style/base/nova/kbd_dark.css new file mode 100644 index 00000000..48636fdb --- /dev/null +++ b/singlestage/src/components/kbd/style/base/nova/kbd_dark.css @@ -0,0 +1,7 @@ +@layer components1 { + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/10; + } + } +} diff --git a/singlestage/src/components/kbd/style/base/sera/kbd.css b/singlestage/src/components/kbd/style/base/sera/kbd.css new file mode 100644 index 00000000..c4b0c7dd --- /dev/null +++ b/singlestage/src/components/kbd/style/base/sera/kbd.css @@ -0,0 +1,36 @@ +@layer components1 { + .singlestage-kbd { + @apply bg-muted + font-medium + font-sans + gap-1 + h-5.5 + min-w-5.5 + px-1.5 + rounded-none + text-muted-foreground + text-xs + w-fit; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-input-group { + .singlestage-kbd { + @apply bg-input; + } + } + + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/20 + text-background; + } + } + + .singlestage-kbd-group { + @apply gap-1; + } +} diff --git a/singlestage/src/components/kbd/style/base/sera/kbd_dark.css b/singlestage/src/components/kbd/style/base/sera/kbd_dark.css new file mode 100644 index 00000000..48636fdb --- /dev/null +++ b/singlestage/src/components/kbd/style/base/sera/kbd_dark.css @@ -0,0 +1,7 @@ +@layer components1 { + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/10; + } + } +} diff --git a/singlestage/src/components/kbd/style/base/vega/kbd.css b/singlestage/src/components/kbd/style/base/vega/kbd.css new file mode 100644 index 00000000..7daa2e79 --- /dev/null +++ b/singlestage/src/components/kbd/style/base/vega/kbd.css @@ -0,0 +1,30 @@ +@layer components1 { + .singlestage-kbd { + @apply bg-muted + font-medium + font-sans + gap-1 + h-5 + min-w-5 + px-1 + rounded-sm + text-muted-foreground + text-xs + w-fit; + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/20 + text-background; + } + } + + .singlestage-kbd-group { + @apply gap-1; + } +} diff --git a/singlestage/src/components/kbd/style/base/vega/kbd_dark.css b/singlestage/src/components/kbd/style/base/vega/kbd_dark.css new file mode 100644 index 00000000..48636fdb --- /dev/null +++ b/singlestage/src/components/kbd/style/base/vega/kbd_dark.css @@ -0,0 +1,7 @@ +@layer components1 { + .singlestage-tooltip-content { + .singlestage-kbd { + @apply bg-background/10; + } + } +} diff --git a/singlestage/src/components/kbd/style/kbd.css b/singlestage/src/components/kbd/style/kbd.css new file mode 100644 index 00000000..57670e88 --- /dev/null +++ b/singlestage/src/components/kbd/style/kbd.css @@ -0,0 +1,14 @@ +@layer components { + .singlestage-kbd { + @apply inline-flex + items-center + justify-center + pointer-events-none + select-none; + } + + .singlestage-kbd-group { + @apply inline-flex + items-center; + } +} diff --git a/singlestage/src/components/label/label.css b/singlestage/src/components/label/label.css deleted file mode 100644 index 262ffa60..00000000 --- a/singlestage/src/components/label/label.css +++ /dev/null @@ -1,19 +0,0 @@ -@layer components { - .singlestage-label { - @apply flex - font-medium - gap-2 - items-center - leading-none - peer-disabled:opacity-50 - peer-disabled:pointer-events-none - select-none - text-sm; - - &:has(> *:disabled), - &:has(+ *:disabled) { - @apply opacity-50 - pointer-events-none; - } - } -} diff --git a/singlestage/src/components/label/mod.rs b/singlestage/src/components/label/mod.rs index 2c2febf3..b5a04cdf 100644 --- a/singlestage/src/components/label/mod.rs +++ b/singlestage/src/components/label/mod.rs @@ -1,3 +1,4 @@ +use crate::{Reactive, primitives::*}; use leptos::prelude::*; /// Renders an accessible label associated with controls @@ -5,6 +6,16 @@ use leptos::prelude::*; pub fn Label( children: Children, + /// Whether the element renders as disabled + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + /// (For use in popover menus) + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Whether the element renders as invalid + #[prop(optional, into)] + invalid: Reactive<bool>, /// The id of the labeled element if it's not a child #[prop(optional, into)] label_for: MaybeProp<String>, @@ -119,14 +130,12 @@ pub fn Label( 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() @@ -155,14 +164,19 @@ pub fn Label( }; view! { - <label - for=move || label_for.get() - class=move || format!("singlestage-label {}", class.get().unwrap_or_default()) + <LabelPrimitive + primitive_type=LabelPrimitiveType::Label + class + disabled + id + inset + invalid + label_for {..global_attrs_1} {..global_attrs_2} > {children()} - </label> + </LabelPrimitive> } } diff --git a/singlestage/src/components/label/style/base/luma/label.css b/singlestage/src/components/label/style/base/luma/label.css new file mode 100644 index 00000000..ccb35788 --- /dev/null +++ b/singlestage/src/components/label/style/base/luma/label.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-label { + @apply font-medium + gap-2 + leading-none + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *), + &:is(:where(*):disabled ~ *), + &:is(:where(*)[aria-disabled="true"] ~ *) { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/label/style/base/lyra/label.css b/singlestage/src/components/label/style/base/lyra/label.css new file mode 100644 index 00000000..a9a80a38 --- /dev/null +++ b/singlestage/src/components/label/style/base/lyra/label.css @@ -0,0 +1,14 @@ +@layer baes { + .singlestage-label { + @apply gap-2 + leading-none + text-xs; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *), + &:is(:where(*):disabled ~ *), + &:is(:where(*)[aria-disabled="true"] ~ *) { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/label/style/base/maia/label.css b/singlestage/src/components/label/style/base/maia/label.css new file mode 100644 index 00000000..ccb35788 --- /dev/null +++ b/singlestage/src/components/label/style/base/maia/label.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-label { + @apply font-medium + gap-2 + leading-none + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *), + &:is(:where(*):disabled ~ *), + &:is(:where(*)[aria-disabled="true"] ~ *) { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/label/style/base/mira/label.css b/singlestage/src/components/label/style/base/mira/label.css new file mode 100644 index 00000000..b8783784 --- /dev/null +++ b/singlestage/src/components/label/style/base/mira/label.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-label { + @apply font-medium + gap-2 + leading-none + text-xs/relaxed; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *), + &:is(:where(*):disabled ~ *), + &:is(:where(*)[aria-disabled="true"] ~ *) { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/label/style/base/nova/label.css b/singlestage/src/components/label/style/base/nova/label.css new file mode 100644 index 00000000..ccb35788 --- /dev/null +++ b/singlestage/src/components/label/style/base/nova/label.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-label { + @apply font-medium + gap-2 + leading-none + text-sm; + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *), + &:is(:where(*):disabled ~ *), + &:is(:where(*)[aria-disabled="true"] ~ *) { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/label/style/base/sera/label.css b/singlestage/src/components/label/style/base/sera/label.css new file mode 100644 index 00000000..bd51e4de --- /dev/null +++ b/singlestage/src/components/label/style/base/sera/label.css @@ -0,0 +1,26 @@ +@layer components1 { + .singlestage-label { + @apply font-semibold + gap-2 + leading-relaxed + text-xs + tracking-wide + uppercase; + + &:is( + :where(.singlestage-checkbox, .singlestage-radio, .singlestage-switch) ~ * + ) { + @apply font-normal + normal-case + text-sm + tracking-normal; + } + + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *), + &:is(:where(*):disabled ~ *), + &:is(:where(*)[aria-disabled="true"] ~ *) { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/label/style/base/vega/label.css b/singlestage/src/components/label/style/base/vega/label.css new file mode 100644 index 00000000..b4892651 --- /dev/null +++ b/singlestage/src/components/label/style/base/vega/label.css @@ -0,0 +1,13 @@ +@layer components1 { + .singlestage-label { + @apply font-medium + gap-2 + leading-none + text-sm; + + &:is(:where(*):disabled ~ *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/label/style/label.css b/singlestage/src/components/label/style/label.css new file mode 100644 index 00000000..786e5593 --- /dev/null +++ b/singlestage/src/components/label/style/label.css @@ -0,0 +1,41 @@ +@layer components { + .singlestage-label { + @apply flex + items-center + select-none; + + /* Peer */ + &:disabled, + &[aria-disabled="true"], + &:is(:where(*):disabled ~ *), + &:is(:where(*)[aria-disabled="true"] ~ *) { + @apply cursor-not-allowed; + } + + /* Group */ + &:disabled, + &[aria-disabled="true"], + &:is(:where(*):disabled *), + &:is(:where(*)[aria-disabled="true"] *) { + @apply pointer-events-none; + } + + &:invalid, + &[aria-invalid="true"], + &:is(:where(*):invalid *), + &:is(:where(*):invalid ~ *), + &:is(:where(*)[aria-invalid="true"] *), + &:is(:where(*)[aria-invalid="true"] ~ *) { + @apply text-destructive; + } + + &:disabled, + &[aria-disabled="true"], + &:is(:where(*):disabled *), + &:is(:where(*):disabled ~ *), + &:is(:where(*)[aria-disabled="true"] *), + &:is(:where(*)[aria-disabled="true"] ~ *) { + @apply opacity-50; + } + } +} diff --git a/singlestage/src/components/link/mod.rs b/singlestage/src/components/link/mod.rs index ac29f432..60c1ca89 100644 --- a/singlestage/src/components/link/mod.rs +++ b/singlestage/src/components/link/mod.rs @@ -1,18 +1,28 @@ -use crate::InputGroupContext; +use crate::{ + BreadcrumbLinkContext, InputGroupContext, SheetContext, SidebarMenuButtonContext, primitives::*, +}; use leptos::prelude::*; +use leptos_router::components::A; /// Creates a styled hyperlink. #[component] pub fn Link( children: Children, - /// Set whether or not this `Link` should appear as a `Button`. + /// This component will render without styling #[prop(optional, into)] - as_button: MaybeProp<bool>, - /// For use with `as_button`, specify the size to render the button. + as_child: MaybeProp<bool>, + + /// Set whether or not this `Link` should appear as something else. This is similar to + /// `asChild`. + /// + /// Accepted values: "button" | "badge" + #[prop(optional, into)] + render_as: MaybeProp<String>, + /// For use with `render_as`, specify the size to render the element. #[prop(optional, into)] size: MaybeProp<String>, - /// For use with `as_button`, specify the style of button to render. + /// For use with `render_as`, specify the variant of the element to render. #[prop(optional, into)] variant: MaybeProp<String>, @@ -44,6 +54,11 @@ pub fn Link( #[prop(optional, into)] target: MaybeProp<String>, + // ARIA ATTRIBUTES + // + #[prop(optional, into)] aria_current: MaybeProp<String>, + #[prop(optional, into)] aria_label: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -201,50 +216,113 @@ pub fn Link( /> }; + let in_sidebar_menu_button: bool = use_context::<SidebarMenuButtonContext>().is_some(); + let in_popover_menu_item: bool = use_context::<MenuItemContext>().is_some(); + let in_breadcrumb_link: bool = use_context::<BreadcrumbLinkContext>().is_some(); + view! { - <a - class=move || { - if as_button.get().unwrap_or_default() { - format!( - "{} {} {} {}", - match variant.get().unwrap_or_default().as_str() { - "primary" => "singlestage-btn-primary", - "secondary" => "singlestage-btn-secondary", - "outline" => "singlestage-btn-outline", - "ghost" => "singlestage-btn-ghost", - "link" => "singlestage-btn-link", - "destructive" => "singlestage-btn-destructive", - _ => "singlestage-btn-link", - }, - match size.get().unwrap_or_default().as_str() { - "sm" => "singlestage-btn-sm", - "small" => "singlestage-btn-sm", - "lg" => "singlestage-btn-lg", - "large" => "singlestage-btn-lg", - "icon" => "singlestage-btn-icon", - "sm-icon" => "singlestage-btn-sm-icon", - "icon-sm" => "singlestage-btn-sm-icon", - "lg-icon" => "singlestage-btn-lg-icon", - "icon-lg" => "singlestage-btn-lg-icon", - _ => "", - }, - if use_context::<InputGroupContext>().is_some() { + <A + attr:aria_current=move || aria_current.get() + attr:aria_label=move || aria_label.get() + attr:class=move || { + format!( + "{} {}", + match render_as.get().unwrap_or_default().as_str() { + "button" => { format!( - "singlestage-input-group-button {}", + "singlestage-button {} {} {}{}", + match variant.get().unwrap_or_default().as_str() { + "secondary" => "singlestage-button-variant-secondary", + "outline" => "singlestage-button-variant-outline", + "ghost" => "singlestage-button-variant-ghost", + "link" => "singlestage-button-variant-link", + "destructive" => "singlestage-button-variant-destructive", + _ => { + if use_context::<InputGroupContext>().is_some() + && variant.get().is_none() + { + "singlestage-button-variant-ghost" + } else { + "singlestage-button-variant-default" + } + } + }, match size.get().unwrap_or_default().as_str() { - "sm" => "singlestage-input-group-button-sm", - "icon-xs" => "singlestage-input-group-button-icon-xs", - "icon-sm" => "singlestage-input-group-button-icon-sm", - _ => "singlestage-input-group-button-xs", + "xs" | "extra small" => "singlestage-button-size-xs", + "sm" | "small" => "singlestage-button-size-sm", + "lg" | "large" => "singlestage-button-size-lg", + "icon" => "singlestage-button-size-icon", + "xs-icon" | "icon-xs" | "icon extra small" + | "extra small icon" => "singlestage-button-size-icon-xs", + "sm-icon" | "icon-sm" | "icon small" | "small icon" => { + "singlestage-button-size-icon-sm" + } + "lg-icon" | "icon-lg" | "icon large" | "large icon" => { + "singlestage-button-size-icon-lg" + } + _ => "singlestage-button-size-default", + }, + if use_context::<InputGroupContext>().is_some() { + format!( + "singlestage-input-group-button {}", + match size.get().unwrap_or_default().as_str() { + "sm" => "singlestage-input-group-button-sm", + "icon-xs" => "singlestage-input-group-button-icon-xs", + "icon-sm" => "singlestage-input-group-button-icon-sm", + _ => "singlestage-input-group-button-xs", + }, + ) + } else { + "".to_string() + }, + if in_sidebar_menu_button { " w-full" } else { "" }, + ) + } + "badge" => { + format!( + "singlestage-badge {}", + match variant.get().unwrap_or_default().as_str() { + "secondary" => "singlestage-badge-variant-secondary", + "destructive" => "singlestage-badge-variant-destructive", + "outline" => "singlestage-badge-variant-outline", + "ghost" => "singlestage-badge-variant-ghost", + "link" => "singlestage-badge-variant-link", + _ => "singlestage-badge-variant-default", }, ) - } else { - "".to_string() - }, - class.get().unwrap_or_default(), - ) - } else { - format!("singlestage-link {}", class.get().unwrap_or_default()) + } + _ => { + if as_child.get().unwrap_or_default() || in_popover_menu_item { + "".to_string() + } else if in_sidebar_menu_button { + format!( + "singlestage-sidebar-menu-button {} {}", + match size.get().unwrap_or_default().as_str() { + "sm" | "small" => "singlestage-sidebar-menu-button-size-sm", + "lg" | "large" => "singlestage-sidebar-menu-button-size-lg", + _ => "singlestage-sidebar-menu-button-size-default", + }, + match variant.get().unwrap_or_default().as_str() { + "outline" => { + "singlestage-sidebar-menu-button-variant-outline" + } + _ => "singlestage-sidebar-menu-button-variant-default", + }, + ) + } else if in_breadcrumb_link { + "singlestage-breadcrumb-link".to_string() + } else { + "singlestage-link".to_string() + } + } + }, + class.get().unwrap_or_default(), + ) + } + href=move || href.get().unwrap_or_default() + on:click=move |_| { + if let Some(sheet) = use_context::<SheetContext>() && in_sidebar_menu_button { + sheet.open.set(false) } } @@ -253,6 +331,6 @@ pub fn Link( {..a_attrs} > {children()} - </a> + </A> } } diff --git a/singlestage/src/components/link/link.css b/singlestage/src/components/link/style/link.css similarity index 100% rename from singlestage/src/components/link/link.css rename to singlestage/src/components/link/style/link.css diff --git a/singlestage/src/components/mod.rs b/singlestage/src/components/mod.rs index 096855f2..6e147a34 100644 --- a/singlestage/src/components/mod.rs +++ b/singlestage/src/components/mod.rs @@ -8,6 +8,11 @@ pub mod alert; #[cfg(feature = "alert")] pub use alert::*; +#[cfg(feature = "alert_dialog")] +pub mod alert_dialog; +#[cfg(feature = "alert_dialog")] +pub use alert_dialog::*; + #[cfg(feature = "aspect_ratio")] pub mod aspect_ratio; #[cfg(feature = "aspect_ratio")] @@ -53,6 +58,11 @@ pub mod checkbox; #[cfg(feature = "checkbox")] pub use checkbox::*; +#[cfg(feature = "collapsible")] +pub mod collapsible; +#[cfg(feature = "collapsible")] +pub use collapsible::*; + #[cfg(feature = "context_menu")] pub mod context_menu; #[cfg(feature = "context_menu")] @@ -143,6 +153,11 @@ pub mod separator; #[cfg(feature = "separator")] pub use separator::*; +#[cfg(feature = "sheet")] +pub mod sheet; +#[cfg(feature = "sheet")] +pub use sheet::*; + #[cfg(feature = "sidebar")] pub mod sidebar; #[cfg(feature = "sidebar")] diff --git a/singlestage/src/components/pagination/link.rs b/singlestage/src/components/pagination/link.rs index 74bfe945..373127b4 100644 --- a/singlestage/src/components/pagination/link.rs +++ b/singlestage/src/components/pagination/link.rs @@ -1,3 +1,4 @@ +use crate::Link; use leptos::prelude::*; /// A pagination link @@ -8,6 +9,7 @@ pub fn PaginationLink( /// Toggle whether or not this button should render with active styling #[prop(optional, into)] active: MaybeProp<bool>, + #[prop(optional, into)] size: MaybeProp<String>, // A ATTRIBUTES // @@ -37,6 +39,10 @@ pub fn PaginationLink( #[prop(optional, into)] target: MaybeProp<String>, + // Aria ATTRIBUTES + // + #[prop(optional, into)] aria_label: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -147,7 +153,7 @@ pub fn PaginationLink( 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() @@ -196,26 +202,22 @@ pub fn PaginationLink( }; view! { - <a {..a_attrs}> - <button - class=move || { - format!( - "{} singlestage-btn-icon {}", - { - match active.get() { - Some(true) => "singlestage-btn-outline", - _ => "singlestage-btn-ghost", - } - }, - class.get().unwrap_or_default(), - ) - } + <Link + aria_current=if active.get_untracked().unwrap_or_default() { + Some("page".to_string()) + } else { + None + } + aria_label=aria_label.get_untracked() + render_as="button" + size=size.get().unwrap_or("icon".to_string()) + variant=if active.get_untracked().unwrap_or_default() { "outline" } else { "ghost" } - {..global_attrs_1} - {..global_attrs_2} - > - {children()} - </button> - </a> + {..a_attrs} + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </Link> } } diff --git a/singlestage/src/components/pagination/next.rs b/singlestage/src/components/pagination/next.rs index 0b2429f1..fe033a3d 100644 --- a/singlestage/src/components/pagination/next.rs +++ b/singlestage/src/components/pagination/next.rs @@ -1,9 +1,10 @@ +use crate::PaginationLink; use leptos::prelude::*; -/// Pagination next button +/// A pagination next button #[component] pub fn PaginationNext( - children: Children, + #[prop(optional)] children: Option<Children>, // A ATTRIBUTES // @@ -138,12 +139,25 @@ pub fn PaginationNext( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { + let a_attrs = view! { + <{..} + download=move || download.get() + href=move || href.get() + hreflang=move || hreflang.get() + type=move || mimetype.get() + ping=move || ping.get() + referrerpolicy=move || referrerpolicy.get() + rel=move || rel.get() + target=move || target.get() + /> + }; + 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() @@ -178,44 +192,36 @@ pub fn PaginationNext( /> }; - let a_attrs = view! { - <{..} - download=move || download.get() - href=move || href.get() - hreflang=move || hreflang.get() - type=move || mimetype.get() - ping=move || ping.get() - referrerpolicy=move || referrerpolicy.get() - rel=move || rel.get() - target=move || target.get() - /> - }; - view! { - <a {..a_attrs}> - <button - class=move || { - format!("singlestage-btn-ghost {}", class.get().unwrap_or_default()) - } + <PaginationLink + aria_label="Go to next page" + size="default" - {..global_attrs_1} - {..global_attrs_2} + {..a_attrs} + {..global_attrs_1} + {..global_attrs_2} + > + <span class="singlestage-pagination-next-text"> + {if let Some(children) = children { + children().into_any() + } else { + "Next".into_any() + }} + </span> + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + class="lucide lucide-chevron-right-icon lucide-chevron-right" > - {children()} - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" - > - <path d="m9 18 6-6-6-6" /> - </svg> - </button> - </a> + <path d="m9 18 6-6-6-6" /> + </svg> + </PaginationLink> } } diff --git a/singlestage/src/components/pagination/pagination.rs b/singlestage/src/components/pagination/pagination.rs index 6dfa1e19..4c02b334 100644 --- a/singlestage/src/components/pagination/pagination.rs +++ b/singlestage/src/components/pagination/pagination.rs @@ -152,7 +152,7 @@ pub fn Pagination( view! { <nav - aria-label="pagination" + aria_label="pagination" role="navigation" class=move || format!("singlestage-pagination {}", class.get().unwrap_or_default()) diff --git a/singlestage/src/components/pagination/previous.rs b/singlestage/src/components/pagination/previous.rs index 2b4df3c7..05e9a0dd 100644 --- a/singlestage/src/components/pagination/previous.rs +++ b/singlestage/src/components/pagination/previous.rs @@ -1,9 +1,10 @@ +use crate::PaginationLink; use leptos::prelude::*; /// A pagination previous button #[component] pub fn PaginationPrevious( - children: Children, + #[prop(optional)] children: Option<Children>, // A ATTRIBUTES // @@ -138,12 +139,25 @@ pub fn PaginationPrevious( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { + let a_attrs = view! { + <{..} + download=move || download.get() + href=move || href.get() + hreflang=move || hreflang.get() + type=move || mimetype.get() + ping=move || ping.get() + referrerpolicy=move || referrerpolicy.get() + rel=move || rel.get() + target=move || target.get() + /> + }; + 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() @@ -178,44 +192,36 @@ pub fn PaginationPrevious( /> }; - let a_attrs = view! { - <{..} - download=move || download.get() - href=move || href.get() - hreflang=move || hreflang.get() - type=move || mimetype.get() - ping=move || ping.get() - referrerpolicy=move || referrerpolicy.get() - rel=move || rel.get() - target=move || target.get() - /> - }; - view! { - <a {..a_attrs}> - <button - class=move || { - format!("singlestage-btn-ghost {}", class.get().unwrap_or_default()) - } + <PaginationLink + aria_label="Go to previous page" + size="default" - {..global_attrs_1} - {..global_attrs_2} + {..a_attrs} + {..global_attrs_1} + {..global_attrs_2} + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + class="lucide lucide-chevron-left-icon lucide-chevron-left" > - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" - > - <path d="m15 18-6-6 6-6" /> - </svg> - {children()} - </button> - </a> + <path d="m15 18-6-6 6-6" /> + </svg> + <span class="singlestage-pagination-previous-text"> + {if let Some(children) = children { + children().into_any() + } else { + "Previous".into_any() + }} + </span> + </PaginationLink> } } diff --git a/singlestage/src/components/pagination/style/base/luma/pagination.css b/singlestage/src/components/pagination/style/base/luma/pagination.css new file mode 100644 index 00000000..0b69437e --- /dev/null +++ b/singlestage/src/components/pagination/style/base/luma/pagination.css @@ -0,0 +1,23 @@ +@layer components1 { + .cn-pagination-content { + @apply gap-1; + } + + .cn-pagination-ellipsis { + @apply items-center + justify-center + size-9; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .cn-pagination-previous { + @apply pl-2!; + } + + .cn-pagination-next { + @apply pr-2!; + } +} diff --git a/singlestage/src/components/pagination/style/base/lyra/pagination.css b/singlestage/src/components/pagination/style/base/lyra/pagination.css new file mode 100644 index 00000000..a6acda95 --- /dev/null +++ b/singlestage/src/components/pagination/style/base/lyra/pagination.css @@ -0,0 +1,23 @@ +@layer components1 { + .singlestage-pagination-content { + @apply gap-0.5; + } + + .singlestage-pagination-ellipsis { + @apply items-center + justify-center + size-8; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-pagination-previous { + @apply pl-1.5!; + } + + .singlestage-pagination-next { + @apply pr-1.5!; + } +} diff --git a/singlestage/src/components/pagination/style/base/maia/pagination.css b/singlestage/src/components/pagination/style/base/maia/pagination.css new file mode 100644 index 00000000..4bc99182 --- /dev/null +++ b/singlestage/src/components/pagination/style/base/maia/pagination.css @@ -0,0 +1,23 @@ +@layer components1 { + .singlestage-pagination-content { + @apply gap-1; + } + + .singlestage-pagination-ellipsis { + @apply items-center + justify-center + size-9; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-pagination-previous { + @apply pl-2!; + } + + .singlestage-pagination-next { + @apply pr-2!; + } +} diff --git a/singlestage/src/components/pagination/style/base/mira/pagination.css b/singlestage/src/components/pagination/style/base/mira/pagination.css new file mode 100644 index 00000000..17d73af3 --- /dev/null +++ b/singlestage/src/components/pagination/style/base/mira/pagination.css @@ -0,0 +1,23 @@ +@layer components1 { + .singlestage-pagination-content { + @apply gap-0.5; + } + + .singlestage-pagination-ellipsis { + @apply items-center + justify-center + size-7; + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-pagination-previous { + @apply pl-2!; + } + + .singlestage-pagination-next { + @apply pr-2!; + } +} diff --git a/singlestage/src/components/pagination/style/base/nova/pagination.css b/singlestage/src/components/pagination/style/base/nova/pagination.css new file mode 100644 index 00000000..a6acda95 --- /dev/null +++ b/singlestage/src/components/pagination/style/base/nova/pagination.css @@ -0,0 +1,23 @@ +@layer components1 { + .singlestage-pagination-content { + @apply gap-0.5; + } + + .singlestage-pagination-ellipsis { + @apply items-center + justify-center + size-8; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-pagination-previous { + @apply pl-1.5!; + } + + .singlestage-pagination-next { + @apply pr-1.5!; + } +} diff --git a/singlestage/src/components/pagination/style/base/sera/pagination.css b/singlestage/src/components/pagination/style/base/sera/pagination.css new file mode 100644 index 00000000..4bc99182 --- /dev/null +++ b/singlestage/src/components/pagination/style/base/sera/pagination.css @@ -0,0 +1,23 @@ +@layer components1 { + .singlestage-pagination-content { + @apply gap-1; + } + + .singlestage-pagination-ellipsis { + @apply items-center + justify-center + size-9; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-pagination-previous { + @apply pl-2!; + } + + .singlestage-pagination-next { + @apply pr-2!; + } +} diff --git a/singlestage/src/components/pagination/style/base/vega/pagination.css b/singlestage/src/components/pagination/style/base/vega/pagination.css new file mode 100644 index 00000000..4bc99182 --- /dev/null +++ b/singlestage/src/components/pagination/style/base/vega/pagination.css @@ -0,0 +1,23 @@ +@layer components1 { + .singlestage-pagination-content { + @apply gap-1; + } + + .singlestage-pagination-ellipsis { + @apply items-center + justify-center + size-9; + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-pagination-previous { + @apply pl-2!; + } + + .singlestage-pagination-next { + @apply pr-2!; + } +} diff --git a/singlestage/src/components/pagination/pagination.css b/singlestage/src/components/pagination/style/pagination.css similarity index 65% rename from singlestage/src/components/pagination/pagination.css rename to singlestage/src/components/pagination/style/pagination.css index f0277623..0913050b 100644 --- a/singlestage/src/components/pagination/pagination.css +++ b/singlestage/src/components/pagination/style/pagination.css @@ -5,16 +5,21 @@ mx-auto w-full; } + .singlestage-pagination-content { @apply flex - flex-row - gap-1 items-center; } + + .singlestage-pagination-next-text, + .singlestage-pagination-previous-text { + @apply hidden + sm:block; + } + .singlestage-pagination-ellipsis { @apply flex items-center - justify-center - size-9; + justify-center; } } diff --git a/singlestage/src/components/popover/content.rs b/singlestage/src/components/popover/content.rs index 57c438ae..075a4a5c 100644 --- a/singlestage/src/components/popover/content.rs +++ b/singlestage/src/components/popover/content.rs @@ -6,6 +6,19 @@ use leptos::prelude::*; pub fn PopoverContent( children: Children, + /// Set how to align the popover + /// + /// Accepted values: "start" | "center" | "end" + /// Default is "start" + #[prop(optional, into)] + align: MaybeProp<String>, + /// Set which side the popover opens relative to the trigger + /// + /// Accepted values: "top" | "right" | "bottom" | "left" + /// Default is "bottom" + #[prop(optional, into)] + side: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -106,6 +119,13 @@ pub fn PopoverContent( translate: MaybeProp<String>, ) -> impl IntoView { let menu = expect_context::<PopoverContext>(); + let menu_ref = NodeRef::<leptos::html::Menu>::new(); + + Effect::new(move || { + if let Some(popover) = menu_ref.get() { + let _ = popover.toggle_popover_with_force(menu.open.get()); + } + }); let global_attrs_1 = view! { <{..} @@ -145,13 +165,31 @@ pub fn PopoverContent( view! { <menu - class=move || { format!("singlestage-popover {}", class.get().unwrap_or_default()) } + class=move || { + format!( + "singlestage-popover-content singlestage-popover singlestage-popover-animations {} {} {}", + match side.get().unwrap_or_default().as_str() { + "top" => "singlestage-popover-top", + "right" => "singlestage-popover-right", + "left" => "singlestage-popover-left", + _ => "singlestage-popover-bottom", + }, + match align.get().unwrap_or_default().as_str() { + "center" => "singlestage-popover-center", + "end" => "singlestage-popover-end", + _ => "singlestage-popover-start", + }, + class.get().unwrap_or_default(), + ) + } + node_ref=menu_ref id={ let menu_id = id.get().unwrap_or(uuid::Uuid::new_v4().to_string()); menu.menu_id.set(menu_id.clone()); menu_id } - popover="auto" + popover=move || if !menu.modal.get() { "auto" } else { "manual" } + style:position-anchor=move || { format!("--{}", menu.trigger_id.get()) } role="menu" {..global_attrs_1} diff --git a/singlestage/src/components/popover/description.rs b/singlestage/src/components/popover/description.rs new file mode 100644 index 00000000..9b2849f2 --- /dev/null +++ b/singlestage/src/components/popover/description.rs @@ -0,0 +1,165 @@ +use leptos::prelude::*; + +/// Displays a description of popover content. +#[component] +pub fn PopoverDescription( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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! { + <p + class=move || { + format!("singlestage-popover-description {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </p> + } +} diff --git a/singlestage/src/components/popover/header.rs b/singlestage/src/components/popover/header.rs new file mode 100644 index 00000000..19702f79 --- /dev/null +++ b/singlestage/src/components/popover/header.rs @@ -0,0 +1,163 @@ +use leptos::prelude::*; + +/// A styled header container for use within PopoverContent. +#[component] +pub fn PopoverHeader( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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! { + <hgroup + class=move || format!("singlestage-popover-header {}", class.get().unwrap_or_default()) + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </hgroup> + } +} diff --git a/singlestage/src/components/popover/mod.rs b/singlestage/src/components/popover/mod.rs index e0058970..9f335019 100644 --- a/singlestage/src/components/popover/mod.rs +++ b/singlestage/src/components/popover/mod.rs @@ -1,18 +1,24 @@ mod content; +mod description; +mod header; mod popover; +mod title; mod trigger; pub use content::*; +pub use description::*; +pub use header::*; pub use popover::*; +pub use title::*; pub use trigger::*; +use crate::Reactive; use leptos::prelude::*; #[derive(Clone)] -pub struct PopoverContext { +pub(crate) struct PopoverContext { + pub modal: Reactive<bool>, pub menu_id: RwSignal<String>, + pub open: Reactive<bool>, pub trigger_id: RwSignal<String>, } - -#[derive(Clone)] -pub struct PopoverTriggerContext {} diff --git a/singlestage/src/components/popover/popover.css b/singlestage/src/components/popover/popover.css deleted file mode 100644 index 1e0b83bd..00000000 --- a/singlestage/src/components/popover/popover.css +++ /dev/null @@ -1,36 +0,0 @@ -@layer components { - /* Hacks to render properly until anchor support - https://developer.mozilla.org/en-US/docs/Web/CSS/anchor#browser_compatibility - */ - /* Webkit -- HAS TO BE FIRST */ - @media screen and (-webkit-min-device-pixel-ratio: 0) { - .singlestage-popover { - @apply relative - bg-transparent; - position-area: bottom span-right; - inset: unset; - } - } - - /* Firefox */ - @-moz-document url-prefix() { - .singlestage-popover { - @apply relative - bg-transparent; - inset: unset; - } - } - /* End hacks */ - - .singlestage-popover { - @apply bg-popover - border - overflow-x-hidden - overflow-y-auto - p-4 - rounded-md - shadow-md - text-popover-foreground - z-50; - } -} diff --git a/singlestage/src/components/popover/popover.rs b/singlestage/src/components/popover/popover.rs index 97ca8d78..1206b2c1 100644 --- a/singlestage/src/components/popover/popover.rs +++ b/singlestage/src/components/popover/popover.rs @@ -1,4 +1,4 @@ -use crate::PopoverContext; +use crate::{PopoverContext, Reactive}; use leptos::{context::Provider, prelude::*}; /// Contains all the parts of a popover. @@ -6,6 +6,15 @@ use leptos::{context::Provider, prelude::*}; pub fn Popover( children: Children, + /// Set whether or not this popover should be modal meaning it can't be light dismissed + /// Use this along with the `open` signal for a manually managed popover + #[prop(optional, into, default = Reactive::new(false))] + modal: Reactive<bool>, + /// Reactive signal that can remotely control the open state of the popover **but is not + /// coupled to the actual open state of the popover** unless `modal` is set to `true` + #[prop(optional, into)] + open: Reactive<bool>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -115,7 +124,9 @@ pub fn Popover( let trigger_id = RwSignal::new(String::new()); let context = PopoverContext { + modal, menu_id, + open, trigger_id, }; diff --git a/singlestage/src/components/popover/style/base/luma/popover.css b/singlestage/src/components/popover/style/base/luma/popover.css new file mode 100644 index 00000000..999568aa --- /dev/null +++ b/singlestage/src/components/popover/style/base/luma/popover.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-popover-content { + @apply bg-popover + flex + flex-col + gap-4 + p-4 + ring-1 + ring-foreground/5 + rounded-3xl + shadow-lg + text-popover-foreground + text-sm; + } + + .singlestage-popover-header { + @apply flex + flex-col + gap-1 + text-sm; + } + + .singlestage-popover-title { + @apply font-medium + text-base; + } + + .singlestage-popover-description { + @apply text-muted-foreground; + } +} diff --git a/singlestage/src/components/popover/style/base/luma/popover_dark.css b/singlestage/src/components/popover/style/base/luma/popover_dark.css new file mode 100644 index 00000000..7d178646 --- /dev/null +++ b/singlestage/src/components/popover/style/base/luma/popover_dark.css @@ -0,0 +1,5 @@ +@layer components1 { + .singlestage-popover-content { + @apply ring-foreground/10; + } +} diff --git a/singlestage/src/components/popover/style/base/lyra/popover.css b/singlestage/src/components/popover/style/base/lyra/popover.css new file mode 100644 index 00000000..5a3811c7 --- /dev/null +++ b/singlestage/src/components/popover/style/base/lyra/popover.css @@ -0,0 +1,32 @@ +@layer components1 { + .singlestage-popover-content { + @apply bg-popover + flex + flex-col + gap-2.5 + p-2.5 + ring-1 + ring-foreground/10 + rounded-none + shadow-md + text-popover-foreground + text-xs; + } + + .singlestage-popover-header { + @apply flex + flex-col + gap-1 + text-xs; + } + + .singlestage-popover-title { + @apply font-medium + text-sm; + } + + .singlestage-popover-description { + @apply text-muted-foreground + text-xs/relaxed; + } +} diff --git a/singlestage/src/components/popover/style/base/maia/popover.css b/singlestage/src/components/popover/style/base/maia/popover.css new file mode 100644 index 00000000..c8bcda31 --- /dev/null +++ b/singlestage/src/components/popover/style/base/maia/popover.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-popover-content { + @apply bg-popover + flex + flex-col + gap-4 + p-4 + ring-1 + ring-foreground/5 + rounded-2xl + shadow-2xl + text-popover-foreground + text-sm; + } + + .singlestage-popover-header { + @apply flex + flex-col + gap-1 + text-sm; + } + + .singlestage-popover-title { + @apply font-medium + text-base; + } + + .singlestage-popover-description { + @apply text-muted-foreground; + } +} diff --git a/singlestage/src/components/popover/style/base/mira/popover.css b/singlestage/src/components/popover/style/base/mira/popover.css new file mode 100644 index 00000000..da2a5f01 --- /dev/null +++ b/singlestage/src/components/popover/style/base/mira/popover.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-popover-content { + @apply bg-popover + flex + flex-col + gap-4 + p-2.5 + ring-1 + ring-foreground/10 + rounded-lg + shadow-md + text-popover-foreground + text-xs; + } + + .singlestage-popover-header { + @apply flex + flex-col + gap-1 + text-xs; + } + + .singlestage-popover-title { + @apply font-medium + text-sm; + } + + .singlestage-popover-description { + @apply text-muted-foreground; + } +} diff --git a/singlestage/src/components/popover/style/base/nova/popover.css b/singlestage/src/components/popover/style/base/nova/popover.css new file mode 100644 index 00000000..e4acbc0a --- /dev/null +++ b/singlestage/src/components/popover/style/base/nova/popover.css @@ -0,0 +1,30 @@ +@layer components1 { + .singlestage-popover-content { + @apply bg-popover + flex + flex-col + gap-2.5 + p-2.5 + ring-1 + ring-foreground/10 + rounded-lg + shadow-md + text-popover-foreground + text-sm; + } + + .singlestage-popover-header { + @apply flex + flex-col + gap-0.5 + text-sm; + } + + .singlestage-popover-title { + @apply font-medium; + } + + .singlestage-popover-description { + @apply text-muted-foreground; + } +} diff --git a/singlestage/src/components/popover/style/base/sera/popover.css b/singlestage/src/components/popover/style/base/sera/popover.css new file mode 100644 index 00000000..05a22191 --- /dev/null +++ b/singlestage/src/components/popover/style/base/sera/popover.css @@ -0,0 +1,35 @@ +@layer components1 { + .singlestage-popover-content { + @apply bg-popover + flex + flex-col + gap-4 + p-4 + ring-1 + ring-foreground/10 + rounded-none + shadow-md + text-popover-foreground + text-sm; + } + + .singlestage-popover-header { + @apply flex + flex-col + gap-1 + text-sm; + } + + .singlestage-popover-title { + @apply font-semibold + text-xs + uppercase; + } + + .singlestage-popover-description { + @apply leading-relaxed + mt-0.5 + text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/popover/style/base/vega/popover.css b/singlestage/src/components/popover/style/base/vega/popover.css new file mode 100644 index 00000000..6747795d --- /dev/null +++ b/singlestage/src/components/popover/style/base/vega/popover.css @@ -0,0 +1,30 @@ +@layer components1 { + .singlestage-popover-content { + @apply bg-popover + flex + flex-col + gap-4 + p-4 + ring-1 + ring-foreground/10 + rounded-md + shadow-md + text-popover-foreground + text-sm; + } + + .singlestage-popover-header { + @apply flex + flex-col + gap-1 + text-sm; + } + + .singlestage-popover-title { + @apply font-medium; + } + + .singlestage-popover-description { + @apply text-muted-foreground; + } +} diff --git a/singlestage/src/components/popover/style/popover.css b/singlestage/src/components/popover/style/popover.css new file mode 100644 index 00000000..723d844d --- /dev/null +++ b/singlestage/src/components/popover/style/popover.css @@ -0,0 +1,193 @@ +@layer components { + .singlestage-popover-content { + @apply outline-hidden + w-72 + z-50; + } + + .singlestage-popover-description { + @apply text-muted-foreground; + } + + /* Animations */ + @media (prefers-reduced-motion: no-preference) { + .singlestage-popover-animations { + opacity: 0; + transform-origin: var(--transform-origin); + transform: var(--transform-scale); + transition-timing-function: var(--transition-timing-function); + transition: all var(--transition-duration) allow-discrete; + + &:popover-open { + opacity: 1; + transform: none; + transform: scale3d(1, 1, 1); + transform: translateX(0); + transform: translateY(0); + } + + @starting-style { + &:popover-open { + opacity: 0; + transform: var(--transform-scale); + } + } + } + + .singlestage-popover-top { + transform: translateY(calc(2 * var(--transform-translate-step))); + + @starting-style { + &:popover-open { + transform: translateY(calc(2 * var(--transform-translate-step))); + } + } + } + + .singlestage-popover-right { + transform: translateX(calc(-2 * var(--transform-translate-step))); + + @starting-style { + &:popover-open { + transform: translateX(calc(-2 * var(--transform-translate-step))); + } + } + } + + .singlestage-popover-bottom { + transform: translateY(calc(-2 * var(--transform-translate-step))); + + @starting-style { + &:popover-open { + transform: translateY(calc(-2 * var(--transform-translate-step))); + } + } + } + + .singlestage-popover-left { + transform: translateX(calc(2 * var(--transform-translate-step))); + + @starting-style { + &:popover-open { + transform: translateX(calc(2 * var(--transform-translate-step))); + } + } + } + } + + /* Disable scrolling while popover is open */ + html:has(.singlestage-context-menu > [popover]:popover-open) { + overflow: hidden; + } + + /* Context menus need to be fixed or they won't spawn in the right place */ + .singlestage-context-menu { + .singlestage-popover { + position: fixed; + } + } + + /* Dropdown/generic stuff */ + .singlestage-popover { + inset: unset; + margin: 0; + overflow: hidden; + position-try-fallbacks: flip-block, flip-inline; + position: absolute; + + &:not(&:popover-open) { + display: none; + } + + /* Align sub-menus with the trigger */ + [popover] { + top: anchor(top); + } + } + + /* Anchor side with spacing */ + .singlestage-popover-top { + bottom: calc(var(--transform-translate-step) + anchor(top)); + } + + .singlestage-popover-right, + .singlestage-popover-right-top { + left: calc(var(--transform-translate-step) + anchor(right)); + } + + .singlestage-popover-bottom { + top: calc(var(--transform-translate-step) + anchor(bottom)); + } + + .singlestage-popover-left, + .singlestage-popover-left-top { + right: calc(var(--transform-translate-step) + anchor(left)); + } + + /* Don't space sub-menus */ + .singlestage-popover { + .singlestage-popover-top { + bottom: anchor(top); + } + + .singlestage-popover-right, + .singlestage-popover-right-top { + left: anchor(right); + } + + .singlestage-popover-bottom { + top: anchor(bottom); + } + + .singlestage-popover-left, + .singlestage-popover-left-top { + right: anchor(left); + } + } + + /* Anchor Align */ + .singlestage-popover-top.singlestage-popover-start, + .singlestage-popover-bottom.singlestage-popover-start { + left: anchor(left); + } + + .singlestage-popover-top.singlestage-popover-center, + .singlestage-popover-bottom.singlestage-popover-center { + justify-self: anchor-center; + } + + .singlestage-popover-top.singlestage-popover-end, + .singlestage-popover-bottom.singlestage-popover-end { + right: anchor(right); + } + + .singlestage-popover-left.singlestage-popover-start, + .singlestage-popover-right.singlestage-popover-start { + top: anchor(top); + } + + .singlestage-popover-left.singlestage-popover-center, + .singlestage-popover-right.singlestage-popover-center { + align-self: anchor-center; + } + + .singlestage-popover-left.singlestage-popover-end, + .singlestage-popover-right.singlestage-popover-end { + top: anchor(bottom); + } + + .singlestage-popover-left-top.singlestage-popover-start, + .singlestage-popover-right-top.singlestage-popover-start { + bottom: anchor(top); + } + + .singlestage-popover-left-top.singlestage-popover-center, + .singlestage-popover-right-top.singlestage-popover-center { + align-self: anchor-center; + } + + .singlestage-popover-left-top.singlestage-popover-end, + .singlestage-popover-right-top.singlestage-popover-end { + bottom: anchor(bottom); + } +} diff --git a/singlestage/src/components/popover/title.rs b/singlestage/src/components/popover/title.rs new file mode 100644 index 00000000..3260f531 --- /dev/null +++ b/singlestage/src/components/popover/title.rs @@ -0,0 +1,165 @@ +use leptos::prelude::*; + +/// Contains the title of the popover. +#[component] +pub fn PopoverTitle( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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! { + <h1 + class=move || { + format!("singlestage-popover-title {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </h1> + } +} diff --git a/singlestage/src/components/popover/trigger.rs b/singlestage/src/components/popover/trigger.rs index 87d9f673..a6a7472b 100644 --- a/singlestage/src/components/popover/trigger.rs +++ b/singlestage/src/components/popover/trigger.rs @@ -1,10 +1,7 @@ -use super::PopoverTriggerContext; -use leptos::{context::Provider, prelude::*}; +use crate::primitives::*; +use leptos::prelude::*; -/// Provides context to a `Button` that triggers a popover. #[component] pub fn PopoverTrigger(children: Children) -> impl IntoView { - let context = PopoverTriggerContext {}; - - view! { <Provider value=context>{children()}</Provider> } + view! { <TriggerPrimitive>{children()}</TriggerPrimitive> } } diff --git a/singlestage/src/components/progress/mod.rs b/singlestage/src/components/progress/mod.rs index 759b1ae0..8376c589 100644 --- a/singlestage/src/components/progress/mod.rs +++ b/singlestage/src/components/progress/mod.rs @@ -1,14 +1,15 @@ +use crate::Reactive; use leptos::prelude::*; /// Displays progress. #[component] pub fn Progress( /// The value that represents 100%, or full. - #[prop(optional, into)] - max: MaybeProp<usize>, + #[prop(optional, into, default = Reactive::new(100.))] + max: Reactive<f64>, /// The current progress value to be rendered. #[prop(optional, into)] - value: MaybeProp<usize>, + value: Reactive<f64>, // GLOBAL ATTRIBUTES // @@ -23,7 +24,7 @@ pub fn Progress( #[prop(optional, into)] autocapitalize: MaybeProp<String>, /// Grabs focus once the page has finished loading. Only one element on the page can be focused - /// at a time. + /// at a time.unwrap_unwrap_ #[prop(optional, into)] autofocus: MaybeProp<bool>, /// Apply classes to the element. @@ -155,23 +156,13 @@ pub fn Progress( }; view! { - <div + <progress class=move || format!("singlestage-progress {}", class.get().unwrap_or_default()) + max=move || max.get() + value=move || value.get() {..global_attrs_1} {..global_attrs_2} - > - <div - class="singlestage-progress-indicator" - style=move || { - format!( - "transform: translateX(-{}%)", - 100. - - ((value.get().unwrap_or_default() as f64 - / max.get().unwrap_or(100) as f64) * 100.), - ) - } - ></div> - </div> + /> } } diff --git a/singlestage/src/components/progress/progress.css b/singlestage/src/components/progress/progress.css deleted file mode 100644 index db3d86f2..00000000 --- a/singlestage/src/components/progress/progress.css +++ /dev/null @@ -1,18 +0,0 @@ -@layer components { - .singlestage-progress { - @apply bg-primary/20 - h-2 - overflow-hidden - relative - rounded-full - w-full; - } - - .singlestage-progress-indicator { - @apply bg-primary - flex-1 - h-full - transition-all - w-full; - } -} diff --git a/singlestage/src/components/progress/style/base/luma/progress.css b/singlestage/src/components/progress/style/base/luma/progress.css new file mode 100644 index 00000000..78779f5e --- /dev/null +++ b/singlestage/src/components/progress/style/base/luma/progress.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-progress { + /* Track */ + &, + &::-webkit-progress-bar { + @apply bg-muted + h-3 + rounded-full; + } + + /* Indicator */ + &::-moz-progress-bar, + &::-webkit-progress-value { + @apply bg-primary + h-3 + rounded-full; + } + } + + .singlestage-progress-label { + @apply font-medium + text-sm; + } + + .singlestage-progress-value { + @apply ml-auto + tabular-nums + text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/progress/style/base/lyra/progress.css b/singlestage/src/components/progress/style/base/lyra/progress.css new file mode 100644 index 00000000..3414a853 --- /dev/null +++ b/singlestage/src/components/progress/style/base/lyra/progress.css @@ -0,0 +1,30 @@ +@layer components1 { + .singlestage-progress { + /* Track */ + &, + &::-webkit-progress-bar { + @apply bg-muted + h-1 + rounded-none; + } + + /* Indicator */ + &::-moz-progress-bar, + &::-webkit-progress-value { + @apply bg-primary + h-1 + rounded-none; + } + } + + .singlestage-progress-label { + @apply text-xs; + } + + .singlestage-progress-value { + @apply ml-auto + tabular-nums + text-muted-foreground + text-xs; + } +} diff --git a/singlestage/src/components/progress/style/base/maia/progress.css b/singlestage/src/components/progress/style/base/maia/progress.css new file mode 100644 index 00000000..7dfabc6d --- /dev/null +++ b/singlestage/src/components/progress/style/base/maia/progress.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-progress { + /* Track */ + &, + &::-webkit-progress-bar { + @apply bg-muted + h-3 + rounded-4xl; + } + + /* Indicator */ + &::-moz-progress-bar, + &::-webkit-progress-value { + @apply bg-primary + h-3 + rounded-4xl; + } + } + + .singlestage-progress-label { + @apply font-medium + text-sm; + } + + .singlestage-progress-value { + @apply ml-auto + tabular-nums + text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/progress/style/base/mira/progress.css b/singlestage/src/components/progress/style/base/mira/progress.css new file mode 100644 index 00000000..a1039316 --- /dev/null +++ b/singlestage/src/components/progress/style/base/mira/progress.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-progress { + /* Track */ + &, + &::-webkit-progress-bar { + @apply bg-muted + h-1 + rounded-md; + } + + /* Indicator */ + &::-moz-progress-bar, + &::-webkit-progress-value { + @apply bg-primary + h-1 + rounded-md; + } + } + + .singlestage-progress-label { + @apply font-medium + text-xs/relaxed; + } + + .singlestage-progress-value { + @apply ml-auto + tabular-nums + text-muted-foreground + text-xs/relaxed; + } +} diff --git a/singlestage/src/components/progress/style/base/nova/progress.css b/singlestage/src/components/progress/style/base/nova/progress.css new file mode 100644 index 00000000..0bfa981c --- /dev/null +++ b/singlestage/src/components/progress/style/base/nova/progress.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-progress { + /* Track */ + &, + &::-webkit-progress-bar { + @apply bg-muted + h-1 + rounded-full; + } + + /* Indicator */ + &::-moz-progress-bar, + &::-webkit-progress-value { + @apply bg-primary + h-1 + rounded-full; + } + } + + .singlestage-progress-label { + @apply font-medium + text-sm; + } + + .singlestage-progress-value { + @apply ml-auto + tabular-nums + text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/progress/style/base/sera/progress.css b/singlestage/src/components/progress/style/base/sera/progress.css new file mode 100644 index 00000000..c6f3d127 --- /dev/null +++ b/singlestage/src/components/progress/style/base/sera/progress.css @@ -0,0 +1,33 @@ +@layer components1 { + .singlestage-progress { + /* Track */ + &, + &::-webkit-progress-bar { + @apply bg-muted + h-0.5 + rounded-none; + } + + /* Indicator */ + &::-moz-progress-bar, + &::-webkit-progress-value { + @apply bg-primary + h-0.5 + rounded-none; + } + } + + .singlestage-progress-label { + @apply font-semibold + text-xs + tracking-wide + uppercase; + } + + .singlestage-progress-value { + @apply ml-auto + tabular-nums + text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/progress/style/base/vega/progress.css b/singlestage/src/components/progress/style/base/vega/progress.css new file mode 100644 index 00000000..b497217e --- /dev/null +++ b/singlestage/src/components/progress/style/base/vega/progress.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-progress { + /* Track */ + &, + &::-webkit-progress-bar { + @apply bg-muted + h-1.5 + rounded-full; + } + + /* Indicator */ + &::-moz-progress-bar, + &::-webkit-progress-value { + @apply bg-primary + h-1.5 + rounded-full; + } + } + + .singlestage-progress-label { + @apply font-medium + text-sm; + } + + .singlestage-progress-value { + @apply ml-auto + tabular-nums + text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/progress/style/progress.css b/singlestage/src/components/progress/style/progress.css new file mode 100644 index 00000000..8c0d8b0c --- /dev/null +++ b/singlestage/src/components/progress/style/progress.css @@ -0,0 +1,16 @@ +@layer components { + .singlestage-progress { + @apply w-full; + } + + /* Animate, but it only works for webkit for now */ + @media (prefers-reduced-motion: no-preference) { + .singlestage-progress { + /* Indicator */ + &::-moz-progress-bar, + &::-webkit-progress-value { + @apply transition-all; + } + } + } +} diff --git a/singlestage/src/components/radio/group.rs b/singlestage/src/components/radio/group.rs index d7fd0d8a..060e4a9f 100644 --- a/singlestage/src/components/radio/group.rs +++ b/singlestage/src/components/radio/group.rs @@ -1,4 +1,4 @@ -use crate::{RadioGroupContext, Reactive}; +use crate::{RadioGroupContext, Reactive, primitives::PopoverMenuContext}; use leptos::{context::Provider, prelude::*}; /// Contains all the parts of a radio group @@ -20,7 +20,7 @@ pub fn RadioGroup( // /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, @@ -173,7 +173,14 @@ pub fn RadioGroup( /> }; - let fieldset_attrs = view! { <{..} disabled=move || disabled.get() form=move || form.get() name=move || name.get() /> }; + let fieldset_attrs = view! { + <{..} + aria_invalid=move || { if invalid.get() { Some("true") } else { None } } + disabled=move || disabled.get() + form=move || form.get() + name=move || name.get() + /> + }; if let Some(default) = default.get_untracked() { value.set(default) @@ -183,13 +190,22 @@ pub fn RadioGroup( name: name .get_untracked() .unwrap_or(uuid::Uuid::new_v4().to_string()), + disabled, invalid, value, }; + let in_popover_menu = use_context::<PopoverMenuContext>().is_some(); + view! { <fieldset - class=move || format!("singlestage-radio-group {}", class.get().unwrap_or_default()) + class=move || { + format!( + "{}{}", + if in_popover_menu { "" } else { "singlestage-radio-group " }, + class.get().unwrap_or_default(), + ) + } {..global_attrs_1} {..global_attrs_2} diff --git a/singlestage/src/components/radio/mod.rs b/singlestage/src/components/radio/mod.rs index 4ad6b818..01e5eb26 100644 --- a/singlestage/src/components/radio/mod.rs +++ b/singlestage/src/components/radio/mod.rs @@ -7,8 +7,9 @@ pub use group::*; pub use radio::*; #[derive(Clone)] -pub struct RadioGroupContext { - name: String, - invalid: Reactive<bool>, - value: Reactive<String>, +pub(crate) struct RadioGroupContext { + pub name: String, + pub disabled: Reactive<bool>, + pub invalid: Reactive<bool>, + pub value: Reactive<String>, } diff --git a/singlestage/src/components/radio/radio.css b/singlestage/src/components/radio/radio.css deleted file mode 100644 index ae9d96d7..00000000 --- a/singlestage/src/components/radio/radio.css +++ /dev/null @@ -1,46 +0,0 @@ -@layer components { - .singlestage-input[type="radio"] { - @apply appearance-none - aria-invalid:border-destructive - aria-invalid:ring-destructive/20 - aspect-square - border - border-input - disabled:cursor-not-allowed - disabled:opacity-50 - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - outline-none - relative - rounded-full - shadow-xs - shrink-0 - size-4 - text-primary - transition-[color,box-shadow]; - - &:checked:before { - @apply -translate-x-1/2 - -translate-y-1/2 - absolute - bg-primary - content-[''] - left-1/2 - rounded-full - size-2 - top-1/2; - } - } - - .singlestage-radio-group { - @apply gap-3 - grid; - - > legend { - @apply font-medium - mb-3 - text-base; - } - } -} diff --git a/singlestage/src/components/radio/radio.rs b/singlestage/src/components/radio/radio.rs index 9bd3c593..6951a68b 100644 --- a/singlestage/src/components/radio/radio.rs +++ b/singlestage/src/components/radio/radio.rs @@ -1,4 +1,4 @@ -use crate::{FieldContext, FieldLabel, RadioGroupContext, Reactive}; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; /// An item in the group that can be checked @@ -6,17 +6,21 @@ use leptos::prelude::*; pub fn Radio( #[prop(optional)] children: Option<Children>, - // RADIO ATTRIBUTES + /// Whether the input is invalid + #[prop(optional, into)] + invalid: Reactive<bool>, + + // CHECKBOX ATTRIBUTES // - /// Whether the command or control is checked + /// Reactive signal coupled to the checkbox's checked value. #[prop(optional, into)] checked: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, - // /// Name of this element. Submitted with the form as part of a name/value pair. - // #[prop(optional, into)] - // name: MaybeProp<String>, + /// Name of this element. Submitted with the form as part of a name/value pair. + #[prop(optional, into)] + name: MaybeProp<String>, /// Toggle whether or not the user can modify the value of this element. #[prop(optional, into)] readonly: MaybeProp<bool>, @@ -25,7 +29,7 @@ pub fn Radio( required: MaybeProp<bool>, /// Whether the form control is disabled #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// The value of the control. When specified in the HTML, corresponds to the initial value #[prop(optional, into)] value: MaybeProp<String>, @@ -116,9 +120,9 @@ pub fn Radio( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp<String>, - /// Define the semantic meaning of content. - #[prop(optional, into)] - role: MaybeProp<String>, + // /// Define the semantic meaning of content. + // #[prop(optional, into)] + // role: MaybeProp<String>, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp<String>, @@ -140,69 +144,23 @@ pub fn Radio( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let radio_group = expect_context::<RadioGroupContext>(); - let radio_ref = { - if let Some(node_ref) = node_ref.get_untracked() { - node_ref - } else { - NodeRef::<leptos::html::Input>::new() - } - }; - - let on_change = move |ev| { - checked.set(event_target_checked(&ev)); - radio_group.value.set(event_target_value(&ev)); - }; - - if let Some(value) = value.get_untracked() - && radio_group.value.get_untracked() == value - { - if let Some(radio) = radio_ref.get_untracked() { - radio.set_checked(true); - } - checked.set(true); - } - - Effect::new(move || { - if radio_group.value.get() == value.get().unwrap_or_default() { - checked.set(true); - } else { - checked.set(false); - } - }); - - Effect::new(move || { - if let Some(radio) = radio_ref.get_untracked() { - radio.set_checked(checked.get()); - } - }); - - Effect::new(move || { - if let Some(radio) = radio_ref.get_untracked() { - radio.set_disabled(disabled.get().unwrap_or_default()); - } - }); - 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() @@ -213,7 +171,6 @@ pub fn Radio( 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() @@ -223,110 +180,49 @@ pub fn Radio( /> }; - let input_id = uuid::Uuid::new_v4(); - let label_id = uuid::Uuid::new_v4(); - let has_children = children.is_some(); - - let radio_attrs = view! { - <{..} - aria_describedby=move || { - if let Some(field) = use_context::<FieldContext>() { - let description_id = field.description_id.get(); - if description_id.is_empty() { None } else { Some(description_id) } - } else { - None - } - } - aria-invalid=move || { - if let Some(radio_group) = use_context::<RadioGroupContext>() { - radio_group.invalid.get().to_string() - } else { - false.to_string() - } - } - aria_labelledby=move || { - if let Some(field) = use_context::<FieldContext>() { - Some(field.label_id.get()) - } else if has_children { - Some(label_id.to_string()) - } else { - None - } - } - checked=move || checked.get_untracked() - class=move || { format!("singlestage-input {}", class.get().unwrap_or_default()) } - disabled=disabled.get_untracked() - form=move || form.get() - name=radio_group.name.clone() - node_ref=radio_ref - on:change=on_change - readonly=move || readonly.get() - required=move || required.get() - type="radio" - value=move || value.get() - /> - }; - if let Some(children) = children { view! { - {if use_context::<FieldContext>().is_some() { - view! { - <input - id=move || id.get().unwrap_or(input_id.to_string()) + <CheckboxPrimitive + primitive_type=CheckboxPrimitiveType::Radio + + checked + class + disabled + form + id + invalid + name + node_ref + readonly + required + value - {..global_attrs_1} - {..global_attrs_2} - {..radio_attrs} - /> - <FieldLabel - class=class.get_untracked() - label_for=id.get_untracked().unwrap_or(input_id.to_string()) - > - {children()} - </FieldLabel> - } - .into_any() - } else { - view! { - <label - class=move || { - format!("singlestage-label {}", class.get().unwrap_or_default()) - } - for=move || id.get().unwrap_or(input_id.to_string()) - id=label_id.to_string() - > - <input - id=move || id.get().unwrap_or(input_id.to_string()) - - {..global_attrs_1} - {..global_attrs_2} - {..radio_attrs} - /> - {children()} - </label> - } - .into_any() - }} + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </CheckboxPrimitive> } .into_any() } else { view! { - <input - id={if let Some(field) = use_context::<FieldContext>() { - if let Some(id) = id.get_untracked() { - field.input_id.set(id.clone()); - Some(id) - } else { - field.input_id.set(input_id.to_string()); - Some(input_id.to_string()) - } - } else { - id.get_untracked() - }} + <CheckboxPrimitive + primitive_type=CheckboxPrimitiveType::Radio + + checked + class + disabled + form + id + invalid + name + node_ref + readonly + required + value {..global_attrs_1} {..global_attrs_2} - {..radio_attrs} /> } .into_any() diff --git a/singlestage/src/components/radio/style/base/luma/radio.css b/singlestage/src/components/radio/style/base/luma/radio.css new file mode 100644 index 00000000..e45c838a --- /dev/null +++ b/singlestage/src/components/radio/style/base/luma/radio.css @@ -0,0 +1,52 @@ +@layer components1 { + .singlestage-radio-group { + @apply gap-3 + grid; + } + + .singlestage-radio-group-item { + @apply bg-input/90 + border + border-transparent + flex + rounded-full + size-4; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary + text-primary-foreground; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &::after { + @apply flex + items-center + justify-center + size-4; + } + + &:checked::before { + @apply -translate-x-1/2 + -translate-y-1/2 + absolute + bg-primary-foreground + left-1/2 + rounded-full + size-2 + top-1/2; + } + } +} diff --git a/singlestage/src/components/radio/style/base/luma/radio_dark.css b/singlestage/src/components/radio/style/base/luma/radio_dark.css new file mode 100644 index 00000000..2f771abc --- /dev/null +++ b/singlestage/src/components/radio/style/base/luma/radio_dark.css @@ -0,0 +1,18 @@ +@layer components1 { + .singlestage-radio-group-item { + &:checked, + &[aria-checked="true"] { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + + &:checked::before { + @apply size-2.5; + } + } +} diff --git a/singlestage/src/components/radio/style/base/lyra/radio.css b/singlestage/src/components/radio/style/base/lyra/radio.css new file mode 100644 index 00000000..6266eeb1 --- /dev/null +++ b/singlestage/src/components/radio/style/base/lyra/radio.css @@ -0,0 +1,56 @@ +@layer components1 { + .singlestage-radio-group { + @apply gap-2 + grid; + } + + .singlestage-radio-group-item { + @apply border-input + flex + rounded-full + size-4; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary + border-primary + text-primary-foreground; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + + &:checked, + &[aria-checked="true"] { + @apply border-primary; + } + } + + &::after { + @apply flex + items-center + justify-center + size-4; + } + + &:checked::before { + @apply -translate-x-1/2 + -translate-y-1/2 + absolute + bg-primary-foreground + left-1/2 + rounded-full + size-2 + top-1/2; + } + } +} diff --git a/singlestage/src/components/radio/style/base/lyra/radio_dark.css b/singlestage/src/components/radio/style/base/lyra/radio_dark.css new file mode 100644 index 00000000..9789111b --- /dev/null +++ b/singlestage/src/components/radio/style/base/lyra/radio_dark.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-radio-group-item { + @apply bg-input/30; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/radio/style/base/maia/radio.css b/singlestage/src/components/radio/style/base/maia/radio.css new file mode 100644 index 00000000..7603ea48 --- /dev/null +++ b/singlestage/src/components/radio/style/base/maia/radio.css @@ -0,0 +1,55 @@ +@layer components1 { + .singlestage-radio-group { + @apply gap-3 + grid; + } + + .singlestage-radio-group-item { + @apply border-input + flex + rounded-full + size-4; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary + border-primary + text-primary-foreground; + } + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + + &:checked, + &[aria-checked="true"] { + @apply border-primary; + } + } + + &::after { + @apply flex + items-center + justify-center + size-4; + } + + &:checked::before { + @apply -translate-x-1/2 + -translate-y-1/2 + absolute + bg-primary-foreground + left-1/2 + rounded-full + size-2 + top-1/2; + } + } +} diff --git a/singlestage/src/components/radio/style/base/maia/radio_dark.css b/singlestage/src/components/radio/style/base/maia/radio_dark.css new file mode 100644 index 00000000..9789111b --- /dev/null +++ b/singlestage/src/components/radio/style/base/maia/radio_dark.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-radio-group-item { + @apply bg-input/30; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/radio/style/base/mira/radio.css b/singlestage/src/components/radio/style/base/mira/radio.css new file mode 100644 index 00000000..b1ce5691 --- /dev/null +++ b/singlestage/src/components/radio/style/base/mira/radio.css @@ -0,0 +1,56 @@ +@layer components1 { + .singlestage-radio-group { + @apply gap-3 + grid; + } + + .singlestage-radio-group-item { + @apply border-input + flex + rounded-full + size-4; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary + border-primary + text-primary-foreground; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + + &:checked, + &[aria-checked="true"] { + @apply border-primary; + } + } + + &::after { + @apply flex + items-center + justify-center + size-4; + } + + &:checked::before { + @apply -translate-x-1/2 + -translate-y-1/2 + absolute + bg-primary-foreground + left-1/2 + rounded-full + size-2 + top-1/2; + } + } +} diff --git a/singlestage/src/components/radio/style/base/mira/radio_dark.css b/singlestage/src/components/radio/style/base/mira/radio_dark.css new file mode 100644 index 00000000..9789111b --- /dev/null +++ b/singlestage/src/components/radio/style/base/mira/radio_dark.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-radio-group-item { + @apply bg-input/30; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/radio/style/base/nova/radio.css b/singlestage/src/components/radio/style/base/nova/radio.css new file mode 100644 index 00000000..6266eeb1 --- /dev/null +++ b/singlestage/src/components/radio/style/base/nova/radio.css @@ -0,0 +1,56 @@ +@layer components1 { + .singlestage-radio-group { + @apply gap-2 + grid; + } + + .singlestage-radio-group-item { + @apply border-input + flex + rounded-full + size-4; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary + border-primary + text-primary-foreground; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + + &:checked, + &[aria-checked="true"] { + @apply border-primary; + } + } + + &::after { + @apply flex + items-center + justify-center + size-4; + } + + &:checked::before { + @apply -translate-x-1/2 + -translate-y-1/2 + absolute + bg-primary-foreground + left-1/2 + rounded-full + size-2 + top-1/2; + } + } +} diff --git a/singlestage/src/components/radio/style/base/nova/radio_dark.css b/singlestage/src/components/radio/style/base/nova/radio_dark.css new file mode 100644 index 00000000..9789111b --- /dev/null +++ b/singlestage/src/components/radio/style/base/nova/radio_dark.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-radio-group-item { + @apply bg-input/30; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/radio/style/base/sera/radio.css b/singlestage/src/components/radio/style/base/sera/radio.css new file mode 100644 index 00000000..6ce90c58 --- /dev/null +++ b/singlestage/src/components/radio/style/base/sera/radio.css @@ -0,0 +1,56 @@ +@layer components1 { + .singlestage-radio-group { + @apply gap-3 + grid; + } + + .singlestage-radio-group-item { + @apply bg-transparent + border + border-input + flex + rounded-full + size-4.5; + + &:checked, + &[aria-checked="true"] { + @apply border-foreground; + } + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + } + + &:checked, + &[aria-checked="true"] { + @apply border-foreground; + } + + &::after { + @apply flex + items-center + justify-center + size-4.5; + } + + &:checked::before { + @apply -translate-x-1/2 + -translate-y-1/2 + absolute + bg-foreground + left-1/2 + rounded-full + size-2 + top-1/2; + } + } +} diff --git a/singlestage/src/components/radio/style/base/sera/radio_dark.css b/singlestage/src/components/radio/style/base/sera/radio_dark.css new file mode 100644 index 00000000..8adbdb68 --- /dev/null +++ b/singlestage/src/components/radio/style/base/sera/radio_dark.css @@ -0,0 +1,9 @@ +@layer components1 { + .singlestage-radio-group-item { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/radio/style/base/vega/radio.css b/singlestage/src/components/radio/style/base/vega/radio.css new file mode 100644 index 00000000..b1ce5691 --- /dev/null +++ b/singlestage/src/components/radio/style/base/vega/radio.css @@ -0,0 +1,56 @@ +@layer components1 { + .singlestage-radio-group { + @apply gap-3 + grid; + } + + .singlestage-radio-group-item { + @apply border-input + flex + rounded-full + size-4; + + &:checked, + &[aria-checked="true"] { + @apply bg-primary + border-primary + text-primary-foreground; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + + &:checked, + &[aria-checked="true"] { + @apply border-primary; + } + } + + &::after { + @apply flex + items-center + justify-center + size-4; + } + + &:checked::before { + @apply -translate-x-1/2 + -translate-y-1/2 + absolute + bg-primary-foreground + left-1/2 + rounded-full + size-2 + top-1/2; + } + } +} diff --git a/singlestage/src/components/radio/style/base/vega/radio_dark.css b/singlestage/src/components/radio/style/base/vega/radio_dark.css new file mode 100644 index 00000000..e36ef17b --- /dev/null +++ b/singlestage/src/components/radio/style/base/vega/radio_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-radio-group-item { + @apply bg-input/30; + + &:checked { + @apply bg-primary; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/radio/style/radio.css b/singlestage/src/components/radio/style/radio.css new file mode 100644 index 00000000..60dcf66e --- /dev/null +++ b/singlestage/src/components/radio/style/radio.css @@ -0,0 +1,29 @@ +@layer components { + .singlestage-radio-group { + @apply w-full; + } + .singlestage-radio-group-item { + @apply appearance-none + aspect-square + border + outline-none + relative + shrink-0; + + &:disabled, + &[aria-disabled="true"] { + @apply cursor-not-allowed + opacity-50; + } + + &::after { + @apply -inset-x-3 + -inset-y-2 + absolute; + } + + &:checked::before { + @apply content-['']; + } + } +} diff --git a/singlestage/src/components/scroll_area/mod.rs b/singlestage/src/components/scroll_area/mod.rs index 24843c90..a5989a41 100644 --- a/singlestage/src/components/scroll_area/mod.rs +++ b/singlestage/src/components/scroll_area/mod.rs @@ -11,6 +11,11 @@ pub fn ScrollArea( #[prop(optional, into)] orientation: MaybeProp<String>, + // LEPTOS ATTRIBUTES + /// A reactive reference to a DOM node that can be used with the node_ref attribute. + #[prop(optional, into)] + node_ref: MaybeProp<NodeRef<leptos::html::Div>>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -116,6 +121,14 @@ pub fn ScrollArea( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { + let node_ref = { + if let Some(node_ref) = node_ref.get_untracked() { + node_ref + } else { + NodeRef::<leptos::html::Div>::new() + } + }; + let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -156,6 +169,7 @@ pub fn ScrollArea( view! { <div + node_ref=node_ref class=move || { format!( "singlestage-scrollbar singlestage-scroll-area {} {}", diff --git a/singlestage/src/components/scroll_area/scroll_area.css b/singlestage/src/components/scroll_area/style/scroll_area.css similarity index 94% rename from singlestage/src/components/scroll_area/scroll_area.css rename to singlestage/src/components/scroll_area/style/scroll_area.css index c85cd4d6..e08df39b 100644 --- a/singlestage/src/components/scroll_area/scroll_area.css +++ b/singlestage/src/components/scroll_area/style/scroll_area.css @@ -1,7 +1,7 @@ @layer components { .singlestage-scroll-area { @apply focus-visible:outline-1 - focus-visible:ring-[3px] + focus-visible:ring-3 focus-visible:ring-ring/50 outline-none overflow-hidden diff --git a/singlestage/src/components/select/mod.rs b/singlestage/src/components/select/mod.rs index fa6812b5..a9bb1c88 100644 --- a/singlestage/src/components/select/mod.rs +++ b/singlestage/src/components/select/mod.rs @@ -1,16 +1,14 @@ -mod content; -mod item; +mod opt_group; +mod option; mod select; -pub use content::*; -pub use item::*; +pub use opt_group::*; +pub use option::*; pub use select::*; use crate::Reactive; -use leptos::prelude::MaybeProp; #[derive(Clone)] -pub struct SelectContext { - placeholder: MaybeProp<String>, +pub(crate) struct SelectContext { value: Reactive<String>, } diff --git a/singlestage/src/components/select/content.rs b/singlestage/src/components/select/opt_group.rs similarity index 92% rename from singlestage/src/components/select/content.rs rename to singlestage/src/components/select/opt_group.rs index 33c3cdb7..9fa69ffb 100644 --- a/singlestage/src/components/select/content.rs +++ b/singlestage/src/components/select/opt_group.rs @@ -1,14 +1,14 @@ -use crate::SelectContext; +use crate::Reactive; use leptos::prelude::*; /// Contains a group of items for the Select. #[component] -pub fn SelectContent( +pub fn SelectOptGroup( children: Children, /// Set whether or not the items in the group are appear as disabled and can be selected. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Label this group of content. #[prop(optional, into)] label: MaybeProp<String>, @@ -118,8 +118,6 @@ pub fn SelectContent( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let select = expect_context::<SelectContext>(); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -168,16 +166,6 @@ pub fn SelectContent( {..global_attrs_1} {..global_attrs_2} > - {if let Some(placeholder) = select.placeholder.get_untracked() { - view! { - <option value="singlestage-select-placeholder" disabled hidden selected> - {placeholder} - </option> - } - .into_any() - } else { - "".into_any() - }} {children()} </optgroup> } diff --git a/singlestage/src/components/select/item.rs b/singlestage/src/components/select/option.rs similarity index 99% rename from singlestage/src/components/select/item.rs rename to singlestage/src/components/select/option.rs index d39c6986..de170024 100644 --- a/singlestage/src/components/select/item.rs +++ b/singlestage/src/components/select/option.rs @@ -4,12 +4,12 @@ use leptos::prelude::*; /// Contains an item with a value to be selected. #[component] -pub fn SelectItem( +pub fn SelectOption( children: Children, /// Set whether or not this item appears disabled and is checkable. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Set a label describing the meaning of this item. #[prop(optional, into)] label: MaybeProp<String>, diff --git a/singlestage/src/components/select/select.css b/singlestage/src/components/select/select.css deleted file mode 100644 index 7ef9a11a..00000000 --- a/singlestage/src/components/select/select.css +++ /dev/null @@ -1,170 +0,0 @@ -@layer components { - select.singlestage-select { - @apply appearance-none - aria-invalid:border-destructive - aria-invalid:ring-destructive/20 - border - border-input - disabled:cursor-not-allowed - disabled:opacity-50 - flex - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - gap-2 - h-9 - items-center - justify-between - outline-none - pl-3 - pr-9 - rounded-md - shadow-xs - text-sm - transition-[color,box-shadow] - min-w-fit - whitespace-nowrap; - - @apply bg-[image:var(--chevron-down-icon-50)] - bg-no-repeat - bg-position-[center_right_0.75rem] - bg-size-[1rem]; - - option, - optgroup { - @apply bg-popover - text-popover-foreground; - } - } - *:not(select).singlestage-select { - @apply inline-flex - relative; - - [data-popover] { - @apply p-1; - - [role="option"] { - @apply [&_svg]:shrink-0 - [&_svg]:size-4 - [&_svg]:text-muted-foreground - aria-disabled:opacity-50 - aria-disabled:pointer-events-none - aria-hidden:hidden - cursor-default - disabled:opacity-50 - disabled:pointer-events-none - flex - gap-2 - items-center - outline-hidden - pl-2 - pr-7.5 - py-1.5 - relative - rounded-sm - select-none - text-sm - truncate - w-full; - - &[aria-selected="true"] { - @apply bg-[image:var(--check-icon)] - bg-no-repeat - bg-position-[center_right_0.5rem] - bg-size-[0.875rem]; - } - - &.active, - &:focus-visible { - @apply bg-accent - text-accent-foreground; - } - } - - [role="listbox"] [role="heading"] { - @apply flex - px-2 - py-1.5 - text-muted-foreground - text-xs; - } - - [role="listbox"] - [role="group"]:not(:has([role="option"]:not([aria-hidden="true"]))) { - @apply hidden; - } - - [role="separator"] { - @apply -mx-1 - border-border - my-1; - } - - > header { - @apply -mt-1 - -mx-1 - border-b - flex - gap-2 - h-9 - items-center - mb-1 - px-3; - - svg { - @apply opacity-50 - shrink-0 - size-4; - } - - input[role="combobox"] { - @apply bg-transparent - disabled:cursor-not-allowed - disabled:opacity-50 - flex - flex-1 - h-10 - min-w-0 - outline-hidden - placeholder:text-muted-foreground - py-3 - rounded-md - text-sm - w-full; - } - } - - [role="listbox"]:not( - :has([data-value]:not([aria-hidden="true"])) - )::before { - @apply flex - items-center - justify-center - p-6 - text-sm - truncate; - } - - [role="listbox"][data-empty]:not( - :has([data-value]:not([aria-hidden="true"])) - )::before { - @apply content-[attr(data-empty)]; - } - - [role="listbox"]:not([data-empty]):not( - :has([data-value]:not([aria-hidden="true"])) - )::before { - @apply content-['No_results_found']; - } - } - - &:not([data-select-initialized]) [data-popover] [role="option"] { - @apply hover:bg-accent - hover:text-accent-foreground; - } - } - - .singlestage-select-placeholder { - @apply text-muted-foreground; - } -} diff --git a/singlestage/src/components/select/select.rs b/singlestage/src/components/select/select.rs index f4df070e..e8043e8f 100644 --- a/singlestage/src/components/select/select.rs +++ b/singlestage/src/components/select/select.rs @@ -12,13 +12,16 @@ pub fn Select( default: MaybeProp<String>, /// Toggle invalid appearance. #[prop(optional, into)] - invalid: MaybeProp<bool>, + invalid: Reactive<bool>, + /// Set the size to render the element. + #[prop(optional, into)] + todo_name_me_size: MaybeProp<String>, /// The placeholder value for the select. #[prop(optional, into)] placeholder: MaybeProp<String>, /// The value of the control. When specified in the HTML, corresponds to the initial value #[prop(optional, into)] - value: Reactive<String>, + value: Option<Reactive<String>>, // LEPTOS ATTRIBUTES /// A reactive reference to a DOM node that can be used with the node_ref attribute. @@ -32,13 +35,13 @@ pub fn Select( autocomplete: MaybeProp<String>, /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, - /// Toggle accepting multiple input values for certain input types. - #[prop(optional, into)] - multiple: MaybeProp<bool>, + // /// Toggle accepting multiple input values for certain input types. + // #[prop(optional, into)] + // multiple: MaybeProp<bool>, /// Name of this element. Submitted with the form as part of a name/value pair. #[prop(optional, into)] name: MaybeProp<String>, @@ -162,41 +165,33 @@ pub fn Select( } }; - let on_change = move |ev| value.set(event_target_value(&ev)); - - if let Some(default) = default.get_untracked() { - value.set(default); + let value: Reactive<String> = value.unwrap_or(if let Some(default) = default.get_untracked() { + Reactive::new(default) } else if placeholder.get_untracked().is_some() { - value.set("singlestage-select-placeholder".to_string()); - } + Reactive::new("singlestage-select-placeholder".to_string()) + } else { + Reactive::new("singlestage-select-value-not-set".to_string()) + }); - if let Some(select) = select_ref.get_untracked() { - if let Some(default) = default.get_untracked() { - select.set_value(&default); - } else if placeholder.get_untracked().is_some() { - select.set_value("singlestage-select-placeholder"); + let disabled: Reactive<bool> = { + if let Some(field) = use_context::<FieldContext>() { + field.disabled } else { - select.set_value(&value.get_untracked()); + disabled } - } - - let context = SelectContext { placeholder, value }; + }; - Effect::new(move || { - if let Some(select) = select_ref.get_untracked() { - select.set_disabled(disabled.get().unwrap_or_default()); + let invalid: Reactive<bool> = { + if let Some(field) = use_context::<FieldContext>() { + field.invalid + } else { + invalid } - }); + }; - // Update value reactively - Effect::new(move || { - if let Some(select) = select_ref.get_untracked() { - let value = value.get(); - if !value.is_empty() { - select.set_value(&value); - } - } - }); + let context = SelectContext { value }; + + let on_change = move |ev| value.set(event_target_value(&ev)); let global_attrs_1 = view! { <{..} @@ -241,7 +236,6 @@ pub fn Select( autocomplete=move || autocomplete.get() disabled=move || disabled.get() form=move || form.get() - multiple=move || multiple.get() name=move || name.get() required=move || required.get() size=move || size.get() @@ -251,53 +245,95 @@ pub fn Select( let input_id = uuid::Uuid::new_v4(); view! { - <select - aria_describedby=move || { - if let Some(field) = use_context::<FieldContext>() { - let description_id = field.description_id.get(); - if description_id.is_empty() { None } else { Some(description_id) } - } else { - None - } - } - aria-invalid=move || { - match invalid.get() { - Some(true) => Some("true"), - _ => None, - } - } - aria_labelledby=move || { - use_context::<FieldContext>().map(|field| field.label_id.get()) - } - node_ref=select_ref - on:change=on_change - class=move || { - format!( - "singlestage-select{} {}", - match value.get().as_str() { - "singlestage-select-placeholder" => " singlestage-select-placeholder", - _ => "", - }, - class.get().unwrap_or_default(), - ) - } - id={if let Some(field) = use_context::<FieldContext>() { - if let Some(id) = id.get_untracked() { - field.input_id.set(id.clone()); - Some(id) - } else { - field.input_id.set(input_id.to_string()); - Some(input_id.to_string()) - } - } else { - id.get_untracked() - }} + <div + class=move || format!("singlestage-select-wrapper {}", class.get().unwrap_or_default()) {..global_attrs_1} {..global_attrs_2} - {..select_attrs} > - <Provider value=context>{children()}</Provider> - </select> + <Provider value=context> + <select + aria_describedby=move || { + if let Some(field) = use_context::<FieldContext>() { + let description_id = field.description_id.get(); + if description_id.is_empty() { None } else { Some(description_id) } + } else { + None + } + } + aria_invalid=move || { + match invalid.get() { + true => Some("true"), + _ => None, + } + } + aria_labelledby=move || { + use_context::<FieldContext>().map(|field| field.label_id.get()) + } + class=move || { + format!( + "singlestage-select{}{}", + match todo_name_me_size.get().unwrap_or_default().as_str() { + "sm" | "small" => " singlestage-select-size-sm", + _ => "", + }, + match value.get().as_str() { + "singlestage-select-placeholder" => { + " singlestage-select-placeholder" + } + _ => "", + }, + ) + } + disabled=move || disabled.get() + prop:disabled=move || disabled.get() + node_ref=select_ref + on:change=on_change + id=move || { + if let Some(field) = use_context::<FieldContext>() { + if let Some(id) = id.get_untracked() { + field.input_id.set(id.clone()); + Some(id) + } else { + field.input_id.set(input_id.to_string()); + Some(input_id.to_string()) + } + } else { + id.get_untracked() + } + } + prop:value=move || { + if value.get().as_str() == "singlestage-select-value-not-set" { + None + } else { + Some(value.get()) + } + } + + {..select_attrs} + > + <Show when=move || placeholder.get().is_some()> + <option value="singlestage-select-placeholder" disabled hidden selected> + {move || placeholder.get()} + </option> + </Show> + {children()} + </select> + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + class="singlestage-select-icon" + > + <path d="m6 9 6 6 6-6" /> + </svg> + </Provider> + </div> } } diff --git a/singlestage/src/components/select/style/base/luma/select.css b/singlestage/src/components/select/style/base/luma/select.css new file mode 100644 index 00000000..0d934b9b --- /dev/null +++ b/singlestage/src/components/select/style/base/luma/select.css @@ -0,0 +1,52 @@ +@layer components1 { + .singlestage-select { + @apply appearance-none + bg-input/50 + border + border-transparent + h-9 + min-w-0 + pl-3 + pr-8 + py-1 + rounded-3xl + select-none + text-sm + transition-[color,box-shadow,background-color] + w-full; + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &::selection { + @apply bg-primary + text-primary-foreground; + } + } + + .singlestage-select-placeholder { + @apply text-muted-foreground; + } + + .singlestage-select-size-sm { + @apply h-8; + } + + .singlestage-select-icon { + @apply -translate-y-1/2 + right-2.5 + size-4 + text-muted-foreground + top-1/2; + } +} diff --git a/singlestage/src/components/select/style/base/luma/select_dark.css b/singlestage/src/components/select/style/base/luma/select_dark.css new file mode 100644 index 00000000..7cdeefe6 --- /dev/null +++ b/singlestage/src/components/select/style/base/luma/select_dark.css @@ -0,0 +1,9 @@ +@layer components1 { + .singlestage-select { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/select/style/base/lyra/select.css b/singlestage/src/components/select/style/base/lyra/select.css new file mode 100644 index 00000000..60aa6b34 --- /dev/null +++ b/singlestage/src/components/select/style/base/lyra/select.css @@ -0,0 +1,54 @@ +@layer components1 { + .singlestage-select { + @apply appearance-none + bg-transparent + border + border-input + h-8 + min-w-0 + pl-2.5 + pr-8 + py-1 + rounded-none + select-none + text-xs + transition-colors + w-full; + + &:focus-visible { + @apply border-ring + ring-1 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-1 + ring-destructive/20; + } + + &::selection { + @apply bg-primary + text-primary-foreground; + } + } + + .singlestage-select-placeholder { + @apply text-muted-foreground; + } + + .singlestage-select-size-sm { + @apply h-7 + py-0.5 + rounded-none; + } + + .singlestage-select-icon { + @apply -translate-y-1/2 + right-2.5 + size-4 + text-muted-foreground + top-1/2; + } +} diff --git a/singlestage/src/components/select/style/base/lyra/select_dark.css b/singlestage/src/components/select/style/base/lyra/select_dark.css new file mode 100644 index 00000000..80c3e3ac --- /dev/null +++ b/singlestage/src/components/select/style/base/lyra/select_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-select { + @apply bg-input/30; + + &:hover { + @apply bg-input/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/select/style/base/maia/select.css b/singlestage/src/components/select/style/base/maia/select.css new file mode 100644 index 00000000..dd723d75 --- /dev/null +++ b/singlestage/src/components/select/style/base/maia/select.css @@ -0,0 +1,52 @@ +@layer components1 { + .singlestage-select { + @apply appearance-none + bg-input/30 + border + border-input + h-9 + min-w-0 + pl-3 + pr-8 + py-1 + rounded-4xl + select-none + text-sm + transition-colors + w-full; + + &:focus-visible { + @apply border-ring + ring-[3px] + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-[3px] + ring-destructive/20; + } + + &::selection { + @apply bg-primary + text-primary-foreground; + } + } + + .singlestage-select-placeholder { + @apply text-muted-foreground; + } + + .singlestage-select-size-sm { + @apply h-8; + } + + .singlestage-select-icon { + @apply -translate-y-1/2 + right-3.5 + size-4 + text-muted-foreground + top-1/2; + } +} diff --git a/singlestage/src/components/select/style/base/maia/select_dark.css b/singlestage/src/components/select/style/base/maia/select_dark.css new file mode 100644 index 00000000..80c3e3ac --- /dev/null +++ b/singlestage/src/components/select/style/base/maia/select_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-select { + @apply bg-input/30; + + &:hover { + @apply bg-input/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/select/style/base/mira/select.css b/singlestage/src/components/select/style/base/mira/select.css new file mode 100644 index 00000000..6c8fc79d --- /dev/null +++ b/singlestage/src/components/select/style/base/mira/select.css @@ -0,0 +1,58 @@ +@layer components1 { + .singlestage-select { + @apply appearance-none + bg-input/20 + border + border-input + h-7 + min-w-0 + pl-2 + pr-6 + py-0.5 + rounded-md + select-none + text-xs/relaxed + transition-colors + w-full; + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + } + + &::selection { + @apply bg-primary + text-primary-foreground; + } + } + + .singlestage-select-placeholder { + @apply text-muted-foreground; + } + + .singlestage-select-icon { + @apply -translate-y-1/2 + right-1.5 + size-3.5 + text-muted-foreground + top-1/2; + } + + .singlestage-select-size-sm { + @apply h-6 + text-[0.625rem]; + + .singlestage-select-icon { + @apply -translate-y-[calc(--spacing(1.25))] + size-3; + } + } +} diff --git a/singlestage/src/components/select/style/base/mira/select_dark.css b/singlestage/src/components/select/style/base/mira/select_dark.css new file mode 100644 index 00000000..80c3e3ac --- /dev/null +++ b/singlestage/src/components/select/style/base/mira/select_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-select { + @apply bg-input/30; + + &:hover { + @apply bg-input/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/select/style/base/nova/select.css b/singlestage/src/components/select/style/base/nova/select.css new file mode 100644 index 00000000..c11cbd51 --- /dev/null +++ b/singlestage/src/components/select/style/base/nova/select.css @@ -0,0 +1,54 @@ +@layer components1 { + .singlestage-select { + @apply appearance-none + bg-transparent + border + border-input + h-8 + min-w-0 + pl-2.5 + pr-8 + py-1 + rounded-lg + select-none + text-sm + transition-colors + w-full; + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &::selection { + @apply bg-primary + text-primary-foreground; + } + } + + .singlestage-select-placeholder { + @apply text-muted-foreground; + } + + .singlestage-select-size-sm { + @apply h-7 + py-0.5 + rounded-[min(var(--radius-md),10px)]; + } + + .singlestage-select-icon { + @apply -translate-y-1/2 + right-2.5 + size-4 + text-muted-foreground + top-1/2; + } +} diff --git a/singlestage/src/components/select/style/base/nova/select_dark.css b/singlestage/src/components/select/style/base/nova/select_dark.css new file mode 100644 index 00000000..80c3e3ac --- /dev/null +++ b/singlestage/src/components/select/style/base/nova/select_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-select { + @apply bg-input/30; + + &:hover { + @apply bg-input/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/select/style/base/sera/select.css b/singlestage/src/components/select/style/base/sera/select.css new file mode 100644 index 00000000..f56ce470 --- /dev/null +++ b/singlestage/src/components/select/style/base/sera/select.css @@ -0,0 +1,49 @@ +@layer components1 { + .singlestage-select { + @apply appearance-none + bg-transparent + border + border-b-input + border-transparent + h-10 + min-w-0 + pl-0 + pr-8 + py-2 + rounded-none + select-none + text-sm + transition-[color,border-color] + w-full; + + &:focus-visible { + @apply border-b-ring; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-b-destructive; + } + + &::selection { + @apply bg-primary + text-primary-foreground; + } + } + + .singlestage-select-placeholder { + @apply text-muted-foreground; + } + + .singlestage-select-size-sm { + @apply h-9; + } + + .singlestage-select-icon { + @apply -translate-y-1/2 + right-0 + size-3.5 + text-muted-foreground + top-1/2; + } +} diff --git a/singlestage/src/components/select/style/base/sera/select_dark.css b/singlestage/src/components/select/style/base/sera/select_dark.css new file mode 100644 index 00000000..6ff762dd --- /dev/null +++ b/singlestage/src/components/select/style/base/sera/select_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-select { + &:invalid, + &[aria-invalid="true"] { + @apply border-b-destructive/50; + } + } +} diff --git a/singlestage/src/components/select/style/base/vega/select.css b/singlestage/src/components/select/style/base/vega/select.css new file mode 100644 index 00000000..1d6ceaf2 --- /dev/null +++ b/singlestage/src/components/select/style/base/vega/select.css @@ -0,0 +1,53 @@ +@layer components1 { + .singlestage-select { + @apply appearance-none + bg-transparent + border + border-input + h-9 + min-w-0 + pl-2.5 + pr-8 + py-1 + rounded-md + select-none + shadow-xs + text-sm + transition-[color,box-shadow] + w-full; + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &::selection { + @apply bg-primary + text-primary-foreground; + } + } + + .singlestage-select-placeholder { + @apply text-muted-foreground; + } + + .singlestage-select-size-sm { + @apply h-8; + } + + .singlestage-select-icon { + @apply -translate-y-1/2 + right-2.5 + size-4 + text-muted-foreground + top-1/2; + } +} diff --git a/singlestage/src/components/select/style/base/vega/select_dark.css b/singlestage/src/components/select/style/base/vega/select_dark.css new file mode 100644 index 00000000..80c3e3ac --- /dev/null +++ b/singlestage/src/components/select/style/base/vega/select_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-select { + @apply bg-input/30; + + &:hover { + @apply bg-input/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/select/style/select.css b/singlestage/src/components/select/style/select.css new file mode 100644 index 00000000..ad02af36 --- /dev/null +++ b/singlestage/src/components/select/style/select.css @@ -0,0 +1,32 @@ +@layer components { + .singlestage-select-wrapper { + @apply relative + w-fit; + + &:has(.singlestage-select:disabled) { + @apply opacity-50; + } + } + + .singlestage-select { + @apply outline-none; + + &:disabled, + &[aria-disabled="true"] { + @apply cursor-not-allowed + pointer-events-none; + } + + option, + optgroup { + @apply bg-popover + text-popover-foreground; + } + } + + .singlestage-select-icon { + @apply absolute + pointer-events-none + select-none; + } +} diff --git a/singlestage/src/components/separator/mod.rs b/singlestage/src/components/separator/mod.rs index dc4b34af..93fa7df1 100644 --- a/singlestage/src/components/separator/mod.rs +++ b/singlestage/src/components/separator/mod.rs @@ -1,11 +1,15 @@ +use crate::{ItemContext, primitives::*}; use leptos::prelude::*; /// The separator. #[component] pub fn Separator( - /// Toggle whether or not the separator should display vertically. + #[prop(optional)] children: Option<Children>, + + /// Specify the orientation of the separator + /// Accepted values: `"vertical"` | `"horizontal"` (default) #[prop(optional, into)] - vertical: MaybeProp<bool>, + orientation: MaybeProp<String>, // GLOBAL ATTRIBUTES // @@ -117,7 +121,6 @@ pub fn Separator( 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() @@ -152,18 +155,46 @@ pub fn Separator( /> }; - view! { - <div - class=move || format!("singlestage-separator {}", class.get().unwrap_or_default()) - data-orientation=match vertical.get().unwrap_or_default() { - false => "horizontal", - true => "vertical", - } - .to_string() - data-slot="separator" + let in_popover: bool = use_context::<PopoverMenuContext>().is_some(); + let in_item: bool = use_context::<ItemContext>().is_some(); + + if in_popover { + view! { + <hr + class=move || { + format!( + "singlestage-dropdown-menu-separator {}", + class.get().unwrap_or_default(), + ) + } + role="separator" + + {..global_attrs_1} + {..global_attrs_2} + /> + {children.map(|c| c())} + } + .into_any() + } else { + view! { + <div + class=move || { + format!( + "singlestage-separator{} {} {}", + if in_item { " singlestage-item-separator" } else { "" }, + match orientation.get().unwrap_or_default().as_str() { + "vertical" => "singlestage-separator-vertical", + _ => "singlestage-separator-horizontal", + }, + class.get().unwrap_or_default(), + ) + } - {..global_attrs_1} - {..global_attrs_2} - ></div> + {..global_attrs_1} + {..global_attrs_2} + ></div> + {children.map(|c| c())} + } + .into_any() } } diff --git a/singlestage/src/components/separator/separator.css b/singlestage/src/components/separator/separator.css deleted file mode 100644 index 0158af11..00000000 --- a/singlestage/src/components/separator/separator.css +++ /dev/null @@ -1,15 +0,0 @@ -@layer components { - .singlestage-separator { - @apply bg-border - my-4 - shrink-0; - &[data-orientation="horizontal"] { - @apply h-px - w-full; - } - &[data-orientation="vertical"] { - @apply h-full - w-px; - } - } -} diff --git a/singlestage/src/components/separator/style/base/luma/separator.css b/singlestage/src/components/separator/style/base/luma/separator.css new file mode 100644 index 00000000..6397dd8f --- /dev/null +++ b/singlestage/src/components/separator/style/base/luma/separator.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-separator { + @apply bg-border + shrink-0; + } + + .singlestage-separator-horizontal { + @apply h-px + w-full; + } + + .singlestage-separator-vertical { + @apply h-full + w-px; + } +} diff --git a/singlestage/src/components/separator/style/base/lyra/separator.css b/singlestage/src/components/separator/style/base/lyra/separator.css new file mode 100644 index 00000000..6397dd8f --- /dev/null +++ b/singlestage/src/components/separator/style/base/lyra/separator.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-separator { + @apply bg-border + shrink-0; + } + + .singlestage-separator-horizontal { + @apply h-px + w-full; + } + + .singlestage-separator-vertical { + @apply h-full + w-px; + } +} diff --git a/singlestage/src/components/separator/style/base/maia/separator.css b/singlestage/src/components/separator/style/base/maia/separator.css new file mode 100644 index 00000000..6397dd8f --- /dev/null +++ b/singlestage/src/components/separator/style/base/maia/separator.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-separator { + @apply bg-border + shrink-0; + } + + .singlestage-separator-horizontal { + @apply h-px + w-full; + } + + .singlestage-separator-vertical { + @apply h-full + w-px; + } +} diff --git a/singlestage/src/components/separator/style/base/mira/separator.css b/singlestage/src/components/separator/style/base/mira/separator.css new file mode 100644 index 00000000..6397dd8f --- /dev/null +++ b/singlestage/src/components/separator/style/base/mira/separator.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-separator { + @apply bg-border + shrink-0; + } + + .singlestage-separator-horizontal { + @apply h-px + w-full; + } + + .singlestage-separator-vertical { + @apply h-full + w-px; + } +} diff --git a/singlestage/src/components/separator/style/base/nova/separator.css b/singlestage/src/components/separator/style/base/nova/separator.css new file mode 100644 index 00000000..6397dd8f --- /dev/null +++ b/singlestage/src/components/separator/style/base/nova/separator.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-separator { + @apply bg-border + shrink-0; + } + + .singlestage-separator-horizontal { + @apply h-px + w-full; + } + + .singlestage-separator-vertical { + @apply h-full + w-px; + } +} diff --git a/singlestage/src/components/separator/style/base/sera/separator.css b/singlestage/src/components/separator/style/base/sera/separator.css new file mode 100644 index 00000000..6397dd8f --- /dev/null +++ b/singlestage/src/components/separator/style/base/sera/separator.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-separator { + @apply bg-border + shrink-0; + } + + .singlestage-separator-horizontal { + @apply h-px + w-full; + } + + .singlestage-separator-vertical { + @apply h-full + w-px; + } +} diff --git a/singlestage/src/components/separator/style/base/vega/separator.css b/singlestage/src/components/separator/style/base/vega/separator.css new file mode 100644 index 00000000..6397dd8f --- /dev/null +++ b/singlestage/src/components/separator/style/base/vega/separator.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-separator { + @apply bg-border + shrink-0; + } + + .singlestage-separator-horizontal { + @apply h-px + w-full; + } + + .singlestage-separator-vertical { + @apply h-full + w-px; + } +} diff --git a/singlestage/src/components/separator/style/separator.css b/singlestage/src/components/separator/style/separator.css new file mode 100644 index 00000000..fe1ccece --- /dev/null +++ b/singlestage/src/components/separator/style/separator.css @@ -0,0 +1,18 @@ +@layer components { + .singlestage-separator { + @apply bg-border + shrink-0; + } + + .singlestage-separator-horizontal { + @apply h-px + mx-auto + w-full; + } + + .singlestage-separator-vertical { + @apply self-stretch + my-auto + w-px; + } +} diff --git a/singlestage/src/components/sheet/close.rs b/singlestage/src/components/sheet/close.rs new file mode 100644 index 00000000..9447ff37 --- /dev/null +++ b/singlestage/src/components/sheet/close.rs @@ -0,0 +1,9 @@ +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone)] +pub(crate) struct SheetCloseContext {} + +#[component] +pub fn SheetClose(children: Children) -> impl IntoView { + view! { <Provider value=SheetCloseContext {}>{children()}</Provider> } +} diff --git a/singlestage/src/components/sheet/content.rs b/singlestage/src/components/sheet/content.rs new file mode 100644 index 00000000..f8efe505 --- /dev/null +++ b/singlestage/src/components/sheet/content.rs @@ -0,0 +1,224 @@ +use crate::{Button, Reactive, SheetClose, SheetContext}; +use leptos::prelude::*; + +#[component] +pub fn SheetContent( + children: Children, + #[prop(optional, into)] side: Reactive<String>, + #[prop(optional, into, default = true.into())] show_close_button: MaybeProp<bool>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let sheet_context = expect_context::<SheetContext>(); + let overlay_ref = NodeRef::<leptos::html::Dialog>::new(); + + Effect::new(move || { + if let Some(overlay) = overlay_ref.get() { + match sheet_context.open.get() { + true => { + let _ = overlay.show_modal(); + } + false => { + overlay.close(); + } + } + } + }); + + 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() + 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! { + <dialog + class="singlestage-sheet-overlay" + id={ + let content_id = id.get().unwrap_or(uuid::Uuid::new_v4().to_string()); + sheet_context.content_id.set(content_id.clone()); + content_id + } + node_ref=overlay_ref + closedby="any" + > + <aside + class=move || { + format!( + "singlestage-sheet-content {} {}", + match side.get().as_str() { + "top" => "singlestage-sheet-side-top", + "bottom" => "singlestage-sheet-side-bottom", + "left" => "singlestage-sheet-side-left", + _ => "singlestage-sheet-side-right", + }, + class.get().unwrap_or_default(), + ) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + <Show when=move || show_close_button.get().unwrap_or_default()> + <SheetClose> + <Button variant="ghost" class="singlestage-sheet-close" size="icon-sm"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + class="lucide lucide-x-icon lucide-x" + > + <path d="M18 6 6 18" /> + <path d="m6 6 12 12" /> + </svg> + <span class="sr-only">"Close"</span> + </Button> + </SheetClose> + </Show> + </aside> + </dialog> + } +} diff --git a/singlestage/src/components/sheet/description.rs b/singlestage/src/components/sheet/description.rs new file mode 100644 index 00000000..60b841e8 --- /dev/null +++ b/singlestage/src/components/sheet/description.rs @@ -0,0 +1,163 @@ +use leptos::prelude::*; + +#[component] +pub fn SheetDescription( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <p + class=move || { + format!("singlestage-sheet-description {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </p> + } +} diff --git a/singlestage/src/components/sheet/footer.rs b/singlestage/src/components/sheet/footer.rs new file mode 100644 index 00000000..d80676f0 --- /dev/null +++ b/singlestage/src/components/sheet/footer.rs @@ -0,0 +1,163 @@ +use leptos::prelude::*; + +#[component] +pub fn SheetFooter( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <footer + class=move || { + format!("singlestage-sheet-footer {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </footer> + } +} diff --git a/singlestage/src/components/sheet/header.rs b/singlestage/src/components/sheet/header.rs new file mode 100644 index 00000000..b0f955cf --- /dev/null +++ b/singlestage/src/components/sheet/header.rs @@ -0,0 +1,161 @@ +use leptos::prelude::*; + +#[component] +pub fn SheetHeader( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <hgroup + class=format!("singlestage-sheet-header {}", class.get().unwrap_or_default()) + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </hgroup> + } +} diff --git a/singlestage/src/components/sheet/mod.rs b/singlestage/src/components/sheet/mod.rs new file mode 100644 index 00000000..42aa355f --- /dev/null +++ b/singlestage/src/components/sheet/mod.rs @@ -0,0 +1,17 @@ +mod close; +mod content; +mod description; +mod footer; +mod header; +mod sheet; +mod title; +mod trigger; + +pub use close::*; +pub use content::*; +pub use description::*; +pub use footer::*; +pub use header::*; +pub use sheet::*; +pub use title::*; +pub use trigger::*; diff --git a/singlestage/src/components/sheet/sheet.rs b/singlestage/src/components/sheet/sheet.rs new file mode 100644 index 00000000..cb9de4f5 --- /dev/null +++ b/singlestage/src/components/sheet/sheet.rs @@ -0,0 +1,23 @@ +use crate::Reactive; +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone)] +pub struct SheetContext { + pub content_id: RwSignal<String>, + pub open: Reactive<bool>, + pub trigger_id: RwSignal<String>, +} + +#[component] +pub fn Sheet(children: Children, #[prop(optional, into)] open: Reactive<bool>) -> impl IntoView { + let content_id = RwSignal::new(String::new()); + let trigger_id = RwSignal::new(String::new()); + + let context = SheetContext { + content_id, + open, + trigger_id, + }; + + view! { <Provider value=context>{children()}</Provider> } +} diff --git a/singlestage/src/components/sheet/style/base/luma/sheet.css b/singlestage/src/components/sheet/style/base/luma/sheet.css new file mode 100644 index 00000000..4ccda818 --- /dev/null +++ b/singlestage/src/components/sheet/style/base/luma/sheet.css @@ -0,0 +1,81 @@ +@layer components1 { + .singlestage-sheet-overlay::backdrop { + @apply bg-black/30 + supports-backdrop-filter:backdrop-blur-sm; + } + + .singlestage-sheet-content { + @apply bg-clip-padding + bg-popover + /* duration-200 */ + /* ease-in-out */ + fixed + flex + flex-col + shadow-xl + text-popover-foreground + text-sm + /* transition */ + z-50; + } + + .singlestage-sheet-side-top { + @apply border-b + h-auto + inset-x-0 + top-0; + } + + .singlestage-sheet-side-right { + @apply border-l + h-full + inset-y-0 + right-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-side-bottom { + @apply border-t + bottom-0 + h-auto + inset-x-0; + } + + .singlestage-sheet-side-left { + @apply border-r + h-full + inset-y-0 + left-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-close { + @apply absolute + bg-secondary + right-4 + top-4; + } + + .singlestage-sheet-header { + @apply gap-1.5 + p-6; + } + + .singlestage-sheet-footer { + @apply gap-2 + p-6; + } + + .singlestage-sheet-title { + @apply font-medium + text-base + text-foreground; + } + + .singlestage-sheet-description { + @apply text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/sheet/style/base/lyra/sheet.css b/singlestage/src/components/sheet/style/base/lyra/sheet.css new file mode 100644 index 00000000..282b2c54 --- /dev/null +++ b/singlestage/src/components/sheet/style/base/lyra/sheet.css @@ -0,0 +1,80 @@ +@layer components1 { + .singlestage-sheet-overlay::backdrop { + @apply bg-black/10 + supports-backdrop-filter:backdrop-blur-xs; + } + + .singlestage-sheet-content { + @apply bg-clip-padding + bg-popover + /* duration-200 */ + /* ease-in-out */ + fixed + flex + flex-col + shadow-lg + text-popover-foreground + text-xs/relaxed + /* transition */ + z-50; + } + + .singlestage-sheet-side-top { + @apply border-b + h-auto + inset-x-0 + top-0; + } + + .singlestage-sheet-side-right { + @apply border-l + h-full + inset-y-0 + right-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-side-bottom { + @apply border-t + bottom-0 + h-auto + inset-x-0; + } + + .singlestage-sheet-side-left { + @apply border-r + h-full + inset-y-0 + left-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-close { + @apply absolute + right-3 + top-3; + } + + .singlestage-sheet-header { + @apply gap-0.5 + p-4; + } + + .singlestage-sheet-footer { + @apply gap-2 + p-4; + } + + .singlestage-sheet-title { + @apply font-medium + text-foreground + text-sm; + } + + .singlestage-sheet-description { + @apply text-muted-foreground + text-xs/relaxed; + } +} diff --git a/singlestage/src/components/sheet/style/base/maia/sheet.css b/singlestage/src/components/sheet/style/base/maia/sheet.css new file mode 100644 index 00000000..cbe1eba3 --- /dev/null +++ b/singlestage/src/components/sheet/style/base/maia/sheet.css @@ -0,0 +1,80 @@ +@layer components1 { + .singlestage-sheet-overlay::backdrop { + @apply bg-black/80 + supports-backdrop-filter:backdrop-blur-xs; + } + + .singlestage-sheet-content { + @apply bg-clip-padding + bg-popover + /* duration-200 */ + /* ease-in-out */ + fixed + flex + flex-col + shadow-lg + text-popover-foreground + text-sm + /* transition */ + z-50; + } + + .singlestage-sheet-side-top { + @apply border-b + h-auto + inset-x-0 + top-0; + } + + .singlestage-sheet-side-right { + @apply border-l + h-full + inset-y-0 + right-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-side-bottom { + @apply border-t + bottom-0 + h-auto + inset-x-0; + } + + .singlestage-sheet-side-left { + @apply border-r + h-full + inset-y-0 + left-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-close { + @apply absolute + right-4 + top-4; + } + + .singlestage-sheet-header { + @apply gap-1.5 + p-6; + } + + .singlestage-sheet-footer { + @apply gap-2 + p-6; + } + + .singlestage-sheet-title { + @apply font-medium + text-base + text-foreground; + } + + .singlestage-sheet-description { + @apply text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/sheet/style/base/mira/sheet.css b/singlestage/src/components/sheet/style/base/mira/sheet.css new file mode 100644 index 00000000..c82e7199 --- /dev/null +++ b/singlestage/src/components/sheet/style/base/mira/sheet.css @@ -0,0 +1,80 @@ +@layer components1 { + .singlestage-sheet-overlay::backdrop { + @apply bg-black/80 + supports-backdrop-filter:backdrop-blur-xs; + } + + .singlestage-sheet-content { + @apply bg-clip-padding + bg-popover + /* duration-200 */ + /* ease-in-out */ + fixed + flex + flex-col + shadow-lg + text-popover-foreground + text-xs/relaxed + /* transition */ + z-50; + } + + .singlestage-sheet-side-top { + @apply border-b + h-auto + inset-x-0 + top-0; + } + + .singlestage-sheet-side-right { + @apply border-l + h-full + inset-y-0 + right-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-side-bottom { + @apply border-t + bottom-0 + h-auto + inset-x-0; + } + + .singlestage-sheet-side-left { + @apply border-r + h-full + inset-y-0 + left-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-close { + @apply absolute + right-4 + top-4; + } + + .singlestage-sheet-header { + @apply gap-1.5 + p-6; + } + + .singlestage-sheet-footer { + @apply gap-2 + p-6; + } + + .singlestage-sheet-title { + @apply font-medium + text-foreground + text-sm; + } + + .singlestage-sheet-description { + @apply text-muted-foreground + text-xs/relaxed; + } +} diff --git a/singlestage/src/components/sheet/style/base/nova/sheet.css b/singlestage/src/components/sheet/style/base/nova/sheet.css new file mode 100644 index 00000000..4706c55d --- /dev/null +++ b/singlestage/src/components/sheet/style/base/nova/sheet.css @@ -0,0 +1,81 @@ +@layer components1 { + .singlestage-sheet-overlay::backdrop { + @apply bg-black/10 + supports-backdrop-filter:backdrop-blur-xs; + } + + .singlestage-sheet-content { + @apply bg-clip-padding + bg-popover + /* duration-200 */ + /* ease-in-out */ + fixed + flex + flex-col + gap-4 + shadow-lg + text-popover-foreground + text-sm + /* transition */ + z-50; + } + + .singlestage-sheet-side-top { + @apply border-b + h-auto + inset-x-0 + top-0; + } + + .singlestage-sheet-side-right { + @apply border-l + h-full + inset-y-0 + right-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-side-bottom { + @apply border-t + bottom-0 + h-auto + inset-x-0; + } + + .singlestage-sheet-side-left { + @apply border-r + h-full + inset-y-0 + left-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-close { + @apply absolute + right-3 + top-3; + } + + .singlestage-sheet-header { + @apply gap-0.5 + p-4; + } + + .singlestage-sheet-footer { + @apply gap-2 + p-4; + } + + .singlestage-sheet-title { + @apply font-medium + text-base + text-foreground; + } + + .singlestage-sheet-description { + @apply text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/sheet/style/base/sera/sheet.css b/singlestage/src/components/sheet/style/base/sera/sheet.css new file mode 100644 index 00000000..3b03de97 --- /dev/null +++ b/singlestage/src/components/sheet/style/base/sera/sheet.css @@ -0,0 +1,85 @@ +@layer components1 { + .singlestage-sheet-overlay::backdrop { + @apply bg-black/20 + supports-backdrop-filter:backdrop-blur-sm; + } + + .singlestage-sheet-content { + @apply bg-clip-padding + bg-popover + /* duration-200 */ + /* ease-in-out */ + fixed + flex + flex-col + shadow-md + text-popover-foreground + text-sm + /* transition */ + z-50; + } + + .singlestage-sheet-side-top { + @apply border-b + h-auto + inset-x-0 + top-0; + } + + .singlestage-sheet-side-right { + @apply border-l + h-full + inset-y-0 + right-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-side-bottom { + @apply border-t + bottom-0 + h-auto + inset-x-0; + } + + .singlestage-sheet-side-left { + @apply border-r + h-full + inset-y-0 + left-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-close { + @apply absolute + bg-secondary + right-4 + top-4; + } + + .singlestage-sheet-header { + @apply gap-1.5 + p-8; + } + + .singlestage-sheet-footer { + @apply gap-2 + p-8; + } + + .singlestage-sheet-title { + @apply font-semibold + text-foreground + text-lg + tracking-wider + uppercase; + } + + .singlestage-sheet-description { + @apply leading-relaxed + mt-0.5 + text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/sheet/style/base/vega/sheet.css b/singlestage/src/components/sheet/style/base/vega/sheet.css new file mode 100644 index 00000000..c7ae343a --- /dev/null +++ b/singlestage/src/components/sheet/style/base/vega/sheet.css @@ -0,0 +1,80 @@ +@layer components1 { + .singlestage-sheet-overlay::backdrop { + @apply bg-black/10 + supports-backdrop-filter:backdrop-blur-xs; + } + + .singlestage-sheet-content { + @apply bg-clip-padding + bg-popover + /* duration-200 */ + /* ease-in-out */ + fixed + flex + flex-col + gap-4 + shadow-lg + text-popover-foreground + text-sm + /* transition */ + z-50; + } + + .singlestage-sheet-side-top { + @apply border-b + h-auto + inset-x-0 + top-0; + } + + .singlestage-sheet-side-right { + @apply border-l + h-full + inset-y-0 + right-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-side-bottom { + @apply border-t + bottom-0 + h-auto + inset-x-0; + } + + .singlestage-sheet-side-left { + @apply border-r + h-full + inset-y-0 + left-0 + sm:max-w-sm + w-3/4; + } + + .singlestage-sheet-close { + @apply absolute + right-4 + top-4; + } + + .singlestage-sheet-header { + @apply gap-1.5 + p-4; + } + + .singlestage-sheet-footer { + @apply gap-2 + p-4; + } + + .singlestage-sheet-title { + @apply text-foreground + font-medium; + } + + .singlestage-sheet-description { + @apply text-muted-foreground + text-sm; + } +} diff --git a/singlestage/src/components/sheet/style/sheet.css b/singlestage/src/components/sheet/style/sheet.css new file mode 100644 index 00000000..7cd35bef --- /dev/null +++ b/singlestage/src/components/sheet/style/sheet.css @@ -0,0 +1,89 @@ +@layer components { + @media (prefers-reduced-motion: no-preference) { + .singlestage-sheet-overlay { + transition: + opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1), + overlay 0.15s allow-discrete, + display 0.15s allow-discrete; + + opacity: 0; + + ::webkit-scrollbar { + display: none; + } + + &::backdrop { + transition: + display 0.1s allow-discrete, + overlay 0.1s allow-discrete, + background-color 0.1s; + background-color: hsl(0 0 0 / 0); + } + + &[open] { + opacity: 1; + } + } + + @starting-style { + .singlestage-sheet-overlay[open] { + opacity: 0; + + &::backdrop { + background-color: hsl(0 0 0 / 0); + } + } + } + + /* Disable scrolling while popover is open */ + html:has(.singlestage-sheet-overlay[open]) { + overflow: hidden; + } + } + + .singlestage-sheet-overlay { + ::backdrop { + @apply fixed + inset-0 + /* transition-opacity */ + z-50; + } + } + + /* TODO: */ + /* .singlestage-sheet-side-top { */ + /* @apply data-ending-style:translate-y-[-2.5rem] */ + /* data-starting-style:translate-y-[-2.5rem]; */ + /* } */ + /**/ + /* .singlestage-sheet-side-right { */ + /* @apply data-ending-style:translate-x-[2.5rem] */ + /* data-starting-style:translate-x-[2.5rem]; */ + /* } */ + /**/ + /* .singlestage-sheet-side-bottom { */ + /* @apply data-ending-style:translate-y-[2.5rem] */ + /* data-starting-style:translate-y-[2.5rem]; */ + /* } */ + /**/ + /* .singlestage-sheet-side-left { */ + /* @apply data-ending-style:translate-x-[-2.5rem] */ + /* data-starting-style:translate-x-[-2.5rem]; */ + /* } */ + + .singlestage-sheet-header { + @apply flex + flex-col; + } + + .singlestage-sheet-footer { + @apply flex + flex-col + mt-auto; + } + + /* Disable scrolling while popover is open */ + html:has(.singlestage-sheet-overlay:popover-open) { + overflow: hidden; + } +} diff --git a/singlestage/src/components/sheet/title.rs b/singlestage/src/components/sheet/title.rs new file mode 100644 index 00000000..141f66d9 --- /dev/null +++ b/singlestage/src/components/sheet/title.rs @@ -0,0 +1,161 @@ +use leptos::prelude::*; + +#[component] +pub fn SheetTitle( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <h2 + class=move || { format!("singlestage-sheet-title {}", class.get().unwrap_or_default()) } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </h2> + } +} diff --git a/singlestage/src/components/sheet/trigger.rs b/singlestage/src/components/sheet/trigger.rs new file mode 100644 index 00000000..150ba3d4 --- /dev/null +++ b/singlestage/src/components/sheet/trigger.rs @@ -0,0 +1,7 @@ +use crate::primitives::*; +use leptos::prelude::*; + +#[component] +pub fn SheetTrigger(children: Children) -> impl IntoView { + view! { <TriggerPrimitive>{children()}</TriggerPrimitive> } +} diff --git a/singlestage/src/components/sidebar/collapsible/mod.rs b/singlestage/src/components/sidebar/collapsible/mod.rs deleted file mode 100644 index dce374a4..00000000 --- a/singlestage/src/components/sidebar/collapsible/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod collapsible; -mod content; -mod sub; -mod sub_item; - -pub use collapsible::*; -pub use content::*; -pub use sub::*; -pub use sub_item::*; - -#[derive(Clone)] -pub struct CollapsibleContext { - id: String, -} diff --git a/singlestage/src/components/sidebar/collapsible/sub.rs b/singlestage/src/components/sidebar/collapsible/sub.rs deleted file mode 100644 index 8e782be1..00000000 --- a/singlestage/src/components/sidebar/collapsible/sub.rs +++ /dev/null @@ -1,7 +0,0 @@ -use leptos::prelude::*; - -/// Wraps a submenu within a SidebarMenu. -#[component] -pub fn SidebarMenuSub(children: Children) -> impl IntoView { - view! { {children()} } -} diff --git a/singlestage/src/components/sidebar/content.rs b/singlestage/src/components/sidebar/content.rs index 3e651ae1..9e33cf3f 100644 --- a/singlestage/src/components/sidebar/content.rs +++ b/singlestage/src/components/sidebar/content.rs @@ -152,8 +152,12 @@ pub fn SidebarContent( view! { <section - id="sidebar-content" - class=move || { format!("singlestage-scrollbar {}", class.get().unwrap_or_default()) } + class=move || { + format!( + "singlestage-sidebar-content singlestage-scrollbar {}", + class.get().unwrap_or_default(), + ) + } {..global_attrs_1} {..global_attrs_2} diff --git a/singlestage/src/components/sidebar/footer.rs b/singlestage/src/components/sidebar/footer.rs index 8832d0b5..39a1c07e 100644 --- a/singlestage/src/components/sidebar/footer.rs +++ b/singlestage/src/components/sidebar/footer.rs @@ -1,4 +1,3 @@ -use super::SidebarContext; use leptos::prelude::*; /// A sticky footer displayed at the bottom of the sidebar. @@ -111,14 +110,12 @@ pub fn SidebarFooter( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let sidebar = expect_context::<SidebarContext>(); - 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() @@ -155,7 +152,7 @@ pub fn SidebarFooter( view! { <footer - on:click=move |_| sidebar.close_if_small_screen() + class=move || format!("singlestage-sidebar-footer {}", class.get().unwrap_or_default()) {..global_attrs_1} {..global_attrs_2} diff --git a/singlestage/src/components/sidebar/group/content.rs b/singlestage/src/components/sidebar/group/content.rs index cb41768c..dac68ee8 100644 --- a/singlestage/src/components/sidebar/group/content.rs +++ b/singlestage/src/components/sidebar/group/content.rs @@ -2,6 +2,164 @@ use leptos::prelude::*; /// Wraps the content of a sidebar group. #[component] -pub fn SidebarGroupContent(children: Children) -> impl IntoView { - view! { {children()} } +pub fn SidebarGroupContent( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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! { + <div + class=move || { + format!("singlestage-sidebar-group-content {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + } } diff --git a/singlestage/src/components/sidebar/group/group.rs b/singlestage/src/components/sidebar/group/group.rs index e8e232e6..cf048a0d 100644 --- a/singlestage/src/components/sidebar/group/group.rs +++ b/singlestage/src/components/sidebar/group/group.rs @@ -120,7 +120,7 @@ pub fn SidebarGroup( 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() @@ -157,8 +157,8 @@ pub fn SidebarGroup( view! { <div - id="group" - aria-labelledby=move || label_id.get() + aria_labelledby=move || label_id.get() + class=move || format!("singlestage-sidebar-group {}", class.get().unwrap_or_default()) role="group" {..global_attrs_1} diff --git a/singlestage/src/components/sidebar/group/label.rs b/singlestage/src/components/sidebar/group/label.rs index 75e97971..01870cd6 100644 --- a/singlestage/src/components/sidebar/group/label.rs +++ b/singlestage/src/components/sidebar/group/label.rs @@ -113,15 +113,15 @@ pub fn SidebarGroupLabel( ) -> impl IntoView { let group = expect_context::<SidebarGroupContext>(); - let uuid: String = uuid::Uuid::new_v4().to_string(); - group.label_id.set(uuid.clone()); + let uuid = uuid::Uuid::new_v4(); + group.label_id.set(uuid.to_string()); 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() @@ -157,7 +157,15 @@ pub fn SidebarGroupLabel( }; view! { - <h3 {..global_attrs_1} {..global_attrs_2} id=uuid> + <h3 + class=move || { + format!("singlestage-sidebar-group-label {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + id=uuid.to_string() + > {children()} </h3> } diff --git a/singlestage/src/components/sidebar/group/mod.rs b/singlestage/src/components/sidebar/group/mod.rs index 85c262b1..c0dfe739 100644 --- a/singlestage/src/components/sidebar/group/mod.rs +++ b/singlestage/src/components/sidebar/group/mod.rs @@ -9,6 +9,6 @@ pub use label::*; use leptos::prelude::*; #[derive(Clone)] -pub struct SidebarGroupContext { +pub(crate) struct SidebarGroupContext { pub label_id: RwSignal<String>, } diff --git a/singlestage/src/components/sidebar/header.rs b/singlestage/src/components/sidebar/header.rs index 4ae3aa9e..7205cb45 100644 --- a/singlestage/src/components/sidebar/header.rs +++ b/singlestage/src/components/sidebar/header.rs @@ -1,4 +1,4 @@ -use super::SidebarContext; +// use super::SidebarContext; use leptos::prelude::*; #[component] @@ -110,14 +110,12 @@ pub fn SidebarHeader( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let sidebar = expect_context::<SidebarContext>(); - 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() @@ -154,7 +152,7 @@ pub fn SidebarHeader( view! { <header - on:click=move |_| sidebar.close_if_small_screen() + class=format!("singlestage-sidebar-header {}", class.get().unwrap_or_default()) {..global_attrs_1} {..global_attrs_2} diff --git a/singlestage/src/components/sidebar/inset.rs b/singlestage/src/components/sidebar/inset.rs new file mode 100644 index 00000000..e601f61a --- /dev/null +++ b/singlestage/src/components/sidebar/inset.rs @@ -0,0 +1,163 @@ +// TODO: Work on this + +use leptos::prelude::*; + +#[component] +pub fn SidebarInset( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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() + /> + }; + + 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! { + <main + class=move || format!("singlestage-sidebar-inset {}", class.get().unwrap_or_default()) + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </main> + } +} diff --git a/singlestage/src/components/sidebar/menu/action.rs b/singlestage/src/components/sidebar/menu/action.rs new file mode 100644 index 00000000..4258932d --- /dev/null +++ b/singlestage/src/components/sidebar/menu/action.rs @@ -0,0 +1,25 @@ +use crate::Button; +use leptos::prelude::*; + +#[component] +pub fn SidebarMenuAction( + children: Children, + #[prop(optional, into)] class: MaybeProp<String>, + #[prop(optional, into)] show_on_hover: MaybeProp<bool>, +) -> impl IntoView { + view! { + <Button + as_child=true + class=format!( + "singlestage-sidebar-menu-action{} {}", + match show_on_hover.get_untracked().unwrap_or_default() { + false => "", + true => " singlestage-sidebar-menu-action-show-on-hover", + }, + class.get().unwrap_or_default(), + ) + > + {children()} + </Button> + } +} diff --git a/singlestage/src/components/sidebar/menu/button.rs b/singlestage/src/components/sidebar/menu/button.rs index 6d02d0c7..729673d4 100644 --- a/singlestage/src/components/sidebar/menu/button.rs +++ b/singlestage/src/components/sidebar/menu/button.rs @@ -1,7 +1,29 @@ -use leptos::prelude::*; +use crate::{SidebarContext, Tooltip, TooltipContent, TooltipTrigger}; +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone)] +pub(crate) struct SidebarMenuButtonContext {} -/// Wraps content displayed in the click area of a SidebarMenuItem. #[component] -pub fn SidebarMenuButton(children: Children) -> impl IntoView { - view! { {children()} } +pub fn SidebarMenuButton( + children: Children, + #[prop(optional, into)] tooltip: MaybeProp<String>, +) -> impl IntoView { + let sidebar = expect_context::<SidebarContext>(); + + view! { + <Provider value=SidebarMenuButtonContext {}> + <Tooltip> + <TooltipTrigger>{children()}</TooltipTrigger> + <Show when=move || !sidebar.open.get() && tooltip.get().is_some()> + <TooltipContent side=match sidebar.side.get().as_str() { + "right" => "left", + _ => "right", + }> + <p>{move || tooltip.get().unwrap_or_default()}</p> + </TooltipContent> + </Show> + </Tooltip> + </Provider> + } } diff --git a/singlestage/src/components/sidebar/menu/item.rs b/singlestage/src/components/sidebar/menu/item.rs index 5f0c6fd6..85db3c61 100644 --- a/singlestage/src/components/sidebar/menu/item.rs +++ b/singlestage/src/components/sidebar/menu/item.rs @@ -1,4 +1,3 @@ -use crate::SidebarContext; use leptos::prelude::*; /// Contains an element within the SidebarMenu. @@ -117,14 +116,12 @@ pub fn SidebarMenuItem( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let sidebar = expect_context::<SidebarContext>(); - 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() @@ -161,8 +158,10 @@ pub fn SidebarMenuItem( view! { <li + class=move || { + format!("singlestage-sidebar-menu-item {}", class.get().unwrap_or_default()) + } value=move || value.get() - on:click=move |_| sidebar.close_if_small_screen() {..global_attrs_1} {..global_attrs_2} diff --git a/singlestage/src/components/sidebar/menu/menu.rs b/singlestage/src/components/sidebar/menu/menu.rs index 0682a642..201a42bb 100644 --- a/singlestage/src/components/sidebar/menu/menu.rs +++ b/singlestage/src/components/sidebar/menu/menu.rs @@ -115,7 +115,7 @@ pub fn SidebarMenu( 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() @@ -151,7 +151,12 @@ pub fn SidebarMenu( }; view! { - <ul {..global_attrs_1} {..global_attrs_2}> + <ul + class=move || format!("singlestage-sidebar-menu {}", class.get().unwrap_or_default()) + + {..global_attrs_1} + {..global_attrs_2} + > {children()} </ul> } diff --git a/singlestage/src/components/sidebar/menu/mod.rs b/singlestage/src/components/sidebar/menu/mod.rs index 7508f387..efd465d1 100644 --- a/singlestage/src/components/sidebar/menu/mod.rs +++ b/singlestage/src/components/sidebar/menu/mod.rs @@ -1,7 +1,9 @@ +mod action; mod button; mod item; mod menu; +pub use action::*; pub use button::*; pub use item::*; pub use menu::*; diff --git a/singlestage/src/components/sidebar/mod.rs b/singlestage/src/components/sidebar/mod.rs index 4d291546..c6634f60 100644 --- a/singlestage/src/components/sidebar/mod.rs +++ b/singlestage/src/components/sidebar/mod.rs @@ -1,90 +1,34 @@ -mod collapsible; mod content; mod footer; mod group; mod header; +mod inset; mod menu; mod provider; +mod rail; mod separator; mod sidebar; +mod sub; mod trigger; -pub use collapsible::*; pub use content::*; pub use footer::*; pub use group::*; pub use header::*; +pub use inset::*; pub use menu::*; pub use provider::*; +pub use rail::*; pub use separator::*; pub use sidebar::*; +pub use sub::*; pub use trigger::*; use crate::Reactive; -use leptos::{ev::MouseEvent, prelude::*}; -use std::sync::Arc; - -// Large/small screen breakpoint magic numbers for show/hide -const BREAKPOINT_REM: usize = 48; -const BREAKPOINT_PX: usize = 768; -const DEFAULT_FONT_PX: usize = 16; -const SIDEBAR_MOBILE_WIDTH_REM: usize = 18; // main.css #[derive(Clone)] pub struct SidebarContext { - close_if_small_screen: Arc<dyn Fn() + Send + Sync>, - pub hidden: Reactive<bool>, + pub open: Reactive<bool>, pub side: Reactive<String>, -} - -impl SidebarContext { - pub fn close_if_small_screen(&self) { - (self.close_if_small_screen)() - } -} - -/// Get current viewport width -fn get_screen_width() -> Option<f64> { - if let Ok(js_width) = window().inner_width() - && let Some(f64_width) = js_width.as_f64() - { - return Some(f64_width); - } - - None -} - -/// Get the font size setting of the browser viewing the page -fn get_font_size() -> Option<usize> { - if let Some(dom) = document().document_element() - && let Ok(Some(css)) = window().get_computed_style(&dom) - && let Ok(mut font_size) = css.get_property_value("font-size") - { - // Cut off "px" - let _ = font_size.split_off(font_size.len() - 2); - - if let Ok(parsed_font_size) = font_size.parse::<usize>() { - return Some(parsed_font_size); - } - } - - None -} - -/// Determine if the viewport is smaller than 48rem wide -fn screen_is_small() -> bool { - let breakpoint; - if let Some(font_size) = get_font_size() { - breakpoint = font_size * BREAKPOINT_REM; - } else { - breakpoint = BREAKPOINT_PX; - } - - if let Some(screen_width) = get_screen_width() - && screen_width < breakpoint as f64 - { - return true; - } - - false + pub is_mobile: Reactive<bool>, } diff --git a/singlestage/src/components/sidebar/provider.rs b/singlestage/src/components/sidebar/provider.rs index a3a7ea18..d2eab418 100644 --- a/singlestage/src/components/sidebar/provider.rs +++ b/singlestage/src/components/sidebar/provider.rs @@ -7,25 +7,179 @@ use leptos::{context::Provider, prelude::*}; pub fn SidebarProvider( children: Children, - /// Reactive signal coupled to the sidebar's hidden state. + /// Reactive signal coupled to the sidebar's mobile state. #[prop(optional, into)] - hidden: Reactive<bool>, + is_mobile: Reactive<bool>, + /// Reactive signal coupled to the sidebar's open state. + #[prop(optional, into, default = Reactive::new(true))] + open: Reactive<bool>, /// Reactive signal coupled to which side of its container the sidebar renders on. #[prop(optional, into, default = Reactive::new("left".to_string()))] side: Reactive<String>, -) -> impl IntoView { - // Sent from SidebarGroupContent and SidebarMenuContent - let close_if_small_screen = move || { - if screen_is_small() { - hidden.set(true) - } - }; + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { let context = SidebarContext { - close_if_small_screen: Arc::new(close_if_small_screen), - hidden, + open, side, + is_mobile, }; - view! { <Provider value=context>{children()}</Provider> } + 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! { + <Provider value=context> + <div + class=move || { + format!("singlestage-sidebar-wrapper {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + </Provider> + } } diff --git a/singlestage/src/components/sidebar/rail.rs b/singlestage/src/components/sidebar/rail.rs new file mode 100644 index 00000000..787a2a72 --- /dev/null +++ b/singlestage/src/components/sidebar/rail.rs @@ -0,0 +1,14 @@ +use crate::SidebarContext; +use leptos::prelude::*; + +#[component] +pub fn SidebarRail() -> impl IntoView { + let sidebar = expect_context::<SidebarContext>(); + + view! { + <button + class="singlestage-sidebar-rail" + on:click=move |_| sidebar.open.set(!sidebar.open.get_untracked()) + /> + } +} diff --git a/singlestage/src/components/sidebar/separator.rs b/singlestage/src/components/sidebar/separator.rs index 8ca4a7e2..4b0ff4c1 100644 --- a/singlestage/src/components/sidebar/separator.rs +++ b/singlestage/src/components/sidebar/separator.rs @@ -1,6 +1,8 @@ +// TODO: Merge with Separator primitive + use leptos::prelude::*; -/// Visually eparates sidebar content. +/// Visually separates sidebar content. #[component] pub fn SidebarSeparator( // GLOBAL ATTRIBUTES @@ -113,7 +115,7 @@ pub fn SidebarSeparator( 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() @@ -150,6 +152,9 @@ pub fn SidebarSeparator( view! { <hr + class=move || { + format!("singlestage-sidebar-separator {}", class.get().unwrap_or_default()) + } role="separator" {..global_attrs_1} diff --git a/singlestage/src/components/sidebar/sidebar.css b/singlestage/src/components/sidebar/sidebar.css deleted file mode 100644 index 85e3c52c..00000000 --- a/singlestage/src/components/sidebar/sidebar.css +++ /dev/null @@ -1,251 +0,0 @@ -@layer components { - .singlestage-sidebar { - &:not([data-sidebar-initialized]) { - @apply max-md:hidden; - } - - &:not([aria-hidden]), - &[aria-hidden="false"] { - @apply max-md:bg-black/50 - max-md:fixed - max-md:inset-0 - max-md:z-40; - } - - & + * { - @apply transition-[margin] - ease-in-out - duration-300; - } - - &:not([data-side]), - &[data-side="left"] { - nav { - @apply border-r - left-0; - } - - & + * { - @apply md:ml-(--sidebar-width) - relative; - } - - &[aria-hidden="true"] { - nav { - @apply -translate-x-full; - } - - & + * { - @apply md:ml-0; - } - } - } - - &[data-side="right"] { - nav { - @apply border-l - right-0; - } - - & + * { - @apply md:mr-(--sidebar-width) - relative; - } - - &[aria-hidden="true"] { - nav { - @apply translate-x-full; - } - - & + * { - @apply md:mr-0; - } - } - } - - nav { - @apply bg-sidebar - duration-300 - ease-in-out - fixed - flex - flex-col - inset-y-0 - md:w-(--sidebar-width) - text-sidebar-foreground - transition-transform - w-(--sidebar-mobile-width) - z-50; - - > header, - > footer { - @apply flex - flex-col - gap-2 - p-2; - } - - [role="separator"] { - @apply border-sidebar-border - mx-2 - w-auto; - } - - > section { - @apply flex - flex-1 - flex-col - gap-2 - min-h-0 - overflow-y-auto - overscroll-none; - - > [role="group"] { - @apply flex - flex-col - min-w-0 - p-2 - relative - w-full; - } - - h3 { - @apply [&>svg]:shrink-0 - [&>svg]:size-4 - duration-200 - ease-linear - flex - focus-visible:ring-2 - font-medium - h-8 - items-center - outline-hidden - px-2 - ring-sidebar-ring - rounded-md - shrink-0 - text-sidebar-foreground/70 - text-xs - transition-[margin,opacity]; - } - - ul { - @apply flex - w-full - min-w-0 - flex-col - gap-1; - - li { - @apply relative; - - > a, - > details > summary { - @apply [&>span:last-child]:truncate - [&>svg]:shrink-0 - [&>svg]:size-4 - [&[aria-current=page]]:bg-sidebar-accent - [&[aria-current=page]]:font-medium - [&[aria-current=page]]:text-sidebar-accent-foreground - active:bg-sidebar-accent - active:text-sidebar-accent-foreground - aria-disabled:opacity-50 - aria-disabled:pointer-events-none - disabled:opacity-50 - disabled:pointer-events-none - flex - focus-visible:ring-2 - gap-2 - hover:bg-sidebar-accent - hover:text-sidebar-accent-foreground - items-center - outline-hidden - overflow-hidden - p-2 - ring-sidebar-ring - rounded-md - text-left - text-sm - transition-[width,height,padding] - w-full; - - &:not([data-variant]), - &[data-variant="default"] { - @apply hover:bg-sidebar-accent - hover:text-sidebar-accent-foreground; - } - - &[data-variant="outline"] { - @apply bg-background - hover:bg-sidebar-accent - hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))] - hover:text-sidebar-accent-foreground - shadow-[0_0_0_1px_hsl(var(--sidebar-border))]; - } - - &:not([data-size]), - &[data-size="default"] { - @apply h-8 - text-sm; - } - - &[data-size="sm"] { - @apply h-7 - text-xs; - } - - &[data-size="lg"] { - @apply group-data-[collapsible=icon]:p-0! - h-12 - text-sm; - } - } - - > details { - &:not([open]) { - > summary { - &::after { - @apply -rotate-90; - } - } - } - - > summary { - &::after { - @apply bg-muted-foreground - block - content-[''] - ease-linear - mask-[image:var(--chevron-down-icon)] - mask-center - mask-no-repeat - mask-size-[1rem] - ml-auto - size-3.5 - transition-transform; - } - } - - &::details-content { - @apply px-3.5; - } - } - } - - ul { - @apply border-l - border-sidebar-border - flex - flex-col - gap-1 - min-w-0 - px-2.5 - py-0.5 - translate-x-px - w-full; - } - } - } - } - } -} diff --git a/singlestage/src/components/sidebar/sidebar.rs b/singlestage/src/components/sidebar/sidebar.rs index ba84754e..f145fd89 100644 --- a/singlestage/src/components/sidebar/sidebar.rs +++ b/singlestage/src/components/sidebar/sidebar.rs @@ -1,10 +1,66 @@ use super::*; +use crate::{Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle}; use leptos::prelude::*; +// Large/small screen breakpoint magic numbers for show/hide +const BREAKPOINT_REM: usize = 48; +const BREAKPOINT_PX: usize = 768; + +/// Get current viewport width +fn get_screen_width() -> Option<f64> { + if let Ok(js_width) = window().inner_width() + && let Some(f64_width) = js_width.as_f64() + { + return Some(f64_width); + } + + None +} + +/// Get the font size setting of the browser viewing the page +fn get_font_size() -> Option<usize> { + if let Some(dom) = document().document_element() + && let Ok(Some(css)) = window().get_computed_style(&dom) + && let Ok(mut font_size) = css.get_property_value("font-size") + { + // Cut off "px" + let _ = font_size.split_off(font_size.len() - 2); + + if let Ok(parsed_font_size) = font_size.parse::<usize>() { + return Some(parsed_font_size); + } + } + + None +} + +/// Determine if the viewport is smaller than 48rem wide +fn screen_is_small() -> bool { + let breakpoint; + if let Some(font_size) = get_font_size() { + breakpoint = font_size * BREAKPOINT_REM; + } else { + breakpoint = BREAKPOINT_PX; + } + + if let Some(screen_width) = get_screen_width() + && screen_width < breakpoint as f64 + { + return true; + } + + false +} + /// The sidebar container. #[component] pub fn Sidebar( - children: Children, + children: ChildrenFn, + + #[prop(optional, into)] collapsible: MaybeProp<String>, + #[prop(optional, into)] default_open: MaybeProp<bool>, + #[prop(optional, into)] side: MaybeProp<String>, + #[prop(optional, into)] variant: MaybeProp<String>, // GLOBAL ATTRIBUTES // @@ -112,132 +168,257 @@ pub fn Sidebar( translate: MaybeProp<String>, ) -> impl IntoView { let sidebar = expect_context::<SidebarContext>(); + sidebar.open.set(default_open.get().unwrap_or(true)); - // Initialize on component load - let sidebar_ref = NodeRef::<leptos::html::Aside>::new(); - - // Close sidebar on screen resize if the screen becomes small. - // Like rotating a phone from landscape to portrait - let sidebar_close = sidebar.close_if_small_screen.clone(); - window_event_listener(leptos::ev::resize, move |_| { - sidebar_close(); - }); + if let Some(side) = side.get() { + sidebar.side.set(side); + }; + let side: Reactive<String> = sidebar.side; - let sidebar_close = sidebar.close_if_small_screen.clone(); + // client init Effect::new(move || { - sidebar_close(); - - // CSS hides the sidebar until we tell it otherwise - // to avoid it appearing and then disappearing in a server render - // since the server can't predict the client's screen size. - // Now we tell it otherwise. - if let Some(writer) = sidebar_ref.write().as_ref() { - let _ = writer.set_attribute("data-sidebar-initialized", ""); - } - }); - - // Ideally we'd have an ev.stop_propagation() on the nav element - // but that breaks client-side navigation. - // - // So we'll do a bit of math to calculate if the click was over the nav area. - // If the screen is small and the click was off the bar, it closes. - // SidebarGroupContent and SidebarMenuContent fire their own close events. - // - // It's written out again instead of using the helpers to save a few calls - // and allow returning early cause I'm into that type of stuff. - let on_click = move |ev: MouseEvent| { - let screen_width = get_screen_width().unwrap_or(0.) as usize; - if screen_width == 0 { + if default_open.get_untracked().is_some() { return; } - let font_size; - if let Some(size) = get_font_size() { - font_size = size; - } else { - font_size = DEFAULT_FONT_PX; - } + sidebar.is_mobile.set(screen_is_small()); + sidebar.open.set(!sidebar.is_mobile.get_untracked()); + }); - // Check if screen is small - let breakpoint = font_size * BREAKPOINT_REM; - if screen_width > breakpoint { - // Screen is large, bail - return; - } + window_event_listener(leptos::ev::resize, move |_| { + // possibly useless optimization + let screen_is_small = screen_is_small(); - // Check if click was on the bar - let click_x = ev.client_x() as usize; - let bar_width = SIDEBAR_MOBILE_WIDTH_REM * font_size; - match sidebar.side.get_untracked().as_str() { - "right" => { - if click_x < (screen_width - bar_width) { - sidebar.hidden.set(true); - } - } - _ => { - if click_x > bar_width { - sidebar.hidden.set(true); - } + // this should only run when breakpoint is hit + if sidebar.is_mobile.get_untracked() != screen_is_small { + if screen_is_small { + sidebar.open.set(false); } - } - }; - 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() - /> - }; + sidebar.is_mobile.set(screen_is_small); + } + }); - 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() - /> - }; + let children = StoredValue::new(children); view! { - <aside - node_ref=sidebar_ref - class="singlestage-sidebar" - on:click=on_click - data-side=move || sidebar.side.get() - aria-hidden=move || sidebar.hidden.get().to_string() + <Show + when=move || collapsible.get().unwrap_or_default().as_str() != "none" + fallback=move || { + 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() + 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! { + <div + class=move || { + format!( + "singlestage-sidebar-collapsible-none {}", + class.get().unwrap_or_default(), + ) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children.read_value()()} + </div> + } + } > - <nav - aria-label="Sidebar navigation" + <Show + when=move || !sidebar.is_mobile.get() + fallback=move || { + 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() + 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! { + <Sheet open=sidebar.open> + <SheetContent class="singlestage-sidebar-mobile" side> + <SheetHeader class="sr-only"> + <SheetTitle>"Sidebar"</SheetTitle> + <SheetDescription> + "Displays the mobile sidebar." + </SheetDescription> + </SheetHeader> + <div + class=move || { + format!( + "singlestage-sidebar-inner {}", + class.get().unwrap_or_default(), + ) + } - {..global_attrs_1} - {..global_attrs_2} + {..global_attrs_1} + {..global_attrs_2} + > + {children.read_value()()} + </div> + </SheetContent> + </Sheet> + } + } > - {children()} - </nav> - </aside> + { + 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() + 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! { + <div + aria_expanded=move || { + match sidebar.open.get() { + true => Some("true"), + false => None, + } + } + class=move || { + format!( + "singlestage-sidebar{} {} {}", + match sidebar.open.get() { + true => "", + false => { + match collapsible.get().unwrap_or_default().as_str() { + "icon" => " singlestage-sidebar-collapsible-icon", + _ => " singlestage-sidebar-collapsible-offcanvas", + } + } + }, + match side.get().as_str() { + "right" => "singlestage-sidebar-side-right", + _ => "singlestage-sidebar-side-left", + }, + match variant.get().unwrap_or_default().as_str() { + "floating" => "singlestage-sidebar-variant-floating", + "inset" => "singlestage-sidebar-variant-inset", + _ => "singlestage-sidebar-variant-sidebar", + }, + ) + } + > + <div class="singlestage-sidebar-gap" /> + <div class="singlestage-sidebar-container"> + <div + class=move || { + format!( + "singlestage-sidebar-inner {}", + class.get().unwrap_or_default(), + ) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children.read_value()()} + </div> + </div> + </div> + } + } + </Show> + </Show> } } diff --git a/singlestage/src/components/sidebar/style/base/luma/sidebar.css b/singlestage/src/components/sidebar/style/base/luma/sidebar.css new file mode 100644 index 00000000..a59a37d3 --- /dev/null +++ b/singlestage/src/components/sidebar/style/base/luma/sidebar.css @@ -0,0 +1,328 @@ +@layer components1 { + .singlestage-sidebar-gap { + @apply duration-200 + ease-linear + transition-[width]; + } + + .singlestage-sidebar-inner { + @apply bg-sidebar; + } + + .singlestage-sidebar-rail:hover::after { + @apply bg-sidebar-border; + } + + .singlestage-sidebar-inset { + @apply bg-background; + } + + .singlestage-sidebar-input { + @apply bg-input/50 + h-8 + shadow-none + w-full; + } + + .singlestage-sidebar-header { + @apply [--radius:var(--radius-xl)] + gap-2 + p-2; + } + + .singlestage-sidebar-content { + @apply [--radius:var(--radius-xl)] + gap-2 + /* no-scrollbar */; + } + + .singlestage-sidebar-footer { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-separator { + @apply bg-sidebar-border + mx-2; + } + + .singlestage-sidebar-group { + @apply p-2; + } + + .singlestage-sidebar-menu { + @apply gap-0.5; + } + + .singlestage-sidebar-group-content { + @apply text-sm; + } + + .singlestage-sidebar-group-label { + @apply duration-200 + ease-linear + font-medium + h-8 + px-3 + ring-sidebar-ring + rounded-xl + text-sidebar-foreground/70 + text-xs + transition-[margin,opacity]; + + &:focus-visible { + @apply ring-2; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-group-action { + @apply absolute + p-0 + right-3 + ring-sidebar-ring + rounded-xl + text-sidebar-foreground + top-3.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-button { + @apply gap-2 + px-3 + py-2 + ring-sidebar-ring + rounded-xl + text-left + text-sm + transition-[width,height,padding]; + + &:active, + &[aria-current="page"] { + @apply bg-sidebar-accent + font-medium + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + /* &[aria-expanded="true"], */ + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-variant-default:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + .singlestage-sidebar-menu-button-variant-outline { + @apply bg-background + shadow-[0_0_0_1px_var(--sidebar-border)]; + + &:hover { + @apply bg-sidebar-accent + shadow-[0_0_0_1px_var(--sidebar-accent)] + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-size-default { + @apply h-9 + text-sm; + } + + .singlestage-sidebar-menu-button-size-sm { + @apply h-8 + text-xs; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply h-14 + px-3 + text-sm; + } + + .singlestage-sidebar-menu-action { + @apply absolute + aspect-square + p-0 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-2 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-hover/menu-button:text-sidebar-accent-foreground + right-1 + ring-sidebar-ring + rounded-xl + text-sidebar-foreground + top-1.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-badge { + @apply absolute + flex + font-medium + h-5 + min-w-5 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-data-active/menu-button:text-sidebar-accent-foreground + peer-hover/menu-button:text-sidebar-accent-foreground + pointer-events-none + px-1 + right-1 + rounded-xl + text-sidebar-foreground + text-xs; + } + + .singlestage-sidebar-menu-skeleton { + @apply gap-2 + h-8 + px-2 + rounded-xl; + } + + .singlestage-sidebar-menu-skeleton-icon { + @apply rounded-xl + size-4; + } + + .singlestage-sidebar-menu-skeleton-text { + @apply h-4; + } + + .singlestage-sidebar-menu-sub { + @apply border-l + border-sidebar-border + gap-1 + mx-3.5 + px-2.5 + py-0.5 + translate-x-px; + } + + .singlestage-sidebar-menu-sub-button { + @apply /* TODO: test/finish this */ + data-[size=md]:text-sm + data-[size=sm]:text-xs + gap-2 + h-7 + px-3 + ring-sidebar-ring + rounded-xl + text-sidebar-foreground; + + &:active { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4 + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-variant-floating { + .singlestage-sidebar-inner { + @apply ring-1 + ring-sidebar-border + rounded-2xl + shadow-sm; + } + } + + .singlestage-sidebar-wrapper { + &:has(.singlestage-sidebar-variant-inset) { + > .singlestage-sidebar-inset { + @apply md:m-2 + md:ml-0 + md:rounded-2xl + md:shadow-sm; + } + } + + &:has(> .singlestage-sidebar-collapsible-offcanvas):has( + > .singlestage-sidebar .singlestage-sidebar-rail + ) { + > .singlestage-sidebar-inset { + @apply md:ml-2; + } + } + } + + .singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-group-label { + @apply -mt-8 + opacity-0; + } + + .singlestage-sidebar-menu-button { + @apply p-2! + size-8!; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply p-0!; + } + + .singlestage-sidebar-menu-sub { + @apply hidden; + } + } + + .singlestage-sidebar-menu-item { + &:has(.singlestage-sidebar-menu-action) { + .singlestage-sidebar-menu-button { + @apply pr-8; + } + } + } +} diff --git a/singlestage/src/components/sidebar/style/base/lyra/sidebar.css b/singlestage/src/components/sidebar/style/base/lyra/sidebar.css new file mode 100644 index 00000000..3ed12a0d --- /dev/null +++ b/singlestage/src/components/sidebar/style/base/lyra/sidebar.css @@ -0,0 +1,323 @@ +@layer components1 { + .singlestage-sidebar-gap { + @apply duration-200 + ease-linear + transition-[width]; + } + + .singlestage-sidebar-inner { + @apply bg-sidebar; + } + + .singlestage-sidebar-rail:hover::after { + @apply bg-sidebar-border; + } + + .singlestage-sidebar-inset { + @apply bg-background; + } + + .singlestage-sidebar-input { + @apply bg-background + h-8 + shadow-none + w-full; + } + + .singlestage-sidebar-header { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-content { + @apply gap-0 + /* no-scrollbar */; + } + + .singlestage-sidebar-footer { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-separator { + @apply bg-sidebar-border + mx-2; + } + + .singlestage-sidebar-group { + @apply p-2; + } + + .singlestage-sidebar-menu { + @apply gap-0; + } + + .singlestage-sidebar-group-content { + @apply text-xs; + } + + .singlestage-sidebar-group-label { + @apply duration-200 + ease-linear + h-8 + px-2 + ring-sidebar-ring + rounded-none + text-sidebar-foreground/70 + text-xs + transition-[margin,opacity]; + + &:focus-visible { + @apply ring-2; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-group-action { + @apply absolute + p-0 + right-3 + ring-sidebar-ring + rounded-none + text-sidebar-foreground + top-3.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-button { + @apply gap-2 + p-2 + ring-sidebar-ring + rounded-none + text-left + text-xs + transition-[width,height,padding]; + + &:active, + &[aria-current="page"] { + @apply bg-sidebar-accent + font-medium + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + /* &[aria-expanded="true"], */ + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-variant-default:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + .singlestage-sidebar-menu-button-variant-outline { + @apply bg-background + shadow-[0_0_0_1px_var(--sidebar-border)]; + + &:hover { + @apply bg-sidebar-accent + shadow-[0_0_0_1px_var(--sidebar-accent)] + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-size-default { + @apply h-8 + text-xs; + } + + .singlestage-sidebar-menu-button-size-sm { + @apply h-7 + text-xs; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply h-12 + text-xs; + } + + .singlestage-sidebar-menu-action { + @apply absolute + aspect-square + p-0 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-hover/menu-button:text-sidebar-accent-foreground + right-1 + ring-sidebar-ring + rounded-none + text-sidebar-foreground + top-1.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-badge { + @apply absolute + flex + font-medium + h-5 + min-w-5 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-data-active/menu-button:text-sidebar-accent-foreground + peer-hover/menu-button:text-sidebar-accent-foreground + pointer-events-none + px-1 + right-1 + rounded-none + text-sidebar-foreground + text-xs; + } + + .singlestage-sidebar-menu-skeleton { + @apply gap-2 + h-8 + px-2 + rounded-none; + } + + .singlestage-sidebar-menu-skeleton-icon { + @apply rounded-none + size-4; + } + + .singlestage-sidebar-menu-skeleton-text { + @apply h-4; + } + + .singlestage-sidebar-menu-sub { + @apply border-l + border-sidebar-border + gap-1 + mx-3.5 + px-2.5 + py-0.5 + translate-x-px; + } + + .singlestage-sidebar-menu-sub-button { + @apply /* TODO: test/finish this */ + data-[size=md]:text-xs + data-[size=sm]:text-xs + gap-2 + h-7 + px-2 + ring-sidebar-ring + rounded-none + text-sidebar-foreground; + + &:active { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4 + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-variant-floating { + .singlestage-sidebar-inner { + @apply ring-1 + ring-sidebar-border + rounded-none + shadow-sm; + } + } + + .singlestage-sidebar-wrapper { + &:has(> .singlestage-sidebar-variant-inset) { + > .singlestage-sidebar-inset { + @apply md:m-2 + md:ml-0 + md:rounded-none + md:shadow-sm; + } + } + + &:has(> .singlestage-sidebar-collapsible-offcanvas):has( + > .singlestage-sidebar .singlestage-sidebar-rail + ) { + > .singlestage-sidebar-inset { + @apply md:ml-2; + } + } + } + + .singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-group-label { + @apply -mt-8 + opacity-0; + } + + .singlestage-sidebar-menu-button { + @apply p-2! + size-8!; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply p-0!; + } + + .singlestage-sidebar-menu-sub { + @apply hidden; + } + + .singlestage-sidebar-menu-item { + &:has(.singlestage-sidebar-menu-action) { + .singlestage-sidebar-menu-button { + @apply pr-8; + } + } + } + } +} diff --git a/singlestage/src/components/sidebar/style/base/maia/sidebar.css b/singlestage/src/components/sidebar/style/base/maia/sidebar.css new file mode 100644 index 00000000..de149e2d --- /dev/null +++ b/singlestage/src/components/sidebar/style/base/maia/sidebar.css @@ -0,0 +1,328 @@ +@layer components1 { + .singlestage-sidebar-gap { + @apply duration-200 + ease-linear + transition-[width]; + } + + .singlestage-sidebar-inner { + @apply bg-sidebar; + } + + .singlestage-sidebar-rail:hover::after { + @apply bg-sidebar-border; + } + + .singlestage-sidebar-inset { + @apply bg-background; + } + + .singlestage-sidebar-input { + @apply bg-background + h-8 + shadow-none + w-full; + } + + .singlestage-sidebar-header { + @apply [--radius:var(--radius-xl)] + gap-2 + p-2; + } + + .singlestage-sidebar-content { + @apply [--radius:var(--radius-xl)] + gap-2 + /* no-scrollbar */; + } + + .singlestage-sidebar-footer { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-separator { + @apply bg-sidebar-border + mx-2; + } + + .singlestage-sidebar-group { + @apply p-2; + } + + .singlestage-sidebar-menu { + @apply gap-1; + } + + .singlestage-sidebar-group-content { + @apply text-sm; + } + + .singlestage-sidebar-group-label { + @apply duration-200 + ease-linear + font-medium + h-8 + px-3 + ring-sidebar-ring + rounded-md + text-sidebar-foreground/70 + text-xs + transition-[margin,opacity]; + + &:focus-visible { + @apply ring-2; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-group-action { + @apply absolute + p-0 + right-3 + ring-sidebar-ring + rounded-md + text-sidebar-foreground + top-3.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-button { + @apply gap-2 + px-3 + py-2 + ring-sidebar-ring + rounded-lg + text-left + text-sm + transition-[width,height,padding]; + + &:active, + &[aria-current="page"] { + @apply bg-sidebar-accent + font-medium + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + /* &[aria-expanded="true"], */ + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-variant-default:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + .singlestage-sidebar-menu-button-variant-outline { + @apply bg-background + shadow-[0_0_0_1px_var(--sidebar-border)]; + + &:hover { + @apply bg-sidebar-accent + shadow-[0_0_0_1px_var(--sidebar-accent)] + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-size-default { + @apply h-9 + text-sm; + } + + .singlestage-sidebar-menu-button-size-sm { + @apply h-8 + text-xs; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply h-14 + px-3 + text-sm; + } + + .singlestage-sidebar-menu-action { + @apply absolute + aspect-square + p-0 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-2 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-hover/menu-button:text-sidebar-accent-foreground + right-1 + ring-sidebar-ring + rounded-md + text-sidebar-foreground + top-1.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-badge { + @apply absolute + flex + font-medium + h-5 + min-w-5 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-data-active/menu-button:text-sidebar-accent-foreground + peer-hover/menu-button:text-sidebar-accent-foreground + pointer-events-none + px-1 + right-1 + rounded-md + text-sidebar-foreground + text-xs; + } + + .singlestage-sidebar-menu-skeleton { + @apply gap-2 + h-8 + px-2 + rounded-md; + } + + .singlestage-sidebar-menu-skeleton-icon { + @apply rounded-md + size-4; + } + + .singlestage-sidebar-menu-skeleton-text { + @apply h-4; + } + + .singlestage-sidebar-menu-sub { + @apply border-l + border-sidebar-border + gap-1 + mx-3.5 + px-2.5 + py-0.5 + translate-x-px; + } + + .singlestage-sidebar-menu-sub-button { + @apply /* TODO: test/finish this */ + data-[size=md]:text-sm + data-[size=sm]:text-xs + gap-2 + h-7 + px-2 + ring-sidebar-ring + rounded-md + text-sidebar-foreground; + + &:active { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4 + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-variant-floating { + .singlestage-sidebar-inner { + @apply ring-1 + ring-sidebar-border + rounded-lg + shadow-sm; + } + } + + .singlestage-sidebar-wrapper { + &:has(> .singlestage-sidebar-variant-inset) { + > .singlestage-sidebar-inset { + @apply md:m-2 + md:ml-0 + md:rounded-xl + md:shadow-sm; + } + } + + &:has(> .singlestage-sidebar-collapsible-offcanvas):has( + > .singlestage-sidebar .singlestage-sidebar-rail + ) { + > .singlestage-sidebar-inset { + @apply md:ml-2; + } + } + } + + .singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-group-label { + @apply -mt-8 + opacity-0; + } + + .singlestage-sidebar-menu-button { + @apply p-2! + size-8!; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply p-0!; + } + + .singlestage-sidebar-menu-sub { + @apply hidden; + } + } + + .singlestage-sidebar-menu-item { + &:has(.singlestage-sidebar-menu-action) { + .singlestage-sidebar-menu-button { + @apply pr-8; + } + } + } +} diff --git a/singlestage/src/components/sidebar/style/base/mira/sidebar.css b/singlestage/src/components/sidebar/style/base/mira/sidebar.css new file mode 100644 index 00000000..1ebdc629 --- /dev/null +++ b/singlestage/src/components/sidebar/style/base/mira/sidebar.css @@ -0,0 +1,325 @@ +@layer components1 { + .singlestage-sidebar-gap { + @apply duration-200 + ease-linear + transition-[width]; + } + + .singlestage-sidebar-inner { + @apply bg-sidebar; + } + + .singlestage-sidebar-rail:hover::after { + @apply bg-sidebar-border; + } + + .singlestage-sidebar-inset { + @apply bg-background; + } + + .singlestage-sidebar-input { + @apply bg-muted/20 + border-input + dark:bg-muted/30 + h-8 + w-full; + } + + .singlestage-sidebar-header { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-content { + @apply gap-0 + /* no-scrollbar */; + } + + .singlestage-sidebar-footer { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-separator { + @apply bg-sidebar-border + mx-2; + } + + .singlestage-sidebar-group { + @apply px-2 + py-1; + } + + .singlestage-sidebar-menu { + @apply gap-px; + } + + .singlestage-sidebar-group-content { + @apply text-xs; + } + + .singlestage-sidebar-group-label { + @apply duration-200 + ease-linear + h-8 + px-2 + ring-sidebar-ring + rounded-md + text-sidebar-foreground/70 + text-xs + transition-[margin,opacity]; + + &:focus-visible { + @apply ring-2; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-group-action { + @apply absolute + p-0 + right-3 + ring-sidebar-ring + rounded-md + text-sidebar-foreground + top-3.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-button { + @apply gap-2 + p-2 + ring-sidebar-ring + rounded-[calc(var(--radius-sm)+2px)] + text-left + text-xs + transition-[width,height,padding]; + + &:active, + &[aria-current="page"] { + @apply bg-sidebar-accent + font-medium + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + /* &[aria-expanded="true"], */ + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-variant-default:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + .singlestage-sidebar-menu-button-variant-outline { + @apply bg-background + shadow-[0_0_0_1px_var(--sidebar-border)]; + + &:hover { + @apply bg-sidebar-accent + shadow-[0_0_0_1px_var(--sidebar-accent)] + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-size-default { + @apply h-8 + text-xs; + } + + .singlestage-sidebar-menu-button-size-sm { + @apply h-7 + text-xs; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply h-12 + text-xs; + } + + .singlestage-sidebar-menu-action { + @apply absolute + aspect-square + p-0 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-hover/menu-button:text-sidebar-accent-foreground + right-1 + ring-sidebar-ring + rounded-[calc(var(--radius-sm)-2px)] + text-sidebar-foreground + top-1.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-badge { + @apply absolute + flex + font-medium + h-5 + min-w-5 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-data-active/menu-button:text-sidebar-accent-foreground + peer-hover/menu-button:text-sidebar-accent-foreground + pointer-events-none + px-1 + right-1 + rounded-[calc(var(--radius-sm)-2px)] + text-sidebar-foreground + text-xs; + } + + .singlestage-sidebar-menu-skeleton { + @apply gap-2 + h-8 + px-2 + rounded-md; + } + + .singlestage-sidebar-menu-skeleton-icon { + @apply rounded-md + size-4; + } + + .singlestage-sidebar-menu-skeleton-text { + @apply h-4; + } + + .singlestage-sidebar-menu-sub { + @apply border-l + border-sidebar-border + gap-1 + mx-3.5 + px-2.5 + py-0.5 + translate-x-px; + } + + .singlestage-sidebar-menu-sub-button { + @apply /* TODO: test/finish this */ + data-[size=md]:text-xs + data-[size=sm]:text-xs + gap-2 + h-7 + px-2 + ring-sidebar-ring + rounded-md + text-sidebar-foreground; + + &:active { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4 + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-variant-floating { + .singlestage-sidebar-inner { + @apply ring-1 + ring-sidebar-border + rounded-lg + shadow-sm; + } + } + + .singlestage-sidebar-wrapper { + &:has(> .singlestage-sidebar-variant-inset) { + > .singlestage-sidebar-inset { + @apply md:m-2 + md:ml-0 + md:rounded-xl + md:shadow-sm; + } + } + + &:has(> .singlestage-sidebar-collapsible-offcanvas):has( + > .singlestage-sidebar .singlestage-sidebar-rail + ) { + > .singlestage-sidebar-inset { + @apply md:ml-2; + } + } + } + + .singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-group-label { + @apply -mt-8 + opacity-0; + } + + .singlestage-sidebar-menu-button { + @apply p-2! + size-8!; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply p-0!; + } + + .singlestage-sidebar-menu-sub { + @apply hidden; + } + } + + .singlestage-sidebar-menu-item { + &:has(.singlestage-sidebar-menu-action) { + .singlestage-sidebar-menu-button { + @apply pr-8; + } + } + } +} diff --git a/singlestage/src/components/sidebar/style/base/nova/sidebar.css b/singlestage/src/components/sidebar/style/base/nova/sidebar.css new file mode 100644 index 00000000..acc6276f --- /dev/null +++ b/singlestage/src/components/sidebar/style/base/nova/sidebar.css @@ -0,0 +1,324 @@ +@layer components1 { + .singlestage-sidebar-gap { + @apply duration-200 + ease-linear + transition-[width]; + } + + .singlestage-sidebar-inner { + @apply bg-sidebar; + } + + .singlestage-sidebar-rail:hover::after { + @apply bg-sidebar-border; + } + + .singlestage-sidebar-inset { + @apply bg-background; + } + + .singlestage-sidebar-input { + @apply bg-background + h-8 + shadow-none + w-full; + } + + .singlestage-sidebar-header { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-content { + @apply gap-0 + /* no-scrollbar */; + } + + .singlestage-sidebar-footer { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-separator { + @apply bg-sidebar-border + mx-2; + } + + .singlestage-sidebar-group { + @apply p-2; + } + + .singlestage-sidebar-menu { + @apply gap-0; + } + + .singlestage-sidebar-group-content { + @apply text-sm; + } + + .singlestage-sidebar-group-label { + @apply duration-200 + ease-linear + font-medium + h-8 + px-2 + ring-sidebar-ring + rounded-md + text-sidebar-foreground/70 + text-xs + transition-[margin,opacity]; + + &:focus-visible { + @apply ring-2; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-group-action { + @apply absolute + p-0 + right-3 + ring-sidebar-ring + rounded-md + text-sidebar-foreground + top-3.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-button { + @apply gap-2 + p-2 + ring-sidebar-ring + rounded-md + text-left + text-sm + transition-[width,height,padding]; + + &:active, + &[aria-current="page"] { + @apply bg-sidebar-accent + font-medium + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + /* &[aria-expanded="true"], */ + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-variant-default:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + .singlestage-sidebar-menu-button-variant-outline { + @apply bg-background + shadow-[0_0_0_1px_var(--sidebar-border)]; + + &:hover { + @apply bg-sidebar-accent + shadow-[0_0_0_1px_var(--sidebar-accent)] + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-size-default { + @apply h-8 + text-sm; + } + + .singlestage-sidebar-menu-button-size-sm { + @apply h-7 + text-xs; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply h-12 + text-sm; + } + + .singlestage-sidebar-menu-action { + @apply absolute + aspect-square + p-0 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-hover/menu-button:text-sidebar-accent-foreground + right-1 + ring-sidebar-ring + rounded-md + text-sidebar-foreground + top-1.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-badge { + @apply absolute + flex + font-medium + h-5 + min-w-5 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-data-active/menu-button:text-sidebar-accent-foreground + peer-hover/menu-button:text-sidebar-accent-foreground + pointer-events-none + px-1 + right-1 + rounded-md + text-sidebar-foreground + text-xs; + } + + .singlestage-sidebar-menu-skeleton { + @apply gap-2 + h-8 + px-2 + rounded-md; + } + + .singlestage-sidebar-menu-skeleton-icon { + @apply rounded-md + size-4; + } + + .singlestage-sidebar-menu-skeleton-text { + @apply h-4; + } + + .singlestage-sidebar-menu-sub { + @apply border-l + border-sidebar-border + gap-1 + mx-3.5 + px-2.5 + py-0.5 + translate-x-px; + } + + .singlestage-sidebar-menu-sub-button { + @apply /* TODO: test/finish this */ + data-[size=md]:text-sm + data-[size=sm]:text-xs + gap-2 + h-7 + px-2 + ring-sidebar-ring + rounded-md + text-sidebar-foreground; + + &:active { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4 + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-variant-floating { + .singlestage-sidebar-inner { + @apply ring-1 + ring-sidebar-border + rounded-lg + shadow-sm; + } + } + + .singlestage-sidebar-wrapper { + &:has(> .singlestage-sidebar-variant-inset) { + > .singlestage-sidebar-inset { + @apply md:m-2 + md:ml-0 + md:rounded-xl + md:shadow-sm; + } + } + + &:has(> .singlestage-sidebar-collapsible-offcanvas):has( + > .singlestage-sidebar .singlestage-sidebar-rail + ) { + > .singlestage-sidebar-inset { + @apply md:ml-2; + } + } + } + + .singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-group-label { + @apply -mt-8 + opacity-0; + } + + .singlestage-sidebar-menu-button { + @apply p-2! + size-8!; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply p-0!; + } + + .singlestage-sidebar-menu-sub { + @apply hidden; + } + } + + .singlestage-sidebar-menu-item { + &:has(.singlestage-sidebar-menu-action) { + .singlestage-sidebar-menu-button { + @apply pr-8; + } + } + } +} diff --git a/singlestage/src/components/sidebar/style/base/sera/sidebar.css b/singlestage/src/components/sidebar/style/base/sera/sidebar.css new file mode 100644 index 00000000..325ce95b --- /dev/null +++ b/singlestage/src/components/sidebar/style/base/sera/sidebar.css @@ -0,0 +1,331 @@ +@layer components1 { + .singlestage-sidebar-gap { + @apply duration-200 + ease-linear + transition-[width]; + } + + .singlestage-sidebar-inner { + @apply bg-sidebar; + } + + .singlestage-sidebar-rail:hover::after { + @apply bg-sidebar-border; + } + + .singlestage-sidebar-inset { + @apply bg-background; + } + + .singlestage-sidebar-input { + @apply bg-transparent + border-input + h-8 + shadow-none + w-full; + } + + .singlestage-sidebar-header { + @apply [--radius:0] + gap-2 + p-2; + } + + .singlestage-sidebar-content { + @apply [--radius:0] + gap-2 + /* no-scrollbar */; + } + + .singlestage-sidebar-footer { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-separator { + @apply bg-sidebar-border + mx-2; + } + + .singlestage-sidebar-group { + @apply p-2; + } + + .singlestage-sidebar-menu { + @apply gap-0.5; + } + + .singlestage-sidebar-group-content { + @apply text-sm; + } + + .singlestage-sidebar-group-label { + @apply duration-200 + ease-linear + font-semibold + h-8 + px-3 + ring-sidebar-ring + rounded-none + text-sidebar-foreground/70 + text-xs + tracking-wider + transition-[margin,opacity] + uppercase; + + &:focus-visible { + @apply ring-2; + } + + > svg { + @apply size-3.5; + } + } + + .singlestage-sidebar-group-action { + @apply absolute + p-0 + right-3 + ring-sidebar-ring + rounded-none + text-sidebar-foreground + top-3.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-3.5; + } + } + + .singlestage-sidebar-menu-button { + @apply gap-2 + px-3 + py-2 + ring-sidebar-ring + rounded-none + text-left + text-sm + transition-[width,height,padding]; + + &:active, + &[aria-current="page"] { + @apply bg-sidebar-accent + font-medium + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + /* &[aria-expanded="true"], */ + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-variant-default:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + .singlestage-sidebar-menu-button-variant-outline { + @apply bg-background + shadow-[0_0_0_1px_var(--sidebar-border)]; + + &:hover { + @apply bg-sidebar-accent + shadow-[0_0_0_1px_var(--sidebar-accent)] + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-size-default { + @apply h-9 + text-sm; + } + + .singlestage-sidebar-menu-button-size-sm { + @apply h-8 + text-xs; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply h-14 + px-3 + text-sm; + } + + .singlestage-sidebar-menu-action { + @apply absolute + aspect-square + p-0 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-2 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-hover/menu-button:text-sidebar-accent-foreground + right-1 + ring-sidebar-ring + rounded-none + text-sidebar-foreground + top-1.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-3.5; + } + } + + .singlestage-sidebar-menu-badge { + @apply absolute + flex + font-medium + h-5 + min-w-5 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-data-active/menu-button:text-sidebar-accent-foreground + peer-hover/menu-button:text-sidebar-accent-foreground + pointer-events-none + px-1 + right-1 + rounded-none + text-sidebar-foreground + text-xs; + } + + .singlestage-sidebar-menu-skeleton { + @apply gap-2 + h-8 + px-2 + rounded-none; + } + + .singlestage-sidebar-menu-skeleton-icon { + @apply rounded-none + size-3.5; + } + + .singlestage-sidebar-menu-skeleton-text { + @apply h-4; + } + + .singlestage-sidebar-menu-sub { + @apply border-l + border-sidebar-border + gap-1 + mx-3.5 + px-2.5 + py-0.5 + translate-x-px; + } + + .singlestage-sidebar-menu-sub-button { + @apply /* TODO: test/finish this */ + data-[size=md]:text-sm + data-[size=sm]:text-xs + gap-2 + h-7 + px-3 + ring-sidebar-ring + rounded-none + text-sidebar-foreground; + + &:active { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-3.5 + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-variant-floating { + .singlestage-sidebar-inner { + @apply ring-1 + ring-sidebar-border + rounded-none + shadow-sm; + } + } + + .singlestage-sidebar-wrapper { + &:has(> .singlestage-sidebar-variant-inset) { + > .singlestage-sidebar-inset { + @apply md:m-2 + md:ml-0 + md:rounded-none + md:shadow-sm; + } + } + + &:has(> .singlestage-sidebar-collapsible-offcanvas):has( + > .singlestage-sidebar .singlestage-sidebar-rail + ) { + > .singlestage-sidebar-inset { + @apply md:ml-2; + } + } + } + + .singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-group-label { + @apply -mt-8 + opacity-0; + } + + .singlestage-sidebar-menu-button { + @apply p-2! + size-8!; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply p-0!; + } + + .singlestage-sidebar-menu-sub { + @apply hidden; + } + } + + .singlestage-sidebar-menu-item { + &:has(.singlestage-sidebar-menu-action) { + .singlestage-sidebar-menu-button { + @apply pr-8; + } + } + } +} diff --git a/singlestage/src/components/sidebar/style/base/vega/sidebar.css b/singlestage/src/components/sidebar/style/base/vega/sidebar.css new file mode 100644 index 00000000..c052b57a --- /dev/null +++ b/singlestage/src/components/sidebar/style/base/vega/sidebar.css @@ -0,0 +1,324 @@ +@layer components1 { + .singlestage-sidebar-gap { + @apply duration-200 + ease-linear + transition-[width]; + } + + .singlestage-sidebar-inner { + @apply bg-sidebar; + } + + .singlestage-sidebar-rail:hover::after { + @apply bg-sidebar-border; + } + + .singlestage-sidebar-inset { + @apply bg-background; + } + + .singlestage-sidebar-input { + @apply bg-background + h-8 + shadow-none + w-full; + } + + .singlestage-sidebar-header { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-content { + @apply gap-2 + /* no-scrollbar */; + } + + .singlestage-sidebar-footer { + @apply gap-2 + p-2; + } + + .singlestage-sidebar-separator { + @apply bg-sidebar-border + mx-2; + } + + .singlestage-sidebar-group { + @apply p-2; + } + + .singlestage-sidebar-menu { + @apply gap-1; + } + + .singlestage-sidebar-group-content { + @apply text-sm; + } + + .singlestage-sidebar-group-label { + @apply duration-200 + ease-linear + font-medium + h-8 + px-2 + ring-sidebar-ring + rounded-md + text-sidebar-foreground/70 + text-xs + transition-[margin,opacity]; + + &:focus-visible { + @apply ring-2; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-group-action { + @apply absolute + p-0 + right-3 + ring-sidebar-ring + rounded-md + text-sidebar-foreground + top-3.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-button { + @apply gap-2 + p-2 + ring-sidebar-ring + rounded-md + text-left + text-sm + transition-[width,height,padding]; + + &:active, + &[aria-current="page"] { + @apply bg-sidebar-accent + font-medium + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + /* &[aria-expanded="true"], */ + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-variant-default:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + .singlestage-sidebar-menu-button-variant-outline { + @apply bg-background + shadow-[0_0_0_1px_var(--sidebar-border)]; + + &:hover { + @apply bg-sidebar-accent + shadow-[0_0_0_1px_var(--sidebar-accent)] + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-menu-button-size-default { + @apply h-8 + text-sm; + } + + .singlestage-sidebar-menu-button-size-sm { + @apply h-7 + text-xs; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply h-12 + text-sm; + } + + .singlestage-sidebar-menu-action { + @apply absolute + aspect-square + p-0 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-hover/menu-button:text-sidebar-accent-foreground + right-1 + ring-sidebar-ring + rounded-md + text-sidebar-foreground + top-1.5 + w-5; + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4; + } + } + + .singlestage-sidebar-menu-badge { + @apply absolute + flex + font-medium + h-5 + min-w-5 + /* TODO: test/finish this */ + peer-data-[size=default]/menu-button:top-1.5 + peer-data-[size=lg]/menu-button:top-2.5 + peer-data-[size=sm]/menu-button:top-1 + peer-data-active/menu-button:text-sidebar-accent-foreground + peer-hover/menu-button:text-sidebar-accent-foreground + pointer-events-none + px-1 + right-1 + rounded-md + text-sidebar-foreground + text-xs; + } + + .singlestage-sidebar-menu-skeleton { + @apply gap-2 + h-8 + px-2 + rounded-md; + } + + .singlestage-sidebar-menu-skeleton-icon { + @apply rounded-md + size-4; + } + + .singlestage-sidebar-menu-skeleton-text { + @apply h-4; + } + + .singlestage-sidebar-menu-sub { + @apply border-l + border-sidebar-border + gap-1 + mx-3.5 + px-2.5 + py-0.5 + translate-x-px; + } + + .singlestage-sidebar-menu-sub-button { + @apply /* TODO: test/finish this */ + data-[size=md]:text-sm + data-[size=sm]:text-xs + gap-2 + h-7 + px-2 + ring-sidebar-ring + rounded-md + text-sidebar-foreground; + + &:active { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + &:focus-visible { + @apply ring-2; + } + + &:hover { + @apply bg-sidebar-accent + text-sidebar-accent-foreground; + } + + > svg { + @apply size-4 + text-sidebar-accent-foreground; + } + } + + .singlestage-sidebar-variant-floating { + .singlestage-sidebar-inner { + @apply ring-1 + ring-sidebar-border + rounded-lg + shadow-sm; + } + } + + .singlestage-sidebar-wrapper { + &:has(> .singlestage-sidebar-variant-inset) { + > .singlestage-sidebar-inset { + @apply md:m-2 + md:ml-0 + md:rounded-xl + md:shadow-sm; + } + } + + &:has(> .singlestage-sidebar-collapsible-offcanvas):has( + > .singlestage-sidebar .singlestage-sidebar-rail + ) { + > .singlestage-sidebar-inset { + @apply md:ml-2; + } + } + } + + .singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-group-label { + @apply -mt-8 + opacity-0; + } + + .singlestage-sidebar-menu-button { + @apply p-2! + size-8!; + } + + .singlestage-sidebar-menu-button-size-lg { + @apply p-0!; + } + + .singlestage-sidebar-menu-sub { + @apply hidden; + } + } + + .singlestage-sidebar-menu-item { + &:has(.singlestage-sidebar-menu-action) { + .singlestage-sidebar-menu-button { + @apply pr-8; + } + } + } +} diff --git a/singlestage/src/components/sidebar/style/sidebar.css b/singlestage/src/components/sidebar/style/sidebar.css new file mode 100644 index 00000000..b3d61d4b --- /dev/null +++ b/singlestage/src/components/sidebar/style/sidebar.css @@ -0,0 +1,424 @@ +@layer components { + .singlestage-sidebar-wrapper { + @apply flex + min-h-svh + w-full; + + &:has(> .singlestage-sidebar-variant-inset) { + @apply bg-sidebar; + } + } + + .singlestage-sidebar { + @apply hidden + min-[48rem]:block + text-sidebar-foreground; + } + + .singlestage-sidebar-mobile { + @apply bg-sidebar + p-0 + text-sidebar-foreground + w-(--sidebar-width); + + & > button { + @apply hidden; + } + } + + .singlestage-sidebar-gap { + @apply bg-transparent + relative + w-(--sidebar-width); + } + + .singlestage-sidebar-container { + @apply duration-200 + ease-linear + sticky /* fixed */ + h-svh + hidden + inset-y-0 + md:flex + transition-[left,right,width] + w-(--sidebar-width) + z-10; + } + + .singlestage-sidebar-inner { + @apply flex + flex-col + size-full; + } + + .singlestage-sidebar-rail { + @apply absolute + ease-linear + hidden + inset-y-0 + ltr:-translate-x-1/2 + rtl:-translate-x-1/2 + sm:flex + transition-all + w-4 + z-20; + + &::after { + @apply absolute + inset-y-0 + start-1/2 + w-[2px]; + } + } + + .singlestage-sidebar-inset { + @apply flex + flex-1 + flex-col + relative + w-full; + } + + .singlestage-sidebar-header { + @apply flex + flex-col; + } + + .singlestage-sidebar-footer { + @apply flex + flex-col; + } + + .singlestage-sidebar-separator { + @apply w-auto; + } + + .singlestage-sidebar-content { + @apply flex + flex-1 + flex-col + min-h-0 + overflow-auto; + } + + .singlestage-sidebar-group { + @apply flex + flex-col + min-w-0 + relative + w-full; + } + + .singlestage-sidebar-group-label { + @apply flex + items-center + outline-hidden + shrink-0; + + > svg { + @apply shrink-0; + } + } + + .singlestage-sidebar-group-action { + @apply aspect-square + flex + items-center + justify-center + md:after:hidden + outline-hidden + transition-transform; + + &::after { + @apply -inset-2 + absolute; + } + + > svg { + @apply shrink-0; + } + } + + .singlestage-sidebar-group-content { + @apply w-full; + } + + .singlestage-sidebar-menu { + @apply flex + flex-col + min-w-0 + w-full; + } + + .singlestage-sidebar-menu-item { + @apply relative; + + &:focus-within, + &:hover { + .singlestage-sidebar-menu-action-show-on-hover { + @apply opacity-100; + } + } + } + + .singlestage-sidebar-menu-button { + @apply flex + items-center + outline-hidden + overflow-hidden + w-full; + + &:active, + &[aria-current="page"] { + .singlestage-sidebar-menu-action-show-on-hover { + @apply text-sidebar-accent-foreground; + } + } + + &:disabled, + &[aria-disabled="true"] { + @apply opacity-50 + pointer-events-none; + } + + > span:last-child { + @apply truncate; + } + + svg { + @apply shrink-0 + size-4; + } + } + + .singlestage-sidebar-menu-action { + @apply flex + items-center + justify-center + md:after:hidden + outline-hidden + transition-transform; + + &::after { + @apply -inset-2 + absolute; + } + + > svg { + @apply shrink-0; + } + } + + .singlestage-sidebar-menu-action-show-on-hover { + @apply md:opacity-0; + + &[aria-expanded="true"] { + @apply opacity-100; + } + } + + .singlestage-sidebar-menu-badge { + @apply flex + items-center + justify-center + select-none + tabular-nums; + } + + .singlestage-sidebar-menu-skeleton { + @apply flex + items-center; + } + + .singlestage-sidebar-menu-skeleton-text { + @apply flex-1 + /* random % between 50 and 90 */ + max-w-(--skeleton-width); + } + + .singlestage-sidebar-menu-sub { + @apply flex + flex-col + min-w-0; + } + + .singlestage-sidebar-menu-sub-item { + @apply relative; + } + + .singlestage-sidebar-menu-sub-button { + @apply -translate-x-px + flex + items-center + min-w-0 + outline-hidden + overflow-hidden; + + &:disabled, + &[aria-disabled="true"] { + @apply opacity-50 + pointer-events-none; + } + + > span:last-child { + @apply truncate; + } + + svg { + @apply shrink-0; + } + } + + .singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-content { + @apply overflow-hidden; + } + + .singlestage-sidebar-group-action { + @apply hidden; + } + + .singlestage-sidebar-group-label { + @apply hidden; + } + + .singlestage-sidebar-menu-action { + @apply hidden; + } + + .singlestage-sideabar-menu-badge { + @apply hidden; + } + + .singlestage-sidebar-menu-sub-button { + @apply hidden; + } + } + + .singlestage-sidebar-collapsible-none { + @apply bg-sidebar + flex + flex-col + h-full + text-sidebar-foreground + w-(--sidebar-width) flex-col; + } + + .singlestage-sidebar-variant-floating, + .singlestage-sidebar-variant-inset { + .singlestage-sidebar-container { + @apply p-2; + } + + &.singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-gap { + @apply w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]; + } + + .singlestage-sidebar-container { + @apply w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]; + } + } + } + + .singlestage-sidebar-variant-sidebar { + &.singlestage-sidebar-collapsible-icon { + .singlestage-sidebar-container { + @apply w-(--sidebar-width-icon); + } + + .singlestage-sidebar-gap { + @apply w-(--sidebar-width-icon); + } + } + + &.singlestage-sidebar-side-left { + .singlestage-sidebar-container { + @apply border-r; + } + } + + &.singlestage-sidebar-side-right { + .singlestage-sidebar-container { + @apply border-l; + } + } + } + + .singlestage-sidebar-side-left { + .singlestage-sidebar-container { + @apply left-0; + } + + .singlestage-sidebar-rail { + @apply -right-4 + cursor-w-resize; + } + + &:not([aria-expanded="true"]) { + .singlestage-sidebar-rail { + @apply cursor-e-resize; + } + } + } + + .singlestage-sidebar-side-right { + .singlestage-sidebar-container { + @apply right-0; + } + + .singlestage-sidebar-gap { + @apply rotate-180; + } + + .singlestage-sidebar-rail { + @apply cursor-e-resize + left-0; + } + + &:not([aria-expanded="true"]) { + .singlestage-sidebar-rail { + @apply cursor-w-resize; + } + } + } + + .singlestage-sidebar-collapsible-offcanvas { + .singlestage-sidebar-gap { + @apply w-0; + } + + .singlestage-sidebar-rail { + @apply translate-x-0; + + &::after { + @apply left-full; + } + + &:hover { + @apply bg-sidebar; + } + } + + &.singlestage-sidebar-side-left { + .singlestage-sidebar-container { + @apply fixed /**/ + left-[calc(var(--sidebar-width)*-1)]; + } + + .singlestage-sidebar-rail { + @apply -right-2; + } + } + + &.singlestage-sidebar-side-right { + .singlestage-sidebar-container { + @apply right-[calc(var(--sidebar-width)*-1)]; + } + + .singlestage-sidebar-rail { + @apply -left-2; + } + } + } +} diff --git a/singlestage/src/components/sidebar/sub/button.rs b/singlestage/src/components/sidebar/sub/button.rs new file mode 100644 index 00000000..d7eb2fd9 --- /dev/null +++ b/singlestage/src/components/sidebar/sub/button.rs @@ -0,0 +1,26 @@ +use crate::{SidebarContext, SidebarMenuButtonContext, Tooltip, TooltipContent, TooltipTrigger}; +use leptos::{context::Provider, prelude::*}; + +#[component] +pub fn SidebarMenuSubButton( + children: Children, + #[prop(optional, into)] tooltip: MaybeProp<String>, +) -> impl IntoView { + let sidebar = expect_context::<SidebarContext>(); + + view! { + <Provider value=SidebarMenuButtonContext {}> + <Tooltip> + <TooltipTrigger>{children()}</TooltipTrigger> + <Show when=move || !sidebar.open.get() && tooltip.get().is_some()> + <TooltipContent side=match sidebar.side.get().as_str() { + "right" => "left", + _ => "right", + }> + <p>{move || tooltip.get().unwrap_or_default()}</p> + </TooltipContent> + </Show> + </Tooltip> + </Provider> + } +} diff --git a/singlestage/src/components/sidebar/collapsible/sub_item.rs b/singlestage/src/components/sidebar/sub/item.rs similarity index 97% rename from singlestage/src/components/sidebar/collapsible/sub_item.rs rename to singlestage/src/components/sidebar/sub/item.rs index 57e5de97..7127b5ad 100644 --- a/singlestage/src/components/sidebar/collapsible/sub_item.rs +++ b/singlestage/src/components/sidebar/sub/item.rs @@ -1,4 +1,3 @@ -use crate::SidebarContext; use leptos::prelude::*; /// A menu item within the SidebarMenuSub menu. @@ -117,14 +116,12 @@ pub fn SidebarMenuSubItem( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let sidebar = expect_context::<SidebarContext>(); - 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() @@ -161,7 +158,9 @@ pub fn SidebarMenuSubItem( view! { <li - on:click=move |_| sidebar.close_if_small_screen() + class=move || { + format!("singlestage-sidebar-menu-sub-item {}", class.get().unwrap_or_default()) + } value=move || value.get() {..global_attrs_1} diff --git a/singlestage/src/components/sidebar/sub/mod.rs b/singlestage/src/components/sidebar/sub/mod.rs new file mode 100644 index 00000000..cbb149fa --- /dev/null +++ b/singlestage/src/components/sidebar/sub/mod.rs @@ -0,0 +1,170 @@ +mod button; +mod item; + +pub use button::*; +pub use item::*; + +use leptos::prelude::*; + +/// Wraps a submenu within a SidebarMenu. +#[component] +pub fn SidebarMenuSub( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <div + class=move || { + format!("singlestage-sidebar-menu-sub {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + } +} diff --git a/singlestage/src/components/sidebar/trigger.rs b/singlestage/src/components/sidebar/trigger.rs index d1a5f84a..b6a1cdb8 100644 --- a/singlestage/src/components/sidebar/trigger.rs +++ b/singlestage/src/components/sidebar/trigger.rs @@ -1,10 +1,10 @@ -use crate::sidebar::*; +use crate::{Button, sidebar::*}; use leptos::prelude::*; /// Wrapper that toggles showing/hiding of the sidebar. #[component] pub fn SidebarTrigger( - children: Children, + #[prop(optional)] children: Option<Children>, // GLOBAL ATTRIBUTES // @@ -153,15 +153,64 @@ pub fn SidebarTrigger( /> }; - view! { - <div - on:click=move |_| { sidebar.hidden.set(!sidebar.hidden.get_untracked()) } + if let Some(children) = children { + return view! { + <div + on:click=move |_| { sidebar.open.set(!sidebar.open.get_untracked()) } - {..global_attrs_1} - {..global_attrs_2} - > + {..global_attrs_1} + {..global_attrs_2} + > + + {children()} + </div> + } + .into_any(); + } - {children()} - </div> + view! { + <Button + on:click=move |_| sidebar.open.set(!sidebar.open.get_untracked()) + size="sm-icon" + variant="ghost" + > + <Show + when=move || sidebar.side.get().as_str() == "right" + fallback=|| { + view! { + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + > + <rect width="18" height="18" x="3" y="3" rx="2" /> + <path d="M9 3v18" /> + </svg> + } + } + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + > + <rect width="18" height="18" x="3" y="3" rx="2" /> + <path d="M15 3v18" /> + </svg> + </Show> + </Button> } + .into_any() } diff --git a/singlestage/src/components/skeleton/style/base/luma/skeleton.css b/singlestage/src/components/skeleton/style/base/luma/skeleton.css new file mode 100644 index 00000000..dc10bcf7 --- /dev/null +++ b/singlestage/src/components/skeleton/style/base/luma/skeleton.css @@ -0,0 +1,6 @@ +@layer components1 { + .singlestage-skeleton { + @apply bg-muted + rounded-2xl; + } +} diff --git a/singlestage/src/components/skeleton/style/base/lyra/skeleton.css b/singlestage/src/components/skeleton/style/base/lyra/skeleton.css new file mode 100644 index 00000000..91290852 --- /dev/null +++ b/singlestage/src/components/skeleton/style/base/lyra/skeleton.css @@ -0,0 +1,6 @@ +@layer components1 { + .singlestage-skeleton { + @apply bg-muted + rounded-none; + } +} diff --git a/singlestage/src/components/skeleton/style/base/maia/skeleton.css b/singlestage/src/components/skeleton/style/base/maia/skeleton.css new file mode 100644 index 00000000..9f9615b5 --- /dev/null +++ b/singlestage/src/components/skeleton/style/base/maia/skeleton.css @@ -0,0 +1,6 @@ +@layer components1 { + .singlestage-skeleton { + @apply bg-muted + rounded-xl; + } +} diff --git a/singlestage/src/components/skeleton/style/base/mira/skeleton.css b/singlestage/src/components/skeleton/style/base/mira/skeleton.css new file mode 100644 index 00000000..57a024f6 --- /dev/null +++ b/singlestage/src/components/skeleton/style/base/mira/skeleton.css @@ -0,0 +1,6 @@ +@layer components1 { + .singlestage-skeleton { + @apply bg-muted + rounded-md; + } +} diff --git a/singlestage/src/components/skeleton/style/base/nova/skeleton.css b/singlestage/src/components/skeleton/style/base/nova/skeleton.css new file mode 100644 index 00000000..57a024f6 --- /dev/null +++ b/singlestage/src/components/skeleton/style/base/nova/skeleton.css @@ -0,0 +1,6 @@ +@layer components1 { + .singlestage-skeleton { + @apply bg-muted + rounded-md; + } +} diff --git a/singlestage/src/components/skeleton/style/base/sera/skeleton.css b/singlestage/src/components/skeleton/style/base/sera/skeleton.css new file mode 100644 index 00000000..e44a7c6e --- /dev/null +++ b/singlestage/src/components/skeleton/style/base/sera/skeleton.css @@ -0,0 +1,5 @@ +@layer components1 { + .singlestage-skeleton { + @apply bg-muted; + } +} diff --git a/singlestage/src/components/skeleton/style/base/vega/skeleton.css b/singlestage/src/components/skeleton/style/base/vega/skeleton.css new file mode 100644 index 00000000..57a024f6 --- /dev/null +++ b/singlestage/src/components/skeleton/style/base/vega/skeleton.css @@ -0,0 +1,6 @@ +@layer components1 { + .singlestage-skeleton { + @apply bg-muted + rounded-md; + } +} diff --git a/singlestage/src/components/skeleton/skeleton.css b/singlestage/src/components/skeleton/style/skeleton.css similarity index 56% rename from singlestage/src/components/skeleton/skeleton.css rename to singlestage/src/components/skeleton/style/skeleton.css index 29d2c460..f7f42481 100644 --- a/singlestage/src/components/skeleton/skeleton.css +++ b/singlestage/src/components/skeleton/style/skeleton.css @@ -1,6 +1,5 @@ @layer components { .singlestage-skeleton { - @apply animate-pulse - bg-accent; + @apply animate-pulse; } } diff --git a/singlestage/src/components/slider/mod.rs b/singlestage/src/components/slider/mod.rs index 6325f727..3c4b1bed 100644 --- a/singlestage/src/components/slider/mod.rs +++ b/singlestage/src/components/slider/mod.rs @@ -1,16 +1,46 @@ -use crate::{FieldContext, FieldLabel, Reactive}; +use crate::{FieldContext, Label, Reactive}; use leptos::prelude::*; +// TODO: figure this out +// +// HTML5 doesn't support multiple thumb sliders without some trickery. +// +// The options are: +// 1. Wait for support +// 2. Do trickery in the `Slider` component, but that means the slider can't be a regular HTML +// input and may break accessibility and cause other problems +// 3. Have two slider components, like `Slider` and `RangeSlider`. That way the non-standard +// behavior is opt-in +// 4. Do something like `<Slider multiple=true />` and conditionally render the output which is +// basically the same as #3 but handling props might get interesting +// +// `Reactive` is already set up to handle the different value type conversions but for now I'm just +// going to skip multiple thumb support and comment out the `Reactive` code for now +// +// Another issue to be solved is in the case of a single thumb `Slider` sharing a `value` signal with +// some other component, say a `Progress`, which "should work": +// ```rs +// let value = RwSignal::new(1); +// +// view!{ +// <Progress value /> +// <Slider value /> +// } +// ``` +// This won't compile. #3 is probably the right solution, or something else + /// A simple component containing all parts of a slider. #[component] pub fn Slider( #[prop(optional)] children: Option<Children>, + #[prop(optional, into)] orientation: MaybeProp<String>, + /// Sets the default value of the element. Setting `value` sets this once at page load. /// Use this for subsequent updates. #[prop(optional, into)] default: MaybeProp<f64>, - /// The reactive value signal of this input. Also sets initial `default` value, but doesn't + /// The reactive value signal of this input. Also sets the initial `default` value, but doesn't /// update it. #[prop(optional, into)] value: Reactive<f64>, @@ -30,7 +60,7 @@ pub fn Slider( autocomplete: MaybeProp<String>, /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, @@ -38,17 +68,17 @@ pub fn Slider( #[prop(optional, into)] list: MaybeProp<String>, /// The greatest value in the range of permitted values. - #[prop(optional, into)] - max: MaybeProp<f64>, + #[prop(optional, into, default = Reactive::new(100.))] + max: Reactive<f64>, /// The lowest value in the range of permitted values. #[prop(optional, into)] - min: MaybeProp<f64>, + min: Reactive<f64>, /// Name of this element. Submitted with the form as part of a name/value pair. #[prop(optional, into)] name: MaybeProp<String>, /// Define the granularity of expected input value. - #[prop(optional, into)] - step: MaybeProp<f64>, + #[prop(optional, into, default = Reactive::new(1.))] + step: Reactive<f64>, // GLOBAL ATTRIBUTES // @@ -163,25 +193,22 @@ pub fn Slider( } }; - let max_default = 100.; - let min_default = 0.; - let step_default = 1.; - - let init_value = { - if let Some(default_value) = default.get_untracked() { - default_value - } else { - value.get_untracked() - } - }; + let slider_value = RwSignal::new({ + let init_value = { + if let Some(default_value) = default.get_untracked() { + value.set(default_value.clone()); + default_value + } else { + value.get_untracked() + } + }; - if let Some(slider) = slider_ref.get_untracked() { - slider.set_value_as_number(init_value); - }; + if let Some(slider) = slider_ref.get_untracked() { + slider.set_value_as_number(init_value); + }; - let slider_value = RwSignal::new({ - let max = max.get_untracked().unwrap_or(max_default); - let min = min.get_untracked().unwrap_or(min_default); + let max = max.get_untracked(); + let min = min.get_untracked(); let percent = if max == min { 0. @@ -194,35 +221,35 @@ pub fn Slider( // On default Effect::new(move || { - if let Some(default) = default.get() - && let Some(slider) = slider_ref.get_untracked() - { - slider.set_default_value(&default.to_string()); + if let Some(slider) = slider_ref.get() { + if let Some(default_value) = default.get() { + slider.set_default_value(&default_value.to_string()); + } } }); // On disabled Effect::new(move || { - if let Some(slider) = slider_ref.get_untracked() { - slider.set_disabled(disabled.get().unwrap_or_default()); + if let Some(slider) = slider_ref.get() { + slider.set_disabled(disabled.get()); } }); - let update_slider = move |min: f64, max: f64, current_value: f64| { - // Update slider - let percent: f64 = if max == min { + // Update slider track + Effect::new(move || { + let percent: f64 = if max.get() == min.get() { 0. } else { - ((current_value - min) / (max - min)) * 100. + ((value.get() - min.get()) / (max.get() - min.get())) * 100. }; slider_value.set(format!("{}%", &percent.to_string())); - }; + }); let on_input = move |ev| { let target_value = event_target_value(&ev); if let Ok(mut current_value) = target_value.parse::<f64>() { - let min = min.get_untracked().unwrap_or(min_default); - let max = max.get_untracked().unwrap_or(max_default); + let min = min.get_untracked(); + let max = max.get_untracked(); if current_value < min { current_value = min @@ -233,8 +260,6 @@ pub fn Slider( }; value.set(current_value); - - update_slider(min, max, current_value); } }; @@ -276,73 +301,11 @@ pub fn Slider( /> }; - let range_attrs = view! { - <{..} - autocomplete=move || autocomplete.get() - form=move || form.get() - list=move || list.get() - name=move || name.get() - /> - }; - - let update_max = move || { - let current_value = value.get_untracked(); - let max = max.get().unwrap_or(max_default); - let min = min.get_untracked().unwrap_or(min_default); - - if current_value > max { - value.set(max) - } - - update_slider(min, max, current_value); - max - }; - - let update_min = move || { - let current_value = value.get_untracked(); - let max = max.get_untracked().unwrap_or(max_default); - let min = min.get().unwrap_or(min_default); - - if current_value < min { - value.set(min) - } - - update_slider(min, max, current_value); - min - }; - - let update_prop_value = move || { - let mut new_value = value.get(); - let max = max.get_untracked().unwrap_or(max_default); - let min = min.get_untracked().unwrap_or(min_default); - - // Make sure value is in range - if new_value > max { - new_value = max; - value.set(max); - } else if new_value < min { - new_value = min; - value.set(min); - } - - update_slider(min, max, new_value); - new_value.to_string() - }; - - let update_step = move || { - let current_value = value.get(); - let max = max.get_untracked().unwrap_or(max_default); - let min = min.get_untracked().unwrap_or(min_default); - let step = step.get().unwrap_or(step_default); - update_slider(min, max, current_value); - step - }; - let input_id = uuid::Uuid::new_v4(); let label_id = uuid::Uuid::new_v4(); let has_children = children.is_some(); - let slider_attrs = view! { + let custom_attrs = view! { <{..} aria_describedby=move || { if let Some(field) = use_context::<FieldContext>() { @@ -352,83 +315,92 @@ pub fn Slider( None } } + aria_disabled=move || { + if let Some(field) = use_context::<FieldContext>() && field.disabled.get() { + Some("true".to_string()) + } else if disabled.get() { + Some("true".to_string()) + } else { + None + } + } + aria_invalid=move || { + if let Some(field) = use_context::<FieldContext>() && field.invalid.get() { + Some("true".to_string()) + } else { + None + } + } aria_labelledby=move || { if let Some(field) = use_context::<FieldContext>() { Some(field.label_id.get()) - } else if has_children { Some(label_id.to_string()) } else { None } + } else if has_children { + Some(label_id.to_string()) + } else { + None + } } - class=move || format!("singlestage-input {}", class.get().unwrap_or_default()) + autocomplete=move || autocomplete.get() + class=move || format!("singlestage-slider{} {}", + match orientation.get().unwrap_or_default().as_str() { + "vertical" => " singlestage-slider-vertical", + _ => " singlestage-slider-horizontal", + }, + class.get().unwrap_or_default()) disabled=disabled.get_untracked() - max=update_max - min=update_min + form=move || form.get() + id={if let Some(field) = use_context::<FieldContext>() { + if let Some(id) = id.get_untracked() { + field.input_id.set(id.clone()); + Some(id) + } else { + field.input_id.set(input_id.to_string()); + Some(input_id.to_string()) + } + } else if let Some(id) = id.get_untracked() { + Some(id) + } else { + if has_children { Some(input_id.to_string()) } else { None } + }} + list=move || list.get() + name=move || name.get() + max=move || max.get() + min=move || min.get() node_ref=slider_ref on:input=on_input - prop:value=update_prop_value - step=update_step + prop:value=move || value.get() + step=move || step.get() style:--slider-value=move || slider_value.get() type="range" - value=init_value.to_string() + value=default.get_untracked().unwrap_or(value.get_untracked()) /> }; if let Some(children) = children { view! { - {if use_context::<FieldContext>().is_some() { - view! { - <FieldLabel - class=class.get_untracked() - label_for=id.get_untracked().unwrap_or(input_id.to_string()) - > - {children()} - </FieldLabel> - } - .into_any() - } else { - view! { - <label - class=move || { - format!( - "singlestage-label singlestage-slider-label {}", - class.get().unwrap_or_default(), - ) - } - for=move || id.get().unwrap_or(input_id.to_string()) - id=label_id.to_string() - > - {children()} - </label> - } - .into_any() - }} + <Label + class + disabled + id=label_id.to_string() + label_for=id.get_untracked().unwrap_or(input_id.to_string()) + > + {children()} + </Label> <input - id=move || id.get().unwrap_or(input_id.to_string()) {..global_attrs_1} {..global_attrs_2} - {..range_attrs} - {..slider_attrs} + {..custom_attrs} /> } .into_any() } else { view! { <input - id={if let Some(field) = use_context::<FieldContext>() { - if let Some(id) = id.get_untracked() { - field.input_id.set(id.clone()); - Some(id) - } else { - field.input_id.set(input_id.to_string()); - Some(input_id.to_string()) - } - } else { - id.get_untracked() - }} {..global_attrs_1} {..global_attrs_2} - {..range_attrs} - {..slider_attrs} + {..custom_attrs} /> } .into_any() diff --git a/singlestage/src/components/slider/slider.css b/singlestage/src/components/slider/slider.css deleted file mode 100644 index fb672230..00000000 --- a/singlestage/src/components/slider/slider.css +++ /dev/null @@ -1,104 +0,0 @@ -@layer components { - .singlestage-input[type="range"] { - @apply appearance-none - disabled:cursor-not-allowed - disabled:opacity-50 - flex - items-center - outline-none - p-0; - --slider-value: 20%; - - &:hover, - &:focus-visible { - &::-webkit-slider-thumb { - @apply ring-4; - } - &::-moz-range-thumb { - @apply ring-4; - } - &::-ms-thumb { - @apply ring-4; - } - } - - &::-webkit-slider-thumb { - @apply -mt-1.25 - appearance-none - bg-background - block - border - border-primary - ring-ring/50 - rounded-full - shadow-sm - shrink-0 - size-4; - } - &::-webkit-slider-runnable-track { - @apply appearance-none - h-1.5 - rounded-full - w-full; - background: linear-gradient( - to right, - var(--primary) var(--slider-value), - var(--muted) var(--slider-value) - ); - } - &::-moz-range-thumb { - @apply -mt-1.25 - appearance-none - bg-background - block - border - border-primary - ring-ring/50 - rounded-full - shadow-sm - shrink-0 - size-4; - } - &::-moz-range-track { - @apply appearance-none - h-1.5 - rounded-full - w-full; - background: linear-gradient( - to right, - var(--primary) var(--slider-value), - var(--muted) var(--slider-value) - ); - } - &::-ms-thumb { - @apply -mt-1.25 - appearance-none - bg-background - block - border - border-primary - ring-ring/50 - rounded-full - shadow-sm - shrink-0 - size-4; - } - &::-ms-track { - @apply appearance-none - h-1.5 - rounded-full - w-full; - } - &::-ms-fill-lower { - @apply bg-primary - rounded-full; - } - &::-ms-fill-upper { - @apply bg-muted rounded-full; - } - } - - .singlestage-slider-label { - @apply my-3; - } -} diff --git a/singlestage/src/components/slider/style/base/luma/slider.css b/singlestage/src/components/slider/style/base/luma/slider.css new file mode 100644 index 00000000..0d50d9ff --- /dev/null +++ b/singlestage/src/components/slider/style/base/luma/slider.css @@ -0,0 +1,93 @@ +@layer components1 { + .singlestage-slider { + &::-webkit-slider-thumb, + &::-moz-range-thumb, + &::-ms-thumb { + @apply bg-white + h-4 + ring-1 + ring-black/10 + rounded-full + shadow-md + transition-[color,box-shadow,background-color] + w-6; + + &:focus-visible { + @apply outline-hidden + ring-4 + ring-ring/30; + } + + &:hover { + @apply ring-4 + ring-ring/30; + } + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply bg-input/90 + rounded-full; + } + + &::-ms-fill-lower { + @apply bg-primary + rounded-full; + } + + &::-ms-fill-upper { + @apply bg-input/90 + rounded-full; + } + } + + .singlestage-slider-range { + @apply bg-primary; + } + + .singlestage-slider-horizontal { + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-2 + w-full; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to right, + var(--primary) var(--slider-value), + color-mix(in oklab, var(--input) 90%, transparent) var(--slider-value) + ); + } + } + + .singlestage-slider-vertical { + @apply min-h-40; + + &::-webkit-slider-thumb, + &::-moz-range-thumb, + &::-ms-thumb { + @apply h-6 + w-4; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-full + w-2; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to top, + var(--primary) var(--slider-value), + color-mix(in oklab, var(--input) 90%, transparent) var(--slider-value) + ); + } + } +} diff --git a/singlestage/src/components/slider/style/base/lyra/slider.css b/singlestage/src/components/slider/style/base/lyra/slider.css new file mode 100644 index 00000000..22a1a7cd --- /dev/null +++ b/singlestage/src/components/slider/style/base/lyra/slider.css @@ -0,0 +1,89 @@ +@layer components1 { + .singlestage-slider { + &::-webkit-slider-thumb, + &::-moz-range-thumb, + &::-ms-thumb { + @apply bg-white + border + border-ring + relative + ring-ring/50 + rounded-none + size-3 + transition-[color,box-shadow]; + + &:active { + @apply ring-1; + } + + &:focus-visible { + @apply outline-hidden + ring-1; + } + + &:hover { + @apply ring-1; + } + + ::after { + @apply -inset-2 + absolute; + } + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply bg-muted + rounded-none; + } + + &::-ms-fill-lower { + @apply bg-primary + rounded-none; + } + + &::-ms-fill-upper { + @apply bg-muted + rounded-none; + } + } + + .singlestage-slider-horizontal { + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-1 + w-full; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to right, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } + + .singlestage-slider-vertical { + @apply min-h-40; + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-full + w-1; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to top, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } +} diff --git a/singlestage/src/components/slider/style/base/maia/slider.css b/singlestage/src/components/slider/style/base/maia/slider.css new file mode 100644 index 00000000..5b01248e --- /dev/null +++ b/singlestage/src/components/slider/style/base/maia/slider.css @@ -0,0 +1,80 @@ +@layer components1 { + .singlestage-slider { + &::-webkit-slider-thumb, + &::-moz-range-thumb, + &::-ms-thumb { + @apply bg-white + border + border-primary + ring-ring/50 + rounded-4xl + shadow-sm + size-4 + transition-colors; + + &:focus-visible { + @apply outline-hidden + ring-4; + } + + &:hover { + @apply ring-4; + } + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply bg-muted + rounded-4xl; + } + + &::-ms-fill-lower { + @apply bg-primary + rounded-full; + } + + &::-ms-fill-upper { + @apply bg-muted + rounded-full; + } + } + + .singlestage-slider-horizontal { + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-3 + w-full; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to right, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } + + .singlestage-slider-vertical { + @apply min-h-40; + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-full + w-3; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to top, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } +} diff --git a/singlestage/src/components/slider/style/base/mira/slider.css b/singlestage/src/components/slider/style/base/mira/slider.css new file mode 100644 index 00000000..da4d91ad --- /dev/null +++ b/singlestage/src/components/slider/style/base/mira/slider.css @@ -0,0 +1,89 @@ +@layer components1 { + .singlestage-slider { + &::-webkit-slider-thumb, + &::-moz-range-thumb, + &::-ms-thumb { + @apply bg-white + border + border-ring + relative + ring-ring/30 + rounded-md + size-3 + transition-[color,box-shadow]; + + &:active { + @apply ring-2; + } + + &:focus-visible { + @apply outline-hidden + ring-2; + } + + &:hover { + @apply ring-2; + } + + ::after { + @apply -inset-2 + absolute; + } + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply bg-muted + rounded-md; + } + + &::-ms-fill-lower { + @apply bg-primary + rounded-full; + } + + &::-ms-fill-upper { + @apply bg-muted + rounded-full; + } + } + + .singlestage-slider-horizontal { + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-1 + w-full; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to right, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } + + .singlestage-slider-vertical { + @apply min-h-40; + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-full + w-1; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to top, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } +} diff --git a/singlestage/src/components/slider/style/base/nova/slider.css b/singlestage/src/components/slider/style/base/nova/slider.css new file mode 100644 index 00000000..73642836 --- /dev/null +++ b/singlestage/src/components/slider/style/base/nova/slider.css @@ -0,0 +1,89 @@ +@layer components1 { + .singlestage-slider { + &::-webkit-slider-thumb, + &::-moz-range-thumb, + &::-ms-thumb { + @apply bg-white + border + border-ring + relative + ring-ring/50 + rounded-full + size-3 + transition-[color,box-shadow]; + + &:active { + @apply ring-3; + } + + &:focus-visible { + @apply outline-hidden + ring-3; + } + + &:hover { + @apply ring-3; + } + + ::after { + @apply -inset-2 + absolute; + } + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply bg-muted + rounded-full; + } + + &::-ms-fill-lower { + @apply bg-primary + rounded-full; + } + + &::-ms-fill-upper { + @apply bg-muted + rounded-full; + } + } + + .singlestage-slider-horizontal { + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-1 + w-full; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to right, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } + + .singlestage-slider-vertical { + @apply min-h-40; + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-full + w-1; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to top, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } +} diff --git a/singlestage/src/components/slider/style/base/sera/slider.css b/singlestage/src/components/slider/style/base/sera/slider.css new file mode 100644 index 00000000..21ee7e7b --- /dev/null +++ b/singlestage/src/components/slider/style/base/sera/slider.css @@ -0,0 +1,77 @@ +@layer components1 { + .singlestage-slider { + &::-webkit-slider-thumb, + &::-moz-range-thumb, + &::-ms-thumb { + @apply bg-primary + border-none + size-3 + transition-colors; + + &:focus-visible { + @apply outline-hidden + ring-2 + ring-ring/30; + } + + &:hover { + @apply ring-2 + ring-ring/30; + } + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply bg-input/50; + } + + &::-ms-fill-lower { + @apply bg-primary + rounded-full; + } + + &::-ms-fill-upper { + @apply bg-input/50 + rounded-full; + } + } + + .singlestage-slider-horizontal { + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-0.5 + w-full; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to right, + var(--primary) var(--slider-value), + color-mix(in oklab, var(--input) 50%, transparent) var(--slider-value) + ); + } + } + + .singlestage-slider-vertical { + @apply min-h-40; + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-full + w-0.5; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to top, + var(--primary) var(--slider-value), + color-mix(in oklab, var(--input) 50%, transparent) var(--slider-value) + ); + } + } +} diff --git a/singlestage/src/components/slider/style/base/vega/slider.css b/singlestage/src/components/slider/style/base/vega/slider.css new file mode 100644 index 00000000..52bb0ff9 --- /dev/null +++ b/singlestage/src/components/slider/style/base/vega/slider.css @@ -0,0 +1,80 @@ +@layer components1 { + .singlestage-slider { + &::-webkit-slider-thumb, + &::-moz-range-thumb, + &::-ms-thumb { + @apply bg-white + border + border-primary + ring-ring/50 + rounded-full + shadow-sm + size-4 + transition-[color,box-shadow]; + + &:focus-visible { + @apply outline-hidden + ring-4; + } + + &:hover { + @apply ring-4; + } + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply bg-muted + rounded-full; + } + + &::-ms-fill-lower { + @apply bg-primary + rounded-full; + } + + &::-ms-fill-upper { + @apply bg-muted + rounded-full; + } + } + + .singlestage-slider-horizontal { + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-1.5 + w-full; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to right, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } + + .singlestage-slider-vertical { + @apply min-h-40; + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-full + w-1.5; + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track { + background: linear-gradient( + to top, + var(--primary) var(--slider-value), + var(--muted) var(--slider-value) + ); + } + } +} diff --git a/singlestage/src/components/slider/style/slider.css b/singlestage/src/components/slider/style/slider.css new file mode 100644 index 00000000..6acf5e08 --- /dev/null +++ b/singlestage/src/components/slider/style/slider.css @@ -0,0 +1,65 @@ +@layer components { + .singlestage-slider { + /* @apply flex */ + /* items-center */ + /* relative */ + /* select-none */ + /* touch-none */ + /* w-full; */ + + &:disabled, + &[aria-disabled="true"] { + @apply opacity-50; + } + + &::-webkit-slider-thumb, + &::-moz-range-thumb, + &::-ms-thumb { + @apply appearance-none + block + select-none + shrink-0; + + &:disabled { + @apply opacity-50 + pointer-events-none; + } + } + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply appearance-none; + /* grow */ + /* overflow-hidden */ + /* relative */ + /* select-none; */ + } + } + + .singlestage-slider-horizontal { + @apply w-full; + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply w-full; + } + } + + .singlestage-slider-vertical { + appearance: slider-vertical; + direction: rtl; + writing-mode: vertical-lr; + + @apply /* flex-col */ + h-full; + /* w-auto; */ + + &::-moz-range-track, + &::-webkit-slider-runnable-track, + &::-ms-track { + @apply h-full; + } + } +} diff --git a/singlestage/src/components/spinner/spinner.css b/singlestage/src/components/spinner/style/spinner.css similarity index 100% rename from singlestage/src/components/spinner/spinner.css rename to singlestage/src/components/spinner/style/spinner.css diff --git a/singlestage/src/components/switch/mod.rs b/singlestage/src/components/switch/mod.rs index 96591a0b..07c23a34 100644 --- a/singlestage/src/components/switch/mod.rs +++ b/singlestage/src/components/switch/mod.rs @@ -1,4 +1,4 @@ -use crate::{CheckboxGroupContext, FieldContext, FieldLabel, Reactive}; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; /// A control that allows the user to toggle between checked and not checked. @@ -6,9 +6,16 @@ use leptos::prelude::*; pub fn Switch( #[prop(optional)] children: Option<Children>, + /// Whether the input is invalid + #[prop(optional, into)] + invalid: Reactive<bool>, + /// Set the size to render the Switch + #[prop(optional, into)] + size: MaybeProp<String>, + // CHECKBOX ATTRIBUTES // - /// Whether the command or control is checked + /// Reactive signal coupled to the checkbox's checked value. #[prop(optional, into)] checked: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. @@ -25,7 +32,7 @@ pub fn Switch( required: MaybeProp<bool>, /// Whether the form control is disabled #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// The value of the control. When specified in the HTML, corresponds to the initial value #[prop(optional, into)] value: MaybeProp<String>, @@ -140,69 +147,6 @@ pub fn Switch( #[prop(optional, into)] translate: MaybeProp<String>, ) -> impl IntoView { - let switch_ref = { - if let Some(node_ref) = node_ref.get_untracked() { - node_ref - } else { - NodeRef::<leptos::html::Input>::new() - } - }; - - let on_change = move |ev| { - let switch_checked = event_target_checked(&ev); - - checked.set(switch_checked); - - if let Some(checkbox_value) = value.get_untracked() - && let Some(checkbox_group) = use_context::<CheckboxGroupContext>() - { - match switch_checked { - true => checkbox_group.value.update(|group_value| { - group_value.push(checkbox_value); - }), - false => checkbox_group.value.update(|group_value| { - if let Some(index) = group_value.iter().position(|el| *el == checkbox_value) { - group_value.swap_remove(index); - } - }), - } - } - }; - - if let Some(value) = value.get_untracked() - && let Some(checkbox_group) = use_context::<CheckboxGroupContext>() - && checkbox_group.value.get_untracked().contains(&value) - && let Some(switch) = switch_ref.get_untracked() - { - switch.set_checked(true) - } - - Effect::new(move || { - if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() - && let Some(value) = value.get_untracked() - { - if checkbox_group.value.get().contains(&value) { - checked.set(true); - } else { - checked.set(false); - } - } - }); - - Effect::new(move || { - if let Some(switch) = switch_ref.get_untracked() { - switch.set_checked(checked.get()); - } - }); - - Effect::new(move || { - if let Some(switch) = switch_ref.get_untracked() - && let Some(disabled) = disabled.get() - { - switch.set_disabled(disabled); - } - }); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -214,14 +158,12 @@ pub fn Switch( 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() @@ -241,111 +183,51 @@ pub fn Switch( /> }; - let input_id = uuid::Uuid::new_v4(); - let label_id = uuid::Uuid::new_v4(); - let has_children = children.is_some(); - - let custom_attrs = view! { - <{..} - aria_describedby=move || { - if let Some(field) = use_context::<FieldContext>() { - let description_id = field.description_id.get(); - if description_id.is_empty() { None } else { Some(description_id) } - } else { - None - } - } - aria-invalid=move || { - if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() { - checkbox_group.invalid.get().to_string() - } else { - false.to_string() - } - } - aria_labelledby=move || { - if let Some(field) = use_context::<FieldContext>() { - Some(field.label_id.get()) - } else if has_children { - Some(label_id.to_string()) - } else { - None - } - } - checked=checked.get_untracked() - class=move || { format!("singlestage-input {}", class.get().unwrap_or_default()) } - disabled=disabled.get_untracked() - form=move || form.get() - name=move || name.get() - node_ref=switch_ref - on:change=on_change - readonly=move || readonly.get() - required=move || required.get() - role="switch" - type="checkbox" - value=move || value.get() - /> - }; - if let Some(children) = children { view! { - {if use_context::<FieldContext>().is_some() { - view! { - <input - id=move || id.get().unwrap_or(input_id.to_string()) + <CheckboxPrimitive + primitive_type=CheckboxPrimitiveType::Switch + + checked + class + disabled + form + id + invalid + name + node_ref + readonly + required + size + value - {..global_attrs_1} - {..global_attrs_2} - {..custom_attrs} - /> - <FieldLabel - class=class.get_untracked() - label_for=id.get_untracked().unwrap_or(input_id.to_string()) - > - {children()} - </FieldLabel> - } - .into_any() - } else { - view! { - <label - class=move || { - format!("singlestage-label {}", class.get().unwrap_or_default()) - } - for=move || id.get().unwrap_or(input_id.to_string()) - id=label_id.to_string() - > - <input - id=move || id.get().unwrap_or(input_id.to_string()) - - {..global_attrs_1} - {..global_attrs_2} - {..custom_attrs} - /> - {children()} - </label> - } - .into_any() - }} + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </CheckboxPrimitive> } .into_any() } else { view! { - <input - id={if let Some(field) = use_context::<FieldContext>() { - if let Some(id) = id.get_untracked() { - field.input_id.set(id.clone()); - Some(id) - } else { - field.input_id.set(input_id.to_string()); - Some(input_id.to_string()) - } - } else { - id.get_untracked() - }} + <CheckboxPrimitive + primitive_type=CheckboxPrimitiveType::Switch + + checked + class + disabled + form + id + invalid + name + node_ref + readonly + required + size + value {..global_attrs_1} {..global_attrs_2} - {..custom_attrs} /> } .into_any() diff --git a/singlestage/src/components/switch/style/base/luma/switch.css b/singlestage/src/components/switch/style/base/luma/switch.css new file mode 100644 index 00000000..bf53734d --- /dev/null +++ b/singlestage/src/components/switch/style/base/luma/switch.css @@ -0,0 +1,65 @@ +@layer components1 { + .singlestage-switch { + @apply border-2 + rounded-full + shrink-0; + + &:checked { + @apply bg-primary + border-primary; + } + + &:not(&:checked) { + @apply bg-input/90 + border-transparent; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &::after { + @apply bg-background + /* not-dark:bg-clip-padding */ + rounded-full + shadow-sm; + } + + &:checked::after { + @apply translate-x-[calc(100%-8px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } + + .singlestage-switch-size-default { + @apply h-5 + w-11; + + &::after { + @apply h-4 + w-6; + } + } + + .singlestage-switch-size-sm { + @apply h-4 + w-7; + + &::after { + @apply h-3 + w-4; + } + } +} diff --git a/singlestage/src/components/switch/style/base/luma/switch_dark.css b/singlestage/src/components/switch/style/base/luma/switch_dark.css new file mode 100644 index 00000000..754dc81a --- /dev/null +++ b/singlestage/src/components/switch/style/base/luma/switch_dark.css @@ -0,0 +1,21 @@ +@layer components1 { + .singlestage-switch { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + + &:not(&:checked) { + @apply bg-input/80; + } + + &:checked::after { + @apply bg-primary-foreground; + } + + &:not(&:checked)::after { + @apply bg-foreground; + } + } +} diff --git a/singlestage/src/components/switch/style/base/lyra/switch.css b/singlestage/src/components/switch/style/base/lyra/switch.css new file mode 100644 index 00000000..c797590b --- /dev/null +++ b/singlestage/src/components/switch/style/base/lyra/switch.css @@ -0,0 +1,68 @@ +@layer components1 { + .singlestage-switch { + @apply border + border-transparent + rounded-full + shrink-0; + + &:checked { + @apply bg-primary; + } + + &:not(&:checked) { + @apply bg-input; + } + + &:focus-visible { + @apply border-ring + ring-1 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-1 + ring-destructive/20; + } + + &::after { + @apply bg-background + rounded-full; + } + } + + .singlestage-switch-size-default { + @apply h-[18.4px] + w-[32px]; + + &::after { + @apply size-4; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } + + .singlestage-switch-size-sm { + @apply h-[14px] + w-[24px]; + + &::after { + @apply size-3; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } +} diff --git a/singlestage/src/components/switch/style/base/lyra/switch_dark.css b/singlestage/src/components/switch/style/base/lyra/switch_dark.css new file mode 100644 index 00000000..754dc81a --- /dev/null +++ b/singlestage/src/components/switch/style/base/lyra/switch_dark.css @@ -0,0 +1,21 @@ +@layer components1 { + .singlestage-switch { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + + &:not(&:checked) { + @apply bg-input/80; + } + + &:checked::after { + @apply bg-primary-foreground; + } + + &:not(&:checked)::after { + @apply bg-foreground; + } + } +} diff --git a/singlestage/src/components/switch/style/base/maia/switch.css b/singlestage/src/components/switch/style/base/maia/switch.css new file mode 100644 index 00000000..f42c8159 --- /dev/null +++ b/singlestage/src/components/switch/style/base/maia/switch.css @@ -0,0 +1,68 @@ +@layer components1 { + .singlestage-switch { + @apply border + border-transparent + rounded-full + shrink-0; + + &:checked { + @apply bg-primary; + } + + &:not(&:checked) { + @apply bg-input; + } + + &:focus-visible { + @apply border-ring + ring-[3px] + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-[3px] + ring-destructive/20; + } + + &::after { + @apply bg-background + rounded-full; + } + } + + .singlestage-switch-size-default { + @apply h-[18.4px] + w-[32px]; + + &::after { + @apply size-4; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } + + .singlestage-switch-size-sm { + @apply h-[14px] + w-[24px]; + + &::after { + @apply size-3; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } +} diff --git a/singlestage/src/components/switch/style/base/maia/switch_dark.css b/singlestage/src/components/switch/style/base/maia/switch_dark.css new file mode 100644 index 00000000..c3228715 --- /dev/null +++ b/singlestage/src/components/switch/style/base/maia/switch_dark.css @@ -0,0 +1,21 @@ +@layer components1 { + .singlestage-switch { + &:not(&:checked) { + @apply bg-input/80; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + + &:checked::after { + @apply bg-primary-foreground; + } + + &:not(&:checked)::after { + @apply bg-foreground; + } + } +} diff --git a/singlestage/src/components/switch/style/base/mira/switch.css b/singlestage/src/components/switch/style/base/mira/switch.css new file mode 100644 index 00000000..49893d6c --- /dev/null +++ b/singlestage/src/components/switch/style/base/mira/switch.css @@ -0,0 +1,68 @@ +@layer components1 { + .singlestage-switch { + @apply border + border-transparent + rounded-full + shrink-0; + + &:checked { + @apply bg-primary; + } + + &:not(&:checked) { + @apply bg-input; + } + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + } + + &::after { + @apply bg-background + rounded-full; + } + } + + .singlestage-switch-size-default { + @apply h-[16.6px] + w-[28px]; + + &::after { + @apply size-3.5; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } + + .singlestage-switch-size-sm { + @apply h-[14px] + w-[24px]; + + &::after { + @apply size-3; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } +} diff --git a/singlestage/src/components/switch/style/base/mira/switch_dark.css b/singlestage/src/components/switch/style/base/mira/switch_dark.css new file mode 100644 index 00000000..c3228715 --- /dev/null +++ b/singlestage/src/components/switch/style/base/mira/switch_dark.css @@ -0,0 +1,21 @@ +@layer components1 { + .singlestage-switch { + &:not(&:checked) { + @apply bg-input/80; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + + &:checked::after { + @apply bg-primary-foreground; + } + + &:not(&:checked)::after { + @apply bg-foreground; + } + } +} diff --git a/singlestage/src/components/switch/style/base/nova/switch.css b/singlestage/src/components/switch/style/base/nova/switch.css new file mode 100644 index 00000000..ebd71832 --- /dev/null +++ b/singlestage/src/components/switch/style/base/nova/switch.css @@ -0,0 +1,68 @@ +@layer components1 { + .singlestage-switch { + @apply border + border-transparent + rounded-full + shrink-0; + + &:checked { + @apply bg-primary; + } + + &:not(&:checked) { + @apply bg-input; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &::after { + @apply bg-background + rounded-full; + } + } + + .singlestage-switch-size-default { + @apply h-[18.4px] + w-[32px]; + + &::after { + @apply size-4; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } + + .singlestage-switch-size-sm { + @apply h-[14px] + w-[24px]; + + &::after { + @apply size-3; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } +} diff --git a/singlestage/src/components/switch/style/base/nova/switch_dark.css b/singlestage/src/components/switch/style/base/nova/switch_dark.css new file mode 100644 index 00000000..c3228715 --- /dev/null +++ b/singlestage/src/components/switch/style/base/nova/switch_dark.css @@ -0,0 +1,21 @@ +@layer components1 { + .singlestage-switch { + &:not(&:checked) { + @apply bg-input/80; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + + &:checked::after { + @apply bg-primary-foreground; + } + + &:not(&:checked)::after { + @apply bg-foreground; + } + } +} diff --git a/singlestage/src/components/switch/style/base/sera/switch.css b/singlestage/src/components/switch/style/base/sera/switch.css new file mode 100644 index 00000000..b2bae5e8 --- /dev/null +++ b/singlestage/src/components/switch/style/base/sera/switch.css @@ -0,0 +1,60 @@ +@layer components1 { + .singlestage-switch { + @apply border + rounded-none + shrink-0; + + &:checked { + @apply bg-primary + border-primary; + } + + &:not(&:checked) { + @apply bg-input + border-input/50; + } + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + } + + &::after { + @apply bg-background; + } + + &:checked::after { + @apply translate-x-[calc(100%+2px)]; + } + + &::not(&:checked)::after { + @apply translate-x-0.25; + } + } + + .singlestage-switch-size-default { + @apply h-4.5 + w-8.25; + + &::after { + @apply size-3.5; + } + } + + .singlestage-switch-size-sm { + @apply h-3.5 + w-6.25; + + &::after { + @apply size-2.5; + } + } +} diff --git a/singlestage/src/components/switch/style/base/sera/switch_dark.css b/singlestage/src/components/switch/style/base/sera/switch_dark.css new file mode 100644 index 00000000..d88464c5 --- /dev/null +++ b/singlestage/src/components/switch/style/base/sera/switch_dark.css @@ -0,0 +1,17 @@ +@layer components1 { + .singlestage-switch { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + + &:checked::after { + @apply bg-primary-foreground; + } + + &:not(&:checked)::after { + @apply bg-foreground; + } + } +} diff --git a/singlestage/src/components/switch/style/base/vega/switch.css b/singlestage/src/components/switch/style/base/vega/switch.css new file mode 100644 index 00000000..036c26fb --- /dev/null +++ b/singlestage/src/components/switch/style/base/vega/switch.css @@ -0,0 +1,69 @@ +@layer components1 { + .singlestage-switch { + @apply border + border-transparent + rounded-full + shadow-xs + shrink-0; + + &:checked { + @apply bg-primary; + } + + &:not(&:checked) { + @apply bg-input; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + + &::after { + @apply bg-background + rounded-full; + } + } + + .singlestage-switch-size-default { + @apply h-[18.4px] + w-[32px]; + + &::after { + @apply size-4; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } + + .singlestage-switch-size-sm { + @apply h-[14px] + w-[24px]; + + &::after { + @apply size-3; + } + + &:checked::after { + @apply translate-x-[calc(100%-2px)]; + } + + &:not(&:checked)::after { + @apply translate-x-0; + } + } +} diff --git a/singlestage/src/components/switch/style/base/vega/switch_dark.css b/singlestage/src/components/switch/style/base/vega/switch_dark.css new file mode 100644 index 00000000..59287563 --- /dev/null +++ b/singlestage/src/components/switch/style/base/vega/switch_dark.css @@ -0,0 +1,21 @@ +@layer components1 { + .singlestage-switch { + &:checked::after { + @apply bg-primary-foreground; + } + + &:not(&:checked) { + @apply bg-input/80; + + &::after { + @apply bg-foreground; + } + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/switch/style/switch.css b/singlestage/src/components/switch/style/switch.css new file mode 100644 index 00000000..2ec670a8 --- /dev/null +++ b/singlestage/src/components/switch/style/switch.css @@ -0,0 +1,36 @@ +@layer components { + @media (prefers-reduced-motion: no-preference) { + .singlestage-switch { + @apply transition-all; + + &::after { + @apply transition-transform; + } + } + } + + .singlestage-switch { + @apply appearance-none + inline-flex + items-center + outline-none + relative; + + /* &::after { */ + /* @apply -inset-x-3 -inset-y-2 absolute; */ + /* } */ + + &:disabled, + &[aria-disabled="true"] { + @apply cursor-not-allowed + opacity-50; + } + + &::after { + @apply block + content-[''] + pointer-events-none + ring-0; + } + } +} diff --git a/singlestage/src/components/switch/switch.css b/singlestage/src/components/switch/switch.css deleted file mode 100644 index fd3ee949..00000000 --- a/singlestage/src/components/switch/switch.css +++ /dev/null @@ -1,32 +0,0 @@ -@layer components { - .singlestage-input[type="checkbox"][role="switch"] { - @apply appearance-none - before:bg-background - before:block - before:content-[''] - before:pointer-events-none - before:ring-0 - before:rounded-full - before:size-4 - before:transition-all - bg-input - border - border-transparent - checked:before:ms-3.5 - checked:bg-primary - disabled:cursor-not-allowed - disabled:opacity-50 - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - h-[1.15rem] - inline-flex - items-center - outline-none - rounded-full - shadow-xs - shrink-0 - transition-all - w-8; - } -} diff --git a/singlestage/src/components/table/style/base/luma/table.css b/singlestage/src/components/table/style/base/luma/table.css new file mode 100644 index 00000000..901e5267 --- /dev/null +++ b/singlestage/src/components/table/style/base/luma/table.css @@ -0,0 +1,75 @@ +@layer components1 { + .singlestage-table-container { + @apply overflow-x-auto + relative + w-full; + } + + .singlestage-table { + @apply caption-bottom + text-sm + w-full; + + caption { + @apply mt-4 + text-muted-foreground + text-sm; + } + + thead { + tr { + @apply border-b; + } + } + + tbody { + tr:last-child { + @apply border-0; + } + } + + tfoot { + @apply bg-muted/50 + border-t + font-medium; + + > tr:last-child { + @apply border-b-0; + } + } + + tr { + @apply border-b + data-[state=selected]:bg-muted + transition-colors; + + &:hover { + @apply bg-muted/50; + } + } + + th { + @apply align-middle + font-medium + h-12 + px-3 + text-foreground + text-left + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + td { + @apply align-middle + p-3 + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + } +} diff --git a/singlestage/src/components/table/style/base/lyra/table.css b/singlestage/src/components/table/style/base/lyra/table.css new file mode 100644 index 00000000..af6fc9c7 --- /dev/null +++ b/singlestage/src/components/table/style/base/lyra/table.css @@ -0,0 +1,75 @@ +@layer components1 { + .singlestage-table-container { + @apply overflow-x-auto + relative + w-full; + } + + .singlestage-table { + @apply caption-bottom + text-xs + w-full; + + thead { + tr { + @apply border-b; + } + } + + .tbody { + tr:last-child { + @apply border-0; + } + } + + tfoot { + @apply bg-muted/50 + border-t + font-medium; + + > tr:last-child { + @apply border-b-0; + } + } + + tr { + @apply border-b + data-[state=selected]:bg-muted + transition-colors; + + &:hover { + @apply bg-muted/50; + } + } + + th { + @apply align-middle + font-medium + h-10 + px-2 + text-foreground + text-left + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + td { + @apply align-middle + p-2 + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + caption { + @apply mt-4 + text-muted-foreground + text-xs; + } + } +} diff --git a/singlestage/src/components/table/style/base/maia/table.css b/singlestage/src/components/table/style/base/maia/table.css new file mode 100644 index 00000000..51430db0 --- /dev/null +++ b/singlestage/src/components/table/style/base/maia/table.css @@ -0,0 +1,75 @@ +@layer components1 { + .singlestage-table-container { + @apply overflow-x-auto + relative + w-full; + } + + .singlestage-table { + @apply caption-bottom + text-sm + w-full; + + thead { + tr { + @apply border-b; + } + } + + tbody { + tr:last-child { + @apply border-0; + } + } + + tfoot { + @apply bg-muted/50 + border-t + font-medium; + + > tr:last-child { + @apply border-b-0; + } + } + + tr { + @apply border-b + data-[state=selected]:bg-muted + transition-colors; + + &:hover { + @apply bg-muted/50; + } + } + + th { + @apply align-middle + font-medium + h-12 + px-3 + text-foreground + text-left + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + td { + @apply align-middle + p-3 + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + caption { + @apply mt-4 + text-muted-foreground + text-sm; + } + } +} diff --git a/singlestage/src/components/table/style/base/mira/table.css b/singlestage/src/components/table/style/base/mira/table.css new file mode 100644 index 00000000..ed920971 --- /dev/null +++ b/singlestage/src/components/table/style/base/mira/table.css @@ -0,0 +1,75 @@ +@layer components1 { + .singlestage-table-container { + @apply overflow-x-auto + relative + w-full; + } + + .singlestage-table { + @apply caption-bottom + text-xs + w-full; + + thead { + tr { + @apply border-b; + } + } + + tbody { + tr:last-child { + @apply border-0; + } + } + + tfoot { + @apply bg-muted/50 + border-t + font-medium; + + > td:last-child { + @apply border-b-0; + } + } + + tr { + @apply border-b + data-[state=selected]:bg-muted + transition-colors; + + &:hover { + @apply bg-muted/50; + } + } + + th { + @apply align-middle + font-medium + h-10 + px-2 + text-foreground + text-left + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + td { + @apply align-middle + p-2 + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + caption { + @apply mt-4 + text-muted-foreground + text-xs; + } + } +} diff --git a/singlestage/src/components/table/style/base/nova/table.css b/singlestage/src/components/table/style/base/nova/table.css new file mode 100644 index 00000000..eea7978f --- /dev/null +++ b/singlestage/src/components/table/style/base/nova/table.css @@ -0,0 +1,75 @@ +@layer components1 { + .singlestage-table-container { + @apply overflow-x-auto + relative + w-full; + } + + .singlestage-table { + @apply caption-bottom + text-sm + w-full; + + thead { + tr { + @apply border-b; + } + } + + tbody { + tr:last-child { + @apply border-0; + } + } + + tfoot { + @apply bg-muted/50 + border-t + font-medium; + + > tr:last-child { + @apply border-b-0; + } + } + + tr { + @apply border-b + data-[state=selected]:bg-muted + transition-colors; + + &:hover { + @apply bg-muted/50; + } + } + + th { + @apply align-middle + font-medium + h-10 + px-2 + text-foreground + text-left + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + td { + @apply align-middle + p-2 + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + caption { + @apply mt-4 + text-muted-foreground + text-sm; + } + } +} diff --git a/singlestage/src/components/table/style/base/sera/table.css b/singlestage/src/components/table/style/base/sera/table.css new file mode 100644 index 00000000..464d4131 --- /dev/null +++ b/singlestage/src/components/table/style/base/sera/table.css @@ -0,0 +1,78 @@ +@layer components1 { + .singlestage-table-container { + @apply overflow-x-auto + relative + w-full; + } + + .singlestage-table { + @apply caption-bottom + text-sm + w-full; + + thead { + tr { + @apply border-b; + } + } + + tbody { + tr:last-child { + @apply border-0; + } + } + + tfoot { + @apply bg-muted/50 + border-t + font-medium; + + > tr:last-child { + @apply border-b-0; + } + } + + tr { + @apply border-b + data-[state=selected]:bg-muted + transition-colors; + + &:hover { + @apply bg-muted/50; + } + } + + th { + @apply align-middle + font-medium + h-12 + px-3 + text-left + text-muted-foreground + text-xs + tracking-wider + uppercase + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + td { + @apply align-middle + p-3 + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + caption { + @apply mt-4 + text-muted-foreground + text-sm; + } + } +} diff --git a/singlestage/src/components/table/style/base/vega/table.css b/singlestage/src/components/table/style/base/vega/table.css new file mode 100644 index 00000000..787993a3 --- /dev/null +++ b/singlestage/src/components/table/style/base/vega/table.css @@ -0,0 +1,74 @@ +@layer components1 { + .singlestage-table-container { + @apply overflow-x-auto + relative + w-full; + } + + .singlestage-table { + @apply caption-bottom + text-sm + w-full; + + caption { + @apply mt-4 + text-muted-foreground + text-sm; + } + + thead { + @apply [&_tr]:border-b; + } + + th { + @apply align-middle + border-b + font-medium + h-10 + px-2 + text-foreground + text-left + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + tbody { + tr:last-child { + @apply border-0; + } + } + + tr { + @apply border-b + data-[state=selected]:bg-muted + transition-colors; + + &:hover { + @apply bg-muted/50; + } + } + + td { + @apply align-middle + p-2 + whitespace-nowrap; + + &:has(.singlestage-checkbox) { + @apply pr-0; + } + } + + tfoot { + @apply bg-muted/50 + border-t + font-medium; + + > tr:last-child { + @apply border-b-0; + } + } + } +} diff --git a/singlestage/src/components/table/style/table.css b/singlestage/src/components/table/style/table.css new file mode 100644 index 00000000..9831046d --- /dev/null +++ b/singlestage/src/components/table/style/table.css @@ -0,0 +1,9 @@ +@layer components { + .singlestage-table { + tr { + &:has(*:[aria-expanded="true"]) { + @apply bg-muted/50; + } + } + } +} diff --git a/singlestage/src/components/table/table.css b/singlestage/src/components/table/table.css deleted file mode 100644 index f626873b..00000000 --- a/singlestage/src/components/table/table.css +++ /dev/null @@ -1,54 +0,0 @@ -@layer components { - .singlestage-table { - @apply text-sm - w-full; - - thead { - @apply [&_tr]:border-b; - } - - tbody { - @apply [&_tr:last-child]:border-0; - } - - tfoot { - @apply [&>tr]:last:border-b-0 - bg-muted/50 - border-t - font-medium; - } - - tr { - @apply border-b - hover:bg-muted/50 - transition-colors; - } - - th { - @apply [&:has([role=checkbox])]:pr-0 - [&>[role=checkbox]]:translate-y-[2px] - align-middle - font-medium - h-10 - px-2 - text-foreground - text-left - whitespace-nowrap; - } - - td { - @apply [&:has([role=checkbox])]:pr-0 - [&>[role=checkbox]]:translate-y-[2px] - align-middle - p-2; - /* whitespace-nowrap; */ - } - - caption { - @apply caption-bottom - mt-4 - text-muted-foreground - text-sm; - } - } -} diff --git a/singlestage/src/components/table/table.rs b/singlestage/src/components/table/table.rs index 7561d4c4..6a7152db 100644 --- a/singlestage/src/components/table/table.rs +++ b/singlestage/src/components/table/table.rs @@ -151,13 +151,15 @@ pub fn Table( }; view! { - <table - class=move || { format!("singlestage-table {}", class.get().unwrap_or_default()) } + <div class="singlestage-table-container"> + <table + class=move || { format!("singlestage-table {}", class.get().unwrap_or_default()) } - {..global_attrs_1} - {..global_attrs_2} - > - {children()} - </table> + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </table> + </div> } } 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::<TabsContext>(); + 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! { <div - aria-labelledby=move || format!("{}-tab", value.get().unwrap_or_default()) - aria-selected=move || { - if value.get().unwrap_or_default() == tabs.value.get() { "true" } else { "false" } - } - hidden=move || value.get().unwrap_or_default() != tabs.value.get() + aria_expanded=move || { if tab_selected() { "true" } else { "false" } } + aria_labelledby=move || format!("{}-tab", value.get().unwrap_or_default()) + aria_selected=move || { if tab_selected() { "true" } else { "false" } } + class=move || format!("singlestage-tabs-content {}", class.get().unwrap_or_default()) + hidden=move || !tab_selected() id=move || format!("{}-panel", value.get().unwrap_or_default()) + prop:hidden=move || !tab_selected() role="tabpanel" - tabindex="-1" + tabindex="0" {..global_attrs_1} {..global_attrs_2} diff --git a/singlestage/src/components/tabs/list.rs b/singlestage/src/components/tabs/list.rs index dd3a3d5d..4dd24aa0 100644 --- a/singlestage/src/components/tabs/list.rs +++ b/singlestage/src/components/tabs/list.rs @@ -5,6 +5,11 @@ use leptos::prelude::*; pub fn TabsList( children: Children, + /// Set the display variant of the tab buttons. + /// Accepted values: "default" | "line" + #[prop(optional, into)] + variant: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -86,9 +91,6 @@ pub fn TabsList( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp<String>, - // /// Define the semantic meaning of content. - // #[prop(optional, into)] - // role: MaybeProp<String>, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp<String>, @@ -115,7 +117,6 @@ pub fn TabsList( 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() @@ -140,7 +141,6 @@ pub fn TabsList( 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() @@ -152,7 +152,16 @@ pub fn TabsList( view! { <nav - aria-orientation="horizontal" + class=move || { + format!( + "singlestage-tabs-list {} {}", + match variant.get().unwrap_or_default().as_str() { + "line" => "singlestage-tabs-list-variant-line", + _ => "singlestage-tabs-list-variant-default", + }, + class.get().unwrap_or_default(), + ) + } role="tablist" {..global_attrs_1} diff --git a/singlestage/src/components/tabs/mod.rs b/singlestage/src/components/tabs/mod.rs index 5c731b21..0fccbf33 100644 --- a/singlestage/src/components/tabs/mod.rs +++ b/singlestage/src/components/tabs/mod.rs @@ -11,6 +11,6 @@ pub use tabs::*; pub use trigger::*; #[derive(Clone)] -pub struct TabsContext { +pub(crate) struct TabsContext { value: Reactive<String>, } diff --git a/singlestage/src/components/tabs/style/base/luma/tabs.css b/singlestage/src/components/tabs/style/base/luma/tabs.css new file mode 100644 index 00000000..80792a2d --- /dev/null +++ b/singlestage/src/components/tabs/style/base/luma/tabs.css @@ -0,0 +1,59 @@ +@layer components1 { + .singlestage-tabs { + @apply gap-2; + } + + .singlestage-tabs-list { + @apply p-1 + rounded-full; + } + + .singlestage-tabs-trigger { + @apply border + border-transparent! + font-medium + gap-2 + px-3 + py-1 + rounded-full + text-sm; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-tabs-list-variant-line { + @apply rounded-none; + } + + .singlestage-tabs-orientation-horizontal { + .singlestage-tabs-list { + @apply h-9; + } + } + + .singlestage-tabs-orientation-vertical { + .singlestage-tabs-list { + @apply rounded-2xl; + } + + .singlestage-tabs-trigger { + @apply px-3 + py-1.5 + rounded-2xl; + } + } + + .singlestage-tabs-content { + @apply text-sm; + } +} diff --git a/singlestage/src/components/tabs/style/base/lyra/tabs.css b/singlestage/src/components/tabs/style/base/lyra/tabs.css new file mode 100644 index 00000000..a99592c6 --- /dev/null +++ b/singlestage/src/components/tabs/style/base/lyra/tabs.css @@ -0,0 +1,53 @@ +@layer components1 { + .singlestage-tabs { + @apply gap-2; + } + + .singlestage-tabs-list { + @apply p-[3px] + rounded-none; + } + + .singlestage-tabs-trigger { + @apply border + border-transparent + font-medium + gap-1.5 + px-1.5 + py-0.5 + rounded-none + text-xs; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-tabs-list-variant-line { + @apply rounded-none; + } + + .singlestage-tabs-orientation-horizontal { + .singlestage-tabs-list { + @apply h-8; + } + } + + .singlestage-tabs-orientation-horizontal { + .singlestage-tabs-trigger { + @apply py-[calc(--spacing(1.25))]; + } + } + + .singlestage-tabs-content { + @apply text-xs/relaxed; + } +} diff --git a/singlestage/src/components/tabs/style/base/maia/tabs.css b/singlestage/src/components/tabs/style/base/maia/tabs.css new file mode 100644 index 00000000..4e79508e --- /dev/null +++ b/singlestage/src/components/tabs/style/base/maia/tabs.css @@ -0,0 +1,58 @@ +@layer components1 { + .singlestage-tabs { + @apply gap-2; + } + + .singlestage-tabs-list { + @apply p-[3px] + rounded-4xl; + } + + .singlestage-tabs-trigger { + @apply border + border-transparent + font-medium + gap-1.5 + px-2 + py-1 + rounded-xl + text-sm; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-tabs-list-variant-line { + @apply rounded-none; + } + + .singlestage-tabs-orientation-horizontal { + .singlestage-tabs-list { + @apply h-9; + } + } + + .singlestage-tabs-orientation-vertical { + .singlestage-tabs-list { + @apply rounded-2xl; + } + + .singlestage-tabs-trigger { + @apply px-2.5 + py-1.5; + } + } + + .singlestage-tabs-content { + @apply text-sm; + } +} diff --git a/singlestage/src/components/tabs/style/base/mira/tabs.css b/singlestage/src/components/tabs/style/base/mira/tabs.css new file mode 100644 index 00000000..ca7574f1 --- /dev/null +++ b/singlestage/src/components/tabs/style/base/mira/tabs.css @@ -0,0 +1,53 @@ +@layer components1 { + .singlestage-tabs { + @apply gap-2; + } + + .singlestage-tabs-list { + @apply p-[3px] + rounded-lg; + } + + .singlestage-tabs-trigger { + @apply border + border-transparent + font-medium + gap-1.5 + px-1.5 + py-0.5 + rounded-md + text-xs; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-tabs-list-variant-line { + @apply rounded-none; + } + + .singlestage-tabs-orientation-horizontal { + .singlestage-tabs-list { + @apply h-8; + } + } + + .singlestage-tabs-orientation-vertical { + .singlestage-tabs-trigger { + @apply py-[calc(--spacing(1.25))]; + } + } + + .singlestage-tabs-content { + @apply text-xs/relaxed; + } +} diff --git a/singlestage/src/components/tabs/style/base/nova/tabs.css b/singlestage/src/components/tabs/style/base/nova/tabs.css new file mode 100644 index 00000000..a0ae01c6 --- /dev/null +++ b/singlestage/src/components/tabs/style/base/nova/tabs.css @@ -0,0 +1,63 @@ +@layer components1 { + .singlestage-tabs { + @apply gap-2; + } + + .singlestage-tabs-list { + @apply p-[3px] + rounded-lg; + } + + .singlestage-tabs-trigger { + @apply border + border-transparent + font-medium + gap-1.5 + px-1.5 + py-0.5 + rounded-md + text-sm; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-tabs-list-variant-default { + .singlestage-tabs-trigger { + &:active, + &[aria-selected="true"] { + @apply shadow-sm; + } + } + } + + .singlestage-tabs-list-variant-line { + @apply rounded-none; + + .singlestage-tabs-trigger { + &:active, + &[aria-selected="true"] { + @apply shadow-none; + } + } + } + + .singlestage-tabs-orientation-horizontal { + .singlestage-tabs-list { + @apply h-8; + } + } + + .singlestage-tabs-content { + @apply text-sm; + } +} diff --git a/singlestage/src/components/tabs/style/base/sera/tabs.css b/singlestage/src/components/tabs/style/base/sera/tabs.css new file mode 100644 index 00000000..68054666 --- /dev/null +++ b/singlestage/src/components/tabs/style/base/sera/tabs.css @@ -0,0 +1,50 @@ +@layer components1 { + .singlestage-tabs { + @apply gap-2; + } + + .singlestage-tabs-list { + @apply p-1; + } + + .singlestage-tabs-trigger { + @apply border + border-transparent + font-semibold + gap-2 + px-4 + py-1.5 + text-xs + tracking-wider + uppercase; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2.5; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-tabs-orientation-horizontal { + .singlestage-tabs-list { + @apply h-10; + } + } + + .singlestage-tabs-orientation-vertical { + .singlestage-tabs-trigger { + @apply px-4 + py-2; + } + } + + .singlestage-tabs-content { + @apply text-sm; + } +} diff --git a/singlestage/src/components/tabs/style/base/vega/tabs.css b/singlestage/src/components/tabs/style/base/vega/tabs.css new file mode 100644 index 00000000..6c09a750 --- /dev/null +++ b/singlestage/src/components/tabs/style/base/vega/tabs.css @@ -0,0 +1,63 @@ +@layer components1 { + .singlestage-tabs { + @apply gap-2; + } + + .singlestage-tabs-list { + @apply p-[3px] + rounded-lg; + } + + .singlestage-tabs-trigger { + @apply border + border-transparent + font-medium + gap-1.5 + px-2 + py-1 + rounded-md + text-sm; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-tabs-list-variant-default { + .singlestage-tabs-trigger { + &:active, + &[aria-selected="true"] { + @apply shadow-sm; + } + } + } + + .singlestage-tabs-list-variant-line { + @apply rounded-none; + + .singlestage-tabs-trigger { + &:active, + &[aria-selected="true"] { + @apply shadow-none; + } + } + } + + .singlestage-tabs-orientation-horizontal { + .singlestage-tabs-list { + @apply h-9; + } + } + + .singlestage-tabs-content { + @apply text-sm; + } +} diff --git a/singlestage/src/components/tabs/style/tabs.css b/singlestage/src/components/tabs/style/tabs.css new file mode 100644 index 00000000..0e4caf6a --- /dev/null +++ b/singlestage/src/components/tabs/style/tabs.css @@ -0,0 +1,116 @@ +@layer components2 { + .singlestage-tabs { + @apply flex; + } + + .singlestage-tabs-list { + @apply inline-flex + items-center + justify-center + text-muted-foreground + w-fit; + } + + .singlestage-tabs-trigger { + @apply flex-1 + h-[calc(100%-1px)] + inline-flex + items-center + justify-center + relative + text-foreground/60 + transition-all + whitespace-nowrap; + + &:active, + &[aria-selected="true"] { + @apply bg-background + text-foreground; + } + + &:disabled, + &[aria-disabled="true"] { + @apply opacity-50 + pointer-events-none; + } + + &:focus-visible { + @apply border-ring + outline-1 + outline-ring + ring-[3px] + ring-ring/50; + } + + &:hover { + @apply text-foreground; + } + + svg { + @apply pointer-events-none + shrink-0; + } + } + + .singlestage-tabs-content { + @apply flex-1 + outline-none; + } + + .singlestage-tabs-list-variant-default { + @apply bg-muted; + } + + .singlestage-tabs-list-variant-line { + @apply bg-transparent + gap-1; + + .singlestage-tabs-trigger { + @apply bg-transparent; + + &::after { + @apply absolute + bg-foreground + content-[''] + opacity-0 + transition-opacity; + } + + &:active, + &[aria-selected="true"] { + @apply bg-transparent; + + &::after { + @apply opacity-100; + } + } + } + } + + .singlestage-tabs-orientation-horizontal { + @apply flex-col; + + .singlestage-tabs-trigger::after { + @apply bottom-[-5px] + h-0.5 + inset-x-0; + } + } + + .singlestage-tabs-orientation-vertical { + .singlestage-tabs-list { + @apply flex-col + h-fit; + } + .singlestage-tabs-trigger { + @apply justify-start + w-full; + + &::after { + @apply -right-1 + inset-y-0 + w-0.5; + } + } + } +} diff --git a/singlestage/src/components/tabs/style/tabs_dark.css b/singlestage/src/components/tabs/style/tabs_dark.css new file mode 100644 index 00000000..b603952e --- /dev/null +++ b/singlestage/src/components/tabs/style/tabs_dark.css @@ -0,0 +1,26 @@ +@layer components2 { + .singlestage-tabs-trigger { + @apply text-muted-foreground; + + &:active, + &[aria-selected="true"] { + @apply bg-input/30 + border-input + text-foreground; + } + + &:hover { + @apply text-foreground; + } + } + + .singlestage-tabs-list-variant-line { + .singlestage-tabs-trigger { + &:active, + &[aria-selected="true"] { + @apply bg-transparent + border-transparent; + } + } + } +} diff --git a/singlestage/src/components/tabs/tabs.css b/singlestage/src/components/tabs/tabs.css deleted file mode 100644 index c30b60e2..00000000 --- a/singlestage/src/components/tabs/tabs.css +++ /dev/null @@ -1,58 +0,0 @@ -@layer components { - .singlestage-tabs { - @apply flex - flex-col - gap-2; - - [role="tablist"] { - @apply bg-muted - h-9 - inline-flex - items-center - justify-center - p-[3px] - rounded-lg - text-muted-foreground - w-fit; - - [role="tab"] { - @apply [&_svg:not([class*='size-'])]:size-4 - [&_svg]:pointer-events-none - [&_svg]:shrink-0 - border - border-transparent - disabled:opacity-50 - disabled:pointer-events-none - flex-1 - focus-visible:border-ring - focus-visible:outline-1 - focus-visible:outline-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - font-medium - gap-1.5 - h-[calc(100%_-_1px)] - inline-flex - items-center - justify-center - px-2 - py-1 - rounded-md - text-foreground - text-sm - transition-[color,box-shadow] - whitespace-nowrap; - - &[aria-selected="true"] { - @apply bg-background - shadow-sm; - } - } - } - - [role="tabpanel"] { - @apply flex-1 - outline-none; - } - } -} diff --git a/singlestage/src/components/tabs/tabs.rs b/singlestage/src/components/tabs/tabs.rs index e7503c7b..fa779cc2 100644 --- a/singlestage/src/components/tabs/tabs.rs +++ b/singlestage/src/components/tabs/tabs.rs @@ -7,6 +7,10 @@ use leptos::{context::Provider, prelude::*}; pub fn Tabs( children: Children, + /// Set which direction the tabs should flow. + /// Accepted values: "horizontal" | "vertical" + #[prop(optional, into)] + orientation: MaybeProp<String>, /// Reactive signal coupled to the value of the current selected tab #[prop(optional, into)] value: Reactive<String>, @@ -160,7 +164,16 @@ pub fn Tabs( view! { <div - class=move || { format!("singlestage-tabs {}", class.get().unwrap_or_default()) } + class=move || { + format!( + "singlestage-tabs {} {}", + match orientation.get().unwrap_or_default().as_str() { + "vertical" => "singlestage-tabs-orientation-vertical", + _ => "singlestage-tabs-orientation-horizontal", + }, + class.get().unwrap_or_default(), + ) + } {..global_attrs_1} {..global_attrs_2} diff --git a/singlestage/src/components/tabs/trigger.rs b/singlestage/src/components/tabs/trigger.rs index ce38a61e..b7d0bb03 100644 --- a/singlestage/src/components/tabs/trigger.rs +++ b/singlestage/src/components/tabs/trigger.rs @@ -1,4 +1,5 @@ use super::TabsContext; +use crate::Reactive; use leptos::prelude::*; /// The button that activates its associated content. @@ -23,7 +24,7 @@ pub fn TabsTrigger( commandfor: MaybeProp<String>, /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, @@ -167,12 +168,21 @@ pub fn TabsTrigger( tabs.value.set(value.get_untracked().unwrap_or_default()) } + 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() contenteditable=move || contenteditable.get() dir=move || dir.get() draggable=move || draggable.get() @@ -224,14 +234,12 @@ pub fn TabsTrigger( view! { <button - aria-controls=move || format!("{}-panel", value.get().unwrap_or_default()) - aria-selected=move || { - if let Some(value) = value.get() { - if value == tabs.value.get() { "true" } else { "false" } - } else { - "false" - } - } + aria_controls=move || format!("{}-panel", value.get().unwrap_or_default()) + aria_disabled=move || { if disabled.get() { Some("true") } else { None } } + aria_selected=move || { if tab_selected() { "true" } else { "false" } } + class=move || format!("singlestage-tabs-trigger {}", class.get().unwrap_or_default()) + disabled + prop:disabled=move || disabled.get() id=move || format!("{}-tab", value.get().unwrap_or_default()) on:click=move |_| { if let Some(value) = value.get_untracked() { @@ -239,7 +247,7 @@ pub fn TabsTrigger( } } role="tab" - tabindex="0" + tabindex=move || { if tab_selected() { "0" } else { "-1" } } type="button" {..global_attrs_1} diff --git a/singlestage/src/components/textarea/mod.rs b/singlestage/src/components/textarea/mod.rs index 56036896..aa1632aa 100644 --- a/singlestage/src/components/textarea/mod.rs +++ b/singlestage/src/components/textarea/mod.rs @@ -1,4 +1,4 @@ -use crate::{FieldContext, FieldLabel, Reactive}; +use crate::{FieldContext, InputGroupContext, Label, Reactive}; use leptos::prelude::*; /// Creates a textarea that takes children as a default value. @@ -12,10 +12,10 @@ pub fn Textarea( default: MaybeProp<String>, /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Toggle invalid appearance. #[prop(optional, into)] - invalid: MaybeProp<bool>, + invalid: Reactive<bool>, /// The reactive value signal of this input. Also sets initial `default` value, but doesn't /// update it. #[prop(optional, into)] @@ -185,31 +185,35 @@ pub fn Textarea( } }; - let on_input = move |ev| { - value.set(event_target_value(&ev)); - }; - Effect::new(move || { - if let Some(textarea) = textarea_ref.get_untracked() - && let Some(default_value) = default.get() + if let Some(val) = default.get() + && let Some(input) = textarea_ref.get() { - let _ = textarea.set_default_value(&default_value); + let _ = input.set_default_value(&val); + + value.set(val); } }); - Effect::new(move || { - if let Some(textarea) = textarea_ref.get_untracked() - && let Some(disabled) = disabled.get() - { - textarea.set_disabled(disabled) + let disabled: Reactive<bool> = { + if let Some(field) = use_context::<FieldContext>() { + field.disabled + } else { + disabled } - }); + }; - Effect::new(move || { - if let Some(textarea) = textarea_ref.get_untracked() { - textarea.set_value(&value.get()); + let invalid: Reactive<bool> = { + if let Some(field) = use_context::<FieldContext>() { + field.invalid + } else { + invalid } - }); + }; + + let on_input = move |ev| { + value.set(event_target_value(&ev)); + }; let global_attrs_1 = view! { <{..} @@ -251,7 +255,10 @@ pub fn Textarea( let input_id = uuid::Uuid::new_v4(); let label_id = uuid::Uuid::new_v4(); - let has_children = children.is_some(); + + let has_children: bool = children.is_some(); + let in_field: bool = use_context::<FieldContext>().is_some(); + let in_input_group: bool = use_context::<InputGroupContext>().is_some(); let textarea_attrs = view! { <{..} @@ -263,12 +270,8 @@ pub fn Textarea( None } } - aria-invalid=move || { - match invalid.get() { - Some(true) => Some("true"), - _ => None, - } - } + aria_disabled=move || { if disabled.get() { Some("true") } else { None } } + aria_invalid=move || { if invalid.get() { Some("true") } else { None } } aria_labelledby=move || { if let Some(field) = use_context::<FieldContext>() { Some(field.label_id.get()) @@ -280,10 +283,34 @@ pub fn Textarea( } autocomplete=move || autocomplete.get() cols=move || cols.get() - class=move || { format!("singlestage-textarea {}", class.get().unwrap_or_default()) } + class=move || { + format!( + "singlestage-textarea{} {}", + if in_input_group { + " singlestage-input-group-textarea singlestage-input-group-control" + } else { + "" + }, + class.get().unwrap_or_default(), + ) + } dirname=move || dirname.get() disabled=move || disabled.get() + prop:disabled=move || disabled.get() form=move || form.get() + id={if let Some(field) = use_context::<FieldContext>() { + if let Some(id) = id.get_untracked() { + field.input_id.set(id.clone()); + Some(id) + } else { + field.input_id.set(input_id.to_string()); + Some(input_id.to_string()) + } + } else if let Some(id) = id.get_untracked() { + Some(id) + } else { + if has_children { Some(input_id.to_string()) } else { None } + }} maxlength=move || maxlength.get() minlength=move || minlength.get() name=move || name.get() @@ -293,77 +320,38 @@ pub fn Textarea( readonly=move || readonly.get() required=move || required.get() rows=move || rows.get() + prop:value=move || value.get() wrap=move || wrap.get() /> }; if let Some(children) = children { view! { - {if use_context::<FieldContext>().is_some() { - view! { - <FieldLabel - class=class.get_untracked() - label_for=id.get_untracked().unwrap_or(input_id.to_string()) - > - {children()} - </FieldLabel> - } - .into_any() - } else { - view! { - <label - class=move || { - format!( - "singlestage-label singlestage-textarea-label {}", - class.get().unwrap_or_default(), - ) - } - for=move || id.get().unwrap_or(input_id.to_string()) - id=label_id.to_string() - > - {children()} - </label> - } - .into_any() - }} - <textarea - id=move || id.get().unwrap_or(input_id.to_string()) - {..global_attrs_1} - {..global_attrs_2} - {..textarea_attrs} + <Label + class=format!( + "{}{}", + if in_field { "" } else { "singlestage-textarea-label " }, + class.get_untracked().unwrap_or_default(), + ) + id=label_id.to_string() + label_for=id.get_untracked().unwrap_or(input_id.to_string()) > - {if let Some(default) = default.get_untracked() { - default - } else { - value.get_untracked() - }} + {children()} + </Label> + <textarea {..global_attrs_1} {..global_attrs_2} {..textarea_attrs}> + {default.get_untracked().unwrap_or(value.get_untracked())} </textarea> } .into_any() } else { view! { <textarea - id={if let Some(field) = use_context::<FieldContext>() { - if let Some(id) = id.get_untracked() { - field.input_id.set(id.clone()); - Some(id) - } else { - field.input_id.set(input_id.to_string()); - Some(input_id.to_string()) - } - } else { - id.get_untracked() - }} {..global_attrs_1} {..global_attrs_2} {..textarea_attrs} > - {if let Some(default) = default.get_untracked() { - default - } else { - value.get_untracked() - }} + {default.get_untracked().unwrap_or(value.get_untracked())} </textarea> } .into_any() diff --git a/singlestage/src/components/textarea/style/base/luma/textarea.css b/singlestage/src/components/textarea/style/base/luma/textarea.css new file mode 100644 index 00000000..d9227454 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/luma/textarea.css @@ -0,0 +1,29 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-input/50 + border + border-transparent + dark:aria-invalid:border-destructive/50 + dark:aria-invalid:ring-destructive/40 + md:text-sm + px-3 + py-3 + resize-none + rounded-2xl + text-base + transition-[color,box-shadow,background-color]; + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/luma/textarea_dark.css b/singlestage/src/components/textarea/style/base/luma/textarea_dark.css new file mode 100644 index 00000000..692e83e7 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/luma/textarea_dark.css @@ -0,0 +1,9 @@ +@layer components1 { + .singlestage-textarea { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/lyra/textarea.css b/singlestage/src/components/textarea/style/base/lyra/textarea.css new file mode 100644 index 00000000..c0d363eb --- /dev/null +++ b/singlestage/src/components/textarea/style/base/lyra/textarea.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-transparent + border + border-input + md:text-xs + px-2.5 + py-2 + rounded-none + text-xs + transition-colors; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-input/50; + } + + &:focus-visible { + @apply border-ring + ring-1 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-1 + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/lyra/textarea_dark.css b/singlestage/src/components/textarea/style/base/lyra/textarea_dark.css new file mode 100644 index 00000000..32a2c94c --- /dev/null +++ b/singlestage/src/components/textarea/style/base/lyra/textarea_dark.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-input/30; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-input/80; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/maia/textarea.css b/singlestage/src/components/textarea/style/base/maia/textarea.css new file mode 100644 index 00000000..cb9cb8f5 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/maia/textarea.css @@ -0,0 +1,27 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-input/30 + border + border-input + md:text-sm + px-3 + py-3 + resize-none + rounded-xl + text-base + transition-colors; + + &:focus-visible { + @apply border-ring + ring-[3px] + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-[3px] + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/maia/textarea_dark.css b/singlestage/src/components/textarea/style/base/maia/textarea_dark.css new file mode 100644 index 00000000..ea7eb7e6 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/maia/textarea_dark.css @@ -0,0 +1,7 @@ +@layer components1 { + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } +} diff --git a/singlestage/src/components/textarea/style/base/mira/textarea.css b/singlestage/src/components/textarea/style/base/mira/textarea.css new file mode 100644 index 00000000..2ffc626c --- /dev/null +++ b/singlestage/src/components/textarea/style/base/mira/textarea.css @@ -0,0 +1,27 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-input/20 + border + border-input + md:text-xs/relaxed + px-2 + py-2 + resize-none + rounded-md + text-sm + transition-colors; + + &:focus-visible { + @apply border-ring + ring-2 + ring-ring/30; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-2 + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/mira/textarea_dark.css b/singlestage/src/components/textarea/style/base/mira/textarea_dark.css new file mode 100644 index 00000000..ea9ae693 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/mira/textarea_dark.css @@ -0,0 +1,11 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-input/30; + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/nova/textarea.css b/singlestage/src/components/textarea/style/base/nova/textarea.css new file mode 100644 index 00000000..a8394aa0 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/nova/textarea.css @@ -0,0 +1,31 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-transparent + border + border-input + md:text-sm + px-2.5 + py-2 + rounded-lg + text-base + transition-colors; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-input/50; + } + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/nova/textarea_dark.css b/singlestage/src/components/textarea/style/base/nova/textarea_dark.css new file mode 100644 index 00000000..32a2c94c --- /dev/null +++ b/singlestage/src/components/textarea/style/base/nova/textarea_dark.css @@ -0,0 +1,16 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-input/30; + + &:disabled, + &[aria-disabled="true"] { + @apply bg-input/80; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/sera/textarea.css b/singlestage/src/components/textarea/style/base/sera/textarea.css new file mode 100644 index 00000000..657a1633 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/sera/textarea.css @@ -0,0 +1,24 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-transparent + border + border-b-input + border-transparent + md:text-sm + px-0 + py-3 + resize-none + rounded-none + text-base + transition-[color,border-color]; + + &:focus-visible { + @apply border-b-ring; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-b-destructive; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/sera/textarea_dark.css b/singlestage/src/components/textarea/style/base/sera/textarea_dark.css new file mode 100644 index 00000000..589f2c01 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/sera/textarea_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-textarea { + &:invalid, + &[aria-invalid="true"] { + @apply border-b-destructive/50; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/vega/textarea.css b/singlestage/src/components/textarea/style/base/vega/textarea.css new file mode 100644 index 00000000..69689c10 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/vega/textarea.css @@ -0,0 +1,27 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-transparent + border + border-input + md:text-sm + px-2.5 + py-2 + rounded-md + shadow-xs + text-base + transition-[color,box-shadow]; + + &:focus-visible { + @apply border-ring + ring-3 + ring-ring/50; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-3 + ring-destructive/20; + } + } +} diff --git a/singlestage/src/components/textarea/style/base/vega/textarea_dark.css b/singlestage/src/components/textarea/style/base/vega/textarea_dark.css new file mode 100644 index 00000000..ea9ae693 --- /dev/null +++ b/singlestage/src/components/textarea/style/base/vega/textarea_dark.css @@ -0,0 +1,11 @@ +@layer components1 { + .singlestage-textarea { + @apply bg-input/30; + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive/50 + ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/textarea/style/textarea.css b/singlestage/src/components/textarea/style/textarea.css new file mode 100644 index 00000000..cb6e6581 --- /dev/null +++ b/singlestage/src/components/textarea/style/textarea.css @@ -0,0 +1,18 @@ +@layer components { + .singlestage-textarea { + @apply field-sizing-content + flex + min-h-16 + outline-none + w-full; + + &:disabled { + @apply cursor-not-allowed + opacity-50; + } + + ::placeholder { + @apply text-muted-foreground; + } + } +} diff --git a/singlestage/src/components/textarea/textarea.css b/singlestage/src/components/textarea/textarea.css deleted file mode 100644 index 87f7135f..00000000 --- a/singlestage/src/components/textarea/textarea.css +++ /dev/null @@ -1,31 +0,0 @@ -@layer components { - .singlestage-textarea { - @apply aria-invalid:border-destructive - aria-invalid:ring-destructive/20 - bg-transparent - border - border-input - disabled:cursor-not-allowed - disabled:opacity-50 - field-sizing-content - flex - focus-visible:border-ring - focus-visible:ring-[3px] - focus-visible:ring-ring/50 - md:text-sm - min-h-16 - outline-none - placeholder:text-muted-foreground - px-3 - py-2 - rounded-md - shadow-xs - text-base - transition-[color,box-shadow] - w-full; - } - - .singlestage-textarea-label { - @apply my-3; - } -} diff --git a/singlestage/src/components/theme_provider/Theme/mod.rs b/singlestage/src/components/theme_provider/Theme/mod.rs index 8e62427d..a3b26bee 100644 --- a/singlestage/src/components/theme_provider/Theme/mod.rs +++ b/singlestage/src/components/theme_provider/Theme/mod.rs @@ -1,8 +1,8 @@ mod amber; mod blue; -mod default; mod lime; mod mono; +mod neutral; mod orange; mod purple; mod red; @@ -14,9 +14,9 @@ mod yellow; pub use amber::*; pub use blue::*; -pub use default::*; pub use lime::*; pub use mono::*; +pub use neutral::*; pub use orange::*; pub use purple::*; pub use red::*; diff --git a/singlestage/src/components/theme_provider/Theme/default.rs b/singlestage/src/components/theme_provider/Theme/neutral.rs similarity index 98% rename from singlestage/src/components/theme_provider/Theme/default.rs rename to singlestage/src/components/theme_provider/Theme/neutral.rs index 0132c44b..20ebb1b0 100644 --- a/singlestage/src/components/theme_provider/Theme/default.rs +++ b/singlestage/src/components/theme_provider/Theme/neutral.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; #[allow(non_upper_case_globals)] /// Uses tailwind neutral and blue palettes -pub const Default: Theme = Theme { +pub const Neutral: Theme = Theme { common: Cow::Borrowed( r#" --color-blue-50: oklch(97% 0.014 254.604); diff --git a/singlestage/src/components/theme_provider/ThemeBase.rs b/singlestage/src/components/theme_provider/ThemeBase.rs new file mode 100644 index 00000000..6243466b --- /dev/null +++ b/singlestage/src/components/theme_provider/ThemeBase.rs @@ -0,0 +1,81 @@ +#[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 std::borrow::Cow; + +#[cfg(not(feature = "islands"))] +#[derive(Clone)] +pub struct ThemeBase { + pub dark: Cow<'static, str>, + pub light: Cow<'static, str>, +} + +#[cfg(feature = "islands")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "islands")] +#[derive(Clone, Deserialize, Serialize)] +pub struct ThemeBase { + pub dark: Cow<'static, str>, + pub light: Cow<'static, str>, +} + +#[allow(non_upper_case_globals)] +#[cfg(feature = "style_luma")] +pub const Luma: ThemeBase = ThemeBase { + dark: Cow::Borrowed(CSS_LUMA_DARK), + light: Cow::Borrowed(CSS_LUMA), +}; + +#[allow(non_upper_case_globals)] +#[cfg(feature = "style_lyra")] +pub const Lyra: ThemeBase = ThemeBase { + dark: Cow::Borrowed(CSS_LYRA_DARK), + light: Cow::Borrowed(CSS_LYRA), +}; + +#[allow(non_upper_case_globals)] +#[cfg(feature = "style_maia")] +pub const Maia: ThemeBase = ThemeBase { + dark: Cow::Borrowed(CSS_MAIA_DARK), + light: Cow::Borrowed(CSS_MAIA), +}; + +#[allow(non_upper_case_globals)] +#[cfg(feature = "style_mira")] +pub const Mira: ThemeBase = ThemeBase { + dark: Cow::Borrowed(CSS_MIRA_DARK), + light: Cow::Borrowed(CSS_MIRA), +}; + +#[allow(non_upper_case_globals)] +#[cfg(feature = "style_nova")] +pub const Nova: ThemeBase = ThemeBase { + dark: Cow::Borrowed(CSS_NOVA_DARK), + light: Cow::Borrowed(CSS_NOVA), +}; + +#[allow(non_upper_case_globals)] +#[cfg(feature = "style_sera")] +pub const Sera: ThemeBase = ThemeBase { + dark: Cow::Borrowed(CSS_SERA_DARK), + light: Cow::Borrowed(CSS_SERA), +}; + +#[allow(non_upper_case_globals)] +#[cfg(feature = "style_vega")] +pub const Vega: ThemeBase = ThemeBase { + dark: Cow::Borrowed(CSS_VEGA_DARK), + light: Cow::Borrowed(CSS_VEGA), +}; diff --git a/singlestage/src/components/theme_provider/main.css b/singlestage/src/components/theme_provider/main.css index a9e4f9f9..dc2261a5 100644 --- a/singlestage/src/components/theme_provider/main.css +++ b/singlestage/src/components/theme_provider/main.css @@ -1,4 +1,8 @@ -@import "tailwindcss"; +@layer theme, base, components, components1, components2, utilities; + +@import "tailwindcss/theme.css" layer(theme); +@import "tailwindcss/preflight.css" layer(base); +@import "tailwindcss/utilities.css" layer(utilities); @theme inline { --radius-sm: calc(var(--radius) - 4px); @@ -87,6 +91,7 @@ --accent: oklch(0.97 0 0); --accent-foreground: oklch(0.205 0 0); --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.97 0.01 17); --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); --sidebar: oklch(0.985 0 0); @@ -97,11 +102,13 @@ } * { - @apply border-border outline-ring/50; + @apply border-border + outline-ring/50; } ::selection { - @apply bg-selection text-selection-foreground; + @apply bg-selection + text-selection-foreground; } html { @@ -109,26 +116,16 @@ } body { - @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; + @apply antialiased + bg-background + overscroll-none + text-foreground; } a:active, button:active { - @apply opacity-60 md:opacity-100; + @apply opacity-60 + md:opacity-100; } .singlestage-scrollbar { @@ -149,3 +146,15 @@ } } } + +/* /* Animation */ + +@layer base { + :root { + --transform-origin: 0% 339px; + --transform-scale: scale3d(0.95, 0.95, 0.95); + --transform-translate-step: 0.25rem; + --transition-duration: 0.15s; + --transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } +} diff --git a/singlestage/src/components/theme_provider/main_dark.css b/singlestage/src/components/theme_provider/main_dark.css new file mode 100644 index 00000000..4709aaaa --- /dev/null +++ b/singlestage/src/components/theme_provider/main_dark.css @@ -0,0 +1,35 @@ +@layer base { + /* Base stuff dark overrides */ + :root { + --scrollbar-thumb: rgba(255, 255, 255, 0.3); + color-scheme: dark; + + --surface: oklch(0.2 0 0); + --code-highlight: oklch(0.27 0 0); + --code-number: oklch(0.72 0 0); + --selection: oklch(0.922 0 0); + --selection-foreground: oklch(0.205 0 0); + + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.371 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --destructive-foreground: oklch(0.58 0.22 27); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + } +} diff --git a/singlestage/src/components/theme_provider/mod.rs b/singlestage/src/components/theme_provider/mod.rs index f46a81f2..c45d2436 100644 --- a/singlestage/src/components/theme_provider/mod.rs +++ b/singlestage/src/components/theme_provider/mod.rs @@ -1,516 +1,48 @@ +mod mode; +pub use mode::*; + #[allow(non_snake_case)] pub mod Theme; -mod mode; -use crate::CSS; -pub use mode::*; +#[allow(non_snake_case)] +pub mod ThemeBase; + +use crate::{CSS, CSS_DARK}; use leptos::prelude::*; use leptos_meta::Style; -// TODO: CSS should be in an actual css file and run through tailwind somehow. This works for now - #[derive(Clone)] pub struct ThemeProviderContext { - /// Set the theme's light/dark mode behavior. Defaults to `Mode::Auto`. + /// Set which theme base to use + pub base: RwSignal<ThemeBase::ThemeBase>, + /// Set the theme's light/dark mode behavior pub mode: RwSignal<Mode>, - /// The get/set/update the current theme in use. + /// Set the current theme pub theme: RwSignal<Theme::Theme>, } -// TODO: Don't have css here -static DARK_COMMON: &str = r#" - --scrollbar-thumb: rgba(255, 255, 255, 0.3); - color-scheme: dark; - - --surface: oklch(0.2 0 0); - --code-highlight: oklch(0.27 0 0); - --code-number: oklch(0.72 0 0); - --selection: oklch(0.922 0 0); - --selection-foreground: oklch(0.205 0 0); - - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.269 0 0); - --popover-foreground: oklch(0.985 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.371 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); -"#; - -// TODO: Don't have css here -// TODO: This code is dead and here for future reference -static _DARK_OVERRIDES_TW: &str = r#" -@layer components { - .singlestage-input-group { - @apply dark:bg-input/30; - - .singlestage-input, - .singlestage-textarea { - @apply !bg-transparent; - } - } - - .singlestage-input-group:has([aria-invalid="true"]) { - @apply dark:ring-destructive/40; - } - - .singlestage-field-button:has(input:checked) { - @apply bg-primary/10; - } - - .singlestage-carousel-previous, - .singlestage-carousel-next { - @apply aria-invalid:ring-destructive/40 - bg-input/30 - border-input - hover:bg-input/50; - } - - .singlestage-btn-primary { - .singlestage-input { - @apply border-primary-foreground/30!; - } - } - - .singlestage-textarea { - @apply aria-invalid:ring-destructive/40 - bg-input/30; - } - - select.singlestage-select { - @apply aria-invalid:ring-destructive/40 - bg-input/30 - hover:bg-input/50; - } - - .singlestage-input[type="radio"] { - @apply aria-invalid:ring-destructive/40 - bg-input/30; - } - - .singlestage-input[type="color"], - .singlestage-input[type="date"], - .singlestage-input[type="datetime-local"], - .singlestage-input[type="email"], - .singlestage-input[type="file"], - .singlestage-input[type="month"], - .singlestage-input[type="number"], - .singlestage-input[type="password"], - .singlestage-input[type="search"], - .singlestage-input[type="tel"], - .singlestage-input[type="text"], - .singlestage-input[type="time"], - .singlestage-input[type="url"], - .singlestage-input[type="week"] { - @apply bg-input/30 - aria-invalid:ring-destructive/40; - } - - .singlestage-input[type="checkbox"]:not([role="switch"]) { - @apply aria-invalid:ring-destructive/40 - bg-input/30 - checked:bg-primary; - } - - .singlestage-tabs { - [role="tablist"] { - [role="tab"] { - @apply text-muted-foreground; - &[aria-selected="true"] { - @apply bg-input/30 - border-input - text-foreground; - } - } - } - } - - .singlestage-input[type="checkbox"][role="switch"] { - @apply before:bg-foreground - bg-input/80 - checked:before:bg-primary-foreground - checked:bg-primary; - } - - .singlestage-badge-primary, - .singlestage-badge-secondary, - .singlestage-badge-destructive, - .singlestage-badge-outline { - @apply aria-invalid:ring-destructive/40; - } - - .singlestage-badge-destructive { - @apply bg-destructive/60 - focus-visible:ring-destructive/40; - } - - .singlestage-btn-primary, - .singlestage-btn-secondary, - .singlestage-btn-outline, - .singlestage-btn-ghost, - .singlestage-btn-link, - .singlestage-btn-destructive, - .singlestage-btn-icon { - @apply aria-invalid:ring-destructive/40; - } - - .singlestage-btn-outline { - @apply bg-input/30 - border-input; - - &:hover, - &[aria-pressed="true"] { - @apply bg-accent/50; - } - } - - .singlestage-btn-ghost { - &:hover, - &[aria-pressed="true"] { - @apply bg-accent/50; - } - .singlestage-btn-destructive { - @apply focus-visible:ring-destructive/40 - bg-destructive/60; - - &:hover, - &[aria-pressed="true"] { - @apply bg-destructive/50; - } - } - } -}"#; - -static DARK_OVERRIDES: &str = r#"@layer components { - .singlestage-input-group { - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 30%, transparent); - } - - .singlestage-input, - .singlestage-textarea { - background-color: transparent !important; - } - } - - .singlestage-input-group:has([aria-invalid="true"]) { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } - } - - .singlestage-field-button:has(input:checked) { - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--primary) 10%, transparent); - } - } - - .singlestage-carousel-previous, .singlestage-carousel-next { - border-color: var(--input); - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 30%, transparent); - } - &:hover { - @media (hover: hover) { - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 50%, transparent); - } - } - } - &[aria-invalid="true"] { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix(in oklab, var(--destructive) 40%, transparent); - } - } - } - - .singlestage-btn-primary { - .singlestage-input[type="checkbox"], - .singlestage-input[type="radio"] { - @supports (color: color-mix(in lab, red, red)) { - border-color: color-mix(in oklab, var(--primary-foreground) 30%, transparent) !important; - } - } - } - - .singlestage-textarea { - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 30%, transparent); - } - &[aria-invalid="true"] { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } - } - } - select.singlestage-select { - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 30%, transparent); - } - &:hover { - @media (hover: hover) { - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 50%, transparent); - } - } - } - &[aria-invalid="true"] { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } - } - } - .singlestage-input[type="radio"] { - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 30%, transparent); - } - &[aria-invalid="true"] { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } - } - } - .singlestage-input[type="color"], - .singlestage-input[type="date"], - .singlestage-input[type="datetime-local"], - .singlestage-input[type="email"], - .singlestage-input[type="file"], - .singlestage-input[type="month"], - .singlestage-input[type="number"], - .singlestage-input[type="password"], - .singlestage-input[type="search"], - .singlestage-input[type="tel"], - .singlestage-input[type="time"], - .singlestage-input[type="url"], - .singlestage-input[type="week"], - .singlestage-input[type="text"] { - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 30%, transparent); - } - &[aria-invalid="true"] { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } - } - } - .singlestage-input[type="checkbox"]:not([role="switch"]) { - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 30%, transparent); - } - &:checked { - background-color: var(--primary); - } - &[aria-invalid="true"] { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } - } - } - .singlestage-tabs { - [role="tablist"] { - [role="tab"] { - color: var(--muted-foreground); - &[aria-selected="true"] { - border-color: var(--input); - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix( - in oklab, - var(--input) 30%, - transparent - ); - } - color: var(--foreground); - } - } - } - } - .singlestage-input[type="checkbox"][role="switch"] { - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 80%, transparent); - } - &::before { - content: var(--tw-content); - background-color: var(--foreground); - } - &:checked { - background-color: var(--primary); - } - &:checked { - &::before { - content: var(--tw-content); - background-color: var(--primary-foreground); - } - } - } - .singlestage-badge-primary, - .singlestage-badge-secondary, - .singlestage-badge-destructive, - .singlestage-badge-outline { - &[aria-invalid="true"] { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } - } - } - .singlestage-badge-destructive { - background-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix( - in oklab, - var(--destructive) 60%, - transparent - ); - } - &:focus-visible { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } - } - } - .singlestage-btn-primary, - .singlestage-btn-secondary, - .singlestage-btn-outline, - .singlestage-btn-ghost, - .singlestage-btn-link, - .singlestage-btn-destructive, - .singlestage-btn-icon { - &[aria-invalid="true"] { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } +/// Provides nonce support for inline styles. Returns `None` if the `nonce` +/// feature is not enabled or no nonce is in context. +fn request_nonce() -> Option<String> { + #[cfg(feature = "nonce")] + { + leptos::nonce::use_nonce().map(|n| n.to_string()) } - } - .singlestage-btn-outline { - border-color: var(--input); - background-color: var(--input); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--input) 30%, transparent); + #[cfg(not(feature = "nonce"))] + { + None } - &:hover, - &[aria-pressed="true"] { - background-color: var(--accent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--accent) 50%, transparent); - } - } - } - .singlestage-btn-ghost { - &:hover, - &[aria-pressed="true"] { - background-color: var(--accent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--accent) 50%, transparent); - } - } - } - .singlestage-btn-destructive { - background-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix( - in oklab, - var(--destructive) 60%, - transparent - ); - } - &:focus-visible { - --tw-ring-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - --tw-ring-color: color-mix( - in oklab, - var(--destructive) 40%, - transparent - ); - } - } - &:hover, - &[aria-pressed="true"] { - background-color: var(--destructive); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix( - in oklab, - var(--destructive) 50%, - transparent - ); - } - } - } -}"#; +} /// Provides theme support to children. Note: Setting `mode` and `theme` here are only used for /// initial values. Updates should be done via `ThemeProviderContext`. #[component] pub fn ThemeProviderInner( children: Children, + /// Set the initial theme base Defaults to ThemeBase::Vega. + #[prop(optional, into)] + base: MaybeProp<ThemeBase::ThemeBase>, /// Set the initial light/dark mode behavior. Defaults to `auto`/`Mode::Auto`. /// /// Accepted values: `auto` | `dark` | `light` or a `Mode` @@ -521,36 +53,52 @@ pub fn ThemeProviderInner( theme: MaybeProp<Theme::Theme>, ) -> impl IntoView { let mode = RwSignal::<Mode>::new(mode.get_untracked().unwrap_or_default().into()); - let theme = RwSignal::new(theme.get_untracked().unwrap_or(Theme::Default)); + let theme = RwSignal::new(theme.get_untracked().unwrap_or(Theme::Neutral)); + let base = RwSignal::new(base.get_untracked().unwrap_or(ThemeBase::Vega)); - let context = ThemeProviderContext { theme, mode }; + let context = ThemeProviderContext { base, mode, theme }; provide_context(context); + // TODO: Consider slicing up the base theme and merging everything to reduce css + // bloat/duplication view! { <Style id="singlestage">{CSS}</Style> <style - id="theme" + id="theme-base" + nonce=request_nonce() inner_html=move || { - let theme = theme.get(); match mode.get() { Mode::Dark => { + format!("{}{}", base.get().light.to_string(), base.get().dark.to_string()) + } + Mode::Light => base.get().light.to_string(), + Mode::Auto => { format!( - ":root{{ {} {} {}}}\n{}\n", - DARK_COMMON, - theme.common, - theme.dark, - DARK_OVERRIDES, + "{}\n\n@media (prefers-color-scheme: dark) {{{}}}", + base.get().light.to_string(), + base.get().dark.to_string(), ) } + } + } + ></style> + <style + id="theme" + nonce=request_nonce() + inner_html=move || { + let theme = theme.get(); + match mode.get() { + Mode::Dark => { + format!(":root{{ {} {}}}\n{}\n", theme.common, theme.dark, CSS_DARK) + } Mode::Light => format!(":root{{ {} {}}}\n", theme.common, theme.light), Mode::Auto => { format!( - ":root{{ {} {}}}\n\n@media (prefers-color-scheme: dark) {{\n :root {{ {} {} }}\n{}}}", + ":root{{ {} {}}}\n\n@media (prefers-color-scheme: dark) {{{}\n :root {{ {} }}}}", theme.common, theme.light, - DARK_COMMON, + CSS_DARK, theme.dark, - DARK_OVERRIDES, ) } } @@ -598,7 +146,7 @@ pub fn ThemeProvider( provide_meta_context(); view! { <ThemeProviderInner - theme=theme.unwrap_or(Theme::Default) + theme=theme.unwrap_or(Theme::Neutral) mode=mode.unwrap_or("auto".into()) > {children()} diff --git a/singlestage/src/components/toggle/mod.rs b/singlestage/src/components/toggle/mod.rs index 18a56287..77418a44 100644 --- a/singlestage/src/components/toggle/mod.rs +++ b/singlestage/src/components/toggle/mod.rs @@ -1,4 +1,4 @@ -use crate::Reactive; +use crate::{FieldContext, Reactive}; use leptos::prelude::*; /// A two-state button that toggles between on and off. @@ -8,13 +8,13 @@ pub fn Toggle( // TOGGLE ATTRIBUTES // + /// Reactive signal coupled to the toggle's pressed state. + #[prop(optional, into)] + pressed: Reactive<bool>, /// The size of the toggle. Leave this empty for the default size. /// Sizes: sm / small | lg / large #[prop(optional, into)] size: MaybeProp<String>, - /// Reactive signal coupled to the toggle's pressed state. - #[prop(optional, into)] - pressed: Reactive<bool>, /// The display variant of the toggle. Leave this empty for the default variant. /// Variants: outline #[prop(optional, into)] @@ -33,7 +33,7 @@ pub fn Toggle( commandfor: MaybeProp<String>, /// Toggle whether or not the input is disabled. #[prop(optional, into)] - disabled: MaybeProp<bool>, + disabled: Reactive<bool>, /// Associate this element with a form element that may not be its parent by its `id`. #[prop(optional, into)] form: MaybeProp<String>, @@ -78,6 +78,12 @@ pub fn Toggle( #[prop(optional, into)] value: MaybeProp<String>, + // ARIA ATTRIBUTES + // + /// Defines an accessible string value that can be used to name an element. + #[prop(optional, into)] + aria_label: MaybeProp<String>, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -182,10 +188,17 @@ pub fn Toggle( /// Defines localization behavior for the element. #[prop(optional, into)] translate: MaybeProp<String>, - /// Defines an accessible string value that can be used to name an element. - #[prop(optional, into)] - aria_label: MaybeProp<String>, ) -> impl IntoView { + let update_disabled = move || { + if let Some(field) = use_context::<FieldContext>() { + let is_disabled = field.disabled.get(); + disabled.set(is_disabled); + is_disabled + } else { + disabled.get() + } + }; + let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -204,6 +217,7 @@ pub fn Toggle( itemid=move || itemid.get() /> }; + let global_attrs_2 = view! { <{..} itemprop=move || itemprop.get() @@ -221,9 +235,9 @@ pub fn Toggle( tabindex=move || tabindex.get() title=move || title.get() translate=move || translate.get() - aria-label=move || aria_label.get() /> }; + let button_attrs = view! { <{..} command=move || command.get() @@ -240,31 +254,31 @@ pub fn Toggle( value=move || value.get() /> }; + view! { <button - type="button" + aria_disabled=move || if update_disabled() { Some("true") } else { None } + aria_label=move || aria_label.get() + aria_pressed=move || if pressed.get() { Some("true") } else { None } class=move || { format!( - "{} {} {}", + "singlestage-toggle {} {} {}", match variant.get().unwrap_or_default().as_str() { - "outline" => "singlestage-toggle-outline", - _ => "singlestage-toggle-default", + "outline" => "singlestage-toggle-variant-outline", + _ => "singlestage-toggle-variant-default", }, match size.get().unwrap_or_default().as_str() { - "small" => "singlestage-toggle-sm", - "sm" => "singlestage-toggle-sm", - "large" => "singlestage-toggle-lg", - "lg" => "singlestage-toggle-lg", - _ => "", + "sm" | "small" => "singlestage-toggle-size-sm", + "lg" | "large" => "singlestage-toggle-size-lg", + _ => "singlestage-toggle-size-default", }, class.get().unwrap_or_default(), ) } - data-state=move || if pressed.get() { "on" } else { "off" } - aria-pressed=move || pressed.get().to_string() - disabled=move || disabled.get_untracked() - prop:disabled=move || disabled.get() + disabled=update_disabled + prop:disabled=update_disabled on:click=move |_| pressed.set(!pressed.get_untracked()) + type="button" {..global_attrs_1} {..global_attrs_2} diff --git a/singlestage/src/components/toggle/style/base/luma/toggle.css b/singlestage/src/components/toggle/style/base/luma/toggle.css new file mode 100644 index 00000000..9ed5082b --- /dev/null +++ b/singlestage/src/components/toggle/style/base/luma/toggle.css @@ -0,0 +1,88 @@ +@layer components1 { + .singlestage-toggle { + @apply font-medium + gap-1 + rounded-3xl + text-sm + transition-colors; + + &:focus-visible { + @apply border-ring + ring-ring/30; + } + + &:hover { + @apply text-foreground; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-destructive/20; + } + + &[aria-pressed="true"] { + @apply bg-muted; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-toggle-variant-default { + @apply bg-transparent; + } + + .singlestage-toggle-variant-outline { + @apply bg-transparent + border + border-input; + + &:hover { + @apply bg-muted; + } + } + + .singlestage-toggle-size-default { + @apply h-9 + min-w-9 + px-3; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2.5; + } + } + + .singlestage-toggle-size-sm { + @apply h-8 + min-w-8 + px-3; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + } + + .singlestage-toggle-size-lg { + @apply h-10 + min-w-10 + px-4; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-3; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-3; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/luma/toggle_dark.css b/singlestage/src/components/toggle/style/base/luma/toggle_dark.css new file mode 100644 index 00000000..bbb3cba0 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/luma/toggle_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-toggle { + &:invalid, + &[aria-invalid="true"] { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/lyra/toggle.css b/singlestage/src/components/toggle/style/base/lyra/toggle.css new file mode 100644 index 00000000..4f028ad7 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/lyra/toggle.css @@ -0,0 +1,89 @@ +@layer components1 { + .singlestage-toggle { + @apply font-medium + gap-1 + rounded-none + text-xs + transition-all; + + &:focus-visible { + @apply border-ring + ring-ring/50; + } + + &:hover { + @apply text-foreground; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-destructive/20; + } + + &[aria-pressed="true"] { + @apply bg-muted; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-toggle-variant-default { + @apply bg-transparent; + } + + .singlestage-toggle-variant-outline { + @apply bg-transparent + border + border-input; + + &:hover { + @apply bg-muted; + } + } + + .singlestage-toggle-size-default { + @apply h-8 + min-w-8 + px-2.5; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + } + + .singlestage-toggle-size-sm { + @apply h-7 + min-w-7 + px-2.5 + rounded-none; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + } + + .singlestage-toggle-size-lg { + @apply h-9 + min-w-9 + px-2.5; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/lyra/toggle_dark.css b/singlestage/src/components/toggle/style/base/lyra/toggle_dark.css new file mode 100644 index 00000000..bbb3cba0 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/lyra/toggle_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-toggle { + &:invalid, + &[aria-invalid="true"] { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/maia/toggle.css b/singlestage/src/components/toggle/style/base/maia/toggle.css new file mode 100644 index 00000000..eacbafa1 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/maia/toggle.css @@ -0,0 +1,88 @@ +@layer components1 { + .singlestage-toggle { + @apply font-medium + gap-1 + rounded-4xl + text-sm + transition-colors; + + &:focus-visible { + @apply border-ring + ring-ring/50; + } + + &:hover { + @apply text-foreground; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-destructive/20; + } + + &[aria-pressed="true"] { + @apply bg-muted; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-toggle-variant-default { + @apply bg-transparent; + } + + .singlestage-toggle-variant-outline { + @apply bg-transparent + border + border-input; + + &:hover { + @apply bg-muted; + } + } + + .singlestage-toggle-size-default { + @apply h-9 + min-w-9 + px-3; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2.5; + } + } + + .singlestage-toggle-size-sm { + @apply h-8 + min-w-8 + px-3; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + } + + .singlestage-toggle-size-lg { + @apply h-10 + min-w-10 + px-4; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-3; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-3; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/maia/toggle_dark.css b/singlestage/src/components/toggle/style/base/maia/toggle_dark.css new file mode 100644 index 00000000..bbb3cba0 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/maia/toggle_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-toggle { + &:invalid, + &[aria-invalid="true"] { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/mira/toggle.css b/singlestage/src/components/toggle/style/base/mira/toggle.css new file mode 100644 index 00000000..000005c0 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/mira/toggle.css @@ -0,0 +1,94 @@ +@layer components1 { + .singlestage-toggle { + @apply font-medium + gap-1 + rounded-md + text-xs + transition-all; + + &:focus-visible { + @apply border-ring + ring-ring/50; + } + + &:hover { + @apply text-foreground; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-destructive/20; + } + + &[aria-pressed="true"] { + @apply bg-muted; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-toggle-variant-default { + @apply bg-transparent; + } + + .singlestage-toggle-variant-outline { + @apply bg-transparent + border + border-input; + + &:hover { + @apply bg-muted; + } + } + + .singlestage-toggle-size-default { + @apply h-7 + min-w-7 + px-2; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + } + + .singlestage-toggle-size-sm { + @apply h-6 + min-w-6 + px-2 + rounded-[min(var(--radius-md),8px)] + text-[0.625rem]; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + svg:not([class*="size-"]) { + @apply size-3; + } + } + + .singlestage-toggle-size-lg { + @apply h-8 + min-w-8 + px-2.5; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/mira/toggle_dark.css b/singlestage/src/components/toggle/style/base/mira/toggle_dark.css new file mode 100644 index 00000000..bbb3cba0 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/mira/toggle_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-toggle { + &:invalid, + &[aria-invalid="true"] { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/nova/toggle.css b/singlestage/src/components/toggle/style/base/nova/toggle.css new file mode 100644 index 00000000..7785db4d --- /dev/null +++ b/singlestage/src/components/toggle/style/base/nova/toggle.css @@ -0,0 +1,94 @@ +@layer components1 { + .singlestage-toggle { + @apply font-medium + gap-1 + rounded-lg + text-sm + transition-all; + + &:focus-visible { + @apply border-ring + ring-ring/50; + } + + &:hover { + @apply text-foreground; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-destructive/20; + } + + &[aria-pressed="true"] { + @apply bg-muted; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-toggle-variant-default { + @apply bg-transparent; + } + + .singlestage-toggle-variant-outline { + @apply bg-transparent + border + border-input; + + &:hover { + @apply bg-muted; + } + } + + .singlestage-toggle-size-default { + @apply h-8 + min-w-8 + px-2.5; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + } + + .singlestage-toggle-size-sm { + @apply h-7 + min-w-7 + px-2.5 + rounded-[min(var(--radius-md),12px)] + text-[0.8rem]; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-toggle-size-lg { + @apply h-9 + min-w-9 + px-2.5; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/nova/toggle_dark.css b/singlestage/src/components/toggle/style/base/nova/toggle_dark.css new file mode 100644 index 00000000..bbb3cba0 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/nova/toggle_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-toggle { + &:invalid, + &[aria-invalid="true"] { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/sera/toggle.css b/singlestage/src/components/toggle/style/base/sera/toggle.css new file mode 100644 index 00000000..bdb3df48 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/sera/toggle.css @@ -0,0 +1,90 @@ +@layer components1 { + .singlestage-toggle { + @apply font-semibold + gap-1.5 + rounded-none + text-xs + tracking-widest + transition-colors + uppercase; + + &:focus-visible { + @apply border-ring + ring-ring/30; + } + + &:hover { + @apply text-foreground; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-destructive/20; + } + + &[aria-pressed="true"] { + @apply bg-muted; + } + + svg:not([class*="size-"]) { + @apply size-3.5; + } + } + + .singlestage-toggle-variant-default { + @apply bg-transparent; + } + + .singlestage-toggle-variant-outline { + @apply bg-transparent + border + border-input; + + &:hover { + @apply bg-muted; + } + } + + .singlestage-toggle-size-default { + @apply h-10 + min-w-10 + px-6; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-4; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-4; + } + } + + .singlestage-toggle-size-sm { + @apply h-9 + min-w-9 + px-4; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-3; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-3; + } + } + + .singlestage-toggle-size-lg { + @apply h-11 + min-w-11 + px-8; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-5; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/sera/toggle_dark.css b/singlestage/src/components/toggle/style/base/sera/toggle_dark.css new file mode 100644 index 00000000..bbb3cba0 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/sera/toggle_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-toggle { + &:invalid, + &[aria-invalid="true"] { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/vega/toggle.css b/singlestage/src/components/toggle/style/base/vega/toggle.css new file mode 100644 index 00000000..e502cd70 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/vega/toggle.css @@ -0,0 +1,85 @@ +@layer components1 { + .singlestage-toggle { + @apply font-medium + gap-1 + rounded-md + text-sm + transition-[color,box-shadow]; + + &:focus-visible { + @apply border-ring + ring-ring/50; + } + + &:hover { + @apply text-foreground; + } + + &:invalid, + &[aria-invalid="true"] { + @apply border-destructive + ring-destructive/20; + } + + &[aria-pressed="true"] { + @apply bg-muted; + } + + svg:not([class*="size-"]) { + @apply size-4; + } + } + + .singlestage-toggle-variant-outline { + @apply bg-transparent + border + border-input + shadow-xs; + + &:hover { + @apply bg-muted; + } + } + + .singlestage-toggle-size-default { + @apply h-9 + min-w-9 + px-2.5; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + } + + .singlestage-toggle-size-sm { + @apply h-8 + min-w-8 + px-2.5; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-1.5; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-1.5; + } + } + + .singlestage-toggle-size-lg { + @apply h-10 + min-w-10 + px-2.5; + + &:has(svg:last-child:not(svg:first-child)) { + @apply pr-2; + } + + &:has(svg:first-child:not(svg:last-child)) { + @apply pl-2; + } + } +} diff --git a/singlestage/src/components/toggle/style/base/vega/toggle_dark.css b/singlestage/src/components/toggle/style/base/vega/toggle_dark.css new file mode 100644 index 00000000..bbb3cba0 --- /dev/null +++ b/singlestage/src/components/toggle/style/base/vega/toggle_dark.css @@ -0,0 +1,8 @@ +@layer components1 { + .singlestage-toggle { + &:invalid, + &[aria-invalid="true"] { + @apply ring-destructive/40; + } + } +} diff --git a/singlestage/src/components/toggle/style/toggle.css b/singlestage/src/components/toggle/style/toggle.css new file mode 100644 index 00000000..326eeb39 --- /dev/null +++ b/singlestage/src/components/toggle/style/toggle.css @@ -0,0 +1,28 @@ +@layer components { + .singlestage-toggle { + @apply inline-flex + items-center + justify-center + outline-none + whitespace-nowrap; + + &:disabled, + &[aria-disabled="true"] { + @apply opacity-50 + pointer-events-none; + } + + &:focus-visible { + @apply ring-[3px]; + } + + &:hover { + @apply bg-muted; + } + + svg { + @apply pointer-events-none + shrink-0; + } + } +} diff --git a/singlestage/src/components/toggle/toggle.css b/singlestage/src/components/toggle/toggle.css deleted file mode 100644 index 3d4b8a7a..00000000 --- a/singlestage/src/components/toggle/toggle.css +++ /dev/null @@ -1,59 +0,0 @@ -@layer components { - .singlestage-toggle-default, - .singlestage-toggle-outline { - @apply - inline-flex - items-center - justify-center - gap-2 - rounded-md - text-sm - font-medium - hover:bg-muted - hover:text-muted-foreground - disabled:pointer-events-none - disabled:opacity-50 - data-[state=on]:bg-accent - data-[state=on]:text-accent-foreground - [&_svg]:pointer-events-none - [&_svg:not([class*='size-'])]:size-4 - [&_svg]:shrink-0 - focus-visible:border-ring - focus-visible:ring-ring/50 - focus-visible:ring-[3px] - outline-none - transition-[color,box-shadow] - aria-invalid:ring-destructive/20 - dark:aria-invalid:ring-destructive/40 - aria-invalid:border-destructive - whitespace-nowrap - h-9 - px-2 - min-w-9; -} - - .singlestage-toggle-sm { - @apply - h-8 - px-1.5 - min-w-8; - } - - .singlestage-toggle-lg { - @apply - h-10 - px-2.5 - min-w-10; - } - - .singlestage-toggle-outline { - @apply - border - border-input - bg-transparent - shadow-xs - hover:bg-accent - hover:text-accent-foreground; - } - -} diff --git a/singlestage/src/components/tooltip/content.rs b/singlestage/src/components/tooltip/content.rs new file mode 100644 index 00000000..ae15f817 --- /dev/null +++ b/singlestage/src/components/tooltip/content.rs @@ -0,0 +1,202 @@ +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<String>, + /// 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<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let tooltip = expect_context::<TooltipContext>(); + let content_ref = NodeRef::<leptos::html::Div>::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! { + <div + class=move || { + format!( + "singlestage-tooltip-content singlestage-popover singlestage-popover-animations {} {} {}", + match side.get().unwrap_or_default().as_str() { + "right" => "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="hint" + style:position-anchor=move || format!("--{}", tooltip.trigger_id.get()) + on:toggle=move |_| { + if let Some(content) = content_ref.get_untracked() { + tooltip.open.set(content.matches(":popover-open").unwrap_or(false)); + } + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + } +} 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<String>, - /// 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<String>, - #[prop(optional, into)] value: Signal<String>, +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<String>, - /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, - /// 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<bool>, - /// Apply classes to the element. - #[prop(optional, into)] - class: MaybeProp<String>, - /// Allows client-side editing of the element by the user. - /// - /// Accepted values: "true" | "false" | "plaintext-only" - #[prop(optional, into)] - contenteditable: MaybeProp<String>, - /// Indicate directionality of the element's text. - /// - /// Accepted values: "ltr" | "rtl" | "auto" - #[prop(optional, into)] - dir: MaybeProp<String>, - /// Toggle whether the element can be dragged. - #[prop(optional, into)] - draggable: MaybeProp<bool>, - /// Modifies the appearance of the enter key on virtual keyboards. - #[prop(optional, into)] - enterkeyhint: MaybeProp<String>, - /// Expose elements in the shadow DOM to be manipulated by the DOM. - #[prop(optional, into)] - exportparts: MaybeProp<String>, - /// Controls hidden status of the element. - #[prop(optional, into)] - hidden: MaybeProp<String>, - /// Set the id of this element. - #[prop(optional, into)] - id: MaybeProp<String>, - /// Toggle if the browser reacts to input events from this element. - #[prop(optional, into)] - inert: MaybeProp<bool>, - /// Hints to the browser of what type of virtual keyboard to display when editing this element - /// or its children. - #[prop(optional, into)] - inputmode: MaybeProp<String>, - /// Used to render a standard element as a custom element. - #[prop(optional, into)] - is: MaybeProp<String>, - /// Unique global identifier of an item. - #[prop(optional, into)] - itemid: MaybeProp<String>, - /// Used to add properties to an item. - #[prop(optional, into)] - itemprop: MaybeProp<String>, - /// Used to associate an item with a related non-parent element that's using `itemscope`. - #[prop(optional, into)] - itemref: MaybeProp<String>, - /// Used to declare that children elements are related to a particular item. - #[prop(optional, into)] - itemscope: MaybeProp<String>, - /// URL of data used to define `itemprops`. - #[prop(optional, into)] - itemtype: MaybeProp<String>, - /// Defines the language of an element. - #[prop(optional, into)] - lang: MaybeProp<String>, - /// Cryptographic "number used once". - #[prop(optional, into)] - nonce: MaybeProp<String>, - /// List of the part names of the element. - #[prop(optional, into)] - part: MaybeProp<String>, - /// Designate an element as a popover element. - #[prop(optional, into)] - popover: MaybeProp<String>, - /// Define the semantic meaning of content. - #[prop(optional, into)] - role: MaybeProp<String>, - /// Assigns a slot to an element. - #[prop(optional, into)] - slot: MaybeProp<String>, - /// Toggle spellcheck for this input. - /// - /// Accepted values: "default" | "true" | "false". - #[prop(optional, into)] - spellcheck: MaybeProp<String>, - /// Define CSS to be applied to the element. - #[prop(optional, into)] - style: MaybeProp<String>, - /// Controls how an element behaves when a user navigates using the tab key. - #[prop(optional, into)] - tabindex: MaybeProp<usize>, - /// Describes the content of the element to screen readers. - #[prop(optional, into)] - title: MaybeProp<String>, - /// Defines localization behavior for the element. - #[prop(optional, into)] - translate: MaybeProp<String>, -) -> 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! { - <div - data-align=move || { - let align = align.get().unwrap_or_default(); - if align.is_empty() { "center".to_owned() } else { align } - } - data-side=move || { - let side = side.get().unwrap_or_default(); - if side.is_empty() { "top".to_owned() } else { side } - } - data-tooltip=move || value.get() +use leptos::prelude::*; - {..global_attrs_1} - {..global_attrs_2} - > - {children()} - </div> - } +#[derive(Clone)] +pub(crate) struct TooltipContext { + open: RwSignal<bool>, + trigger_id: RwSignal<String>, } diff --git a/singlestage/src/components/tooltip/style/base/luma/tooltip.css b/singlestage/src/components/tooltip/style/base/luma/tooltip.css new file mode 100644 index 00000000..db15d193 --- /dev/null +++ b/singlestage/src/components/tooltip/style/base/luma/tooltip.css @@ -0,0 +1,43 @@ +@layer components1 { + .singlestage-tooltip-content { + @apply gap-1.5 + inline-flex + items-center + px-3 + py-1.5 + rounded-xl + text-xs; + + &:has(.singlestage-kbd) { + @apply pr-1.5; + } + + .singlestage-kbd { + @apply isolate + relative + rounded-lg + z-50; + } + } + + /* TODO: Arrows */ + /* .singlestage-tooltip-arrow { */ + /* @apply size-2.5 */ + /* translate-y-[calc(-50%-2px)] */ + /* rotate-45 */ + /* rounded-[2px] */ + /* data-[side=left]:translate-x-[-1.5px] */ + /* data-[side=right]:translate-x-[1.5px]; */ + /* } */ + /**/ + /* .singlestage-tooltip-arrow-logical { */ + /* @apply data-[side=inline-end]:top-1/2! */ + /* data-[side=inline-end]:-left-1 */ + /* data-[side=inline-end]:translate-x-[1.5px] */ + /* data-[side=inline-end]:-translate-y-1/2 */ + /* data-[side=inline-start]:top-1/2! */ + /* data-[side=inline-start]:-right-1 */ + /* data-[side=inline-start]:translate-x-[-1.5px] */ + /* data-[side=inline-start]:-translate-y-1/2; */ + /* } */ +} diff --git a/singlestage/src/components/tooltip/style/base/lyra/tooltip.css b/singlestage/src/components/tooltip/style/base/lyra/tooltip.css new file mode 100644 index 00000000..986ba7e6 --- /dev/null +++ b/singlestage/src/components/tooltip/style/base/lyra/tooltip.css @@ -0,0 +1,39 @@ +@layer components1 { + .singlestage-tooltip-content { + @apply gap-1.5 + inline-flex + items-center + px-3 + py-1.5 + rounded-none + text-xs; + + &:has(.singlestage-kbd) { + @apply pr-1.5; + } + + .singlestage-kbd { + @apply isolate + relative + rounded-none + z-50; + } + } + + /* TODO: Arrows */ + /* .singlestage-tooltip-arrow { */ + /* @apply size-2.5 */ + /* translate-y-[calc(-50%-2px)] */ + /* rotate-45 */ + /* rounded-none; */ + /* } */ + /**/ + /* .singlestage-tooltip-arrow-logical { */ + /* @apply data-[side=inline-end]:top-1/2! */ + /* data-[side=inline-end]:-left-1 */ + /* data-[side=inline-end]:-translate-y-1/2 */ + /* data-[side=inline-start]:top-1/2! */ + /* data-[side=inline-start]:-right-1 */ + /* data-[side=inline-start]:-translate-y-1/2; */ + /* } */ +} diff --git a/singlestage/src/components/tooltip/style/base/maia/tooltip.css b/singlestage/src/components/tooltip/style/base/maia/tooltip.css new file mode 100644 index 00000000..6c4833f3 --- /dev/null +++ b/singlestage/src/components/tooltip/style/base/maia/tooltip.css @@ -0,0 +1,43 @@ +@layer components1 { + .singlestage-tooltip-content { + @apply gap-1.5 + inline-flex + items-center + px-3 + py-1.5 + rounded-2xl + text-xs; + + &:has(.singlestage-kbd) { + @apply pr-1.5; + } + + .singlestage-kbd { + @apply isolate + relative + rounded-4xl + z-50; + } + } + + /* TODO: Arrows */ + /* .singlestage-tooltip-arrow { */ + /* @apply size-2.5 */ + /* translate-y-[calc(-50%-2px)] */ + /* rotate-45 */ + /* rounded-[2px] */ + /* data-[side=left]:translate-x-[-1.5px] */ + /* data-[side=right]:translate-x-[1.5px]; */ + /* } */ + /**/ + /* .singlestage-tooltip-arrow-logical { */ + /* @apply data-[side=inline-end]:top-1/2! */ + /* data-[side=inline-end]:-left-1 */ + /* data-[side=inline-end]:translate-x-[1.5px] */ + /* data-[side=inline-end]:-translate-y-1/2 */ + /* data-[side=inline-start]:top-1/2! */ + /* data-[side=inline-start]:-right-1 */ + /* data-[side=inline-start]:translate-x-[-1.5px] */ + /* data-[side=inline-start]:-translate-y-1/2; */ + /* } */ +} diff --git a/singlestage/src/components/tooltip/style/base/mira/tooltip.css b/singlestage/src/components/tooltip/style/base/mira/tooltip.css new file mode 100644 index 00000000..6e825a21 --- /dev/null +++ b/singlestage/src/components/tooltip/style/base/mira/tooltip.css @@ -0,0 +1,39 @@ +@layer components1 { + .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 size-2.5 */ + /* translate-y-[calc(-50%-2px)] */ + /* rotate-45 */ + /* rounded-[2px]; */ + /* } */ + /**/ + /* .singlestage-tooltip-arrow-logical { */ + /* @apply data-[side=inline-end]:top-1/2! */ + /* data-[side=inline-end]:-left-1 */ + /* data-[side=inline-end]:-translate-y-1/2 */ + /* data-[side=inline-start]:top-1/2! */ + /* data-[side=inline-start]:-right-1 */ + /* data-[side=inline-start]:-translate-y-1/2; */ + /* } */ +} diff --git a/singlestage/src/components/tooltip/style/base/nova/tooltip.css b/singlestage/src/components/tooltip/style/base/nova/tooltip.css new file mode 100644 index 00000000..6e825a21 --- /dev/null +++ b/singlestage/src/components/tooltip/style/base/nova/tooltip.css @@ -0,0 +1,39 @@ +@layer components1 { + .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 size-2.5 */ + /* translate-y-[calc(-50%-2px)] */ + /* rotate-45 */ + /* rounded-[2px]; */ + /* } */ + /**/ + /* .singlestage-tooltip-arrow-logical { */ + /* @apply data-[side=inline-end]:top-1/2! */ + /* data-[side=inline-end]:-left-1 */ + /* data-[side=inline-end]:-translate-y-1/2 */ + /* data-[side=inline-start]:top-1/2! */ + /* data-[side=inline-start]:-right-1 */ + /* data-[side=inline-start]:-translate-y-1/2; */ + /* } */ +} diff --git a/singlestage/src/components/tooltip/style/base/sera/tooltip.css b/singlestage/src/components/tooltip/style/base/sera/tooltip.css new file mode 100644 index 00000000..381fa0da --- /dev/null +++ b/singlestage/src/components/tooltip/style/base/sera/tooltip.css @@ -0,0 +1,41 @@ +@layer components1 { + .singlestage-tooltip-content { + @apply gap-1.5 + inline-flex + items-center + px-3 + py-1.5 + rounded-none + text-xs; + + &:has(.singlestage-kbd) { + @apply pr-1.5; + } + + .singlestage-kbd { + @apply isolate + relative + rounded-none + z-50; + } + } + + /* TODO: Arrows */ + /* .singlestage-tooltip-arrow { */ + /* @apply size-2.5 */ + /* translate-y-[calc(-50%-2px)] */ + /* rotate-45 */ + /* rounded-none; */ + /* } */ + /**/ + /* .singlestage-tooltip-arrow-logical { */ + /* @apply data-[side=inline-end]:top-1/2! */ + /* data-[side=inline-end]:-left-1 */ + /* data-[side=inline-end]:translate-x-[1.5px] */ + /* data-[side=inline-end]:-translate-y-1/2 */ + /* data-[side=inline-start]:top-1/2! */ + /* data-[side=inline-start]:-right-1 */ + /* data-[side=inline-start]:translate-x-[-1.5px] */ + /* data-[side=inline-start]:-translate-y-1/2; */ + /* } */ +} 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..96517088 --- /dev/null +++ b/singlestage/src/components/tooltip/style/base/vega/tooltip.css @@ -0,0 +1,38 @@ +@layer components1 { + .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 new file mode 100644 index 00000000..f7b5cc18 --- /dev/null +++ b/singlestage/src/components/tooltip/style/tooltip.css @@ -0,0 +1,38 @@ +@layer components { + .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.css b/singlestage/src/components/tooltip/tooltip.css deleted file mode 100644 index 956ae8fd..00000000 --- a/singlestage/src/components/tooltip/tooltip.css +++ /dev/null @@ -1,102 +0,0 @@ -@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; - } - } - } - } -} 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> 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! { + <div + class="singlestage-tooltip" + + {..global_attrs_1} + {..global_attrs_2} + > + <Provider value=context>{children()}</Provider> + </div> + } +} 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let tooltip = expect_context::<TooltipContext>(); + + 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! { + <div + class=move || format!("singlestage-tooltip-trigger {}", class.get().unwrap_or_default()) + id={ + let trigger_id = id.get().unwrap_or(uuid::Uuid::new_v4().to_string()); + tooltip.trigger_id.set(trigger_id.clone()); + trigger_id + } + on:mouseenter=move |_| { tooltip.open.set(true) } + on:mouseleave=move |_| { tooltip.open.set(false) } + style:anchor-name=move || format!("--{}", tooltip.trigger_id.get()) + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + } +} diff --git a/singlestage/src/lib.rs b/singlestage/src/lib.rs index 94337e28..180c44cc 100644 --- a/singlestage/src/lib.rs +++ b/singlestage/src/lib.rs @@ -1,14 +1,55 @@ #![allow(clippy::module_inception)] -use std::include_str; + mod components; pub mod reactive; + pub use components::*; pub use reactive::*; +pub(crate) mod primitives; #[cfg(feature = "macro")] extern crate singlestage_macro; #[cfg(feature = "macro")] pub use singlestage_macro::*; +use std::include_str; + #[cfg(feature = "theme_provider")] static CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage.css")); +#[cfg(feature = "theme_provider")] +static CSS_DARK: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_dark.css")); + +#[cfg(feature = "style_luma")] +static CSS_LUMA: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_luma.css")); +#[cfg(feature = "style_luma")] +static CSS_LUMA_DARK: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_luma_dark.css")); + +#[cfg(feature = "style_lyra")] +static CSS_LYRA: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_lyra.css")); +#[cfg(feature = "style_lyra")] +static CSS_LYRA_DARK: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_lyra_dark.css")); + +#[cfg(feature = "style_maia")] +static CSS_MAIA: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_maia.css")); +#[cfg(feature = "style_maia")] +static CSS_MAIA_DARK: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_maia_dark.css")); + +#[cfg(feature = "style_mira")] +static CSS_MIRA: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_mira.css")); +#[cfg(feature = "style_mira")] +static CSS_MIRA_DARK: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_mira_dark.css")); + +#[cfg(feature = "style_nova")] +static CSS_NOVA: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_nova.css")); +#[cfg(feature = "style_nova")] +static CSS_NOVA_DARK: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_nova_dark.css")); + +#[cfg(feature = "style_sera")] +static CSS_SERA: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_sera.css")); +#[cfg(feature = "style_sera")] +static CSS_SERA_DARK: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_sera_dark.css")); + +#[cfg(feature = "style_vega")] +static CSS_VEGA: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_vega.css")); +#[cfg(feature = "style_vega")] +static CSS_VEGA_DARK: &str = include_str!(concat!(env!("OUT_DIR"), "/singlestage_vega_dark.css")); diff --git a/singlestage/src/primitives/checkbox.rs b/singlestage/src/primitives/checkbox.rs new file mode 100644 index 00000000..d395824e --- /dev/null +++ b/singlestage/src/primitives/checkbox.rs @@ -0,0 +1,464 @@ +use super::{LabelPrimitive, LabelPrimitiveType}; +use crate::{CheckboxGroupContext, FieldContext, RadioGroupContext, Reactive}; +use leptos::prelude::*; + +#[derive(Clone, Copy)] +pub enum CheckboxPrimitiveType { + Checkbox, + Switch, + Radio, +} + +#[component] +pub fn CheckboxPrimitive( + primitive_type: CheckboxPrimitiveType, + + #[prop(optional)] children: Option<Children>, + + /// Whether the input is invalid + #[prop(into)] + invalid: Reactive<bool>, + /// Set the size to render the Switch + #[prop(optional, into)] + size: MaybeProp<String>, + + // CHECKBOX ATTRIBUTES + // + /// Reactive signal coupled to the checkbox's checked value. + #[prop(into)] + checked: Reactive<bool>, + /// Associate this element with a form element that may not be its parent by its `id`. + #[prop(into)] + form: MaybeProp<String>, + /// Name of this element. Submitted with the form as part of a name/value pair. + #[prop(into)] + name: MaybeProp<String>, + /// Toggle whether or not the user can modify the value of this element. + #[prop(into)] + readonly: MaybeProp<bool>, + /// Toggle whether or not this element requires a value for form submission. + #[prop(into)] + required: MaybeProp<bool>, + /// Whether the form control is disabled + #[prop(into)] + disabled: Reactive<bool>, + /// The value of the control. When specified in the HTML, corresponds to the initial value + #[prop(into)] + value: MaybeProp<String>, + + // LEPTOS ATTRIBUTES + /// A reactive reference to a DOM node that can be used with the node_ref attribute. + #[prop(into)] + node_ref: MaybeProp<NodeRef<leptos::html::Input>>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + // /// Define the semantic meaning of content. + // #[prop(optional, into)] + // role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let input_ref = { + if let Some(node_ref) = node_ref.get_untracked() { + node_ref + } else { + NodeRef::<leptos::html::Input>::new() + } + }; + + let update_checked = move || { + if let Some(value) = value.get() { + match primitive_type { + CheckboxPrimitiveType::Checkbox | CheckboxPrimitiveType::Switch => { + if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() { + let is_checked = checkbox_group.value.get().contains(&value); + checked.set(is_checked); + is_checked + } else { + checked.get() + } + } + CheckboxPrimitiveType::Radio => { + if let Some(radio_group) = use_context::<RadioGroupContext>() { + let is_checked = radio_group.value.get() == value; + checked.set(is_checked); + is_checked + } else { + checked.get() + } + } + } + } else { + checked.get() + } + }; + + let disabled: Reactive<bool> = { + if let Some(field) = use_context::<FieldContext>() { + field.disabled + } else if let Some(radio_group) = use_context::<RadioGroupContext>() { + radio_group.disabled + } else if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() { + checkbox_group.disabled + } else { + disabled + } + }; + + let invalid: Reactive<bool> = { + if let Some(field) = use_context::<FieldContext>() { + field.invalid + } else if let Some(radio_group) = use_context::<RadioGroupContext>() { + radio_group.invalid + } else if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() { + checkbox_group.invalid + } else { + invalid + } + }; + + let on_change = move |ev| { + let input_checked = event_target_checked(&ev); + + checked.set(input_checked); + + match primitive_type { + CheckboxPrimitiveType::Checkbox | CheckboxPrimitiveType::Switch => { + if let Some(input_value) = value.get_untracked() + && let Some(checkbox_group) = use_context::<CheckboxGroupContext>() + { + match input_checked { + true => checkbox_group.value.update(|group_value| { + group_value.push(input_value); + }), + false => checkbox_group.value.update(|group_value| { + if let Some(index) = + group_value.iter().position(|el| *el == input_value) + { + group_value.swap_remove(index); + } + }), + } + } + } + CheckboxPrimitiveType::Radio => { + if let Some(radio_group) = use_context::<RadioGroupContext>() { + radio_group.value.set(event_target_value(&ev)); + } + } + } + }; + + 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() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + let input_id = uuid::Uuid::new_v4(); + let label_id = uuid::Uuid::new_v4(); + let has_children = children.is_some(); + + let input_attrs = view! { + <{..} + aria_checked=move || { if update_checked() { Some("true") } else { None } } + aria_describedby=move || { + if let Some(field) = use_context::<FieldContext>() { + let description_id = field.description_id.get(); + if description_id.is_empty() { None } else { Some(description_id) } + } else { + None + } + } + aria_disabled=move || { if disabled.get() { Some("true") } else { None } } + aria_invalid=move || { if invalid.get() { Some("true") } else { None } } + aria_labelledby=move || { + if let Some(field) = use_context::<FieldContext>() { + Some(field.label_id.get()) + } else if has_children { + Some(label_id.to_string()) + } else { + None + } + } + checked=update_checked + prop:checked=update_checked + class=move || { + format!( + "{} {}", + match primitive_type { + CheckboxPrimitiveType::Checkbox => { + if class + .get() + .unwrap_or_default() + .contains("singlestage-dropdown-menu-checkbox-item") + { + "".to_owned() + } else { + "singlestage-checkbox".to_owned() + } + } + CheckboxPrimitiveType::Switch => { + format!( + "singlestage-switch {}", + match size.get().unwrap_or_default().as_str() { + "small" | "sm" => "singlestage-switch-size-sm", + _ => "singlestage-switch-size-default", + }, + ) + } + CheckboxPrimitiveType::Radio => { + if class + .get() + .unwrap_or_default() + .contains("singlestage-dropdown-menu-radio-item") + { + "".to_owned() + } else { + "singlestage-radio-group-item".to_owned() + } + } + }, + class.get().unwrap_or_default(), + ) + } + disabled=move || disabled.get() + prop:disabled=move || disabled.get() + form=move || form.get() + id={if let Some(field) = use_context::<FieldContext>() { + if let Some(id) = id.get_untracked() { + field.input_id.set(id.clone()); + Some(id) + } else { + field.input_id.set(input_id.to_string()); + Some(input_id.to_string()) + } + } else if let Some(id) = id.get_untracked() { + Some(id) + } else { + if has_children { Some(input_id.to_string()) } else { None } + }} + name=move || { + if let Some(name) = name.get() { + Some(name) + } else { + match primitive_type { + CheckboxPrimitiveType::Checkbox | CheckboxPrimitiveType::Switch => { + if let Some(checkbox_group) = use_context::<CheckboxGroupContext>() { + Some(format!("{}[]", checkbox_group.name.clone())) + } else { + None + } + } + CheckboxPrimitiveType::Radio => { + if let Some(radio_group) = use_context::<RadioGroupContext>() { + Some(radio_group.name.clone()) + } else { + None + } + } + } + } + } + node_ref=input_ref + on:change=on_change + readonly=move || readonly.get() + required=move || required.get() + role=match primitive_type { + CheckboxPrimitiveType::Switch => Some("switch"), + _ => None, + } + type=match primitive_type { + CheckboxPrimitiveType::Radio => "radio", + _ => "checkbox", + } + value=move || value.get() + prop:value=move || value.get() + /> + }; + + let in_field = use_context::<FieldContext>().is_some(); + if let Some(children) = children { + if in_field { + view! { + <input + + {..global_attrs_1} + {..global_attrs_2} + {..input_attrs} + /> + <LabelPrimitive + primitive_type=LabelPrimitiveType::FieldLabel + + class + disabled + id=label_id.to_string() + invalid + label_for=id.get_untracked().unwrap_or(input_id.to_string()) + > + {children()} + </LabelPrimitive> + } + .into_any() + } else { + view! { + <LabelPrimitive + primitive_type=LabelPrimitiveType::Label + + class + disabled + id=label_id.to_string() + invalid + label_for=id.get_untracked().unwrap_or(input_id.to_string()) + > + <input + + {..global_attrs_1} + {..global_attrs_2} + {..input_attrs} + /> + {children()} + </LabelPrimitive> + } + .into_any() + } + } else { + view! { + <input + + {..global_attrs_1} + {..global_attrs_2} + {..input_attrs} + /> + } + .into_any() + } +} diff --git a/singlestage/src/primitives/dialog/action.rs b/singlestage/src/primitives/dialog/action.rs new file mode 100644 index 00000000..9c9bfd6b --- /dev/null +++ b/singlestage/src/primitives/dialog/action.rs @@ -0,0 +1,9 @@ +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone)] +pub(crate) struct DialogActionContext {} + +#[component] +pub fn DialogActionPrimitive(children: Children) -> impl IntoView { + view! { <Provider value=DialogActionContext {}>{children()}</Provider> } +} diff --git a/singlestage/src/primitives/dialog/cancel.rs b/singlestage/src/primitives/dialog/cancel.rs new file mode 100644 index 00000000..16874163 --- /dev/null +++ b/singlestage/src/primitives/dialog/cancel.rs @@ -0,0 +1,9 @@ +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone)] +pub(crate) struct DialogCancelContext {} + +#[component] +pub fn DialogCancelPrimitive(children: Children) -> impl IntoView { + view! { <Provider value=DialogCancelContext {}>{children()}</Provider> } +} diff --git a/singlestage/src/primitives/dialog/content.rs b/singlestage/src/primitives/dialog/content.rs new file mode 100644 index 00000000..ec89bdf1 --- /dev/null +++ b/singlestage/src/primitives/dialog/content.rs @@ -0,0 +1,243 @@ +use super::DialogContext; +use crate::Button; +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone)] +pub(crate) struct DialogCloseContext {} + +/// Contains content to be rendered in the main body of the dialog. +#[component] +pub fn DialogContentPrimitive( + children: Children, + + /// Toggles whether or not a close button should appear in the top right corner of the dialog. + /// + /// Defaults to `true` for `Dialog` and `false` for `AlertDialog` + #[prop(optional, into)] + close_button: MaybeProp<bool>, + /// Set the size of dialog to render. + /// + /// Accepted values are "sm" + #[prop(optional, into)] + size: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let dialog_context = expect_context::<DialogContext>(); + let dialog_ref = NodeRef::<leptos::html::Dialog>::new(); + + Effect::new(move || { + if let Some(dialog) = dialog_ref.get() { + match dialog_context.open.get() { + true => { + let _ = dialog.show_modal(); + } + false => { + dialog.close(); + } + } + } + }); + + 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() + 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! { + <dialog + class=move || { + format!( + "{}{} {}", + match dialog_context.alert { + true => "singlestage-alert-dialog-overlay", + false => "singlestage-dialog-overlay", + }, + if dialog_context.alert { + match size.get().unwrap_or_default().as_str() { + "sm" => " singlestage-alert-dialog-size-sm", + _ => " singlestage-alert-dialog-size-default", + } + } else { + "" + }, + class.get().unwrap_or_default(), + ) + } + node_ref=dialog_ref + closedby={match dialog_context.alert { + true => "closerequest", + false => "any", + }} + + {..global_attrs_1} + {..global_attrs_2} + > + <div class={if dialog_context.alert { + "singlestage-alert-dialog-content" + } else { + "singlestage-dialog-content" + }}> + {children()} <Show when=move || close_button.get().unwrap_or(!dialog_context.alert)> + <Provider value=DialogCloseContext {}> + <Button class="singlestage-dialog-close" variant="ghost" size="icon-sm"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + class="lucide lucide-x-icon lucide-x" + > + <path d="M18 6 6 18" /> + <path d="m6 6 12 12" /> + </svg> + <span class="sr-only">"Close"</span> + </Button> + </Provider> + </Show> + </div> + </dialog> + } +} diff --git a/singlestage/src/primitives/dialog/description.rs b/singlestage/src/primitives/dialog/description.rs new file mode 100644 index 00000000..bb86a913 --- /dev/null +++ b/singlestage/src/primitives/dialog/description.rs @@ -0,0 +1,174 @@ +use super::DialogContext; +use leptos::prelude::*; + +/// A short description/subheading describing dialog content. +#[component] +pub fn DialogDescriptionPrimitive( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let dialog = expect_context::<DialogContext>(); + + 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() + 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! { + <p + class=move || { + format!( + "{} {}", + match dialog.alert { + true => " singlestage-alert-dialog-description", + false => "singlestage-dialog-description", + }, + class.get().unwrap_or_default(), + ) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </p> + } +} diff --git a/singlestage/src/primitives/dialog/dialog.rs b/singlestage/src/primitives/dialog/dialog.rs new file mode 100644 index 00000000..5a2569a5 --- /dev/null +++ b/singlestage/src/primitives/dialog/dialog.rs @@ -0,0 +1,173 @@ +use super::DialogContext; +use crate::Reactive; +use leptos::{context::Provider, prelude::*}; + +/// Contains all the parts of a Dialog component. +#[component] +pub fn DialogPrimitive( + children: Children, + + #[prop(optional, into)] alert: bool, + + /// Reactive signal that can remotely control the open state of the popover **but is not + /// coupled to the actual open state of the popover** + #[prop(optional, into)] + open: Reactive<bool>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let context = DialogContext { + alert, + open, + ..Default::default() + }; + + 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! { + <div {..global_attrs_1} {..global_attrs_2}> + <Provider value=context>{children()}</Provider> + </div> + } +} diff --git a/singlestage/src/primitives/dialog/footer.rs b/singlestage/src/primitives/dialog/footer.rs new file mode 100644 index 00000000..cd88fae2 --- /dev/null +++ b/singlestage/src/primitives/dialog/footer.rs @@ -0,0 +1,174 @@ +use super::DialogContext; +use leptos::prelude::*; + +/// Displays at the bottom of the dialog, contains calls to action. +#[component] +pub fn DialogFooterPrimitive( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let dialog = expect_context::<DialogContext>(); + + 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() + 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! { + <footer + class=move || { + format!( + "{} {}", + match dialog.alert { + true => " singlestage-alert-dialog-footer", + false => "singlestage-dialog-footer", + }, + class.get().unwrap_or_default(), + ) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </footer> + } +} diff --git a/singlestage/src/primitives/dialog/header.rs b/singlestage/src/primitives/dialog/header.rs new file mode 100644 index 00000000..347525f1 --- /dev/null +++ b/singlestage/src/primitives/dialog/header.rs @@ -0,0 +1,174 @@ +use super::DialogContext; +use leptos::prelude::*; + +/// Contains the dialog title and a description to be rendered in the open dialog. +#[component] +pub fn DialogHeaderPrimitive( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let dialog = expect_context::<DialogContext>(); + + 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() + 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! { + <hgroup + class=move || { + format!( + "{} {}", + match dialog.alert { + true => "singlestage-alert-dialog-header", + false => "singlestage-dialog-header", + }, + class.get().unwrap_or_default(), + ) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </hgroup> + } +} diff --git a/singlestage/src/primitives/dialog/media.rs b/singlestage/src/primitives/dialog/media.rs new file mode 100644 index 00000000..8a5fc92e --- /dev/null +++ b/singlestage/src/primitives/dialog/media.rs @@ -0,0 +1,175 @@ +use leptos::prelude::*; + +/// Contains a media element such as an icon or image to render in the header of the dialog. +#[component] +pub fn DialogMediaPrimitive( + children: Children, + + /// The display variant of the media element such as "destructive". + #[prop(optional, into)] + variant: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <div + class=move || { + format!( + "singlestage-alert-dialog-media{} {}", + match variant.get().unwrap_or_default().as_str() { + "destructive" => " singlestage-alert-dialog-media-variant-destructive", + _ => "", + }, + class.get().unwrap_or_default(), + ) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </div> + } +} diff --git a/singlestage/src/primitives/dialog/mod.rs b/singlestage/src/primitives/dialog/mod.rs new file mode 100644 index 00000000..7ee89b09 --- /dev/null +++ b/singlestage/src/primitives/dialog/mod.rs @@ -0,0 +1,27 @@ +mod action; +mod cancel; +mod content; +mod description; +mod dialog; +mod footer; +mod header; +mod media; +mod title; + +pub use action::*; +pub use cancel::*; +pub use content::*; +pub use description::*; +pub use dialog::*; +pub use footer::*; +pub use header::*; +pub use media::*; +pub use title::*; + +use crate::Reactive; + +#[derive(Clone, Default)] +pub(crate) struct DialogContext { + pub alert: bool, + pub open: Reactive<bool>, +} diff --git a/singlestage/src/primitives/dialog/title.rs b/singlestage/src/primitives/dialog/title.rs new file mode 100644 index 00000000..94e3b7da --- /dev/null +++ b/singlestage/src/primitives/dialog/title.rs @@ -0,0 +1,174 @@ +use super::DialogContext; +use leptos::prelude::*; + +/// A title describing dialog content. +#[component] +pub fn DialogTitlePrimitive( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let dialog = expect_context::<DialogContext>(); + + 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() + 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! { + <h2 + class=move || { + format!( + "{} {}", + match dialog.alert { + true => "singlestage-alert-dialog-title", + false => "singlestage-dialog-title", + }, + class.get().unwrap_or_default(), + ) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </h2> + } +} diff --git a/singlestage/src/primitives/label.rs b/singlestage/src/primitives/label.rs new file mode 100644 index 00000000..852d6ad9 --- /dev/null +++ b/singlestage/src/primitives/label.rs @@ -0,0 +1,265 @@ +use crate::primitives::*; +use crate::{FieldContext, RadioGroupContext, Reactive}; +use leptos::prelude::*; + +#[derive(Clone, Copy)] +pub enum LabelPrimitiveType { + Label, + FieldLabel, + MenuLabel, +} + +#[component] +pub fn LabelPrimitive( + primitive_type: LabelPrimitiveType, + + children: Children, + + /// Whether the element renders as disabled + #[prop(into)] + disabled: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + /// (For use in popover menus) + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Whether the element renders as invalid + #[prop(into)] + invalid: Reactive<bool>, + /// The id of the labeled element if it's not a child + #[prop(into)] + label_for: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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! { + <label + aria_disabled=move || { + if disabled.get() { + Some("true".to_string()) + } else { + if let Some(field) = use_context::<FieldContext>() && field.disabled.get() { + Some("true".to_string()) + } else { + None + } + } + } + aria_invalid=move || { + if invalid.get() { + Some("true".to_string()) + } else { + if let Some(field) = use_context::<FieldContext>() && field.invalid.get() { + Some("true".to_string()) + } else if let Some(radio_group) = use_context::<RadioGroupContext>() + && radio_group.invalid.get() + { + Some("true".to_string()) + } else { + None + } + } + } + + // TODO: Do this with CSS. + class=move || { + format!( + "{} {}", + match primitive_type { + LabelPrimitiveType::Label => "singlestage-label".to_string(), + LabelPrimitiveType::FieldLabel => "singlestage-field-label".to_string(), + LabelPrimitiveType::MenuLabel => { + format!( + "singlestage-dropdown-menu-label{}", + if inset.get().unwrap_or_default() { + " singlestage-dropdown-menu-inset" + } else { + "" + }, + ) + } + }, + class.get().unwrap_or_default(), + ) + } + + for=move || { + match primitive_type { + LabelPrimitiveType::FieldLabel => { + if let Some(field_context) = use_context::<FieldContext>() { + Some(field_context.input_id.get()) + } else { + label_for.get() + } + } + _ => label_for.get(), + } + } + + id={match primitive_type { + LabelPrimitiveType::MenuLabel => { + let label_id = id.get().unwrap_or(uuid::Uuid::new_v4().to_string()); + if let Some(group) = use_context::<PopoverMenuGroupContext>() { + group.heading_id.set(label_id.clone()); + } + Some(label_id.to_owned()) + } + LabelPrimitiveType::FieldLabel => { + if let Some(field_context) = use_context::<FieldContext>() { + let label_id = id.get().unwrap_or(uuid::Uuid::new_v4().to_string()); + field_context.label_id.set(label_id.clone()); + Some(label_id.to_owned()) + } else { + id.get() + } + } + LabelPrimitiveType::Label => id.get(), + }} + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </label> + } +} diff --git a/singlestage/src/primitives/mod.rs b/singlestage/src/primitives/mod.rs new file mode 100644 index 00000000..ad19912c --- /dev/null +++ b/singlestage/src/primitives/mod.rs @@ -0,0 +1,11 @@ +pub mod checkbox; +pub mod dialog; +pub mod label; +pub mod popover_menu; +pub mod trigger; + +pub use checkbox::*; +pub use dialog::*; +pub use label::*; +pub use popover_menu::*; +pub use trigger::*; diff --git a/singlestage/src/primitives/popover_menu/content.rs b/singlestage/src/primitives/popover_menu/content.rs new file mode 100644 index 00000000..a0d76a77 --- /dev/null +++ b/singlestage/src/primitives/popover_menu/content.rs @@ -0,0 +1,271 @@ +use super::{MenuSubContext, PopoverMenuContext}; +use crate::ContextMenuContext; +use leptos::prelude::*; + +pub enum MenuContentPrimitiveType { + Main, + Sub, +} + +#[component] +pub fn MenuContentPrimitive( + primitive_type: MenuContentPrimitiveType, + + children: Children, + + /// Set how to align the popover + /// + /// Accepted values: "start" | "center" | "end" + /// Default is "start" + #[prop(optional, into)] + align: MaybeProp<String>, + /// Set which side the popover opens relative to the trigger + /// + /// Accepted values: "top" | "right" | "bottom" | "left" + /// Default is "bottom" + #[prop(optional, into)] + side: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let menu = expect_context::<PopoverMenuContext>(); + + let menu_ref = NodeRef::<leptos::html::Menu>::new(); + + Effect::new(move || { + if let Some(popover) = menu_ref.get() { + if let Some(sub) = use_context::<MenuSubContext>() { + let _ = popover.toggle_popover_with_force(sub.open.get()); + } else { + let _ = popover.toggle_popover_with_force(menu.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() + 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() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + let is_context_menu = use_context::<ContextMenuContext>().is_some(); + let is_sub = StoredValue::new(match primitive_type { + MenuContentPrimitiveType::Main => false, + MenuContentPrimitiveType::Sub => true, + }); + + view! { + <menu + class=move || { + format!( + "singlestage-dropdown-menu-content{} singlestage-popover {} {}", + if is_sub.get_value() { + " singlestage-dropdown-menu-sub-content".to_owned() + } else { + "".to_owned() + }, + if is_context_menu && !is_sub.get_value() { + "".to_owned() + } else { + format!( + "singlestage-popover-animations {} {}", + match side.get().unwrap_or_default().as_str() { + "top" => "singlestage-popover-top", + "right" | "right-bottom" => "singlestage-popover-right", + "right-top" => "singlestage-popover-right-top", + "left" | "left-bottom" => "singlestage-popover-left", + "left-top" => "singlestage-popover-left-top", + _ => { + if is_sub.get_value() { + "singlestage-popover-right" + } else { + "singlestage-popover-bottom" + } + } + }, + match align.get().unwrap_or_default().as_str() { + "center" => "singlestage-popover-center", + "end" => "singlestage-popover-end", + _ => "singlestage-popover-start", + }, + ) + }, + class.get().unwrap_or_default(), + ) + } + id={ + let menu_id = id.get().unwrap_or(uuid::Uuid::new_v4().to_string()); + if let Some(sub) = use_context::<MenuSubContext>() { + sub.menu_id.set(menu_id.clone()); + } else { + menu.menu_id.set(menu_id.clone()); + } + menu_id + } + node_ref=menu_ref + on:contextmenu=move |ev| { + if use_context::<ContextMenuContext>().is_some() { + ev.prevent_default(); + } + } + popover=move || { + if !menu.modal.get() || is_sub.get_value() { "auto" } else { "manual" } + } + role="menu" + style=move || { + if let Some(context_menu) = use_context::<ContextMenuContext>() + && !is_sub.get_value() + { + Some( + format!( + "left: {}px; top: {}px; {}", + context_menu.x.get(), + context_menu.y.get(), + style.get().unwrap_or_default(), + ), + ) + } else { + style.get() + } + } + style:position-anchor=move || { + if is_sub.get_value() { + let sub = expect_context::<MenuSubContext>(); + format!("--{}", sub.trigger_id.get()) + } else { + format!("--{}", menu.trigger_id.get()) + } + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </menu> + } +} diff --git a/singlestage/src/primitives/popover_menu/group.rs b/singlestage/src/primitives/popover_menu/group.rs new file mode 100644 index 00000000..72cedf3f --- /dev/null +++ b/singlestage/src/primitives/popover_menu/group.rs @@ -0,0 +1,167 @@ +use super::PopoverMenuGroupContext; +use leptos::{context::Provider, prelude::*}; + +/// Contains multiple items. +#[component] +pub fn MenuGroupPrimitive( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let heading_id = RwSignal::new(String::new()); + + let context = PopoverMenuGroupContext { heading_id }; + + 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() + popover=move || popover.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! { + <div + aria_labelledby=move || heading_id.get() + class=move || { + format!("singlestage-dropdown-menu-group {}", class.get().unwrap_or_default()) + } + role="group" + + {..global_attrs_1} + {..global_attrs_2} + > + <Provider value=context>{children()}</Provider> + </div> + } +} diff --git a/singlestage/src/primitives/popover_menu/item.rs b/singlestage/src/primitives/popover_menu/item.rs new file mode 100644 index 00000000..ea9eaa1e --- /dev/null +++ b/singlestage/src/primitives/popover_menu/item.rs @@ -0,0 +1,345 @@ +use super::{MenuSubContext, PopoverMenuContext}; +use crate::{Button, Checkbox, Radio, Reactive}; +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone, Copy)] +pub enum MenuItemPrimitiveType { + Item, + Checkbox, + Radio, + SubTrigger, +} + +#[derive(Clone)] +pub(crate) struct MenuItemContext { + pub dismiss: Reactive<bool>, +} + +#[component] +pub fn MenuItemPrimitive( + primitive_type: MenuItemPrimitiveType, + + children: Children, + + #[prop(optional, into)] checked: Reactive<bool>, + + /// This component will render without styling + #[prop(optional, into)] + as_child: MaybeProp<bool>, + /// Controls whether the item appears disabled and is clickable. + #[prop(optional, into)] + disabled: Reactive<bool>, + /// Toggle whether clicking this item dismisses its parent menu + #[prop(optional, into, default = Reactive::new(true))] + dismiss: Reactive<bool>, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp<bool>, + /// Set the display variant of the item. + /// + /// Accepted values: "destructive" + #[prop(optional, into)] + variant: MaybeProp<String>, + + // LI ATTRIBRUTES + // + /// The current ordinal value of the item. + #[prop(into)] + value: MaybeProp<String>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <li + class=move || { + format!( + "{}{}{}{} {}", + match primitive_type { + MenuItemPrimitiveType::SubTrigger => "singlestage-dropdown-menu-sub-trigger", + _ => "singlestage-dropdown-menu-item", + }, + if disabled.get() { + " singlestage-dropdown-menu-item-variant-disabled" + } else { + "" + }, + if inset.get().unwrap_or_default() { + " singlestage-dropdown-menu-inset" + } else { + "" + }, + match variant.get().unwrap_or_default().as_str() { + "destructive" => " singlestage-dropdown-menu-item-variant-destructive", + _ => "", + }, + class.get().unwrap_or_default(), + ) + } + on:click=move |_| { + match primitive_type { + MenuItemPrimitiveType::Checkbox | MenuItemPrimitiveType::Radio => { + if dismiss.get() { + if let Some(menu) = use_context::<PopoverMenuContext>() + && !menu.modal.get() + { + menu.open.set(false); + } else if let Some(menu) = use_context::<PopoverMenuContext>() { + menu.open.set(false); + } + } + } + MenuItemPrimitiveType::Item => { + if as_child.get().unwrap_or_default() && dismiss.get() { + if let Some(menu) = use_context::<PopoverMenuContext>() + && !menu.modal.get() + { + menu.open.set(false); + } + } + } + _ => {} + } + } + + role="menuitem" + value=move || value.get() + + {..global_attrs_1} + {..global_attrs_2} + > + {match primitive_type { + MenuItemPrimitiveType::Item => { + view! { + <Provider value=MenuItemContext { + dismiss, + }> + {if as_child.get().unwrap_or_default() { + children().into_any() + } else { + view! { + <Button disabled id> + {children()} + </Button> + } + .into_any() + }} + </Provider> + } + .into_any() + } + MenuItemPrimitiveType::Checkbox | MenuItemPrimitiveType::Radio => { + view! { + <label aria_disabled=move || { + if disabled.get() { Some("true") } else { None } + }> + {children()} + {match primitive_type { + MenuItemPrimitiveType::Checkbox => { + view! { + <Checkbox + class="singlestage-dropdown-menu-checkbox-item" + checked + disabled + id + /> + } + .into_any() + } + MenuItemPrimitiveType::Radio => { + view! { + <Radio + class="singlestage-dropdown-menu-radio-item" + checked + disabled + id + value + /> + } + .into_any() + } + _ => view! {}.into_any(), + }} + </label> + } + .into_any() + } + MenuItemPrimitiveType::SubTrigger => { + let sub = expect_context::<MenuSubContext>(); + + view! { + <button + aria_controls=move || sub.menu_id.get() + aria_haspopup="menu" + id={ + let trigger_id = id + .get() + .unwrap_or(uuid::Uuid::new_v4().to_string()); + sub.trigger_id.set(trigger_id.clone()); + trigger_id + } + popovertarget=move || sub.menu_id.get() + popovertargetaction="toggle" + style:anchor-name=move || format!("--{}", sub.trigger_id.get()) + type="button" + > + + {children()} + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + class="ml-auto" + > + <path d="m9 18 6-6-6-6" /> + </svg> + </button> + } + .into_any() + } + }} + </li> + } +} diff --git a/singlestage/src/primitives/popover_menu/mod.rs b/singlestage/src/primitives/popover_menu/mod.rs new file mode 100644 index 00000000..c27a5baf --- /dev/null +++ b/singlestage/src/primitives/popover_menu/mod.rs @@ -0,0 +1,34 @@ +mod content; +mod group; +mod item; +mod shortcut; +mod sub; + +pub use content::*; +pub use group::*; +pub use item::*; +pub use shortcut::*; +pub use sub::*; + +use crate::Reactive; +use leptos::prelude::*; + +#[derive(Clone, Default)] +pub(crate) struct PopoverMenuContext { + pub modal: Reactive<bool>, + pub menu_id: RwSignal<String>, + pub open: Reactive<bool>, + pub trigger_id: RwSignal<String>, +} + +#[derive(Clone)] +pub(crate) struct PopoverMenuGroupContext { + pub heading_id: RwSignal<String>, +} + +#[derive(Clone)] +pub(crate) struct MenuSubContext { + pub menu_id: RwSignal<String>, + pub open: Reactive<bool>, + pub trigger_id: RwSignal<String>, +} diff --git a/singlestage/src/primitives/popover_menu/shortcut.rs b/singlestage/src/primitives/popover_menu/shortcut.rs new file mode 100644 index 00000000..da94fb0a --- /dev/null +++ b/singlestage/src/primitives/popover_menu/shortcut.rs @@ -0,0 +1,163 @@ +use leptos::prelude::*; + +#[component] +pub fn MenuShortcutPrimitive( + 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + 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() + 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! { + <span + class=move || { + format!("singlestage-dropdown-menu-shortcut {}", class.get().unwrap_or_default()) + } + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + </span> + } +} diff --git a/singlestage/src/primitives/popover_menu/sub.rs b/singlestage/src/primitives/popover_menu/sub.rs new file mode 100644 index 00000000..0f658814 --- /dev/null +++ b/singlestage/src/primitives/popover_menu/sub.rs @@ -0,0 +1,180 @@ +use super::MenuSubContext; +use crate::Reactive; +use leptos::{context::Provider, prelude::*}; + +/// Contains all the parts of a sub menu. +#[component] +pub fn MenuSubPrimitive( + children: Children, + + /// Reactive signal that can remotely control the open state of the popover **but is not + /// coupled to the actual open state of the popover** + #[prop(optional, into)] + open: Reactive<bool>, + + // 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<String>, + /// Sets whether the input value should be capitalized and how. If a parent `<form>` 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<String>, + /// 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<bool>, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp<String>, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp<String>, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp<String>, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp<bool>, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp<String>, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp<String>, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp<String>, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp<String>, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp<bool>, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp<String>, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp<String>, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp<String>, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp<String>, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp<String>, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp<String>, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp<String>, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp<String>, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp<String>, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp<String>, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp<String>, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp<String>, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp<String>, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp<String>, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp<String>, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp<usize>, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp<String>, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp<String>, +) -> impl IntoView { + let menu_id = RwSignal::new(String::new()); + let trigger_id = RwSignal::new(String::new()); + + let context = MenuSubContext { + menu_id, + 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! { + <div + on:mouseenter=move |_| { open.set(true) } + on:mouseleave=move |_| { open.set(false) } + + {..global_attrs_1} + {..global_attrs_2} + > + <Provider value=context>{children()}</Provider> + </div> + } +} diff --git a/singlestage/src/primitives/trigger.rs b/singlestage/src/primitives/trigger.rs new file mode 100644 index 00000000..3ea4d7f6 --- /dev/null +++ b/singlestage/src/primitives/trigger.rs @@ -0,0 +1,9 @@ +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone)] +pub(crate) struct TriggerContext {} + +#[component] +pub fn TriggerPrimitive(children: Children) -> impl IntoView { + view! { <Provider value=TriggerContext {}>{children()}</Provider> }.into_any() +} diff --git a/singlestage/src/reactive.rs b/singlestage/src/reactive.rs index d47d0092..e2fe87de 100644 --- a/singlestage/src/reactive.rs +++ b/singlestage/src/reactive.rs @@ -4,6 +4,15 @@ use std::fmt::Debug; #[cfg(feature = "stores")] use reactive_stores::{ArcField, ArcStore, Field, Store, StoreField, Subfield}; +// TODO: For experimental multi-thumb `Slider` support - use or remove me +// +// fn usize_to_f64(value: usize) -> f64 { +// // HACK: a 64 bit usize isn't guaranteed to fit in a 64 bit float, so do some parsing +// // shenanigans and return f64 max if the number is large enough, this easy and should +// // work 99.999% of the time +// value.to_string().parse::<f64>().unwrap_or(f64::MAX) +// } + // Rust gets upset about trying to use `#[cfg(feature = "stores")]` in a where clause: // https://github.com/rust-lang/rust/issues/115590 // HACK: So we'll just define `Reactive` conditionally for now because it's easy and it works. @@ -140,6 +149,28 @@ impl From<&str> for Reactive<String> { } } +// TODO: For experimental multi-thumb `Slider` support - use or remove me +// +// impl From<usize> for Reactive<f64> { +// fn from(value: usize) -> Self { +// Reactive::RwSignal(RwSignal::new(usize_to_f64(value))) +// } +// } +// +// impl From<usize> for Reactive<Vec<f64>> { +// fn from(value: usize) -> Self { +// Reactive::RwSignal(RwSignal::new(vec![usize_to_f64(value)])) +// } +// } +// +// impl From<Vec<usize>> for Reactive<Vec<f64>> { +// fn from(value: Vec<usize>) -> Self { +// Reactive::RwSignal(RwSignal::new( +// value.iter().map(|el| usize_to_f64(el.to_owned())).collect(), +// )) +// } +// } + impl<T> From<T> for Reactive<T> where T: Send + Sync + Clone + 'static, @@ -149,6 +180,15 @@ where } } +impl<T> From<T> for Reactive<Vec<T>> +where + T: Send + Sync + Clone + 'static, +{ + fn from(value: T) -> Self { + Reactive::RwSignal(RwSignal::new(vec![value])) + } +} + impl<T> From<RwSignal<T>> for Reactive<T> where T: Send + Sync + Clone + 'static, diff --git a/test_site/.gitignore b/test_site/.gitignore new file mode 100644 index 00000000..2ec32721 --- /dev/null +++ b/test_site/.gitignore @@ -0,0 +1,19 @@ +# Generated by Cargo +# will have compiled files and executables +data/ +dist/ +target/ +backup.sql +pkg + +# These are backup files generated by rustfmt +**/*.rs.bk + +# node e2e test tools and outputs +node_modules/ +test-results/ +end2end/playwright-report/ +playwright/.cache/ + +# Tauri +src-tauri/gen diff --git a/test_site/Cargo.lock b/test_site/Cargo.lock new file mode 100644 index 00000000..8c64571a --- /dev/null +++ b/test_site/Cargo.lock @@ -0,0 +1,7308 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "any_spawner" +version = "0.3.0" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "futures", + "thiserror 2.0.20", + "tokio", + "wasm-bindgen-futures", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "axum", + "cfg-if", + "console_error_panic_hook", + "console_log", + "http", + "icondata", + "leptos", + "leptos_axum", + "leptos_meta", + "leptos_router", + "log", + "serde", + "simple_logger", + "singlestage", + "thiserror 2.0.20", + "tokio", + "tower", + "tower-http 0.7.0", + "wasm-bindgen", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "attribute-derive" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05832cdddc8f2650cc2cc187cc2e952b8c133a48eb055f35211f61ee81502d77" +dependencies = [ + "attribute-derive-macro", + "derive-where", + "manyhow", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7cdbbd4bd005c5d3e2e9c885e6fa575db4f4a3572335b974d8db853b6beb61" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn 2.0.119", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "base64 0.22.1", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "multer", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "codee" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9dbbdc4b4d349732bc6690de10a9de952bd39ba6a065c586e26600b6b0b91f5" +dependencies = [ + "serde", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "collection_literals" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compression-codecs" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "config" +version = "0.15.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85f248a4de22d204ceabc6299d89d2c70fbd7f09fea53c06c852369652d8139" +dependencies = [ + "convert_case 0.6.0", + "pathdiff", + "serde_core", + "toml 1.1.4+spec-1.1.0", + "winnow 1.0.4", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86919cef3e37b9356ccf54d4421208c17ecfda01beae61393e7ffd72916c0ef1" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "const-str" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f12cc9948ed9604230cdddc7c86e270f9401ccbe3c2e98a4378c5e7632212f" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "const_str_slice_concat" +version = "0.1.0" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case_extras" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589c70f0faf8aa9d17787557d5eae854d7755cac50f5c3d12c81d3d57661cebb" +dependencies = [ + "convert_case 0.11.0", +] + +[[package]] +name = "cookie" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.20", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive-where" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "either_of" +version = "0.1.9" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "paste", + "pin-project-lite", +] + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.4+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1731451909bde27714eacba19c2566362a7f35224f52b153d3f42cf60f72472" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fern" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" +dependencies = [ + "log", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "guardian" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hdrhistogram" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d1053f4708f0af3cf9fc5bffc7e68a914a3c45becb231c80068c9c3f78bea" +dependencies = [ + "byteorder", + "num-traits", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html-escape" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9356095b4b41197bba32173600e1582792cda618f65d12f68e2e77d273413c5" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hydrate" +version = "0.1.0" +dependencies = [ + "app", + "console_error_panic_hook", + "console_log", + "leptos", + "log", + "wasm-bindgen", +] + +[[package]] +name = "hydration_context" +version = "0.3.1" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "futures", + "js-sys", + "or_poisoned", + "pin-project-lite", + "serde", + "throw_error", + "wasm-bindgen", +] + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icondata" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "412c95f764f89aad1a8505aa4273d931b66d5a26d1e6609a37243784a9128ad9" +dependencies = [ + "icondata_ai", + "icondata_bi", + "icondata_bs", + "icondata_cg", + "icondata_ch", + "icondata_core", + "icondata_fa", + "icondata_fi", + "icondata_hi", + "icondata_im", + "icondata_io", + "icondata_lu", + "icondata_mdi", + "icondata_oc", + "icondata_ri", + "icondata_si", + "icondata_tb", + "icondata_ti", + "icondata_vs", + "icondata_wi", +] + +[[package]] +name = "icondata_ai" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "153b31389f611b00f7392c2f79b5bb8332e30a5eee5bffd66fc4fe767c1de59c" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_bi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f178e5643b61ddf06b4b0c68e0fa5674cbd750be9414e5e02e2e3dd740d9924" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_bs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a8a72809ffc53e8a61dd19079693dbc0478b7b27e09ca1c1e81c04e283f485" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_cg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db064a49229a38dd5bae614b194adc5e3ee5351f5c5d9e539cd159bf84a92539" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_ch" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998079d1d0673fbb1bd3753ef714e5668fb5f595ffa9bc89eb5b01b6b4f75ca5" +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 = "icondata_fa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0108c409b3c06be1124a377b827922ad6091cb9eb467d521ea1cb3b1c3d7a76" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_fi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0656636c0e5c070e8501667f287e1751ea4bec9945eed3c66ac472511632b9b0" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_hi" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ba6ed6e155898d296fc5d4127eb651b667643368698dd02b1e9383eab3eef" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_im" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e723800b6a6067a40cf311a9135d739412323f03f88e826a20f1249933187aa5" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_io" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7886629becd205d6f035dc084b68d2b9cc5ba8b03714f2c533c921ed98c6525a" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_lu" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25663a6cc5371e9e33c98bab870f4aa2fd68c88853f1467c8fccfaf112782b1" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_mdi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9517acba09219ff7caebd797b2db68f09b3621c6bc848fe9894f2d30e4f6cdb" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_oc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088863ff0b1dba4a6e8b3fec797be6ed8503c2d44fd47107f7f6da3abcb6cc87" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_ri" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1851b5662f59b888ba24595886a9e6b6f5c11151b3640ee1875810f7544865" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_si" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68943eec8a766d8e5213385610aa47bf0bec5ddb76fdcf819721a05fb5c8f6c5" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_tb" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f08d4ae577034728dc8856d3bdbced6a406aadfd7386e0e24e5efb7be98685" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_ti" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26660305b27c2831c8e701434e55f6fb589c12bdd43e5ec1e2ae270dbeb185e" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_vs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe2547385392ec350a5a7f80c463767b1d7f7a37cf1431cac2754918a4c1b6a9" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icondata_wi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352f5a5df000eabb35894cec75048e97a46d738ebdaf1e060670b0cf473a2419" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.13.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "leptos" +version = "0.8.20" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "any_spawner", + "base64 0.22.1", + "cfg-if", + "either_of", + "futures", + "getrandom 0.4.3", + "hydration_context", + "leptos_config", + "leptos_dom", + "leptos_hot_reload", + "leptos_macro", + "leptos_server", + "oco_ref", + "or_poisoned", + "paste", + "rand 0.9.5", + "reactive_graph", + "rustc-hash", + "rustc_version", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn", + "slotmap", + "tachys", + "thiserror 2.0.20", + "throw_error", + "typed-builder", + "typed-builder-macro", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_split_helpers", + "web-sys", +] + +[[package]] +name = "leptos_axum" +version = "0.8.10" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "any_spawner", + "axum", + "futures", + "hydration_context", + "leptos", + "leptos_integration_utils", + "leptos_macro", + "leptos_meta", + "leptos_router", + "or_poisoned", + "server_fn", + "tachys", + "tokio", + "tower", + "tower-http 0.6.11", +] + +[[package]] +name = "leptos_config" +version = "0.8.10" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "config", + "serde", + "thiserror 2.0.20", + "typed-builder", +] + +[[package]] +name = "leptos_dom" +version = "0.8.8" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "js-sys", + "or_poisoned", + "reactive_graph", + "send_wrapper", + "tachys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_hot_reload" +version = "0.8.6" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "anyhow", + "camino", + "indexmap 2.14.0", + "or_poisoned", + "proc-macro2", + "quote", + "rstml", + "serde", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "leptos_integration_utils" +version = "0.8.8" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "futures", + "hydration_context", + "leptos", + "leptos_config", + "leptos_meta", + "leptos_router", + "reactive_graph", +] + +[[package]] +name = "leptos_macro" +version = "0.8.17" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "attribute-derive", + "cfg-if", + "convert_case 0.11.0", + "convert_case_extras", + "html-escape", + "itertools", + "leptos_hot_reload", + "prettyplease", + "proc-macro-error2", + "proc-macro2", + "quote", + "rstml", + "rustc_version", + "server_fn_macro", + "syn 2.0.119", + "uuid", +] + +[[package]] +name = "leptos_meta" +version = "0.8.6" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "futures", + "indexmap 2.14.0", + "leptos", + "or_poisoned", + "send_wrapper", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_router" +version = "0.8.15" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "any_spawner", + "either_of", + "futures", + "gloo-net", + "js-sys", + "leptos", + "leptos_router_macro", + "or_poisoned", + "percent-encoding", + "reactive_graph", + "rustc_version", + "send_wrapper", + "tachys", + "thiserror 2.0.20", + "url", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_router_macro" +version = "0.8.6" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "leptos_server" +version = "0.8.7" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "any_spawner", + "base64 0.22.1", + "codee", + "futures", + "hydration_context", + "or_poisoned", + "reactive_graph", + "send_wrapper", + "serde", + "serde_json", + "server_fn", + "tachys", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "manyhow" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "manyhow-macros" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.20", + "windows-sys 0.61.2", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "next_tuple" +version = "0.1.0" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "oco_ref" +version = "0.2.1" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "serde", + "thiserror 2.0.20", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "open" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cfef937e9c486488c7e3d949ae31c0f1d06bdacd75b99c086cb35356e30408" +dependencies = [ + "dunce", + "is-wsl", + "libc", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "or_poisoned" +version = "0.1.0" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "version_check", + "yansi", +] + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quote-use" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e" +dependencies = [ + "quote", + "quote-use-macros", +] + +[[package]] +name = "quote-use-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "reactive_graph" +version = "0.2.14" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "any_spawner", + "async-lock", + "futures", + "guardian", + "hydration_context", + "indexmap 2.14.0", + "or_poisoned", + "paste", + "pin-project-lite", + "rustc-hash", + "rustc_version", + "send_wrapper", + "serde", + "slotmap", + "thiserror 2.0.20", + "web-sys", +] + +[[package]] +name = "reactive_stores" +version = "0.4.3" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "guardian", + "indexmap 2.14.0", + "itertools", + "or_poisoned", + "paste", + "reactive_graph", + "reactive_stores_macro", + "rustc-hash", + "send_wrapper", +] + +[[package]] +name = "reactive_stores_macro" +version = "0.4.3" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "convert_case 0.11.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.20", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http 0.6.11", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rstml" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61cf4616de7499fc5164570d40ca4e1b24d231c6833a88bff0fe00725080fd56" +dependencies = [ + "derive-where", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.119", + "syn_derive", + "thiserror 2.0.20", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.13.1", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_qs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" +dependencies = [ + "percent-encoding", + "serde", + "thiserror 2.0.20", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "server_fn" +version = "0.8.13" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "axum", + "base64 0.22.1", + "bytes", + "const-str", + "const_format", + "futures", + "gloo-net", + "http", + "http-body-util", + "hyper", + "inventory", + "js-sys", + "or_poisoned", + "pin-project-lite", + "rustc_version", + "rustversion", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "thiserror 2.0.20", + "throw_error", + "tokio", + "tower", + "tower-layer", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.8.10" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "const_format", + "convert_case 0.11.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.8.5" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "server_fn_macro", + "syn 2.0.119", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha256" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_logger" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7038d0e96661bf9ce647e1a6f6ef6d6f3663f66d9bf741abf14ba4876071c17" +dependencies = [ + "colored", + "log", + "time", + "windows-sys 0.61.2", +] + +[[package]] +name = "singlestage" +version = "0.5.0" +dependencies = [ + "leptos", + "leptos_meta", + "leptos_router", + "rayon", + "reqwest", + "sha256", + "singlestage_macro", + "uuid", + "web-sys", +] + +[[package]] +name = "singlestage_macro" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f833d819750ca73ed9658c83636b35fcac6bc8d008b26d4dfdc771ad050bc2d3" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tachys" +version = "0.2.18" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "any_spawner", + "async-trait", + "const_str_slice_concat", + "drain_filter_polyfill", + "either_of", + "erased", + "futures", + "html-escape", + "indexmap 2.14.0", + "itertools", + "js-sys", + "next_tuple", + "oco_ref", + "or_poisoned", + "paste", + "reactive_graph", + "reactive_stores", + "rustc-hash", + "rustc_version", + "send_wrapper", + "slotmap", + "throw_error", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "tao" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +dependencies = [ + "bitflags 2.13.1", + "block2", + "core-foundation 0.10.1", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni 0.21.1", + "libc", + "log", + "ndk", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni 0.21.1", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.20", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.119", + "tauri-utils", + "thiserror 2.0.20", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "walkdir", +] + +[[package]] +name = "tauri-plugin-log" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6792296e6f389268016c77db21ebae1fc0568f2fccf88b1ec7e2ea71330afb4c" +dependencies = [ + "android_logger", + "fern", + "log", + "objc2", + "objc2-foundation", + "serde", + "serde_json", + "serde_repr", + "swift-rs", + "tauri", + "tauri-plugin", + "thiserror 2.0.20", + "time", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.20", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-runtime" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni 0.21.1", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.20", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" +dependencies = [ + "gtk", + "http", + "jni 0.21.1", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.20", + "toml 1.1.4+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.4+spec-1.1.0", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +dependencies = [ + "new_debug_unreachable", +] + +[[package]] +name = "test_site" +version = "0.1.0" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-log", + "tauri-plugin-opener", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "throw_error" +version = "0.3.1" +source = "git+https://github.com/leptos-rs/leptos#fa1b92d187d0e50b281574ea6785b5c302216e13" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "hdrhistogram", + "indexmap 2.14.0", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-http" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233" +dependencies = [ + "async-compression", + "bitflags 2.13.1", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.20", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "sha1", + "thiserror 2.0.20", +] + +[[package]] +name = "typed-builder" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "895a2607575412a4eda1df892084a375ea10dfeadc4d7d2ab87b854e4ddc7ba1" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288cb0ebe215033bf949ae1fd046726daa4c32a157f24b9dc6ac387a52aa759" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "wasm-bindgen-test-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ff1c1b360982e93b6d8ea9c04836f71dba0817a16f91e229cf3a51bdd9d987" + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm_split_helpers" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab578aae2fe2916edaea06843187d50f87b0965622da0ceef648edca27b385ba" +dependencies = [ + "async-once-cell", + "wasm_split_macros", +] + +[[package]] +name = "wasm_split_macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e653af7ee4a9ef0fce481a9ec6f43cb78de20d0cdb4f4f5862e1dc6e407e6c8" +dependencies = [ + "base16", + "quote", + "sha2", + "syn 2.0.119", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.20", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni 0.21.1", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.20", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "769381976ea05c1f0abde0ca527b02e415bccbdbad148534a2b0c1901c9ab934" +dependencies = [ + "serde", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zvariant" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e28c25bd8bb8da5a1f3e7065d0c156b9ee9a7973adf78b0e35eaefdf3b1b5c" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d496a145685283b67e232bd9e47377f6b60ad9d51e3601b23867f77c42477f96" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629d80ece222cad20fe0e8741be493c4ab166acf3b85341bdc2cdbcfd8f3c2d6" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.3", + "winnow 1.0.4", +] diff --git a/test_site/Cargo.toml b/test_site/Cargo.toml new file mode 100644 index 00000000..8e21feb2 --- /dev/null +++ b/test_site/Cargo.toml @@ -0,0 +1,105 @@ +[workspace] +members = ["app", "hydrate", "src-tauri"] +resolver = "2" + +[workspace.dependencies] +# leptos = { version = "*", features = ["nightly"] } +# leptos_axum = { version = "*" } +# leptos_meta = { version = "*" } +# leptos_router = { version = "*", features = ["nightly"] } +leptos = { git = "https://github.com/leptos-rs/leptos", features = ["nightly"] } +leptos_axum = { git = "https://github.com/leptos-rs/leptos" } +leptos_meta = { git = "https://github.com/leptos-rs/leptos" } +leptos_router = { git = "https://github.com/leptos-rs/leptos", features = [ + "nightly", +] } +# singlestage = { version = "0.4", features = ["nightly"] } +singlestage = { path = "../singlestage", features = ["nightly"] } + +axum = "*" +cfg-if = "*" +console_error_panic_hook = "*" +console_log = "*" +http = "*" +icondata = "*" +log = "*" +serde = { version = "*", features = ["derive"] } +serde_json = "*" +simple_logger = "*" +tauri = "*" +tauri-build = "*" +tauri-plugin-log = "*" +tauri-plugin-opener = "*" +thiserror = "*" +tokio = "*" +tower = "*" +tower-http = "*" +wasm-bindgen = "*" +wasm-bindgen-test = "*" +web-sys = { version = "*", features = [ + "MediaQueryList", + "HtmlDocument", + "Document", + "Window", +] } + +[profile.release] +codegen-units = 1 +lto = true +opt-level = "z" + +# Cargo Leptos Stuff + +[[workspace.metadata.leptos]] +# Compilation Parameters +always-erase-components = false +# bin-cargo-args = ["--timings"] +# bin-cargo-command = "cross" +bin-default-features = false +bin-package = "app" +bin-features = ["ssr"] +# bin-profile-dev = "debug" +# bin-profile-release = "release" +bin-target = "app" +# bin-target-triple = "x86_64-unknown-linux-gnu" # Env: LEPTOS_BIN_TARGET_TRIPLE +disable-erase-components = false +features = [] +hash-file-name = "hash.txt" +hash-files = false +# lib-cargo-args = ["--timings"] +lib-default-features = false +lib-package = "hydrate" +lib-features = ["hydrate"] +# lib-profile-dev = "debug" +# lib-release-profile = "release" +wasm-opt-features = [ + "-Oz", + "--enable-bulk-memory", + "--enable-nontrapping-float-to-int", +] + +# Site Parameters +assets-dir = "public" +browserquery = "defaults" +disable-server-fn-hash = true +end2end-cmd = "npx playwright test" +end2end-dir = "end2end" +js-dir = "src" +js-minify = true +name = "test_site" +output-name = "test_site" +reload-port = 3001 +server-fn-mod-path = false +server-fn-prefix = "/api" +site-addr = "127.0.0.1:3000" +site-pkg-dir = "pkg" +site-root = "target/site" +# style-file = "style/main.css" +# tailwind-config-file = "tailwind.config.js" +tailwind-input-file = "style/tailwind.css" +watch-additional-files = [ + "../singlestage", + "../singlestage_macro", + "app", + "hydrate", +] diff --git a/test_site/README.md b/test_site/README.md new file mode 100644 index 00000000..5e708ad4 --- /dev/null +++ b/test_site/README.md @@ -0,0 +1,59 @@ +# Singlestage Test Harness + +This is lighter weight test project for working on singlestage without having +to wait for all the macros in the docs site for code blocks and shit + +todo: playwright or something + +| Server | Port | +| - | - | +| `cargo leptos` | 3000 | +| `trunk` | 3010 | + +## Usage + +### Dev build in ssr mode + +```bash +cargo install cargo-leptos +``` + +```bash +./dev ssr +``` + +### Dev build in csr mode + +```bash +cargo install trunk +``` + +```bash +./dev csr +``` + +### Dev build native + +```bash +cargo install tauri-cli +``` + +```bash +./dev native +``` + +### Dev build android + +See [https://tauri.app/start/prerequisites/#android](https://tauri.app/start/prerequisites/#android) + +```bash +cargo install tauri-cli +``` + +```bash +cargo tauri android init +``` + +```bash +./dev android +``` diff --git a/test_site/Trunk.toml b/test_site/Trunk.toml new file mode 100644 index 00000000..22b3e062 --- /dev/null +++ b/test_site/Trunk.toml @@ -0,0 +1,15 @@ +[build] +target = "app/index.html" +minify = "on_release" +ignore = "src-tauri" + +[serve] +addresses = ["0.0.0.0"] +port = 3010 +ws_protocol = "ws" + +[tools] +tailwindcss = "4.1.18" + +[watch] +watch = ["../singlestage", "../singlestage_macro"] diff --git a/test_site/app/Cargo.toml b/test_site/app/Cargo.toml new file mode 100644 index 00000000..0bd73805 --- /dev/null +++ b/test_site/app/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "app" +authors = ["Adam Doyle <adam@doordesk.net>"] +version = "0.1.0" +edition = "2024" + +[dependencies] +leptos.workspace = true +leptos_axum = { workspace = true, optional = true } +leptos_meta.workspace = true +leptos_router.workspace = true + +cfg-if.workspace = true +http.workspace = true +icondata.workspace = true +serde = { workspace = true } +singlestage = { workspace = true, optional = true } +thiserror.workspace = true + +# ssr +axum = { workspace = true, optional = true } +simple_logger = { workspace = true, optional = true } +tokio = { workspace = true, optional = true, features = ["full"] } +tower = { workspace = true, optional = true, features = ["full"] } +tower-http = { workspace = true, optional = true, features = [ + "compression-full", + "cors", + "decompression-full", +] } + +# csr +console_error_panic_hook = { workspace = true, optional = true } +console_log = { workspace = true, optional = true } +log = { workspace = true, optional = true } + +web-sys = { workspace = true } + +[dev-dependencies] +wasm-bindgen = { workspace = true } +wasm-bindgen-test = { workspace = true } + + +[features] +csr = [ + "dep:console_error_panic_hook", + "dep:console_log", + "dep:log", + "leptos/csr", + "singlestage/csr", +] +hydrate = ["leptos/hydrate", "singlestage/hydrate"] +ssr = [ + "dep:axum", + "dep:leptos_axum", + "dep:log", + "dep:simple_logger", + "dep:tokio", + "dep:tower", + "dep:tower-http", + "leptos/ssr", + "leptos_meta/ssr", + "leptos_router/ssr", + "singlestage/ssr", +] diff --git a/test_site/app/index.html b/test_site/app/index.html new file mode 100644 index 00000000..cc7745b0 --- /dev/null +++ b/test_site/app/index.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <style> + @media (prefers-color-scheme: dark) { + body, + html { + background-color: #0a0a0a; + } + } + </style> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link data-trunk rel="copy-dir" href="../public" data-target-path="/" /> + <link data-trunk rel="tailwind-css" href="../style/tailwind.css" /> + <link data-trunk rel="rust" data-wasm-opt="z" data-weak-refs /> + </head> + <body></body> +</html> diff --git a/test_site/app/src/components/mod.rs b/test_site/app/src/components/mod.rs new file mode 100644 index 00000000..6e182c71 --- /dev/null +++ b/test_site/app/src/components/mod.rs @@ -0,0 +1,5 @@ +mod sidebar; +mod theme_switcher; + +pub use sidebar::*; +pub use theme_switcher::*; diff --git a/test_site/app/src/components/sidebar/button.rs b/test_site/app/src/components/sidebar/button.rs new file mode 100644 index 00000000..b066faac --- /dev/null +++ b/test_site/app/src/components/sidebar/button.rs @@ -0,0 +1,31 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn SidebarButton() -> impl IntoView { + let sidebar = expect_context::<SidebarContext>(); + + view! { + <Tooltip> + <TooltipTrigger> + <Button variant="ghost" size="sm-icon"> + <Show + when=move || { !sidebar.open.get() } + fallback=move || match sidebar.side.get().as_str() { + "right" => 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(), + }} + </Show> + </Button> + </TooltipTrigger> + <TooltipContent side="bottom" align="start"> + <p>"Toggle sidebar"</p> + </TooltipContent> + </Tooltip> + } +} diff --git a/test_site/app/src/components/sidebar/mod.rs b/test_site/app/src/components/sidebar/mod.rs new file mode 100644 index 00000000..96b146f8 --- /dev/null +++ b/test_site/app/src/components/sidebar/mod.rs @@ -0,0 +1,105 @@ +use leptos::prelude::*; +use singlestage::*; + +mod button; +mod test_buttons; +mod test_menu; + +pub use button::*; +use test_buttons::*; +use test_menu::*; + +#[component] +pub fn AppSidebar() -> impl IntoView { + let routes = StoredValue::new([ + // "All", + "Button", + "Checkbox", + "Context Menu", + "Dropdown", + "Form Reset", + "Input", + "Label", + "Radio", + "Select", + "Slider", + "Textarea", + "Toggle", + "Tooltip", + ]); + + view! { + <Sidebar> + <SidebarHeader> + <Link + as_child=true + href="/" + on:click=move |_| { + if let Some(sheet) = use_context::<SheetContext>() { + sheet.open.set(false) + } + } + > + <div class="flex m-2"> + <div class="flex justify-center items-center max-h-12 rounded-md shadow-sm bg-(--primary) text-(--primary-foreground) min-w-12 max-w-12 min-h-12"> + <svg + class="h-9 fill-current" + viewBox="0 0 44.238907 53.197109" + version="1.1" + xml:space="preserve" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + > + <defs id="defs1" /> + <g id="layer1" transform="translate(-83.28999,-146.33474)"> + <path + id="path643" + d="m 111.70944,146.33474 -1.3503,0.0398 -1.35031,0.0398 -0.50539,0.19534 -0.5054,0.19534 -0.42788,0.43149 -0.42788,0.4315 -0.95188,1.54151 -0.9524,1.54099 -0.0879,0.0543 -0.0879,0.0543 -0.21807,-0.0548 -0.10284,-0.0258 h -5.1e-4 c -0.0255,0.002 -0.0512,4.1e-4 -0.0765,-0.004 -0.001,-1.9e-4 -0.002,-3.2e-4 -0.004,-5.2e-4 -0.002,-2.8e-4 -0.004,-2.2e-4 -0.005,-5.1e-4 -0.002,-1.9e-4 -0.003,-3.9e-4 -0.004,-5.2e-4 0.001,-1e-5 0.004,-1.4e-4 0.006,0 -0.0297,-0.002 -0.006,3.5e-4 -0.046,-0.006 -0.007,-0.002 -0.0139,-0.003 -0.0207,-0.005 -0.003,-7.2e-4 -0.005,-10e-4 -0.008,-0.002 -7.9e-4,-2.2e-4 -0.003,-5e-4 -0.003,-5.2e-4 -0.0105,-0.003 -0.0208,-0.006 -0.031,-0.01 h -5.2e-4 -5.2e-4 c -2.1e-4,-4e-5 -0.001,6e-5 -0.002,0 l -5.1e-4,-5.1e-4 h -5.2e-4 c -0.0126,-0.001 -0.0248,-0.003 -0.0372,-0.006 -0.004,-0.001 -0.0126,-0.003 -0.0196,-0.005 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 c -0.002,-7.1e-4 -0.005,-0.001 -0.007,-0.002 -0.002,-3.6e-4 -0.003,-0.001 -0.005,-0.002 -6.9e-4,-1.6e-4 -0.001,-3.5e-4 -0.002,-5.2e-4 -0.0219,-0.005 -0.0437,-0.0111 -0.0641,-0.0212 v -5.2e-4 h -5.2e-4 c -0.002,-8.6e-4 -0.004,-0.001 -0.006,-0.003 l -5.1e-4,-5.2e-4 -5.2e-4,-5.2e-4 h -5.2e-4 c -0.004,-0.002 -0.009,-0.004 -0.0129,-0.006 -0.004,-0.002 -0.009,-0.005 -0.0129,-0.007 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 l -5.2e-4,-5.2e-4 v -5.1e-4 h -5.2e-4 -5.1e-4 c -8e-4,-4.4e-4 -0.002,-9.2e-4 -0.003,-0.002 -0.001,-7.1e-4 -0.003,-0.002 -0.004,-0.002 -0.007,-0.003 -0.002,-0.001 -0.0269,-0.0134 -0.005,-0.003 -0.0106,-0.006 -0.016,-0.009 -0.002,-0.001 -0.004,-0.002 -0.006,-0.004 h -5.1e-4 l -5.2e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.1e-4 v -5.2e-4 c -0.005,-0.003 -0.002,-0.001 -0.0191,-0.0108 -0.0169,-0.0104 -0.0335,-0.0207 -0.0501,-0.0315 -0.014,-0.009 -0.028,-0.018 -0.0419,-0.0269 -0.007,-0.004 -0.0135,-0.008 -0.0202,-0.0124 -0.003,-0.002 -0.006,-0.003 -0.008,-0.005 v -5.1e-4 c -0.006,-0.004 -0.0127,-0.007 -0.0186,-0.0114 -0.009,-0.006 -0.009,-0.007 -0.0124,-0.009 -8.3e-4,-6.2e-4 -0.002,-5.9e-4 -0.003,-0.002 h -5.1e-4 c -4.3e-4,-3.1e-4 -0.001,-6.8e-4 -0.002,-0.001 h -5.2e-4 v -5.1e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 -5.2e-4 c -0.004,-0.002 -0.008,-0.004 -0.0114,-0.006 h -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 c -5.8e-4,-2.8e-4 -0.001,-7.5e-4 -0.002,-0.001 -0.0159,-0.008 -0.0333,-0.0162 -0.0558,-0.0284 -3.4e-4,-1.8e-4 -6.9e-4,-3.4e-4 -0.001,-5.2e-4 -0.002,-9e-4 -0.005,-0.003 -0.007,-0.004 -0.0108,-0.006 -0.0216,-0.0118 -0.032,-0.0181 -0.002,-0.001 -0.006,-0.003 -0.007,-0.004 -0.0188,-0.0102 0.007,0.004 -0.0238,-0.0129 0,0 -0.001,-9.3e-4 -0.002,-0.001 -0.001,-7.3e-4 -0.002,-9.8e-4 -0.003,-0.002 -0.001,-7.8e-4 -0.002,-0.001 -0.004,-0.002 -0.001,-9.1e-4 -0.002,-0.001 -0.003,-0.002 -0.002,-10e-4 -0.005,-0.003 -0.007,-0.005 -4.1e-4,-1.5e-4 -10e-4,-4.6e-4 -0.002,-5.2e-4 -0.002,-9.3e-4 -0.006,-0.003 -0.007,-0.004 v -5.2e-4 c -0.0101,-0.006 -0.0221,-0.0114 -0.0315,-0.0186 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 l -5.2e-4,-5.2e-4 v -5.1e-4 h -5.1e-4 v -5.2e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 v 5.2e-4 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 v -5.1e-4 c -0.002,-9.2e-4 -0.004,-0.001 -0.004,-0.002 h -5.1e-4 c -2.8e-4,-9e-5 -0.001,-3.5e-4 -0.003,-0.001 -0.0114,-0.004 -0.0226,-0.008 -0.0341,-0.0124 -0.0178,-0.005 -0.0281,-0.009 -0.0444,-0.0145 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 c -0.002,-6.9e-4 -0.006,-0.002 -0.0114,-0.004 -0.003,-9.6e-4 -0.003,-0.001 -0.005,-0.002 0.003,9.7e-4 0.003,10e-4 -0.0139,-0.005 -0.002,-6.7e-4 -0.004,-0.001 -0.006,-0.002 -0.0296,-0.0104 -0.0585,-0.0217 -0.0853,-0.0382 v -5.2e-4 h -5.2e-4 c -0.002,-8.3e-4 -0.004,-0.001 -0.006,-0.002 l -5.2e-4,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.2e-4 -5.2e-4,-5.1e-4 v -5.2e-4 -5.2e-4 c -0.003,-0.002 -0.005,-0.003 -0.008,-0.005 -0.0133,-0.007 -0.0261,-0.0156 -0.0388,-0.0238 -5.8e-4,-2.9e-4 -10e-4,-6.3e-4 -0.002,-0.001 -3.2e-4,-1.7e-4 -7.4e-4,-3.7e-4 -0.001,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 v -5.1e-4 h -5.2e-4 c -0.002,-0.001 -0.005,-0.003 -0.009,-0.005 h -5.2e-4 c -2.2e-4,-1.4e-4 -8.1e-4,-8.8e-4 -10e-4,-10e-4 h -5.2e-4 v -5.1e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 l -5.2e-4,-5.2e-4 c -0.004,-0.002 -0.007,-0.003 -0.008,-0.003 5.8e-4,8e-5 0.002,3.5e-4 0.003,5.2e-4 h 5.2e-4 5.2e-4 c 3.8e-4,-2e-5 0.001,-2e-5 0.002,0 -10e-4,-6.6e-4 -0.005,-0.002 -0.014,-0.006 -0.0263,-0.0133 -0.0407,-0.0223 -0.0517,-0.0289 -0.001,-7.3e-4 -0.004,-0.002 -0.005,-0.003 -3.4e-4,-1.7e-4 -6.9e-4,-3.4e-4 -10e-4,-5.1e-4 -0.004,-0.002 -0.008,-0.005 -0.0103,-0.006 -0.003,-0.002 -0.006,-0.003 -0.009,-0.005 -4.2e-4,-2.4e-4 -6.3e-4,-2.9e-4 -0.001,-5.2e-4 -2e-4,-1.1e-4 -7.9e-4,-3.8e-4 -0.001,-5.1e-4 h -5.1e-4 c -0.002,-0.001 -0.006,-0.004 -0.007,-0.005 v -5.1e-4 h -5.2e-4 -5.1e-4 c -0.01,-0.005 -0.004,-0.002 -0.0181,-0.0109 -0.002,-0.001 -0.004,-0.002 -0.006,-0.004 2.9e-4,2.2e-4 -0.002,-9.2e-4 -0.003,-0.002 l -5.2e-4,-5.2e-4 -5.1e-4,-5.2e-4 h -5.2e-4 l -5.2e-4,-5.1e-4 c -0.002,-0.001 -0.002,-0.001 -0.003,-0.002 -3.5e-4,-2.3e-4 -0.001,-8.9e-4 -0.002,-0.001 l -5.2e-4,-5.2e-4 h -5.1e-4 c -0.012,-0.007 -0.01,-0.006 -0.0233,-0.0145 h -5.1e-4 v -5.2e-4 c -0.003,-0.002 -0.006,-0.003 -0.008,-0.005 v -5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 -10e-4,-5.2e-4 c -5.3e-4,-3.2e-4 -9.8e-4,-6.8e-4 -0.002,-0.001 -8.7e-4,-3.8e-4 -0.002,-7.1e-4 -0.003,-0.001 -9.4e-4,-4.8e-4 -0.002,-6.6e-4 -0.005,-0.002 -0.007,-0.003 -0.005,-0.002 -0.0191,-0.009 -10e-4,-6.9e-4 -0.004,-0.002 -0.006,-0.003 h -5.2e-4 v -5.1e-4 c -0.019,-0.008 -0.019,-0.008 -0.0362,-0.0165 -1.7e-4,-1.1e-4 -8.3e-4,-4e-4 -0.001,-5.2e-4 h -5.2e-4 l -5.2e-4,-5.1e-4 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 v -5.2e-4 h -5.1e-4 c -0.003,-0.002 -0.007,-0.004 -0.0114,-0.006 -4e-4,-2e-4 -6.4e-4,-3.1e-4 -0.001,-5.1e-4 -5.9e-4,-2.9e-4 -4.7e-4,-2.6e-4 -10e-4,-5.2e-4 -0.018,-0.009 -0.0219,-0.0107 -0.032,-0.0165 7e-5,5e-5 -0.002,-0.001 -0.004,-0.002 -0.001,-7.9e-4 -0.003,-0.001 -0.004,-0.002 h -5.2e-4 c -3.7e-4,-2.1e-4 -7.3e-4,-3.2e-4 -0.001,-5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.1e-4 c -0.002,-0.001 -0.003,-0.002 -0.004,-0.003 -0.0212,-0.0115 -0.0267,-0.0154 -0.0429,-0.0253 -0.003,-0.002 -0.007,-0.005 -0.01,-0.007 -0.007,-0.005 -0.0139,-0.01 -0.0207,-0.0145 -0.0111,-0.008 -0.023,-0.0161 -0.0331,-0.0253 -0.006,-0.004 -0.0125,-0.008 -0.0181,-0.0119 -0.001,-8.4e-4 -0.004,-0.003 -0.005,-0.004 -0.009,-0.005 -0.008,-0.005 -0.0222,-0.0129 l -5.1e-4,-5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.2e-4 h -5.2e-4 c -0.002,-9.4e-4 -0.005,-0.002 -0.008,-0.003 -0.005,-0.002 -0.009,-0.004 -0.014,-0.007 l -5.2e-4,-5.2e-4 h -5.2e-4 c -0.002,-6.2e-4 -0.004,-0.002 -0.005,-0.002 -4e-4,-2.1e-4 -8e-4,-3.9e-4 -10e-4,-5.1e-4 h -5.2e-4 c -0.003,-10e-4 -0.002,-6.8e-4 -0.0176,-0.008 -0.002,-0.001 -0.004,-0.002 -0.007,-0.004 -7.6e-4,-3.3e-4 -0.002,-6.8e-4 -0.003,-0.001 -8.8e-4,-2.9e-4 -0.002,-6.9e-4 -0.003,-0.001 -0.003,-0.001 -0.007,-0.003 -0.009,-0.004 h -5.1e-4 v -5.2e-4 c -0.0113,-0.004 -0.0224,-0.009 -0.0331,-0.0145 0.007,0.003 0.0267,0.0121 0.0202,0.009 -0.0116,-0.006 -0.0244,-0.0104 -0.0357,-0.0171 -0.004,-0.002 -0.007,-0.004 -0.0109,-0.007 l -5.2e-4,-5.1e-4 h -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 c -2.2e-4,-1.8e-4 -8.3e-4,-8.4e-4 -0.001,-10e-4 l -5.1e-4,-5.2e-4 v -5.2e-4 h -5.2e-4 c -0.003,-0.001 -0.005,-0.003 -0.008,-0.005 -8.9e-4,-4.7e-4 -0.002,-0.001 -0.003,-0.002 l -0.002,-10e-4 -0.31833,-0.12868 -0.12299,-0.0501 c -0.01,-0.003 -0.0192,-0.006 -0.0284,-0.01 -0.005,-0.001 -0.0102,-0.003 -0.0202,-0.006 h -5.1e-4 l -5.2e-4,-5.2e-4 -5.2e-4,-5.2e-4 h -5.1e-4 c -10e-4,-3.6e-4 -0.002,-6.8e-4 -0.003,-10e-4 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 -5.2e-4,-5.1e-4 h -5.2e-4 -5.1e-4 c -0.0128,-0.002 -0.0258,-0.005 -0.0382,-0.009 -0.0194,-0.007 -0.0302,-0.008 -0.0651,-0.0269 -0.004,-0.001 -0.007,-0.003 -0.0109,-0.004 -0.007,-0.002 -0.0141,-0.004 -0.0212,-0.007 0.005,0.002 0.0132,0.004 0.01,0.003 -0.004,-10e-4 -0.008,-0.002 -0.0119,-0.004 h -5.2e-4 -5.2e-4 c -0.002,-6.1e-4 -0.004,-0.001 -0.006,-0.002 -0.002,-7e-4 -0.009,-0.003 -0.016,-0.006 -0.004,-9.2e-4 -0.006,-0.002 -0.007,-0.002 h -5.2e-4 c -0.001,-2.2e-4 -0.003,-2.9e-4 -0.004,-5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 v -5.2e-4 c -0.0258,-0.007 -0.0352,-0.0104 -0.061,-0.0202 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.2e-4 -5.1e-4 c -0.009,-0.002 -0.018,-0.005 -0.0269,-0.008 -7.7e-4,-2.6e-4 -8.3e-4,-2.8e-4 -0.002,-5.2e-4 -5.1e-4,-1.7e-4 -10e-4,-3.4e-4 -0.002,-5.1e-4 -0.015,-0.005 -0.0179,-0.006 -0.0346,-0.0129 h -5.2e-4 v -5.2e-4 c -0.003,-10e-4 -0.005,-0.002 -0.008,-0.003 -0.01,-0.003 -0.0201,-0.007 -0.03,-0.0103 1.7e-4,8e-5 -10e-4,-3.1e-4 -0.002,-5.1e-4 -0.001,-3.2e-4 -0.002,-7.4e-4 -0.003,-0.001 -4.7e-4,-1.5e-4 -0.001,-3.6e-4 -0.002,-5.1e-4 h -5.1e-4 c -0.014,-0.004 -0.018,-0.006 -0.0336,-0.0114 -0.0148,-0.006 -0.0136,-0.005 -0.0248,-0.0103 -0.003,-0.001 -0.005,-0.002 -0.008,-0.004 -0.003,-0.001 -0.005,-0.002 -0.007,-0.003 -8.3e-4,-3.8e-4 -0.002,-7e-4 -0.003,-0.001 v -5.1e-4 h -5.2e-4 c -0.01,-0.004 -0.0191,-0.008 -0.0269,-0.0109 h -5.1e-4 v -5.1e-4 h -5.2e-4 -5.2e-4 c -0.002,-6.3e-4 -0.006,-0.002 -0.0108,-0.004 -0.004,-0.001 -0.008,-0.003 -0.0114,-0.005 l -5.2e-4,-5.2e-4 h -5.1e-4 c -0.004,-0.001 -0.007,-0.002 -0.0109,-0.004 -0.007,-0.003 -0.0105,-0.004 -0.0186,-0.008 h -5.1e-4 c -0.001,-5.3e-4 -0.004,-0.001 -0.006,-0.002 h -5.2e-4 -5.1e-4 v -5.2e-4 c -0.003,-0.001 -0.004,-0.002 -0.006,-0.003 -5e-4,-1.9e-4 -0.001,-3.2e-4 -0.002,-5.1e-4 -2.3e-4,-9e-5 -8.6e-4,-4.5e-4 -10e-4,-5.2e-4 -0.003,-0.001 -0.006,-0.002 -0.009,-0.004 -5.1e-4,-2e-4 -0.001,-3.1e-4 -0.002,-5.1e-4 h -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 c -0.002,-4.5e-4 -0.006,-0.002 -0.009,-0.003 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 v -5.1e-4 h -5.1e-4 c -0.003,-8.6e-4 -0.005,-0.001 -0.006,-0.002 h -5.1e-4 c -0.004,-10e-4 -0.005,-0.002 -0.0129,-0.004 -6.8e-4,-2.2e-4 -0.0118,-0.004 -0.0202,-0.007 -0.001,-4.4e-4 -0.003,-6.6e-4 -0.004,-0.001 h -5.2e-4 v -5.1e-4 h -5.2e-4 c -0.001,-3.5e-4 -0.004,-0.001 -0.004,-0.002 -0.0172,-0.005 -0.0267,-0.008 -0.045,-0.014 -0.0254,-0.009 -0.0393,-0.0149 -0.0522,-0.0202 -0.0169,-0.006 -0.0326,-0.0125 -0.0476,-0.0176 -0.0152,-0.006 -0.0302,-0.0106 -0.0455,-0.016 -0.004,-0.001 -0.008,-0.003 -0.0124,-0.004 -10e-4,-3.5e-4 -0.003,-0.001 -0.005,-0.002 -10e-4,-4.4e-4 -0.002,-7.7e-4 -0.003,-0.001 l -5.2e-4,-5.1e-4 c -0.006,-0.002 -0.0127,-0.004 -0.0191,-0.006 -0.01,-0.003 -0.0209,-0.007 -0.032,-0.0109 -0.002,-6e-4 -0.003,-9.4e-4 -0.005,-0.002 l -5.2e-4,-5.1e-4 c -5.3e-4,-1.9e-4 -10e-4,-3.4e-4 -0.002,-5.2e-4 -0.0146,-0.005 -0.0215,-0.007 -0.0367,-0.0129 -0.005,-0.002 -0.0103,-0.005 -0.0155,-0.007 h -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 -5.2e-4 -5.1e-4 v -5.2e-4 -5.2e-4 h -5.2e-4 -5.2e-4 -5.1e-4 l -5.2e-4,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 -5.2e-4 -5.1e-4 l -5.2e-4,-5.2e-4 -5.2e-4,-5.2e-4 h -5.1e-4 c -0.002,-9.4e-4 -0.005,-0.002 -0.009,-0.004 -0.002,-8e-4 -0.006,-0.002 -0.0114,-0.004 0.002,5.8e-4 0.005,0.001 0.009,0.002 -0.004,-0.001 -0.005,-0.001 -0.007,-0.002 h -5.2e-4 c -7e-4,-2e-4 -0.002,-3.5e-4 -0.003,-5.1e-4 h -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 c -6.4e-4,-1.8e-4 -7.5e-4,-4.1e-4 -10e-4,-5.2e-4 -0.001,-3.7e-4 -0.004,-10e-4 -0.005,-0.002 -0.002,-6.3e-4 -0.003,-6.4e-4 -0.008,-0.002 -6.8e-4,-2.5e-4 -0.002,-7.9e-4 -0.003,-0.001 -0.003,-9.3e-4 -0.006,-0.002 -0.0108,-0.004 -0.005,-0.001 -0.009,-0.002 -0.014,-0.004 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 c -3.7e-4,1e-5 -0.001,3e-5 -0.002,0 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 v -5.1e-4 -5.2e-4 c -0.0217,-0.004 -0.0435,-0.008 -0.0651,-0.0124 -0.0169,-0.004 -0.0362,-0.008 -0.0517,-0.0124 -0.0149,-0.004 -0.0298,-0.008 -0.0444,-0.0124 -2.1e-4,-8e-5 -0.001,-4.3e-4 -0.002,-5.1e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 c -0.0193,-0.006 -0.0306,-0.01 -0.0455,-0.0155 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 c -2.7e-4,-9e-5 -7.8e-4,8e-5 -10e-4,0 v -5.1e-4 h -5.2e-4 v -5.2e-4 h -5.1e-4 -5.2e-4 c -0.002,-6.1e-4 -0.004,-0.001 -0.006,-0.002 h -5.2e-4 c -9.4e-4,-1.2e-4 -0.003,-4.1e-4 -0.004,-5.1e-4 h -5.1e-4 -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 v -5.2e-4 -5.1e-4 c -0.011,-0.003 -0.0194,-0.006 -0.0305,-0.009 -0.002,-7.9e-4 -0.004,-0.001 -0.007,-0.002 h -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 -5.2e-4 -5.2e-4 v -5.2e-4 h -5.1e-4 -5.2e-4 c -0.007,-0.002 -0.0134,-0.003 -0.015,-0.004 h -5.1e-4 v -5.2e-4 h -5.2e-4 c -8.1e-4,-1.7e-4 -0.005,-0.002 -0.009,-0.003 -4.4e-4,-1.4e-4 -0.002,-3.5e-4 -0.002,-5.2e-4 -0.007,-0.002 -0.0131,-0.003 -0.017,-0.005 -0.0199,-0.006 -0.0375,-0.0108 -0.0553,-0.0176 -0.013,-0.005 -0.0154,-0.007 -0.0202,-0.009 7.7e-4,2.9e-4 -9e-5,-1.7e-4 -0.005,-0.002 -9.2e-4,-3.4e-4 -0.001,-3.5e-4 -0.002,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.2e-4 c -0.002,-6.5e-4 -0.008,-0.003 -0.0134,-0.005 -0.002,-7.8e-4 -0.005,-0.002 -0.006,-0.002 h -5.1e-4 v -5.1e-4 h -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.2e-4 -5.1e-4,-5.2e-4 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 -5.2e-4 c -6.8e-4,-1.3e-4 -0.001,-3.9e-4 -0.002,-5.2e-4 h -5.2e-4 c -0.002,-4e-4 -0.004,-0.001 -0.006,-0.002 -0.002,-2.4e-4 -0.004,-7.8e-4 -0.006,-10e-4 h -5.1e-4 -5.2e-4 -5.2e-4 -5.1e-4 v -5.2e-4 h -5.2e-4 c -0.009,-0.001 -0.0112,-0.001 -0.0217,-0.004 -0.003,-6.3e-4 -0.006,-0.001 -0.009,-0.002 -8.8e-4,-2.1e-4 -0.003,-10e-4 -0.003,-10e-4 -10e-4,-2.4e-4 -0.003,-2.8e-4 -0.004,-5.2e-4 -0.0122,-0.002 -0.0103,-0.002 -0.0212,-0.004 h -5.1e-4 v -5.2e-4 h -5.2e-4 -5.2e-4 l -5.2e-4,-5.1e-4 h -5.1e-4 c -0.002,-1.7e-4 -0.005,-7.5e-4 -0.007,-10e-4 h -5.1e-4 c -0.0165,-0.002 -0.0331,-0.006 -0.0491,-0.0103 h -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 -5.1e-4 -5.2e-4 v -5.2e-4 h -5.2e-4 c -0.0141,-0.002 -0.028,-0.003 -0.0418,-0.006 -0.0139,-0.003 -0.0184,-0.004 -0.0284,-0.006 -3.1e-4,-1.3e-4 -0.004,-7.4e-4 -0.008,-0.002 -5.7e-4,-1.2e-4 -4.2e-4,1.3e-4 -0.001,0 -0.008,-0.002 -0.0199,-0.004 -0.0243,-0.005 -0.004,-6.5e-4 -0.008,-0.002 -0.0114,-0.003 -2.9e-4,-1e-5 -7.8e-4,4e-5 -0.001,0 -4.9e-4,-1.1e-4 -0.002,-4.5e-4 -0.002,-5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 -5.2e-4 -5.1e-4 v -5.1e-4 h -5.2e-4 c -0.001,-1.8e-4 -0.002,-3.4e-4 -0.004,-5.2e-4 h -5.1e-4 c -2.3e-4,1e-5 -0.001,0 -0.002,0 h -5.2e-4 c -0.0115,-0.002 -0.0153,-0.002 -0.0269,-0.005 -0.004,-7.7e-4 -0.008,-0.002 -0.0114,-0.003 -8.3e-4,-1.7e-4 -0.002,-3.6e-4 -0.003,-5.2e-4 h -5.1e-4 c -9.4e-4,-2e-4 -0.003,-2.6e-4 -0.004,-5.1e-4 h -5.1e-4 c -0.002,-4.2e-4 -0.004,-0.001 -0.006,-0.002 -0.001,-3.2e-4 -0.003,-7e-4 -0.004,-10e-4 -0.004,-6.8e-4 -0.006,-0.001 -0.01,-0.002 -0.005,-9e-4 -0.01,-0.002 -0.0119,-0.002 0.001,1.6e-4 0.004,2.5e-4 0.006,5.1e-4 -0.002,-2.8e-4 -0.005,-7.2e-4 -0.007,-0.001 h -5.1e-4 -5.2e-4 -5.2e-4 c -0.0198,-0.003 -0.0397,-0.007 -0.0589,-0.0129 -2.1e-4,-4e-5 -0.001,3e-5 -0.002,0 v -5.2e-4 c -10e-4,-2.2e-4 -0.003,-6.6e-4 -0.004,-0.001 -7.6e-4,-2.1e-4 -0.001,-3e-4 -0.002,-5.2e-4 h -5.2e-4 -5.1e-4 -5.2e-4 l -5.2e-4,-5.1e-4 c -0.002,-4.9e-4 -0.005,-10e-4 -0.0108,-0.003 -0.0112,-0.003 -0.0291,-0.006 -0.045,-0.0114 h -5.1e-4 -5.2e-4 c -0.0189,-0.005 -0.03,-0.009 -0.0377,-0.0108 -0.003,-8.5e-4 -0.005,-0.001 -0.016,-0.005 -0.004,-0.001 -0.009,-0.003 -0.0129,-0.004 0.003,8.2e-4 0.006,0.001 0.008,0.002 -0.0202,-0.006 -0.0398,-0.013 -0.0589,-0.0222 -0.002,-7.9e-4 -0.005,-0.003 -0.007,-0.004 -0.001,-5e-4 -0.002,-0.001 -0.003,-0.002 -0.001,-5e-4 -0.003,-0.001 -0.005,-0.002 -3.6e-4,-1.7e-4 -6.8e-4,-3.5e-4 -10e-4,-5.2e-4 -3.6e-4,-1.6e-4 -6.7e-4,-3.5e-4 -0.001,-5.1e-4 -1.5e-4,-6e-5 -6.5e-4,-3.1e-4 -0.001,-5.2e-4 h -5.1e-4 l -5.2e-4,-5.2e-4 h -5.2e-4 c -3.3e-4,-1.5e-4 -6.6e-4,-3.4e-4 -0.001,-5.1e-4 v -5.2e-4 h -5.2e-4 c -0.003,-0.001 -0.007,-0.002 -0.0109,-0.004 -7.3e-4,-2.9e-4 -10e-4,-4.2e-4 -0.002,-5.1e-4 v -5.2e-4 h -5.2e-4 c -0.004,-0.002 -0.007,-0.003 -0.008,-0.003 l -5.2e-4,-5.2e-4 h -5.2e-4 c -0.001,-3.2e-4 -0.002,-6.4e-4 -0.003,-10e-4 h -5.1e-4 l -5.2e-4,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 -5.2e-4,-5.2e-4 h -5.2e-4 -5.1e-4 c -8.2e-4,-3.1e-4 -0.002,-6.7e-4 -0.003,-0.001 l -5.1e-4,-5.2e-4 h -5.2e-4 c -0.004,-0.001 -0.008,-0.003 -0.0119,-0.004 v -5.2e-4 h -5.2e-4 -5.2e-4 l -5.1e-4,-5.2e-4 h -5.2e-4 c -0.003,-4.7e-4 -0.006,-0.001 -0.009,-0.002 -0.005,-10e-4 -0.0111,-0.003 -0.0165,-0.005 -0.002,-4e-4 -0.008,-0.002 -0.0145,-0.004 h -5.2e-4 c -2e-4,-2e-5 -10e-4,-4.7e-4 -0.002,-5.1e-4 h -5.2e-4 c -0.003,-8.5e-4 -0.006,-0.002 -0.008,-0.003 h -5.2e-4 c -0.0114,-0.001 -0.0229,-0.004 -0.0341,-0.007 -5e-4,-1.3e-4 -0.001,7e-5 -0.002,0 l -5.1e-4,-5.1e-4 h -5.2e-4 c -0.0168,-0.003 -0.0335,-0.006 -0.0496,-0.0119 -0.002,-6.3e-4 -0.003,-0.001 -0.005,-0.002 h -5.2e-4 v -5.2e-4 h -5.2e-4 -5.1e-4 c -4.9e-4,-1.4e-4 -0.001,-3.7e-4 -0.002,-5.2e-4 -0.004,-0.001 -0.007,-0.002 -0.0114,-0.004 -0.002,-5.4e-4 -0.003,-0.001 -0.005,-0.002 -5e-4,-1.9e-4 -10e-4,-3.3e-4 -0.002,-5.2e-4 -5e-4,-1.9e-4 -0.001,-3.2e-4 -0.002,-5.2e-4 -7.6e-4,-2.1e-4 -0.001,-2.9e-4 -0.002,-5.1e-4 -0.0107,-0.003 -0.0213,-0.007 -0.0315,-0.0109 0.009,0.004 0.0143,0.006 -0.0202,-0.009 -0.002,-9.9e-4 -0.004,-0.003 -0.007,-0.004 -3.4e-4,-1.6e-4 -7e-4,-3.5e-4 -0.001,-5.1e-4 h -5.1e-4 c -0.0167,-0.001 -0.0333,-0.004 -0.0496,-0.008 -10e-4,-3.1e-4 -0.002,-7.1e-4 -0.004,-0.001 -0.001,-2.2e-4 -0.002,-2.7e-4 -0.003,-5.1e-4 -0.002,-3.7e-4 -0.003,-0.001 -0.005,-0.002 -3e-4,-9e-5 -6.5e-4,-4e-4 -10e-4,-5.2e-4 h -5.2e-4 c -0.003,-8.3e-4 -0.006,-0.002 -0.009,-0.003 -6.8e-4,-2.1e-4 -10e-4,-3.2e-4 -0.002,-5.2e-4 -0.002,-4.5e-4 -0.003,-6e-4 -0.005,-10e-4 -8.7e-4,-1.3e-4 -0.002,-3.9e-4 -0.003,-5.2e-4 -6.1e-4,-5e-5 -0.002,-4.2e-4 -0.003,-5.2e-4 -0.003,-3.1e-4 -0.006,-7.2e-4 -0.009,-0.001 h -5.2e-4 c -0.0108,-10e-4 -0.0219,-0.003 -0.0326,-0.005 h -5.2e-4 c -2.2e-4,-5e-5 -0.001,-4.7e-4 -0.002,-5.2e-4 h -5.2e-4 c -0.0113,-0.001 -0.0225,-0.004 -0.0336,-0.005 -3.2e-4,-8e-5 -6.7e-4,-4.6e-4 -0.001,-5.2e-4 -0.005,-8e-4 -0.0136,5.9e-4 -0.0181,-5.2e-4 h -5.2e-4 l -5.1e-4,-5.1e-4 h -5.2e-4 l -5.2e-4,-5.2e-4 h -5.1e-4 v -5.2e-4 l -5.2e-4,-5.1e-4 v -5.2e-4 -5.2e-4 -5.1e-4 c -0.0141,-0.003 -0.0278,-0.007 -0.0413,-0.0124 -0.004,-0.001 -0.006,-0.002 -0.008,-0.003 -8.6e-4,-2.8e-4 -0.0029,-0.001 -0.0041,-0.002 -9.04e-4,-1.7e-4 -0.0018,-3.2e-4 -0.0026,-5.2e-4 h -5.17e-4 v -5.1e-4 h -5.17e-4 c -0.0014,-5.1e-4 -0.0026,-8.3e-4 -0.0047,-0.002 h -5.17e-4 c -7.97e-4,-2.8e-4 -0.0017,-7.2e-4 -0.0026,-0.001 -0.0037,-9.1e-4 -0.0072,-0.002 -0.01085,-0.003 -0.0042,-0.001 -0.007,-0.002 -0.01033,-0.004 h -5.17e-4 c -8.85e-4,-3.9e-4 -0.0035,-0.001 -0.0052,-0.002 -5.97e-4,-1.7e-4 -9.57e-4,-3.4e-4 -0.0016,-5.1e-4 h -5.16e-4 -5.17e-4 c -8.58e-4,-3e-4 -7.57e-4,-2.5e-4 -0.0015,-5.2e-4 l -5.17e-4,-5.2e-4 h -5.17e-4 -5.17e-4 -5.16e-4 -5.17e-4 v -5.1e-4 h -5.17e-4 c -1.95e-4,-6e-5 -7.04e-4,-4.1e-4 -0.001,-5.2e-4 h -5.17e-4 -5.17e-4 c -0.0045,1.5e-4 -0.0089,5.2e-4 -0.01344,5.2e-4 -0.02156,0.002 -0.04278,-0.003 -0.06408,-0.004 -0.003,2e-5 -0.0058,-4.3e-4 -0.0088,-5.1e-4 -0.0088,-2e-4 -0.01756,5e-5 -0.02635,0 -0.0022,-1e-5 -0.0045,1e-5 -0.0067,0 -0.0047,5.9e-4 -0.0097,6.1e-4 -0.01447,0.001 h -5.17e-4 l -5.16e-4,5.2e-4 h -5.17e-4 -5.17e-4 -5.17e-4 -5.17e-4 -5.16e-4 c -0.002,1.9e-4 -0.0037,9.3e-4 -0.0057,0.001 -0.0092,0.002 -0.01815,0.003 -0.02739,0.004 -0.01904,0.003 -0.03808,0.006 -0.05736,0.007 -6.56e-4,4e-5 -0.0014,-5e-5 -0.0021,0 h -5.17e-4 -5.17e-4 c -0.0018,2.8e-4 -0.0038,8.4e-4 -0.0057,0.001 -0.0094,0.001 -0.0088,0.001 -0.01809,0.002 -0.0016,1.3e-4 -0.007,6.7e-4 -0.01033,10e-4 -0.0039,4.7e-4 -0.0071,0.001 -0.0098,0.002 h -5.16e-4 -5.17e-4 -5.17e-4 c -0.01683,0.002 -0.01204,0.001 -0.03462,0.003 -0.0151,3.7e-4 -0.03038,2.6e-4 -0.04547,0 -0.0099,-1.6e-4 -0.01956,-4.7e-4 -0.02946,-5.1e-4 -0.0022,-1e-4 -0.0059,-2e-5 -0.0098,0 -4.3e-4,0 -6.68e-4,-1e-5 -10e-4,0 -0.0097,0.001 -0.01967,0.002 -0.02946,0.003 -0.0051,3.5e-4 -0.0075,3.4e-4 -0.01033,5.1e-4 h -5.17e-4 -5.16e-4 -5.17e-4 -5.17e-4 -5.17e-4 l -5.16e-4,5.2e-4 c -4.42e-4,2e-5 -0.0011,-2e-5 -0.0016,0 -0.01062,0.002 -0.02134,0.003 -0.03204,0.004 -0.03273,0.009 -0.06879,0.0144 -0.108521,0.0171 l -5.16e-4,5.2e-4 c -0.0058,0.004 -0.0116,0.009 -0.01757,0.0124 -0.0031,0.002 -0.0061,0.004 -0.0093,0.006 h -5.17e-4 l -5.17e-4,5.2e-4 -5.16e-4,5.2e-4 c -0.0018,10e-4 -0.004,0.002 -0.0057,0.003 -0.01016,0.007 -0.02137,0.0129 -0.02997,0.017 -0.0022,0.002 -0.0045,0.003 -0.0067,0.005 -0.01927,0.0103 -0.02753,0.015 -0.03101,0.017 -0.0053,0.003 -0.0105,0.006 -0.01602,0.009 -0.0041,0.002 -0.0079,0.003 -0.01137,0.005 h -5.17e-4 -5.17e-4 l -5.17e-4,0.001 h -5.17e-4 -5.16e-4 -5.17e-4 -5.17e-4 v 5.1e-4 h -5.17e-4 c -3e-4,1.3e-4 -0.0012,-1.3e-4 -0.0015,0 l -5.17e-4,0.001 h -5.16e-4 v 5.1e-4 h -5.17e-4 -5.17e-4 -5.17e-4 -5.16e-4 l -5.17e-4,10e-4 h -5.17e-4 c -0.0043,0.002 -0.0086,0.004 -0.01292,0.006 -0.0085,0.003 -0.01286,0.005 -0.01705,0.006 h -5.17e-4 l -5.17e-4,5.2e-4 -0.134875,0.0909 -0.207223,0.13849 -0.154512,0.12713 -0.154513,0.12712 -0.257865,0.67748 -0.257866,0.67696 -0.835608,2.40192 -0.835608,2.40192 -0.187585,0.19947 -0.187069,0.19896 -0.262516,0.0801 -0.263033,0.0796 -0.685746,0.10542 -0.686263,0.1049 -0.482658,-0.19999 -0.482658,-0.19999 -1.645378,0.13746 -1.645377,0.13798 -0.487826,0.27802 -0.487825,0.2775 -0.446485,0.42013 -0.445967,0.42065 -0.245463,0.43046 -0.245463,0.43047 -0.349333,0.88883 -0.349849,0.88884 -0.077,0.18603 -0.07648,0.18604 -0.216525,0.14211 -0.216524,0.14211 -1.30793,0.14521 -1.307414,0.14572 -0.130742,0.10852 -0.130741,0.10801 v 0.0827 0.0822 l 0.158647,0.20102 0.15813,0.20154 0.0093,0.42426 0.0098,0.42375 -0.178801,0.16691 -0.1788,0.1664 0.05116,0.13281 0.05116,0.13281 0.192753,0.0667 0.192753,0.0667 1.312065,-0.0517 1.312581,-0.0512 0.14056,0.0672 0.141077,0.0672 0.269234,0.80771 0.268718,0.8077 0.450618,0.69608 0.450619,0.69608 0.443383,0.53847 0.443384,0.53847 0.433049,0.27698 0.433048,0.27699 0.418063,0.14211 0.418062,0.14159 0.512113,0.0481 0.512114,0.0476 1.076937,-0.13901 1.076936,-0.13901 0.404627,-0.17364 0.404626,-0.17311 0.274919,-0.21033 0.275435,-0.2098 v -0.13436 -0.13488 l -0.486792,0.0548 -0.486792,0.0548 -0.550354,0.0475 -0.550354,0.047 0.127124,-0.0801 0.126608,-0.0796 0.889351,-0.10129 0.888835,-0.10129 0.42323,-0.11834 0.42323,-0.11782 0.749825,-0.59531 0.749308,-0.59531 0.817004,-0.46147 0.817005,-0.46147 0.973589,-0.46561 0.97358,-0.4656 0.71934,-0.20568 0.71985,-0.20567 0.27078,-0.0584 0.27027,-0.0579 0.22945,0.36535 0.22944,0.36536 0.31626,0.36121 0.31626,0.36122 0.21291,0.57464 0.2129,0.57413 0.14728,0.8816 0.1478,0.8816 -0.0522,1.82366 -0.0512,1.82366 -0.13384,1.16324 -0.13332,1.16375 -0.16485,0.64337 -0.16433,0.64337 -0.27492,0.56379 -0.27492,0.56379 -0.70022,0.70435 -0.70021,0.70435 v 0.11938 0.11885 l 0.19068,0.12248 0.19017,0.12195 0.52555,0.1018 0.52503,0.10129 0.78704,-0.0398 0.78703,-0.0403 0.25425,-0.11524 0.25373,-0.11575 -0.53072,-0.045 -0.5302,-0.045 -0.005,-0.0687 -0.005,-0.0682 0.59325,-0.74311 0.59324,-0.74311 0.56328,-0.41341 0.56327,-0.41341 0.19689,-0.34158 0.19637,-0.34158 0.13281,-0.63459 0.13229,-0.6351 0.0868,-0.8046 0.0868,-0.80409 0.0832,-1.27021 0.0832,-1.26969 0.006,-0.6351 0.006,-0.6351 0.10129,-0.29611 0.10077,-0.29662 0.18552,-0.0264 0.185,-0.0269 0.14366,0.21911 0.14366,0.21963 -0.0579,0.99218 -0.0579,0.99271 -0.12712,1.43919 -0.12712,1.43918 -0.12661,0.97359 -0.12661,0.97358 -0.0434,0.21188 -0.0439,0.21187 0.0889,-0.13901 0.0894,-0.13901 0.13384,-0.57723 0.13385,-0.57722 0.16174,-1.56993 0.16175,-1.56993 0.0496,-1.14308 0.0496,-1.14257 0.2036,-0.76223 0.20413,-0.76171 0.17156,-1.00614 0.17157,-1.00562 v -0.31729 -0.3173 l 0.10542,-0.0842 0.10593,-0.0832 0.43047,-0.0548 0.43046,-0.0548 0.13126,0.14882 0.13178,0.14883 0.20929,0.42427 0.2098,0.42323 0.21963,0.67696 0.22014,0.67748 0.20981,0.84697 0.2098,0.84646 0.13126,0.71985 0.13074,0.71882 0.12868,1.01596 0.12815,1.01648 0.20929,2.49804 0.20878,2.497 0.0517,0.25787 0.0522,0.25631 -1.21388,0.005 -1.21337,0.005 -0.84646,0.1912 -0.84697,0.19121 -0.29508,0.12247 -0.29507,0.12247 -0.19172,0.18035 -0.19172,0.17984 0.003,0.1757 0.003,0.1757 0.18759,0.22841 0.18707,0.22892 0.31522,0.13126 0.31471,0.13126 0.87024,-0.006 0.87074,-0.006 0.76171,-0.0682 0.76223,-0.0687 0.061,0.0351 0.0594,0.0346 -0.0904,0.19947 -0.091,0.19895 0.26097,1.85105 0.26045,1.85053 0.17053,1.31258 0.17053,1.31207 0.37931,3.43597 0.37982,3.43544 0.046,0.12041 0.0465,0.12041 0.24856,0.16691 0.24857,0.16692 0.0496,0.1974 0.0496,0.1974 0.29352,0.17312 0.29352,0.17312 v 0.0579 0.0579 l -0.16691,0.14315 -0.16692,0.14262 -0.0568,0.22583 -0.0563,0.22582 0.0853,1.13068 0.0858,1.13017 0.10387,0.12506 0.10387,0.12505 -0.0987,0.29869 -0.0987,0.29869 0.10336,0.48783 0.10283,0.48782 0.10904,0.1788 0.10904,0.1788 0.28112,0.0667 0.2806,0.0672 0.15089,0.0181 0.1509,0.0176 0.0563,0.0563 0.0553,0.0558 0.51315,-0.0491 0.51418,-0.0501 0.21187,-0.0517 0.21136,-0.0512 0.0801,0.0191 0.0801,0.0191 0.36586,-0.13953 0.36587,-0.13953 0.0718,-0.14107 0.0734,-0.14056 0.0563,-0.52607 0.0579,-0.52607 -0.13746,-0.26561 -0.13746,-0.26614 0.10129,-0.24494 0.1018,-0.24443 -0.0584,-0.74156 -0.0584,-0.74156 -0.0868,-0.52968 -0.0889,-0.5302 -0.24856,-0.1943 -0.24857,-0.19431 0.32091,-0.20722 0.32091,-0.20722 0.0269,-0.22738 0.0264,-0.22789 0.3638,-0.17622 0.36432,-0.17621 0.14315,0.0553 0.14314,0.0548 -0.0543,0.24649 -0.0548,0.2465 0.18759,0.9493 0.18707,0.94929 0.0713,0.1385 0.0718,0.13849 0.3421,0.17777 0.3421,0.17828 0.31677,0.0506 0.31678,0.0501 0.54312,-0.0982 0.54312,-0.0987 0.37879,-0.17777 0.3793,-0.17828 0.2837,-0.28422 0.28423,-0.2837 v -0.23978 -0.23926 l -0.17674,-0.87954 -0.17725,-0.87953 -0.12506,-0.19637 -0.12505,-0.19585 0.10025,-0.12093 0.0997,-0.1204 -0.0899,-0.5302 -0.0899,-0.52969 -0.0434,-1.39681 -0.0434,-1.39733 -0.13178,-1.26969 -0.13126,-1.27021 -0.17415,-1.22783 -0.17415,-1.22732 -0.29249,-1.55391 -0.293,-1.55442 -0.12713,-0.24598 -0.12712,-0.24598 -0.16691,-0.10439 -0.1664,-0.10387 v -0.0672 -0.0656 l 0.21135,-0.0806 0.21188,-0.0806 v -0.17001 -0.17002 l -0.10594,-0.39532 -0.10542,-0.39481 -0.31471,-0.25322 -0.31471,-0.25269 -0.12971,-0.43305 -0.1297,-0.43253 -0.76843,-3.81011 -0.76843,-3.8101 0.032,-0.84646 0.0315,-0.84646 0.17829,-0.339 0.17828,-0.33848 0.24029,-0.22324 0.24082,-0.22324 0.25424,-0.12713 0.25374,-0.12764 1.39681,-0.44803 1.39733,-0.44752 0.31729,-0.31368 0.31781,-0.31367 0.0517,-0.25787 0.0517,-0.25786 0.20205,-0.19017 0.20257,-0.18965 v -0.20258 -0.20257 l -0.0796,-0.14883 -0.0796,-0.14882 -0.32298,-0.22015 -0.32246,-0.22065 -0.52348,-0.10077 -0.52348,-0.10077 -1.04283,0.14159 -1.04283,0.14108 -0.19017,0.047 -0.19069,0.0465 v -0.12144 -0.12144 l 0.21963,-0.20515 0.21962,-0.20516 0.12144,-0.23306 0.12092,-0.23254 h 0.13074 0.13023 l 0.2775,-0.21188 0.2775,-0.21135 h 0.0889 0.0889 l 0.0548,0.14262 0.0553,0.14315 -0.13229,0.0739 -0.13229,0.0739 0.0491,0.0801 0.0491,0.0796 0.31265,-5.1e-4 0.31264,-5.2e-4 0.97358,-0.13384 0.97359,-0.13436 0.42323,-0.032 0.42323,-0.0326 0.50798,-0.14572 0.50797,-0.14573 0.19431,-0.12247 0.19378,-0.12248 0.20206,-0.34778 0.20154,-0.3483 0.0589,-0.31833 0.0584,-0.31832 -0.1049,-0.12609 -0.10439,-0.12609 0.10232,-0.12299 0.10232,-0.12351 -0.0434,-0.0703 -0.0429,-0.0703 -0.25114,-0.0191 -0.25115,-0.0186 -0.0501,0.13022 -0.0501,0.13074 -0.21239,-0.13901 -0.21239,-0.13952 -0.0362,0.11523 -0.0362,0.11524 -0.0496,0.22686 -0.0496,0.22738 -0.1478,0.0439 -0.14779,0.0444 -1.00666,0.1297 -1.00614,0.12971 -0.11213,0.0925 -0.11163,0.0925 -0.0889,-0.0548 -0.0889,-0.0548 v -0.2744 -0.27441 l -0.0889,-0.0889 -0.0884,-0.0884 -0.0398,-0.48162 -0.0398,-0.48162 0.25941,0.13435 0.25993,0.13436 1.03302,-0.10128 1.03301,-0.10077 0.1974,-0.12196 0.19792,-0.12195 0.0491,0.0491 0.0491,0.0491 0.015,0.19069 0.0145,0.19068 0.39843,-0.008 0.39791,-0.008 0.0951,-0.0956 0.0951,-0.0951 -0.10181,-0.34003 -0.1018,-0.34055 -0.31471,-0.30644 -0.31419,-0.30696 -0.19586,-0.0754 -0.19637,-0.0744 -0.69298,5.1e-4 -0.69298,0.001 -1.43919,0.16691 -1.43918,0.16692 -0.0636,10e-4 -0.0636,0.001 v 0.0848 0.0847 h 0.11059 0.1111 l 0.1478,0.10801 0.14779,0.108 -0.0558,0.14573 -0.0563,0.14624 h -0.21549 -0.21549 l -0.4315,-0.21755 -0.43149,-0.21705 -0.20464,-0.11523 -0.20464,-0.11524 -0.79065,-0.14883 -0.79013,-0.14883 -1.20613,0.10129 -1.20665,0.10128 -0.31729,-0.001 -0.31781,-5.2e-4 0.001,-0.14831 0.001,-0.14831 0.0465,-0.17363 0.0465,-0.17364 0.18551,-0.11782 0.18501,-0.11782 0.38085,-0.0579 0.38086,-0.0584 0.83509,-0.17932 0.83457,-0.17983 h 0.24495 0.24494 l 0.31626,-0.1695 0.31575,-0.16898 h 0.18035 0.18035 l 0.23151,-0.18242 0.23151,-0.1819 0.15348,-0.35812 0.15348,-0.3576 -0.0186,-0.37001 -0.0186,-0.37052 -0.25476,-0.0253 -0.25477,-0.0253 -0.10697,0.23461 -0.10697,0.23461 -0.15555,-0.20618 -0.15554,-0.20619 -0.0372,-0.29973 -0.0377,-0.2992 0.0243,-0.0289 0.0238,-0.0284 0.17105,0.0553 0.17156,0.0543 0.0548,0.17208 0.0548,0.17208 0.22479,-0.0558 0.22479,-0.0563 0.031,-0.0315 0.0315,-0.0315 -0.0574,-0.30541 -0.0574,-0.3054 -0.29714,-0.32867 -0.29714,-0.32918 -0.26975,-0.0853 -0.26924,-0.0863 -0.67747,0.076 -0.67748,0.0754 -1.10071,0.20257 -1.10019,0.20309 -0.1416,0.0631 -0.14107,0.063 -0.19741,-0.12712 -0.1974,-0.12764 -0.71985,0.0367 -0.71934,0.0362 -1.60869,0.25993 -1.60868,0.25994 -0.31781,0.0816 -0.3173,0.0822 -0.007,0.14469 -0.007,0.14521 -0.0894,-0.25424 -0.0889,-0.25373 -0.12919,-0.14832 -0.12868,-0.14831 h -0.19895 -0.19844 v 0.0506 0.0501 l 0.25425,0.23771 0.25373,0.23771 v 0.29094 0.29197 l -0.16175,0.14625 -0.16174,0.14624 -0.23823,-0.0274 -0.23823,-0.0274 -0.13126,-0.38034 -0.13177,-0.38034 -0.1571,-0.0305 -0.15658,-0.0305 -0.0723,0.11421 -0.0724,0.1142 -0.001,0.27027 -5.1e-4,0.27027 0.19844,0.23099 0.19843,0.23048 0.40153,-0.0155 0.40204,-0.015 0.16847,-0.0904 0.16898,-0.0899 0.12402,-0.2682 0.12454,-0.26872 -0.0501,0.8046 -0.0501,0.80409 -0.20516,1.65106 -0.20567,1.65106 -0.12816,0.93121 -0.12764,0.93121 10e-4,0.4656 0.001,0.46613 0.12661,0.67696 0.12661,0.67747 0.048,0.25787 0.0481,0.25787 -1.25728,0.0915 -1.25729,0.0904 -0.0372,-0.0336 -0.0372,-0.0336 0.14883,-1.45211 0.14831,-1.45211 0.11834,-0.22893 0.11834,-0.22841 -0.0977,-0.25889 -0.0982,-0.2589 0.23978,-2.39779 0.23926,-2.39727 0.0724,-0.88315 0.0734,-0.88367 0.2098,-0.30179 0.20981,-0.3023 0.19327,-0.12196 0.19275,-0.12247 0.33435,-0.0465 0.33434,-0.047 0.10749,-0.76171 0.10749,-0.76223 0.10077,-0.38085 0.10077,-0.38086 0.15606,-0.31471 0.15658,-0.31471 0.1633,-0.0884 0.16329,-0.0868 h 0.20826 0.20826 l 0.0517,0.13384 0.0512,0.13385 v 0.23409 0.23409 l 0.1757,0.15917 0.1757,0.15864 1.12707,0.0336 1.12706,0.0331 0.18862,-0.18862 0.18913,-0.18914 -0.0543,-0.47697 -0.0543,-0.47697 -0.11885,-0.44959 -0.11886,-0.4501 -0.13746,-0.21032 -0.13797,-0.21033 -0.26355,-0.16019 -0.26304,-0.15968 -0.56172,-0.10077 z m -11.63701,2.70061 c 2.9e-4,7e-5 7.2e-4,-6e-5 0.001,0 -3.1e-4,-6e-5 -7.3e-4,-4.3e-4 -0.001,-5.2e-4 z m 4.07158,1.51929 h 5.2e-4 v -5.2e-4 h -5.2e-4 z m -4.46794,-0.90744 -0.04806,0.23874 -0.04754,0.23823 -0.08888,0.23306 -0.08888,0.23306 -0.192237,-0.001 -0.192753,-0.002 -0.116272,-0.0739 -0.116272,-0.0734 0.12299,-0.36949 0.122473,-0.37 0.322978,-0.0269 z m 1.74305,0.56224 0.26406,0.0222 0.26407,0.0217 0.71778,0.28061 0.71727,0.2806 0.23048,0.17208 0.22996,0.17208 -0.11007,0.41445 -0.11059,0.41444 -0.0584,0.0568 -0.0584,0.0574 -0.21342,-0.11369 -0.21394,-0.11317 -0.59066,-0.18655 -0.59066,-0.18604 -0.35967,-0.20567 -0.35967,-0.20567 10e-4,-0.10232 0.001,-0.1018 0.11989,-0.339 z m 2.89026,0.55035 h 0.0424 0.0424 v 0.0424 0.0424 h -0.0424 -0.0424 v -0.0424 z m 15.25436,1.96112 0.11214,0.2682 0.11214,0.2682 -0.045,0.23307 -0.0444,0.23254 h -0.14883 -0.14934 l -0.10542,0.12712 -0.10542,0.12713 h -0.28061 -0.28112 l -0.0465,0.0749 -0.0465,0.0755 -0.27596,0.0429 -0.27595,0.0429 -0.17776,0.0579 -0.17777,0.0574 -0.10439,-0.40876 -0.1049,-0.40876 -0.0775,-0.19431 -0.0775,-0.1943 v -0.12816 -0.12816 l 0.37827,0.0563 0.37776,0.0568 0.40514,-0.0884 0.40514,-0.0884 0.36742,-0.0408 z m -25.33695,4.22197 0.130742,0.0501 0.130224,0.0501 v 0.0651 0.0651 l -0.249597,0.0496 -0.250114,0.0501 -0.892969,0.0579 -0.892968,0.0579 v -0.0915 -0.0915 l 0.147794,-0.045 0.148311,-0.045 0.864547,-0.0863 z m -3.929993,0.13281 0.381373,0.17311 0.380855,0.1726 0.287838,0.2434 0.287837,0.24339 0.628386,0.75913 0.628902,0.75964 0.156579,0.30696 0.15658,0.30696 0.129708,0.50281 0.130225,0.50281 0.05788,0.69867 0.05788,0.69866 h -0.04806 -0.04754 l -0.08578,-0.27491 -0.08578,-0.27544 -0.224793,-0.84646 -0.225309,-0.84646 -0.276986,-0.601 -0.276986,-0.60099 -0.321944,-0.35605 -0.321944,-0.35657 -0.345715,-0.19947 -0.345716,-0.19999 -0.436665,-0.12712 -0.436149,-0.12765 -0.622185,0.078 -0.621667,0.0775 -0.402043,0.19896 -0.402042,0.19843 v -0.1049 -0.10542 l 0.298689,-0.26252 0.29869,-0.262 0.357084,-0.18293 0.357601,-0.18294 0.465605,-0.004 z m -1.311547,1.12138 0.274918,0.0594 0.274919,0.0594 0.298173,0.1509 0.29869,0.15089 0.386539,0.41341 0.386023,0.4129 0.319877,0.64647 0.319361,0.64699 0.04393,0.38757 0.04341,0.38757 -0.116789,-0.016 -0.116789,-0.0155 -0.465604,-0.0264 -0.465605,-0.0264 -1.003039,-0.0863 -1.002523,-0.0863 -0.151412,-0.0806 -0.151412,-0.0811 0.02635,-1.23507 0.02584,-1.23507 0.09767,-0.11782 0.09767,-0.11782 0.289905,-0.0956 z m 33.63206,0.14986 0.11678,0.15916 0.11679,0.15968 -0.001,0.17467 -0.001,0.17415 -0.0625,0.0848 -0.062,0.0847 -0.13695,0.10698 -0.13746,0.10645 -0.0951,-0.11472 -0.0956,-0.11472 -0.0522,-0.25942 -0.0517,-0.25942 0.23099,-0.15141 z m 4.05401,0.54053 h 0.12196 0.12195 v 0.0847 0.0848 h -0.17415 -0.17415 l 0.0522,-0.0848 z m -33.693549,0.25425 h 0.245463 0.245463 l 0.530717,0.50798 0.530717,0.50746 0.296623,-0.007 0.296105,-0.007 0.495577,-0.062 0.495061,-0.0615 0.133842,0.20412 0.133842,0.20413 -0.100253,0.16019 -0.09974,0.15968 v 0.0434 0.0434 h 0.116789 0.116788 l 0.09457,0.11472 0.09508,0.11421 -0.07338,0.39067 -0.07286,0.39119 0.163298,0.10852 0.163298,0.10852 -0.05374,0.14005 -0.05323,0.13952 -0.185001,0.06 -0.184485,0.0594 0.148311,0.0398 0.147795,0.0393 v 0.1173 0.11679 l -0.274919,0.0476 -0.275435,0.0475 -0.592212,0.0837 -0.592729,0.0842 -0.338997,0.0501 -0.338998,0.0507 -0.104386,-0.36846 -0.104386,-0.36845 -0.08992,-0.41858 -0.08992,-0.41858 -0.412378,-1.21129 z m 13.283939,0.10904 0.0496,0.0796 0.0491,0.0801 -0.0961,0.0961 -0.0961,0.0961 -0.29869,-0.0258 -0.29869,-0.0253 -0.11265,0.0935 -0.11318,0.0935 -0.0444,-0.11575 -0.0444,-0.11576 0.0558,-0.0863 0.0558,-0.0863 0.447,-0.0419 z m -2.33319,0.22789 0.21136,0.0222 0.21187,0.0217 v 0.25269 0.25322 l -0.13074,0.0253 -0.13074,0.0248 -0.20567,-0.21859 -0.20567,-0.21911 0.12454,-0.0811 z m -12.009083,0.97565 0.131775,0.10801 0.132292,0.10852 0.195854,0.44183 0.195853,0.44183 0.0036,0.7705 0.0041,0.76998 -0.113172,0.13642 -0.113171,0.13643 -1.495516,0.0248 -1.495516,0.0248 -0.141593,0.0703 -0.14211,0.0698 -0.077,-0.36638 -0.077,-0.36639 0.03359,-0.0636 0.03307,-0.0636 h 0.241329 0.241329 l 1.044898,-0.13023 1.044897,-0.13074 0.185002,-0.0925 0.185001,-0.0925 0.101286,-0.24495 0.101286,-0.24494 -0.05013,-0.35761 -0.05013,-0.3576 -0.05994,-0.2961 z m 12.284003,0.20257 0.0367,0.19482 0.0367,0.19482 -0.0475,0.46612 -0.047,0.46561 -0.1943,0.0274 -0.19379,0.0274 0.0248,-0.6625 0.0248,-0.66249 0.17984,-0.0258 z m -16.226398,0.85525 h 0.08062 0.0801 v 0.14366 0.14366 l 0.227893,0.23719 0.227376,0.23772 h -0.2651 -0.264583 l -0.05219,0.0847 -0.05271,0.0847 -0.269751,-0.006 -0.270268,-0.006 -0.127124,-0.0579 -0.127124,-0.0579 -0.01757,-0.31109 -0.01809,-0.31109 0.424264,-0.092 z m 16.089978,0.82527 0.23978,0.28577 0.23977,0.28629 0.2589,-0.19069 0.2589,-0.19068 h 0.0811 0.0811 l 0.2744,0.34675 0.2744,0.34674 0.24081,-0.0481 0.2403,-0.0481 v 0.11782 0.11782 l -0.43564,0.47697 -0.43563,0.47646 -0.23771,0.12299 -0.23719,0.12299 h -0.28061 -0.2806 l -0.25373,-0.18035 -0.25373,-0.18035 -0.2744,-0.35708 -0.27389,-0.35761 5.2e-4,-0.16071 5.1e-4,-0.16123 0.14935,-0.22634 0.14934,-0.22686 0.2372,-0.18655 z m -16.6486,0.48266 0.211357,0.032 0.211873,0.032 0.190686,0.29352 0.190169,0.29352 v 0.2434 0.24288 l -0.06356,0.0398 -0.06356,0.0393 -0.523482,-0.24546 -0.523482,-0.24495 -0.04703,-0.076 -0.04651,-0.0755 0.08992,-0.2589 0.09043,-0.2589 0.141594,-0.0284 z m 1.143083,0.0331 v 0.64182 0.64182 h -0.08733 -0.08733 l -0.04444,-0.11523 -0.04444,-0.11524 0.02584,-0.49196 0.02584,-0.49145 0.105937,-0.0351 z m 4.233333,3.49643 v 0.0646 0.0646 l -0.31781,0.45475 -0.31781,0.45476 -0.361735,0.27595 -0.361735,0.27595 -0.315226,0.0878 -0.315743,0.0873 -0.338481,5.2e-4 h -0.33848 l -0.519349,-0.24546 -0.518831,-0.24547 -0.06305,-0.1018 -0.06305,-0.10232 0.28577,0.0724 0.285771,0.0729 0.804085,-5.1e-4 h 0.804602 l 0.417029,-0.21136 0.417029,-0.21136 0.408244,-0.39687 z m 12.621964,0.0734 h 0.14987 0.14986 l 0.19068,0.17777 0.19069,0.17725 0.0956,0.22479 0.0961,0.22479 0.0341,2.11667 0.0336,2.11666 -0.0977,1.21337 -0.0977,1.21336 -0.16382,0.53175 -0.16433,0.53175 -0.17725,0.22479 -0.17673,0.22531 -0.24236,0.12506 -0.24236,0.12557 h -0.13385 -0.13384 l -0.045,-0.11782 -0.0455,-0.11782 0.17156,-0.9617 0.17157,-0.9617 0.0868,-0.84697 0.0868,-0.84646 0.0894,-1.60869 0.0894,-1.60869 -0.0553,-0.71985 -0.0548,-0.71934 0.0966,-0.36018 z m -9.540498,0.25425 -0.0801,0.077 -0.08062,0.0775 -0.76171,0.0915 -0.762228,0.092 -0.289388,5.2e-4 -0.289388,5.1e-4 0.04961,-0.0496 0.04961,-0.0496 0.874882,-0.11679 0.874882,-0.11731 0.207222,-0.003 z m 26.551868,25.99273 h 0.16175 0.16174 l 0.21549,0.25579 0.2155,0.25632 -0.046,0.11989 -0.046,0.11989 0.13436,0.64905 0.13436,0.64906 v 0.20257 0.20257 h -0.16382 -0.16329 l -0.13695,-0.65629 -0.13745,-0.65629 -0.0677,-0.12712 -0.0677,-0.12661 -0.26665,-0.0269 -0.26665,-0.0264 v -0.0868 -0.0873 l 0.1695,-0.21549 0.1695,-0.21497 v -0.11576 z m -1.77819,0.59221 h 0.11679 0.11627 l 0.10232,0.1235 0.10232,0.12299 -0.0543,0.0889 -0.0548,0.0884 h -0.10801 l -0.108,-5.1e-4 -0.0563,-0.0563 -0.0563,-0.0563 v -0.15503 z m -3.29023,0.74052 0.094,0.094 0.094,0.0941 0.0501,0.36174 0.0501,0.36173 0.27337,-0.0217 0.27337,-0.0217 0.002,0.10645 0.002,0.10594 0.047,1.03714 0.047,1.03715 h -0.0827 -0.0806 l -0.0558,-0.74053 -0.0563,-0.74104 -0.0486,-0.24701 -0.0486,-0.24701 -0.0584,0.11989 -0.0584,0.1204 0.0486,0.9586 0.047,0.95912 0.14779,0.0822 0.14728,0.0827 v 0.24082 0.24029 l -0.12712,0.0491 -0.12713,0.0486 v 0.12971 0.1297 l -0.0641,-0.007 -0.063,-0.006 -0.339,-0.10439 -0.33848,-0.10439 0.26459,-0.14004 0.26458,-0.13953 0.0439,-0.22996 0.0439,-0.22996 -0.0832,-0.93741 -0.0832,-0.93741 -0.20929,-0.22169 -0.20877,-0.22169 -0.0491,-0.47491 -0.0491,-0.47542 0.14418,-0.0553 z m -1.02785,0.094 0.14831,0.0279 0.14831,0.0279 0.0269,0.28526 0.0274,0.28525 -0.0739,0.0739 -0.0739,0.0739 -0.10129,-0.10129 -0.1018,-0.1018 v -0.28577 z m -0.43305,0.85886 0.0413,0.0672 0.0419,0.0677 0.0264,1.20354 0.0264,1.20355 0.14108,0.0444 0.14056,0.045 0.0527,0.13797 0.0532,0.13798 -0.0667,0.0574 -0.0656,0.0579 -0.21187,-0.0543 -0.21136,-0.0548 -0.0155,-0.25011 -0.016,-0.25012 -0.0873,-0.88883 -0.0873,-0.88935 -0.003,-0.26872 -0.003,-0.26923 -0.14831,-0.0382 -0.14831,-0.0388 0.27026,-0.0103 z m 0.26355,0.0749 0.0847,0.0522 0.0847,0.0522 v 0.0796 0.0801 h -0.0847 -0.0847 v -0.13229 z m -0.25373,3.0241 0.092,0.0915 0.0915,0.092 -0.0279,0.18707 -0.0284,0.18759 -0.0636,-0.0646 -0.0636,-0.0646 v -0.21445 z m 2.37092,0.006 0.0563,0.0558 0.0563,0.0553 h -0.11317 -0.11266 l 0.0563,-0.0553 z m -2.28617,0.84646 0.0563,0.0558 0.0563,0.0553 -0.0563,0.0563 -0.0563,0.0563 v -0.11317 z m 9.94825,-40.78642 -0.59273,0.0558 -0.59273,0.0558 -0.38086,0.0687 -0.38085,0.0682 0.84646,-0.047 0.84646,-0.047 0.12712,-0.077 z m 0.0527,0.74931 -0.11111,0.0444 -0.1111,0.0444 0.16226,0.007 0.16227,0.007 -0.0512,-0.0512 z" + /> + </g> + </svg> + </div> + <div class="content-center m-auto"> + <p class="font-semibold text-center">"Singlestage UI"</p> + <p class="text-sm text-center"> + "Test Site " + {if cfg!(feature = "csr") { + "CSR" + } else if cfg!(feature = "ssr") { + "SSR" + } else { + "ERR" + }} + </p> + </div> + </div> + </Link> + </SidebarHeader> + <SidebarContent> + <SidebarGroup> + <SidebarGroupLabel>"Components"</SidebarGroupLabel> + <SidebarGroupContent> + <SidebarMenu> + <For + each=move || routes.get_value() + key=|route| route.to_owned() + let(route) + > + <SidebarMenuItem> + <SidebarMenuButton> + <Link href=format!( + "/{}", + &route.to_lowercase().replace(" ", "_"), + )>{route}</Link> + </SidebarMenuButton> + </SidebarMenuItem> + </For> + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + <TestMenu /> + <TestButtons /> + </SidebarContent> + </Sidebar> + } +} diff --git a/test_site/app/src/components/sidebar/test_buttons.rs b/test_site/app/src/components/sidebar/test_buttons.rs new file mode 100644 index 00000000..d63cd52d --- /dev/null +++ b/test_site/app/src/components/sidebar/test_buttons.rs @@ -0,0 +1,224 @@ +use leptos::prelude::*; +use singlestage::*; + +/// Converts string To Title Case +fn string_to_title(input: &str) -> String { + let mut buf = String::with_capacity(input.len()); + let mut input_iter = input.char_indices(); + + while let Some((index, mut char)) = input_iter.next() { + if index == 0 { + char.make_ascii_uppercase(); + buf.push(char); + continue; + } + + match char { + ' ' | '-' | '_' => { + buf.push(' '); + if let Some((_, mut char)) = input_iter.next() { + char.make_ascii_uppercase(); + buf.push(char) + } else { + break; + } + } + _ => { + char.make_ascii_lowercase(); + buf.push(char) + } + } + } + + buf +} + +#[derive(Clone, Default)] +struct MenuThing { + aria_current: String, + href: String, + size: String, + variant: String, +} + +#[derive(Clone, Default)] +struct MenuGroup { + title: String, + things: Vec<MenuThing>, +} + +#[component] +pub fn TestButtons() -> impl IntoView { + let menu_groups = vec![ + MenuGroup { + title: "Inactive".to_string(), + things: vec![ + MenuThing { + href: "/1".to_string(), + size: "small".to_string(), + ..Default::default() + }, + MenuThing { + href: "/2".to_string(), + size: "default".to_string(), + ..Default::default() + }, + MenuThing { + href: "/3".to_string(), + size: "large".to_string(), + ..Default::default() + }, + ], + }, + MenuGroup { + title: "Active".to_string(), + things: vec![ + MenuThing { + aria_current: "page".to_string(), + href: "/1".to_string(), + size: "small".to_string(), + ..Default::default() + }, + MenuThing { + aria_current: "page".to_string(), + href: "/2".to_string(), + size: "default".to_string(), + ..Default::default() + }, + MenuThing { + aria_current: "page".to_string(), + href: "/3".to_string(), + size: "large".to_string(), + ..Default::default() + }, + ], + }, + MenuGroup { + title: "Outline Inactive".to_string(), + things: vec![ + MenuThing { + href: "/1".to_string(), + size: "small".to_string(), + variant: "outline".to_string(), + ..Default::default() + }, + MenuThing { + href: "/2".to_string(), + size: "default".to_string(), + variant: "outline".to_string(), + ..Default::default() + }, + MenuThing { + href: "/3".to_string(), + size: "large".to_string(), + variant: "outline".to_string(), + ..Default::default() + }, + ], + }, + MenuGroup { + title: "Outline Active".to_string(), + things: vec![ + MenuThing { + aria_current: "page".to_string(), + href: "/1".to_string(), + size: "small".to_string(), + variant: "outline".to_string(), + }, + MenuThing { + aria_current: "page".to_string(), + href: "/2".to_string(), + size: "default".to_string(), + variant: "outline".to_string(), + }, + MenuThing { + aria_current: "page".to_string(), + href: "/3".to_string(), + size: "large".to_string(), + variant: "outline".to_string(), + }, + ], + }, + ]; + + let links = menu_groups.clone(); + let buttons = menu_groups; + + view! { + <SidebarMenu> + <For each=move || links.to_owned() key=|group| group.title.clone() let(group)> + <SidebarGroup> + <SidebarGroupLabel>"Link "{group.title}</SidebarGroupLabel> + <SidebarGroupContent> + <For + each=move || group.things.to_owned() + key=|thing| thing.size.to_string() + let(thing) + > + <SidebarMenuButton> + <Link + aria_current=thing.aria_current.clone() + href=thing.href + size=thing.size.clone() + variant=thing.variant.clone() + > + {icon!(icondata::LuLink)} + <span>{string_to_title(&thing.size)}</span> + </Link> + </SidebarMenuButton> + </For> + </SidebarGroupContent> + </SidebarGroup> + </For> + <For each=move || buttons.to_owned() key=|group| group.title.clone() let(group)> + <SidebarGroup> + <SidebarGroupLabel>"Button "{group.title}</SidebarGroupLabel> + <SidebarGroupContent> + <For + each=move || group.things.to_owned() + key=|thing| thing.size.to_string() + let(thing) + > + <SidebarMenuButton> + <Button + aria_current=thing.aria_current.clone() + size=thing.size.clone() + variant=thing.variant.clone() + > + {icon!(icondata::MdiButtonPointer)} + <span>{string_to_title(&thing.size)}</span> + </Button> + </SidebarMenuButton> + </For> + </SidebarGroupContent> + </SidebarGroup> + </For> + <SidebarGroup> + <SidebarGroupLabel>"Link Button"</SidebarGroupLabel> + <SidebarGroupContent> + <For + each=move || [ + "default", + "secondary", + "outline", + "ghost", + "link", + "destructive", + ] + key=|variant| variant.to_string() + let(variant) + > + <SidebarMenuItem> + <SidebarMenuButton> + <Link href="/link-button" render_as="button" variant> + {icon!(icondata::LuInfo)} + <span>"Link Button"</span> + </Link> + </SidebarMenuButton> + </SidebarMenuItem> + </For> + </SidebarGroupContent> + </SidebarGroup> + </SidebarMenu> + } +} diff --git a/test_site/app/src/components/sidebar/test_menu.rs b/test_site/app/src/components/sidebar/test_menu.rs new file mode 100644 index 00000000..0e4f92bd --- /dev/null +++ b/test_site/app/src/components/sidebar/test_menu.rs @@ -0,0 +1,62 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TestMenu() -> impl IntoView { + view! { + <SidebarGroup> + <SidebarGroupLabel>"Sidebar Test"</SidebarGroupLabel> + <SidebarGroupContent> + <SidebarMenu> + <SidebarMenuItem> + <Collapsible> + <CollapsibleTrigger> + <SidebarMenuButton> + <Button>{icon!(icondata::LuMenu)} "Test Menu"</Button> + </SidebarMenuButton> + </CollapsibleTrigger> + <CollapsibleContent> + <SidebarMenuSub> + <SidebarMenuSubItem> + <SidebarMenuSubButton> + <Link href="/1"> + <span>"One"</span> + </Link> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + </SidebarMenuSub> + <SidebarMenuSub> + <SidebarMenuSubItem> + <SidebarMenuSubButton> + <Link href="/2"> + <span>"Two"</span> + </Link> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + </SidebarMenuSub> + <SidebarMenuSub> + <SidebarMenuSubItem> + <SidebarMenuSubButton> + <Link href="/3"> + <span>"Three"</span> + </Link> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + </SidebarMenuSub> + <SidebarMenuSub> + <SidebarMenuSubItem> + <SidebarMenuSubButton> + <Link href="/4"> + <span>"Four"</span> + </Link> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + </SidebarMenuSub> + </CollapsibleContent> + </Collapsible> + </SidebarMenuItem> + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + } +} diff --git a/test_site/app/src/components/theme_switcher.rs b/test_site/app/src/components/theme_switcher.rs new file mode 100644 index 00000000..da0a922b --- /dev/null +++ b/test_site/app/src/components/theme_switcher.rs @@ -0,0 +1,202 @@ +use leptos::{prelude::*, wasm_bindgen::JsCast}; +use singlestage::*; + +#[component] +pub fn ThemeSwitcher() -> impl IntoView { + let theme_context = expect_context::<ThemeProviderContext>(); + let prefers_dark = RwSignal::new(false); + let selected_theme = RwSignal::new("neutral".to_string()); + let selected_base = RwSignal::new("vega".to_string()); + + // TODO: Make reactive + Effect::new(move || { + let media_query = window() + .match_media("(prefers-color-scheme: dark)") + .unwrap_or(None); + + if let Some(media_query) = media_query.as_ref() { + let matches = media_query.matches(); + + prefers_dark.set(matches); + } + }); + + Effect::new(move || { + if let Ok(cookies) = document().unchecked_ref::<web_sys::HtmlDocument>().cookie() { + let mut theme = ""; + let mut theme_mode = ""; + + let _ = cookies + .split(";") + .map(|cookie| { + let split = cookie.split("=").collect::<Vec<&str>>(); + + match split[0].trim() { + "theme" => theme = split[1], + "theme_mode" => theme_mode = split[1], + _ => {} + } + }) + .count(); + + theme_context.mode.set(Mode::from(theme_mode)); + + if !theme.is_empty() { + selected_theme.set(theme.to_string()); + } + } + }); + + let swap_theme = move |_| { + match theme_context.mode.get_untracked() { + Mode::Dark => { + theme_context.mode.set(Mode::Light); + } + Mode::Light => { + theme_context.mode.set(Mode::Dark); + } + _ => { + if prefers_dark.get_untracked() { + theme_context.mode.set(Mode::Light); + } else { + theme_context.mode.set(Mode::Dark); + } + } + }; + + let _ = document() + .unchecked_ref::<web_sys::HtmlDocument>() + .set_cookie( + format!("theme_mode={}; Path=/", theme_context.mode.get_untracked()).as_str(), + ); + }; + + Effect::new(move || { + let _ = document() + .unchecked_ref::<web_sys::HtmlDocument>() + .set_cookie(format!("theme={}; Path=/", selected_theme.get()).as_str()); + + theme_context + .theme + .set(match selected_theme.get().as_str() { + "amber" => Theme::Amber, + "blue" => Theme::Blue, + "lime" => Theme::Lime, + "mono" => Theme::Mono, + "neutral" => Theme::Neutral, + "orange" => Theme::Orange, + "purple" => Theme::Purple, + "red" => Theme::Red, + "rose" => Theme::Rose, + "scaled" => Theme::Scaled, + "teal" => Theme::Teal, + "violet" => Theme::Violet, + "yellow" => Theme::Yellow, + _ => Theme::Neutral, + }) + }); + + Effect::new(move || { + // let _ = document() + // .unchecked_ref::<web_sys::HtmlDocument>() + // .set_cookie(format!("theme={}; Path=/", selected_theme.get()).as_str()); + + theme_context.base.set(match selected_base.get().as_str() { + "luma" => ThemeBase::Luma, + "lyra" => ThemeBase::Lyra, + "maia" => ThemeBase::Maia, + "mira" => ThemeBase::Mira, + "nova" => ThemeBase::Nova, + "sera" => ThemeBase::Sera, + _ => ThemeBase::Vega, + }) + }); + + view! { + <span class="flex space-x-2"> + <Tooltip> + <TooltipTrigger> + <Select value=selected_base todo_name_me_size="sm"> + <SelectOption value="luma">"Luma"</SelectOption> + <SelectOption value="lyra">"Lyra"</SelectOption> + <SelectOption value="maia">"Maia"</SelectOption> + <SelectOption value="mira">"Mira"</SelectOption> + <SelectOption value="nova">"Nova"</SelectOption> + <SelectOption value="sera">"Sera"</SelectOption> + <SelectOption value="vega">"Vega"</SelectOption> + </Select> + </TooltipTrigger> + <TooltipContent side="bottom"> + <p>"Select base theme"</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Select value=selected_theme todo_name_me_size="sm"> + <SelectOptGroup label="Colors"> + <SelectOption value="neutral">"Neutral"</SelectOption> + <SelectOption value="amber">"Amber"</SelectOption> + <SelectOption value="blue">"Blue"</SelectOption> + <SelectOption value="lime">"Lime"</SelectOption> + <SelectOption value="orange">"Orange"</SelectOption> + <SelectOption value="purple">"Purple"</SelectOption> + <SelectOption value="red">"Red"</SelectOption> + <SelectOption value="rose">"Rose"</SelectOption> + <SelectOption value="teal">"Teal"</SelectOption> + <SelectOption value="violet">"Violet"</SelectOption> + <SelectOption value="yellow">"Yellow"</SelectOption> + </SelectOptGroup> + <SelectOptGroup label="Layout"> + <SelectOption value="mono">"Mono"</SelectOption> + <SelectOption value="scaled">"Scaled"</SelectOption> + </SelectOptGroup> + </Select> + </TooltipTrigger> + <TooltipContent side="bottom"> + <p>"Select color scheme"</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button variant="outline" size="sm-icon" on:click=swap_theme> + {move || match theme_context.mode.get() { + Mode::Light => { + view! { <span>{icon!(icondata::LuSun)}</span> }.into_any() + } + Mode::Dark => { + view! { <span>{icon!(icondata::LuMoon)}</span> }.into_any() + } + _ => { + match prefers_dark.get() { + false => { + view! { <span>{icon!(icondata::LuSun)}</span> }.into_any() + } + true => { + view! { <span>{icon!(icondata::LuMoon)}</span> }.into_any() + } + } + } + }} + </Button> + </TooltipTrigger> + <TooltipContent side="bottom"> + <p>"Toggle dark mode"</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Link + href="https://github.com/adoyle0/singlestage-ui" + render_as="button" + size="sm-icon" + > + {icon!(icondata::SiGithub)} + </Link> + </TooltipTrigger> + <TooltipContent side="bottom"> + <p>"GitHub repository"</p> + </TooltipContent> + </Tooltip> + </span> + } +} diff --git a/test_site/app/src/lib.rs b/test_site/app/src/lib.rs new file mode 100644 index 00000000..92f4143d --- /dev/null +++ b/test_site/app/src/lib.rs @@ -0,0 +1,93 @@ +use leptos::prelude::*; +use leptos_meta::{MetaTags, Title, provide_meta_context}; +use leptos_router::{ + StaticSegment, + components::{Route, Router, Routes}, +}; + +mod components; +mod routes; + +use components::*; +use routes::*; +use singlestage::*; + +pub fn shell(options: LeptosOptions) -> impl IntoView { + view! { + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <AutoReload options=options.clone() /> + <HydrationScripts options /> + <MetaTags /> + </head> + <body> + <App /> + </body> + </html> + } +} + +#[component] +pub fn App() -> impl IntoView { + // Provides context that manages stylesheets, titles, meta tags, etc. + provide_meta_context(); + + // This tells the client where to find the server in csr builds + #[cfg(feature = "csr")] + leptos::server_fn::client::set_server_url("http://localhost:3000"); + + view! { + <Title text="test_site" /> + + <ThemeProvider> + <SidebarProvider> + <AppSidebar /> + <SidebarInset> + <Router> + <header class="flex sticky inset-x-0 top-0 z-10 items-center border-b bg-(--background) isolate shrink-0"> + <div class="flex gap-2 justify-between items-center px-4 w-full h-14"> + <SidebarTrigger> + <SidebarButton /> + </SidebarTrigger> + <ThemeSwitcher /> + </div> + </header> + + <main class="my-8 mx-2 sm:mx-12 max-w-4xl"> + { + #[cfg(not(feature = "csr"))] + // In csr mode the css file is sourced in index.html + view! { + <leptos_meta::Stylesheet + id="leptos" + href="/pkg/test_site.css" + /> + } + } <Routes fallback=NotFound> + <Route path=StaticSegment("/") view=Home /> + // TODO: Figure out why Trunk won't compile this + // <Route path=StaticSegment("/all") view=DebugAll /> + <Route path=StaticSegment("/button") view=DebugButton /> + <Route path=StaticSegment("/checkbox") view=DebugCheckbox /> + <Route path=StaticSegment("/context_menu") view=DebugContextMenu /> + <Route path=StaticSegment("/dropdown") view=DebugDropdown /> + <Route path=StaticSegment("/form_reset") view=DebugFormReset /> + <Route path=StaticSegment("/input") view=DebugInput /> + <Route path=StaticSegment("/label") view=DebugLabel /> + <Route path=StaticSegment("/radio") view=DebugRadio /> + <Route path=StaticSegment("/select") view=DebugSelect /> + <Route path=StaticSegment("/slider") view=DebugSlider /> + <Route path=StaticSegment("/textarea") view=DebugTextarea /> + <Route path=StaticSegment("/toggle") view=DebugToggle /> + <Route path=StaticSegment("/tooltip") view=DebugTooltip /> + </Routes> + </main> + </Router> + </SidebarInset> + </SidebarProvider> + </ThemeProvider> + } +} diff --git a/test_site/app/src/main.rs b/test_site/app/src/main.rs new file mode 100644 index 00000000..fea5a18b --- /dev/null +++ b/test_site/app/src/main.rs @@ -0,0 +1,66 @@ +use app::*; +use leptos::prelude::*; + +#[cfg(feature = "ssr")] +#[tokio::main] +async fn main() { + use axum::Router; + use leptos_axum::{LeptosRoutes, generate_route_list}; + use tower::ServiceBuilder; + use tower_http::{ + compression::CompressionLayer, + cors::{Any, CorsLayer}, + decompression::RequestDecompressionLayer, + }; + + let conf = get_configuration(None).unwrap(); + let addr = conf.leptos_options.site_addr; + let leptos_options = conf.leptos_options; + // Generate the list of routes in your Leptos App + let routes = generate_route_list(App); + + let app = Router::new() + .leptos_routes(&leptos_options, routes, { + let leptos_options = leptos_options.clone(); + move || shell(leptos_options.clone()) + }) + .fallback(leptos_axum::file_and_error_handler(shell)) + .with_state(leptos_options); + + // Support for both request and response compression + let compression_layer = ServiceBuilder::new() + .layer(CompressionLayer::new()) + .layer(RequestDecompressionLayer::new()); + + // Allow incoming requests from any origin in debug builds to avoid errors when testing over a + // network or things like Tauri builds + let cors_layer = ServiceBuilder::new().layer(if cfg!(debug_assertions) { + CorsLayer::new().allow_origin(Any) + } else { + CorsLayer::new() + }); + + // run our app with hyper + // `axum::Server` is a re-export of `hyper::Server` + leptos::logging::log!("listening on http://{}", &addr); + let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); + axum::serve( + listener, + app.layer(compression_layer) + .layer(cors_layer) + .into_make_service(), + ) + .await + .unwrap(); +} + +#[cfg(not(feature = "ssr"))] +fn main() { + // set up logging + _ = console_log::init_with_level(log::Level::Debug); + console_error_panic_hook::set_once(); + + mount_to_body(|| { + view! { <App /> } + }) +} diff --git a/docs/src/routes/debug/button.rs b/test_site/app/src/routes/debug/button.rs similarity index 100% rename from docs/src/routes/debug/button.rs rename to test_site/app/src/routes/debug/button.rs diff --git a/test_site/app/src/routes/debug/checkbox.rs b/test_site/app/src/routes/debug/checkbox.rs new file mode 100644 index 00000000..42dc2139 --- /dev/null +++ b/test_site/app/src/routes/debug/checkbox.rs @@ -0,0 +1,124 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DebugCheckbox() -> impl IntoView { + let checked = RwSignal::new(true); + let disabled = RwSignal::new(true); + let invalid = RwSignal::new(true); + let value = RwSignal::new(vec!["two".to_string()]); + + view! { + <h1 class="text-4xl font-semibold">"Checkbox"</h1> + <ul class="singlestage-ulist text-(--muted-foreground)"> + <li>"Checked state should not flicker on load or reload"</li> + <li> + "Checked state should not lose sync with the signal when alternating clicking the + button and the checkbox" + </li> + <li>"Disabled state should not flicker on load or reload"</li> + </ul> + + <ul class="text-(--muted-foreground)"> + <li>"Checked: "{move || checked.get().to_string()}</li> + <li>"Disabled: "{move || disabled.get().to_string()}</li> + </ul> + + <Label> + <Checkbox disabled checked /> + "Accept terms and conditions" + </Label> + + <Label> + <Switch disabled checked /> + "Accept terms and conditions" + </Label> + + <Button variant="outline" on:click=move |_| { checked.set(!checked.get_untracked()) }> + "Toggle Checked" + </Button> + + <p>{move || value.get()}</p> + <CheckboxGroup disabled invalid value> + <legend>"Group"</legend> + <Checkbox value="one">"One"</Checkbox> + <Checkbox value="two">"Two"</Checkbox> + <Checkbox value="three">"Three"</Checkbox> + </CheckboxGroup> + <CheckboxGroup disabled invalid value> + <legend>"Group"</legend> + <Switch value="one">"One"</Switch> + <Switch value="two">"Two"</Switch> + <Switch value="three">"Three"</Switch> + </CheckboxGroup> + + <Switch disabled invalid> + "Solo" + </Switch> + + <Field disabled invalid> + <Checkbox>"in Field"</Checkbox> + </Field> + + <Field disabled invalid> + <Switch>"in Field"</Switch> + </Field> + + <span class="space-x-2"> + <Button variant="outline" on:click=move |_| { disabled.set(!disabled.get_untracked()) }> + "Toggle Disabled" + </Button> + <Button variant="outline" on:click=move |_| { invalid.set(!invalid.get_untracked()) }> + "Toggle Invalid" + </Button> + <Button + variant="outline" + on:click=move |_| { + value + .update(move |v| { + let idx = v.into_iter().position(|el| *el == "one".to_string()); + if let Some(idx) = idx { + v.remove(idx); + } else { + v.push("one".to_string()); + } + }) + } + > + "One" + </Button> + <Button + variant="outline" + on:click=move |_| { + value + .update(move |v| { + let idx = v.into_iter().position(|el| *el == "two".to_string()); + if let Some(idx) = idx { + v.remove(idx); + } else { + v.push("two".to_string()); + } + }) + } + > + "Two" + </Button> + <Button + variant="outline" + on:click=move |_| { + value + .update(move |v| { + let idx = v.into_iter().position(|el| *el == "three".to_string()); + if let Some(idx) = idx { + v.remove(idx); + } else { + v.push("three".to_string()); + } + }) + } + > + "Three" + </Button> + </span> + } +} diff --git a/docs/src/routes/debug/context_menu.rs b/test_site/app/src/routes/debug/context_menu.rs similarity index 100% rename from docs/src/routes/debug/context_menu.rs rename to test_site/app/src/routes/debug/context_menu.rs diff --git a/test_site/app/src/routes/debug/dropdown.rs b/test_site/app/src/routes/debug/dropdown.rs new file mode 100644 index 00000000..cd370c0d --- /dev/null +++ b/test_site/app/src/routes/debug/dropdown.rs @@ -0,0 +1,151 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +fn Item() -> impl IntoView { + view! { + <DropdownMenuItem on:click=move |_| { + leptos::logging::log!("hey") + }>"Click me"</DropdownMenuItem> + } +} + +#[component] +pub fn DropdownTriggersDialog() -> impl IntoView { + let open = RwSignal::new(false); + + view! { + <p>"Dialog Trigger"</p> + + <Dialog open> + <DialogContent>"hey"</DialogContent> + + <DropdownMenu> + <DropdownMenuTrigger> + <Button>"Dialog Menu"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuItem>"Close dropdown"</DropdownMenuItem> + <DropdownMenuItem dismiss=false>"Don't dismiss"</DropdownMenuItem> + <DialogTrigger> + <DropdownMenuItem>"Implicit trigger"</DropdownMenuItem> + </DialogTrigger> + <DialogTrigger> + <DropdownMenuItem>"Implicit no dismiss"</DropdownMenuItem> + </DialogTrigger> + <DropdownMenuItem on:click={move |ev| { + ev.prevent_default(); + open.set(true); + }}>"Open with signal"</DropdownMenuItem> + <DropdownMenuItem + dismiss=false + on:click={move |ev| { + ev.prevent_default(); + open.set(true); + }} + > + "Open with signal no dismiss" + </DropdownMenuItem> + <DropdownMenuItem as_child=true> + <Button on:click={move |ev| { + ev.prevent_default(); + open.set(true); + }}>"Child open with signal"</Button> + </DropdownMenuItem> + <DropdownMenuItem dismiss=false as_child=true> + <Button on:click={move |ev| { + ev.prevent_default(); + open.set(true); + }}>"Child open with signal no dismiss"</Button> + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </Dialog> + } +} + +#[component] +pub fn DropdownSignal() -> impl IntoView { + let open = RwSignal::new(false); + + view! { + <Switch checked=open /> + + <DropdownMenu open modal=true> + <DropdownMenuTrigger> + <Button>"Signal"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent> + <Item /> + <DropdownMenuItem dismiss=false>"No dismiss"</DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + } +} + +#[component] +pub fn DropdownDismiss() -> impl IntoView { + view! { + <DropdownMenu> + <DropdownMenuTrigger> + <Button>"No dismiss"</Button> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuItem dismiss=false>"no dismiss"</DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + } +} + +#[component] +pub fn DropdownPosition() -> impl IntoView { + let variations = [ + ("top", "start"), + ("top", "center"), + ("top", "end"), + ("right", "start"), + ("right", "center"), + ("right", "end"), + ("bottom", "start"), + ("bottom", "center"), + ("bottom", "end"), + ("left", "start"), + ("left", "center"), + ("left", "end"), + ]; + + view! { + <p>"Positioning"</p> + <For each=move || variations key=|_| "42" let(variation)> + <DropdownMenu> + <DropdownMenuTrigger> + <Button size="xs">{variation.0}" "{variation.1}</Button> + </DropdownMenuTrigger> + <DropdownMenuContent side={variation.0} align={variation.1}> + <Item /> + </DropdownMenuContent> + </DropdownMenu> + </For> + } +} + +#[component] +pub fn DebugDropdown() -> impl IntoView { + view! { + <h1 class="text-4xl font-semibold">"Dropdown Menu"</h1> + <ul class="singlestage-ulist text-(--muted-foreground)"> + <li>"Console should log \"hey\" when clicked"</li> + <li>"Console should log \"hey\" when tapped"</li> + <li> + "Console should log \"hey\" when selected with keyboard and "<Kbd>"Enter"</Kbd> + " is pressed" + </li> + </ul> + + <DropdownSignal /> + <DropdownDismiss /> + <DropdownPosition /> + + <DropdownTriggersDialog /> + } +} diff --git a/docs/src/routes/debug/form_reset.rs b/test_site/app/src/routes/debug/form_reset.rs similarity index 65% rename from docs/src/routes/debug/form_reset.rs rename to test_site/app/src/routes/debug/form_reset.rs index 3274a764..52c3d17a 100644 --- a/docs/src/routes/debug/form_reset.rs +++ b/test_site/app/src/routes/debug/form_reset.rs @@ -10,15 +10,11 @@ pub fn DebugFormReset() -> impl IntoView { let update_text = move |_| { input_value.set(String::from("updated value")); - input_default.set(String::from("updated value")); text_value.set(String::from("updated value")); - text_default.set(String::from("updated value")); }; let reset_text = move |_| { - input_value.set(String::from("reset value")); input_default.set(String::from("reset value")); - text_value.set(String::from("reset value")); text_default.set(String::from("reset value")); }; @@ -40,37 +36,40 @@ pub fn DebugFormReset() -> impl IntoView { <form class="space-y-3" style="width: 540px;"> - <Label attr:r#for="controlled-input">"Controlled Input"</Label> - <Input attr:id="controlled-input" value=input_value default=input_default /> + <Input value=input_value default=input_default> + "Controlled Input" + </Input> - <Label attr:r#for="html-input">"HTML Input"</Label> + <Label label_for="html-input">"HTML Input"</Label> <input id="html-input" type="text" class="singlestage-input" - value=move || input_default.get() + value=move || input_default.get_untracked() prop:value=move || input_value.get() /> - <Label attr:r#for="controlled-textarea">"Controlled Textarea"</Label> - <Textarea attr:id="controlled-textarea" value=text_value default=text_default /> + <Textarea value=text_value default=text_default> + "Controlled Textarea" + </Textarea> - <Label attr:r#for="html-textarea">"HTML Textarea"</Label> + <Label label_for="html-textarea">"HTML Textarea"</Label> <textarea class="singlestage-textarea" id="html-textarea" - prop:textContent=move || text_default.get() prop:value=move || text_value.get() - /> + > + {move || text_default.get_untracked()} + </textarea> <span class="space-x-2"> - <Button variant="outline" attr:r#type="button" on:click=update_text> - "Controlled Update Text" + <Button variant="outline" button_type="button" on:click=update_text> + "Controlled Update Text (value)" </Button> - <Button variant="outline" attr:r#type="button" on:click=reset_text> - "Controlled Reset Text" + <Button variant="outline" button_type="button" on:click=reset_text> + "Controlled Reset Text (default)" </Button> - <Button variant="outline" attr:r#type="reset"> + <Button variant="outline" button_type="reset"> "HTML Reset" </Button> </span> diff --git a/docs/src/routes/debug/input.rs b/test_site/app/src/routes/debug/input.rs similarity index 86% rename from docs/src/routes/debug/input.rs rename to test_site/app/src/routes/debug/input.rs index d6d1c5aa..73e8ae18 100644 --- a/docs/src/routes/debug/input.rs +++ b/test_site/app/src/routes/debug/input.rs @@ -18,7 +18,13 @@ pub fn DebugInput() -> impl IntoView { <li>"Invalid: "{move || invalid.get().to_string()}</li> </ul> - <Input disabled invalid /> + <Input disabled invalid> + "Solo" + </Input> + + <Field disabled invalid> + <Input>"Field"</Input> + </Field> <span class="space-x-2"> <Button variant="outline" on:click=move |_| disabled.set(!disabled.get())> diff --git a/test_site/app/src/routes/debug/label.rs b/test_site/app/src/routes/debug/label.rs new file mode 100644 index 00000000..7f4dc372 --- /dev/null +++ b/test_site/app/src/routes/debug/label.rs @@ -0,0 +1,39 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DebugLabel() -> impl IntoView { + view! { + <h1 class="text-4xl font-semibold">"Labels"</h1> + <ul class="singlestage-ulist text-(--muted-foreground)"> + <li>"Labels should all be visually consistent"</li> + <li>"label and input ids should match"</li> + </ul> + + <h2>"Solo:"</h2> + <Radio>"label"</Radio> + <Checkbox>"label"</Checkbox> + <Switch>"label"</Switch> + <Input>"label"</Input> + <Textarea>"label"</Textarea> + + <h2>"Field:"</h2> + <FieldSet> + <Field> + <Radio>"label"</Radio> + </Field> + <Field> + <Checkbox>"label"</Checkbox> + </Field> + <Field> + <Switch>"label"</Switch> + </Field> + <Field> + <Input>"label"</Input> + </Field> + <Field> + <Textarea>"label"</Textarea> + </Field> + </FieldSet> + } +} diff --git a/docs/src/routes/debug/mod.rs b/test_site/app/src/routes/debug/mod.rs similarity index 60% rename from docs/src/routes/debug/mod.rs rename to test_site/app/src/routes/debug/mod.rs index e7efd7cc..7f4829ad 100644 --- a/docs/src/routes/debug/mod.rs +++ b/test_site/app/src/routes/debug/mod.rs @@ -6,39 +6,45 @@ mod context_menu; mod dropdown; mod form_reset; mod input; +mod label; mod radio; mod select; mod slider; mod textarea; mod toggle; +mod tooltip; -use button::*; -use checkbox::*; -use context_menu::*; -use dropdown::*; -use form_reset::*; -use input::*; -use radio::*; -use select::*; -use slider::*; -use textarea::*; -use toggle::*; +pub use button::*; +pub use checkbox::*; +pub use context_menu::*; +pub use dropdown::*; +pub use form_reset::*; +pub use input::*; +pub use label::*; +pub use radio::*; +pub use select::*; +pub use slider::*; +pub use textarea::*; +pub use toggle::*; +pub use tooltip::*; #[component] -pub fn ReactiveDebug() -> impl IntoView { +pub fn DebugAll() -> impl IntoView { view! { <div class="space-y-6"> + <DebugButton /> <DebugCheckbox /> - <DebugInput /> - <DebugTextarea /> + <DebugContextMenu /> + <DebugDropdown /> <DebugFormReset /> - <DebugSelect /> + <DebugInput /> + <DebugLabel /> <DebugRadio /> + <DebugSelect /> <DebugSlider /> - <DebugButton /> + <DebugTextarea /> <DebugToggle /> - <DebugDropdown /> - <DebugContextMenu /> + <DebugTooltip /> </div> } } diff --git a/docs/src/routes/debug/radio.rs b/test_site/app/src/routes/debug/radio.rs similarity index 91% rename from docs/src/routes/debug/radio.rs rename to test_site/app/src/routes/debug/radio.rs index 323a22fb..9dd43261 100644 --- a/docs/src/routes/debug/radio.rs +++ b/test_site/app/src/routes/debug/radio.rs @@ -26,11 +26,20 @@ pub fn DebugRadio() -> impl IntoView { </ul> <RadioGroup disabled invalid value> + <legend>"Group"</legend> <Radio value="one">"One"</Radio> <Radio value="two">"Two"</Radio> <Radio value="three">"Three"</Radio> </RadioGroup> + <Radio disabled invalid> + "Solo" + </Radio> + + <Field disabled invalid> + <Radio>"in Field"</Radio> + </Field> + <span class="space-x-2"> <Button variant="outline" on:click=move |_| { disabled.set(!disabled.get_untracked()) }> "Toggle Disabled" diff --git a/docs/src/routes/debug/select.rs b/test_site/app/src/routes/debug/select.rs similarity index 90% rename from docs/src/routes/debug/select.rs rename to test_site/app/src/routes/debug/select.rs index a2321138..ff777040 100644 --- a/docs/src/routes/debug/select.rs +++ b/test_site/app/src/routes/debug/select.rs @@ -27,9 +27,9 @@ pub fn DebugSelect() -> impl IntoView { <Label> "Select me" <Select disabled value invalid default="two"> - <SelectItem value="one">"One"</SelectItem> - <SelectItem value="two">"Two"</SelectItem> - <SelectItem value="three">"Three"</SelectItem> + <SelectOption value="one">"One"</SelectOption> + <SelectOption value="two">"Two"</SelectOption> + <SelectOption value="three">"Three"</SelectOption> </Select> </Label> diff --git a/docs/src/routes/debug/slider.rs b/test_site/app/src/routes/debug/slider.rs similarity index 98% rename from docs/src/routes/debug/slider.rs rename to test_site/app/src/routes/debug/slider.rs index abcb8247..d461de64 100644 --- a/docs/src/routes/debug/slider.rs +++ b/test_site/app/src/routes/debug/slider.rs @@ -36,7 +36,6 @@ pub fn DebugSlider() -> impl IntoView { <form id="slider"> <Slider disabled max min step value /> - <Slider default=42. /> </form> <Label>"Min" <Slider min=0. max=value step value=min /></Label> <Label>"Max" <Slider min=value max=100. step value=max /></Label> diff --git a/docs/src/routes/debug/textarea.rs b/test_site/app/src/routes/debug/textarea.rs similarity index 85% rename from docs/src/routes/debug/textarea.rs rename to test_site/app/src/routes/debug/textarea.rs index 289c7548..2e212013 100644 --- a/docs/src/routes/debug/textarea.rs +++ b/test_site/app/src/routes/debug/textarea.rs @@ -18,7 +18,13 @@ pub fn DebugTextarea() -> impl IntoView { <li>"Invalid: "{move || invalid.get().to_string()}</li> </ul> - <Textarea disabled invalid /> + <Textarea disabled invalid> + "Solo" + </Textarea> + + <Field disabled invalid> + <Textarea>"Field"</Textarea> + </Field> <span class="space-x-2"> <Button variant="outline" on:click=move |_| disabled.set(!disabled.get())> diff --git a/docs/src/routes/debug/toggle.rs b/test_site/app/src/routes/debug/toggle.rs similarity index 100% rename from docs/src/routes/debug/toggle.rs rename to test_site/app/src/routes/debug/toggle.rs diff --git a/test_site/app/src/routes/debug/tooltip.rs b/test_site/app/src/routes/debug/tooltip.rs new file mode 100644 index 00000000..c7795e79 --- /dev/null +++ b/test_site/app/src/routes/debug/tooltip.rs @@ -0,0 +1,72 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DebugTooltip() -> impl IntoView { + view! { + <h1 class="text-4xl font-semibold">"Tooltip"</h1> + + <div class="my-4"> + <div class="flex"> + <For + each=move || ["start", "center", "end"] + key=|align| align.to_string() + let(align) + > + <Tooltip> + <TooltipTrigger> + <Button class="w-[4rem]" variant="outline"> + "-" + </Button> + </TooltipTrigger> + <TooltipContent align={align}> + <p>"Add to library"</p> + </TooltipContent> + </Tooltip> + </For> + </div> + + <div class="flex gap-16"> + <For each=move || ["left", "right"] key=|align| align.to_string() let(side)> + <div class="flex-col gap-2"> + <For + each=move || ["start", "center", "end"] + key=|align| align.to_string() + let(align) + > + <Tooltip> + <TooltipTrigger> + <Button class="w-[4rem]" variant="outline"> + "-" + </Button> + </TooltipTrigger> + <TooltipContent align={align} side={side}> + <p>"Add to library"</p> + </TooltipContent> + </Tooltip> + </For> + </div> + </For> + </div> + + <div class="flex"> + <For + each=move || ["start", "center", "end"] + key=|align| align.to_string() + let(align) + > + <Tooltip> + <TooltipTrigger> + <Button class="w-[4rem]" variant="outline"> + "-" + </Button> + </TooltipTrigger> + <TooltipContent align={align} side="bottom"> + <p>"Add to library"</p> + </TooltipContent> + </Tooltip> + </For> + </div> + </div> + } +} diff --git a/test_site/app/src/routes/home.rs b/test_site/app/src/routes/home.rs new file mode 100644 index 00000000..535a2b7c --- /dev/null +++ b/test_site/app/src/routes/home.rs @@ -0,0 +1,7 @@ +use leptos::prelude::*; +// use singlestage::*; + +#[component] +pub fn Home() -> impl IntoView { + view! {} +} diff --git a/test_site/app/src/routes/mod.rs b/test_site/app/src/routes/mod.rs new file mode 100644 index 00000000..f519dc70 --- /dev/null +++ b/test_site/app/src/routes/mod.rs @@ -0,0 +1,7 @@ +mod debug; +mod home; +mod not_found; + +pub use debug::*; +pub use home::*; +pub use not_found::*; diff --git a/test_site/app/src/routes/not_found.rs b/test_site/app/src/routes/not_found.rs new file mode 100644 index 00000000..f49c991c --- /dev/null +++ b/test_site/app/src/routes/not_found.rs @@ -0,0 +1,34 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn NotFound() -> impl IntoView { + view! { + <Empty> + <EmptyHeader> + <EmptyMedia variant="icon"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + > + <path stroke="none" d="M0 0h24v24H0z" fill="none" /> + <path d="M3 8v3a1 1 0 0 0 1 1h3" /> + <path d="M7 8v8" /> + <path d="M17 8v3a1 1 0 0 0 1 1h3" /> + <path d="M21 8v8" /> + <path d="M10 10v4a2 2 0 1 0 4 0v-4a2 2 0 1 0 -4 0" /> + </svg> + </EmptyMedia> + <EmptyTitle>"Not Found"</EmptyTitle> + <EmptyDescription>"The page you're looking for doesn't exist."</EmptyDescription> + </EmptyHeader> + </Empty> + } +} diff --git a/test_site/dev b/test_site/dev new file mode 100755 index 00000000..385b4275 --- /dev/null +++ b/test_site/dev @@ -0,0 +1,25 @@ +#!/bin/bash + +case $1 in + csr) + trunk serve --features csr + ;; + ssr) + LEPTOS_SITE_ADDR="0.0.0.0:3000" cargo leptos watch + ;; + native) + # Hacks for nvidia on wayland + # https://github.com/tauri-apps/tauri/issues/10702#issuecomment-2935208923 + if [ "$(lsmod | grep nvidia)" != "" ]; then + __NV_DISABLE_EXPLICIT_SYNC=1 cargo tauri dev + else + cargo tauri dev + fi + ;; + android) + cargo tauri android dev + ;; + *) + echo "Invalid command" + ;; +esac diff --git a/test_site/hydrate/Cargo.toml b/test_site/hydrate/Cargo.toml new file mode 100644 index 00000000..7753c1c6 --- /dev/null +++ b/test_site/hydrate/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "hydrate" +authors = ["Adam Doyle <adam@doordesk.net>"] +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +app = { path = "../app", default-features = false, features = ["hydrate"] } +console_error_panic_hook.workspace = true +console_log.workspace = true +leptos = { workspace = true, features = ["hydrate"] } +log.workspace = true +wasm-bindgen.workspace = true + +[features] +hydrate = [] diff --git a/test_site/hydrate/src/lib.rs b/test_site/hydrate/src/lib.rs new file mode 100644 index 00000000..fcdaf436 --- /dev/null +++ b/test_site/hydrate/src/lib.rs @@ -0,0 +1,9 @@ +#[wasm_bindgen::prelude::wasm_bindgen] +pub fn hydrate() { + use app::*; + // initializes logging using the `log` crate + _ = console_log::init_with_level(log::Level::Debug); + console_error_panic_hook::set_once(); + + leptos::mount::hydrate_body(App); +} diff --git a/test_site/public/android-icon-144x144.png b/test_site/public/android-icon-144x144.png new file mode 100644 index 00000000..83a6beb2 Binary files /dev/null and b/test_site/public/android-icon-144x144.png differ diff --git a/test_site/public/android-icon-192x192.png b/test_site/public/android-icon-192x192.png new file mode 100644 index 00000000..ef0e8b95 Binary files /dev/null and b/test_site/public/android-icon-192x192.png differ diff --git a/test_site/public/android-icon-36x36.png b/test_site/public/android-icon-36x36.png new file mode 100644 index 00000000..ccdb522b Binary files /dev/null and b/test_site/public/android-icon-36x36.png differ diff --git a/test_site/public/android-icon-48x48.png b/test_site/public/android-icon-48x48.png new file mode 100644 index 00000000..4ee746ac Binary files /dev/null and b/test_site/public/android-icon-48x48.png differ diff --git a/test_site/public/android-icon-72x72.png b/test_site/public/android-icon-72x72.png new file mode 100644 index 00000000..f56ef3b6 Binary files /dev/null and b/test_site/public/android-icon-72x72.png differ diff --git a/test_site/public/android-icon-96x96.png b/test_site/public/android-icon-96x96.png new file mode 100644 index 00000000..45c999a9 Binary files /dev/null and b/test_site/public/android-icon-96x96.png differ diff --git a/test_site/public/apple-icon-114x114.png b/test_site/public/apple-icon-114x114.png new file mode 100644 index 00000000..c2794f3b Binary files /dev/null and b/test_site/public/apple-icon-114x114.png differ diff --git a/test_site/public/apple-icon-120x120.png b/test_site/public/apple-icon-120x120.png new file mode 100644 index 00000000..e9fb473e Binary files /dev/null and b/test_site/public/apple-icon-120x120.png differ diff --git a/test_site/public/apple-icon-144x144.png b/test_site/public/apple-icon-144x144.png new file mode 100644 index 00000000..83a6beb2 Binary files /dev/null and b/test_site/public/apple-icon-144x144.png differ diff --git a/test_site/public/apple-icon-152x152.png b/test_site/public/apple-icon-152x152.png new file mode 100644 index 00000000..5608a65f Binary files /dev/null and b/test_site/public/apple-icon-152x152.png differ diff --git a/test_site/public/apple-icon-180x180.png b/test_site/public/apple-icon-180x180.png new file mode 100644 index 00000000..9f3fa55d Binary files /dev/null and b/test_site/public/apple-icon-180x180.png differ diff --git a/test_site/public/apple-icon-57x57.png b/test_site/public/apple-icon-57x57.png new file mode 100644 index 00000000..a6df7d28 Binary files /dev/null and b/test_site/public/apple-icon-57x57.png differ diff --git a/test_site/public/apple-icon-60x60.png b/test_site/public/apple-icon-60x60.png new file mode 100644 index 00000000..47cf58d7 Binary files /dev/null and b/test_site/public/apple-icon-60x60.png differ diff --git a/test_site/public/apple-icon-72x72.png b/test_site/public/apple-icon-72x72.png new file mode 100644 index 00000000..f56ef3b6 Binary files /dev/null and b/test_site/public/apple-icon-72x72.png differ diff --git a/test_site/public/apple-icon-76x76.png b/test_site/public/apple-icon-76x76.png new file mode 100644 index 00000000..19db05ba Binary files /dev/null and b/test_site/public/apple-icon-76x76.png differ diff --git a/test_site/public/apple-icon-precomposed.png b/test_site/public/apple-icon-precomposed.png new file mode 100644 index 00000000..ba0fd464 Binary files /dev/null and b/test_site/public/apple-icon-precomposed.png differ diff --git a/test_site/public/apple-icon.png b/test_site/public/apple-icon.png new file mode 100644 index 00000000..ba0fd464 Binary files /dev/null and b/test_site/public/apple-icon.png differ diff --git a/test_site/public/favicon-16x16.png b/test_site/public/favicon-16x16.png new file mode 100644 index 00000000..583a7b00 Binary files /dev/null and b/test_site/public/favicon-16x16.png differ diff --git a/test_site/public/favicon-32x32.png b/test_site/public/favicon-32x32.png new file mode 100644 index 00000000..b9f890cd Binary files /dev/null and b/test_site/public/favicon-32x32.png differ diff --git a/test_site/public/favicon-96x96.png b/test_site/public/favicon-96x96.png new file mode 100644 index 00000000..45c999a9 Binary files /dev/null and b/test_site/public/favicon-96x96.png differ diff --git a/test_site/public/favicon.ico b/test_site/public/favicon.ico new file mode 100644 index 00000000..38a99331 Binary files /dev/null and b/test_site/public/favicon.ico differ diff --git a/test_site/public/ms-icon-144x144.png b/test_site/public/ms-icon-144x144.png new file mode 100644 index 00000000..83a6beb2 Binary files /dev/null and b/test_site/public/ms-icon-144x144.png differ diff --git a/test_site/public/ms-icon-150x150.png b/test_site/public/ms-icon-150x150.png new file mode 100644 index 00000000..174d8071 Binary files /dev/null and b/test_site/public/ms-icon-150x150.png differ diff --git a/test_site/public/ms-icon-310x310.png b/test_site/public/ms-icon-310x310.png new file mode 100644 index 00000000..a3d87914 Binary files /dev/null and b/test_site/public/ms-icon-310x310.png differ diff --git a/test_site/public/ms-icon-70x70.png b/test_site/public/ms-icon-70x70.png new file mode 100644 index 00000000..b6f69ff8 Binary files /dev/null and b/test_site/public/ms-icon-70x70.png differ diff --git a/test_site/rust-toolchain.toml b/test_site/rust-toolchain.toml new file mode 100644 index 00000000..271800cb --- /dev/null +++ b/test_site/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/test_site/src-tauri/.gitignore b/test_site/src-tauri/.gitignore new file mode 100644 index 00000000..502406b4 --- /dev/null +++ b/test_site/src-tauri/.gitignore @@ -0,0 +1,4 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +/gen/schemas diff --git a/test_site/src-tauri/Cargo.toml b/test_site/src-tauri/Cargo.toml new file mode 100644 index 00000000..626e5fcc --- /dev/null +++ b/test_site/src-tauri/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "test_site" +authors = ["Adam Doyle <adam@doordesk.net>"] +description = "A Tauri App" +edition = "2024" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "test_site_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build.workspace = true + +[dependencies] +log.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +tauri-plugin-log.workspace = true +tauri-plugin-opener.workspace = true +tauri.workspace = true diff --git a/test_site/src-tauri/build.rs b/test_site/src-tauri/build.rs new file mode 100644 index 00000000..795b9b7c --- /dev/null +++ b/test_site/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/test_site/src-tauri/capabilities/default.json b/test_site/src-tauri/capabilities/default.json new file mode 100644 index 00000000..c135d7f1 --- /dev/null +++ b/test_site/src-tauri/capabilities/default.json @@ -0,0 +1,11 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "enables the default permissions", + "windows": [ + "main" + ], + "permissions": [ + "core:default" + ] +} diff --git a/test_site/src-tauri/icons/128x128.png b/test_site/src-tauri/icons/128x128.png new file mode 100644 index 00000000..77e7d233 Binary files /dev/null and b/test_site/src-tauri/icons/128x128.png differ diff --git a/test_site/src-tauri/icons/128x128@2x.png b/test_site/src-tauri/icons/128x128@2x.png new file mode 100644 index 00000000..0f7976f1 Binary files /dev/null and b/test_site/src-tauri/icons/128x128@2x.png differ diff --git a/test_site/src-tauri/icons/32x32.png b/test_site/src-tauri/icons/32x32.png new file mode 100644 index 00000000..98fda06f Binary files /dev/null and b/test_site/src-tauri/icons/32x32.png differ diff --git a/test_site/src-tauri/icons/Square107x107Logo.png b/test_site/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 00000000..f35d84ff Binary files /dev/null and b/test_site/src-tauri/icons/Square107x107Logo.png differ diff --git a/test_site/src-tauri/icons/Square142x142Logo.png b/test_site/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 00000000..1823bb26 Binary files /dev/null and b/test_site/src-tauri/icons/Square142x142Logo.png differ diff --git a/test_site/src-tauri/icons/Square150x150Logo.png b/test_site/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 00000000..dc2b22ce Binary files /dev/null and b/test_site/src-tauri/icons/Square150x150Logo.png differ diff --git a/test_site/src-tauri/icons/Square284x284Logo.png b/test_site/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 00000000..0ed3984c Binary files /dev/null and b/test_site/src-tauri/icons/Square284x284Logo.png differ diff --git a/test_site/src-tauri/icons/Square30x30Logo.png b/test_site/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 00000000..60bf0ead Binary files /dev/null and b/test_site/src-tauri/icons/Square30x30Logo.png differ diff --git a/test_site/src-tauri/icons/Square310x310Logo.png b/test_site/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 00000000..c8ca0ad1 Binary files /dev/null and b/test_site/src-tauri/icons/Square310x310Logo.png differ diff --git a/test_site/src-tauri/icons/Square44x44Logo.png b/test_site/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 00000000..8756459b Binary files /dev/null and b/test_site/src-tauri/icons/Square44x44Logo.png differ diff --git a/test_site/src-tauri/icons/Square71x71Logo.png b/test_site/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 00000000..2c8023cc Binary files /dev/null and b/test_site/src-tauri/icons/Square71x71Logo.png differ diff --git a/test_site/src-tauri/icons/Square89x89Logo.png b/test_site/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 00000000..2c5e6034 Binary files /dev/null and b/test_site/src-tauri/icons/Square89x89Logo.png differ diff --git a/test_site/src-tauri/icons/StoreLogo.png b/test_site/src-tauri/icons/StoreLogo.png new file mode 100644 index 00000000..17d142c0 Binary files /dev/null and b/test_site/src-tauri/icons/StoreLogo.png differ diff --git a/test_site/src-tauri/icons/icon.icns b/test_site/src-tauri/icons/icon.icns new file mode 100644 index 00000000..a2993adc Binary files /dev/null and b/test_site/src-tauri/icons/icon.icns differ diff --git a/test_site/src-tauri/icons/icon.ico b/test_site/src-tauri/icons/icon.ico new file mode 100644 index 00000000..06c23c82 Binary files /dev/null and b/test_site/src-tauri/icons/icon.ico differ diff --git a/test_site/src-tauri/icons/icon.png b/test_site/src-tauri/icons/icon.png new file mode 100644 index 00000000..d1756ce4 Binary files /dev/null and b/test_site/src-tauri/icons/icon.png differ diff --git a/test_site/src-tauri/src/lib.rs b/test_site/src-tauri/src/lib.rs new file mode 100644 index 00000000..9c3118c5 --- /dev/null +++ b/test_site/src-tauri/src/lib.rs @@ -0,0 +1,16 @@ +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .setup(|app| { + if cfg!(debug_assertions) { + app.handle().plugin( + tauri_plugin_log::Builder::default() + .level(log::LevelFilter::Info) + .build(), + )?; + } + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/test_site/src-tauri/src/main.rs b/test_site/src-tauri/src/main.rs new file mode 100644 index 00000000..8227a080 --- /dev/null +++ b/test_site/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + test_site_lib::run(); +} diff --git a/test_site/src-tauri/tauri.conf.json b/test_site/src-tauri/tauri.conf.json new file mode 100644 index 00000000..dabe8cfb --- /dev/null +++ b/test_site/src-tauri/tauri.conf.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "test_site", + "version": "0.1.0", + "identifier": "com.test_site.dev", + "build": { + "beforeDevCommand": "trunk serve --features csr", + "devUrl": "http://localhost:3010", + "beforeBuildCommand": "trunk build --features csr", + "frontendDist": "../dist" + }, + "app": { + "withGlobalTauri": true, + "windows": [ + { + "title": "test_site", + "width": 800, + "height": 600, + "resizable": true, + "fullscreen": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/test_site/style/tailwind.css b/test_site/style/tailwind.css new file mode 100644 index 00000000..f1d8c73c --- /dev/null +++ b/test_site/style/tailwind.css @@ -0,0 +1 @@ +@import "tailwindcss";