yew_navbar/
lib.rs

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
6//! # Yew Navbar - Documentation
7//!
8//! Welcome to the official documentation for Yew Navbar. This library provides a customizable
9//! Yew component for creating responsive top-level navigation bar with various styling options.
10//!
11//! ## Usage
12//!
13//! To use this library, add the following dependency to your `Cargo.toml` file:
14//!
15//! ```sh
16//! cargo add yew-navbar
17//! ```
18//!
19//! To integrate the library into your Yew application, you can use the `Navbar` component.
20//! Here's a basic example of how to use it:
21//!
22//! ```rust
23//! use yew::prelude::*;
24//! use yew_navbar::Navbar;
25//!
26//! #[function_component]
27//! pub fn MyNavbar() -> Html {
28//!     let menus = vec![/* define your menu items here */];
29//!
30//!     html! {
31//!         <Navbar menus={menus} />
32//!         // Your component logic here...
33//!     }
34//! }
35//! ```
36//!
37//! For more detailed information, check the [examples] provided in the library.
38//!
39//! [examples]: https://github.com/next-rs/yew-navbar/tree/main/examples
40//!
41//! ## Configuration
42//!
43//! The `Navbar` component allows you to customize its appearance through the `NavbarProps` structure.
44//! You can adjust properties such as menu items, button text, styling classes, and more.
45//! Refer to the `NavbarProps` documentation for detailed configuration options.
46//!
47//! ```rust
48//! use yew::prelude::*;
49//! use yew_navbar::{NavbarProps, Navbar};
50//!
51//! let navbar_props = NavbarProps {
52//!     menus: vec![/* define your menu items here */],
53//!     // Add other properties as needed...
54//!     // ...,
55//!     ..NavbarProps::default()
56//! };
57//!
58//! let navbar_component = html! {
59//!     <Navbar ..navbar_props />
60//! };
61//! ```
62//!
63//! ## NavbarProps Structure
64//!
65//! The `NavbarProps` structure is used to configure the appearance and behavior of the `Navbar` component.
66//!
67//! ### Properties
68//!
69//! - `menus`: A vector of `Menu` structs representing the navigation menu items.
70//! - `button_href`: The href attribute for the optional button in the navbar.
71//! - `button_text`: The text content for the optional button in the navbar.
72//! - `navbar_class`: CSS class for styling the main navbar container.
73//! - `logo_class`: CSS class for styling the logo container.
74//! - `menu_toggle_class`: CSS class for styling the menu toggle button.
75//! - `line_class`: CSS class for styling the lines of the menu toggle button.
76//! - `flex_container_class`: CSS class for styling the flex container of the navbar.
77//! - `hidden_md_class`: CSS class for hiding elements on medium-sized screens.
78//! - `nav_class`: CSS class for styling the navigation menu.
79//! - `menu_item_class`: CSS class for styling individual menu items.
80//! - `button_class`: CSS class for styling the optional button in the navbar.
81//! - `button_link_class`: CSS class for styling the optional button as a link.
82//! - `dropdown_item_class`: CSS class for styling items in the dropdown menu.
83//! - `dropdown_class`: CSS class for styling the dropdown menu container.
84//! - `search_input_class`: CSS class for styling the search input.
85//! - `logo_src`: The source URL for the logo image.
86//! - `logo_alt`: The alt text for the logo image.
87//! - `logo_img_class`: CSS class for styling the logo image.
88//! - `logo_link`: The href attribute for the logo link.
89//!
90//! ## Contribution
91//!
92//! If you encounter any issues or have suggestions for improvements, feel free to contribute
93//! to the [GitHub repository](https://github.com/next-rs/yew-navbar). We appreciate your feedback
94//! and involvement in making Yew Navbar better!
95//!
96//! ## Acknowledgments
97//!
98//! Special thanks to the Yew community and contributors for such an amazing framework.
99//!
100
101use 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/// Properties for the Navbar component
120#[derive(Clone, Properties, PartialEq)]
121pub struct NavbarProps {
122    // Main props
123
124    /// Vector of menu items to be displayed in the navbar.
125    #[prop_or_default]
126    pub menus: Vec<Menu>,
127
128    /// Href attribute for the optional button in the navbar.
129    #[prop_or_default]
130    pub button_href: &'static str,
131
132    /// Text content for the optional button in the navbar.
133    #[prop_or_default]
134    pub button_text: &'static str,
135
136    // Styling props
137
138    /// CSS class for styling the main navbar container.
139    #[prop_or(NAVBAR_CLASS)]
140    pub navbar_class: &'static str,
141
142    /// CSS class for styling the logo container.
143    #[prop_or(LOGO_CLASS)]
144    pub logo_class: &'static str,
145
146    /// CSS class for styling the menu toggle button.
147    #[prop_or(MENU_TOGGLE_CLASS)]
148    pub menu_toggle_class: &'static str,
149
150    /// CSS class for styling the lines of the menu toggle button.
151    #[prop_or(LINE_CLASS)]
152    pub line_class: &'static str,
153
154    /// CSS class for styling the flex container of the navbar.
155    #[prop_or(FLEX_CONTAINER_CLASS)]
156    pub flex_container_class: &'static str,
157
158    /// CSS class for hiding elements on medium-sized screens.
159    #[prop_or(HIDDEN_MD_CLASS)]
160    pub hidden_md_class: &'static str,
161
162    /// CSS class for styling the navigation menu.
163    #[prop_or(NAV_CLASS)]
164    pub nav_class: &'static str,
165
166    /// CSS class for styling individual menu items.
167    #[prop_or(MENU_ITEM_CLASS)]
168    pub menu_item_class: &'static str,
169
170    /// CSS class for styling the optional button in the navbar.
171    #[prop_or_default]
172    pub button_class: &'static str,
173
174    /// CSS class for styling the optional button as a link.
175    #[prop_or(BUTTON_LINK_CLASS)]
176    pub button_link_class: &'static str,
177
178    /// CSS class for styling items in the dropdown menu.
179    #[prop_or(DROPDOWN_ITEM_CLASS)]
180    pub dropdown_item_class: &'static str,
181
182    /// CSS class for styling the dropdown menu container.
183    #[prop_or(DROPDOWN_CLASS)]
184    pub dropdown_class: &'static str,
185
186    /// CSS class for styling the search input.
187    #[prop_or(SEARCH_INPUT_CLASS)]
188    pub search_input_class: &'static str,
189
190    // Logo props
191
192    /// Source URL for the logo image.
193    #[prop_or("images/logo.png")]
194    pub logo_src: &'static str,
195
196    /// Alt text for the logo image.
197    #[prop_or("logo")]
198    pub logo_alt: &'static str,
199
200    /// CSS class for styling the logo image.
201    #[prop_or(LOGO_IMG_CLASS)]
202    pub logo_img_class: &'static str,
203
204    /// Href attribute for the logo link.
205    #[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}