mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-23 06:19:22 -05:00
Feat/popover (#57)
* feat: add popover component * feat: popover component placement * feat: popover add style * feat: popover placement * feat: popover add click trigger
This commit is contained in:
parent
46af07746c
commit
62ce434774
11 changed files with 1047 additions and 42 deletions
|
@ -81,6 +81,7 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
||||||
<Route path="/calendar" view=CalendarPage/>
|
<Route path="/calendar" view=CalendarPage/>
|
||||||
<Route path="/time-picker" view=TimePickerPage/>
|
<Route path="/time-picker" view=TimePickerPage/>
|
||||||
<Route path="/date-picker" view=DatePickerPage/>
|
<Route path="/date-picker" view=DatePickerPage/>
|
||||||
|
<Route path="/popover" view=PopoverPage/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
||||||
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
||||||
|
|
|
@ -221,6 +221,10 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
||||||
value: "modal".into(),
|
value: "modal".into(),
|
||||||
label: "Modal".into(),
|
label: "Modal".into(),
|
||||||
},
|
},
|
||||||
|
MenuItemOption {
|
||||||
|
value: "popover".into(),
|
||||||
|
label: "Popover".into(),
|
||||||
|
},
|
||||||
MenuItemOption {
|
MenuItemOption {
|
||||||
value: "progress".into(),
|
value: "progress".into(),
|
||||||
label: "Progress".into(),
|
label: "Progress".into(),
|
||||||
|
|
|
@ -25,6 +25,7 @@ mod message;
|
||||||
mod mobile;
|
mod mobile;
|
||||||
mod modal;
|
mod modal;
|
||||||
mod nav_bar;
|
mod nav_bar;
|
||||||
|
mod popover;
|
||||||
mod progress;
|
mod progress;
|
||||||
mod radio;
|
mod radio;
|
||||||
mod select;
|
mod select;
|
||||||
|
@ -70,6 +71,7 @@ pub use message::*;
|
||||||
pub use mobile::*;
|
pub use mobile::*;
|
||||||
pub use modal::*;
|
pub use modal::*;
|
||||||
pub use nav_bar::*;
|
pub use nav_bar::*;
|
||||||
|
pub use popover::*;
|
||||||
pub use progress::*;
|
pub use progress::*;
|
||||||
pub use radio::*;
|
pub use radio::*;
|
||||||
pub use select::*;
|
pub use select::*;
|
||||||
|
|
326
demo/src/pages/popover/mod.rs
Normal file
326
demo/src/pages/popover/mod.rs
Normal file
|
@ -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! {
|
||||||
|
<div style="width: 896px; margin: 0 auto;">
|
||||||
|
<h1>"Popover"</h1>
|
||||||
|
<Demo>
|
||||||
|
<Space>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Hover"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
<Popover trigger_type=PopoverTriggerType::Click>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Click"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</Space>
|
||||||
|
<DemoCode slot>
|
||||||
|
|
||||||
|
{highlight_str!(
|
||||||
|
r#"
|
||||||
|
<Space>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>
|
||||||
|
"Hover"
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</Space>
|
||||||
|
"#,
|
||||||
|
"rust"
|
||||||
|
)}
|
||||||
|
|
||||||
|
</DemoCode>
|
||||||
|
</Demo>
|
||||||
|
<h3>"Placement"</h3>
|
||||||
|
<Style>".demo-popover .thaw-button { width: 100% } .demo-popover .thaw-popover-trigger { display: block }"</Style>
|
||||||
|
<Demo>
|
||||||
|
<Grid x_gap=8 y_gap=8 cols=3 class="demo-popover">
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::TopStart>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Top Start"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::Top>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Top"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::TopEnd>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Top End"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::LeftStart>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Left Start"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem offset=1>
|
||||||
|
<Popover placement=PopoverPlacement::RightStart>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Right Start"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::Left>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Left"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem offset=1>
|
||||||
|
<Popover placement=PopoverPlacement::Right>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Right"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::LeftEnd>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Left End"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem offset=1>
|
||||||
|
<Popover placement=PopoverPlacement::RightEnd>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Right End"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::BottomStart>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Bottom Start"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::Bottom>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Bottom"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::BottomEnd>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Bottom End"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
</Grid>
|
||||||
|
<DemoCode slot>
|
||||||
|
|
||||||
|
{highlight_str!(
|
||||||
|
r#"
|
||||||
|
<Grid x_gap=8 y_gap=8 cols=3 class="demo-popover">
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::TopStart>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Top Start"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::Top>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Top"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::TopEnd>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Top End"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::LeftStart>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Left Start"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem offset=1>
|
||||||
|
<Popover placement=PopoverPlacement::RightStart>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Right Start"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::Left>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Left"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem offset=1>
|
||||||
|
<Popover placement=PopoverPlacement::Right>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Right"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::LeftEnd>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Left End"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem offset=1>
|
||||||
|
<Popover placement=PopoverPlacement::RightEnd>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Right End"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::BottomStart>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Bottom Start"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::Bottom>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Bottom"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<Popover placement=PopoverPlacement::BottomEnd>
|
||||||
|
<PopoverTrigger slot>
|
||||||
|
<Button>"Bottom End"</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
"Content"
|
||||||
|
</Popover>
|
||||||
|
</GridItem>
|
||||||
|
</Grid>
|
||||||
|
"#,
|
||||||
|
"rust"
|
||||||
|
)}
|
||||||
|
|
||||||
|
</DemoCode>
|
||||||
|
</Demo>
|
||||||
|
<h3>"Popover Props"</h3>
|
||||||
|
<Table single_column=true>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>"Name"</th>
|
||||||
|
<th>"Type"</th>
|
||||||
|
<th>"Default"</th>
|
||||||
|
<th>"Description"</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>"class"</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"MaybeSignal<String>"</Text>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"Default::default()"</Text>
|
||||||
|
</td>
|
||||||
|
<td>"Addtional classes for the trigger element."</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>"content_class"</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"MaybeSignal<String>"</Text>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"Default::default()"</Text>
|
||||||
|
</td>
|
||||||
|
<td>"Content class of the popover."</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>"placement"</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"PopoverPlacement"</Text>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"PopoverPlacement::Top"</Text>
|
||||||
|
</td>
|
||||||
|
<td>"Popover placement."</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>"children"</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"Children"</Text>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td>"The content inside popover."</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
<h3>"Popover Slots"</h3>
|
||||||
|
<Table single_column=true>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>"Name"</th>
|
||||||
|
<th>"Default"</th>
|
||||||
|
<th>"Description"</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>"PopoverTrigger"</td>
|
||||||
|
<td></td>
|
||||||
|
<td>"The element or component that triggers popover."</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,53 +3,365 @@ use web_sys::DomRect;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum FollowerPlacement {
|
pub enum FollowerPlacement {
|
||||||
// Top,
|
Top,
|
||||||
// Bottom,
|
Bottom,
|
||||||
// Left,
|
Left,
|
||||||
// Right,
|
Right,
|
||||||
// TopStart,
|
TopStart,
|
||||||
// TopEnd,
|
TopEnd,
|
||||||
// LeftStart,
|
LeftStart,
|
||||||
// LeftEnd,
|
LeftEnd,
|
||||||
// RightStart,
|
RightStart,
|
||||||
// RightEnd,
|
RightEnd,
|
||||||
BottomStart,
|
BottomStart,
|
||||||
// BottomEnd,
|
BottomEnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Copy for FollowerPlacement {}
|
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,
|
placement: FollowerPlacement,
|
||||||
target_rect: DomRect,
|
target_rect: DomRect,
|
||||||
follower_rect: DomRect,
|
follower_rect: DomRect,
|
||||||
) -> Option<String> {
|
) -> Option<FollowerPlacementOffset> {
|
||||||
// TODO: Implements FollowerPlacement more properties
|
match placement {
|
||||||
_ = placement;
|
FollowerPlacement::Top => {
|
||||||
let mut style = String::new();
|
let left = target_rect.x() + target_rect.width() / 2.0;
|
||||||
let left = target_rect.x();
|
let (top, placement) = {
|
||||||
let top = {
|
let follower_height = follower_rect.height();
|
||||||
let follower_height = follower_rect.height();
|
let target_y = target_rect.y();
|
||||||
let target_y = target_rect.y();
|
let target_height = target_rect.height();
|
||||||
let target_height = target_rect.height();
|
let top = target_y - follower_height;
|
||||||
let top = target_y + target_height;
|
|
||||||
|
|
||||||
let Some(inner_height) = window_inner_height() else {
|
let Some(inner_height) = window_inner_height() else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if top + follower_height > inner_height && target_y - follower_height >= 0.0 {
|
if top < 0.0 && target_y + target_height + follower_height <= inner_height {
|
||||||
target_y - follower_height
|
(target_y + target_height, FollowerPlacement::Bottom)
|
||||||
} else {
|
} else {
|
||||||
top
|
(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<f64> {
|
||||||
|
let Ok(inner_width) = window().inner_width() else {
|
||||||
|
return None;
|
||||||
};
|
};
|
||||||
|
let Some(inner_width) = inner_width.as_f64() else {
|
||||||
style.push_str(&format!(
|
return None;
|
||||||
"transform: translateX({left}px) translateY({top}px);"
|
};
|
||||||
));
|
Some(inner_width)
|
||||||
|
|
||||||
Some(style)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_inner_height() -> Option<f64> {
|
fn window_inner_height() -> Option<f64> {
|
||||||
|
|
|
@ -5,8 +5,8 @@ use crate::{
|
||||||
utils::{add_event_listener, EventListenerHandle},
|
utils::{add_event_listener, EventListenerHandle},
|
||||||
utils::{mount_style, with_hydration_off},
|
utils::{mount_style, with_hydration_off},
|
||||||
};
|
};
|
||||||
use get_placement_style::get_follower_placement_style;
|
|
||||||
pub use get_placement_style::FollowerPlacement;
|
pub use get_placement_style::FollowerPlacement;
|
||||||
|
use get_placement_style::{get_follower_placement_offset, FollowerPlacementOffset};
|
||||||
use leptos::{
|
use leptos::{
|
||||||
html::{AnyElement, ElementDescriptor, ToHtmlElement},
|
html::{AnyElement, ElementDescriptor, ToHtmlElement},
|
||||||
leptos_dom::helpers::WindowListenerHandle,
|
leptos_dom::helpers::WindowListenerHandle,
|
||||||
|
@ -19,6 +19,7 @@ pub struct Follower {
|
||||||
show: MaybeSignal<bool>,
|
show: MaybeSignal<bool>,
|
||||||
#[prop(optional)]
|
#[prop(optional)]
|
||||||
width: Option<FollowerWidth>,
|
width: Option<FollowerWidth>,
|
||||||
|
#[prop(into)]
|
||||||
placement: FollowerPlacement,
|
placement: FollowerPlacement,
|
||||||
children: Children,
|
children: Children,
|
||||||
}
|
}
|
||||||
|
@ -145,6 +146,7 @@ fn FollowerContainer<El: ElementDescriptor + Clone + 'static>(
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let content_ref = create_node_ref::<html::Div>();
|
let content_ref = create_node_ref::<html::Div>();
|
||||||
let content_style = create_rw_signal(String::new());
|
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 sync_position: Callback<()> = Callback::new(move |_| {
|
||||||
let Some(content_ref) = content_ref.get_untracked() else {
|
let Some(content_ref) = content_ref.get_untracked() else {
|
||||||
return;
|
return;
|
||||||
|
@ -162,10 +164,17 @@ fn FollowerContainer<El: ElementDescriptor + Clone + 'static>(
|
||||||
};
|
};
|
||||||
style.push_str(&width);
|
style.push_str(&width);
|
||||||
}
|
}
|
||||||
if let Some(placement_style) =
|
if let Some(FollowerPlacementOffset {
|
||||||
get_follower_placement_style(placement, target_rect, content_rect)
|
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 {
|
} else {
|
||||||
logging::error!("Thaw-Binder: get_follower_placement_style return None");
|
logging::error!("Thaw-Binder: get_follower_placement_style return None");
|
||||||
}
|
}
|
||||||
|
@ -201,6 +210,7 @@ fn FollowerContainer<El: ElementDescriptor + Clone + 'static>(
|
||||||
.child(
|
.child(
|
||||||
html::div()
|
html::div()
|
||||||
.classes("thaw-binder-follower-content")
|
.classes("thaw-binder-follower-content")
|
||||||
|
.attr("data-thaw-placement", move || placement_str.get())
|
||||||
.node_ref(content_ref)
|
.node_ref(content_ref)
|
||||||
.attr("style", move || content_style.get())
|
.attr("style", move || content_style.get())
|
||||||
.child(children()),
|
.child(children()),
|
||||||
|
|
|
@ -24,6 +24,7 @@ mod menu;
|
||||||
mod message;
|
mod message;
|
||||||
pub mod mobile;
|
pub mod mobile;
|
||||||
mod modal;
|
mod modal;
|
||||||
|
mod popover;
|
||||||
mod progress;
|
mod progress;
|
||||||
mod radio;
|
mod radio;
|
||||||
mod select;
|
mod select;
|
||||||
|
@ -66,6 +67,7 @@ pub use loading_bar::*;
|
||||||
pub use menu::*;
|
pub use menu::*;
|
||||||
pub use message::*;
|
pub use message::*;
|
||||||
pub use modal::*;
|
pub use modal::*;
|
||||||
|
pub use popover::*;
|
||||||
pub use progress::*;
|
pub use progress::*;
|
||||||
pub use radio::*;
|
pub use radio::*;
|
||||||
pub use select::*;
|
pub use select::*;
|
||||||
|
|
185
src/popover/mod.rs
Normal file
185
src/popover/mod.rs
Normal file
|
@ -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<String>,
|
||||||
|
#[prop(optional, into)] content_class: MaybeSignal<String>,
|
||||||
|
#[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::<html::Div>();
|
||||||
|
let target_ref = create_node_ref::<html::Div>();
|
||||||
|
let is_show_popover = create_rw_signal(false);
|
||||||
|
let show_popover_handle = store_value(None::<TimeoutHandle>);
|
||||||
|
|
||||||
|
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<web_sys::Element> =
|
||||||
|
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! {
|
||||||
|
<Binder target_ref>
|
||||||
|
<div
|
||||||
|
class=ssr_class
|
||||||
|
use:dyn_classes=class
|
||||||
|
class="thaw-popover-trigger"
|
||||||
|
ref=target_ref
|
||||||
|
on:mouseenter=on_mouse_enter
|
||||||
|
on:mouseleave=on_mouse_leave
|
||||||
|
>
|
||||||
|
{(popover_trigger.children)()}
|
||||||
|
</div>
|
||||||
|
<Follower slot show=is_show_popover placement>
|
||||||
|
<div
|
||||||
|
class="thaw-popover"
|
||||||
|
style=move || css_vars.get()
|
||||||
|
ref=popover_ref
|
||||||
|
on:mouseenter=on_mouse_enter
|
||||||
|
on:mouseleave=on_mouse_leave
|
||||||
|
>
|
||||||
|
<div class=move || content_class.get()>{children()}</div>
|
||||||
|
<div class="thaw-popover__angle-container">
|
||||||
|
<div class="thaw-popover__angle"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Follower>
|
||||||
|
</Binder>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<PopoverPlacement> 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
src/popover/popover.css
Normal file
140
src/popover/popover.css
Normal file
|
@ -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;
|
||||||
|
}
|
20
src/popover/theme.rs
Normal file
20
src/popover/theme.rs
Normal file
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,9 @@ use self::common::CommonTheme;
|
||||||
use crate::{
|
use crate::{
|
||||||
mobile::{NavBarTheme, TabbarTheme},
|
mobile::{NavBarTheme, TabbarTheme},
|
||||||
AlertTheme, AutoCompleteTheme, AvatarTheme, BreadcrumbTheme, ButtonTheme, CalendarTheme,
|
AlertTheme, AutoCompleteTheme, AvatarTheme, BreadcrumbTheme, ButtonTheme, CalendarTheme,
|
||||||
ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme, MessageTheme, ProgressTheme,
|
ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme, MessageTheme, PopoverTheme,
|
||||||
SelectTheme, SkeletionTheme, SliderTheme, SpinnerTheme, SwitchTheme, TableTheme, TagTheme,
|
ProgressTheme, SelectTheme, SkeletionTheme, SliderTheme, SpinnerTheme, SwitchTheme, TableTheme,
|
||||||
TimePickerTheme, TypographyTheme, UploadTheme,
|
TagTheme, TimePickerTheme, TypographyTheme, UploadTheme,
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ pub struct Theme {
|
||||||
pub calendar: CalendarTheme,
|
pub calendar: CalendarTheme,
|
||||||
pub time_picker: TimePickerTheme,
|
pub time_picker: TimePickerTheme,
|
||||||
pub date_picker: DatePickerTheme,
|
pub date_picker: DatePickerTheme,
|
||||||
|
pub popover: PopoverTheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
|
@ -74,6 +75,7 @@ impl Theme {
|
||||||
calendar: CalendarTheme::light(),
|
calendar: CalendarTheme::light(),
|
||||||
time_picker: TimePickerTheme::light(),
|
time_picker: TimePickerTheme::light(),
|
||||||
date_picker: DatePickerTheme::light(),
|
date_picker: DatePickerTheme::light(),
|
||||||
|
popover: PopoverTheme::light(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn dark() -> Self {
|
pub fn dark() -> Self {
|
||||||
|
@ -104,6 +106,7 @@ impl Theme {
|
||||||
calendar: CalendarTheme::dark(),
|
calendar: CalendarTheme::dark(),
|
||||||
time_picker: TimePickerTheme::dark(),
|
time_picker: TimePickerTheme::dark(),
|
||||||
date_picker: DatePickerTheme::dark(),
|
date_picker: DatePickerTheme::dark(),
|
||||||
|
popover: PopoverTheme::dark(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue