From 62ce4347747fe17d34c813984ce01ced79db11ce Mon Sep 17 00:00:00 2001 From: luoxiaozero <48741584+luoxiaozero@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:31:17 +0800 Subject: [PATCH] Feat/popover (#57) * feat: add popover component * feat: popover component placement * feat: popover add style * feat: popover placement * feat: popover add click trigger --- demo/src/app.rs | 1 + demo/src/pages/components.rs | 4 + demo/src/pages/mod.rs | 2 + demo/src/pages/popover/mod.rs | 326 ++++++++++++++++ src/components/binder/get_placement_style.rs | 382 +++++++++++++++++-- src/components/binder/mod.rs | 18 +- src/lib.rs | 2 + src/popover/mod.rs | 185 +++++++++ src/popover/popover.css | 140 +++++++ src/popover/theme.rs | 20 + src/theme/mod.rs | 9 +- 11 files changed, 1047 insertions(+), 42 deletions(-) create mode 100644 demo/src/pages/popover/mod.rs create mode 100644 src/popover/mod.rs create mode 100644 src/popover/popover.css create mode 100644 src/popover/theme.rs diff --git a/demo/src/app.rs b/demo/src/app.rs index 9fada06..19a8842 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -81,6 +81,7 @@ fn TheRouter(is_routing: RwSignal) -> impl IntoView { + diff --git a/demo/src/pages/components.rs b/demo/src/pages/components.rs index c5ead86..da98a42 100644 --- a/demo/src/pages/components.rs +++ b/demo/src/pages/components.rs @@ -221,6 +221,10 @@ pub(crate) fn gen_menu_data() -> Vec { value: "modal".into(), label: "Modal".into(), }, + MenuItemOption { + value: "popover".into(), + label: "Popover".into(), + }, MenuItemOption { value: "progress".into(), label: "Progress".into(), diff --git a/demo/src/pages/mod.rs b/demo/src/pages/mod.rs index a813b43..5932a1f 100644 --- a/demo/src/pages/mod.rs +++ b/demo/src/pages/mod.rs @@ -25,6 +25,7 @@ mod message; mod mobile; mod modal; mod nav_bar; +mod popover; mod progress; mod radio; mod select; @@ -70,6 +71,7 @@ pub use message::*; pub use mobile::*; pub use modal::*; pub use nav_bar::*; +pub use popover::*; pub use progress::*; pub use radio::*; pub use select::*; diff --git a/demo/src/pages/popover/mod.rs b/demo/src/pages/popover/mod.rs new file mode 100644 index 0000000..9566e8b --- /dev/null +++ b/demo/src/pages/popover/mod.rs @@ -0,0 +1,326 @@ +use crate::components::{Demo, DemoCode}; +use leptos::*; +use leptos_meta::Style; +use prisms::highlight_str; +use thaw::*; + +#[component] +pub fn PopoverPage() -> impl IntoView { + view! { +
+

"Popover"

+ + + + + + + "Content" + + + + + + "Content" + + + + + {highlight_str!( + r#" + + + + + + "Content" + + + "#, + "rust" + )} + + + +

"Placement"

+ + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + {highlight_str!( + r#" + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + + + + + "Content" + + + + "#, + "rust" + )} + + + +

"Popover Props"

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
"Name""Type""Default""Description"
"class" + "MaybeSignal" + + "Default::default()" + "Addtional classes for the trigger element."
"content_class" + "MaybeSignal" + + "Default::default()" + "Content class of the popover."
"placement" + "PopoverPlacement" + + "PopoverPlacement::Top" + "Popover placement."
"children" + "Children" + "The content inside popover."
+

"Popover Slots"

+ + + + + + + + + + + + + + + +
"Name""Default""Description"
"PopoverTrigger""The element or component that triggers popover."
+
+ } +} diff --git a/src/components/binder/get_placement_style.rs b/src/components/binder/get_placement_style.rs index 58554d4..5bf341a 100644 --- a/src/components/binder/get_placement_style.rs +++ b/src/components/binder/get_placement_style.rs @@ -3,53 +3,365 @@ use web_sys::DomRect; #[derive(Clone)] pub enum FollowerPlacement { - // Top, - // Bottom, - // Left, - // Right, - // TopStart, - // TopEnd, - // LeftStart, - // LeftEnd, - // RightStart, - // RightEnd, + Top, + Bottom, + Left, + Right, + TopStart, + TopEnd, + LeftStart, + LeftEnd, + RightStart, + RightEnd, BottomStart, - // BottomEnd, + BottomEnd, } impl Copy for FollowerPlacement {} -pub fn get_follower_placement_style( +impl FollowerPlacement { + pub fn as_str(&self) -> &'static str { + match self { + Self::Top => "top", + Self::Bottom => "bottom", + Self::Left => "left", + Self::Right => "right", + Self::TopStart => "top-start", + Self::TopEnd => "top-end", + Self::LeftStart => "left-start", + Self::LeftEnd => "left-end", + Self::RightStart => "right-start", + Self::RightEnd => "right-end", + Self::BottomStart => "bottom-start", + Self::BottomEnd => "bottom-end", + } + } +} + +pub struct FollowerPlacementOffset { + pub top: f64, + pub left: f64, + pub transform: String, + pub placement: FollowerPlacement, +} + +pub fn get_follower_placement_offset( placement: FollowerPlacement, target_rect: DomRect, follower_rect: DomRect, -) -> Option { - // TODO: Implements FollowerPlacement more properties - _ = placement; - let mut style = String::new(); - let left = target_rect.x(); - let top = { - let follower_height = follower_rect.height(); - let target_y = target_rect.y(); - let target_height = target_rect.height(); - let top = target_y + target_height; +) -> Option { + match placement { + FollowerPlacement::Top => { + let left = target_rect.x() + target_rect.width() / 2.0; + let (top, placement) = { + let follower_height = follower_rect.height(); + let target_y = target_rect.y(); + let target_height = target_rect.height(); + let top = target_y - follower_height; - let Some(inner_height) = window_inner_height() else { - return None; - }; + let Some(inner_height) = window_inner_height() else { + return None; + }; - if top + follower_height > inner_height && target_y - follower_height >= 0.0 { - target_y - follower_height - } else { - top + if top < 0.0 && target_y + target_height + follower_height <= inner_height { + (target_y + target_height, FollowerPlacement::Bottom) + } else { + (top, FollowerPlacement::Top) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::from("translateX(-50%)"), + placement, + }) } + FollowerPlacement::TopStart => { + let left = target_rect.x(); + let (top, placement) = { + let follower_height = follower_rect.height(); + let target_y = target_rect.y(); + let target_height = target_rect.height(); + let top = target_y - follower_height; + + let Some(inner_height) = window_inner_height() else { + return None; + }; + + if top < 0.0 && target_y + target_height + follower_height <= inner_height { + (target_y + target_height, FollowerPlacement::BottomStart) + } else { + (top, FollowerPlacement::TopStart) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::new(), + placement, + }) + } + FollowerPlacement::TopEnd => { + let left = target_rect.x() + target_rect.width(); + let (top, placement) = { + let follower_height = follower_rect.height(); + let target_y = target_rect.y(); + let target_height = target_rect.height(); + let top = target_y - follower_height; + + let Some(inner_height) = window_inner_height() else { + return None; + }; + + if top < 0.0 && target_y + target_height + follower_height <= inner_height { + (target_y + target_height, FollowerPlacement::BottomEnd) + } else { + (top, FollowerPlacement::TopEnd) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::from("translateX(-100%)"), + placement, + }) + } + FollowerPlacement::Left => { + let top = target_rect.y() + target_rect.height() / 2.0; + let (left, placement) = { + let follower_width = follower_rect.width(); + let target_x = target_rect.x(); + let target_width = target_rect.width(); + let left = target_x - follower_width; + + let Some(inner_width) = window_inner_width() else { + return None; + }; + + if left < 0.0 && target_x + target_width + follower_width > inner_width { + (target_x + follower_width, FollowerPlacement::Right) + } else { + (left, FollowerPlacement::Left) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::from("translateY(-50%)"), + placement, + }) + } + FollowerPlacement::LeftStart => { + let top = target_rect.y(); + let (left, placement) = { + let follower_width = follower_rect.width(); + let target_x = target_rect.x(); + let target_width = target_rect.width(); + let left = target_x - follower_width; + + let Some(inner_width) = window_inner_width() else { + return None; + }; + + if left < 0.0 && target_x + target_width + follower_width > inner_width { + (target_x + follower_width, FollowerPlacement::RightStart) + } else { + (left, FollowerPlacement::LeftStart) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::new(), + placement, + }) + } + FollowerPlacement::LeftEnd => { + let top = target_rect.y() + target_rect.height(); + let (left, placement) = { + let follower_width = follower_rect.width(); + let target_x = target_rect.x(); + let target_width = target_rect.width(); + let left = target_x - follower_width; + + let Some(inner_width) = window_inner_width() else { + return None; + }; + + if left < 0.0 && target_x + target_width + follower_width > inner_width { + (target_x + follower_width, FollowerPlacement::RightEnd) + } else { + (left, FollowerPlacement::LeftEnd) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::from("translateY(-100%)"), + placement, + }) + } + FollowerPlacement::Right => { + let top = target_rect.y() + target_rect.height() / 2.0; + let (left, placement) = { + let follower_width = follower_rect.width(); + let target_x = target_rect.x(); + let target_width = target_rect.width(); + let left = target_x + target_width; + + let Some(inner_width) = window_inner_width() else { + return None; + }; + + if left + follower_width > inner_width && target_x - follower_width >= 0.0 { + (target_x - follower_width, FollowerPlacement::Left) + } else { + (left, FollowerPlacement::Right) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::from("translateY(-50%)"), + placement, + }) + } + FollowerPlacement::RightStart => { + let top = target_rect.y(); + let (left, placement) = { + let follower_width = follower_rect.width(); + let target_x = target_rect.x(); + let target_width = target_rect.width(); + let left = target_x + target_width; + + let Some(inner_width) = window_inner_width() else { + return None; + }; + + if left + follower_width > inner_width && target_x - follower_width >= 0.0 { + (target_x - follower_width, FollowerPlacement::LeftStart) + } else { + (left, FollowerPlacement::RightStart) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::new(), + placement, + }) + } + FollowerPlacement::RightEnd => { + let top = target_rect.y() + target_rect.height(); + let (left, placement) = { + let follower_width = follower_rect.width(); + let target_x = target_rect.x(); + let target_width = target_rect.width(); + let left = target_x + target_width; + + let Some(inner_width) = window_inner_width() else { + return None; + }; + + if left + follower_width > inner_width && target_x - follower_width >= 0.0 { + (target_x - follower_width, FollowerPlacement::LeftEnd) + } else { + (left, FollowerPlacement::RightEnd) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::from("translateY(-100%)"), + placement, + }) + } + FollowerPlacement::Bottom => { + let left = target_rect.x() + target_rect.width() / 2.0; + let (top, placement) = { + let follower_height = follower_rect.height(); + let target_y = target_rect.y(); + let target_height = target_rect.height(); + let top = target_y + target_height; + + let Some(inner_height) = window_inner_height() else { + return None; + }; + + if top + follower_height > inner_height && target_y - follower_height >= 0.0 { + (target_y - follower_height, FollowerPlacement::Top) + } else { + (top, FollowerPlacement::Bottom) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::from("translateX(-50%)"), + placement, + }) + } + FollowerPlacement::BottomStart => { + let left = target_rect.x(); + let (top, placement) = { + let follower_height = follower_rect.height(); + let target_y = target_rect.y(); + let target_height = target_rect.height(); + let top = target_y + target_height; + + let Some(inner_height) = window_inner_height() else { + return None; + }; + + if top + follower_height > inner_height && target_y - follower_height >= 0.0 { + (target_y - follower_height, FollowerPlacement::TopStart) + } else { + (top, FollowerPlacement::BottomStart) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::new(), + placement, + }) + } + FollowerPlacement::BottomEnd => { + let left = target_rect.x() + target_rect.width(); + let (top, placement) = { + let follower_height = follower_rect.height(); + let target_y = target_rect.y(); + let target_height = target_rect.height(); + let top = target_y + target_height; + + let Some(inner_height) = window_inner_height() else { + return None; + }; + + if top + follower_height > inner_height && target_y - follower_height >= 0.0 { + (target_y - follower_height, FollowerPlacement::TopEnd) + } else { + (top, FollowerPlacement::BottomEnd) + } + }; + Some(FollowerPlacementOffset { + top, + left, + transform: String::from("translateX(-100%)"), + placement, + }) + } + } +} + +fn window_inner_width() -> Option { + let Ok(inner_width) = window().inner_width() else { + return None; }; - - style.push_str(&format!( - "transform: translateX({left}px) translateY({top}px);" - )); - - Some(style) + let Some(inner_width) = inner_width.as_f64() else { + return None; + }; + Some(inner_width) } fn window_inner_height() -> Option { diff --git a/src/components/binder/mod.rs b/src/components/binder/mod.rs index cc91a62..e6809e8 100644 --- a/src/components/binder/mod.rs +++ b/src/components/binder/mod.rs @@ -5,8 +5,8 @@ use crate::{ utils::{add_event_listener, EventListenerHandle}, utils::{mount_style, with_hydration_off}, }; -use get_placement_style::get_follower_placement_style; pub use get_placement_style::FollowerPlacement; +use get_placement_style::{get_follower_placement_offset, FollowerPlacementOffset}; use leptos::{ html::{AnyElement, ElementDescriptor, ToHtmlElement}, leptos_dom::helpers::WindowListenerHandle, @@ -19,6 +19,7 @@ pub struct Follower { show: MaybeSignal, #[prop(optional)] width: Option, + #[prop(into)] placement: FollowerPlacement, children: Children, } @@ -145,6 +146,7 @@ fn FollowerContainer( ) -> impl IntoView { let content_ref = create_node_ref::(); let content_style = create_rw_signal(String::new()); + let placement_str = create_rw_signal(placement.as_str()); let sync_position: Callback<()> = Callback::new(move |_| { let Some(content_ref) = content_ref.get_untracked() else { return; @@ -162,10 +164,17 @@ fn FollowerContainer( }; style.push_str(&width); } - if let Some(placement_style) = - get_follower_placement_style(placement, target_rect, content_rect) + if let Some(FollowerPlacementOffset { + top, + left, + transform, + placement, + }) = get_follower_placement_offset(placement, target_rect, content_rect) { - style.push_str(&placement_style); + placement_str.set(placement.as_str()); + style.push_str(&format!( + "transform: translateX({left}px) translateY({top}px) {transform};" + )); } else { logging::error!("Thaw-Binder: get_follower_placement_style return None"); } @@ -201,6 +210,7 @@ fn FollowerContainer( .child( html::div() .classes("thaw-binder-follower-content") + .attr("data-thaw-placement", move || placement_str.get()) .node_ref(content_ref) .attr("style", move || content_style.get()) .child(children()), diff --git a/src/lib.rs b/src/lib.rs index 6934546..f86b994 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ mod menu; mod message; pub mod mobile; mod modal; +mod popover; mod progress; mod radio; mod select; @@ -66,6 +67,7 @@ pub use loading_bar::*; pub use menu::*; pub use message::*; pub use modal::*; +pub use popover::*; pub use progress::*; pub use radio::*; pub use select::*; diff --git a/src/popover/mod.rs b/src/popover/mod.rs new file mode 100644 index 0000000..14b80f1 --- /dev/null +++ b/src/popover/mod.rs @@ -0,0 +1,185 @@ +mod theme; + +use std::time::Duration; + +use crate::{ + components::{Binder, Follower, FollowerPlacement}, + use_theme, + utils::{add_event_listener, dyn_classes, mount_style, ssr_class}, + Theme, +}; +use leptos::{leptos_dom::helpers::TimeoutHandle, *}; +pub use theme::PopoverTheme; + +#[slot] +pub struct PopoverTrigger { + children: Children, +} + +#[component] +pub fn Popover( + #[prop(optional, into)] class: MaybeSignal, + #[prop(optional, into)] content_class: MaybeSignal, + #[prop(optional)] trigger_type: PopoverTriggerType, + popover_trigger: PopoverTrigger, + #[prop(optional)] placement: PopoverPlacement, + children: Children, +) -> impl IntoView { + mount_style("popover", include_str!("./popover.css")); + let theme = use_theme(Theme::light); + let css_vars = create_memo(move |_| { + let mut css_vars = String::new(); + theme.with(|theme| { + css_vars.push_str(&format!( + "--thaw-background-color: {};", + theme.time_picker.panel_background_color + )); + }); + css_vars + }); + let popover_ref = create_node_ref::(); + let target_ref = create_node_ref::(); + let is_show_popover = create_rw_signal(false); + let show_popover_handle = store_value(None::); + + let on_mouse_enter = move |_| { + if trigger_type != PopoverTriggerType::Hover { + return; + } + show_popover_handle.update_value(|handle| { + if let Some(handle) = handle.take() { + handle.clear(); + } + }); + is_show_popover.set(true); + }; + let on_mouse_leave = move |_| { + if trigger_type != PopoverTriggerType::Hover { + return; + } + show_popover_handle.update_value(|handle| { + if let Some(handle) = handle.take() { + handle.clear(); + } + *handle = set_timeout_with_handle( + move || { + is_show_popover.set(false); + }, + Duration::from_millis(100), + ) + .ok(); + }); + }; + #[cfg(any(feature = "csr", feature = "hydrate"))] + { + let handle = window_event_listener(ev::click, move |ev| { + use leptos::wasm_bindgen::__rt::IntoJsResult; + if trigger_type != PopoverTriggerType::Click { + return; + } + let el = ev.target(); + let mut el: Option = + el.into_js_result().map_or(None, |el| Some(el.into())); + let body = document().body().unwrap(); + while let Some(current_el) = el { + if current_el == *body { + break; + }; + let Some(popover_el) = popover_ref.get_untracked() else { + break; + }; + if current_el == ***popover_el { + return; + } + el = current_el.parent_element(); + } + is_show_popover.set(false); + }); + on_cleanup(move || handle.remove()); + } + + target_ref.on_load(move |target_el| { + add_event_listener(target_el.into_any(), ev::click, move |event| { + if trigger_type != PopoverTriggerType::Click { + return; + } + event.stop_propagation(); + is_show_popover.update(|show| *show = !*show); + }); + }); + + let ssr_class = ssr_class(&class); + view! { + +
+ {(popover_trigger.children)()} +
+ +
+
{children()}
+
+
+
+
+
+
+ } +} + +#[derive(Default, PartialEq, Clone)] +pub enum PopoverTriggerType { + #[default] + Hover, + Click, +} + +impl Copy for PopoverTriggerType {} + +#[derive(Default)] +pub enum PopoverPlacement { + #[default] + Top, + Bottom, + Left, + Right, + TopStart, + TopEnd, + LeftStart, + LeftEnd, + RightStart, + RightEnd, + BottomStart, + BottomEnd, +} + +impl From for FollowerPlacement { + fn from(value: PopoverPlacement) -> Self { + match value { + PopoverPlacement::Top => Self::Top, + PopoverPlacement::Bottom => Self::Bottom, + PopoverPlacement::Left => Self::Left, + PopoverPlacement::Right => Self::Right, + PopoverPlacement::TopStart => Self::TopStart, + PopoverPlacement::TopEnd => Self::TopEnd, + PopoverPlacement::LeftStart => Self::LeftStart, + PopoverPlacement::LeftEnd => Self::LeftEnd, + PopoverPlacement::RightStart => Self::RightStart, + PopoverPlacement::RightEnd => Self::RightEnd, + PopoverPlacement::BottomStart => Self::BottomStart, + PopoverPlacement::BottomEnd => Self::BottomEnd, + } + } +} diff --git a/src/popover/popover.css b/src/popover/popover.css new file mode 100644 index 0000000..57adf03 --- /dev/null +++ b/src/popover/popover.css @@ -0,0 +1,140 @@ +.thaw-popover { + position: relative; + padding: 8px 14px; + background-color: var(--thaw-background-color); + border-radius: 3px; +} + +.thaw-popover-trigger { + display: inline-block; +} + +.thaw-popover__angle-container { + position: absolute; +} + +.thaw-popover__angle { + position: absolute; + background: var(--thaw-background-color); + width: 10px; + height: 10px; +} + +[data-thaw-placement="top-start"] > .thaw-popover, +[data-thaw-placement="top-end"] > .thaw-popover, +[data-thaw-placement="top"] > .thaw-popover { + margin-bottom: 10px; + 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); +} + +[data-thaw-placement="top-start"] .thaw-popover__angle-container, +[data-thaw-placement="top-end"] .thaw-popover__angle-container, +[data-thaw-placement="top"] .thaw-popover__angle-container { + width: 100%; + height: 10px; + bottom: -10px; + left: 0; + right: 0; +} + +[data-thaw-placement="top-start"] .thaw-popover__angle, +[data-thaw-placement="top-end"] .thaw-popover__angle, +[data-thaw-placement="top"] .thaw-popover__angle { + left: 50%; + transform: rotate(45deg) translateX(-7px); +} + +[data-thaw-placement="bottom-start"] > .thaw-popover, +[data-thaw-placement="bottom-end"] > .thaw-popover, +[data-thaw-placement="bottom"] > .thaw-popover { + margin-top: 10px; + 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); +} + +[data-thaw-placement="bottom-start"] .thaw-popover__angle-container, +[data-thaw-placement="bottom-end"] .thaw-popover__angle-container, +[data-thaw-placement="bottom"] .thaw-popover__angle-container { + width: 100%; + height: 10px; + top: -10px; + left: 0; + right: 0; +} + +[data-thaw-placement="bottom-start"] .thaw-popover__angle, +[data-thaw-placement="bottom-end"] .thaw-popover__angle, +[data-thaw-placement="bottom"] .thaw-popover__angle { + left: 50%; + transform: rotate(45deg) translateY(7px); +} + +[data-thaw-placement="left-start"] > .thaw-popover, +[data-thaw-placement="left-end"] > .thaw-popover, +[data-thaw-placement="left"] > .thaw-popover { + 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__angle-container, +[data-thaw-placement="left-end"] .thaw-popover__angle-container, +[data-thaw-placement="left"] .thaw-popover__angle-container { + width: 10px; + height: 100%; + right: -10px; + top: 0; + bottom: 0; +} + +[data-thaw-placement="left-start"] .thaw-popover__angle, +[data-thaw-placement="left-end"] .thaw-popover__angle, +[data-thaw-placement="left"] .thaw-popover__angle { + top: 50%; + transform: rotate(45deg) translateX(-7px); +} + +[data-thaw-placement="right-start"] > .thaw-popover, +[data-thaw-placement="right-end"] > .thaw-popover, +[data-thaw-placement="right"] > .thaw-popover { + 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__angle-container, +[data-thaw-placement="right-end"] .thaw-popover__angle-container, +[data-thaw-placement="right"] .thaw-popover__angle-container { + width: 10px; + height: 100%; + left: -10px; + top: 0; + bottom: 0; +} + +[data-thaw-placement="right-start"] .thaw-popover__angle, +[data-thaw-placement="right-end"] .thaw-popover__angle, +[data-thaw-placement="right"] .thaw-popover__angle { + top: 50%; + transform: rotate(45deg) translateY(-7px); +} + +[data-thaw-placement="bottom-start"] .thaw-popover__angle, +[data-thaw-placement="top-start"] .thaw-popover__angle { + left: 16px; +} +[data-thaw-placement="bottom-end"] .thaw-popover__angle, +[data-thaw-placement="top-end"] .thaw-popover__angle { + left: initial; + right: 7px; +} +[data-thaw-placement="right-start"] .thaw-popover__angle, +[data-thaw-placement="left-start"] .thaw-popover__angle { + top: 16px; +} +[data-thaw-placement="right-end"] .thaw-popover__angle, +[data-thaw-placement="left-end"] .thaw-popover__angle { + top: initial; + bottom: 7px; +} diff --git a/src/popover/theme.rs b/src/popover/theme.rs new file mode 100644 index 0000000..af03bbd --- /dev/null +++ b/src/popover/theme.rs @@ -0,0 +1,20 @@ +use crate::theme::ThemeMethod; + +#[derive(Clone)] +pub struct PopoverTheme { + pub background_color: String, +} + +impl ThemeMethod for PopoverTheme { + fn light() -> Self { + Self { + background_color: "#fff".into(), + } + } + + fn dark() -> Self { + Self { + background_color: "#48484e".into(), + } + } +} diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 4e435c3..e9605b0 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -4,9 +4,9 @@ use self::common::CommonTheme; use crate::{ mobile::{NavBarTheme, TabbarTheme}, AlertTheme, AutoCompleteTheme, AvatarTheme, BreadcrumbTheme, ButtonTheme, CalendarTheme, - ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme, MessageTheme, ProgressTheme, - SelectTheme, SkeletionTheme, SliderTheme, SpinnerTheme, SwitchTheme, TableTheme, TagTheme, - TimePickerTheme, TypographyTheme, UploadTheme, + ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme, MessageTheme, PopoverTheme, + ProgressTheme, SelectTheme, SkeletionTheme, SliderTheme, SpinnerTheme, SwitchTheme, TableTheme, + TagTheme, TimePickerTheme, TypographyTheme, UploadTheme, }; use leptos::*; @@ -43,6 +43,7 @@ pub struct Theme { pub calendar: CalendarTheme, pub time_picker: TimePickerTheme, pub date_picker: DatePickerTheme, + pub popover: PopoverTheme, } impl Theme { @@ -74,6 +75,7 @@ impl Theme { calendar: CalendarTheme::light(), time_picker: TimePickerTheme::light(), date_picker: DatePickerTheme::light(), + popover: PopoverTheme::light(), } } pub fn dark() -> Self { @@ -104,6 +106,7 @@ impl Theme { calendar: CalendarTheme::dark(), time_picker: TimePickerTheme::dark(), date_picker: DatePickerTheme::dark(), + popover: PopoverTheme::dark(), } } }