[yew-devel] [PATCH yew-widget-toolkit] touch: fab menu: intoduce sheet variant

Dominik Csapak d.csapak at proxmox.com
Tue Jul 1 08:57:25 CEST 2025


which is also the new default. Exposing the items via a sheet (bottom
SideDialog) make them more readable and accessible in more situations.

Keep the old style via the `Material3` variant.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 src/touch/fab_menu.rs | 125 +++++++++++++++++++++++++++++-------------
 1 file changed, 87 insertions(+), 38 deletions(-)

diff --git a/src/touch/fab_menu.rs b/src/touch/fab_menu.rs
index 6a4f3f7..db97539 100644
--- a/src/touch/fab_menu.rs
+++ b/src/touch/fab_menu.rs
@@ -1,14 +1,24 @@
 use yew::prelude::*;
 
-use crate::css::ColorScheme;
-use crate::props::{ContainerBuilder, EventSubscriber, WidgetBuilder};
-use crate::widget::{Button, Container};
+use crate::css::{self, ColorScheme};
+use crate::props::{ContainerBuilder, CssPaddingBuilder, EventSubscriber, WidgetBuilder};
+use crate::touch::{SideDialog, SideDialogController};
+use crate::tr;
+use crate::widget::{Button, Column, Container};
 
 use pwt_macros::{builder, widget};
 
 use super::fab::FabSize;
 use super::Fab;
 
+/// [FabMenu] variant
+#[derive(Copy, Clone, PartialEq, Debug, Default)]
+pub enum FabMenuVariant {
+    #[default]
+    Sheet,
+    Material3,
+}
+
 /// [FabMenu] direction.
 #[derive(Copy, Clone, PartialEq)]
 pub enum FabMenuDirection {
@@ -90,6 +100,11 @@ pub struct FabMenu {
     #[builder]
     pub align: FabMenuAlign,
 
+    /// Menu variant
+    #[prop_or_default]
+    #[builder]
+    pub variant: FabMenuVariant,
+
     /// Child buttons, which popup when main button is pressed.
     ///
     /// We currently support up to 5 children.
@@ -193,51 +208,85 @@ impl Component for PwtFabMenu {
             FabMenuColor::Tertiary => (ColorScheme::Tertiary, ColorScheme::TertiaryContainer),
         };
 
+        let (fab_size, fab_classes) = match (props.variant, self.show_items) {
+            (FabMenuVariant::Material3, true) => (FabSize::Small, classes!(close_color, "rounded")),
+            (_, false) | (FabMenuVariant::Sheet, true) => (props.size, classes!()),
+        };
+
         let main_button = Fab::new(main_icon_class)
-            .size(if self.show_items {
-                FabSize::Small
-            } else {
-                props.size
-            })
+            .size(fab_size)
             .class(props.main_button_class.clone())
-            .class(if self.show_items { close_color } else { color })
-            .class(self.show_items.then_some("rounded"))
+            .class(fab_classes)
             .on_activate(ctx.link().callback(|_| Msg::Toggle));
 
-        let mut container = Container::new()
-            .with_std_props(&props.std_props)
-            .listeners(&props.listeners)
-            .class("pwt-fab-menu-container")
-            .class(self.show_items.then_some("active"))
-            .onkeydown({
-                let link = ctx.link().clone();
-                move |event: KeyboardEvent| {
-                    if event.key() == "Escape" {
-                        link.send_message(Msg::Close)
-                    }
-                }
-            });
-
-        for (i, child) in props.children.iter().enumerate() {
+        let btn_class = match props.variant {
+            FabMenuVariant::Sheet => classes!("pwt-button-text"),
+            FabMenuVariant::Material3 => classes!(color, "pwt-fab-menu-item", "medium"),
+        };
+
+        let children = props.children.iter().enumerate().filter_map(|(i, child)| {
             if i >= 5 {
                 log::error!("FabMenu only supports 5 child buttons.");
-                break;
+                return None;
             }
 
             let on_activate = child.on_activate.clone();
             let link = ctx.link().clone();
 
-            let child = Button::new(child.text.clone())
-                .icon_class(child.icon.clone())
-                .class("medium")
-                .class(color)
-                .class("pwt-fab-menu-item")
-                .on_activate(move |event| {
-                    link.send_message(Msg::Toggle);
-                    on_activate.emit(event);
-                });
-            container.add_child(child);
-        }
+            Some(
+                Button::new(child.text.clone())
+                    .icon_class(child.icon.clone())
+                    .class(btn_class.clone())
+                    .on_activate(move |event| {
+                        link.send_message(Msg::Toggle);
+                        on_activate.emit(event);
+                    })
+                    .into(),
+            )
+        });
+
+        let container: Option<Html> = match props.variant {
+            FabMenuVariant::Sheet => {
+                let controller = SideDialogController::new();
+                self.show_items.then_some(
+                    SideDialog::new()
+                        .controller(controller.clone())
+                        .location(crate::touch::SideDialogLocation::Bottom)
+                        .on_close(ctx.link().callback(|_| Msg::Toggle))
+                        .with_child(
+                            Column::new()
+                                .class(css::FlexFit)
+                                .padding(2)
+                                .gap(1)
+                                .children(children)
+                                .with_child(html!(<hr />))
+                                .with_child(
+                                    Button::new(tr!("Cancel"))
+                                        .class("pwt-button-text")
+                                        .on_activate(move |_| controller.close_dialog()),
+                                ),
+                        )
+                        .into(),
+                )
+            }
+            FabMenuVariant::Material3 => Some(
+                Container::new()
+                    .with_std_props(&props.std_props)
+                    .listeners(&props.listeners)
+                    .class("pwt-fab-menu-container")
+                    .class(self.show_items.then_some("active"))
+                    .onkeydown({
+                        let link = ctx.link().clone();
+                        move |event: KeyboardEvent| {
+                            if event.key() == "Escape" {
+                                link.send_message(Msg::Close)
+                            }
+                        }
+                    })
+                    .children(children)
+                    .into(),
+            ),
+        };
 
         Container::new()
             .with_std_props(&props.std_props)
@@ -261,7 +310,7 @@ impl Component for PwtFabMenu {
                     })
                     .with_child(main_button),
             )
-            .with_child(container)
+            .with_optional_child(container)
             .into()
     }
 }
-- 
2.39.5





More information about the yew-devel mailing list