feat: popover appearance

This commit is contained in:
luoxiao 2024-06-11 16:48:41 +08:00
parent c41d177782
commit f999e252e4
6 changed files with 93 additions and 77 deletions

View file

@ -68,7 +68,7 @@ pub fn Demo(demo_code: DemoCode, #[prop(optional)] children: Option<Children>) -
view! { view! {
<div class="demo-demo__view">{children()}</div> <div class="demo-demo__view">{children()}</div>
<div class="demo-demo__toolbar" class=("demo-demo__toolbar--code", move || !is_show_code.get())> <div class="demo-demo__toolbar" class=("demo-demo__toolbar--code", move || !is_show_code.get())>
<Popover tooltip=true> <Popover appearance=PopoverAppearance::Inverted>
<PopoverTrigger slot> <PopoverTrigger slot>
<span on:click=move |_| is_show_code.update(|show| *show = !*show) class="demo-demo__toolbar-btn"> <span on:click=move |_| is_show_code.update(|show| *show = !*show) class="demo-demo__toolbar-btn">
{ {

View file

@ -54,7 +54,7 @@ view! {
</Popover> </Popover>
</GridItem> </GridItem>
<GridItem> <GridItem>
<Popover placement=PopoverPlacement::LeftStart> <Popover placement=PopoverPlacement::LeftStart trigger_type=PopoverTriggerType::Click>
<PopoverTrigger slot> <PopoverTrigger slot>
<Button>"Left Start"</Button> <Button>"Left Start"</Button>
</PopoverTrigger> </PopoverTrigger>
@ -70,7 +70,7 @@ view! {
</Popover> </Popover>
</GridItem> </GridItem>
<GridItem> <GridItem>
<Popover placement=PopoverPlacement::Left> <Popover placement=PopoverPlacement::Left trigger_type=PopoverTriggerType::Click>
<PopoverTrigger slot> <PopoverTrigger slot>
<Button>"Left"</Button> <Button>"Left"</Button>
</PopoverTrigger> </PopoverTrigger>
@ -129,12 +129,18 @@ view! {
} }
``` ```
### Tooltip ### Appearance
```rust demo ```rust demo
view! { view! {
<Space> <Space>
<Popover tooltip=true> <Popover appearance=PopoverAppearance::Brand>
<PopoverTrigger slot>
<Button>"Hover"</Button>
</PopoverTrigger>
"Content"
</Popover>
<Popover appearance=PopoverAppearance::Inverted>
<PopoverTrigger slot> <PopoverTrigger slot>
<Button>"Hover"</Button> <Button>"Hover"</Button>
</PopoverTrigger> </PopoverTrigger>

View file

@ -1,32 +1,33 @@
use crate::ConfigInjection; use crate::ConfigInjection;
use leptos::{leptos_dom::helpers::TimeoutHandle, *}; use leptos::{leptos_dom::helpers::TimeoutHandle, *};
use palette::bool_mask::BoolMask;
use std::time::Duration; use std::time::Duration;
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement}; use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement};
use thaw_utils::{add_event_listener, class_list, mount_style, OptionalProp}; use thaw_utils::{add_event_listener, class_list, mount_style};
#[slot] #[slot]
pub struct PopoverTrigger { pub struct PopoverTrigger {
#[prop(optional, into)] #[prop(optional, into)]
class: OptionalProp<MaybeSignal<String>>, class: MaybeProp<String>,
children: Children, children: Children,
} }
#[component] #[component]
pub fn Popover( pub fn Popover(
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>, #[prop(optional, into)] class: MaybeProp<String>,
#[prop(optional)] trigger_type: PopoverTriggerType, #[prop(optional)] trigger_type: PopoverTriggerType,
popover_trigger: PopoverTrigger, popover_trigger: PopoverTrigger,
#[prop(optional)] placement: PopoverPlacement, #[prop(optional)] placement: PopoverPlacement,
#[prop(optional)] tooltip: bool, #[prop(optional, into)] appearance: Option<MaybeSignal<PopoverAppearance>>,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
mount_style("popover", include_str!("./popover.css")); mount_style("popover", include_str!("./popover.css"));
let config_provider = ConfigInjection::use_(); let config_provider = ConfigInjection::use_();
let popover_ref = create_node_ref::<html::Div>(); let popover_ref = NodeRef::<html::Div>::new();
let target_ref = create_node_ref::<html::Div>(); let target_ref = NodeRef::<html::Div>::new();
let is_show_popover = create_rw_signal(false); let is_show_popover = RwSignal::new(false);
let show_popover_handle = store_value(None::<TimeoutHandle>); let show_popover_handle = StoredValue::new(None::<TimeoutHandle>);
let on_mouse_enter = move |_| { let on_mouse_enter = move |_| {
if trigger_type != PopoverTriggerType::Hover { if trigger_type != PopoverTriggerType::Hover {
@ -63,6 +64,9 @@ pub fn Popover(
if trigger_type != PopoverTriggerType::Click { if trigger_type != PopoverTriggerType::Click {
return; return;
} }
if !is_show_popover.get_untracked() {
return;
}
let el = ev.target(); let el = ev.target();
let mut el: Option<web_sys::Element> = let mut el: Option<web_sys::Element> =
el.into_js_result().map_or(None, |el| Some(el.into())); el.into_js_result().map_or(None, |el| Some(el.into()));
@ -101,7 +105,7 @@ pub fn Popover(
view! { view! {
<Binder target_ref> <Binder target_ref>
<div <div
class=class_list!["thaw-popover-trigger", trigger_class.map(| c | move || c.get())] class=class_list!["thaw-popover-trigger", trigger_class]
ref=target_ref ref=target_ref
on:mouseenter=on_mouse_enter on:mouseenter=on_mouse_enter
on:mouseleave=on_mouse_leave on:mouseleave=on_mouse_leave
@ -117,11 +121,11 @@ pub fn Popover(
let:display let:display
> >
<div <div
class=if tooltip { class=class_list![
"thaw-config-provider thaw-popover-surface thaw-popover--tooltip" } "thaw-config-provider thaw-popover-surface",
else { appearance.map(|appearance| move || format!("thaw-popover-surface--{}", appearance.get().as_str())),
"thaw-config-provider thaw-popover-surface" class
} ]
data-thaw-id=config_provider.id().clone() data-thaw-id=config_provider.id().clone()
style=move || display.get() style=move || display.get()
@ -129,7 +133,7 @@ pub fn Popover(
on:mouseenter=on_mouse_enter on:mouseenter=on_mouse_enter
on:mouseleave=on_mouse_leave on:mouseleave=on_mouse_leave
> >
<div class=class.map(|c| move || c.get())>{children()}</div> {children()}
<div class="thaw-popover-surface__angle"> <div class="thaw-popover-surface__angle">
</div> </div>
</div> </div>
@ -139,6 +143,21 @@ pub fn Popover(
} }
} }
#[derive(Clone)]
pub enum PopoverAppearance {
Brand,
Inverted,
}
impl PopoverAppearance {
pub fn as_str(&self) -> &'static str {
match self {
PopoverAppearance::Brand => "brand",
PopoverAppearance::Inverted => "inverted",
}
}
}
#[derive(Default, PartialEq, Clone)] #[derive(Default, PartialEq, Clone)]
pub enum PopoverTriggerType { pub enum PopoverTriggerType {
#[default] #[default]

View file

@ -14,6 +14,16 @@ div.thaw-popover-surface {
color: var(--colorNeutralForeground1); color: var(--colorNeutralForeground1);
} }
div.thaw-popover-surface--brand {
background-color: var(--colorBrandBackground);
color: var(--colorNeutralForegroundOnBrand);
}
div.thaw-popover-surface--inverted {
background-color: var(--colorNeutralBackgroundStatic);
color: var(--colorNeutralForegroundStaticInverted);
}
.thaw-popover-surface__angle { .thaw-popover-surface__angle {
position: absolute; position: absolute;
background-color: inherit; background-color: inherit;
@ -53,99 +63,70 @@ div.thaw-popover-surface {
left: 50%; left: 50%;
} }
[data-thaw-placement="left-start"] > .thaw-popover, [data-thaw-placement="left-start"] > .thaw-popover-surface,
[data-thaw-placement="left-end"] > .thaw-popover, [data-thaw-placement="left-end"] > .thaw-popover-surface,
[data-thaw-placement="left"] > .thaw-popover { [data-thaw-placement="left"] > .thaw-popover-surface {
margin-right: 10px; margin-right: 10px;
box-shadow: 3px 0 6px -4px rgba(0, 0, 0, 0.12),
6px 0 16px 0 rgba(0, 0, 0, 0.08), 9px 0 28px 8px rgba(0, 0, 0, 0.05);
} }
[data-thaw-placement="left-start"] .thaw-popover-surface__angle, [data-thaw-placement="left-start"] .thaw-popover-surface__angle,
[data-thaw-placement="left-end"] .thaw-popover-surface__angle, [data-thaw-placement="left-end"] .thaw-popover-surface__angle,
[data-thaw-placement="left"] .thaw-popover-surface__angle { [data-thaw-placement="left"] .thaw-popover-surface__angle {
width: 10px;
height: 100%;
right: -10px;
top: 0;
bottom: 0;
}
[data-thaw-placement="left-start"] .thaw-popover-surface__angle::before,
[data-thaw-placement="left-end"] .thaw-popover-surface__angle::before,
[data-thaw-placement="left"] .thaw-popover-surface__angle::before {
top: 50%;
transform: rotate(45deg) translateX(-7px); transform: rotate(45deg) translateX(-7px);
top: 50%;
right: -10px;
} }
[data-thaw-placement="right-start"] > .thaw-popover, [data-thaw-placement="right-start"] > .thaw-popover-surface,
[data-thaw-placement="right-end"] > .thaw-popover, [data-thaw-placement="right-end"] > .thaw-popover-surface,
[data-thaw-placement="right"] > .thaw-popover { [data-thaw-placement="right"] > .thaw-popover-surface {
margin-left: 10px; margin-left: 10px;
box-shadow: -3px 0 6px -4px rgba(0, 0, 0, 0.12),
-6px 0 16px 0 rgba(0, 0, 0, 0.08), -9px 0 28px 8px rgba(0, 0, 0, 0.05);
} }
[data-thaw-placement="right-start"] .thaw-popover-surface__angle, [data-thaw-placement="right-start"] .thaw-popover-surface__angle,
[data-thaw-placement="right-end"] .thaw-popover-surface__angle, [data-thaw-placement="right-end"] .thaw-popover-surface__angle,
[data-thaw-placement="right"] .thaw-popover-surface__angle { [data-thaw-placement="right"] .thaw-popover-surface__angle {
width: 10px;
height: 100%;
left: -10px;
top: 0;
bottom: 0;
}
[data-thaw-placement="right-start"] .thaw-popover-surface__angle::before,
[data-thaw-placement="right-end"] .thaw-popover-surface__angle::before,
[data-thaw-placement="right"] .thaw-popover-surface__angle::before {
top: 50%;
transform: rotate(45deg) translateY(-7px); transform: rotate(45deg) translateY(-7px);
top: 50%;
left: -10px;
} }
[data-thaw-placement="bottom-start"] .thaw-popover-surface__angle, [data-thaw-placement="bottom-start"] .thaw-popover-surface__angle,
[data-thaw-placement="top-start"] .thaw-popover-surface__angle { [data-thaw-placement="top-start"] .thaw-popover-surface__angle {
left: 16px; left: 16px;
} }
[data-thaw-placement="bottom-end"] .thaw-popover-surface__angle::before, [data-thaw-placement="bottom-end"] .thaw-popover-surface__angle,
[data-thaw-placement="top-end"] .thaw-popover-surface__angle::before { [data-thaw-placement="top-end"] .thaw-popover-surface__angle {
left: initial; left: initial;
right: 7px; right: 7px;
} }
[data-thaw-placement="right-start"] .thaw-popover-surface__angle::before, [data-thaw-placement="right-start"] .thaw-popover-surface__angle,
[data-thaw-placement="left-start"] .thaw-popover-surface__angle::before { [data-thaw-placement="left-start"] .thaw-popover-surface__angle {
top: 16px; top: 16px;
} }
[data-thaw-placement="right-end"] .thaw-popover-surface__angle::before, [data-thaw-placement="right-end"] .thaw-popover-surface__angle,
[data-thaw-placement="left-end"] .thaw-popover-surface__angle::before { [data-thaw-placement="left-end"] .thaw-popover-surface__angle {
top: initial; top: initial;
bottom: 7px; bottom: 7px;
} }
.thaw-popover.popover-transition-enter-from, .thaw-popover-surface.popover-transition-enter-from,
.thaw-popover.popover-transition-leave-to { .thaw-popover-surface.popover-transition-leave-to {
opacity: 0; opacity: 0;
transform: scale(0.85); transform: scale(0.85);
} }
.thaw-popover.popover-transition-enter-to, .thaw-popover-surface.popover-transition-enter-to,
.thaw-popover.popover-transition-leave-from { .thaw-popover-surface.popover-transition-leave-from {
transform: scale(1); transform: scale(1);
opacity: 1; opacity: 1;
} }
.thaw-popover.popover-transition-enter-active { .thaw-popover-surface.popover-transition-leave-active,
transition: box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1), .thaw-popover-surface.popover-transition-enter-active {
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1), transition: box-shadow var(--durationSlow) var(--curveDecelerateMid),
color 0.3s cubic-bezier(0.4, 0, 0.2, 1), background-color var(--durationSlow) var(--curveDecelerateMid),
opacity 0.15s cubic-bezier(0, 0, 0.2, 1), color var(--durationSlow) var(--curveDecelerateMid),
transform 0.15s cubic-bezier(0, 0, 0.2, 1); opacity var(--durationNormal) var(--curveDecelerateMid),
} transform var(--durationNormal) var(--curveDecelerateMid);
.thaw-popover.popover-transition-leave-active {
transition: box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.15s cubic-bezier(0.4, 0, 1, 1),
transform 0.15s cubic-bezier(0.4, 0, 1, 1);
} }

View file

@ -2,6 +2,7 @@ use thaw_macro::WriteCSSVars;
#[derive(Clone, WriteCSSVars)] #[derive(Clone, WriteCSSVars)]
pub struct ColorTheme { pub struct ColorTheme {
pub color_neutral_background_static: String,
pub color_neutral_background_disabled: String, pub color_neutral_background_disabled: String,
pub color_neutral_background_1: String, pub color_neutral_background_1: String,
pub color_neutral_background_1_hover: String, pub color_neutral_background_1_hover: String,
@ -15,6 +16,7 @@ pub struct ColorTheme {
pub color_neutral_background_5: String, pub color_neutral_background_5: String,
pub color_neutral_background_6: String, pub color_neutral_background_6: String,
pub color_neutral_foreground_static_inverted: String,
pub color_neutral_foreground_disabled: String, pub color_neutral_foreground_disabled: String,
pub color_neutral_foreground_1: String, pub color_neutral_foreground_1: String,
pub color_neutral_foreground_1_hover: String, pub color_neutral_foreground_1_hover: String,
@ -100,6 +102,7 @@ pub struct ColorTheme {
impl ColorTheme { impl ColorTheme {
pub fn light() -> Self { pub fn light() -> Self {
Self { Self {
color_neutral_background_static: "#333333".into(),
color_neutral_background_disabled: "#f0f0f0".into(), color_neutral_background_disabled: "#f0f0f0".into(),
color_neutral_background_1: "#ffffff".into(), color_neutral_background_1: "#ffffff".into(),
color_neutral_background_1_hover: "#f5f5f5".into(), color_neutral_background_1_hover: "#f5f5f5".into(),
@ -113,6 +116,7 @@ impl ColorTheme {
color_neutral_background_5: "#ebebeb".into(), color_neutral_background_5: "#ebebeb".into(),
color_neutral_background_6: "#e6e6e6".into(), color_neutral_background_6: "#e6e6e6".into(),
color_neutral_foreground_static_inverted: "#ffffff".into(),
color_neutral_foreground_disabled: "#bdbdbd".into(), color_neutral_foreground_disabled: "#bdbdbd".into(),
color_neutral_foreground_1: "#242424".into(), color_neutral_foreground_1: "#242424".into(),
color_neutral_foreground_1_hover: "#242424".into(), color_neutral_foreground_1_hover: "#242424".into(),
@ -199,6 +203,7 @@ impl ColorTheme {
pub fn dark() -> Self { pub fn dark() -> Self {
Self { Self {
color_neutral_background_static: "#3d3d3d".into(),
color_neutral_background_disabled: "#141414".into(), color_neutral_background_disabled: "#141414".into(),
color_neutral_background_1: "#292929".into(), color_neutral_background_1: "#292929".into(),
color_neutral_background_1_hover: "#3d3d3d".into(), color_neutral_background_1_hover: "#3d3d3d".into(),
@ -212,6 +217,7 @@ impl ColorTheme {
color_neutral_background_5: "#000000".into(), color_neutral_background_5: "#000000".into(),
color_neutral_background_6: "#333333".into(), color_neutral_background_6: "#333333".into(),
color_neutral_foreground_static_inverted: "#ffffff".into(),
color_neutral_foreground_disabled: "#5c5c5c".into(), color_neutral_foreground_disabled: "#5c5c5c".into(),
color_neutral_foreground_1: "#fff".into(), color_neutral_foreground_1: "#fff".into(),
color_neutral_foreground_1_hover: "#fff".into(), color_neutral_foreground_1_hover: "#fff".into(),

View file

@ -260,7 +260,11 @@ fn get_timeout(mut delays: Vec<String>, durations: &Vec<String>) -> u64 {
return 0; return 0;
} }
let s = s.split_at(s.len() - 1).0; let s = if s.ends_with("ms") {
s.split_at(s.len() - 2).0
} else {
s.split_at(s.len() - 1).0
};
(s.parse::<f32>().unwrap_or_default() * 1000.0).floor() as u64 (s.parse::<f32>().unwrap_or_default() * 1000.0).floor() as u64
} }