#[cfg(feature = "yew-nested-router")]
mod router;
#[cfg(feature = "yew-nested-router")]
pub use router::*;
use std::collections::HashSet;
use crate::ouia;
use crate::prelude::{Icon, Id, OuiaComponentType};
use crate::utils::{Ouia, OuiaSafe};
use std::fmt::Debug;
use yew::prelude::*;
const OUIA_NAV: Ouia = ouia!("Nav");
const OUIA_NAV_ITEM: Ouia = ouia!("NavItem");
#[derive(Clone, Debug, PartialEq, Properties)]
pub struct NavProperties {
#[prop_or_default]
pub children: Html,
#[prop_or_default]
pub ouia_id: Option<String>,
#[prop_or(OUIA_NAV.component_type())]
pub ouia_type: OuiaComponentType,
#[prop_or(OuiaSafe::TRUE)]
pub ouia_safe: OuiaSafe,
}
#[function_component(Nav)]
pub fn nav(props: &NavProperties) -> Html {
let ouia_id = use_memo(props.ouia_id.clone(), |id| {
id.clone().unwrap_or(OUIA_NAV.generated_id())
});
html! {
<nav
class="pf-v6-c-nav"
aria-label="Global"
data-ouia-component-id={(*ouia_id).clone()}
data-ouia-component-type={props.ouia_type}
data-ouia-safe={props.ouia_safe}
>
{ props.children.clone() }
</nav>
}
}
#[derive(Clone, PartialEq, Properties)]
pub struct NavListProperties {
#[prop_or_default]
pub children: Html,
}
#[function_component(NavList)]
pub fn nav_list(props: &NavListProperties) -> Html {
html! { <ul class="pf-v6-c-nav__list" role="list">{ props.children.clone() }</ul> }
}
#[derive(Clone, PartialEq, Properties)]
pub struct NavGroupProperties {
#[prop_or_default]
pub children: Html,
#[prop_or_default]
pub title: String,
}
#[function_component(NavGroup)]
pub fn nav_group(props: &NavGroupProperties) -> Html {
html! {
<section class="pf-v6-c-nav__section">
<h2 class="pf-v6-c-nav__section-title">{ props.title.clone() }</h2>
<NavList>{ props.children.clone() }</NavList>
</section>
}
}
#[derive(Clone, PartialEq, Properties)]
pub struct NavItemProperties {
#[prop_or_default]
pub children: Html,
#[prop_or_default]
pub onclick: Callback<()>,
#[prop_or_default]
pub ouia_id: Option<String>,
#[prop_or(OUIA_NAV_ITEM.component_type())]
pub ouia_type: OuiaComponentType,
#[prop_or(OuiaSafe::TRUE)]
pub ouia_safe: OuiaSafe,
}
#[function_component(NavItem)]
pub fn nav_item(props: &NavItemProperties) -> Html {
let ouia_id = use_memo(props.ouia_id.clone(), |id| {
id.clone().unwrap_or(OUIA_NAV_ITEM.generated_id())
});
html! (
<li
class="pf-v6-c-nav__item"
data-ouia-component-id={(*ouia_id).clone()}
data-ouia-component-type={props.ouia_type}
data-ouia-safe={props.ouia_safe}
>
<a href="#" class="pf-v6-c-nav__link" onclick={props.onclick.reform(|_|())}>
{ props.children.clone() }
</a>
</li>
)
}
#[derive(Clone, PartialEq, Properties)]
pub struct NavLinkProperties {
#[prop_or_default]
pub children: Html,
#[prop_or_default]
pub href: AttrValue,
#[prop_or_default]
pub target: Option<AttrValue>,
}
#[function_component(NavLink)]
pub fn nav_link(props: &NavLinkProperties) -> Html {
html! (
<li class="pf-v6-c-nav__item">
<a href={&props.href} class="pf-v6-c-nav__link" target={&props.target}>
{ props.children.clone() }
</a>
</li>
)
}
#[derive(Clone, PartialEq)]
pub struct Expandable {
callback: Callback<(Id, bool)>,
}
impl Expandable {
pub fn state(&self, id: Id, active: bool) {
self.callback.emit((id, active));
}
}
#[derive(Clone, PartialEq, Properties)]
pub struct NavExpandableProperties {
#[prop_or_default]
pub children: Html,
#[prop_or_default]
pub title: String,
#[prop_or_default]
pub expanded: bool,
}
pub struct NavExpandable {
expanded: Option<bool>,
context: Expandable,
active: HashSet<Id>,
}
#[doc(hidden)]
#[derive(Clone, Debug)]
pub enum MsgExpandable {
Toggle,
ChildState(Id, bool),
}
impl Component for NavExpandable {
type Message = MsgExpandable;
type Properties = NavExpandableProperties;
fn create(ctx: &Context<Self>) -> Self {
let expanded = match ctx.props().expanded {
true => Some(true),
false => None,
};
log::debug!("Creating new NavExpandable");
let callback = ctx
.link()
.callback(|(id, state)| MsgExpandable::ChildState(id, state));
Self {
expanded,
active: Default::default(),
context: Expandable { callback },
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
MsgExpandable::Toggle => {
self.expanded = Some(!self.is_expanded(ctx));
}
MsgExpandable::ChildState(id, state) => match state {
true => {
self.active.insert(id);
}
false => {
self.active.remove(&id);
}
},
}
true
}
fn changed(&mut self, ctx: &Context<Self>, _: &Self::Properties) -> bool {
if ctx.props().expanded {
self.expanded = Some(true);
}
true
}
fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
if first_render && self.expanded.is_none() && self.is_expanded(ctx) {
self.expanded = Some(true);
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let mut classes = Classes::from("pf-v6-c-nav__item pf-m-expandable");
let expanded = self.is_expanded(ctx);
if expanded {
classes.push("pf-m-expanded");
}
let context = self.context.clone();
html! {
<ContextProvider<Expandable> {context}>
<li class={classes}>
<button
class="pf-v6-c-nav__link"
aria-expanded={expanded.to_string()}
onclick={ctx.link().callback(|_|MsgExpandable::Toggle)}
>
{ &ctx.props().title }
<span class="pf-v6-c-nav__toggle">
<span class="pf-v6-c-nav__toggle-icon">{ Icon::AngleRight }</span>
</span>
</button>
<section class="pf-v6-c-nav__subnav" hidden={!expanded}>
<NavList>{ ctx.props().children.clone() }</NavList>
</section>
</li>
</ContextProvider<Expandable>>
}
}
}
impl NavExpandable {
fn is_expanded(&self, ctx: &Context<Self>) -> bool {
self.expanded.unwrap_or_else(|| {
let active = !self.active.is_empty();
ctx.props().expanded || active
})
}
}
#[hook]
pub fn use_expandable() -> Option<Expandable> {
use_context::<Expandable>()
}