Skip to content

Commit c1a9f84

Browse files
authored
Feat/binder animation (#131)
* feat: Popover adds animation * feat: ColorPicker adds animation * feat: AutoComplete adds animation * feat: Select adds animation * feat: DatePicker adds animation * feat: TimePicker adds animation
1 parent d238344 commit c1a9f84

File tree

15 files changed

+461
-227
lines changed

15 files changed

+461
-227
lines changed

thaw/src/auto_complete/auto-complete.css

+28
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,31 @@
1818
.thaw-auto-complete__menu-item--selected {
1919
background-color: var(--thaw-background-color-hover);
2020
}
21+
22+
.thaw-auto-complete__menu.fade-in-scale-up-transition-leave-active {
23+
transform-origin: inherit;
24+
transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1),
25+
transform 0.2s cubic-bezier(0.4, 0, 1, 1),
26+
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
27+
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
28+
}
29+
30+
.thaw-auto-complete__menu.fade-in-scale-up-transition-enter-active {
31+
transform-origin: inherit;
32+
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1),
33+
transform 0.2s cubic-bezier(0, 0, 0.2, 1),
34+
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
35+
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
36+
}
37+
38+
.thaw-auto-complete__menu.fade-in-scale-up-transition-enter-from,
39+
.thaw-auto-complete__menu.fade-in-scale-up-transition-leave-to {
40+
opacity: 0;
41+
transform: scale(0.9);
42+
}
43+
44+
.thaw-auto-complete__menu.fade-in-scale-up-transition-leave-from,
45+
.thaw-auto-complete__menu.fade-in-scale-up-transition-enter-to {
46+
opacity: 1;
47+
transform: scale(1);
48+
}

thaw/src/auto_complete/mod.rs

+65-58
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod theme;
22

33
use crate::{
4-
components::{Binder, Follower, FollowerPlacement, FollowerWidth},
4+
components::{Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth},
55
use_theme,
66
utils::{class_list::class_list, mount_style, Model, OptionalProp, StoredMaybeSignal},
77
ComponentRef, Input, InputPrefix, InputRef, InputSuffix, Theme,
@@ -196,69 +196,76 @@ pub fn AutoComplete(
196196
placement=FollowerPlacement::BottomStart
197197
width=FollowerWidth::Target
198198
>
199-
<div
200-
class="thaw-auto-complete__menu"
201-
style=move || menu_css_vars.get()
202-
ref=menu_ref
199+
<CSSTransition
200+
node_ref=menu_ref
201+
name="fade-in-scale-up-transition"
202+
show=is_show_menu
203+
let:display
203204
>
205+
<div
206+
class="thaw-auto-complete__menu"
207+
style=move || display.get().map(|d| d.to_string()).unwrap_or_else(|| menu_css_vars.get())
208+
ref=menu_ref
209+
>
204210

205-
{move || {
206-
options
207-
.get()
208-
.into_iter()
209-
.enumerate()
210-
.map(|(index, v)| {
211-
let AutoCompleteOption { value: option_value, label } = v;
212-
let menu_item_ref = create_node_ref::<html::Div>();
213-
let on_click = move |_| {
214-
select_value(option_value.clone());
215-
};
216-
let on_mouseenter = move |_| {
217-
select_option_index.set(Some(index));
218-
};
219-
let on_mousedown = move |ev: ev::MouseEvent| {
220-
ev.prevent_default();
221-
};
222-
create_effect(move |_| {
223-
if Some(index) == select_option_index.get() {
224-
if !is_show_menu.get() {
225-
return;
226-
}
227-
if let Some(menu_item_ref) = menu_item_ref.get() {
228-
let menu_ref = menu_ref.get().unwrap();
229-
let menu_rect = menu_ref.get_bounding_client_rect();
230-
let item_rect = menu_item_ref.get_bounding_client_rect();
231-
if item_rect.y() < menu_rect.y() {
232-
menu_item_ref.scroll_into_view_with_bool(true);
233-
} else if item_rect.y() + item_rect.height()
234-
> menu_rect.y() + menu_rect.height()
235-
{
236-
menu_item_ref.scroll_into_view_with_bool(false);
211+
{move || {
212+
options
213+
.get()
214+
.into_iter()
215+
.enumerate()
216+
.map(|(index, v)| {
217+
let AutoCompleteOption { value: option_value, label } = v;
218+
let menu_item_ref = create_node_ref::<html::Div>();
219+
let on_click = move |_| {
220+
select_value(option_value.clone());
221+
};
222+
let on_mouseenter = move |_| {
223+
select_option_index.set(Some(index));
224+
};
225+
let on_mousedown = move |ev: ev::MouseEvent| {
226+
ev.prevent_default();
227+
};
228+
create_effect(move |_| {
229+
if Some(index) == select_option_index.get() {
230+
if !is_show_menu.get() {
231+
return;
232+
}
233+
if let Some(menu_item_ref) = menu_item_ref.get() {
234+
let menu_ref = menu_ref.get().unwrap();
235+
let menu_rect = menu_ref.get_bounding_client_rect();
236+
let item_rect = menu_item_ref.get_bounding_client_rect();
237+
if item_rect.y() < menu_rect.y() {
238+
menu_item_ref.scroll_into_view_with_bool(true);
239+
} else if item_rect.y() + item_rect.height()
240+
> menu_rect.y() + menu_rect.height()
241+
{
242+
menu_item_ref.scroll_into_view_with_bool(false);
243+
}
237244
}
238245
}
239-
}
240-
});
241-
view! {
242-
<div
243-
class="thaw-auto-complete__menu-item"
244-
class=(
245-
"thaw-auto-complete__menu-item--selected",
246-
move || Some(index) == select_option_index.get(),
247-
)
246+
});
247+
view! {
248+
<div
249+
class="thaw-auto-complete__menu-item"
250+
class=(
251+
"thaw-auto-complete__menu-item--selected",
252+
move || Some(index) == select_option_index.get(),
253+
)
248254

249-
on:click=on_click
250-
on:mousedown=on_mousedown
251-
on:mouseenter=on_mouseenter
252-
ref=menu_item_ref
253-
>
254-
{label}
255-
</div>
256-
}
257-
})
258-
.collect_view()
259-
}}
255+
on:click=on_click
256+
on:mousedown=on_mousedown
257+
on:mouseenter=on_mouseenter
258+
ref=menu_item_ref
259+
>
260+
{label}
261+
</div>
262+
}
263+
})
264+
.collect_view()
265+
}}
260266

261-
</div>
267+
</div>
268+
</CSSTransition>
262269
</Follower>
263270
</Binder>
264271
}

thaw/src/color_picker/color-picker.css

+24
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,27 @@
8989
border: 2px solid white;
9090
cursor: pointer;
9191
}
92+
93+
.thaw-color-picker-popover.fade-in-scale-up-transition-leave-active {
94+
transform-origin: inherit;
95+
transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1),
96+
transform 0.2s cubic-bezier(0.4, 0, 1, 1);
97+
}
98+
99+
.thaw-color-picker-popover.fade-in-scale-up-transition-enter-active {
100+
transform-origin: inherit;
101+
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1),
102+
transform 0.2s cubic-bezier(0, 0, 0.2, 1);
103+
}
104+
105+
.thaw-color-picker-popover.fade-in-scale-up-transition-enter-from,
106+
.thaw-color-picker-popover.fade-in-scale-up-transition-leave-to {
107+
opacity: 0;
108+
transform: scale(0.9);
109+
}
110+
111+
.thaw-color-picker-popover.fade-in-scale-up-transition-leave-from,
112+
.thaw-color-picker-popover.fade-in-scale-up-transition-enter-to {
113+
opacity: 1;
114+
transform: scale(1);
115+
}

thaw/src/color_picker/mod.rs

+14-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub use color::*;
55
pub use theme::ColorPickerTheme;
66

77
use crate::{
8-
components::{Binder, Follower, FollowerPlacement},
8+
components::{Binder, CSSTransition, Follower, FollowerPlacement},
99
use_theme,
1010
utils::{class_list::class_list, mount_style, Model, OptionalProp},
1111
Theme,
@@ -152,15 +152,21 @@ pub fn ColorPicker(
152152
</div>
153153
</div>
154154
<Follower slot show=is_show_popover placement=FollowerPlacement::BottomStart>
155-
<div
156-
class="thaw-color-picker-popover"
157-
ref=popover_ref
158-
style=move || popover_css_vars.get()
155+
<CSSTransition
156+
node_ref=popover_ref name="fade-in-scale-up-transition"
157+
show=is_show_popover
158+
let:display
159159
>
160+
<div
161+
class="thaw-color-picker-popover"
162+
ref=popover_ref
163+
style=move || display.get().map(|d| d.to_string()).unwrap_or_else(|| popover_css_vars.get())
164+
>
160165

161-
<ColorPanel hue=hue.read_only() sv/>
162-
<HueSlider hue/>
163-
</div>
166+
<ColorPanel hue=hue.read_only() sv/>
167+
<HueSlider hue/>
168+
</div>
169+
</CSSTransition>
164170
</Follower>
165171
</Binder>
166172
}

thaw/src/components/binder/get_placement_style.rs

+17
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ impl FollowerPlacement {
3636
Self::BottomEnd => "bottom-end",
3737
}
3838
}
39+
40+
pub fn transform_origin(&self) -> &'static str {
41+
match self {
42+
Self::Top => "bottom center",
43+
Self::Bottom => "top center",
44+
Self::Left => "center right",
45+
Self::Right => "center left",
46+
Self::TopStart => "bottom left",
47+
Self::TopEnd => "bottom right",
48+
Self::LeftStart => "top right",
49+
Self::LeftEnd => "bottom right",
50+
Self::RightStart => "top left",
51+
Self::RightEnd => "bottom left",
52+
Self::BottomStart => "top left",
53+
Self::BottomEnd => "top right",
54+
}
55+
}
3956
}
4057

4158
pub struct FollowerPlacementOffset {

thaw/src/components/binder/mod.rs

+20-21
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,15 @@ pub enum FollowerWidth {
3434

3535
impl Copy for FollowerWidth {}
3636

37-
3837
/// # Tracking popup
3938
///
4039
/// ```rust
4140
/// use crate::components::{Binder, Follower, FollowerPlacement};
4241
/// use leptos::*;
43-
///
42+
///
4443
/// let div_ref= NodeRef::new();
4544
/// let show = RwSignal::new(false);
46-
///
45+
///
4746
/// view! {
4847
/// <Binder target_ref=div_ref>
4948
/// <div ref=div_ref>
@@ -58,7 +57,8 @@ impl Copy for FollowerWidth {}
5857
#[component]
5958
pub fn Binder<El: ElementDescriptor + Clone + 'static>(
6059
/// Used to track DOM locations
61-
#[prop(into)] target_ref: NodeRef<El>,
60+
#[prop(into)]
61+
target_ref: NodeRef<El>,
6262
/// Content for pop-up display
6363
follower: Follower,
6464
children: Children,
@@ -197,6 +197,10 @@ fn FollowerContainer<El: ElementDescriptor + Clone + 'static>(
197197
}) = get_follower_placement_offset(placement, target_rect, content_rect)
198198
{
199199
placement_str.set(placement.as_str());
200+
style.push_str(&format!(
201+
"transform-origin: {};",
202+
placement.transform_origin()
203+
));
200204
style.push_str(&format!(
201205
"transform: translateX({left}px) translateY({top}px) {transform};"
202206
));
@@ -207,15 +211,14 @@ fn FollowerContainer<El: ElementDescriptor + Clone + 'static>(
207211
content_style.set(style);
208212
});
209213

210-
let is_show = create_memo(move |_| {
214+
Effect::new(move |_| {
211215
if target_ref.get().is_none() {
212-
return false;
216+
return;
213217
}
214218
if content_ref.get().is_none() {
215-
return false;
219+
return;
216220
}
217-
let is_show = show.get();
218-
if is_show {
221+
if show.get() {
219222
request_animation_frame(move || {
220223
sync_position.call(());
221224
});
@@ -225,21 +228,17 @@ fn FollowerContainer<El: ElementDescriptor + Clone + 'static>(
225228
remove_scroll_listener.call(());
226229
remove_resize_listener.call(());
227230
}
228-
is_show
229231
});
230232

231233
let children = with_hydration_off(|| {
232-
html::div()
233-
.classes("thaw-binder-follower-container")
234-
.style("display", move || (!is_show.get()).then_some("none"))
235-
.child(
236-
html::div()
237-
.classes("thaw-binder-follower-content")
238-
.attr("data-thaw-placement", move || placement_str.get())
239-
.node_ref(content_ref)
240-
.attr("style", move || content_style.get())
241-
.child(children()),
242-
)
234+
html::div().classes("thaw-binder-follower-container").child(
235+
html::div()
236+
.classes("thaw-binder-follower-content")
237+
.attr("data-thaw-placement", move || placement_str.get())
238+
.node_ref(content_ref)
239+
.attr("style", move || content_style.get())
240+
.child(children()),
241+
)
243242
});
244243

245244
view! { <Teleport element=children/> }

thaw/src/date_picker/date-picker.css

+24
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,27 @@
172172
text-align: center;
173173
border-radius: 3px;
174174
}
175+
176+
.thaw-date-picker-panel.fade-in-scale-up-transition-leave-active {
177+
transform-origin: inherit;
178+
transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1),
179+
transform 0.2s cubic-bezier(0.4, 0, 1, 1);
180+
}
181+
182+
.thaw-date-picker-panel.fade-in-scale-up-transition-enter-active {
183+
transform-origin: inherit;
184+
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1),
185+
transform 0.2s cubic-bezier(0, 0, 0.2, 1);
186+
}
187+
188+
.thaw-date-picker-panel.fade-in-scale-up-transition-enter-from,
189+
.thaw-date-picker-panel.fade-in-scale-up-transition-leave-to {
190+
opacity: 0;
191+
transform: scale(0.9);
192+
}
193+
194+
.thaw-date-picker-panel.fade-in-scale-up-transition-leave-from,
195+
.thaw-date-picker-panel.fade-in-scale-up-transition-enter-to {
196+
opacity: 1;
197+
transform: scale(1);
198+
}

thaw/src/date_picker/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pub fn DatePicker(
8787
close_panel
8888
selected_date=panel_selected_date
8989
comp_ref=panel_ref
90+
is_show_panel
9091
/>
9192
</Follower>
9293
</Binder>

0 commit comments

Comments
 (0)