feat: binder component add width

This commit is contained in:
luoxiao 2023-11-12 02:34:40 +08:00
parent c57910948f
commit 012ab684ba
7 changed files with 91 additions and 106 deletions

View file

@ -1,7 +1,7 @@
mod theme; mod theme;
use crate::{ use crate::{
components::{Binder, Follower, FollowerPlacement}, components::{Binder, Follower, FollowerPlacement, FollowerWidth},
mount_style, use_theme, mount_style, use_theme,
utils::StoredMaybeSignal, utils::StoredMaybeSignal,
Input, Theme, Input, Theme,
@ -61,7 +61,7 @@ pub fn AutoComplete(
allow_value allow_value
/> />
</div> </div>
<Follower slot show=is_show_menu placement=FollowerPlacement::BottomStart> <Follower slot show=is_show_menu placement=FollowerPlacement::BottomStart width=FollowerWidth::Target>
<div <div
class="thaw-auto-complete__menu" class="thaw-auto-complete__menu"
style=move || menu_css_vars.get() style=move || menu_css_vars.get()

View file

@ -18,10 +18,6 @@
} }
.thaw-color-picker-popover { .thaw-color-picker-popover {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 240px; width: 240px;
padding: 12px; padding: 12px;
background-color: var(--thaw-background-color); background-color: var(--thaw-background-color);
@ -29,7 +25,6 @@
box-sizing: border-box; box-sizing: border-box;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 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); 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
z-index: 1000;
} }
.thaw-color-picker-popover__panel { .thaw-color-picker-popover__panel {

View file

@ -1,7 +1,8 @@
mod color; mod color;
mod theme; mod theme;
use crate::{mount_style, teleport::Teleport, use_theme, Theme}; use crate::components::{Binder, Follower, FollowerPlacement};
use crate::{mount_style, use_theme, Theme};
pub use color::*; pub use color::*;
use leptos::*; use leptos::*;
use leptos::{leptos_dom::helpers::WindowListenerHandle, wasm_bindgen::__rt::IntoJsResult}; use leptos::{leptos_dom::helpers::WindowListenerHandle, wasm_bindgen::__rt::IntoJsResult};
@ -62,18 +63,7 @@ pub fn ColorPicker(#[prop(optional, into)] value: RwSignal<RGBA>) -> impl IntoVi
let trigger_ref = create_node_ref::<html::Div>(); let trigger_ref = create_node_ref::<html::Div>();
let popover_ref = create_node_ref::<html::Div>(); let popover_ref = create_node_ref::<html::Div>();
let show_popover = move |_| { let show_popover = move |_| {
let rect = trigger_ref.get().unwrap().get_bounding_client_rect();
is_show_popover.set(true); is_show_popover.set(true);
if let Some(popover_ref) = popover_ref.get() {
_ = popover_ref.style(
"transform",
format!(
"translateX({}px) translateY({}px)",
rect.x(),
rect.y() + rect.height()
),
);
}
}; };
let timer = window_event_listener(ev::click, move |ev| { let timer = window_event_listener(ev::click, move |ev| {
let el = ev.target(); let el = ev.target();
@ -96,28 +86,24 @@ pub fn ColorPicker(#[prop(optional, into)] value: RwSignal<RGBA>) -> impl IntoVi
on_cleanup(move || timer.remove()); on_cleanup(move || timer.remove());
view! { view! {
<div class="thaw-color-picker-trigger" on:click=show_popover ref=trigger_ref> <Binder target_ref=trigger_ref>
<div class="thaw-color-picker-trigger__content" style=move || style.get()> <div class="thaw-color-picker-trigger" on:click=show_popover ref=trigger_ref>
{move || label.get()} <div class="thaw-color-picker-trigger__content" style=move || style.get()>
{move || label.get()}
</div>
</div> </div>
</div> <Follower slot show=is_show_popover placement=FollowerPlacement::BottomStart>
<Teleport> <div
<div class="thaw-color-picker-popover"
class="thaw-color-picker-popover" ref=popover_ref
ref=popover_ref style=move || popover_css_vars.get()
style=move || { >
if is_show_popover.get() {
popover_css_vars.get()
} else {
"display: none".to_string()
}
}
>
<ColorPanel hue=hue.read_only() sv/> <ColorPanel hue=hue.read_only() sv/>
<HueSlider hue/> <HueSlider hue/>
</div> </div>
</Teleport> </Follower>
</Binder>
} }
} }

View file

@ -32,19 +32,17 @@ pub fn get_follower_placement_style(
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 mut top = target_y + target_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 { if top + follower_height > inner_height && target_y - follower_height >= 0.0 {
if target_y - follower_height >= 0.0 { target_y - follower_height
top = target_y - follower_height } else {
} top
} }
top
}; };
style.push_str(&format!( style.push_str(&format!(

View file

@ -17,10 +17,20 @@ use leptos::{
pub struct Follower { pub struct Follower {
#[prop(into)] #[prop(into)]
show: MaybeSignal<bool>, show: MaybeSignal<bool>,
#[prop(optional)]
width: Option<FollowerWidth>,
placement: FollowerPlacement, placement: FollowerPlacement,
children: Children, children: Children,
} }
#[derive(Clone)]
pub enum FollowerWidth {
Target,
Px(u32),
}
impl Copy for FollowerWidth {}
#[component] #[component]
pub fn Binder<El: ElementDescriptor + Clone + 'static>( pub fn Binder<El: ElementDescriptor + Clone + 'static>(
#[prop(into)] target_ref: NodeRef<El>, #[prop(into)] target_ref: NodeRef<El>,
@ -30,6 +40,7 @@ pub fn Binder<El: ElementDescriptor + Clone + 'static>(
mount_style("binder", include_str!("./binder.css")); mount_style("binder", include_str!("./binder.css"));
let Follower { let Follower {
show: follower_show, show: follower_show,
width: follower_width,
placement: follower_placement, placement: follower_placement,
children: follower_children, children: follower_children,
} = follower; } = follower;
@ -73,7 +84,7 @@ pub fn Binder<El: ElementDescriptor + Clone + 'static>(
let remove_scroll_listener = move |_| { let remove_scroll_listener = move |_| {
scrollable_element_handle_vec.update_value(|vec| { scrollable_element_handle_vec.update_value(|vec| {
vec.drain(..).into_iter().for_each(|handle| handle.remove()); vec.drain(..).for_each(|handle| handle.remove());
}); });
scroll_listener.set_value(None); scroll_listener.set_value(None);
}; };
@ -108,6 +119,7 @@ pub fn Binder<El: ElementDescriptor + Clone + 'static>(
<FollowerContainer <FollowerContainer
show=follower_show show=follower_show
target_ref target_ref
width=follower_width
placement=follower_placement placement=follower_placement
add_scroll_listener add_scroll_listener
remove_scroll_listener remove_scroll_listener
@ -123,6 +135,7 @@ pub fn Binder<El: ElementDescriptor + Clone + 'static>(
fn FollowerContainer<El: ElementDescriptor + Clone + 'static>( fn FollowerContainer<El: ElementDescriptor + Clone + 'static>(
show: MaybeSignal<bool>, show: MaybeSignal<bool>,
target_ref: NodeRef<El>, target_ref: NodeRef<El>,
width: Option<FollowerWidth>,
placement: FollowerPlacement, placement: FollowerPlacement,
#[prop(into)] add_scroll_listener: Callback<Callback<()>>, #[prop(into)] add_scroll_listener: Callback<Callback<()>>,
#[prop(into)] remove_scroll_listener: Callback<()>, #[prop(into)] remove_scroll_listener: Callback<()>,
@ -142,7 +155,13 @@ fn FollowerContainer<El: ElementDescriptor + Clone + 'static>(
let target_rect = target_ref.get_bounding_client_rect(); let target_rect = target_ref.get_bounding_client_rect();
let content_rect = content_ref.get_bounding_client_rect(); let content_rect = content_ref.get_bounding_client_rect();
let mut style = String::new(); let mut style = String::new();
style.push_str(&format!("width: {}px;", target_rect.width())); if let Some(width) = width {
let width = match width {
FollowerWidth::Target => format!("width: {}px;", target_rect.width()),
FollowerWidth::Px(width) => format!("width: {width}px;"),
};
style.push_str(&width);
}
if let Some(placement_style) = if let Some(placement_style) =
get_follower_placement_style(placement, target_rect, content_rect) get_follower_placement_style(placement, target_rect, content_rect)
{ {

View file

@ -1,6 +1,11 @@
mod theme; mod theme;
use crate::{teleport::Teleport, theme::use_theme, utils::mount_style::mount_style, Theme}; use crate::{
components::{Binder, Follower, FollowerPlacement, FollowerWidth},
theme::use_theme,
utils::mount_style::mount_style,
Theme,
};
use leptos::wasm_bindgen::__rt::IntoJsResult; use leptos::wasm_bindgen::__rt::IntoJsResult;
use leptos::*; use leptos::*;
use std::hash::Hash; use std::hash::Hash;
@ -66,20 +71,7 @@ where
let trigger_ref = create_node_ref::<html::Div>(); let trigger_ref = create_node_ref::<html::Div>();
let menu_ref = create_node_ref::<html::Div>(); let menu_ref = create_node_ref::<html::Div>();
let show_menu = move |_| { let show_menu = move |_| {
let rect = trigger_ref.get().unwrap().get_bounding_client_rect();
is_show_menu.set(true); is_show_menu.set(true);
if let Some(menu_ref) = menu_ref.get() {
_ = menu_ref
.style("width", format!("{}px", rect.width()))
.style(
"transform",
format!(
"translateX({}px) translateY({}px) translateX(-50%)",
rect.x() + rect.width() / 2.0,
rect.y() + rect.height()
),
);
}
}; };
let timer = window_event_listener(ev::click, move |ev| { let timer = window_event_listener(ev::click, move |ev| {
let el = ev.target(); let el = ev.target();
@ -111,47 +103,46 @@ where
None => String::new(), None => String::new(),
}); });
view! { view! {
<div class="thaw-select" ref=trigger_ref on:click=show_menu style=move || css_vars.get()> <Binder target_ref=trigger_ref>
<div class="thaw-select" ref=trigger_ref on:click=show_menu style=move || css_vars.get()>
{move || select_option_label.get()} {move || select_option_label.get()}
</div>
<Teleport>
<div
class="thaw-select-menu"
style=move || {
if is_show_menu.get() { menu_css_vars.get() } else { "display: none;".into() }
}
ref=menu_ref
>
<For
each=move || options.get()
key=move |item| item.value.clone()
children=move |item| {
let item = store_value(item);
let onclick = move |_| {
let SelectOption { value: item_value, label: _ } = item.get_value();
value.set(Some(item_value));
is_show_menu.set(false);
};
view! {
<div
class="thaw-select-menu__item"
class=(
"thaw-select-menu__item-selected",
move || value.get() == Some(item.get_value().value),
)
on:click=onclick
>
{item.get_value().label}
</div>
}
}
/>
</div> </div>
</Teleport> <Follower slot show=is_show_menu placement=FollowerPlacement::BottomStart width=FollowerWidth::Target>
<div
class="thaw-select-menu"
style=move || menu_css_vars.get()
ref=menu_ref
>
<For
each=move || options.get()
key=move |item| item.value.clone()
children=move |item| {
let item = store_value(item);
let onclick = move |_| {
let SelectOption { value: item_value, label: _ } = item.get_value();
value.set(Some(item_value));
is_show_menu.set(false);
};
view! {
<div
class="thaw-select-menu__item"
class=(
"thaw-select-menu__item-selected",
move || value.get() == Some(item.get_value().value),
)
on:click=onclick
>
{item.get_value().label}
</div>
}
}
/>
</div>
</Follower>
</Binder>
} }
} }

View file

@ -21,16 +21,12 @@
background-color: var(--thaw-background-color); background-color: var(--thaw-background-color);
box-sizing: border-box; box-sizing: border-box;
padding: 5px; padding: 5px;
position: absolute; width: 100%;
top: 0;
left: 0;
right: 0;
max-height: 200px; max-height: 200px;
border-radius: 3px; border-radius: 3px;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 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); 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
overflow: auto; overflow: auto;
z-index: 1000;
} }
.thaw-select-menu__item { .thaw-select-menu__item {