all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: yew-devel@lists.proxmox.com
Subject: [PATCH yew-widget-toolkit 3/4] touch: navigation rail: add an optional expand button
Date: Mon, 10 Aug 2026 16:34:53 +0200	[thread overview]
Message-ID: <20260810143536.31163-4-d.csapak@proxmox.com> (raw)
In-Reply-To: <20260810143536.31163-1-d.csapak@proxmox.com>

Material Design's expressive navigation rail offers a menu button that
switches between the collapsed and the expanded layout, so users can
keep the labels visible on a medium window or reclaim the space on a
large one. The button is opt-in, existing rails keep following the
viewport alone.

Add a passthrough for AdaptiveScaffold too.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/touch/adaptive_scaffold.rs |  9 ++++-
 src/touch/navigation_rail.rs   | 60 +++++++++++++++++++++++++++++++---
 2 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/src/touch/adaptive_scaffold.rs b/src/touch/adaptive_scaffold.rs
index db378e9..d70b1d8 100644
--- a/src/touch/adaptive_scaffold.rs
+++ b/src/touch/adaptive_scaffold.rs
@@ -99,6 +99,12 @@ pub struct AdaptiveScaffold {
     #[prop_or_default]
     pub rail_expanded_query: Option<AttrValue>,
 
+    /// Let users switch the rail between its collapsed and expanded
+    /// layout (rail layout only, see [NavigationRail::expand_button]).
+    #[builder]
+    #[prop_or_default]
+    pub rail_expand_button: bool,
+
     /// Selection forwarded to the active navigator.
     #[builder(IntoPropValue, into_prop_value)]
     #[prop_or_default]
@@ -191,7 +197,8 @@ impl PwtAdaptiveScaffold {
         if let Some(on_select) = &props.on_select {
             rail = rail.on_select(on_select.clone());
         }
-        rail.router(props.router)
+        rail.expand_button(props.rail_expand_button)
+            .router(props.router)
     }
 
     fn build_bar(props: &AdaptiveScaffold) -> NavigationBar {
diff --git a/src/touch/navigation_rail.rs b/src/touch/navigation_rail.rs
index 7cbc494..05c5a01 100644
--- a/src/touch/navigation_rail.rs
+++ b/src/touch/navigation_rail.rs
@@ -10,7 +10,8 @@ use crate::dom::ViewportQuery;
 use crate::prelude::*;
 use crate::props::{ContainerBuilder, EventSubscriber, WidgetBuilder};
 use crate::state::{NavigationContext, NavigationContextExt, Selection};
-use crate::widget::Container;
+use crate::tr;
+use crate::widget::{ActionIcon, Container};
 
 use crate::widget::TabBarItem;
 
@@ -20,6 +21,12 @@ use pwt_macros::builder;
 /// where the expanded navigation rail is the recommended navigator.
 const DEFAULT_EXPANDED_QUERY: &str = "(min-width: 1200px)";
 
+/// Icon of the expand button in the collapsed layout, Material Design's menu icon.
+const EXPAND_ICON: &str = "fa fa-bars";
+
+/// Icon of the expand button in the expanded layout, Material Design's menu-open icon.
+const COLLAPSE_ICON: &str = "fa fa-outdent";
+
 /// Navigation rail
 ///
 /// # Automatic routing.
@@ -34,6 +41,9 @@ const DEFAULT_EXPANDED_QUERY: &str = "(min-width: 1200px)";
 /// side by side, each item a full-width pill) as soon as
 /// [expanded_query](Self::expanded_query) matches. Set [expanded](Self::expanded) to pin one of the
 /// two variants instead.
+///
+/// Setting [expand_button](Self::expand_button) additionally lets users switch between the two
+/// layouts themselves.
 
 // Note: This is Similatr to TabBar without keyboard support.
 #[derive(Properties, Clone, PartialEq)]
@@ -66,6 +76,22 @@ pub struct NavigationRail {
     #[prop_or(AttrValue::Static(DEFAULT_EXPANDED_QUERY))]
     pub expanded_query: AttrValue,
 
+    /// Show a button at the top of the rail that toggles between the collapsed and the expanded
+    /// layout.
+    ///
+    /// A manual toggle stays in effect until the layout gets selected anew, either by the viewport
+    /// crossing [expanded_query](Self::expanded_query) or by a changed
+    /// [expanded](Self::expanded) property.
+    #[builder]
+    #[prop_or_default]
+    pub expand_button: bool,
+
+    /// Callback emitted whenever the layout switches between collapsed and expanded, with `true`
+    /// meaning expanded.
+    #[builder_cb(IntoEventCallback, into_event_callback, bool)]
+    #[prop_or_default]
+    pub on_expand_change: Option<Callback<bool>>,
+
     /// Navigation bar items.
     items: Vec<TabBarItem>,
 
@@ -151,6 +177,7 @@ pub enum Msg {
     Select(Option<Key>, bool),
     SelectionChange(Selection),
     ExpandedQueryChange(bool),
+    ToggleExpanded,
 }
 
 #[doc(hidden)]
@@ -195,11 +222,16 @@ impl PwtNavigationRail {
         selection
     }
 
-    fn set_expanded(&mut self, expanded: bool) -> bool {
+    fn set_expanded(&mut self, props: &NavigationRail, expanded: bool) -> bool {
         if self.expanded == expanded {
             return false;
         }
         self.expanded = expanded;
+
+        if let Some(on_expand_change) = &props.on_expand_change {
+            on_expand_change.emit(expanded);
+        }
+
         true
     }
 }
@@ -314,9 +346,10 @@ impl Component for PwtNavigationRail {
                 // an explicitly pinned layout ignores the viewport
                 match props.expanded {
                     Some(_) => false,
-                    None => self.set_expanded(matches),
+                    None => self.set_expanded(props, matches),
                 }
             }
+            Msg::ToggleExpanded => self.set_expanded(props, !self.expanded),
         }
     }
 
@@ -333,7 +366,7 @@ impl Component for PwtNavigationRail {
             );
         }
         if query_changed || props.expanded != old_props.expanded {
-            self.set_expanded(props.expanded.unwrap_or(self.query_matches));
+            self.set_expanded(props, props.expanded.unwrap_or(self.query_matches));
         }
         true
     }
@@ -402,9 +435,28 @@ impl Component for PwtNavigationRail {
                 .into()
         });
 
+        let expand_button = props.expand_button.then(|| {
+            let (icon_class, aria_label) = match self.expanded {
+                true => (COLLAPSE_ICON, tr!("Collapse the navigation rail")),
+                false => (EXPAND_ICON, tr!("Expand the navigation rail")),
+            };
+
+            // the container gives the button the same icon column as the items, so both line up
+            Container::new()
+                .class("pwt-navigation-rail-expand-button")
+                .with_child(
+                    ActionIcon::new(icon_class)
+                        .tabindex(0)
+                        .aria_label(aria_label)
+                        .attribute("aria-expanded", self.expanded.to_string())
+                        .on_activate(ctx.link().callback(|_| Msg::ToggleExpanded)),
+                )
+        });
+
         Container::new()
             .class("pwt-navigation-rail")
             .class(self.expanded.then_some("pwt-navigation-rail-expanded"))
+            .with_optional_child(expand_button)
             .with_optional_child(props.leading.clone())
             .with_child(
                 Container::new()
-- 
2.47.3





  parent reply	other threads:[~2026-08-10 14:35 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-10 14:34 [PATCH yew-widget-toolkit/yew-widget-toolkit-assets 0/4] adaptive scaffold/navigation rail improvements Dominik Csapak
2026-08-10 14:34 ` [PATCH yew-widget-toolkit-assets 1/4] navigation rail: style the expand button Dominik Csapak
2026-08-10 14:34 ` [PATCH yew-widget-toolkit 2/4] touch: navigation rail: move auto layout from AdaptiveScaffold Dominik Csapak
2026-08-10 14:34 ` Dominik Csapak [this message]
2026-08-10 14:34 ` [PATCH yew-widget-toolkit 4/4] touch: adaptive scaffold: pass the rail group alignment through Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260810143536.31163-4-d.csapak@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=yew-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal