Skip to content

Click-out functionality #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Raflos10 opened this issue Apr 24, 2025 · 12 comments
Closed

Click-out functionality #18

Raflos10 opened this issue Apr 24, 2025 · 12 comments

Comments

@Raflos10
Copy link
Contributor

Hey, I just gave this library a try for the first time. Excellent job on it, I think it's great.
To be honest though, I was a bit disappointed when I found that clicking outside of dropdown menus doesn't close them.
This is a functionality that will be needed for many components. For example, a date selector. Probably anything that "hovers" on the page, will need it.

It seems that the popular solution is using a full-size clickable div. I do this for modals/dialogs, but for other types of menus, as it was mentioned in the discord, this approach has potential issues (interrupting page functionality, dealing with multiple hovering components, etc etc.)

A native solution might be the way to go, even if it requires a different implementation for each platform. I think it's worth it for something fundamental like this. Let me know what you think.

@Raflos10
Copy link
Contributor Author

My workaround "backdrop" component:

use dioxus::prelude::*;

#[derive(Clone, PartialEq, Hash, Debug, Default)]
#[allow(dead_code)]
pub enum BlurLevel {
    #[default]
    None,
    Small,
    Medium,
}

impl BlurLevel {
    fn as_str(&self) -> &'static str {
        match self {
            BlurLevel::None => "backdrop-blur-none",
            BlurLevel::Small => "backdrop-blur-sm",
            BlurLevel::Medium => "backdrop-blur-md",
        }
    }
}

#[component]
pub fn Backdrop(
    is_active: bool,
    blur_level: Option<BlurLevel>,
    onclick: Callback<Event<MouseData>>,
    z: i32,
) -> Element {
    let class = if is_active {
        &format!(
            "fixed inset-0 flex justify-center items-center {} overflow-hidden transition-[opacity,backdrop-blur] duration-500",
            &blur_level.unwrap_or_default().as_str()
            )
    } else {
        "fixed invisible opacity-0 backdrop-blur-none"
    };
    let z = if is_active { z } else { -1 };

    rsx! {
        div { class, style: "z-index: {z}", onclick }
    }
}

@Raflos10
Copy link
Contributor Author

Usage with the dropdown menu:

use dioxus::prelude::*;
use dioxus_primitives::dropdown_menu::{
    DropdownMenu as DioxusDropdownMenu, DropdownMenuContent, DropdownMenuTrigger,
};

use crate::components::general::Backdrop;

#[derive(Clone, PartialEq, Default)]
#[allow(dead_code)]
pub enum MenuPosition {
    #[default]
    BottomLeft,
    BottomRight,
    TopLeft,
    TopRight,
}

impl MenuPosition {
    fn as_classes(&self) -> &'static str {
        match self {
            MenuPosition::BottomLeft => "top-full left-0",
            MenuPosition::BottomRight => "top-full right-0",
            MenuPosition::TopLeft => "bottom-full left-0",
            MenuPosition::TopRight => "bottom-full right-0",
        }
    }
}

#[component]
pub fn DropdownMenu(
    trigger_element: Element,
    menu_content: Element,
    position: Option<MenuPosition>,
) -> Element {
    let mut is_open = use_signal(|| false);
    let position = position.unwrap_or_default();

    rsx! {
        div { class: "relative",
            Backdrop {
                is_active: is_open(),
                onclick: move |_| is_open.set(false),
                z: 20,
            }
            DioxusDropdownMenu {
                open: is_open,
                on_open_change: move |state| is_open.set(state),
                DropdownMenuTrigger {
                    div { onclick: move |_| is_open.set(true), {trigger_element} }
                }
                DropdownMenuContent { class: "absolute z-21 {position.as_classes()}",
                    div { onclick: move |_| is_open.set(false), {menu_content} }
                }
            }
        }
    }
}

@DogeDark
Copy link
Contributor

One idea was to set focus within the dropdown, and then listen to onfocusout to close the dropdown.

@wheregmis
Copy link
Contributor

@Raflos10 Thank you for reporting the issue with your workaround. I have given an attempt on mentioned branch. Do let me know if thats helpful or not.

@Raflos10
Copy link
Contributor Author

@Raflos10 Thank you for reporting the issue with your workaround. I have given an attempt on mentioned branch. Do let me know if thats helpful or not.

@wheregmis Hey I just got around to trying it out, seems to work. Great job!
It might also be a good idea to make onfocusout an optional callback, which defaults to this behavior.
I'm looking forward to this getting merged in.

@Raflos10
Copy link
Contributor Author

Ah maybe I spoke too soon, it seems that onfocusout closes the menu before the content can receive any click events

@wheregmis
Copy link
Contributor

@Raflos10 Thank you. I think maybe this is fixable? onfocusout closes the menu before the content can receive any click events. I will give it an another attempt. Thank you for trying out.

@wheregmis
Copy link
Contributor

@Raflos10 sorry for pinging you again. I have given another attempt. Now we should be receiving click events and item should be selected. Do let me know if it works.

@Raflos10
Copy link
Contributor Author

@wheregmis Hey I gave it a try, unfortunately though, clicking outside the dropdown now doesn't seem to do anything.
Also, I wasn't using DropdownMenuItem at all before, which I actually think is more flexible. But now you have to use it or clicking something inside the menu doesn't work either.

@Raflos10
Copy link
Contributor Author

Ah sorry I was on the wrong branch.
On the correct branch now, I get this compiler error:

[cargo] error[E0282]: type annotations needed
   --> /home/raflos/.cargo/git/checkouts/components-5dad87fd72f717f6/9d6065f/primitives/src/dropdown_menu.rs:255:23
    |
255 |                 move |e| {
    |                       ^
256 |                     e.stop_propagation();
    |                     - type must be known at this point
    |
help: consider giving this closure parameter an explicit type
    |
255 |                 move |e: /* Type */| {
    |                        ++++++++++++

@wheregmis
Copy link
Contributor

Should be fixed now, I was running it on 0.7 alpha where it wasnt showing that error.

@ealmloff
Copy link
Member

ealmloff commented Jun 11, 2025

Implemented in #22

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants