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