yew_nav_link/components/tabs.rs
1// SPDX-FileCopyrightText: 2024-2026 RAprogramm <andrey.rozanov-vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4//! # `NavTabs`
5//!
6//! Tab navigation container that wraps [`NavTab`](super::NavTab) items.
7//! Renders a `<ul>` with `role="tablist"` and optional full-width layout.
8//!
9//! # Example
10//!
11//! ```rust
12//! use yew::prelude::*;
13//! use yew_nav_link::components::{NavTab, NavTabs};
14//!
15//! #[component]
16//! fn TabBar() -> Html {
17//! html! {
18//! <NavTabs id="main-tabs">
19//! <NavTab active=true onclick={None}>{ "Tab 1" }</NavTab>
20//! <NavTab active=false onclick={None}>{ "Tab 2" }</NavTab>
21//! </NavTabs>
22//! }
23//! }
24//! ```
25//!
26//! # CSS Classes
27//!
28//! | Class | Condition |
29//! |-------|-----------|
30//! | `nav-tabs` | Always applied |
31//! | `nav-tabs-fill` | Applied when `full_width` is `true` |
32//!
33//! # Props
34//!
35//! | Prop | Type | Default | Description |
36//! |------|------|---------|-------------|
37//! | `full_width` | `bool` | `false` | Stretch tabs to fill width |
38//! | `role` | `&'static str` | `"tablist"` | ARIA role |
39//! | `id` | `Option<&'static str>` | `None` | Container id |
40//! | `classes` | `Classes` | — | Additional CSS classes |
41//! | `children` | `Children` | — | Tab items |
42
43use yew::prelude::*;
44
45/// Properties for the [`NavTabs`] component.
46///
47/// | Prop | Type | Default | Description |
48/// |------|------|---------|-------------|
49/// | `full_width` | `bool` | `false` | Stretch tabs to fill width |
50/// | `role` | `&'static str` | `"tablist"` | ARIA role |
51/// | `id` | `Option<&'static str>` | `None` | Container id |
52/// | `classes` | `Classes` | — | Additional CSS classes |
53/// | `children` | `Children` | — | Tab items |
54#[derive(Properties, Clone, PartialEq, Debug)]
55pub struct NavTabsProps {
56 /// Additional CSS classes applied to the tabs container.
57 #[prop_or_default]
58 pub classes: Classes,
59
60 /// ARIA role for the tab list. Defaults to `"tablist"`.
61 #[prop_or("tablist")]
62 pub role: &'static str,
63
64 /// Optional `id` attribute for the tabs container.
65 #[prop_or_default]
66 pub id: Option<&'static str>,
67
68 /// Whether tabs should stretch to fill the full width of the container.
69 #[prop_or_default]
70 pub full_width: bool,
71
72 /// Tab items rendered inside the container.
73 pub children: Children
74}
75
76/// Tab navigation container that wraps [`NavTab`](super::NavTab) items.
77///
78/// Renders a `<ul>` element with ARIA `role="tablist"`.
79///
80/// # CSS Classes
81///
82/// - `nav-tabs` - Always applied
83/// - `nav-tabs-fill` - Applied when `full_width` is `true`
84#[function_component]
85pub fn NavTabs(props: &NavTabsProps) -> Html {
86 let mut classes = props.classes.clone();
87 classes.push("nav-tabs");
88
89 if props.full_width {
90 classes.push("nav-tabs-fill");
91 }
92
93 html! {
94 <ul
95 {classes}
96 id={props.id}
97 role={props.role}
98 >
99 { for props.children.iter() }
100 </ul>
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn nav_tabs_props_default() {
110 let props = NavTabsProps {
111 classes: Classes::default(),
112 role: "tablist",
113 id: None,
114 full_width: false,
115 children: Children::new(vec![])
116 };
117
118 assert_eq!(props.role, "tablist");
119 assert!(!props.full_width);
120 }
121
122 #[test]
123 fn nav_tabs_full_width() {
124 let props = NavTabsProps {
125 classes: Classes::default(),
126 role: "tablist",
127 id: Some("main-tabs"),
128 full_width: true,
129 children: Children::new(vec![])
130 };
131
132 assert!(props.full_width);
133 assert_eq!(props.id, Some("main-tabs"));
134 }
135}