Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/drawer modal scrollbar #160

Merged
merged 5 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions demo_markdown/docs/drawer/mod.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,27 @@ view! {
}
```

### Scroll content

```rust demo
let show = create_rw_signal(false);

view! {
<Button on_click=move |_| show.set(true)>"Open"</Button>
<Drawer show width="160px" title="Scroll content">
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>
}
```

### Drawer Props

| Name | Type | Default | Desciption |
| --- | --- | --- | --- |
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the drawer element. |
| show | `Model<bool>` | | Whether to show drawer. |
| mask_closeable | `MaybeSignal<bool>` | `true` | Whether to emit hide event when click mask. |
| close_on_esc | `bool` | `true` | Whether to close drawer on Esc is pressed. |
| title | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Drawer title. |
| placement | `MaybeSignal<DrawerPlacement>` | `DrawerPlacement::Right` | Drawer placement. |
| width | `MaybeSignal<String>` | `520px` | Drawer width. |
Expand Down
3 changes: 2 additions & 1 deletion thaw/src/drawer/drawer.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@

.thaw-drawer > .thaw-card > .thaw-card__content {
flex: 1;
padding: 20px 28px;
padding: 0;
overflow: hidden;
}

.thaw-drawer--placement-top {
Expand Down
110 changes: 66 additions & 44 deletions thaw/src/drawer/mod.rs
Original file line number Diff line number Diff line change
@@ -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<bool>,
#[prop(default = true.into(), into)] mask_closeable: MaybeSignal<bool>,
#[prop(default = true, into)] close_on_esc: bool,
#[prop(optional, into)] title: OptionalProp<MaybeSignal<String>>,
#[prop(optional, into)] placement: MaybeSignal<DrawerPlacement>,
#[prop(default = MaybeSignal::Static("520px".to_string()), into)] width: MaybeSignal<String>,
Expand All @@ -29,6 +31,8 @@ pub fn Drawer(
#[component]
fn DrawerInnr(
show: Model<bool>,
mask_closeable: MaybeSignal<bool>,
close_on_esc: bool,
title: OptionalProp<MaybeSignal<String>>,
placement: MaybeSignal<DrawerPlacement>,
class: OptionalProp<MaybeSignal<String>>,
Expand Down Expand Up @@ -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);
}
Expand All @@ -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! {
<div class="thaw-drawer-container" style=move || style.get()>
<CSSTransition
node_ref=mask_ref
show=show.signal()
name="fade-in-transition"
let:display
>
<div
class="thaw-drawer-mask"
style=move || display.get()
on:click=move |_| show.set(false)
ref=mask_ref
></div>
</CSSTransition>
<CSSTransition
node_ref=drawer_ref
show=show.signal()
name=Memo::new(move |_| {
format!("slide-in-from-{}-transition", placement.get())
})

on_after_enter
on_after_leave
let:display
>
<div
class=class_list![
"thaw-drawer", move || format!("thaw-drawer--placement-{}", placement
.get()), class.map(| c | move || c.get())
]

style=move || display.get()
ref=drawer_ref
role="dialog"
aria-modal="true"
<FocusTrap disabled=!close_on_esc active=show.signal() on_esc>
<div class="thaw-drawer-container" style=move || style.get()>
<CSSTransition
node_ref=mask_ref
show=show.signal()
name="fade-in-transition"
let:display
>
<Card title>{children()}</Card>
</div>
</CSSTransition>
</div>
<div
class="thaw-drawer-mask"
style=move || display.get()
on:click=on_mask_click
ref=mask_ref
></div>
</CSSTransition>
<CSSTransition
node_ref=drawer_ref
show=show.signal()
name=Memo::new(move |_| {
format!("slide-in-from-{}-transition", placement.get())
})

on_after_enter
on_after_leave
let:display
>
<div
class=class_list![
"thaw-drawer", move || format!("thaw-drawer--placement-{}", placement
.get()), class.map(| c | move || c.get())
]

style=move || display.get()
ref=drawer_ref
role="dialog"
aria-modal="true"
>
<Card title>
<Scrollbar content_style="padding: 20px 28px;">
{children()}
</Scrollbar>
</Card>
</div>
</CSSTransition>
</div>
</FocusTrap>
}
}

match mount {
DrawerMount::None => view! { <DrawerInnr show title placement class style children/> },
DrawerMount::None => {
view! { <DrawerInnr show mask_closeable close_on_esc title placement class style children/> }
}
DrawerMount::Body => view! {
<Teleport>
<DrawerInnr show title placement class style children/>
<DrawerInnr show mask_closeable close_on_esc title placement class style children/>
</Teleport>
},
}
Expand Down
158 changes: 68 additions & 90 deletions thaw/src/modal/mod.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -22,40 +22,24 @@ pub fn Modal(
mount_style("modal", include_str!("./modal.css"));

let displayed = RwSignal::new(show.get_untracked());
let esc_handle = StoredValue::new(None::<WindowListenerHandle>);
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 |_| {
if mask_closeable.get_untracked() {
show.set(false);
}
};
let on_esc = Callback::new(move |_: ev::KeyboardEvent| {
show.set(false);
});

let mask_ref = NodeRef::<html::Div>::new();
let scroll_ref = NodeRef::<html::Div>::new();
let scrollbar_ref = ComponentRef::<ScrollbarRef>::new();
let modal_ref = NodeRef::<html::Div>::new();

let click_position = use_click_position();
Expand All @@ -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;
Expand All @@ -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! {
<Teleport>
<div
class="thaw-modal-container"
style:z-index=move || z_index.get()
style=("--thaw-width", move || width.get())
>
<CSSTransition
node_ref=mask_ref
show=show.signal()
name="fade-in-transition"
let:display
>
<div
class="thaw-modal-mask"
style=move || display.get()
on:click=on_mask_click
ref=mask_ref
></div>
</CSSTransition>
<FocusTrap disabled=!close_on_esc active=show.signal() on_esc>
<div
class="thaw-modal-scroll"
style=move || (!displayed.get()).then_some("display: none")
ref=scroll_ref
class="thaw-modal-container"
style:z-index=move || z_index.get()
style=("--thaw-width", move || width.get())
>
<CSSTransition
node_ref=modal_ref
show=show.signal()
name="fade-in-scale-up-transition"
on_enter
on_after_leave=move |_| displayed.set(false)
let:display
<Scrollbar
content_style="min-height: 100%; display: flex;"
style=Signal::derive(move || if displayed.get() { String::new() } else { String::from("display: none") })
comp_ref=scrollbar_ref
>
<div
class="thaw-modal-body"
ref=modal_ref
role="dialog"
aria-modal="true"
style=move || display.get()
<CSSTransition
node_ref=mask_ref
show=show.signal()
name="fade-in-transition"
let:display
>
<div
class="thaw-modal-mask"
style=move || display.get()
on:click=on_mask_click
ref=mask_ref
></div>
</CSSTransition>
<CSSTransition
node_ref=modal_ref
show=show.signal()
name="fade-in-scale-up-transition"
on_enter
on_after_leave=move |_| displayed.set(false)
let:display
>
<Card>
<CardHeader slot>
<span class="thaw-model-title">{move || title.get()}</span>
</CardHeader>
<CardHeaderExtra slot>
<span
style="cursor: pointer;"
on:click=move |_| show.set(false)
>
<Icon icon=icondata_ai::AiCloseOutlined/>
</span>
</CardHeaderExtra>
{children()}
<CardFooter slot if_=modal_footer.is_some()>
<OptionComp value=modal_footer.as_ref() let:footer>
{(footer.children)()}
</OptionComp>
</CardFooter>
</Card>
</div>
</CSSTransition>
<div
class="thaw-modal-body"
ref=modal_ref
role="dialog"
aria-modal="true"
style=move || display.get()
>
<Card>
<CardHeader slot>
<span class="thaw-model-title">{move || title.get()}</span>
</CardHeader>
<CardHeaderExtra slot>
<span
style="cursor: pointer;"
on:click=move |_| show.set(false)
>
<Icon icon=icondata_ai::AiCloseOutlined/>
</span>
</CardHeaderExtra>
{children()}
<CardFooter slot if_=modal_footer.is_some()>
<OptionComp value=modal_footer.as_ref() let:footer>
{(footer.children)()}
</OptionComp>
</CardFooter>
</Card>
</div>
</CSSTransition>
</Scrollbar>
</div>
</div>
</FocusTrap>
</Teleport>
}
}
Loading
Loading