* [RFC yew-widget-toolkit/yew-widget-toolkit-assets 0/2] Animate navigation drawer menus collapsing/expanding
@ 2026-06-19 7:44 Dominik Csapak
2026-06-19 7:44 ` [PATCH yew-widget-toolkit-assets 1/2] nav: add classes for animated navigation drawer menus Dominik Csapak
2026-06-19 7:44 ` [PATCH yew-widget-toolkit 2/2] widget: navigation drawer: make menu collapsing/expanding animated Dominik Csapak
0 siblings, 2 replies; 3+ messages in thread
From: Dominik Csapak @ 2026-06-19 7:44 UTC (permalink / raw)
To: yew-devel
This is a common UI pattern so implement it. The current version only uses
a 0.1s animation time, so it should not reduce the usability at all but
gives a nice feedback for expanding/collapsing.
Sending as RFC regardless since I'm not sure if we want to enable this by
default or not.
proxmox-yew-widget-toolkit-assets:
Dominik Csapak (1):
nav: add classes for animated navigation drawer menus
scss/_nav.scss | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
proxmox-yew-widget-toolkit:
Dominik Csapak (1):
widget: navigation drawer: make menu collapsing/expanding animated
src/dom/focus.rs | 30 ++++++++++++++++++++++++-----
src/widget/nav/navigation_drawer.rs | 24 ++++++++++++++++++-----
2 files changed, 44 insertions(+), 10 deletions(-)
Summary over all repositories:
3 files changed, 74 insertions(+), 10 deletions(-)
--
Generated by murpp 0.11.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH yew-widget-toolkit-assets 1/2] nav: add classes for animated navigation drawer menus
2026-06-19 7:44 [RFC yew-widget-toolkit/yew-widget-toolkit-assets 0/2] Animate navigation drawer menus collapsing/expanding Dominik Csapak
@ 2026-06-19 7:44 ` Dominik Csapak
2026-06-19 7:44 ` [PATCH yew-widget-toolkit 2/2] widget: navigation drawer: make menu collapsing/expanding animated Dominik Csapak
1 sibling, 0 replies; 3+ messages in thread
From: Dominik Csapak @ 2026-06-19 7:44 UTC (permalink / raw)
To: yew-devel
to animate expanding/collapsing of menus in navigation drawers, we have
to add some classes. Since we also want to be able to enable/disable
animation, use a css variable for the transition that is set
accordingly.
The only broadly compatible CSS way to animate the height of something
where the height is unknown ("auto"), is by using a grid layout and
using a '1fr' row template for it.
("auto" height is not animatable, and calc-size only exists in chrome
for now)
The markup has to be changed in the rust code for this to work.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
scss/_nav.scss | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/scss/_nav.scss b/scss/_nav.scss
index 08102d4..48cda9a 100644
--- a/scss/_nav.scss
+++ b/scss/_nav.scss
@@ -157,6 +157,36 @@
text-shadow: none !important;
color: var(--pwt-color);
}
+
+ --pwt-transition-time: 0s;
+
+ &.animated {
+ --pwt-transition-time: 0.1s;
+ }
+
+ .pwt-nav-menu-item-arrow {
+ transition: var(--pwt-transition-time);
+ &.expanded {
+ --dir: -180deg;
+ transform: rotateZ(var(--dir));
+ }
+
+ [dir="rtl"] &.expanded {
+ --dir: 180deg;
+ }
+ }
+
+ .pwt-nav-menu-animation-container {
+ transition: var(--pwt-transition-time);
+ overflow: hidden;
+ display: grid;
+ grid-template-rows: 0fr;
+ min-height: fit-content;
+
+ &.expanded {
+ grid-template-rows: 1fr;
+ }
+ }
}
.pwt-nav-menu-icon {
--
2.47.3
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH yew-widget-toolkit 2/2] widget: navigation drawer: make menu collapsing/expanding animated
2026-06-19 7:44 [RFC yew-widget-toolkit/yew-widget-toolkit-assets 0/2] Animate navigation drawer menus collapsing/expanding Dominik Csapak
2026-06-19 7:44 ` [PATCH yew-widget-toolkit-assets 1/2] nav: add classes for animated navigation drawer menus Dominik Csapak
@ 2026-06-19 7:44 ` Dominik Csapak
1 sibling, 0 replies; 3+ messages in thread
From: Dominik Csapak @ 2026-06-19 7:44 UTC (permalink / raw)
To: yew-devel
This is a common and popular pattern for menus in navigation menus, so
implement it here too, but with the capability to disable it.
To achieve that we have to wrap the submenus in two containers: one that
sets the height from outside and hides the overflow, and one that is
being sized so it gives a 'sliding' animation.
Since the elements are not in a flat list anymore, for keyboard
navigation we have to introduce recursive tabindex search. Copy
`roving_tabindex_next` to `roving_tabindex_next_recursive`, which
searches recursively, and leave the original in place for all other use
sites.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/dom/focus.rs | 30 ++++++++++++++++++++++++-----
src/widget/nav/navigation_drawer.rs | 24 ++++++++++++++++++-----
2 files changed, 44 insertions(+), 10 deletions(-)
diff --git a/src/dom/focus.rs b/src/dom/focus.rs
index 2dedce1..d14023f 100644
--- a/src/dom/focus.rs
+++ b/src/dom/focus.rs
@@ -96,7 +96,14 @@ pub fn get_first_focusable(item_el: web_sys::Element) -> Option<web_sys::HtmlEle
/// Move focus to the next/previous focusable child (calls [roving_tabindex_next_el]).
pub fn roving_tabindex_next(node_ref: &NodeRef, backwards: bool, roving: bool) {
if let Some(el) = node_ref.cast::<web_sys::HtmlElement>() {
- roving_tabindex_next_el(el, backwards, roving);
+ roving_tabindex_next_el(el, backwards, roving, false);
+ }
+}
+
+/// Move focus to the next/previous focusable child including nested ones (calls [roving_tabindex_next_el]) .
+pub fn roving_tabindex_next_recursive(node_ref: &NodeRef, backwards: bool, roving: bool) {
+ if let Some(el) = node_ref.cast::<web_sys::HtmlElement>() {
+ roving_tabindex_next_el(el, backwards, roving, true);
}
}
@@ -104,8 +111,15 @@ pub fn roving_tabindex_next(node_ref: &NodeRef, backwards: bool, roving: bool) {
///
/// If `roving` is enabled, this also sets the `tabindex` attribute for the active child to `"0"`,
/// and to `"-1"` for all other children.
-pub fn roving_tabindex_next_el(el: web_sys::HtmlElement, backwards: bool, roving: bool) {
- let list = roving_tabindex_members(&el);
+///
+/// If `nesting` is enabled, all nested focusable elements are included
+pub fn roving_tabindex_next_el(
+ el: web_sys::HtmlElement,
+ backwards: bool,
+ roving: bool,
+ recursive: bool,
+) {
+ let list = roving_tabindex_members(&el, recursive);
if list.is_empty() {
return;
@@ -251,7 +265,10 @@ pub fn update_roving_tabindex_el(el: web_sys::HtmlElement) {
///
/// This kind of member selection makes it possible to include more complex widget like
/// [MenuButton](crate::widget::menu::MenuButton)s inside a [Toolbar](crate::widget::Toolbar).
-pub fn roving_tabindex_members(el: &web_sys::HtmlElement) -> Vec<web_sys::HtmlElement> {
+pub fn roving_tabindex_members(
+ el: &web_sys::HtmlElement,
+ recursive: bool,
+) -> Vec<web_sys::HtmlElement> {
let mut members: Vec<web_sys::HtmlElement> = Vec::new();
if let Ok(child_list) = el.query_selector_all(":scope > *") {
@@ -263,6 +280,9 @@ pub fn roving_tabindex_members(el: &web_sys::HtmlElement) -> Vec<web_sys::HtmlEl
.unwrap();
if element_is_focusable(&node) {
members.push(node);
+ } else if recursive {
+ let mut recursive_members = roving_tabindex_members(&node, recursive);
+ members.append(&mut recursive_members);
} else if let Ok(Some(child)) = node.query_selector(FOCUSABLE_SELECTOR_ALL) {
let first_focusable_child = child.dyn_into::<web_sys::HtmlElement>().unwrap();
members.push(first_focusable_child);
@@ -285,7 +305,7 @@ pub fn init_roving_tabindex(node_ref: &NodeRef) {
/// This function makes sure that exactly one element has the `tabindex` attribute set to `"0"`. All
/// other elements get a `tabindex` of `"-1"`.
pub fn init_roving_tabindex_el(el: web_sys::HtmlElement, take_focus: bool) {
- let list = roving_tabindex_members(&el);
+ let list = roving_tabindex_members(&el, false);
if list.is_empty() {
return;
diff --git a/src/widget/nav/navigation_drawer.rs b/src/widget/nav/navigation_drawer.rs
index 89b4b4b..d3f79db 100644
--- a/src/widget/nav/navigation_drawer.rs
+++ b/src/widget/nav/navigation_drawer.rs
@@ -15,7 +15,7 @@ use crate::props::{
use crate::state::{NavigationContext, NavigationContextExt, Selection};
use crate::{impl_class_prop_builder, impl_yew_std_props_builder};
-use crate::dom::focus::roving_tabindex_next;
+use crate::dom::focus::roving_tabindex_next_recursive;
use crate::widget::{Column, Container, Fa};
use super::{Menu, MenuEntry, MenuItem};
@@ -75,6 +75,13 @@ pub struct NavigationDrawer {
#[builder]
#[prop_or_default]
router: bool,
+
+ /// Enables animations
+ ///
+ /// If enabled, expanding/collapsing is animated
+ #[builder]
+ #[prop_or(true)]
+ animated: bool,
}
impl AsClassesMut for NavigationDrawer {
@@ -221,7 +228,6 @@ impl PwtNavigationDrawer {
(!hidden).then_some(if is_active { "0" } else { "-1" }),
)
.class("pwt-nav-link")
- .class(hidden.then_some("pwt-d-none"))
.class(crate::css::AlignItems::Baseline)
.class(is_active.then_some("active"))
.onclick(onclick)
@@ -271,9 +277,16 @@ impl PwtNavigationDrawer {
menu.add_child(self.render_single_item(ctx, child, active, level, open, hidden));
if let Some(submenu) = &child.submenu {
+ let mut items = Column::new().min_height(0);
for sub in submenu.children.iter() {
- self.render_menu_entry(ctx, sub, menu, active, level + 1, !open)
+ self.render_menu_entry(ctx, sub, &mut items, active, level + 1, !open)
}
+ menu.add_child(
+ Container::new()
+ .class("pwt-nav-menu-animation-container")
+ .class(open.then_some("expanded"))
+ .with_child(items),
+ );
}
}
MenuEntry::Component(comp) => {
@@ -554,10 +567,10 @@ impl Component for PwtNavigationDrawer {
let onkeydown = Callback::from(move |event: KeyboardEvent| {
match event.key().as_str() {
"ArrowDown" => {
- roving_tabindex_next(&menu_ref, false, false);
+ roving_tabindex_next_recursive(&menu_ref, false, false);
}
"ArrowUp" => {
- roving_tabindex_next(&menu_ref, true, false);
+ roving_tabindex_next_recursive(&menu_ref, true, false);
}
_ => return,
}
@@ -571,6 +584,7 @@ impl Component for PwtNavigationDrawer {
.attribute("role", "navigation")
.attribute("aria-label", props.aria_label.clone())
.class("pwt-nav-menu")
+ .class(props.animated.then_some("animated"))
.class(OverflowX::Hidden)
.class(OverflowY::Auto)
.class(props.class.clone())
--
2.47.3
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-06-19 7:45 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-19 7:44 [RFC yew-widget-toolkit/yew-widget-toolkit-assets 0/2] Animate navigation drawer menus collapsing/expanding Dominik Csapak
2026-06-19 7:44 ` [PATCH yew-widget-toolkit-assets 1/2] nav: add classes for animated navigation drawer menus Dominik Csapak
2026-06-19 7:44 ` [PATCH yew-widget-toolkit 2/2] widget: navigation drawer: make menu collapsing/expanding animated Dominik Csapak
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.