yew-nav 0.22.0

A nav library for Yew
Documentation
use yew::prelude::*;
use yew_router::{Routable, components::Link, hooks::use_route};

#[hook]
pub fn use_is_active_route<R: Routable + 'static>(to: &R) -> bool {
    use_route::<R>().is_some_and(|route| route == *to)
}

#[derive(Properties, PartialEq)]
pub struct NavLinkProps<R: PartialEq> {
    pub to: R,

    #[prop_or_default]
    pub classes: Classes,

    #[prop_or_default]
    pub inactive_classes: Classes,

    #[prop_or_default]
    pub active_classes: Classes,

    #[prop_or_default]
    pub children: Html,
}

#[component]
pub fn NavLink<R: Routable + 'static>(
    NavLinkProps {
        to,
        classes,
        inactive_classes,
        active_classes,
        children,
    }: &NavLinkProps<R>,
) -> Html {
    let is_active_route = use_is_active_route(to);

    html! {
        <Link<R>
            classes={classes!(classes.clone(), (!is_active_route).then_some(inactive_classes.clone()), is_active_route.then_some(active_classes.clone()))}
            to={to.clone()}
        >
            { children.clone() }
        </Link<R>>
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct NavMenuState {
    pub shown: bool,
}

pub enum NavMenuStateAction {
    Open,
    Close,
    Toggle,
}

impl Reducible for NavMenuState {
    type Action = NavMenuStateAction;

    fn reduce(self: std::rc::Rc<Self>, action: Self::Action) -> std::rc::Rc<Self> {
        match action {
            NavMenuStateAction::Open => NavMenuState { shown: true },
            NavMenuStateAction::Close => NavMenuState { shown: false },
            NavMenuStateAction::Toggle => NavMenuState { shown: !self.shown },
        }
        .into()
    }
}

pub type NavMenuStateContext = UseReducerHandle<NavMenuState>;

#[derive(Properties, Debug, PartialEq)]
pub struct NavMenuStateProviderProps {
    #[prop_or_default]
    pub children: Html,
}

#[component]
pub fn NavMenuStateProvider(props: &NavMenuStateProviderProps) -> Html {
    let nav_state_reducer = use_reducer(|| NavMenuState { shown: false });

    html! {
        <ContextProvider<NavMenuStateContext> context={nav_state_reducer}>
            {props.children.clone()}
        </ContextProvider<NavMenuStateContext>>
    }
}

#[derive(Properties, PartialEq)]
pub struct NavMenuButtonProps {
    #[prop_or_default]
    pub classes: Classes,

    #[prop_or_default]
    pub children: Html,
}

#[component]
pub fn NavMenuButton(NavMenuButtonProps { classes, children }: &NavMenuButtonProps) -> Html {
    let nav_menu_state_context =
        use_context::<NavMenuStateContext>().expect("no nav menu state context found");

    let on_click = {
        let nav_menu_state_context = nav_menu_state_context.clone();

        Callback::from(move |_| {
            nav_menu_state_context.dispatch(NavMenuStateAction::Toggle);
        })
    };

    html! {
        <button
            onclick={on_click}
            class={classes!(classes.clone())}
        >
            { children.clone() }
        </button>
    }
}

#[hook]
pub fn use_hide_nav_menu<T>(deps: T)
where
	T: PartialEq + 'static
{
	let nav_menu_state_context = use_context::<NavMenuStateContext>().expect("no nav menu state found");

	use_effect_with(deps, move |_| {
		nav_menu_state_context.dispatch(NavMenuStateAction::Close);
	});
}