mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 08:34:15 -05:00
refactor: Modal
This commit is contained in:
parent
b52d826073
commit
568e629377
18 changed files with 290 additions and 256 deletions
|
@ -61,6 +61,7 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
||||||
<Route path="/combobox" view=ComboboxMdPage/>
|
<Route path="/combobox" view=ComboboxMdPage/>
|
||||||
<Route path="/config-provider" view=ConfigProviderMdPage/>
|
<Route path="/config-provider" view=ConfigProviderMdPage/>
|
||||||
<Route path="/date-picker" view=DatePickerMdPage/>
|
<Route path="/date-picker" view=DatePickerMdPage/>
|
||||||
|
<Route path="/dialog" view=DialogMdPage/>
|
||||||
<Route path="/divider" view=DividerMdPage/>
|
<Route path="/divider" view=DividerMdPage/>
|
||||||
<Route path="/drawer" view=DrawerMdPage/>
|
<Route path="/drawer" view=DrawerMdPage/>
|
||||||
<Route path="/grid" view=GridMdPage/>
|
<Route path="/grid" view=GridMdPage/>
|
||||||
|
@ -71,7 +72,6 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
||||||
<Route path="/loading-bar" view=LoadingBarMdPage/>
|
<Route path="/loading-bar" view=LoadingBarMdPage/>
|
||||||
// <Route path="/message" view=MessageMdPage/>
|
// <Route path="/message" view=MessageMdPage/>
|
||||||
<Route path="/message-bar" view=MessageBarMdPage/>
|
<Route path="/message-bar" view=MessageBarMdPage/>
|
||||||
<Route path="/modal" view=ModalMdPage/>
|
|
||||||
<Route path="/popover" view=PopoverMdPage/>
|
<Route path="/popover" view=PopoverMdPage/>
|
||||||
<Route path="/progress-bar" view=ProgressBarMdPage/>
|
<Route path="/progress-bar" view=ProgressBarMdPage/>
|
||||||
<Route path="/radio" view=RadioMdPage/>
|
<Route path="/radio" view=RadioMdPage/>
|
||||||
|
|
|
@ -195,6 +195,10 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
||||||
value: "/components/date-picker".into(),
|
value: "/components/date-picker".into(),
|
||||||
label: "Date Picker".into(),
|
label: "Date Picker".into(),
|
||||||
},
|
},
|
||||||
|
MenuItemOption {
|
||||||
|
value: "/components/dialog".into(),
|
||||||
|
label: "Dialog".into(),
|
||||||
|
},
|
||||||
MenuItemOption {
|
MenuItemOption {
|
||||||
value: "/components/divider".into(),
|
value: "/components/divider".into(),
|
||||||
label: "Divider".into(),
|
label: "Divider".into(),
|
||||||
|
@ -235,10 +239,6 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
||||||
value: "/components/message-bar".into(),
|
value: "/components/message-bar".into(),
|
||||||
label: "Message Bar".into(),
|
label: "Message Bar".into(),
|
||||||
},
|
},
|
||||||
MenuItemOption {
|
|
||||||
value: "/components/modal".into(),
|
|
||||||
label: "Modal".into(),
|
|
||||||
},
|
|
||||||
MenuItemOption {
|
MenuItemOption {
|
||||||
value: "/components/popover".into(),
|
value: "/components/popover".into(),
|
||||||
label: "Popover".into(),
|
label: "Popover".into(),
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
# Modal
|
# Dialog
|
||||||
|
|
||||||
```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 Modal"</Button>
|
<Button on_click=move |_| open.set(true)>"Open Dialog"</Button>
|
||||||
<Modal title="title" show>
|
<Dialog open>
|
||||||
"hello"
|
<DialogSurface>
|
||||||
</Modal>
|
<DialogBody>
|
||||||
|
<DialogTitle>"Dialog title"</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
"Dialog body"
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button appearance=ButtonAppearance::Primary>"Do Something"</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</DialogBody>
|
||||||
|
</DialogSurface>
|
||||||
|
</Dialog>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -44,6 +44,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
|
||||||
"ComboboxMdPage" => "../docs/combobox/mod.md",
|
"ComboboxMdPage" => "../docs/combobox/mod.md",
|
||||||
"ConfigProviderMdPage" => "../docs/config_provider/mod.md",
|
"ConfigProviderMdPage" => "../docs/config_provider/mod.md",
|
||||||
"DatePickerMdPage" => "../docs/date_picker/mod.md",
|
"DatePickerMdPage" => "../docs/date_picker/mod.md",
|
||||||
|
"DialogMdPage" => "../docs/dialog/mod.md",
|
||||||
"DividerMdPage" => "../docs/divider/mod.md",
|
"DividerMdPage" => "../docs/divider/mod.md",
|
||||||
"DrawerMdPage" => "../docs/drawer/mod.md",
|
"DrawerMdPage" => "../docs/drawer/mod.md",
|
||||||
"GridMdPage" => "../docs/grid/mod.md",
|
"GridMdPage" => "../docs/grid/mod.md",
|
||||||
|
@ -54,7 +55,6 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
|
||||||
"LoadingBarMdPage" => "../docs/loading_bar/mod.md",
|
"LoadingBarMdPage" => "../docs/loading_bar/mod.md",
|
||||||
// "MessageMdPage" => "../docs/message/mod.md",
|
// "MessageMdPage" => "../docs/message/mod.md",
|
||||||
"MessageBarMdPage" => "../docs/message_bar/mod.md",
|
"MessageBarMdPage" => "../docs/message_bar/mod.md",
|
||||||
"ModalMdPage" => "../docs/modal/mod.md",
|
|
||||||
"PopoverMdPage" => "../docs/popover/mod.md",
|
"PopoverMdPage" => "../docs/popover/mod.md",
|
||||||
"ProgressBarMdPage" => "../docs/progress_bar/mod.md",
|
"ProgressBarMdPage" => "../docs/progress_bar/mod.md",
|
||||||
"RadioMdPage" => "../docs/radio/mod.md",
|
"RadioMdPage" => "../docs/radio/mod.md",
|
||||||
|
|
|
@ -45,15 +45,6 @@ impl ButtonShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
pub enum ButtonColor {
|
|
||||||
#[default]
|
|
||||||
Primary,
|
|
||||||
Success,
|
|
||||||
Warning,
|
|
||||||
Error,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone)]
|
#[derive(Default, PartialEq, Clone)]
|
||||||
pub enum ButtonSize {
|
pub enum ButtonSize {
|
||||||
Small,
|
Small,
|
||||||
|
@ -78,7 +69,6 @@ pub fn Button(
|
||||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||||
#[prop(optional, into)] appearance: MaybeSignal<ButtonAppearance>,
|
#[prop(optional, into)] appearance: MaybeSignal<ButtonAppearance>,
|
||||||
#[prop(optional, into)] shape: MaybeSignal<ButtonShape>,
|
#[prop(optional, into)] shape: MaybeSignal<ButtonShape>,
|
||||||
#[prop(optional, into)] color: MaybeSignal<ButtonColor>,
|
|
||||||
#[prop(optional, into)] size: MaybeSignal<ButtonSize>,
|
#[prop(optional, into)] size: MaybeSignal<ButtonSize>,
|
||||||
#[prop(optional, into)] block: MaybeSignal<bool>,
|
#[prop(optional, into)] block: MaybeSignal<bool>,
|
||||||
#[prop(optional, into)] icon: OptionalMaybeSignal<icondata_core::Icon>,
|
#[prop(optional, into)] icon: OptionalMaybeSignal<icondata_core::Icon>,
|
||||||
|
|
|
@ -9,25 +9,10 @@ pub use card_preview::*;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use thaw_utils::{class_list, mount_style, OptionalProp};
|
use thaw_utils::{class_list, mount_style, OptionalProp};
|
||||||
|
|
||||||
#[slot]
|
|
||||||
pub struct CardHeaderExtra {
|
|
||||||
children: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[slot]
|
|
||||||
pub struct CardFooter {
|
|
||||||
#[prop(default = leptos::MaybeSignal::Static(true), into)]
|
|
||||||
if_: MaybeSignal<bool>,
|
|
||||||
children: ChildrenFn,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Card(
|
pub fn Card(
|
||||||
#[prop(optional, into)] title: OptionalProp<MaybeSignal<String>>,
|
|
||||||
#[prop(optional)] card_header_extra: Option<CardHeaderExtra>,
|
|
||||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||||
children: Children,
|
children: Children,
|
||||||
#[prop(optional)] card_footer: Option<CardFooter>,
|
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
mount_style("card", include_str!("./card.css"));
|
mount_style("card", include_str!("./card.css"));
|
||||||
|
|
||||||
|
|
117
thaw/src/dialog/dialog.css
Normal file
117
thaw/src/dialog/dialog.css
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
.thaw-dialog {
|
||||||
|
z-index: 1000000;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface__backdrop {
|
||||||
|
inset: 0px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface__backdrop.fade-in-transition-enter-active {
|
||||||
|
transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface__backdrop.fade-in-transition-leave-active {
|
||||||
|
transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface__backdrop.fade-in-transition-enter-from,
|
||||||
|
.thaw-dialog-surface__backdrop.fade-in-transition-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface__backdrop.fade-in-transition-leave-from,
|
||||||
|
.thaw-dialog-surface__backdrop.fade-in-transition-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface {
|
||||||
|
inset: 0px;
|
||||||
|
padding: 24px;
|
||||||
|
margin: auto;
|
||||||
|
overflow: unset;
|
||||||
|
border: 1px solid var(--colorTransparentStroke);
|
||||||
|
border-radius: var(--borderRadiusXLarge);
|
||||||
|
display: block;
|
||||||
|
user-select: unset;
|
||||||
|
visibility: unset;
|
||||||
|
position: fixed;
|
||||||
|
height: fit-content;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface.fade-in-scale-up-transition-leave-active {
|
||||||
|
transition: opacity 0.25s cubic-bezier(0.4, 0, 1, 1),
|
||||||
|
transform 0.25s cubic-bezier(0.4, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface.fade-in-scale-up-transition-enter-active {
|
||||||
|
transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1),
|
||||||
|
transform 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface.fade-in-scale-up-transition-enter-from,
|
||||||
|
.thaw-dialog-surface.fade-in-scale-up-transition-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-surface.fade-in-scale-up-transition-leave-from,
|
||||||
|
.thaw-dialog-surface.fade-in-scale-up-transition-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-body {
|
||||||
|
overflow: unset;
|
||||||
|
gap: 8px;
|
||||||
|
display: grid;
|
||||||
|
max-height: calc(-48px + 100vh);
|
||||||
|
box-sizing: border-box;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
grid-template-columns: 1fr 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-title {
|
||||||
|
grid-area: 1 / 1 / 1 / 3;
|
||||||
|
grid-column-end: 4;
|
||||||
|
font-family: var(--fontFamilyBase);
|
||||||
|
font-size: var(--fontSizeBase500);
|
||||||
|
font-weight: var(--fontWeightSemibold);
|
||||||
|
line-height: var(--lineHeightBase500);
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-content {
|
||||||
|
padding: var(--strokeWidthThick);
|
||||||
|
margin: calc(var(--strokeWidthThick) * -1);
|
||||||
|
font-family: var(--fontFamilyBase);
|
||||||
|
font-size: var(--fontSizeBase300);
|
||||||
|
font-weight: var(--fontWeightRegular);
|
||||||
|
line-height: var(--lineHeightBase300);
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 32px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
grid-area: 2 / 1 / 2 / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-dialog-actions {
|
||||||
|
grid-column-start: 2;
|
||||||
|
justify-self: end;
|
||||||
|
grid-column-end: 4;
|
||||||
|
gap: 8px;
|
||||||
|
height: fit-content;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
grid-row: 3 / 3;
|
||||||
|
}
|
66
thaw/src/dialog/dialog.rs
Normal file
66
thaw/src/dialog/dialog.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use crate::ConfigInjection;
|
||||||
|
use leptos::*;
|
||||||
|
use thaw_components::{CSSTransition, FocusTrap, Teleport};
|
||||||
|
use thaw_utils::{mount_style, Model};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Dialog(
|
||||||
|
#[prop(into)] open: Model<bool>,
|
||||||
|
#[prop(default = true.into(), into)] mask_closeable: MaybeSignal<bool>,
|
||||||
|
#[prop(default = true, into)] close_on_esc: bool,
|
||||||
|
children: Children,
|
||||||
|
) -> impl IntoView {
|
||||||
|
mount_style("dialog", include_str!("./dialog.css"));
|
||||||
|
let config_provider = ConfigInjection::use_();
|
||||||
|
|
||||||
|
let mask_ref = NodeRef::<html::Div>::new();
|
||||||
|
let on_mask_click = move |_| {
|
||||||
|
if mask_closeable.get_untracked() {
|
||||||
|
open.set(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let on_esc = Callback::new(move |_: ev::KeyboardEvent| {
|
||||||
|
open.set(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Teleport immediate=open.signal()>
|
||||||
|
<FocusTrap disabled=!close_on_esc active=open.signal() on_esc>
|
||||||
|
<div
|
||||||
|
class="thaw-config-provider thaw-dialog"
|
||||||
|
data-thaw-id=config_provider.id().clone()
|
||||||
|
>
|
||||||
|
<CSSTransition
|
||||||
|
node_ref=mask_ref
|
||||||
|
appear=open.get_untracked()
|
||||||
|
show=open.signal()
|
||||||
|
name="fade-in-transition"
|
||||||
|
let:display
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="thaw-dialog-surface__backdrop"
|
||||||
|
style=move || display.get()
|
||||||
|
on:click=on_mask_click
|
||||||
|
ref=mask_ref
|
||||||
|
aria-hidden="true"
|
||||||
|
></div>
|
||||||
|
</CSSTransition>
|
||||||
|
<Provider value=DialogInjection{open}>
|
||||||
|
{children()}
|
||||||
|
</Provider>
|
||||||
|
</div>
|
||||||
|
</FocusTrap>
|
||||||
|
</Teleport>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct DialogInjection {
|
||||||
|
pub open: Model<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DialogInjection {
|
||||||
|
pub fn expect_use() -> Self {
|
||||||
|
expect_context::<Self>()
|
||||||
|
}
|
||||||
|
}
|
10
thaw/src/dialog/dialog_actions.rs
Normal file
10
thaw/src/dialog/dialog_actions.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use leptos::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn DialogActions(children: Children) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="thaw-dialog-actions">
|
||||||
|
{children()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
10
thaw/src/dialog/dialog_body.rs
Normal file
10
thaw/src/dialog/dialog_body.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use leptos::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn DialogBody(children: Children) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="thaw-dialog-body">
|
||||||
|
{children()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
10
thaw/src/dialog/dialog_content.rs
Normal file
10
thaw/src/dialog/dialog_content.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use leptos::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn DialogContent(children: Children) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<h2 class="thaw-dialog-content">
|
||||||
|
{children()}
|
||||||
|
</h2>
|
||||||
|
}
|
||||||
|
}
|
29
thaw/src/dialog/dialog_surface.rs
Normal file
29
thaw/src/dialog/dialog_surface.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use super::dialog::DialogInjection;
|
||||||
|
use leptos::*;
|
||||||
|
use thaw_components::CSSTransition;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn DialogSurface(children: Children) -> impl IntoView {
|
||||||
|
let dialog = DialogInjection::expect_use();
|
||||||
|
let surface_ref = NodeRef::<html::Div>::new();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<CSSTransition
|
||||||
|
node_ref=surface_ref
|
||||||
|
appear=dialog.open.get_untracked()
|
||||||
|
show=dialog.open.signal()
|
||||||
|
name="fade-in-scale-up-transition"
|
||||||
|
let:display
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="thaw-dialog-surface"
|
||||||
|
ref=surface_ref
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
style:display=move || display.get().map(|_| "none")
|
||||||
|
>
|
||||||
|
{children()}
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
}
|
||||||
|
}
|
10
thaw/src/dialog/dialog_title.rs
Normal file
10
thaw/src/dialog/dialog_title.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use leptos::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn DialogTitle(children: Children) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<h2 class="thaw-dialog-title">
|
||||||
|
{children()}
|
||||||
|
</h2>
|
||||||
|
}
|
||||||
|
}
|
13
thaw/src/dialog/mod.rs
Normal file
13
thaw/src/dialog/mod.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
mod dialog;
|
||||||
|
mod dialog_actions;
|
||||||
|
mod dialog_body;
|
||||||
|
mod dialog_content;
|
||||||
|
mod dialog_surface;
|
||||||
|
mod dialog_title;
|
||||||
|
|
||||||
|
pub use dialog::*;
|
||||||
|
pub use dialog_actions::*;
|
||||||
|
pub use dialog_body::*;
|
||||||
|
pub use dialog_content::*;
|
||||||
|
pub use dialog_surface::*;
|
||||||
|
pub use dialog_title::*;
|
|
@ -122,7 +122,7 @@ pub fn Drawer(
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
>
|
>
|
||||||
<Card title>
|
<Card>
|
||||||
<Scrollbar content_style="padding: 20px 28px;">
|
<Scrollbar content_style="padding: 20px 28px;">
|
||||||
{children()}
|
{children()}
|
||||||
</Scrollbar>
|
</Scrollbar>
|
||||||
|
|
|
@ -14,6 +14,7 @@ mod color_picker;
|
||||||
mod combobox;
|
mod combobox;
|
||||||
mod config_provider;
|
mod config_provider;
|
||||||
mod date_picker;
|
mod date_picker;
|
||||||
|
mod dialog;
|
||||||
mod divider;
|
mod divider;
|
||||||
mod drawer;
|
mod drawer;
|
||||||
mod grid;
|
mod grid;
|
||||||
|
@ -24,7 +25,6 @@ mod layout;
|
||||||
mod loading_bar;
|
mod loading_bar;
|
||||||
mod message;
|
mod message;
|
||||||
mod message_bar;
|
mod message_bar;
|
||||||
mod modal;
|
|
||||||
mod nav;
|
mod nav;
|
||||||
mod popover;
|
mod popover;
|
||||||
mod progress_bar;
|
mod progress_bar;
|
||||||
|
@ -61,6 +61,7 @@ pub use color_picker::*;
|
||||||
pub use combobox::*;
|
pub use combobox::*;
|
||||||
pub use config_provider::*;
|
pub use config_provider::*;
|
||||||
pub use date_picker::*;
|
pub use date_picker::*;
|
||||||
|
pub use dialog::*;
|
||||||
pub use divider::*;
|
pub use divider::*;
|
||||||
pub use drawer::*;
|
pub use drawer::*;
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
|
@ -71,7 +72,6 @@ pub use layout::*;
|
||||||
pub use loading_bar::*;
|
pub use loading_bar::*;
|
||||||
pub use message::*;
|
pub use message::*;
|
||||||
pub use message_bar::*;
|
pub use message_bar::*;
|
||||||
pub use modal::*;
|
|
||||||
pub use nav::*;
|
pub use nav::*;
|
||||||
pub use popover::*;
|
pub use popover::*;
|
||||||
pub use progress_bar::*;
|
pub use progress_bar::*;
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
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 {
|
|
||||||
children: ChildrenFn,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn Modal(
|
|
||||||
#[prop(into)] show: Model<bool>,
|
|
||||||
#[prop(default = true.into(), into)] mask_closeable: MaybeSignal<bool>,
|
|
||||||
#[prop(default = true, into)] close_on_esc: bool,
|
|
||||||
#[prop(default = 2000.into(), into)] z_index: MaybeSignal<i16>,
|
|
||||||
#[prop(default = MaybeSignal::Static("600px".to_string()), into)] width: MaybeSignal<String>,
|
|
||||||
#[prop(optional, into)] title: MaybeSignal<String>,
|
|
||||||
children: Children,
|
|
||||||
#[prop(optional)] modal_footer: Option<ModalFooter>,
|
|
||||||
) -> impl IntoView {
|
|
||||||
mount_style("modal", include_str!("./modal.css"));
|
|
||||||
|
|
||||||
let displayed = RwSignal::new(show.get_untracked());
|
|
||||||
Effect::new(move |prev| {
|
|
||||||
let is_show = show.get();
|
|
||||||
if prev.is_some() && is_show {
|
|
||||||
displayed.set(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 scrollbar_ref = ComponentRef::<ScrollbarRef>::new();
|
|
||||||
let modal_ref = NodeRef::<html::Div>::new();
|
|
||||||
|
|
||||||
let click_position = use_click_position();
|
|
||||||
let on_enter = Callback::new(move |_| {
|
|
||||||
let Some(position) = click_position.get_untracked() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(scroll_el) = scrollbar_ref.get_untracked() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let scroll_top = scroll_el.container_scroll_top();
|
|
||||||
|
|
||||||
let Some(modal_el) = modal_ref.get_untracked() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let x = -(modal_el.offset_left() - position.0);
|
|
||||||
let y = -(modal_el.offset_top() - position.1 - scroll_top);
|
|
||||||
|
|
||||||
let _ = modal_el.attr("style", format!("transform-origin: {}px {}px", x, y));
|
|
||||||
});
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<Teleport immediate=show.signal()>
|
|
||||||
<FocusTrap disabled=!close_on_esc active=show.signal() on_esc>
|
|
||||||
<div
|
|
||||||
class="thaw-modal-container"
|
|
||||||
style:z-index=move || z_index.get()
|
|
||||||
style=("--thaw-width", move || width.get())
|
|
||||||
>
|
|
||||||
<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
|
|
||||||
>
|
|
||||||
<CSSTransition
|
|
||||||
node_ref=mask_ref
|
|
||||||
appear=show.get_untracked()
|
|
||||||
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
|
|
||||||
appear=show.get_untracked()
|
|
||||||
show=show.signal()
|
|
||||||
name="fade-in-scale-up-transition"
|
|
||||||
on_enter
|
|
||||||
on_after_leave=move |_| displayed.set(false)
|
|
||||||
let:display
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="thaw-modal-body"
|
|
||||||
ref=modal_ref
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
style=move || display.get()
|
|
||||||
>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<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>
|
|
||||||
</FocusTrap>
|
|
||||||
</Teleport>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
.thaw-modal-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
min-height: 100%;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thaw-modal-container > * {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thaw-modal-mask {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #0007;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thaw-modal-mask.fade-in-transition-enter-active {
|
|
||||||
transition: all 0.25s cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.thaw-modal-mask.fade-in-transition-leave-active {
|
|
||||||
transition: all 0.25s cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.thaw-modal-mask.fade-in-transition-enter-from,
|
|
||||||
.thaw-modal-mask.fade-in-transition-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thaw-modal-mask.fade-in-transition-leave-from,
|
|
||||||
.thaw-modal-mask.fade-in-transition-enter-to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thaw-modal-body {
|
|
||||||
position: relative;
|
|
||||||
margin: auto;
|
|
||||||
width: var(--thaw-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
.thaw-model-title {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-in-scale-up-transition-leave-active.thaw-modal-body {
|
|
||||||
transform-origin: inherit;
|
|
||||||
transition: opacity 0.25s cubic-bezier(0.4, 0, 1, 1),
|
|
||||||
transform 0.25s cubic-bezier(0.4, 0, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-in-scale-up-transition-enter-active.thaw-modal-body {
|
|
||||||
transform-origin: inherit;
|
|
||||||
transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1),
|
|
||||||
transform 0.25s cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-in-scale-up-transition-enter-from.thaw-modal-body,
|
|
||||||
.fade-in-scale-up-transition-leave-to.thaw-modal-body {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-in-scale-up-transition-leave-from.thaw-modal-body,
|
|
||||||
.fade-in-scale-up-transition-enter-to.thaw-modal-body {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue