diff --git a/Cargo.lock b/Cargo.lock index f7784977..40a2fdbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,9 +336,9 @@ dependencies = [ [[package]] name = "async-fs" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" dependencies = [ "async-lock", "blocking", @@ -347,11 +347,11 @@ dependencies = [ [[package]] name = "async-io" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ - "async-lock", + "autocfg", "cfg-if", "concurrent-queue", "futures-io", @@ -360,7 +360,7 @@ dependencies = [ "polling", "rustix 1.1.2", "slab", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ "async-channel", "async-io", @@ -405,9 +405,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" dependencies = [ "async-io", "async-lock", @@ -418,7 +418,7 @@ dependencies = [ "rustix 1.1.2", "signal-hook-registry", "slab", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -3058,7 +3058,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" dependencies = [ "heck 0.4.1", - "proc-macro-crate 2.0.0", + "proc-macro-crate 2.0.2", "proc-macro-error", "proc-macro2", "quote", @@ -3506,9 +3506,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64", "bytes", @@ -3532,9 +3532,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3765,9 +3765,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.1" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -3909,9 +3909,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.78" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" dependencies = [ "once_cell", "wasm-bindgen", @@ -4043,9 +4043,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", @@ -4071,6 +4071,12 @@ dependencies = [ "x11", ] +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -4571,7 +4577,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.106", @@ -5098,12 +5104,13 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "peniko" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9" +checksum = "9b44f9ddd2f480176b34278eb653ec1c8062f3b143a4e16eeff5ffac3334e288" dependencies = [ "color", "kurbo", + "linebender_resource_handle", "smallvec", ] @@ -5330,16 +5337,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", "rustix 1.1.2", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -5402,20 +5409,21 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" dependencies = [ - "toml_edit 0.20.7", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", ] [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.22.27", + "toml_edit 0.23.5", ] [[package]] @@ -5928,9 +5936,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.5" +version = "0.103.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" dependencies = [ "ring", "rustls-pki-types", @@ -6080,9 +6088,9 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "send_wrapper" @@ -6095,10 +6103,11 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" dependencies = [ + "serde_core", "serde_derive", ] @@ -6113,11 +6122,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_core" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", @@ -6126,24 +6144,26 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "serde_path_to_error" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", + "serde_core", ] [[package]] @@ -7249,9 +7269,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" dependencies = [ "rustls", "tokio", @@ -7308,25 +7328,34 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit 0.22.27", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -7334,31 +7363,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.6.3", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap", - "toml_datetime", + "serde", + "serde_spanned", + "toml_datetime 0.6.3", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9" dependencies = [ "indexmap", - "serde", - "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.1", + "toml_parser", + "winnow 0.7.13", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ "winnow 0.7.13", ] @@ -7925,9 +7964,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" dependencies = [ "cfg-if", "once_cell", @@ -7938,9 +7977,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" dependencies = [ "bumpalo", "log", @@ -7952,9 +7991,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.51" +version = "0.4.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe" +checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" dependencies = [ "cfg-if", "js-sys", @@ -7965,9 +8004,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7975,9 +8014,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" dependencies = [ "proc-macro2", "quote", @@ -7988,9 +8027,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" dependencies = [ "unicode-ident", ] @@ -8119,9 +8158,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.78" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" +checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" dependencies = [ "js-sys", "wasm-bindgen", @@ -9193,7 +9232,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.106", @@ -9206,7 +9245,7 @@ version = "5.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.106", @@ -9375,7 +9414,7 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.106", @@ -9388,7 +9427,7 @@ version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.106", diff --git a/README.md b/README.md index 9ddc5a85..51d7b1a5 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Building styled and more featured component libraries on top of Dioxus Primitive We're still in the early days - Many components are still being created and stabilized. -28/28 +28/29 - [x] Accordion - [x] Alert Dialog @@ -43,6 +43,7 @@ We're still in the early days - Many components are still being created and stab - [x] Checkbox - [x] Collapsible - [x] Context Menu +- [ ] Date Picker - [x] Dialog - [x] Dropdown Menu - [x] Hover Card diff --git a/component.json b/component.json index 8984b2b3..628343eb 100644 --- a/component.json +++ b/component.json @@ -32,6 +32,7 @@ "preview/src/components/toggle_group", "preview/src/components/context_menu", "preview/src/components/aspect_ratio", - "preview/src/components/scroll_area" + "preview/src/components/scroll_area", + "preview/src/components/date_picker" ] } diff --git a/preview/src/components/date_picker/component.json b/preview/src/components/date_picker/component.json new file mode 100644 index 00000000..583cd28f --- /dev/null +++ b/preview/src/components/date_picker/component.json @@ -0,0 +1,13 @@ +{ + "name": "date_picker", + "description": "A date picker component to select or input dates.", + "authors": ["Evan Almloff"], + "exclude": ["variants", "docs.md", "component.json"], + "cargoDependencies": [ + { + "name": "dioxus-primitives", + "git": "https://github.com/DioxusLabs/components" + } + ], + "globalAssets": ["../../../assets/dx-components-theme.css"] +} diff --git a/preview/src/components/date_picker/component.rs b/preview/src/components/date_picker/component.rs new file mode 100644 index 00000000..159a2126 --- /dev/null +++ b/preview/src/components/date_picker/component.rs @@ -0,0 +1,89 @@ +use dioxus::prelude::*; + +use dioxus_primitives::{ + calendar::CalendarProps, + date_picker::{self, DatePickerInputProps, DatePickerProps, DatePickerTriggerProps}, +}; + +use crate::components::calendar::component::*; +use time::UtcDateTime; + +#[component] +pub fn DatePicker(props: DatePickerProps) -> Element { + rsx! { + document::Link { rel: "stylesheet", href: asset!("./style.css"), } + div { + date_picker::DatePicker { + class: "date-picker", + value: props.value, + on_value_change: props.on_value_change, + selected_date: props.selected_date, + disabled: props.disabled, + read_only: props.read_only, + separator: props.separator, + on_format_placeholder: props.on_format_placeholder, + attributes: props.attributes, + {props.children} + } + } + } +} + +#[component] +pub fn DatePickerInput(props: DatePickerInputProps) -> Element { + rsx! { + date_picker::DatePickerInput { + class: "date-picker-input", + attributes: props.attributes, + {props.children} + } + } +} + +#[component] +pub fn DatePickerTrigger(props: DatePickerTriggerProps) -> Element { + rsx! { + date_picker::DatePickerTrigger { + class: "date-picker-trigger", + attributes: props.attributes, + {props.children} + } + } +} + +#[component] +pub fn DatePickerCalendar(props: CalendarProps) -> Element { + let mut view_date = use_signal(|| UtcDateTime::now().date()); + + use_effect(move || { + if let Some(date) = (props.selected_date)() { + view_date.set(date); + } + }); + + rsx! { + Calendar { + selected_date: props.selected_date, + on_date_change: props.on_date_change, + on_format_weekday: props.on_format_weekday, + on_format_month: props.on_format_month, + view_date: view_date(), + today: props.today, + on_view_change: move |date| view_date.set(date), + disabled: props.disabled, + first_day_of_week: props.first_day_of_week, + min_date: props.min_date, + max_date: props.max_date, + attributes: props.attributes, + CalendarHeader { + CalendarNavigation { + CalendarPreviousMonthButton {} + CalendarSelectMonth {} + CalendarSelectYear {} + CalendarNextMonthButton {} + } + } + CalendarGrid {} + } + } +} diff --git a/preview/src/components/date_picker/docs.md b/preview/src/components/date_picker/docs.md new file mode 100644 index 00000000..dfbe0232 --- /dev/null +++ b/preview/src/components/date_picker/docs.md @@ -0,0 +1,6 @@ +The DatePicker component is used to display a date input and a Calendar popover, allowing users to enter or select a date value. + +## Component Structure + +```rust +``` \ No newline at end of file diff --git a/preview/src/components/date_picker/mod.rs b/preview/src/components/date_picker/mod.rs new file mode 100644 index 00000000..9a8ae556 --- /dev/null +++ b/preview/src/components/date_picker/mod.rs @@ -0,0 +1,2 @@ +mod component; +pub use component::*; \ No newline at end of file diff --git a/preview/src/components/date_picker/style.css b/preview/src/components/date_picker/style.css new file mode 100644 index 00000000..37af7149 --- /dev/null +++ b/preview/src/components/date_picker/style.css @@ -0,0 +1,71 @@ +.date-picker { + position: relative; + display: inline-flex; + align-items: center; +} + +.date-picker-input { + position: relative; + display: flex; + box-sizing: border-box; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 0.25rem; + padding: 8px 40px 8px 12px; + border: none; + border-radius: 0.5rem; + border-radius: calc(0.5rem); + background: none; + background: var(--light, var(--primary-color)) + var(--dark, var(--primary-color-3)); + box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) + var(--dark, var(--primary-color-7)); + color: var(--secondary-color-4); + gap: 0.25rem; + transition: background-color 100ms ease-out; +} + +.date-picker-trigger { + border: none; + background-color: transparent; + cursor: pointer; + position: relative; + margin-left: -35px; +} + +.date-picker-input input::placeholder { + color: var(--secondary-color-5); +} + +.date-picker[data-state="open"] .date-picker-input { + pointer-events: none; +} + +.date-picker-expand-icon { + width: 20px; + height: 20px; + fill: none; + stroke: var(--primary-color-7); + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} + +.date-picker[data-disabled="true"] .date-picker-input { + color: var(--secondary-color-5); + cursor: not-allowed; +} + +.date-picker-input:hover:not([data-disabled="true"]), +.date-picker-input:focus-visible { + background: var(--light, var(--primary-color-4)) + var(--dark, var(--primary-color-5)); + color: var(--secondary-color-1); + outline: none; +} + +[data-disabled="true"] { + cursor: not-allowed; + opacity: 0.5; +} diff --git a/preview/src/components/date_picker/variants/main/mod.rs b/preview/src/components/date_picker/variants/main/mod.rs new file mode 100644 index 00000000..5b833405 --- /dev/null +++ b/preview/src/components/date_picker/variants/main/mod.rs @@ -0,0 +1,41 @@ +use super::super::component::*; +use dioxus::prelude::*; + +use dioxus_i18n::tid; +use dioxus_primitives::date_picker::DatePickerValue; + +use time::{Date, Month, Weekday}; + +#[component] +pub fn Demo() -> Element { + let v = DatePickerValue::new_day(None); + let mut value = use_signal(|| v); + + let mut selected_date = use_signal(|| None::); + + rsx! { + div { + DatePicker { + value: value(), + selected_date: selected_date(), + on_value_change: move |v| { + tracing::info!("Selected: {v}"); + value.set(v); + selected_date.set(v.date()); + }, + on_format_placeholder: || tid!("YMD"), + DatePickerInput { + DatePickerTrigger { + aria_label: "DatePicker Trigger", + DatePickerCalendar { + selected_date: selected_date(), + on_date_change: move |date| selected_date.set(date), + on_format_weekday: |weekday: Weekday| tid!(&weekday.to_string()), + on_format_month: |month: Month| tid!(&month.to_string()), + } + } + } + } + } + } +} diff --git a/preview/src/components/mod.rs b/preview/src/components/mod.rs index 6913e8b2..c28ca19d 100644 --- a/preview/src/components/mod.rs +++ b/preview/src/components/mod.rs @@ -64,6 +64,7 @@ examples!( checkbox, collapsible, context_menu, + date_picker, dialog, dropdown_menu, hover_card, diff --git a/preview/src/i18n/de-DE.ftl b/preview/src/i18n/de-DE.ftl index cea45f72..ec11174e 100644 --- a/preview/src/i18n/de-DE.ftl +++ b/preview/src/i18n/de-DE.ftl @@ -19,4 +19,7 @@ Wednesday = Mi Thursday = Do Friday = Fr Saturday = Sa -Sunday = So \ No newline at end of file +Sunday = So + +## Date +YMD = JJJJ-MM-TT \ No newline at end of file diff --git a/preview/src/i18n/en-US.ftl b/preview/src/i18n/en-US.ftl index 198f3453..0e28c922 100644 --- a/preview/src/i18n/en-US.ftl +++ b/preview/src/i18n/en-US.ftl @@ -19,4 +19,7 @@ Wednesday = We Thursday = Th Friday = Fr Saturday = Sa -Sunday = Su \ No newline at end of file +Sunday = Su + +## Date +YMD = YYYY-MM-DD \ No newline at end of file diff --git a/preview/src/i18n/es-ES.ftl b/preview/src/i18n/es-ES.ftl index 67393350..27ad71a0 100644 --- a/preview/src/i18n/es-ES.ftl +++ b/preview/src/i18n/es-ES.ftl @@ -19,4 +19,7 @@ Wednesday = Mi Thursday = Ju Friday = Vi Saturday = Sa -Sunday = Do \ No newline at end of file +Sunday = Do + +## Date +YMD = YYYY-MM-DD \ No newline at end of file diff --git a/preview/src/i18n/fr-FR.ftl b/preview/src/i18n/fr-FR.ftl index 9d3e9417..3ac890e7 100644 --- a/preview/src/i18n/fr-FR.ftl +++ b/preview/src/i18n/fr-FR.ftl @@ -19,4 +19,7 @@ Wednesday = Me Thursday = Je Friday = Ve Saturday = Sa -Sunday = Di \ No newline at end of file +Sunday = Di + +## Date +YMD = AAAA-MM-JJ \ No newline at end of file diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 3512968e..05a3b07e 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/DioxusLabs/components" [dependencies] dioxus.workspace = true dioxus-time = { git = "https://github.com/ealmloff/dioxus-std", branch = "0.7" } -time = { version = "0.3.41", features = ["std", "macros"] } +time = { version = "0.3.41", features = ["std", "macros", "parsing"] } tracing.workspace = true [build-dependencies] diff --git a/primitives/src/calendar.rs b/primitives/src/calendar.rs index db0c3f25..ddb51ba1 100644 --- a/primitives/src/calendar.rs +++ b/primitives/src/calendar.rs @@ -134,6 +134,13 @@ fn previous_month(date: Date) -> Option { .ok() } +fn replace_month(date: Date, month: Month) -> Date { + let year = date.year(); + let num_days = month.length(year); + Date::from_calendar_date(year, month, std::cmp::min(date.day(), num_days)) + .expect("invalid or out-of-range date") +} + /// The context provided by the [`Calendar`] component to its children. #[derive(Copy, Clone)] pub struct CalendarContext { @@ -223,6 +230,7 @@ pub struct CalendarProps { pub on_format_month: Callback, /// The month being viewed + #[props(default = ReadSignal::new(Signal::new(UtcDateTime::now().date())))] pub view_date: ReadSignal, /// The current date (used for highlighting today) @@ -889,24 +897,22 @@ pub fn CalendarGrid(props: CalendarGridProps) -> Element { date = date.next_day().expect("invalid or out-of-range date"); } + let mut date = view_date; // Add days of the month let num_days_in_month = view_date.month().length(view_date.year()); for day in 1..=num_days_in_month { - grid.push( - view_date - .replace_day(day) - .expect("invalid or out-of-range date"), - ); + date = view_date + .replace_day(day) + .expect("invalid or out-of-range date"); + grid.push(date); } // Add empty cells to complete the grid (for a clean layout) let remainder = grid.len() % 7; if remainder > 0 { - if let Some(mut date) = next_month(view_date) { - for _ in 1..=(7 - remainder) { - grid.push(date); - date = date.next_day().expect("invalid or out-of-range date"); - } + for _ in 1..=(7 - remainder) { + date = date.next_day().expect("invalid or out-of-range date"); + grid.push(date); } } @@ -1027,11 +1033,11 @@ pub fn CalendarSelectMonth(props: CalendarSelectMonthProps) -> Element { // Get the current view date from context let view_date = (calendar.view_date)(); let mut min_month = Month::January; - if view_date.replace_month(min_month).unwrap() < calendar.min_date { + if replace_month(view_date, min_month) < calendar.min_date { min_month = calendar.min_date.month(); } let mut max_month = Month::December; - if view_date.replace_month(max_month).unwrap() > calendar.max_date { + if replace_month(view_date, max_month) > calendar.max_date { max_month = calendar.max_date.month(); } @@ -1144,11 +1150,11 @@ pub fn CalendarSelectYear(props: CalendarSelectYearProps) -> Element { let view_date = (calendar.view_date)(); let month = view_date.month(); let mut min_year = calendar.min_date.year(); - if calendar.min_date.replace_month(month).unwrap() < calendar.min_date { + if replace_month(calendar.min_date, month) < calendar.min_date { min_year += 1; } let mut max_year = calendar.max_date.year(); - if calendar.max_date.replace_month(month).unwrap() > calendar.max_date { + if replace_month(calendar.max_date, month) > calendar.max_date { max_year -= 1; } diff --git a/primitives/src/date_picker.rs b/primitives/src/date_picker.rs new file mode 100644 index 00000000..daf42690 --- /dev/null +++ b/primitives/src/date_picker.rs @@ -0,0 +1,403 @@ +//! Defines the [`DatePicker`] component and its subcomponents, which allowing users to enter or select a date value + +use crate::{ + dioxus_elements::input_data::MouseButton, + popover::{PopoverContent, PopoverRoot, PopoverTrigger}, + ContentAlign, +}; + +use dioxus::prelude::*; +use time::{macros::format_description, Date}; + +/// The value of the [`DatePicker`] component. +#[derive(Copy, Clone)] +pub struct DatePickerValue { + /// A dates range value or single day + is_range: bool, + /// Current date value + value: DateValue, +} + +impl DatePickerValue { + /// Create a single day value + pub fn new_day(date: Option) -> Self { + let is_range = false; + + match date { + Some(date) => Self { + is_range, + value: DateValue::Single { date }, + }, + None => Self::new_empty(is_range), + } + } + + /// Create new date range value + pub fn new_range(date: Option) -> Self { + let is_range = true; + + match date { + Some(date) => Self { + is_range, + value: DateValue::Range { + start: date, + end: None, + }, + }, + None => Self::new_empty(is_range), + } + } + + /// Create full date range value + pub fn range(start: Date, end: Date) -> Self { + let value = if end < start { + DateValue::Range { + start: end, + end: Some(start), + } + } else { + DateValue::Range { + start, + end: Some(end), + } + }; + Self { + is_range: true, + value, + } + } + + fn new(is_range: bool, date: Option) -> Self { + if is_range { + Self::new_range(date) + } else { + Self::new_day(date) + } + } + + fn new_empty(is_range: bool) -> Self { + Self { + is_range, + value: DateValue::Empty, + } + } + + fn part_count(&self) -> usize { + if self.is_range { + 2 + } else { + 1 + } + } + + fn set_date(&self, date: Option) -> Self { + match self.value { + DateValue::Range { start, end } => { + if end.is_some() { + Self::new_range(date) + } else { + match date { + Some(end) => Self::range(start, end), + None => *self, + } + } + } + _ => Self::new(self.is_range, date), + } + } + + /// Return current selected date + pub fn date(&self) -> Option { + match self.value { + DateValue::Single { date } => Some(date), + DateValue::Range { start, end } => { + if end.is_some() { + return end; + } + + Some(start) + } + DateValue::Empty => None, + } + } + + // Returns `true` if the given date is selected + fn is_date_selected(&self, date: Option) -> bool { + self.date() == date + } + + fn ready_to_close(&self) -> bool { + match self.value { + DateValue::Range { end, .. } => end.is_some(), + _ => true, + } + } +} + +impl std::fmt::Display for DatePickerValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.value) + } +} + +/// The value type of the [`DatePicker`] component. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum DateValue { + /// A single value for the date picker + Single { + /// The selected date + date: Date, + }, + /// A dates range value for the date picker + Range { + /// The first range date + start: Date, + /// The last range date + end: Option, + }, + /// None value for the date picker + Empty, +} + +impl std::fmt::Display for DateValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DateValue::Single { date } => write!(f, "{date}"), + DateValue::Range { start, end } => { + let end_str = match end { + Some(date) => date.to_string(), + None => String::default(), + }; + write!(f, "{start} - {end_str}") + } + DateValue::Empty => write!(f, ""), + } + } +} + +/// The context provided by the [`DatePicker`] component to its children. +#[derive(Copy, Clone)] +struct DatePickerContext { + // State + value: ReadSignal, + on_value_change: Callback, + selected_date: ReadSignal>, + open: Signal, + read_only: ReadSignal, + + // Configuration + disabled: ReadSignal, + separator: &'static str, + format_placeholder: Callback<(), String>, +} + +impl DatePickerContext { + fn set_date(&mut self, date: Option) { + let value = (self.value)(); + if value.is_date_selected(date) { + return; + } + + let value = value.set_date(date); + self.on_value_change.call(value); + + if value.ready_to_close() { + self.open.set(false); + } + } +} + +/// The props for the [`DatePicker`] component. +#[derive(Props, Clone, PartialEq)] +pub struct DatePickerProps { + /// The controlled value of the date picker + pub value: ReadSignal, + + /// Callback when value changes + #[props(default)] + pub on_value_change: Callback, + + /// The selected date + #[props(default)] + pub selected_date: ReadSignal>, + + /// Whether the date picker is disabled + #[props(default)] + pub disabled: ReadSignal, + + /// Whether the date picker is enable user input + #[props(default = ReadSignal::new(Signal::new(false)))] + pub read_only: ReadSignal, + + /// Separator between range value + #[props(default = " - ")] + pub separator: &'static str, + + /// Callback when display placeholder + #[props(default = Callback::new(|_| "YYYY-MM-DD".to_string()))] + pub on_format_placeholder: Callback<(), String>, + + /// Additional attributes to extend the date picker element + #[props(extends = GlobalAttributes)] + pub attributes: Vec, + + /// The children of the date picker element + pub children: Element, +} + +/// # DatePicker +/// +/// The [`DatePicker`] component provides an accessible date input interface. +/// +/// ## Example +/// ```rust +/// ``` +/// +/// # Styling +/// +/// The [`DatePicker`] component defines the following data attributes you can use to control styling: +/// - `data-disabled`: Indicates if the DatePicker is disabled. Possible values are `true` or `false`. +#[component] +pub fn DatePicker(props: DatePickerProps) -> Element { + let open = use_signal(|| false); + + // Create context provider for child components + use_context_provider(|| DatePickerContext { + open, + value: props.value, + on_value_change: props.on_value_change, + selected_date: props.selected_date, + disabled: props.disabled, + read_only: props.read_only, + separator: props.separator, + format_placeholder: props.on_format_placeholder, + }); + + rsx! { + div { + role: "application", + "aria-label": "DatePicker", + "data-disabled": (props.disabled)(), + ..props.attributes, + {props.children} + } + } +} + +/// The props for the [`SelectDateTrigger`] component +#[derive(Props, Clone, PartialEq)] +pub struct DatePickerTriggerProps { + /// Additional attributes for the trigger button + #[props(extends = GlobalAttributes)] + pub attributes: Vec, + + /// The children to render inside the trigger + pub children: Element, +} + +/// # DatePickerTrigger +/// +/// The `PopoverTrigger` is a button that toggles the visibility of the [`PopoverContent`]. +/// +/// ```rust +/// ``` +#[component] +pub fn DatePickerTrigger(props: DatePickerTriggerProps) -> Element { + let mut ctx = use_context::(); + let mut open = ctx.open; + + use_effect(move || { + let date = (ctx.selected_date)(); + ctx.set_date(date); + }); + + rsx! { + PopoverRoot { + class: "popover", + open: open(), + on_open_change: move |v| open.set(v), + PopoverTrigger { attributes: props.attributes, + svg { + class: "date-picker-expand-icon", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + polyline { points: "6 9 12 15 18 9" } + } + } + PopoverContent { + gap: "0.25rem", + align: ContentAlign::End, + {props.children} + } + } + } +} + +/// The props for the [`DatePickerInput`] component +#[derive(Props, Clone, PartialEq)] +pub struct DatePickerInputProps { + /// Additional attributes for the value element + #[props(extends = GlobalAttributes)] + pub attributes: Vec, + + /// The children of the date picker element + pub children: Element, +} + +/// # DatePickerInput +/// +/// The input element for the [`DatePicker`](super::date_picker::DatePicker) component which allow users to enter a date value. +/// +/// ```rust +/// ``` +#[component] +pub fn DatePickerInput(props: DatePickerInputProps) -> Element { + let mut ctx = use_context::(); + + let display_value = use_memo(move || ctx.value.to_string()); + + let placeholder = { + let capacity = (ctx.value)().part_count(); + let text = ctx.format_placeholder.call(()); + vec![text; capacity].join(ctx.separator) + }; + + let handle_input = move |e: Event| { + let text = e.value().parse().unwrap_or(display_value()); + + let value = (ctx.value)(); + let format = format_description!("[year]-[month]-[day]"); + + if value.is_range { + } else { + if text.is_empty() { + ctx.set_date(None); + return; + } + + let date = Date::parse(&text, &format).ok(); + if date.is_some_and(|_| !value.is_date_selected(date)) { + ctx.set_date(date); + } + } + }; + + rsx! { + input { + style: "min-width: 240px", + placeholder, + value: display_value, + disabled: ctx.disabled, + readonly: ctx.read_only, + cursor: if (ctx.read_only)() { "pointer" } else { "text" }, + oninput: handle_input, + onpointerdown: move |event| { + if (ctx.read_only)() && event.trigger_button() == Some(MouseButton::Primary) { + ctx.open.toggle(); + } + }, + ..props.attributes, + } + {props.children} + } +} diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index f9700dc8..35697728 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -14,6 +14,7 @@ pub mod calendar; pub mod checkbox; pub mod collapsible; pub mod context_menu; +pub mod date_picker; pub mod dialog; pub mod dropdown_menu; mod focus;