1#![doc(
2 html_logo_url = "https://github.com/next-rs/yew-navbar/assets/62179149/9b3478b0-aa79-496c-9364-f9bbba01217f",
3 html_favicon_url = "https://github.com/next-rs/yew-navbar/assets/62179149/654266b2-aa7e-4309-a0e9-b3939f739284"
4)]
5
6use yew::prelude::*;
102
103const NAVBAR_CLASS: &str = "fixed top-0 left-0 w-full bg-black text-white font-roboto z-20";
104const LOGO_CLASS: &str = "flex items-center";
105const LOGO_IMG_CLASS: &str = "w-32 md:w-40";
106const MENU_TOGGLE_CLASS: &str = "btn-menu ml-4 md:hidden cursor-pointer";
107const LINE_CLASS: &str = "line h-1 mb-1 bg-white transition-transform transform origin-center";
108const FLEX_CONTAINER_CLASS: &str = "flex justify-between items-center";
109const HIDDEN_MD_CLASS: &str = "hidden md:flex nav-wrap";
110const NAV_CLASS: &str = "flex flex-grow justify-end items-center space-x-4 md:space-x-8";
111const MENU_ITEM_CLASS: &str = "nav-link text-white hover:text-gray-300 transition-colors";
112const BUTTON_LINK_CLASS: &str =
113 "rounded-full py-2 px-6 bg-blue-500 text-white text-lg transition-colors hover:bg-blue-600";
114const DROPDOWN_CLASS: &str =
115 "absolute top-full left-0 mt-2 bg-black text-white p-2 rounded shadow-lg block md:hidden";
116const DROPDOWN_ITEM_CLASS: &str = "border-b border-blue-500";
117const SEARCH_INPUT_CLASS: &str = "hidden md:block rounded-full py-2 px-4 bg-gray-800 text-white text-lg placeholder-gray-500 focus:outline-none";
118
119#[derive(Clone, Properties, PartialEq)]
121pub struct NavbarProps {
122 #[prop_or_default]
126 pub menus: Vec<Menu>,
127
128 #[prop_or_default]
130 pub button_href: &'static str,
131
132 #[prop_or_default]
134 pub button_text: &'static str,
135
136 #[prop_or(NAVBAR_CLASS)]
140 pub navbar_class: &'static str,
141
142 #[prop_or(LOGO_CLASS)]
144 pub logo_class: &'static str,
145
146 #[prop_or(MENU_TOGGLE_CLASS)]
148 pub menu_toggle_class: &'static str,
149
150 #[prop_or(LINE_CLASS)]
152 pub line_class: &'static str,
153
154 #[prop_or(FLEX_CONTAINER_CLASS)]
156 pub flex_container_class: &'static str,
157
158 #[prop_or(HIDDEN_MD_CLASS)]
160 pub hidden_md_class: &'static str,
161
162 #[prop_or(NAV_CLASS)]
164 pub nav_class: &'static str,
165
166 #[prop_or(MENU_ITEM_CLASS)]
168 pub menu_item_class: &'static str,
169
170 #[prop_or_default]
172 pub button_class: &'static str,
173
174 #[prop_or(BUTTON_LINK_CLASS)]
176 pub button_link_class: &'static str,
177
178 #[prop_or(DROPDOWN_ITEM_CLASS)]
180 pub dropdown_item_class: &'static str,
181
182 #[prop_or(DROPDOWN_CLASS)]
184 pub dropdown_class: &'static str,
185
186 #[prop_or(SEARCH_INPUT_CLASS)]
188 pub search_input_class: &'static str,
189
190 #[prop_or("images/logo.png")]
194 pub logo_src: &'static str,
195
196 #[prop_or("logo")]
198 pub logo_alt: &'static str,
199
200 #[prop_or(LOGO_IMG_CLASS)]
202 pub logo_img_class: &'static str,
203
204 #[prop_or("/")]
206 pub logo_link: &'static str,
207}
208
209impl Default for NavbarProps {
210 fn default() -> Self {
211 Self {
212 menus: Default::default(),
213 button_href: Default::default(),
214 button_text: Default::default(),
215 navbar_class: NAVBAR_CLASS,
216 logo_class: LOGO_CLASS,
217 menu_toggle_class: MENU_TOGGLE_CLASS,
218 line_class: LINE_CLASS,
219 flex_container_class: FLEX_CONTAINER_CLASS,
220 hidden_md_class: HIDDEN_MD_CLASS,
221 nav_class: NAV_CLASS,
222 menu_item_class: MENU_ITEM_CLASS,
223 button_class: Default::default(),
224 button_link_class: BUTTON_LINK_CLASS,
225 dropdown_item_class: DROPDOWN_ITEM_CLASS,
226 dropdown_class: DROPDOWN_CLASS,
227 search_input_class: SEARCH_INPUT_CLASS,
228 logo_src: "images/logo.png",
229 logo_alt: "logo",
230 logo_img_class: LOGO_CLASS,
231 logo_link: "/",
232 }
233 }
234}
235
236#[derive(Clone, PartialEq)]
237pub struct Menu {
238 pub id: usize,
239 pub link: &'static str,
240 pub name: &'static str,
241}
242
243#[function_component(Navbar)]
244pub fn navbar_component(props: &NavbarProps) -> Html {
245 let is_dropdown_visible = use_state(|| false);
246
247 html! {
248 <section id="navbar" class={props.navbar_class}>
249 <div class="container mx-auto px-4 py-2">
250 { render_navbar_content(props, is_dropdown_visible) }
251 </div>
252 </section>
253 }
254}
255
256fn render_navbar_content(props: &NavbarProps, is_dropdown_visible: UseStateHandle<bool>) -> Html {
257 html! {
258 <div class={props.flex_container_class}>
259 { render_logo(props) }
260 { render_menu(props) }
261 { render_menu_toggle(props, is_dropdown_visible.clone()) }
262 { render_dropdown_menu(props, is_dropdown_visible) }
263 </div>
264 }
265}
266
267fn render_logo(props: &NavbarProps) -> Html {
268 html! {
269 <div id="logo" class={props.logo_class}>
270 <a href={props.logo_link} class="nav-link">
271 <img src={props.logo_src} alt={props.logo_alt} class={props.logo_img_class} />
272 </a>
273 </div>
274 }
275}
276
277fn render_menu_toggle(props: &NavbarProps, is_dropdown_visible: UseStateHandle<bool>) -> Html {
278 let onclick = {
279 let is_dropdown_visible = is_dropdown_visible.clone();
280
281 Callback::from(move |_| {
282 is_dropdown_visible.set(!*is_dropdown_visible);
283 })
284 };
285
286 html! {
287 <div class={props.menu_toggle_class} onclick={onclick}>
288 <div class={format!("{} w-6", props.line_class)} />
289 <div class={format!("{} w-8", props.line_class)} />
290 <div class={format!("{} w-6", props.line_class)} />
291 </div>
292 }
293}
294
295fn render_menu(props: &NavbarProps) -> Html {
296 html! {
297 <div class={props.nav_class}>
298 <div class={props.hidden_md_class}>
299 <nav id="mainnav" class="mainnav" data-menu-style="horizontal">
300 <ul class="flex space-x-4 md:space-x-8">
301 { for props.menus.iter().map(|menu| render_menu_item(menu, props)) }
302 </ul>
303 </nav>
304 </div>
305 <input type="text" placeholder="Search..." class={props.search_input_class} />
306 { render_button(props) }
307 </div>
308 }
309}
310
311fn render_menu_item(menu: &Menu, props: &NavbarProps) -> Html {
312 html! {
313 <li key={menu.id}>
314 <a class={props.menu_item_class} href={menu.link.to_string()}>{ menu.name }</a>
315 </li>
316 }
317}
318
319fn render_button(props: &NavbarProps) -> Html {
320 html! {
321 <div class={props.button_class}>
322 <a href={props.button_href} class={props.button_link_class} target="_blank">
323 <b>{ props.button_text }</b>
324 </a>
325 </div>
326 }
327}
328
329fn render_dropdown_menu(props: &NavbarProps, is_dropdown_visible: UseStateHandle<bool>) -> Html {
330 if *is_dropdown_visible {
331 html! {
332 <div class={props.dropdown_class}>
333 <ul>
334 { for props.menus.iter().map(|menu| render_dropdown_item(props, menu)) }
335 </ul>
336 </div>
337 }
338 } else {
339 html! {}
340 }
341}
342
343fn render_dropdown_item(props: &NavbarProps, menu: &Menu) -> Html {
344 html! {
345 <li key={menu.id} class={props.dropdown_item_class}>
346 <a href={menu.link.to_string()}>{ menu.name }</a>
347 </li>
348 }
349}