yew_nav/
lib.rs

1use yew::prelude::*;
2use yew_router::{Routable, components::Link, hooks::use_route};
3
4#[hook]
5pub fn use_is_active_route<R: Routable + 'static>(to: &R) -> bool {
6    use_route::<R>().is_some_and(|route| route == *to)
7}
8
9#[derive(Properties, PartialEq)]
10pub struct NavLinkProps<R: PartialEq> {
11    pub to: R,
12
13    #[prop_or_default]
14    pub classes: Classes,
15
16    #[prop_or_default]
17    pub inactive_classes: Classes,
18
19    #[prop_or_default]
20    pub active_classes: Classes,
21
22    #[prop_or_default]
23    pub children: Html,
24}
25
26#[function_component]
27pub fn NavLink<R: Routable + 'static>(
28    NavLinkProps {
29        to,
30        classes,
31        inactive_classes,
32        active_classes,
33        children,
34    }: &NavLinkProps<R>,
35) -> Html {
36    let is_active_route = use_is_active_route(to);
37
38    html! {
39        <Link<R>
40            classes={classes!(classes.clone(), (!is_active_route).then_some(inactive_classes.clone()), is_active_route.then_some(active_classes.clone()))}
41            to={to.clone()}
42        >
43            { children.clone() }
44        </Link<R>>
45    }
46}
47
48#[derive(Clone, Debug, PartialEq)]
49pub struct NavMenuState {
50    pub shown: bool,
51}
52
53pub enum NavMenuStateAction {
54    Open,
55    Close,
56    Toggle,
57}
58
59impl Reducible for NavMenuState {
60    type Action = NavMenuStateAction;
61
62    fn reduce(self: std::rc::Rc<Self>, action: Self::Action) -> std::rc::Rc<Self> {
63        match action {
64            NavMenuStateAction::Open => NavMenuState { shown: true },
65            NavMenuStateAction::Close => NavMenuState { shown: false },
66            NavMenuStateAction::Toggle => NavMenuState { shown: !self.shown },
67        }
68        .into()
69    }
70}
71
72pub type NavMenuStateContext = UseReducerHandle<NavMenuState>;
73
74#[derive(Properties, Debug, PartialEq)]
75pub struct NavMenuStateProviderProps {
76    #[prop_or_default]
77    pub children: Html,
78}
79
80#[function_component]
81pub fn NavMenuStateProvider(props: &NavMenuStateProviderProps) -> Html {
82    let nav_state_reducer = use_reducer(|| NavMenuState { shown: false });
83
84    html! {
85        <ContextProvider<NavMenuStateContext> context={nav_state_reducer}>
86            {props.children.clone()}
87        </ContextProvider<NavMenuStateContext>>
88    }
89}
90
91#[derive(Properties, PartialEq)]
92pub struct NavMenuButtonProps {
93    #[prop_or_default]
94    pub classes: Classes,
95
96    #[prop_or_default]
97    pub children: Html,
98}
99
100#[function_component]
101pub fn NavMenuButton(NavMenuButtonProps { classes, children }: &NavMenuButtonProps) -> Html {
102    let nav_menu_state_context =
103        use_context::<NavMenuStateContext>().expect("no nav menu state context found");
104
105    let on_click = {
106        let nav_menu_state_context = nav_menu_state_context.clone();
107
108        Callback::from(move |_| {
109            nav_menu_state_context.dispatch(NavMenuStateAction::Toggle);
110        })
111    };
112
113    html! {
114        <button
115            onclick={on_click}
116            class={classes!(classes.clone())}
117        >
118            { children.clone() }
119        </button>
120    }
121}
122
123#[hook]
124pub fn use_hide_nav_menu<T>(deps: T)
125where
126	T: PartialEq + 'static
127{
128	let nav_menu_state_context = use_context::<NavMenuStateContext>().expect("no nav menu state found");
129
130	use_effect_with(deps, move |_| {
131		nav_menu_state_context.dispatch(NavMenuStateAction::Close);
132	});
133}