diff --git a/demo_markdown/docs/drawer/mod.md b/demo_markdown/docs/drawer/mod.md index 3594d798..234fd4e9 100644 --- a/demo_markdown/docs/drawer/mod.md +++ b/demo_markdown/docs/drawer/mod.md @@ -39,12 +39,27 @@ view! { } ``` +### Scroll content + +```rust demo +let show = create_rw_signal(false); + +view! { + + + r#"This being said, the world is moving in the direction opposite to Clarke's predictions. In 2001: A Space Odyssey, in the year of 2001, which has already passed, human beings have built magnificent cities in space, and established permanent colonies on the moon, and huge nuclear-powered spacecraft have sailed to Saturn. However, today, in 2018, the walk on the moon has become a distant memory.And the farthest reach of our manned space flights is just as long as the two-hour mileage of a high-speed train passing through my city. At the same time, information technology is developing at an unimaginable speed. With the entire world covered by the Internet, people have gradually lost their interest in space, as they find themselves increasingly comfortable in the space created by IT. Instead of an exploration of the real space, which is full of real difficulties, people now just prefer to experience virtual space through VR. Just like someone said, "You promised me an ocean of stars, but you actually gave me Facebook.""# + +} +``` + ### Drawer Props | Name | Type | Default | Desciption | | --- | --- | --- | --- | | class | `OptionalProp>` | `Default::default()` | Addtional classes for the drawer element. | | show | `Model` | | Whether to show drawer. | +| mask_closeable | `MaybeSignal` | `true` | Whether to emit hide event when click mask. | +| close_on_esc | `bool` | `true` | Whether to close drawer on Esc is pressed. | | title | `OptionalProp>` | `Default::default()` | Drawer title. | | placement | `MaybeSignal` | `DrawerPlacement::Right` | Drawer placement. | | width | `MaybeSignal` | `520px` | Drawer width. | diff --git a/thaw/src/drawer/drawer.css b/thaw/src/drawer/drawer.css index a1403e5c..e3fb15ea 100644 --- a/thaw/src/drawer/drawer.css +++ b/thaw/src/drawer/drawer.css @@ -37,7 +37,8 @@ .thaw-drawer > .thaw-card > .thaw-card__content { flex: 1; - padding: 20px 28px; + padding: 0; + overflow: hidden; } .thaw-drawer--placement-top { diff --git a/thaw/src/drawer/mod.rs b/thaw/src/drawer/mod.rs index dcc4c7f3..fa33ebea 100644 --- a/thaw/src/drawer/mod.rs +++ b/thaw/src/drawer/mod.rs @@ -1,11 +1,13 @@ -use crate::Card; +use crate::{Card, Scrollbar}; use leptos::*; -use thaw_components::{CSSTransition, Teleport}; +use thaw_components::{CSSTransition, FocusTrap, Teleport}; use thaw_utils::{class_list, mount_style, use_lock_html_scroll, Model, OptionalProp}; #[component] pub fn Drawer( #[prop(into)] show: Model, + #[prop(default = true.into(), into)] mask_closeable: MaybeSignal, + #[prop(default = true, into)] close_on_esc: bool, #[prop(optional, into)] title: OptionalProp>, #[prop(optional, into)] placement: MaybeSignal, #[prop(default = MaybeSignal::Static("520px".to_string()), into)] width: MaybeSignal, @@ -29,6 +31,8 @@ pub fn Drawer( #[component] fn DrawerInnr( show: Model, + mask_closeable: MaybeSignal, + close_on_esc: bool, title: OptionalProp>, placement: MaybeSignal, class: OptionalProp>, @@ -57,7 +61,8 @@ pub fn Drawer( let is_lock = RwSignal::new(show.get_untracked()); Effect::new(move |_| { - if show.get() { + let is_show = show.get(); + if is_show { is_lock.set(true); is_css_transition.set(true); } @@ -68,55 +73,72 @@ pub fn Drawer( is_css_transition.set(false); }; + let on_mask_click = move |_| { + if mask_closeable.get_untracked() { + show.set(false); + } + }; + let on_esc = Callback::new(move |_: ev::KeyboardEvent| { + show.set(false); + }); + view! { -
- -
-
- - +
+
+ + + +
+ } } match mount { - DrawerMount::None => view! { }, + DrawerMount::None => { + view! { } + } DrawerMount::Body => view! { - + }, } diff --git a/thaw/src/modal/mod.rs b/thaw/src/modal/mod.rs index 790d8fe5..7000d20e 100644 --- a/thaw/src/modal/mod.rs +++ b/thaw/src/modal/mod.rs @@ -1,7 +1,7 @@ -use crate::{Card, CardFooter, CardHeader, CardHeaderExtra, Icon}; -use leptos::{leptos_dom::helpers::WindowListenerHandle, *}; -use thaw_components::{CSSTransition, OptionComp, Teleport}; -use thaw_utils::{mount_style, use_click_position, Model}; +use crate::{Card, CardFooter, CardHeader, CardHeaderExtra, Icon, Scrollbar, ScrollbarRef}; +use leptos::*; +use thaw_components::{CSSTransition, FocusTrap, OptionComp, Teleport}; +use thaw_utils::{mount_style, use_click_position, ComponentRef, Model}; #[slot] pub struct ModalFooter { @@ -22,30 +22,11 @@ pub fn Modal( mount_style("modal", include_str!("./modal.css")); let displayed = RwSignal::new(show.get_untracked()); - let esc_handle = StoredValue::new(None::); Effect::new(move |prev| { let is_show = show.get(); if prev.is_some() && is_show { displayed.set(true); } - - if close_on_esc { - if is_show && !prev.unwrap_or(false) { - let handle = window_event_listener(ev::keydown, move |e| { - if &e.code() == "Escape" { - show.set(false); - } - }); - esc_handle.set_value(Some(handle)); - } else { - esc_handle.update_value(|handle| { - if let Some(handle) = handle.take() { - handle.remove(); - } - }) - } - } - is_show }); let on_mask_click = move |_| { @@ -53,9 +34,12 @@ pub fn Modal( show.set(false); } }; + let on_esc = Callback::new(move |_: ev::KeyboardEvent| { + show.set(false); + }); let mask_ref = NodeRef::::new(); - let scroll_ref = NodeRef::::new(); + let scrollbar_ref = ComponentRef::::new(); let modal_ref = NodeRef::::new(); let click_position = use_click_position(); @@ -64,10 +48,10 @@ pub fn Modal( return; }; - let Some(scroll_el) = scroll_ref.get_untracked() else { + let Some(scroll_el) = scrollbar_ref.get_untracked() else { return; }; - let scroll_top = scroll_el.scroll_top(); + let scroll_top = scroll_el.container_scroll_top(); let Some(modal_el) = modal_ref.get_untracked() else { return; @@ -79,77 +63,71 @@ pub fn Modal( let _ = modal_el.attr("style", format!("transform-origin: {}px {}px", x, y)); }); - on_cleanup(move || { - esc_handle.update_value(|handle| { - if let Some(handle) = handle.take() { - handle.remove(); - } - }) - }); - view! { -
- -
-
+
- - - + + +
-
+
} } diff --git a/thaw/src/modal/modal.css b/thaw/src/modal/modal.css index 97efb784..867fc3e9 100644 --- a/thaw/src/modal/modal.css +++ b/thaw/src/modal/modal.css @@ -13,13 +13,6 @@ pointer-events: auto; } -.thaw-modal-scroll { - display: flex; - min-height: 100%; - min-width: 100%; - overflow: scroll; -} - .thaw-modal-mask { position: fixed; top: 0; diff --git a/thaw/src/scrollbar/mod.rs b/thaw/src/scrollbar/mod.rs index 6329c9e6..f56bcd8b 100644 --- a/thaw/src/scrollbar/mod.rs +++ b/thaw/src/scrollbar/mod.rs @@ -4,7 +4,7 @@ pub use theme::ScrollbarTheme; use crate::{use_theme, Theme}; use leptos::{leptos_dom::helpers::WindowListenerHandle, *}; -use thaw_utils::{class_list, mount_style, OptionalProp}; +use thaw_utils::{class_list, mount_style, ComponentRef, OptionalProp}; #[component] pub fn Scrollbar( @@ -13,6 +13,7 @@ pub fn Scrollbar( #[prop(optional, into)] content_class: OptionalProp>, #[prop(optional, into)] content_style: OptionalProp>, #[prop(default = 8)] size: u8, + #[prop(optional)] comp_ref: Option>, children: Children, ) -> impl IntoView { mount_style("scrollbar", include_str!("./scrollbar.css")); @@ -49,6 +50,12 @@ pub fn Scrollbar( let content_height = RwSignal::new(0); let thumb_status = StoredValue::new(None::); + if let Some(comp_ref) = comp_ref { + comp_ref.load(ScrollbarRef { + container_scroll_top, + }); + } + let x_thumb_width = Memo::new(move |_| { let content_width = f64::from(content_width.get()); let x_track_width = f64::from(x_track_width.get()); @@ -322,3 +329,14 @@ enum ThumbStatus { Enter, DelayLeave, } + +#[derive(Clone)] +pub struct ScrollbarRef { + container_scroll_top: RwSignal, +} + +impl ScrollbarRef { + pub fn container_scroll_top(&self) -> i32 { + self.container_scroll_top.get() + } +} diff --git a/thaw_components/Cargo.toml b/thaw_components/Cargo.toml index 39b9a2c6..5d4a1baf 100644 --- a/thaw_components/Cargo.toml +++ b/thaw_components/Cargo.toml @@ -16,6 +16,7 @@ leptos = { version = "0.6.10" } thaw_utils = { workspace = true } web-sys = { version = "0.3.69", features = ["DomRect"] } cfg-if = "1.0.0" +uuid = { version = "1.7.0", features = ["v4"] } [features] csr = ["leptos/csr"] diff --git a/thaw_components/src/focus_trap/mod.rs b/thaw_components/src/focus_trap/mod.rs new file mode 100644 index 00000000..7bc8b560 --- /dev/null +++ b/thaw_components/src/focus_trap/mod.rs @@ -0,0 +1,59 @@ +use leptos::{leptos_dom::helpers::WindowListenerHandle, *}; +use std::cell::RefCell; + +#[cfg(any(feature = "csr", feature = "hydrate"))] +thread_local! { + static STACK: RefCell> = Default::default(); +} + +#[component] +pub fn FocusTrap( + disabled: bool, + #[prop(into)] active: MaybeSignal, + #[prop(into)] on_esc: Callback, + children: Children, +) -> impl IntoView { + #[cfg(any(feature = "csr", feature = "hydrate"))] + if disabled == false { + let esc_handle = StoredValue::new(None::); + let id = StoredValue::new(uuid::Uuid::new_v4()); + + let is_current_active = + move || STACK.with_borrow(|stack| id.with_value(|id| stack.last() == Some(id))); + let deactivate = move || { + esc_handle.update_value(|handle| { + if let Some(handle) = handle.take() { + handle.remove(); + } + }); + STACK.with_borrow_mut(|stack| stack.retain(|value| id.with_value(|id| id != value))); + }; + + Effect::new(move |prev| { + let is_active = active.get(); + if is_active && !prev.unwrap_or(false) { + let handle = window_event_listener(ev::keydown, move |e| { + if &e.code() == "Escape" { + if is_current_active() { + on_esc.call(e); + } + } + }); + esc_handle.set_value(Some(handle)); + STACK.with_borrow_mut(|stack| { + stack.push(id.get_value()); + }); + } else { + deactivate(); + } + + is_active + }); + + on_cleanup(move || { + deactivate(); + }); + } + + children() +} diff --git a/thaw_components/src/lib.rs b/thaw_components/src/lib.rs index c5f2e01b..49012966 100644 --- a/thaw_components/src/lib.rs +++ b/thaw_components/src/lib.rs @@ -1,5 +1,6 @@ mod binder; mod css_transition; +mod focus_trap; mod if_comp; mod option_comp; mod teleport; @@ -7,6 +8,7 @@ mod wave; pub use binder::{Binder, Follower, FollowerPlacement, FollowerWidth}; pub use css_transition::CSSTransition; +pub use focus_trap::FocusTrap; pub use if_comp::{ElseIf, If, Then}; pub use option_comp::OptionComp; pub use teleport::Teleport;