refactor: Drawer

This commit is contained in:
luoxiao 2024-06-29 18:32:40 +08:00
parent 7eb81f32ae
commit f08956586e
9 changed files with 547 additions and 451 deletions

View file

@ -1,54 +1,143 @@
# Drawer # Drawer
```rust demo ```rust demo
let show = create_rw_signal(false); let open = RwSignal::new(false);
let placement = create_rw_signal(DrawerPlacement::Top); let position = RwSignal::new(DrawerPosition::Top);
let open = Callback::new(move |new_placement: DrawerPlacement| { let open_f = Callback::new(move |new_position: DrawerPosition| {
// Note: Since `show` changes are made in real time, // Note: Since `show` changes are made in real time,
// please put it in front of `show.set(true)` when changing `placement`. // please put it in front of `show.set(true)` when changing `placement`.
placement.set(new_placement); position.set(new_position);
show.set(true); open.set(true);
}); });
view! { view! {
<ButtonGroup> <ButtonGroup>
<Button on_click=Callback::new(move |_| open.call(DrawerPlacement::Top))>"Top"</Button> <Button on_click=Callback::new(move |_| open_f.call(DrawerPosition::Top))>"Top"</Button>
<Button on_click=Callback::new(move |_| open.call(DrawerPlacement::Right))>"Right"</Button> <Button on_click=Callback::new(move |_| open_f.call(DrawerPosition::Right))>"Right"</Button>
<Button on_click=Callback::new(move |_| open.call(DrawerPlacement::Bottom))>"Bottom"</Button> <Button on_click=Callback::new(move |_| open_f.call(DrawerPosition::Bottom))>"Bottom"</Button>
<Button on_click=Callback::new(move |_| open.call(DrawerPlacement::Left))>"Left"</Button> <Button on_click=Callback::new(move |_| open_f.call(DrawerPosition::Left))>"Left"</Button>
</ButtonGroup> </ButtonGroup>
<Drawer title="Title" show placement> <OverlayDrawer open position>
"Hello" <DrawerHeader>
</Drawer> <DrawerHeaderTitle>
<DrawerHeaderTitleAction slot>
<Button
appearance=ButtonAppearance::Subtle
on_click=move |_| open.set(false)
>
"x"
</Button>
</DrawerHeaderTitleAction>
"Default Drawer"
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>"Drawer content"</p>
</DrawerBody>
</OverlayDrawer>
} }
``` ```
### Customize display area ### Inline
```rust demo ```rust demo
let show = create_rw_signal(false); let open_left = RwSignal::new(false);
let open_right = RwSignal::new(false);
let open_buttom = RwSignal::new(false);
view! { view! {
<div style="position: relative; overflow: hidden; height: 200px; background-color: #0078ff88;"> <div style="display: flex; flex-direction: column; overflow: hidden; height: 400px; background-color: #0078ff88;">
<Button on_click=move |_| show.set(true)>"Open"</Button> <div style="display: flex; overflow: hidden; height: 400px;">
<Drawer show mount=DrawerMount::None width="50%"> <InlineDrawer open=open_left>
"Current position" <DrawerHeader>
</Drawer> <DrawerHeaderTitle>
<DrawerHeaderTitleAction slot>
<Button
appearance=ButtonAppearance::Subtle
on_click=move |_| open_left.set(false)
>
"x"
</Button>
</DrawerHeaderTitleAction>
"Inline Drawer"
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>"Drawer content"</p>
</DrawerBody>
</InlineDrawer>
<div style="flex: 1">
<Button on_click=move |_| open_left.set(true)>"Open left"</Button>
<Button on_click=move |_| open_right.set(true)>"Open right"</Button>
<Button on_click=move |_| open_buttom.set(true)>"Open buttom"</Button>
</div>
<InlineDrawer open=open_right position=DrawerPosition::Right>
<DrawerHeader>
<DrawerHeaderTitle>
<DrawerHeaderTitleAction slot>
<Button
appearance=ButtonAppearance::Subtle
on_click=move |_| open_right.set(false)
>
"x"
</Button>
</DrawerHeaderTitleAction>
"Inline Drawer"
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>"Drawer content"</p>
</DrawerBody>
</InlineDrawer>
</div>
<InlineDrawer open=open_buttom position=DrawerPosition::Bottom>
<DrawerHeader>
<DrawerHeaderTitle>
<DrawerHeaderTitleAction slot>
<Button
appearance=ButtonAppearance::Subtle
on_click=move |_| open_buttom.set(false)
>
"x"
</Button>
</DrawerHeaderTitleAction>
"Inline Drawer"
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>"Drawer content"</p>
</DrawerBody>
</InlineDrawer>
</div> </div>
} }
``` ```
### Scroll content ### With Scroll
```rust demo ```rust demo
let show = create_rw_signal(false); let open = RwSignal::new(false);
view! { view! {
<Button on_click=move |_| show.set(true)>"Open"</Button> <Button on_click=move |_| open.set(true)>"Open"</Button>
<Drawer show width="160px" title="Scroll content"> <OverlayDrawer open>
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.""# <DrawerHeader>
</Drawer> <DrawerHeaderTitle>
<DrawerHeaderTitleAction slot>
<Button
appearance=ButtonAppearance::Subtle
on_click=move |_| open.set(false)
>
"x"
</Button>
</DrawerHeaderTitleAction>
"Default Drawer"
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p style="line-height: 40px">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.""#</p>
</DrawerBody>
</OverlayDrawer>
} }
``` ```

View file

@ -1,184 +1,49 @@
.thaw-drawer-container { .thaw-drawer-header {
position: absolute; width: 100%;
top: 0; max-width: 100%;
right: 0; padding: var(--spacingVerticalXXL) var(--spacingHorizontalXXL)
left: 0; var(--spacingVerticalS);
bottom: 0; gap: var(--spacingHorizontalS);
pointer-events: none; align-self: stretch;
} display: flex;
flex-direction: column;
.thaw-drawer-container > * {
pointer-events: auto;
}
.thaw-drawer-mask {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
background-color: #0007;
}
.thaw-drawer {
position: absolute;
}
.thaw-drawer > .thaw-card {
border-radius: 0;
height: 100%;
box-sizing: border-box; box-sizing: border-box;
position: relative;
z-index: 2;
} }
.thaw-drawer > .thaw-card > .thaw-card__header { .thaw-drawer-header-title {
border-bottom: 1px solid var(--thaw-border-color); column-gap: var(--spacingHorizontalS);
font-size: 16px; justify-content: space-between;
align-items: center;
display: flex;
} }
.thaw-drawer > .thaw-card > .thaw-card__content { .thaw-drawer-header-title__heading {
flex: 1; font-family: var(--fontFamilyBase);
padding: 0; font-size: var(--fontSizeBase500);
overflow: hidden; font-weight: var(--fontWeightSemibold);
line-height: var(--lineHeightBase500);
margin: 0px;
grid-area: 1 / 1 / 1 / 3;
} }
.thaw-drawer--placement-top { .thaw-drawer-header-title__action {
height: var(--thaw-height); margin-right: calc(var(--spacingHorizontalS) * -1);
top: 0; grid-row: 1 / 1;
left: 0; grid-column-start: 3;
right: 0; place-self: start end;
} }
.thaw-drawer--placement-bottom { .thaw-drawer-body {
height: var(--thaw-height); padding: 0 var(--spacingHorizontalXXL);
bottom: 0; flex: 1 1 0%;
left: 0; align-self: stretch;
right: 0; position: relative;
z-index: 1;
overflow: auto;
} }
.thaw-drawer--placement-left { .thaw-drawer-body:last-child {
width: var(--thaw-width); padding-bottom: calc(var(--spacingHorizontalXXL) + 1px);
top: 0;
bottom: 0;
left: 0;
}
.thaw-drawer--placement-right {
width: var(--thaw-width);
top: 0;
bottom: 0;
right: 0;
}
.thaw-drawer.slide-in-from-right-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer.slide-in-from-right-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer.slide-in-from-right-transition-enter-to {
transform: translateX(0);
}
.thaw-drawer.slide-in-from-right-transition-enter-from {
transform: translateX(100%);
}
.thaw-drawer.slide-in-from-right-transition-leave-from {
transform: translateX(0);
}
.thaw-drawer.slide-in-from-right-transition-leave-to {
transform: translateX(100%);
}
.thaw-drawer.slide-in-from-left-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer.slide-in-from-left-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer.slide-in-from-left-transition-enter-to {
transform: translateX(0);
}
.thaw-drawer.slide-in-from-left-transition-enter-from {
transform: translateX(-100%);
}
.thaw-drawer.slide-in-from-left-transition-leave-from {
transform: translateX(0);
}
.thaw-drawer.slide-in-from-left-transition-leave-to {
transform: translateX(-100%);
}
.thaw-drawer.slide-in-from-top-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer.slide-in-from-top-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer.slide-in-from-top-transition-enter-to {
transform: translateY(0);
}
.thaw-drawer.slide-in-from-top-transition-enter-from {
transform: translateY(-100%);
}
.thaw-drawer.slide-in-from-top-transition-leave-from {
transform: translateY(0);
}
.thaw-drawer.slide-in-from-top-transition-leave-to {
transform: translateY(-100%);
}
.thaw-drawer.slide-in-from-bottom-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer.slide-in-from-bottom-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer.slide-in-from-bottom-transition-enter-to {
transform: translateY(0);
}
.thaw-drawer.slide-in-from-bottom-transition-enter-from {
transform: translateY(100%);
}
.thaw-drawer.slide-in-from-bottom-transition-leave-from {
transform: translateY(0);
}
.thaw-drawer.slide-in-from-bottom-transition-leave-to {
transform: translateY(100%);
}
.thaw-drawer-mask.fade-in-transition-enter-active {
transition: all 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer-mask.fade-in-transition-leave-active {
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer-mask.fade-in-transition-enter-from,
.thaw-drawer-mask.fade-in-transition-leave-to {
opacity: 0;
}
.thaw-drawer-mask.fade-in-transition-leave-from,
.thaw-drawer-mask.fade-in-transition-enter-to {
opacity: 1;
} }

View file

@ -1,10 +1,13 @@
use crate::Scrollbar;
use leptos::*; use leptos::*;
#[component] #[component]
pub fn DrawerBody(children: Children) -> impl IntoView { pub fn DrawerBody(children: Children) -> impl IntoView {
view! { view! {
<Scrollbar>
<div class="thaw-drawer-body"> <div class="thaw-drawer-body">
{children()} {children()}
</div> </div>
</Scrollbar>
} }
} }

View file

@ -0,0 +1,145 @@
.thaw-inline-drawer {
--thaw-drawer--size: 320px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
position: relative;
max-width: 100vw;
height: auto;
max-height: 100vh;
background-color: var(--colorNeutralBackground1);
color: var(--colorNeutralForeground1);
transform: translate3d(0px, 0px, 0px);
opacity: 1;
will-change: opacity, transform;
transition-property: opacity, transform;
transition-duration: var(--durationGentle);
box-sizing: border-box;
border-right: var(--strokeWidthThin) solid var(--colorTransparentStroke);
overflow: hidden;
}
.thaw-inline-drawer--position-top {
height: var(--thaw-drawer--size);
top: 0;
left: 0;
right: 0;
}
.thaw-inline-drawer--position-bottom {
height: var(--thaw-drawer--size);
bottom: 0;
left: 0;
right: 0;
}
.thaw-inline-drawer--position-left {
width: var(--thaw-drawer--size);
top: 0;
bottom: 0;
left: 0;
}
.thaw-inline-drawer--position-right {
width: var(--thaw-drawer--size);
top: 0;
bottom: 0;
right: 0;
}
.thaw-inline-drawer.slide-in-from-right-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-inline-drawer.slide-in-from-right-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-inline-drawer.slide-in-from-right-transition-enter-to {
transform: translateX(0);
}
.thaw-inline-drawer.slide-in-from-right-transition-enter-from {
transform: translateX(100%);
}
.thaw-inline-drawer.slide-in-from-right-transition-leave-from {
transform: translateX(0);
}
.thaw-inline-drawer.slide-in-from-right-transition-leave-to {
transform: translateX(100%);
}
.thaw-inline-drawer.slide-in-from-left-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-inline-drawer.slide-in-from-left-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-inline-drawer.slide-in-from-left-transition-enter-to {
transform: translateX(0);
}
.thaw-inline-drawer.slide-in-from-left-transition-enter-from {
transform: translateX(-100%);
}
.thaw-inline-drawer.slide-in-from-left-transition-leave-from {
transform: translateX(0);
}
.thaw-inline-drawer.slide-in-from-left-transition-leave-to {
transform: translateX(-100%);
}
.thaw-inline-drawer.slide-in-from-top-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-inline-drawer.slide-in-from-top-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-inline-drawer.slide-in-from-top-transition-enter-to {
transform: translateY(0);
}
.thaw-inline-drawer.slide-in-from-top-transition-enter-from {
transform: translateY(-100%);
}
.thaw-inline-drawer.slide-in-from-top-transition-leave-from {
transform: translateY(0);
}
.thaw-inline-drawer.slide-in-from-top-transition-leave-to {
transform: translateY(-100%);
}
.thaw-inline-drawer.slide-in-from-bottom-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-inline-drawer.slide-in-from-bottom-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-inline-drawer.slide-in-from-bottom-transition-enter-to {
transform: translateY(0);
}
.thaw-inline-drawer.slide-in-from-bottom-transition-enter-from {
transform: translateY(100%);
}
.thaw-inline-drawer.slide-in-from-bottom-transition-leave-from {
transform: translateY(0);
}
.thaw-inline-drawer.slide-in-from-bottom-transition-leave-to {
transform: translateY(100%);
}

View file

@ -1,30 +1,43 @@
use super::{DrawerPosition, DrawerSize};
use leptos::*; use leptos::*;
use thaw_components::CSSTransition; use thaw_components::CSSTransition;
use thaw_utils::{class_list, mount_style, Model}; use thaw_utils::{class_list, mount_style, Model};
#[component] #[component]
fn InlineDrawer( pub fn InlineDrawer(
open: Model<bool>, #[prop(into)] open: Model<bool>,
mask_closeable: MaybeSignal<bool>, #[prop(optional, into)] position: MaybeSignal<DrawerPosition>,
close_on_esc: bool, #[prop(optional, into)] size: MaybeSignal<DrawerSize>,
// placement: MaybeSignal<DrawerPlacement>,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
mount_style("overlay-drawer", include_str!("./overlay-drawer.css")); mount_style("drawer", include_str!("./drawer.css"));
mount_style("inline-drawer", include_str!("./inline-drawer.css"));
let drawer_ref = NodeRef::<html::Div>::new(); let drawer_ref = NodeRef::<html::Div>::new();
let placement = Memo::new(move |prev| { let is_css_transition = RwSignal::new(false);
// let placement: = placement.get().as_str(); let on_after_enter = move |_| {
// let Some(prev) = prev else { is_css_transition.set(false);
// return placement; };
// }; let lazy_position = Memo::new(move |prev| {
let position = position.get().as_str();
let Some(prev) = prev else {
return position;
};
// if is_css_transition.get() { if is_css_transition.get() {
// prev prev
// } else { } else {
// placement position
// } }
"left"
}); });
Effect::new(move |_| {
let is_open = open.get();
if is_open {
is_css_transition.set(true);
}
});
let on_after_leave = move |_| {
is_css_transition.set(false);
};
view! { view! {
<CSSTransition <CSSTransition
@ -32,16 +45,23 @@ fn InlineDrawer(
appear=open.get_untracked() appear=open.get_untracked()
show=open.signal() show=open.signal()
name=Memo::new(move |_| { name=Memo::new(move |_| {
format!("slide-in-from-{}-transition", placement.get()) format!("slide-in-from-{}-transition", lazy_position.get())
}) })
on_after_enter
on_after_leave
let:display let:display
> >
<div <div
class=class_list![ class=class_list![
"thaw-overlay-drawer", move || format!("thaw-drawer--placement-{}", "thaw-inline-drawer",
placement.get()) move || format!("thaw-inline-drawer--position-{}", lazy_position.get())
] ]
style=move || {
let size = move || {format!("--thaw-drawer--size: {}", size.get().as_size_str(position))};
display
.get()
.map_or_else(size, |d| d.to_string())
}
ref=drawer_ref ref=drawer_ref
> >
{children()} {children()}

View file

@ -1,169 +1,27 @@
// mod inline_drawer; mod drawer_body;
// mod overlay_drawer; mod drawer_header;
mod drawer_header_title;
mod inline_drawer;
mod overlay_drawer;
// pub use inline_drawer::*; pub use drawer_body::*;
// pub use overlay_drawer::*; pub use drawer_header::*;
pub use drawer_header_title::*;
pub use inline_drawer::*;
pub use overlay_drawer::*;
use crate::{Card, Scrollbar}; use leptos::{MaybeSignal, SignalWith};
use leptos::*;
use thaw_components::{CSSTransition, FocusTrap, Teleport};
use thaw_utils::{class_list, mount_style, use_lock_html_scroll, Model, OptionalProp};
#[component] #[derive(Clone, Default, Copy)]
pub fn Drawer( pub enum DrawerPosition {
#[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>,
#[prop(default = MaybeSignal::Static("260px".to_string()), into)] height: MaybeSignal<String>,
#[prop(default = 2000.into(), into)] z_index: MaybeSignal<i16>,
#[prop(optional, into)] mount: DrawerMount,
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
children: Children,
) -> impl IntoView {
mount_style("drawer", include_str!("./drawer.css"));
let style = create_memo(move |_| {
let mut style = String::new();
style.push_str(&format!("--thaw-width: {};", width.get()));
style.push_str(&format!("--thaw-height: {};", height.get()));
style.push_str(&format!("z-index: {};", z_index.get()));
style
});
#[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>>,
style: Memo<String>,
children: Children,
) -> impl IntoView {
let mask_ref = NodeRef::<html::Div>::new();
let drawer_ref = NodeRef::<html::Div>::new();
let is_css_transition = RwSignal::new(false);
let placement = Memo::new(move |prev| {
let placement = placement.get().as_str();
let Some(prev) = prev else {
return placement;
};
if is_css_transition.get() {
prev
} else {
placement
}
});
let on_after_enter = move |_| {
is_css_transition.set(false);
};
let is_lock = RwSignal::new(show.get_untracked());
Effect::new(move |_| {
let is_show = show.get();
if is_show {
is_lock.set(true);
is_css_transition.set(true);
}
});
use_lock_html_scroll(is_lock.into());
let on_after_leave = move |_| {
is_lock.set(false);
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! {
<FocusTrap disabled=!close_on_esc active=show.signal() on_esc>
<div class="thaw-drawer-container" style=move || style.get()>
<CSSTransition
node_ref=mask_ref
appear=show.get_untracked()
show=show.signal()
name="fade-in-transition"
let:display
>
<div
class="thaw-drawer-mask"
style=move || display.get()
on:click=on_mask_click
ref=mask_ref
></div>
</CSSTransition>
<CSSTransition
node_ref=drawer_ref
appear=show.get_untracked()
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>
<Scrollbar content_style="padding: 20px 28px;">
{children()}
</Scrollbar>
</Card>
</div>
</CSSTransition>
</div>
</FocusTrap>
}
}
match mount {
DrawerMount::None => {
view! { <DrawerInnr show mask_closeable close_on_esc title placement class style children/> }
}
DrawerMount::Body => view! {
<Teleport immediate=show.signal()>
<DrawerInnr show mask_closeable close_on_esc title placement class style children/>
</Teleport>
},
}
}
#[derive(Clone, Default)]
pub enum DrawerPlacement {
Top, Top,
Bottom, Bottom,
Left,
#[default] #[default]
Left,
Right, Right,
} }
impl Copy for DrawerPlacement {} impl DrawerPosition {
impl DrawerPlacement {
pub fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
match self { match self {
Self::Top => "top", Self::Top => "top",
@ -174,9 +32,25 @@ impl DrawerPlacement {
} }
} }
#[derive(Default)] #[derive(Clone, Default, Copy)]
pub enum DrawerMount { pub enum DrawerSize {
None,
#[default] #[default]
Body, Small,
Medium,
Large,
Full,
}
impl DrawerSize {
fn as_size_str(&self, position: MaybeSignal<DrawerPosition>) -> &'static str {
match self {
Self::Small => "320px",
Self::Medium => "592px",
Self::Large => "940px",
Self::Full => position.with(|p| match p {
DrawerPosition::Top | DrawerPosition::Bottom => "100vh",
DrawerPosition::Left | DrawerPosition::Right => "100vw",
}),
}
}
} }

View file

@ -18,6 +18,24 @@
background-color: rgba(0, 0, 0, 0.4); background-color: rgba(0, 0, 0, 0.4);
} }
.thaw-overlay-drawer__backdrop.fade-in-transition-enter-active {
transition: all 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-overlay-drawer__backdrop.fade-in-transition-leave-active {
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-overlay-drawer__backdrop.fade-in-transition-enter-from,
.thaw-overlay-drawer__backdrop.fade-in-transition-leave-to {
opacity: 0;
}
.thaw-overlay-drawer__backdrop.fade-in-transition-leave-from,
.thaw-overlay-drawer__backdrop.fade-in-transition-enter-to {
opacity: 1;
}
.thaw-overlay-drawer { .thaw-overlay-drawer {
--thaw-drawer--size: 320px; --thaw-drawer--size: 320px;
display: flex; display: flex;
@ -25,11 +43,6 @@
align-items: flex-start; align-items: flex-start;
justify-content: flex-start; justify-content: flex-start;
position: fixed; position: fixed;
top: 0px;
bottom: 0px;
right: auto;
left: 0px;
width: var(--thaw-drawer--size);
max-width: 100vw; max-width: 100vw;
height: auto; height: auto;
max-height: 100vh; max-height: 100vh;
@ -46,52 +59,127 @@
overflow: hidden; overflow: hidden;
} }
.thaw-drawer-header {
width: 100%; .thaw-overlay-drawer--position-top {
max-width: 100%; height: var(--thaw-drawer--size);
padding: var(--spacingVerticalXXL) var(--spacingHorizontalXXL) top: 0;
var(--spacingVerticalS); left: 0;
gap: var(--spacingHorizontalS); right: 0;
align-self: stretch;
display: flex;
flex-direction: column;
box-sizing: border-box;
position: relative;
z-index: 2;
} }
.thaw-drawer-header-title { .thaw-overlay-drawer--position-bottom {
column-gap: var(--spacingHorizontalS); height: var(--thaw-drawer--size);
justify-content: space-between; bottom: 0;
align-items: center; left: 0;
display: flex; right: 0;
} }
.thaw-drawer-header-title__heading { .thaw-overlay-drawer--position-left {
font-family: var(--fontFamilyBase); width: var(--thaw-drawer--size);
font-size: var(--fontSizeBase500); top: 0;
font-weight: var(--fontWeightSemibold); bottom: 0;
line-height: var(--lineHeightBase500); left: 0;
margin: 0px;
grid-area: 1 / 1 / 1 / 3;
} }
.thaw-drawer-header-title__action { .thaw-overlay-drawer--position-right {
margin-right: calc(var(--spacingHorizontalS)* -1); width: var(--thaw-drawer--size);
grid-row: 1 / 1; top: 0;
grid-column-start: 3; bottom: 0;
place-self: start end; right: 0;
} }
.thaw-drawer-body { .thaw-overlay-drawer.slide-in-from-right-transition-leave-active {
padding: 0 var(--spacingHorizontalXXL); transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
flex: 1 1 0%;
align-self: stretch;
position: relative;
z-index: 1;
overflow: auto;
} }
.thaw-drawer-body:last-child { .thaw-overlay-drawer.slide-in-from-right-transition-enter-active {
padding-bottom: calc(var(--spacingHorizontalXXL) + 1px); transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-overlay-drawer.slide-in-from-right-transition-enter-to {
transform: translateX(0);
}
.thaw-overlay-drawer.slide-in-from-right-transition-enter-from {
transform: translateX(100%);
}
.thaw-overlay-drawer.slide-in-from-right-transition-leave-from {
transform: translateX(0);
}
.thaw-overlay-drawer.slide-in-from-right-transition-leave-to {
transform: translateX(100%);
}
.thaw-overlay-drawer.slide-in-from-left-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-overlay-drawer.slide-in-from-left-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-overlay-drawer.slide-in-from-left-transition-enter-to {
transform: translateX(0);
}
.thaw-overlay-drawer.slide-in-from-left-transition-enter-from {
transform: translateX(-100%);
}
.thaw-overlay-drawer.slide-in-from-left-transition-leave-from {
transform: translateX(0);
}
.thaw-overlay-drawer.slide-in-from-left-transition-leave-to {
transform: translateX(-100%);
}
.thaw-overlay-drawer.slide-in-from-top-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-overlay-drawer.slide-in-from-top-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-overlay-drawer.slide-in-from-top-transition-enter-to {
transform: translateY(0);
}
.thaw-overlay-drawer.slide-in-from-top-transition-enter-from {
transform: translateY(-100%);
}
.thaw-overlay-drawer.slide-in-from-top-transition-leave-from {
transform: translateY(0);
}
.thaw-overlay-drawer.slide-in-from-top-transition-leave-to {
transform: translateY(-100%);
}
.thaw-overlay-drawer.slide-in-from-bottom-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-overlay-drawer.slide-in-from-bottom-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-overlay-drawer.slide-in-from-bottom-transition-enter-to {
transform: translateY(0);
}
.thaw-overlay-drawer.slide-in-from-bottom-transition-enter-from {
transform: translateY(100%);
}
.thaw-overlay-drawer.slide-in-from-bottom-transition-leave-from {
transform: translateY(0);
}
.thaw-overlay-drawer.slide-in-from-bottom-transition-leave-to {
transform: translateY(100%);
} }

View file

@ -1,42 +1,45 @@
use super::{DrawerPosition, DrawerSize};
use crate::ConfigInjection; use crate::ConfigInjection;
use leptos::*; use leptos::*;
use thaw_components::{CSSTransition, FocusTrap, Teleport}; use thaw_components::{CSSTransition, FocusTrap, Teleport};
use thaw_utils::{class_list, mount_style, use_lock_html_scroll, Model}; use thaw_utils::{class_list, mount_style, use_lock_html_scroll, Model};
#[component] #[component]
fn OverlayDrawer( pub fn OverlayDrawer(
open: Model<bool>, #[prop(into)] open: Model<bool>,
mask_closeable: MaybeSignal<bool>, #[prop(default = true.into(), into)] mask_closeable: MaybeSignal<bool>,
close_on_esc: bool, #[prop(optional, into)] close_on_esc: bool,
// placement: MaybeSignal<DrawerPlacement>, #[prop(optional, into)] position: MaybeSignal<DrawerPosition>,
#[prop(optional, into)] size: MaybeSignal<DrawerSize>,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
mount_style("drawer", include_str!("./drawer.css"));
mount_style("overlay-drawer", include_str!("./overlay-drawer.css")); mount_style("overlay-drawer", include_str!("./overlay-drawer.css"));
let config_provider = ConfigInjection::use_(); let config_provider = ConfigInjection::use_();
let drawer_ref = NodeRef::<html::Div>::new(); let drawer_ref = NodeRef::<html::Div>::new();
let placement = Memo::new(move |prev| {
// let placement: = placement.get().as_str();
// let Some(prev) = prev else {
// return placement;
// };
// if is_css_transition.get() {
// prev
// } else {
// placement
// }
"left"
});
let is_css_transition = RwSignal::new(false); let is_css_transition = RwSignal::new(false);
let on_after_enter = move |_| { let on_after_enter = move |_| {
is_css_transition.set(false); is_css_transition.set(false);
}; };
let lazy_position = Memo::new(move |prev| {
let position = position.get().as_str();
let Some(prev) = prev else {
return position;
};
if is_css_transition.get() {
prev
} else {
position
}
});
let is_lock = RwSignal::new(open.get_untracked()); let is_lock = RwSignal::new(open.get_untracked());
Effect::new(move |_| { Effect::new(move |_| {
let is_show = open.get(); let is_open = open.get();
if is_show { if is_open {
is_lock.set(true); is_lock.set(true);
is_css_transition.set(true); is_css_transition.set(true);
} }
@ -80,7 +83,7 @@ fn OverlayDrawer(
appear=open.get_untracked() appear=open.get_untracked()
show=open.signal() show=open.signal()
name=Memo::new(move |_| { name=Memo::new(move |_| {
format!("slide-in-from-{}-transition", placement.get()) format!("slide-in-from-{}-transition", lazy_position.get())
}) })
on_after_enter on_after_enter
@ -89,11 +92,16 @@ fn OverlayDrawer(
> >
<div <div
class=class_list![ class=class_list![
"thaw-overlay-drawer", move || format!("thaw-drawer--placement-{}", "thaw-overlay-drawer",
placement.get()) move || format!("thaw-overlay-drawer--position-{}", lazy_position.get())
] ]
style=move || display.get() style=move || {
let size = move || {format!("--thaw-drawer--size: {}", size.get().as_size_str(position))};
display
.get()
.map_or_else(size, |d| d.to_string())
}
ref=drawer_ref ref=drawer_ref
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"

View file

@ -43,6 +43,7 @@ pub struct CommonTheme {
pub spacing_horizontal_m_nudge: String, pub spacing_horizontal_m_nudge: String,
pub spacing_horizontal_m: String, pub spacing_horizontal_m: String,
pub spacing_horizontal_l: String, pub spacing_horizontal_l: String,
pub spacing_horizontal_x_x_l: String,
pub spacing_vertical_none: String, pub spacing_vertical_none: String,
pub spacing_vertical_x_s: String, pub spacing_vertical_x_s: String,
pub spacing_vertical_s_nudge: String, pub spacing_vertical_s_nudge: String,
@ -50,6 +51,7 @@ pub struct CommonTheme {
pub spacing_vertical_m_nudge: String, pub spacing_vertical_m_nudge: String,
pub spacing_vertical_m: String, pub spacing_vertical_m: String,
pub spacing_vertical_l: String, pub spacing_vertical_l: String,
pub spacing_vertical_x_x_l: String,
pub duration_ultra_fast: String, pub duration_ultra_fast: String,
pub duration_faster: String, pub duration_faster: String,
@ -105,6 +107,7 @@ impl CommonTheme {
spacing_horizontal_m_nudge: "10px".into(), spacing_horizontal_m_nudge: "10px".into(),
spacing_horizontal_m: "12px".into(), spacing_horizontal_m: "12px".into(),
spacing_horizontal_l: "16px".into(), spacing_horizontal_l: "16px".into(),
spacing_horizontal_x_x_l: "24px".into(),
spacing_vertical_none: "0".into(), spacing_vertical_none: "0".into(),
spacing_vertical_x_s: "4px".into(), spacing_vertical_x_s: "4px".into(),
spacing_vertical_s_nudge: "6px".into(), spacing_vertical_s_nudge: "6px".into(),
@ -112,6 +115,7 @@ impl CommonTheme {
spacing_vertical_m_nudge: "10px".into(), spacing_vertical_m_nudge: "10px".into(),
spacing_vertical_m: "12px".into(), spacing_vertical_m: "12px".into(),
spacing_vertical_l: "16px".into(), spacing_vertical_l: "16px".into(),
spacing_vertical_x_x_l: "24px".into(),
duration_ultra_fast: "50ms".into(), duration_ultra_fast: "50ms".into(),
duration_faster: "100ms".into(), duration_faster: "100ms".into(),