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}