feature: closable messages and message position (#90)

* feature: closable messages and message position

* all positions

* move close button

* review changes

---------

Co-authored-by: Cristobal Andrada <kandrelczyk@gmail.com>
This commit is contained in:
luoxiaozero 2024-01-22 21:14:55 +08:00 committed by GitHub
parent cef2310866
commit 93b75b2082
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 131 additions and 20 deletions

View file

@ -10,7 +10,7 @@ let success = move |_| {
message.create(
"Success".into(),
MessageVariant::Success,
Default::default(),
MessageOptions {closable: true, duration: std::time::Duration::from_secs(0)},
);
};
let warning = move |_| {
@ -33,8 +33,21 @@ view! {
}
```
### MessageProvider Props
| Name | Type | Default | Desciption |
| --------- | ----------------------------- | ----------------------- | ------------------------------- |
| placement | `MessagePlacement` | `MessagePlacement::Top` | Position to place the messages. |
### MessageProvider Injection Methods
| Name | Type | Description |
| ------ | ------------------------------------------------------------------------------ | ------------------------ |
| create | `fn(&self, content: String, variant: MessageVariant, options: MessageOptions)` | Use create type message. |
### MessageOptions fields
| Name | Type | Default | Description |
| -------- | ----------------- | ------------------------- | --------------------------------------------------------------- |
| duration | `Duration` | `Duration::from_secs(3)` | How long the message will be displayed. 0 for permanent message |
| closable | `bool` | `false` | Can the message be manually closed. |

View file

@ -107,6 +107,7 @@ pub fn AutoComplete(
}
});
} else if key == *"Enter" {
event.prevent_default();
let option_value = options.with_untracked(|options| {
let index = select_option_index.get_untracked();
if options.len() > index {

View file

@ -16,7 +16,7 @@ pub fn Teleport(
let mount = mount.unwrap_or_else(|| {
document()
.body()
.expect("body element not to exist")
.expect("body element to exist")
.unchecked_into()
});

View file

@ -1,18 +1,57 @@
.thaw-message-container {
z-index: 6000;
position: fixed;
top: 12px;
left: 0;
right: 0;
height: 0;
overflow: visible;
display: flex;
flex-direction: column;
pointer-events: none;
}
.thaw-message-container--top {
align-items: center;
top: 12px;
left: 0;
right: 0;
}
.thaw-message-container--bottom {
align-items: center;
bottom: 12px;
left: 0;
right: 0;
}
.thaw-message-container--bottom-left {
align-items: start;
bottom: 12px;
left: 12px;
right: 0px;
}
.thaw-message-container--bottom-right {
align-items: end;
bottom: 12px;
left: 0px;
right: 12px;
}
.thaw-message-container--top-left{
align-items: start;
top: 12px;
left: 12px;
right: 0px;
}
.thaw-message-container--top-right {
align-items: end;
top: 12px;
left: 0px;
right: 12px;
}
.thaw-message-wrapper {
margin-bottom: 8px;
position: relative;
}
.thaw-message {
@ -21,8 +60,8 @@
align-items: center;
flex-wrap: nowrap;
overflow: hidden;
padding: 10px 20px;
max-width: 75vh;
background: var(--thaw-background-color);
@ -30,6 +69,8 @@
border-radius: 3px;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
pointer-events: all;
}
.thaw-message__icon {
@ -42,3 +83,9 @@
.thaw-message__content {
line-height: 1.6;
}
.thaw-message__close {
margin-left: 10px;
display: flex;
cursor: pointer;
}

View file

@ -5,15 +5,18 @@ use uuid::Uuid;
#[component]
pub fn MessageEnvironment(
message: MessageType,
#[prop(into)] on_internal_after_leave: Callback<Uuid>,
#[prop(into)] on_internal_after_leave: Callback<Uuid, ()>,
) -> impl IntoView {
let (id, content, variant, options) = message;
set_timeout(
move || {
on_internal_after_leave.call(id);
},
options.duration,
);
view! { <Message content variant/> }
if !options.duration.is_zero() {
set_timeout(
move || {
on_internal_after_leave.call(id);
},
options.duration,
);
}
view! { <Message id closable=options.closable content variant on_close=on_internal_after_leave/> }
}

View file

@ -1,12 +1,39 @@
use std::time::Duration;
use super::{message_environment::MessageEnvironment, MessageVariant};
use crate::{components::Teleport, utils::mount_style};
use crate::{components::Teleport, utils::{class_list::class_list, mount_style}};
use leptos::*;
use uuid::Uuid;
#[derive(Default, Clone)]
pub enum MessagePlacement {
#[default]
Top,
TopLeft,
TopRight,
Bottom,
BottomLeft,
BottomRight
}
impl MessagePlacement {
fn container_style(&self) -> String {
match self {
MessagePlacement::Top => "thaw-message-container--top".to_owned(),
MessagePlacement::TopLeft => "thaw-message-container--top-left".to_owned(),
MessagePlacement::TopRight => "thaw-message-container--top-right".to_owned(),
MessagePlacement::Bottom => "thaw-message-container--bottom".to_owned(),
MessagePlacement::BottomLeft => "thaw-message-container--bottom-left".to_owned(),
MessagePlacement::BottomRight=> "thaw-message-container--bottom-right".to_owned(),
}
}
}
#[component]
pub fn MessageProvider(children: Children) -> impl IntoView {
pub fn MessageProvider(
#[prop(optional)] placement: MessagePlacement,
children: Children,
) -> impl IntoView {
mount_style("message", include_str!("./message.css"));
let message_list = create_rw_signal::<Vec<MessageType>>(vec![]);
@ -25,7 +52,7 @@ pub fn MessageProvider(children: Children) -> impl IntoView {
message_list,
)>
{children()} <Teleport>
<div class="thaw-message-container">
<div class=class_list!["thaw-message-container", placement.container_style()]>
<For
each=move || message_list.get()
key=|message| message.0
@ -50,12 +77,14 @@ pub(crate) type MessageType = (Uuid, String, MessageVariant, MessageOptions);
#[derive(Clone)]
pub struct MessageOptions {
pub duration: Duration,
pub closable: bool,
}
impl Default for MessageOptions {
fn default() -> Self {
Self {
duration: Duration::from_secs(3),
closable: false,
}
}
}
@ -82,3 +111,5 @@ impl MessageInjection {
pub fn use_message() -> MessageInjection {
expect_context::<MessageInjection>()
}

View file

@ -2,9 +2,10 @@ mod message_environment;
mod message_provider;
mod theme;
use crate::{theme::use_theme, Icon, Theme};
use crate::{theme::use_theme, Icon, Theme, components::{If, Then}};
use icondata::*;
use leptos::*;
use uuid::Uuid;
pub use message_provider::*;
pub use theme::MessageTheme;
@ -34,7 +35,13 @@ impl MessageVariant {
}
#[component]
pub(crate) fn Message(variant: MessageVariant, content: String) -> impl IntoView {
pub(crate) fn Message(
variant: MessageVariant,
content: String,
closable: bool,
id: Uuid,
#[prop(into)] on_close: Callback<Uuid, ()>,
) -> impl IntoView {
let theme = use_theme(Theme::light);
let css_vars = create_memo(move |_| {
let mut css_vars = String::new();
@ -54,7 +61,16 @@ pub(crate) fn Message(variant: MessageVariant, content: String) -> impl IntoView
<Icon icon=variant.icon() style/>
</div>
<div class="thaw-message__content">{content}</div>
<If cond=closable>
<Then slot>
<div class="thaw-message__close" on:click=move |_| on_close.call(id)>
<Icon icon=icondata::Icon::Ai(AiCloseOutlined)/>
</div>
</Then>
</If>
</div>
</div>
}
}