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

"Agent Tasks"

- -
- - + + + + "Comments" + + - - "Share your thoughts about our service." - - - - -
+
+ + + "Feedback" + + + } +} diff --git a/docs/src/routes/components/label/examples/labels_in_fields.rs b/docs/src/routes/components/label/examples/labels_in_fields.rs new file mode 100644 index 00000000..c252c819 --- /dev/null +++ b/docs/src/routes/components/label/examples/labels_in_fields.rs @@ -0,0 +1,97 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn LabelsInFieldsExample() -> impl IntoView { + view! { +
+
+ +
+ "Payment Method" + + "All transactions are secure and encrypted" + + + + + + + + + + + "Enter your 16-digit card number" + + +
+ + + + + + + + + + + + +
+
+
+ +
+ "Billing Address" + + "The billing address associated with your payment method" + + + + + + + +
+
+ + + + - +
+ + + } +} 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! { + + + "Enter your message below." + + "Please enter a valid message." + + } +} 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 button \ 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", 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! { {icon!(LuBold)} } + let pressed = RwSignal::new(false); + + view! { + + + {icon!(icondata::LuBookmark, class="fill-(--foreground)")} + + Bookmark + + } } 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! { - - {icon!(LuUnderline)} - +
+ + "Disabled" + + + "Disabled" + +
} } 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! { - - {icon!(LuItalic)} - - } -} 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! { - - {icon!(LuItalic)} - +
+ + {icon!(icondata::LuItalic)} + "Italic" + + + {icon!(icondata::LuBold)} + "Bold" + +
} } 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! { +
+ + "Small" + + + "Default" + + + "Large" + +
+ } +} 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! { - - {icon!(LuItalic)} - - } -} 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! { {icon!(LuItalic)} "Italic" } + view! { {icon!(LuItalic)} "Italic" } } 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! { } + view! { + + + + + } } 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 = """"

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

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

"More Info" \ +"""" + [[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 global \ +attributes.""" +attrs = [] + +[[references]] +name = "TooltipContent" +description = "." extra = """Implements global \ 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 global \ +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! { - - + + + + + +

"Add to library"

+
} } 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! { + + + + + +

"This feature is currently unavailable"

+
+
+ } +} 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! { -
-
- - - - - - - - - -
- -
-
- - - - - - - - - -
-
- - - - - - - - - -
-
- -
- - - - - - - - - -
-
- } -} 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! { +
+ + + + + + +

"Add to library"

+
+
+
+
+ } +} 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! { + + + + + "Save Changes" "S" + + } +} 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! { -

"Checkbox"

-
    -
  • "Checked state should not flicker on load or reload"
  • -
  • - "Checked state should not lose sync with the signal when alternating clicking the - button and the checkbox" -
  • -
  • "Disabled state should not flicker on load or reload"
  • -
- -
    -
  • "Checked: "{move || checked.get().to_string()}
  • -
  • "Disabled: "{move || disabled.get().to_string()}
  • -
- - - - - - - - } -} diff --git a/docs/src/routes/debug/dropdown.rs b/docs/src/routes/debug/dropdown.rs deleted file mode 100644 index 15200bef..00000000 --- a/docs/src/routes/debug/dropdown.rs +++ /dev/null @@ -1,164 +0,0 @@ -use leptos::prelude::*; -use singlestage::*; - -#[component] -fn Item() -> impl IntoView { - view! { - "Click me" - } -} - -#[component] -pub fn DebugDropdown() -> impl IntoView { - let open = RwSignal::new(false); - - view! { -

"Dropdown Menu"

-
    -
  • "Console should log \"hey\" when clicked"
  • -
  • "Console should log \"hey\" when tapped"
  • -
  • - "Console should log \"hey\" when selected with keyboard and ""Enter" - " is pressed" -
  • -
- - - - - - - - - - - - - - - - - - "no dismiss" - - - - // top - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // right - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // bottom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // left - - - - - - - - - - - - - - - - - - - - - - - - - - - - } -} 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 {

"Wrap your app, probably at the router, with ThemeProvider:"

... -<Stylesheet id="leptos" href="/pkg/my_app.css" /> <ThemeProvider> + <Stylesheet id="leptos" href="/pkg/my_app.css" /> <Router> ... </Router> @@ -20,13 +20,13 @@ pub fn ThemeProviderRoute() -> impl IntoView { "# .to_string() /> -

"Load your own stylesheets before ThemeProvider to avoid conflicts."

+

"Load your own stylesheets after ThemeProvider to avoid conflicts."

"Setting the Default Theme and Mode"

"By default, the theme provider expands to look like this:"

-<ThemeProvider mode="auto" theme=Theme::Default> +<ThemeProvider mode="auto" theme=Theme::Neutral> "# .to_string() /> @@ -38,7 +38,7 @@ pub fn ThemeProviderRoute() -> impl IntoView {

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

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! { - - - - + +
+ + + "SD" + -
-

- "Sofia Davis" -

-

"m@example.com"

-
+
+

+ "Sofia Davis" +

+

"m@example.com"

+
- - - + + + + + +

"New message"

+
+
+
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 {
diff --git a/docs/src/routes/landing/demos/report_issue.rs b/docs/src/routes/landing/demos/report_issue.rs index 68a33d1d..2e0bbc89 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 {
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 {

"m@example.com"

- + "Viewer" + "Developer" + "Billing" + "Owner" @@ -41,12 +41,12 @@ pub fn TeamMembers() -> impl IntoView {

"p@example.com"

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

"i@example.com"

- + "Viewer" + "Developer" + "Billing" + "Owner" diff --git a/docs/src/routes/mod.rs b/docs/src/routes/mod.rs index 2149eea9..30f8066c 100644 --- a/docs/src/routes/mod.rs +++ b/docs/src/routes/mod.rs @@ -1,9 +1,7 @@ mod components; -mod debug; mod getting_started; mod landing; pub use components::*; -pub use debug::*; pub use getting_started::*; pub use landing::*; 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/tailwind.config.js b/docs/tailwind.config.js new file mode 100644 index 00000000..13881799 --- /dev/null +++ b/docs/tailwind.config.js @@ -0,0 +1,14 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: { + relative: true, + files: ["*.html", "./src/**/*.rs"], + transform: { + rs: (content) => content.replace(/(?:^|\s)class:/g, ' '), + }, + }, + theme: { + extend: {}, + }, + plugins: [], +} 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>(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>( + path: P, + ss: Arc, + theme: Arc<&Theme>, + bg: Arc, +) -> String { + let mut highlighter = HighlightFile::new(&path, &ss, &theme).unwrap(); let mut output = "
".to_string();
 
     let mut line = String::new();
@@ -24,14 +24,15 @@ pub fn highlight_html_from_file>(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("
\n"); output diff --git a/docs_macro/src/lib.rs b/docs_macro/src/lib.rs index 97eeab1c..ece010bc 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, } -/// 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::(); + 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::() + 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 { 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::>() - .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::>(); names.sort(); - for name in names { - output.push_str( + + let links = names + .par_iter() + .map(|name| { format!( r#" - - + "{}" - + "#, string_to_kebab(&name), string_to_title(&name) ) - .as_str(), - ) - } - - output.push('}'); + }) + .collect::(); - 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#" + +"#, + string_to_kebab(&component.name), + string_to_pascal(&component.name) + ) + }) + .collect::(); + + format!( r#" use leptos_router::MatchNestedRoutes; #[component(transparent)] -pub fn ComponentRoutes() -> impl MatchNestedRoutes + Clone { - view! { +pub fn ComponentRoutes() -> impl MatchNestedRoutes + Clone {{ + view! {{ } - >"#, - ); - - 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::>() - .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#" - "#, - 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! {{ }} + >{} - } + }} .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::>() - .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#" {{icon!(icondata::LuInfo)}} @@ -293,116 +285,112 @@ 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##" + } 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##" "##, - 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, - reference.description, - reference.extra.unwrap_or_default(), - ) - .as_str(), + title.unwrap_or_default(), + description.unwrap_or_default(), + string_to_pascal(&example.name), + example_code + ) + }) + .collect::(), ) } 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::(); + // Remove trailing comma + let _ = buf.pop(); + Some(buf) + }; - references.push_str( format!( r###" "###, 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::(); - // 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 +411,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::() + .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 1de4e1f8..7c0d5880 100644 --- a/singlestage/Cargo.toml +++ b/singlestage/Cargo.toml @@ -20,8 +20,10 @@ leptos = { workspace = true } leptos_meta = { 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 } @@ -35,6 +37,7 @@ stores = ["dep:reactive_stores"] default = [ "accordion", "alert", + "alert_dialog", "aspect_ratio", "avatar", "badge", @@ -44,6 +47,7 @@ default = [ "card", "carousel", "checkbox", + "collapsible", "context_menu", "dialog", "dropdown", @@ -63,10 +67,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,6 +90,7 @@ default = [ accordion = [] alert = [] +alert_dialog = ["dialog"] aspect_ratio = [] avatar = [] badge = [] @@ -87,7 +100,8 @@ button_group = [] card = [] carousel = [] checkbox = ["field", "label"] -context_menu = ["dropdown"] +collapsible = [] +context_menu = [] dialog = [] dropdown = ["popover"] empty = [] @@ -107,10 +121,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 c32f734d..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,35 +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::>(); + 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", "dialog", "dropdown", "empty", - "field", "input", - "input_group", "item", "kbd", "label", @@ -147,6 +230,7 @@ fn main() { "scroll_area", "select", "separator", + "sheet", "sidebar", "skeleton", "slider", @@ -156,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::>(); - 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::() + ); + 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::() + ); + 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! { -
- {children()} +
+
{children()}
} } 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! {
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 { + @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..cd53f4f4 --- /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..d51d6a49 --- /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 ae44d0c4..de10ee58 100644 --- a/singlestage/src/components/accordion/trigger.rs +++ b/singlestage/src/components/accordion/trigger.rs @@ -161,7 +161,7 @@ pub fn AccordionTrigger( }; view! { - +

{children()} {if let Some(accordion_icon) = accordion_icon { 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, + /// Sets whether the input value should be capitalized and how. If a parent `
` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + // class=move || class.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { +
+ {children()} +
+ } +} diff --git a/singlestage/src/components/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..2fbb0ca2 100644 --- a/singlestage/src/components/alert/alert.rs +++ b/singlestage/src/components/alert/alert.rs @@ -158,15 +158,15 @@ pub fn Alert(
"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! { -
+
{children()}
} 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..e24a5656 --- /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 { + @apply underline + underline-offset-3; + + &:hover { + @apply text-foreground; + } + } + } + + .singlestage-alert-description { + @apply md:text-pretty + text-balance + text-muted-foreground; + + a { + @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! { -

+

{children()}

} 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! { {children()} } +} 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! { {children()} } +} diff --git a/singlestage/src/components/alert_dialog/content.rs b/singlestage/src/components/alert_dialog/content.rs new file mode 100644 index 00000000..76ab65d3 --- /dev/null +++ b/singlestage/src/components/alert_dialog/content.rs @@ -0,0 +1,176 @@ +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. + /// + /// Defaults to `true` for `Dialog` and `false` for `AlertDialog` + #[prop(optional, into)] + close_button: MaybeProp, + /// Set the size of dialog to render. + /// + /// Accepted values are "sm" + #[prop(optional, into)] + size: MaybeProp, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children()} + + } +} diff --git a/singlestage/src/components/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, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children()} + + } +} diff --git a/singlestage/src/components/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, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + class=move || class.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children()} + + } +} diff --git a/singlestage/src/components/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, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children()} + + } +} diff --git a/singlestage/src/components/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, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children()} + + } +} diff --git a/singlestage/src/components/alert_dialog/media.rs b/singlestage/src/components/alert_dialog/media.rs new file mode 100644 index 00000000..2b4bab91 --- /dev/null +++ b/singlestage/src/components/alert_dialog/media.rs @@ -0,0 +1,168 @@ +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, + + /// The display variant of the media element such as "destructive". + #[prop(optional, into)] + variant: MaybeProp, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children()} + + } +} diff --git a/singlestage/src/components/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..e4fb09a3 --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/luma/alert_dialog.css @@ -0,0 +1,101 @@ +@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 { + @apply 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..6f564eac --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/lyra/alert_dialog.css @@ -0,0 +1,100 @@ +@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 { + @apply 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..0d97becc --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/maia/alert_dialog.css @@ -0,0 +1,100 @@ +@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 { + @apply 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..cc116264 --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/mira/alert_dialog.css @@ -0,0 +1,100 @@ +@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 { + @apply 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..18a94700 --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/nova/alert_dialog.css @@ -0,0 +1,110 @@ +@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 { + @apply 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..3c9f4581 --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/sera/alert_dialog.css @@ -0,0 +1,105 @@ +@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 { + @apply 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..fdb90bfb --- /dev/null +++ b/singlestage/src/components/alert_dialog/style/base/vega/alert_dialog.css @@ -0,0 +1,100 @@ +@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 { + @apply 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, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children()} + + } +} diff --git a/singlestage/src/components/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! { {children()} } +} 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..45dc2d50 100644 --- a/singlestage/src/components/aspect_ratio/mod.rs +++ b/singlestage/src/components/aspect_ratio/mod.rs @@ -7,7 +7,7 @@ pub fn AspectRatio( /// The aspect ratio to use to display the child element. #[prop(optional, into)] - ratio: MaybeProp, + ratio: MaybeProp, // GLOBAL ATTRIBUTES // @@ -119,7 +119,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 +156,13 @@ pub fn AspectRatio( view! {
-
{children()}
+ {children()}
} } 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, + // 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, ) -> 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! { -
"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()} -
+ {children()} + } } 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, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children.map(|c| c())} + + } +} 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, ) -> impl IntoView { + let avatar = expect_context::(); + let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -151,15 +154,22 @@ pub fn AvatarFallback( }; view! { -
"none", + false => "flex", + } + .to_string() + } {..global_attrs_1} {..global_attrs_2} > {children()} -
+ } } 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, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { +
+ {children()} +
+ } +} diff --git a/singlestage/src/components/avatar/image.rs b/singlestage/src/components/avatar/image.rs index 54632445..5ec98dd7 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,8 @@ pub fn AvatarImage( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { + let avatar = expect_context::(); + let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -203,7 +206,9 @@ pub fn AvatarImage( view! { move "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, +} 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 a545d618..46d1905c 100644 --- a/singlestage/src/components/badge/mod.rs +++ b/singlestage/src/components/badge/mod.rs @@ -164,13 +164,14 @@ pub fn Badge( 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, - /// Toggle whether or not the list should be reversed. - #[prop(optional, into)] - reversed: MaybeProp, - /// Set which item to start counting from starting with 1. - #[prop(optional, into)] - start: MaybeProp, - // 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! { -
    {children()} -
+ } } 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, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + 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! { + + + + + + + "More" + + } +} diff --git a/singlestage/src/components/breadcrumb/link.rs b/singlestage/src/components/breadcrumb/link.rs new file mode 100644 index 00000000..084dc30d --- /dev/null +++ b/singlestage/src/components/breadcrumb/link.rs @@ -0,0 +1,9 @@ +use leptos::{context::Provider, prelude::*}; + +#[derive(Clone)] +pub(crate) struct BreadcrumbLinkContext {} + +#[component] +pub fn BreadcrumbLink(children: Children) -> impl IntoView { + view! { {children()} } +} 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, + /// Toggle whether or not the list should be reversed. + #[prop(optional, into)] + reversed: MaybeProp, + /// Set which item to start counting from starting with 1. + #[prop(optional, into)] + start: MaybeProp, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + // class=move || class.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { +
    + {children()} +
+ } +} diff --git a/singlestage/src/components/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, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + 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! { + + {children()} + + } +} 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, - /// Define the semantic meaning of content. - #[prop(optional, into)] - role: MaybeProp, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp, @@ -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! { + - } - .into_any() - } else { - view! { -
  • - - - -
  • - } - .into_any() + {..global_attrs_1} + {..global_attrs_2} + > + {if let Some(children) = children { + children() + } else { + view! { + + + + } + .into_any() + }} + } } 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 cf8bbb68..c379ae3c 100644 --- a/singlestage/src/components/button/mod.rs +++ b/singlestage/src/components/button/mod.rs @@ -1,6 +1,6 @@ use crate::{ - DropdownMenuContext, DropdownTriggerContext, InputGroupContext, PopoverContext, - PopoverTriggerContext, Reactive, + CollapsibleContext, FieldContext, InputGroupContext, PopoverContext, Reactive, + SheetCloseContext, SheetContext, SidebarMenuButtonContext, primitives::*, }; use leptos::prelude::*; @@ -13,12 +13,15 @@ pub fn Button( /// Button types: submit | button | reset #[prop(optional, into)] button_type: MaybeProp, + /// Whether the input is invalid + #[prop(optional, into)] + invalid: Reactive, /// 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, - /// The display variant of the button. Defaults to `primary` - /// Variants: primary | secondary | outline | ghost | link | destructive + /// The display variant of the button. Defaults to `default` + /// Variants: default | secondary | outline | ghost | link | destructive | none #[prop(optional, into)] variant: MaybeProp, @@ -191,6 +194,41 @@ pub fn Button( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { + let update_disabled = move || { + if let Some(field) = use_context::() { + 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::() { + 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 +244,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,144 +266,243 @@ 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::().is_some() - || use_context::().is_some(); + let in_input_group: bool = use_context::().is_some(); + let in_popover_menu_item: bool = use_context::().is_some(); + let in_sidebar_menu_button: bool = use_context::().is_some(); + let is_dialog_action: bool = use_context::().is_some(); + let is_dialog_cancel: bool = use_context::().is_some(); + let is_dialog_close: bool = use_context::().is_some(); + let is_sheet_close: bool = use_context::().is_some(); + let is_trigger: bool = use_context::().is_some(); view! { - + {children()} + } } diff --git a/singlestage/src/components/context_menu/label.rs b/singlestage/src/components/context_menu/label.rs index 107a1d08..bd78676d 100644 --- a/singlestage/src/components/context_menu/label.rs +++ b/singlestage/src/components/context_menu/label.rs @@ -1,14 +1,23 @@ -use crate::ContextMenuGroupContext; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// Labels groups. #[component] pub fn ContextMenuLabel( children: Children, + /// Whether the element renders as disabled + #[prop(optional, into)] + disabled: Reactive, /// Set whether or not this element should display inset from its normal position. + /// (For use in popover menus) #[prop(optional, into)] inset: MaybeProp, + /// Whether the element renders as invalid + #[prop(optional, into)] + invalid: Reactive, + /// The id of the labeled element if it's not a child + #[prop(optional, into)] + label_for: MaybeProp, // GLOBAL ATTRIBUTES // @@ -51,6 +60,9 @@ pub fn ContextMenuLabel( /// Controls hidden status of the element. #[prop(optional, into)] hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, /// Toggle if the browser reacts to input events from this element. #[prop(optional, into)] inert: MaybeProp, @@ -88,6 +100,9 @@ pub fn ContextMenuLabel( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp, @@ -109,11 +124,6 @@ pub fn ContextMenuLabel( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let group = expect_context::(); - - let uuid = uuid::Uuid::new_v4().to_string(); - group.heading_id.set(uuid.clone()); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -142,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() @@ -152,24 +163,19 @@ pub fn ContextMenuLabel( }; view! { -
    {children()} -
    + } } diff --git a/singlestage/src/components/context_menu/menu.rs b/singlestage/src/components/context_menu/menu.rs index f60e31cd..7b7f6226 100644 --- a/singlestage/src/components/context_menu/menu.rs +++ b/singlestage/src/components/context_menu/menu.rs @@ -1,16 +1,17 @@ -use crate::{ContextMenuContext, Reactive}; +use crate::{Reactive, primitives::*}; use leptos::{context::Provider, prelude::*}; +#[derive(Clone, Default)] +pub(crate) struct ContextMenuContext { + pub x: RwSignal, + pub y: RwSignal, +} + /// Contains all the parts of a context menu. #[component] pub fn ContextMenu( 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, - // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -116,13 +117,12 @@ pub fn ContextMenu( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let menu_id = RwSignal::new(String::new()); - - let context = ContextMenuContext { - menu_id, - open, - x: RwSignal::new(i32::default()), - y: RwSignal::new(i32::default()), + let popover_context = PopoverMenuContext { + dismissable: Reactive::new(true), + ..Default::default() + }; + let menu_context = ContextMenuContext { + ..Default::default() }; let global_attrs_1 = view! { @@ -171,7 +171,9 @@ pub fn ContextMenu( {..global_attrs_1} {..global_attrs_2} > - {children()} + + {children()} +
    } } diff --git a/singlestage/src/components/context_menu/mod.rs b/singlestage/src/components/context_menu/mod.rs index 2664dcce..dc44231d 100644 --- a/singlestage/src/components/context_menu/mod.rs +++ b/singlestage/src/components/context_menu/mod.rs @@ -1,35 +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 crate::Reactive; -use leptos::prelude::*; - -#[derive(Clone)] -pub struct ContextMenuContext { - menu_id: RwSignal, - pub open: Reactive, - x: RwSignal, - y: RwSignal, -} - -#[derive(Clone)] -pub struct ContextMenuGroupContext { - heading_id: RwSignal, -} diff --git a/singlestage/src/components/context_menu/radio.rs b/singlestage/src/components/context_menu/radio.rs new file mode 100644 index 00000000..0e540ae4 --- /dev/null +++ b/singlestage/src/components/context_menu/radio.rs @@ -0,0 +1,192 @@ +use crate::{Reactive, primitives::*}; +use leptos::prelude::*; + +/// Contains a radio menu item. +#[component] +pub fn ContextMenuRadioItem( + children: Children, + + #[prop(optional, into)] checked: Reactive, + + /// Controls whether the item appears disabled and is clickable. + #[prop(optional, into)] + disabled: Reactive, + /// Toggle whether clicking this item dismisses its parent menu + #[prop(optional, into, default = Reactive::new(true))] + dismiss: Reactive, + /// Set whether or not this element should display inset from its normal position. + #[prop(optional, into)] + inset: MaybeProp, + /// Set the display variant of the item. + /// + /// Accepted values: "destructive" + #[prop(optional, into)] + variant: MaybeProp, + + // LI ATTRIBRUTES + // + /// The current ordinal value of the item. + #[prop(optional, into)] + value: MaybeProp, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + class=move || class.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + 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! { + + {children()} + + } +} 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..2422f52d --- /dev/null +++ b/singlestage/src/components/context_menu/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 ContextMenuRadioGroup( + children: Children, + + /// Set or update the default value. + #[prop(optional, into)] + default: MaybeProp, + /// Set or update the invalid state of the radio group. + #[prop(optional, into)] + invalid: Reactive, + /// Reactive signal coupled to the current selected value of the radio group. + #[prop(optional, into)] + value: Reactive, + + // FIELDSET ATTRIBUTES + // + /// Toggle whether or not the input is disabled. + #[prop(optional, into)] + disabled: Reactive, + /// Associate this element with a form element that may not be its parent by its `id`. + #[prop(optional, into)] + form: MaybeProp, + /// Name of this element. Submitted with the form as part of a name/value pair. + #[prop(optional, into)] + name: MaybeProp, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + class=move || class.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + 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! { + + {children()} + + } +} diff --git a/singlestage/src/components/context_menu/separator.rs b/singlestage/src/components/context_menu/separator.rs index 21ae821d..f91f5a58 100644 --- a/singlestage/src/components/context_menu/separator.rs +++ b/singlestage/src/components/context_menu/separator.rs @@ -1,8 +1,15 @@ +use crate::Separator; use leptos::prelude::*; -/// Visually separates groups of menu items. +/// The separator. #[component] pub fn ContextMenuSeparator( + #[prop(optional)] children: Option, + + /// Toggle whether or not the separator should display vertically. + #[prop(optional, into)] + vertical: MaybeProp, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -84,6 +91,9 @@ pub fn ContextMenuSeparator( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp, @@ -110,6 +120,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 +145,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 +155,27 @@ pub fn ContextMenuSeparator( /> }; - view! { - + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + + } + .into_any() + } else { + view! { + + } + .into_any() } } diff --git a/singlestage/src/components/context_menu/shortcut.rs b/singlestage/src/components/context_menu/shortcut.rs index 4813972e..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! { - {children()} - + } } diff --git a/singlestage/src/components/context_menu/sub/content.rs b/singlestage/src/components/context_menu/sub/content.rs index 6fa37f8a..e5e7a586 100644 --- a/singlestage/src/components/context_menu/sub/content.rs +++ b/singlestage/src/components/context_menu/sub/content.rs @@ -1,4 +1,4 @@ -use crate::ContextMenuSubContext; +use crate::primitives::*; use leptos::prelude::*; /// The component that pops out when the sub trigger is triggered. @@ -6,6 +6,19 @@ use leptos::prelude::*; pub fn ContextMenuSubContent( children: Children, + /// Set how to align the popover + /// + /// Accepted values: "start" | "center" | "end" + /// Default is "start" + #[prop(optional, into)] + align: MaybeProp, + /// Set which side the popover opens relative to the trigger + /// + /// Accepted values: "top" | "right" | "bottom" | "left" + /// Default is "bottom" + #[prop(optional, into)] + side: MaybeProp, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -47,6 +60,9 @@ pub fn ContextMenuSubContent( /// Controls hidden status of the element. #[prop(optional, into)] hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, /// Toggle if the browser reacts to input events from this element. #[prop(optional, into)] inert: MaybeProp, @@ -102,18 +118,6 @@ pub fn ContextMenuSubContent( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let sub = expect_context::(); - let menu_ref = NodeRef::::new(); - - let uuid = uuid::Uuid::new_v4(); - sub.menu_id.set(uuid.to_string()); - - Effect::new(move || { - if let Some(popover) = menu_ref.get_untracked() { - let _ = popover.toggle_popover_with_force(sub.open.get()); - } - }); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -143,7 +147,6 @@ pub fn ContextMenuSubContent( 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() @@ -151,23 +154,19 @@ pub fn ContextMenuSubContent( }; view! { - {children()} - + } } 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, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + class=move || class.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children()} + + } +} diff --git a/singlestage/src/components/context_menu/sub/mod.rs b/singlestage/src/components/context_menu/sub/mod.rs index b8b9704e..ee49e468 100644 --- a/singlestage/src/components/context_menu/sub/mod.rs +++ b/singlestage/src/components/context_menu/sub/mod.rs @@ -1,192 +1,7 @@ mod content; +mod menu; mod trigger; pub use content::*; +pub use menu::*; pub use trigger::*; - -use crate::Reactive; -use leptos::{context::Provider, prelude::*}; - -#[derive(Clone)] -pub struct ContextMenuSubContext { - pub menu_id: RwSignal, - pub open: Reactive, - pub trigger_id: RwSignal, -} - -/// 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, - - // GLOBAL ATTRIBUTES - // - /// A space separated list of keys to focus this element. The first key available on the user's - /// keyboard layout is used. - #[prop(optional, into)] - accesskey: MaybeProp, - /// Sets whether the input value should be capitalized and how. If a parent `` has - /// `autocapitalize` rules set, it will override any rules set here. - /// - /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". - #[prop(optional, into)] - autocapitalize: MaybeProp, - /// Grabs focus once the page has finished loading. Only one element on the page can be focused - /// at a time. - #[prop(optional, into)] - autofocus: MaybeProp, - /// Apply classes to the element. - #[prop(optional, into)] - class: MaybeProp, - /// Allows client-side editing of the element by the user. - /// - /// Accepted values: "true" | "false" | "plaintext-only" - #[prop(optional, into)] - contenteditable: MaybeProp, - /// Indicate directionality of the element's text. - /// - /// Accepted values: "ltr" | "rtl" | "auto" - #[prop(optional, into)] - dir: MaybeProp, - /// Toggle whether the element can be dragged. - #[prop(optional, into)] - draggable: MaybeProp, - /// Modifies the appearance of the enter key on virtual keyboards. - #[prop(optional, into)] - enterkeyhint: MaybeProp, - /// Expose elements in the shadow DOM to be manipulated by the DOM. - #[prop(optional, into)] - exportparts: MaybeProp, - /// Controls hidden status of the element. - #[prop(optional, into)] - hidden: MaybeProp, - /// Set the id of this element. - #[prop(optional, into)] - id: MaybeProp, - /// Toggle if the browser reacts to input events from this element. - #[prop(optional, into)] - inert: MaybeProp, - /// Hints to the browser of what type of virtual keyboard to display when editing this element - /// or its children. - #[prop(optional, into)] - inputmode: MaybeProp, - /// Used to render a standard element as a custom element. - #[prop(optional, into)] - is: MaybeProp, - /// Unique global identifier of an item. - #[prop(optional, into)] - itemid: MaybeProp, - /// Used to add properties to an item. - #[prop(optional, into)] - itemprop: MaybeProp, - /// Used to associate an item with a related non-parent element that's using `itemscope`. - #[prop(optional, into)] - itemref: MaybeProp, - /// Used to declare that children elements are related to a particular item. - #[prop(optional, into)] - itemscope: MaybeProp, - /// URL of data used to define `itemprops`. - #[prop(optional, into)] - itemtype: MaybeProp, - /// Defines the language of an element. - #[prop(optional, into)] - lang: MaybeProp, - /// Cryptographic "number used once". - #[prop(optional, into)] - nonce: MaybeProp, - /// List of the part names of the element. - #[prop(optional, into)] - part: MaybeProp, - /// Designate an element as a popover element. - #[prop(optional, into)] - popover: MaybeProp, - /// Define the semantic meaning of content. - #[prop(optional, into)] - role: MaybeProp, - /// Assigns a slot to an element. - #[prop(optional, into)] - slot: MaybeProp, - /// Toggle spellcheck for this input. - /// - /// Accepted values: "default" | "true" | "false". - #[prop(optional, into)] - spellcheck: MaybeProp, - /// Define CSS to be applied to the element. - #[prop(optional, into)] - style: MaybeProp, - /// Controls how an element behaves when a user navigates using the tab key. - #[prop(optional, into)] - tabindex: MaybeProp, - /// Describes the content of the element to screen readers. - #[prop(optional, into)] - title: MaybeProp, - /// Defines localization behavior for the element. - #[prop(optional, into)] - translate: MaybeProp, -) -> impl IntoView { - let menu_id = RwSignal::new(String::new()); - let trigger_id = RwSignal::new(String::new()); - - let context = ContextMenuSubContext { - 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! { -
    - {children()} -
    - } -} diff --git a/singlestage/src/components/context_menu/sub/trigger.rs b/singlestage/src/components/context_menu/sub/trigger.rs index ec45af06..af75c92b 100644 --- a/singlestage/src/components/context_menu/sub/trigger.rs +++ b/singlestage/src/components/context_menu/sub/trigger.rs @@ -1,14 +1,13 @@ -use crate::ContextMenuSubContext; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// Defines the area where a sub menu can be triggered. #[component] pub fn ContextMenuSubTrigger( children: Children, /// Controls whether the item appears disabled and is clickable. #[prop(optional, into)] - disabled: MaybeProp, + disabled: Reactive, /// Set whether or not this element should display inset from its normal position. #[prop(optional, into)] inset: MaybeProp, @@ -126,19 +125,19 @@ pub fn ContextMenuSubTrigger( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let sub = expect_context::(); - 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() @@ -162,67 +161,25 @@ pub fn ContextMenuSubTrigger( tabindex=move || tabindex.get() title=move || title.get() translate=move || translate.get() + value=move || value.get() /> }; view! { -
  • " singlestage-dropdown-menu-item-destructive", - _ => "", - }, - class.get().unwrap_or_default(), - ) - } - role="menuitem" - value=move || value.get() + - -
  • + {children()} + } } diff --git a/singlestage/src/components/context_menu/trigger.rs b/singlestage/src/components/context_menu/trigger.rs index 19cb43fc..68ed63ee 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, ) -> impl IntoView { - let menu = expect_context::(); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -145,22 +143,49 @@ pub fn ContextMenuTrigger( /> }; + let context_menu = expect_context::(); + let menu = expect_context::(); + + // 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! {
    , - /// 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, - /// Turn this button into a command button for an element via id. - #[prop(optional, into)] - commandfor: MaybeProp, - /// Toggle whether or not the input is disabled. - #[prop(optional, into)] - disabled: Reactive, - /// Associate this element with a form element that may not be its parent by its `id`. - #[prop(optional, into)] - form: MaybeProp, - /// Defines the target for submitted form data. Overrides any parent `` `action` values. - #[prop(optional, into)] - formaction: MaybeProp, - /// Defines the encoding type for submitted form data. Overrides any parent `` - /// `formenctype` values. - /// - /// Accepted values: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain". - #[prop(optional, into)] - formenctype: MaybeProp, - /// Defines the HTTP method used to submit form data. Overrides any parent `` `method` - /// values. - /// - /// Accepted values: "get" | "post" | "dialog". - #[prop(optional, into)] - formmethod: MaybeProp, - /// Toggle whether the form data is validated or not before submission. Overrides any parent - /// `` `novalidate` values. - #[prop(optional, into)] - formnovalidate: MaybeProp, - /// Defines where to display the response received after submission. Overrides any parent - /// `` `target` values. - /// - /// Accepted values: "_self" | "_blank" | "_parent" | "_top", or the `name` of any tab, window, - /// or iframe - #[prop(optional, into)] - formtarget: MaybeProp, - /// Name of this element. Submitted with the form as part of a name/value pair. - #[prop(optional, into)] - name: MaybeProp, - /// Id of a popover to control. - #[prop(optional, into)] - popovertarget: MaybeProp, - /// The action to perform on the target popover. - /// - /// Accepted values: "hide" | "show" | "toggle" - #[prop(optional, into)] - popovertargetaction: MaybeProp, - /// The value associated with this button. - #[prop(optional, into)] - value: MaybeProp, - - // GLOBAL ATTRIBUTES - // - /// A space separated list of keys to focus this element. The first key available on the user's - /// keyboard layout is used. - #[prop(optional, into)] - accesskey: MaybeProp, - /// Sets whether the input value should be capitalized and how. If a parent `` has - /// `autocapitalize` rules set, it will override any rules set here. - /// - /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". - #[prop(optional, into)] - autocapitalize: MaybeProp, - /// Grabs focus once the page has finished loading. Only one element on the page can be focused - /// at a time. - #[prop(optional, into)] - autofocus: MaybeProp, - /// Apply classes to the element. - #[prop(optional, into)] - class: MaybeProp, - /// Allows client-side editing of the element by the user. - /// - /// Accepted values: "true" | "false" | "plaintext-only" - #[prop(optional, into)] - contenteditable: MaybeProp, - /// Indicate directionality of the element's text. - /// - /// Accepted values: "ltr" | "rtl" | "auto" - #[prop(optional, into)] - dir: MaybeProp, - /// Toggle whether the element can be dragged. - #[prop(optional, into)] - draggable: MaybeProp, - /// Modifies the appearance of the enter key on virtual keyboards. - #[prop(optional, into)] - enterkeyhint: MaybeProp, - /// Expose elements in the shadow DOM to be manipulated by the DOM. - #[prop(optional, into)] - exportparts: MaybeProp, - /// Controls hidden status of the element. - #[prop(optional, into)] - hidden: MaybeProp, - /// Set the id of this element. - #[prop(optional, into)] - id: MaybeProp, - /// Toggle if the browser reacts to input events from this element. - #[prop(optional, into)] - inert: MaybeProp, - /// Hints to the browser of what type of virtual keyboard to display when editing this element - /// or its children. - #[prop(optional, into)] - inputmode: MaybeProp, - /// Used to render a standard element as a custom element. - #[prop(optional, into)] - is: MaybeProp, - /// Unique global identifier of an item. - #[prop(optional, into)] - itemid: MaybeProp, - /// Used to add properties to an item. - #[prop(optional, into)] - itemprop: MaybeProp, - /// Used to associate an item with a related non-parent element that's using `itemscope`. - #[prop(optional, into)] - itemref: MaybeProp, - /// Used to declare that children elements are related to a particular item. - #[prop(optional, into)] - itemscope: MaybeProp, - /// URL of data used to define `itemprops`. - #[prop(optional, into)] - itemtype: MaybeProp, - /// Defines the language of an element. - #[prop(optional, into)] - lang: MaybeProp, - /// Cryptographic "number used once". - #[prop(optional, into)] - nonce: MaybeProp, - /// List of the part names of the element. - #[prop(optional, into)] - part: MaybeProp, - /// Designate an element as a popover element. - #[prop(optional, into)] - popover: MaybeProp, - /// Define the semantic meaning of content. - #[prop(optional, into)] - role: MaybeProp, - /// Assigns a slot to an element. - #[prop(optional, into)] - slot: MaybeProp, - /// Toggle spellcheck for this input. - /// - /// Accepted values: "default" | "true" | "false". - #[prop(optional, into)] - spellcheck: MaybeProp, - /// Define CSS to be applied to the element. - #[prop(optional, into)] - style: MaybeProp, - /// Controls how an element behaves when a user navigates using the tab key. - #[prop(optional, into)] - tabindex: MaybeProp, - /// Describes the content of the element to screen readers. - #[prop(optional, into)] - title: MaybeProp, - /// Defines localization behavior for the element. - #[prop(optional, into)] - translate: MaybeProp, -) -> impl IntoView { - let global_attrs_1 = view! { - <{..} - accesskey=move || accesskey.get() - autocapitalize=move || autocapitalize.get() - autofocus=move || autofocus.get() - class=move || class.get() - contenteditable=move || contenteditable.get() - dir=move || dir.get() - draggable=move || draggable.get() - enterkeyhint=move || enterkeyhint.get() - exportparts=move || exportparts.get() - hidden=move || hidden.get() - id=move || id.get() - inert=move || inert.get() - inputmode=move || inputmode.get() - is=move || is.get() - itemid=move || itemid.get() - /> - }; - - 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! { - - - - } +pub fn DialogClose(children: Children) -> impl IntoView { + view! { {children()} } } 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, + // 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! { -
    + {children()} -
    + } } 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, - // /// Set the id of this element. - // #[prop(optional, into)] - // id: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, /// Toggle if the browser reacts to input events from this element. #[prop(optional, into)] inert: MaybeProp, @@ -111,24 +111,18 @@ pub fn DialogDescription( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let dialog = expect_context::(); - - 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! { -

    + {children()} -

    + } } 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, - // TODO:Check for safari support - // /// Specify what actions will close the dialog. - // /// - // /// Accepted values: "any" | "closerequest" | "none" - // #[prop(optional, into)] - // closedby: MaybeProp, - /// Toggle whether or not the dialog is open. - #[prop(optional, into)] - open: MaybeProp, + /// 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, // GLOBAL ATTRIBUTES // @@ -128,42 +116,6 @@ pub fn Dialog( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let dialog_ref = NodeRef::::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! { -
    {(dialog_trigger.children)().into_any()}
    - -
    - {children()} -
    -
    + {children()} + } } 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! { -
    -
    - {children()} -
    -
    + + {children()} + } } 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! { -
    + {children()} -
    + } } diff --git a/singlestage/src/components/dialog/mod.rs b/singlestage/src/components/dialog/mod.rs index ae77ff28..345f91a5 100644 --- a/singlestage/src/components/dialog/mod.rs +++ b/singlestage/src/components/dialog/mod.rs @@ -15,11 +15,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, - pub described_by: RwSignal, -} 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..819bd75a --- /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 { + @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..69177a54 --- /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 { + @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..804e9fa6 --- /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 { + @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..4ce55bfd --- /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 { + @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..a27bae89 --- /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 { + @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..047b425a --- /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 { + @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..02d79ce9 --- /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 { + @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, ) -> impl IntoView { - let dialog = expect_context::(); - - 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! { -

    + {children()} -

    + } } 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! { {children()} } } diff --git a/singlestage/src/components/dropdown/checkbox.rs b/singlestage/src/components/dropdown/checkbox.rs index 15060a13..6a1534a7 100644 --- a/singlestage/src/components/dropdown/checkbox.rs +++ b/singlestage/src/components/dropdown/checkbox.rs @@ -1,9 +1,9 @@ -use crate::{Checkbox, ContextMenuContext, DropdownMenuContext, Reactive}; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// Contains a menu item. +/// Renders a checkbox menu item. #[component] -pub fn CheckboxItem( +pub fn DropdownMenuCheckboxItem( children: Children, #[prop(optional, into)] checked: Reactive, @@ -136,6 +136,7 @@ pub fn CheckboxItem( 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() @@ -170,43 +171,22 @@ pub fn CheckboxItem( }; view! { -
  • " singlestage-dropdown-menu-item-destructive", - _ => "", - }, - class.get().unwrap_or_default(), - ) - } - on:click=move |_| { - if dismiss.get() { - if let Some(menu) = use_context::() { - if menu.dismissable.get() { - menu.open.set(false); - } - } else if let Some(menu) = use_context::() { - menu.open.set(false); - } - } - } - role="menuitem" - value=move || value.get() + - -
  • + {children()} + } } diff --git a/singlestage/src/components/dropdown/content.rs b/singlestage/src/components/dropdown/content.rs index 6029bf9a..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. @@ -118,15 +118,6 @@ pub fn DropdownMenuContent( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let menu = expect_context::(); - let menu_ref = NodeRef::::new(); - - Effect::new(move || { - if let Some(popover) = menu_ref.get_untracked() { - let _ = popover.toggle_popover_with_force(menu.open.get()); - } - }); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -156,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() @@ -164,38 +154,19 @@ pub fn DropdownMenuContent( }; view! { - "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=move || if menu.dismissable.get() { "auto" } else { "manual" } - style:position-anchor=move || { format!("--{}", menu.trigger_id.get()) } - role="menu" + {children()} - + } } diff --git a/singlestage/src/components/dropdown/dropdown.css b/singlestage/src/components/dropdown/dropdown.css deleted file mode 100644 index 2afb3ffe..00000000 --- a/singlestage/src/components/dropdown/dropdown.css +++ /dev/null @@ -1,128 +0,0 @@ -@layer components { - .singlestage-checkbox-item, - .singlestage-radio-item { - @apply appearance-none - outline-none - shrink-0 - size-4 - transition-shadow; - - &:checked:after { - @apply bg-accent-foreground - block - content-[''] - mask-[image:var(--check-icon)] - mask-center - mask-no-repeat - mask-size-[calc(4*var(--spacing))] - size-4; - } - } - - .singlestage-dropdown-menu-content { - @apply min-w-32 - p-1; - } - - .singlestage-dropdown-menu-inset { - @apply !pl-8; - } - - .singlestage-dropdown-menu-item { - > label, - > button { - @apply content-center - cursor-default - focus:bg-accent - focus:text-accent-foreground - flex - gap-1.5 - h-full - hover:bg-accent - hover:text-accent-foreground - items-center - outline-hidden - px-1.5 - py-1 - rounded-md - select-none - text-nowrap - text-sm - w-full; - } - - > .singlestage-label { - @apply h-full - w-full; - } - - svg { - @apply pointer-events-none - shrink-0; - - &:not([class*="size-"]) { - @apply size-4; - } - } - - .singlestage-checkbox, - .singlestage-radio { - @apply ml-auto; - } - } - - .singlestage-dropdown-menu-item-destructive { - > button { - @apply !text-destructive - focus:!bg-destructive/10 - focus:!text-destructive - hover:!bg-destructive/10 - hover:!text-destructive; - } - - svg { - @apply !text-destructive; - } - } - - .singlestage-dropdown-menu-item-disabled { - * { - @apply opacity-50 - pointer-events-none; - } - } - - .singlestage-dropdown-menu-label { - @apply font-medium - px-1.5 - py-1 - text-muted-foreground - text-xs; - } - - .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-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 dea07e4e..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, ) -> 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! { -
    - {children()} -
    + {children()} + } } diff --git a/singlestage/src/components/dropdown/item.rs b/singlestage/src/components/dropdown/item.rs index 64c606cc..fa46639f 100644 --- a/singlestage/src/components/dropdown/item.rs +++ b/singlestage/src/components/dropdown/item.rs @@ -1,11 +1,12 @@ -use crate::{DropdownMenuContext, Reactive}; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// Contains a menu item. +/// Contains a dropdown menu item. #[component] pub fn DropdownMenuItem( children: Children, + #[prop(optional, into)] as_child: MaybeProp, /// Controls whether the item appears disabled and is clickable. #[prop(optional, into)] disabled: Reactive, @@ -129,13 +130,12 @@ pub fn DropdownMenuItem( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let menu = expect_context::(); - 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() @@ -170,44 +170,22 @@ pub fn DropdownMenuItem( }; view! { -
  • " singlestage-dropdown-menu-item-destructive", - _ => "", - }, - class.get().unwrap_or_default(), - ) - } - role="menuitem" - value=move || value.get() + - -
  • + {children()} + } } diff --git a/singlestage/src/components/dropdown/label.rs b/singlestage/src/components/dropdown/label.rs index a012ad9e..57f0f82c 100644 --- a/singlestage/src/components/dropdown/label.rs +++ b/singlestage/src/components/dropdown/label.rs @@ -1,14 +1,23 @@ -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, /// Set whether or not this element should display inset from its normal position. + /// (For use in popover menus) #[prop(optional, into)] inset: MaybeProp, + /// Whether the element renders as invalid + #[prop(optional, into)] + invalid: Reactive, + /// The id of the labeled element if it's not a child + #[prop(optional, into)] + label_for: MaybeProp, // GLOBAL ATTRIBUTES // @@ -51,6 +60,9 @@ pub fn DropdownMenuLabel( /// Controls hidden status of the element. #[prop(optional, into)] hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, /// Toggle if the browser reacts to input events from this element. #[prop(optional, into)] inert: MaybeProp, @@ -88,6 +100,9 @@ pub fn DropdownMenuLabel( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp, @@ -109,11 +124,6 @@ pub fn DropdownMenuLabel( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let group = expect_context::(); - - let uuid = uuid::Uuid::new_v4().to_string(); - group.heading_id.set(uuid.clone()); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -142,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() @@ -152,24 +163,19 @@ pub fn DropdownMenuLabel( }; view! { -
    {children()} -
    + } } diff --git a/singlestage/src/components/dropdown/menu.rs b/singlestage/src/components/dropdown/menu.rs index 815480b3..8c5e97e3 100644 --- a/singlestage/src/components/dropdown/menu.rs +++ b/singlestage/src/components/dropdown/menu.rs @@ -1,6 +1,9 @@ -use crate::{DropdownMenuContext, Reactive}; +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( @@ -123,7 +126,7 @@ pub fn DropdownMenu( let menu_id = RwSignal::new(String::new()); let trigger_id = RwSignal::new(String::new()); - let context = DropdownMenuContext { + let context = PopoverMenuContext { dismissable, menu_id, open, @@ -176,7 +179,9 @@ pub fn DropdownMenu( {..global_attrs_1} {..global_attrs_2} > - {children()} + + {children()} +
    } } diff --git a/singlestage/src/components/dropdown/mod.rs b/singlestage/src/components/dropdown/mod.rs index fa19e54b..dc44231d 100644 --- a/singlestage/src/components/dropdown/mod.rs +++ b/singlestage/src/components/dropdown/mod.rs @@ -5,6 +5,7 @@ mod item; mod label; mod menu; mod radio; +mod radio_group; mod separator; mod shortcut; mod sub; @@ -17,26 +18,8 @@ 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 crate::Reactive; -use leptos::prelude::*; - -#[derive(Clone)] -pub struct DropdownMenuContext { - pub dismissable: Reactive, - pub menu_id: RwSignal, - pub open: Reactive, - pub trigger_id: RwSignal, -} - -#[derive(Clone)] -pub struct DropdownTriggerContext {} - -#[derive(Clone)] -pub struct DropdownMenuGroupContext { - heading_id: RwSignal, -} diff --git a/singlestage/src/components/dropdown/radio.rs b/singlestage/src/components/dropdown/radio.rs index 81e75707..f1e8e7b9 100644 --- a/singlestage/src/components/dropdown/radio.rs +++ b/singlestage/src/components/dropdown/radio.rs @@ -1,9 +1,9 @@ -use crate::{ContextMenuContext, DropdownMenuContext, Radio, Reactive}; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; -/// Contains a menu item. +/// Contains a radio menu item. #[component] -pub fn RadioItem( +pub fn DropdownMenuRadioItem( children: Children, #[prop(optional, into)] checked: Reactive, @@ -136,6 +136,7 @@ pub fn RadioItem( 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() @@ -170,43 +171,22 @@ pub fn RadioItem( }; view! { -
  • " singlestage-dropdown-menu-item-destructive", - _ => "", - }, - class.get().unwrap_or_default(), - ) - } - on:click=move |_| { - if dismiss.get() { - if let Some(menu) = use_context::() { - if menu.dismissable.get() { - menu.open.set(false); - } - } else if let Some(menu) = use_context::() { - menu.open.set(false); - } - } - } - role="menuitem" - value=move || value.get() + - -
  • + {children()} + } } 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, + /// Set or update the invalid state of the radio group. + #[prop(optional, into)] + invalid: Reactive, + /// Reactive signal coupled to the current selected value of the radio group. + #[prop(optional, into)] + value: Reactive, + + // FIELDSET ATTRIBUTES + // + /// Toggle whether or not the input is disabled. + #[prop(optional, into)] + disabled: Reactive, + /// Associate this element with a form element that may not be its parent by its `id`. + #[prop(optional, into)] + form: MaybeProp, + /// Name of this element. Submitted with the form as part of a name/value pair. + #[prop(optional, into)] + name: MaybeProp, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `
    ` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + class=move || class.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + 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! { + + {children()} + + } +} diff --git a/singlestage/src/components/dropdown/separator.rs b/singlestage/src/components/dropdown/separator.rs index 5ee11c50..ff6b199d 100644 --- a/singlestage/src/components/dropdown/separator.rs +++ b/singlestage/src/components/dropdown/separator.rs @@ -1,8 +1,15 @@ +use crate::Separator; use leptos::prelude::*; -/// Visually separates groups of menu items. +/// The separator. #[component] pub fn DropdownMenuSeparator( + #[prop(optional)] children: Option, + + /// Toggle whether or not the separator should display vertically. + #[prop(optional, into)] + vertical: MaybeProp, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -84,6 +91,9 @@ pub fn DropdownMenuSeparator( /// Designate an element as a popover element. #[prop(optional, into)] popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, /// Assigns a slot to an element. #[prop(optional, into)] slot: MaybeProp, @@ -110,6 +120,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 +145,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 +155,27 @@ pub fn DropdownMenuSeparator( /> }; - view! { - + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + + } + .into_any() + } else { + view! { + + } + .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! { - {children()} - + } } 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..77b00743 --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/luma/dropdown.css @@ -0,0 +1,124 @@ +@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 { + > button, + > label { + @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 { + > button, + > label { + @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 { + > button, + > label { + @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"] { + > button { + @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..eade9f16 --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/luma/dropdown_dark.css @@ -0,0 +1,19 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply ring-foreground/10; + } + + .singlestage-dropdown-menu-item-variant-destructive { + > button, + > label { + &: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..4f9acfce --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/lyra/dropdown.css @@ -0,0 +1,120 @@ +@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 { + > button, + > label { + @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 { + > button, + > label { + @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 { + > button, + > label { + @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"] { + > button { + @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..ed171dba --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/lyra/dropdown_dark.css @@ -0,0 +1,11 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > button, + > label { + &: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..a2921303 --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/maia/dropdown.css @@ -0,0 +1,123 @@ +@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 { + > button, + > label { + @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 { + > button, + > label { + @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 { + > button, + > label { + @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"] { + > button { + @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..70e6cac2 --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/maia/dropdown_dark.css @@ -0,0 +1,15 @@ +@layer components1 { + .singlestage-dropdown-menu-content { + @apply ring-foreground/10; + } + + .singlestage-dropdown-menu-item-variant-destructive { + > button, + > label { + &: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..7176ce78 --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/mira/dropdown.css @@ -0,0 +1,125 @@ +@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 { + > button, + > label { + @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 { + > button, + > label { + @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 { + > button, + > label { + @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"] { + > button { + @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..ed171dba --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/mira/dropdown_dark.css @@ -0,0 +1,11 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > button, + > label { + &: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..fcd12169 --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/nova/dropdown.css @@ -0,0 +1,124 @@ +@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 { + > button, + > label { + @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 { + > button, + > label { + @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 { + > button, + > label { + @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"] { + > button { + @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..ed171dba --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/nova/dropdown_dark.css @@ -0,0 +1,11 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > button, + > label { + &: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..8a1f2a0b --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/sera/dropdown.css @@ -0,0 +1,132 @@ +@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 { + > button, + > label { + @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 { + > button, + > label { + @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 { + > button, + > label { + @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"] { + > button { + @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..ed171dba --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/sera/dropdown_dark.css @@ -0,0 +1,11 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > button, + > label { + &: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..81e4ee4f --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/vega/dropdown.css @@ -0,0 +1,124 @@ +@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 { + > button, + > label { + @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 { + > button, + > label { + @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 { + > button, + > label { + @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"] { + > button { + @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..ed171dba --- /dev/null +++ b/singlestage/src/components/dropdown/style/base/vega/dropdown_dark.css @@ -0,0 +1,11 @@ +@layer components1 { + .singlestage-dropdown-menu-item-variant-destructive { + > button, + > label { + &: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..fc392b38 --- /dev/null +++ b/singlestage/src/components/dropdown/style/dropdown.css @@ -0,0 +1,118 @@ +@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 { + > button, + > label { + @apply content-center + cursor-default + flex + h-full + items-center + outline-hidden + relative + select-none + 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 { + > button, + > label { + @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"] { + > button { + @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 index fadadddc..1edc1cef 100644 --- a/singlestage/src/components/dropdown/sub/content.rs +++ b/singlestage/src/components/dropdown/sub/content.rs @@ -1,4 +1,4 @@ -use crate::DropdownMenuSubContext; +use crate::primitives::*; use leptos::prelude::*; /// The component that pops out when the sub trigger is triggered. @@ -6,6 +6,19 @@ use leptos::prelude::*; pub fn DropdownMenuSubContent( children: Children, + /// Set how to align the popover + /// + /// Accepted values: "start" | "center" | "end" + /// Default is "start" + #[prop(optional, into)] + align: MaybeProp, + /// Set which side the popover opens relative to the trigger + /// + /// Accepted values: "top" | "right" | "bottom" | "left" + /// Default is "bottom" + #[prop(optional, into)] + side: MaybeProp, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -105,15 +118,6 @@ pub fn DropdownMenuSubContent( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let sub = expect_context::(); - let menu_ref = NodeRef::::new(); - - Effect::new(move || { - if let Some(popover) = menu_ref.get_untracked() { - let _ = popover.toggle_popover_with_force(sub.open.get()); - } - }); - let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() @@ -143,7 +147,6 @@ pub fn DropdownMenuSubContent( 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() @@ -151,27 +154,19 @@ pub fn DropdownMenuSubContent( }; view! { - {children()} - + } } 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, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + class=move || class.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { + + {children()} + + } +} diff --git a/singlestage/src/components/dropdown/sub/mod.rs b/singlestage/src/components/dropdown/sub/mod.rs index fde6b438..ee49e468 100644 --- a/singlestage/src/components/dropdown/sub/mod.rs +++ b/singlestage/src/components/dropdown/sub/mod.rs @@ -1,192 +1,7 @@ mod content; +mod menu; mod trigger; pub use content::*; +pub use menu::*; pub use trigger::*; - -use crate::Reactive; -use leptos::{context::Provider, prelude::*}; - -#[derive(Clone)] -pub struct DropdownMenuSubContext { - pub menu_id: RwSignal, - pub open: Reactive, - pub trigger_id: RwSignal, -} - -/// 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, - - // GLOBAL ATTRIBUTES - // - /// A space separated list of keys to focus this element. The first key available on the user's - /// keyboard layout is used. - #[prop(optional, into)] - accesskey: MaybeProp, - /// Sets whether the input value should be capitalized and how. If a parent `` has - /// `autocapitalize` rules set, it will override any rules set here. - /// - /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". - #[prop(optional, into)] - autocapitalize: MaybeProp, - /// Grabs focus once the page has finished loading. Only one element on the page can be focused - /// at a time. - #[prop(optional, into)] - autofocus: MaybeProp, - /// Apply classes to the element. - #[prop(optional, into)] - class: MaybeProp, - /// Allows client-side editing of the element by the user. - /// - /// Accepted values: "true" | "false" | "plaintext-only" - #[prop(optional, into)] - contenteditable: MaybeProp, - /// Indicate directionality of the element's text. - /// - /// Accepted values: "ltr" | "rtl" | "auto" - #[prop(optional, into)] - dir: MaybeProp, - /// Toggle whether the element can be dragged. - #[prop(optional, into)] - draggable: MaybeProp, - /// Modifies the appearance of the enter key on virtual keyboards. - #[prop(optional, into)] - enterkeyhint: MaybeProp, - /// Expose elements in the shadow DOM to be manipulated by the DOM. - #[prop(optional, into)] - exportparts: MaybeProp, - /// Controls hidden status of the element. - #[prop(optional, into)] - hidden: MaybeProp, - /// Set the id of this element. - #[prop(optional, into)] - id: MaybeProp, - /// Toggle if the browser reacts to input events from this element. - #[prop(optional, into)] - inert: MaybeProp, - /// Hints to the browser of what type of virtual keyboard to display when editing this element - /// or its children. - #[prop(optional, into)] - inputmode: MaybeProp, - /// Used to render a standard element as a custom element. - #[prop(optional, into)] - is: MaybeProp, - /// Unique global identifier of an item. - #[prop(optional, into)] - itemid: MaybeProp, - /// Used to add properties to an item. - #[prop(optional, into)] - itemprop: MaybeProp, - /// Used to associate an item with a related non-parent element that's using `itemscope`. - #[prop(optional, into)] - itemref: MaybeProp, - /// Used to declare that children elements are related to a particular item. - #[prop(optional, into)] - itemscope: MaybeProp, - /// URL of data used to define `itemprops`. - #[prop(optional, into)] - itemtype: MaybeProp, - /// Defines the language of an element. - #[prop(optional, into)] - lang: MaybeProp, - /// Cryptographic "number used once". - #[prop(optional, into)] - nonce: MaybeProp, - /// List of the part names of the element. - #[prop(optional, into)] - part: MaybeProp, - /// Designate an element as a popover element. - #[prop(optional, into)] - popover: MaybeProp, - /// Define the semantic meaning of content. - #[prop(optional, into)] - role: MaybeProp, - /// Assigns a slot to an element. - #[prop(optional, into)] - slot: MaybeProp, - /// Toggle spellcheck for this input. - /// - /// Accepted values: "default" | "true" | "false". - #[prop(optional, into)] - spellcheck: MaybeProp, - /// Define CSS to be applied to the element. - #[prop(optional, into)] - style: MaybeProp, - /// Controls how an element behaves when a user navigates using the tab key. - #[prop(optional, into)] - tabindex: MaybeProp, - /// Describes the content of the element to screen readers. - #[prop(optional, into)] - title: MaybeProp, - /// Defines localization behavior for the element. - #[prop(optional, into)] - translate: MaybeProp, -) -> impl IntoView { - let menu_id = RwSignal::new(String::new()); - let trigger_id = RwSignal::new(String::new()); - - let context = DropdownMenuSubContext { - 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! { -
    - {children()} -
    - } -} diff --git a/singlestage/src/components/dropdown/sub/trigger.rs b/singlestage/src/components/dropdown/sub/trigger.rs index 13a296da..5d8a9a40 100644 --- a/singlestage/src/components/dropdown/sub/trigger.rs +++ b/singlestage/src/components/dropdown/sub/trigger.rs @@ -1,4 +1,4 @@ -use crate::DropdownMenuSubContext; +use crate::{Reactive, primitives::*}; use leptos::prelude::*; #[component] @@ -7,7 +7,7 @@ pub fn DropdownMenuSubTrigger( /// Controls whether the item appears disabled and is clickable. #[prop(optional, into)] - disabled: MaybeProp, + disabled: Reactive, /// Set whether or not this element should display inset from its normal position. #[prop(optional, into)] inset: MaybeProp, @@ -125,19 +125,19 @@ pub fn DropdownMenuSubTrigger( #[prop(optional, into)] translate: MaybeProp, ) -> impl IntoView { - let sub = expect_context::(); - 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() @@ -166,62 +166,20 @@ pub fn DropdownMenuSubTrigger( }; view! { -
  • " singlestage-dropdown-menu-item-destructive", - _ => "", - }, - class.get().unwrap_or_default(), - ) - } - role="menuitem" + - -
  • + {children()} + } } 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! { {children()} } + view! { {children()} } } 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(
    "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..d5109772 --- /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 { + @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, + /// Sets whether this Field is invalid + #[prop(optional, into)] + invalid: Reactive, /// Sets the display orientation. /// /// Accepted values: "vertical" | "horizontal" | "responsive". Defaults to "vertical". #[prop(optional, into)] orientation: MaybeProp, - /// Sets the display variant of the `Field`. - /// - /// Accepted values: "button". - #[prop(optional, into)] - variant: MaybeProp, // GLOBAL ATTRIBUTES // @@ -120,12 +121,6 @@ pub fn Field( #[prop(optional, into)] translate: MaybeProp, ) -> 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 = { + if let Some(radio_group) = use_context::() { + radio_group.disabled + } else if let Some(checkbox_group) = use_context::() { + checkbox_group.disabled + } else { + disabled + } + }; + + let invalid: Reactive = { + if let Some(radio_group) = use_context::() { + radio_group.invalid + } else if let Some(checkbox_group) = use_context::() { + 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! {
    " 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, + /// Set whether or not this element should display inset from its normal position. + /// (For use in popover menus) + #[prop(optional, into)] + inset: MaybeProp, + /// Whether the element renders as invalid + #[prop(optional, into)] + invalid: Reactive, /// The id of the labeled element if it's not a child #[prop(optional, into)] label_for: MaybeProp, @@ -153,35 +163,20 @@ pub fn FieldLabel( /> }; - let uuid = uuid::Uuid::new_v4(); - view! { - + } } 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, + pub disabled: Reactive, pub input_id: RwSignal, + pub invalid: Reactive, pub label_id: RwSignal, } + +#[derive(Clone)] +pub(crate) struct FieldSetContext {} diff --git a/singlestage/src/components/field/separator.rs b/singlestage/src/components/field/separator.rs index 6f3b6b7a..634bd80f 100644 --- a/singlestage/src/components/field/separator.rs +++ b/singlestage/src/components/field/separator.rs @@ -1,10 +1,15 @@ +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, + /// Toggle whether or not the separator should display vertically. + #[prop(optional, into)] + vertical: MaybeProp, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -150,27 +155,39 @@ pub fn FieldSeparator( }; view! { -
    -
    +
    {if let Some(children) = children { view! { - - {children()} - + {children()} + } .into_any() } else { - "".into_any() + + view! { + + } + .into_any() }}
    } diff --git a/singlestage/src/components/field/set.rs b/singlestage/src/components/field/set.rs index 56228247..d89febfb 100644 --- a/singlestage/src/components/field/set.rs +++ b/singlestage/src/components/field/set.rs @@ -1,5 +1,5 @@ -use crate::Reactive; -use leptos::prelude::*; +use crate::{FieldSetContext, Reactive}; +use leptos::{context::Provider, prelude::*}; /// Container that renders a semantic fieldset with spacing presets. #[component] @@ -172,7 +172,7 @@ pub fn FieldSet( {..global_attrs_2} {..fieldset_attrs} > - {children()} + {children()}

    } } 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..9a0573cc --- /dev/null +++ b/singlestage/src/components/field/style/base/maia/field.css @@ -0,0 +1,115 @@ +@layer components13 { + .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..e167ae43 --- /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 { + @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! { - {children()} - + } } 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 61d7e453..4fb01ec6 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, InputGroupContext, Label, Reactive}; use leptos::prelude::*; /// A form input field. @@ -269,36 +269,33 @@ 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); } - }); - Effect::new(move || { - if let Some(input) = input_ref.get_untracked() { - input.set_disabled(disabled.get()); + default.get() + }; + + let disabled: Reactive = { + if let Some(field) = use_context::() { + field.disabled + } else { + disabled } - }); + }; - 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() - { - input.set_value(""); - } - } - _ => { - if let Some(input) = input_ref.get_untracked() { - input.set_value(&value.get()); - } - } - }, - ); + let invalid: Reactive = { + if let Some(field) = use_context::() { + field.invalid + } else { + invalid + } + }; let on_input = move |ev| { value.set(event_target_value(&ev)); @@ -381,7 +378,10 @@ 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::().is_some(); + let in_input_group: bool = use_context::().is_some(); let custom_attrs = view! { <{..} @@ -393,12 +393,8 @@ pub fn Input( None } } - aria_invalid=move || { - match invalid.get() { - 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::() { @@ -409,8 +405,34 @@ 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 { + "" + }, + 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::() { + 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 || { @@ -421,45 +443,27 @@ 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::().is_some() { - view! { - - {children()} - - } - .into_any() - } else { - view! { - - } - .into_any() - }} + () { - 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 c33d6800..2dc5e3b1 100644 --- a/singlestage/src/components/input_group/group.rs +++ b/singlestage/src/components/input_group/group.rs @@ -154,8 +154,8 @@ pub fn InputGroup( view! {
    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/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, /// Set the display style of the `Item`. /// - /// Accepted values: outline | muted | default + /// Accepted values: "outline" | "muted" | "default" #[prop(optional, into)] variant: MaybeProp, @@ -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()} + {children()}
    } } 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, @@ -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..192b73a1 100644 --- a/singlestage/src/components/item/separator.rs +++ b/singlestage/src/components/item/separator.rs @@ -1,8 +1,11 @@ +use crate::Separator; use leptos::prelude::*; -/// Separates Item content. +/// The separator. #[component] pub fn ItemSeparator( + #[prop(optional)] children: Option, + /// Toggle whether or not the separator should display vertically. #[prop(optional, into)] vertical: MaybeProp, @@ -117,7 +120,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 +155,27 @@ pub fn ItemSeparator( /> }; - view! { -
    "horizontal", - true => "vertical", - } - .to_string() + if let Some(children) = children { + view! { + + {children()} + + } + .into_any() + } else { + view! { +
    + {..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..f20db957 --- /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 { + @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, + /// Set whether or not this element should display inset from its normal position. + /// (For use in popover menus) + #[prop(optional, into)] + inset: MaybeProp, + /// Whether the element renders as invalid + #[prop(optional, into)] + invalid: Reactive, /// The id of the labeled element if it's not a child #[prop(optional, into)] label_for: MaybeProp, @@ -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! { - + } } 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..8076a7e3 100644 --- a/singlestage/src/components/link/mod.rs +++ b/singlestage/src/components/link/mod.rs @@ -1,4 +1,4 @@ -use crate::InputGroupContext; +use crate::{BreadcrumbLinkContext, InputGroupContext, SheetContext, SidebarMenuButtonContext}; use leptos::prelude::*; /// Creates a styled hyperlink. @@ -6,13 +6,16 @@ use leptos::prelude::*; pub fn Link( children: Children, - /// Set whether or not this `Link` should appear as a `Button`. + /// Set whether or not this `Link` should appear as something else. This is similar to + /// `asChild`. + /// + /// Accepted values: "button" | "badge" #[prop(optional, into)] - as_button: MaybeProp, - /// For use with `as_button`, specify the size to render the button. + render_as: MaybeProp, + /// For use with `render_as`, specify the size to render the element. #[prop(optional, into)] size: MaybeProp, - /// 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, @@ -44,6 +47,11 @@ pub fn Link( #[prop(optional, into)] target: MaybeProp, + // ARIA ATTRIBUTES + // + #[prop(optional, into)] aria_current: MaybeProp, + #[prop(optional, into)] aria_label: MaybeProp, + // GLOBAL ATTRIBUTES // /// A space separated list of keys to focus this element. The first key available on the user's @@ -201,50 +209,96 @@ pub fn Link( /> }; + let in_sidebar_menu_button: bool = use_context::().is_some(); + let in_breadcrumb_link: bool = use_context::().is_some(); + view! { "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::().is_some() { + 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::().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::().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() }, ) - } else { - "".to_string() - }, - class.get().unwrap_or_default(), - ) - } else { - format!("singlestage-link {}", class.get().unwrap_or_default()) + } + "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", + }, + ) + } + _ => { + if in_sidebar_menu_button { + "singlestage-sidebar-menu-button singlestage-sidebar-menu-button-size-default singlestage-sidebar-menu-button-variant-default" + .to_string() + } else if in_breadcrumb_link { + "singlestage-breadcrumb-link".to_string() + } else { + "singlestage-link".to_string() + } + } + }, + class.get().unwrap_or_default(), + ) + } + on:click=move |_| { + if let Some(sheet) = use_context::() && in_sidebar_menu_button { + sheet.open.set(false) } } 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, + #[prop(optional, into)] size: MaybeProp, // A ATTRIBUTES // @@ -37,6 +39,10 @@ pub fn PaginationLink( #[prop(optional, into)] target: MaybeProp, + // Aria ATTRIBUTES + // + #[prop(optional, into)] aria_label: MaybeProp, + // 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_attrs} + {..global_attrs_1} + {..global_attrs_2} + > + {children()} + } } 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, // A ATTRIBUTES // @@ -138,12 +139,25 @@ pub fn PaginationNext( #[prop(optional, into)] translate: MaybeProp, ) -> 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! { - - - + + + } } 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! {