Skip to main content

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}