feat: adds NavCategory

This commit is contained in:
luoxiao 2024-08-01 17:31:42 +08:00
parent a11538ee5b
commit 5256c9275c
5 changed files with 185 additions and 17 deletions

View file

@ -1,5 +1,7 @@
mod nav_category;
mod nav_drawer;
mod nav_item;
pub use nav_category::*;
pub use nav_drawer::*;
pub use nav_item::*;

View file

@ -38,7 +38,8 @@
z-index: 2;
}
.thaw-nav-item {
.thaw-nav-item,
.thaw-nav-category-item {
display: flex;
text-transform: none;
position: relative;
@ -58,11 +59,13 @@
cursor: pointer;
}
.thaw-nav-item:hover {
.thaw-nav-item:hover,
.thaw-nav-category-item:hover {
background-color: var(--colorNeutralBackground4Hover);
}
.thaw-nav-item:active {
.thaw-nav-item:active,
.thaw-nav-category-item:active {
background-color: var(--colorNeutralBackground4Pressed);
}
@ -75,3 +78,32 @@
border-radius: var(--borderRadiusCircular);
margin-inline-start: -18px;
}
.thaw-nav-sub-item-group-enter-from,
.thaw-nav-sub-item-group-enter-to {
opacity: 1;
}
.thaw-nav-sub-item-group-leave-to,
.thaw-nav-sub-item-group-enter-from {
opacity: 0;
max-height: 0;
}
.thaw-nav-sub-item-group-leave-active {
overflow: hidden;
transition: max-height 0.15s cubic-bezier(0.4, 0, 0.2, 1) 0s,
opacity 0.15s cubic-bezier(0, 0, 0.2, 1) 0s,
padding-top 0.15s cubic-bezier(0.4, 0, 0.2, 1) 0s;
}
.thaw-nav-sub-item-group-enter-active {
overflow: hidden;
transition: max-height 0.15s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.15s cubic-bezier(0.4, 0, 1, 1),
padding-top 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.thaw-nav-sub-item {
padding-inline-start: 46px;
}

View file

@ -0,0 +1,99 @@
use super::NavDrawerInjection;
use crate::Icon;
use leptos::{either::Either, html, prelude::*};
use thaw_components::CSSTransition;
use thaw_utils::{class_list, StoredMaybeSignal};
#[component]
pub fn NavCategory(
#[prop(into)] value: MaybeSignal<String>,
children: Children,
nav_category_item: NavCategoryItem,
) -> impl IntoView {
let nav_drawer = NavDrawerInjection::expect_context();
let value: StoredMaybeSignal<_> = value.into();
let group_ref = NodeRef::<html::Div>::new();
let on_click = move |_| {
let value = value.get_untracked();
if nav_drawer
.selected_category_value
.with_untracked(|selected_category_value| selected_category_value != Some(&value))
{
nav_drawer.selected_category_value.set(Some(value));
}
};
let is_show_group = Memo::new(move |_| {
nav_drawer
.selected_category_value
.with_untracked(|selected_category_value| {
value.with_untracked(|value| selected_category_value == Some(value))
})
});
let NavCategoryItem {
class: item_class,
icon: item_icon,
children: item_children,
} = nav_category_item;
view! {
<button
class=class_list!["thaw-nav-category-item", item_class]
on:click=on_click
aria-expanded=move || is_show_group.get()
>
{
move || {
if let Some(icon) = item_icon.get() {
Either::Left(view! {
<Icon icon=icon style="font-size: 20px"/>
})
} else {
Either::Right(())
}
}
}
{item_children()}
<span aria-hidden="true" class="thaw-nav-category-item__expand-icon">
<svg
fill="currentColor"
aria-hidden="true"
width="20"
height="20"
viewBox="0 0 20 20"
style=move || if is_show_group.get() {
"transform: rotate(90deg)"
} else {
"transform: rotate(0deg)"
}
>
<path d="M7.65 4.15c.2-.2.5-.2.7 0l5.49 5.46c.21.22.21.57 0 .78l-5.49 5.46a.5.5 0 0 1-.7-.7L12.8 10 7.65 4.85a.5.5 0 0 1 0-.7Z" fill="currentColor"></path>
</svg>
</span>
</button>
<CSSTransition
node_ref=group_ref
show=is_show_group
name="thaw-nav-sub-item-group"
let:display
>
<div
class="thaw-nav-sub-item-group"
node_ref=group_ref
style=move || display.get().unwrap_or_default()
>
{children()}
</div>
</CSSTransition>
}
}
#[slot]
pub struct NavCategoryItem {
#[prop(optional, into)]
class: MaybeProp<String>,
#[prop(optional, into)]
icon: MaybeProp<icondata_core::Icon>,
children: Children,
}

View file

@ -1,12 +1,13 @@
use crate::Scrollbar;
use leptos::{context::Provider, prelude::*};
use thaw_components::OptionComp;
use thaw_utils::{class_list, mount_style, Model};
use thaw_utils::{class_list, mount_style, OptionModel};
#[component]
pub fn NavDrawer(
#[prop(optional, into)] class: MaybeProp<String>,
#[prop(optional, into)] selected_value: Model<String>,
#[prop(optional, into)] selected_value: OptionModel<String>,
#[prop(optional, into)] selected_category_value: OptionModel<String>,
children: Children,
#[prop(optional)] nav_drawer_header: Option<NavDrawerHeader>,
#[prop(optional)] nav_drawer_footer: Option<NavDrawerFooter>,
@ -14,7 +15,7 @@ pub fn NavDrawer(
mount_style("nav-drawer", include_str!("./nav-drawer.css"));
view! {
<Provider value=NavDrawerInjection(selected_value)>
<Provider value=NavDrawerInjection{ selected_value, selected_category_value }>
<div class=class_list!["thaw-nav-drawer", class]>
<OptionComp value=nav_drawer_header let:header>
<header class="thaw-nav-drawer__header">{(header.children)()}</header>
@ -43,8 +44,13 @@ pub struct NavDrawerFooter {
}
#[derive(Clone)]
pub(crate) struct NavDrawerInjection(pub Model<String>);
pub(crate) fn use_nav_drawer() -> NavDrawerInjection {
expect_context()
pub(crate) struct NavDrawerInjection {
pub selected_value: OptionModel<String>,
pub selected_category_value: OptionModel<String>,
}
impl NavDrawerInjection {
pub fn expect_context() -> Self {
expect_context()
}
}

View file

@ -1,4 +1,5 @@
use crate::{use_nav_drawer, Icon};
use super::NavDrawerInjection;
use crate::Icon;
use leptos::{either::Either, prelude::*};
use thaw_utils::{class_list, StoredMaybeSignal};
@ -10,12 +11,15 @@ pub fn NavItem(
#[prop(optional, into)] href: Option<MaybeSignal<String>>,
children: Children,
) -> impl IntoView {
let nav_drawer = use_nav_drawer();
let nav_drawer = NavDrawerInjection::expect_context();
let value: StoredMaybeSignal<_> = value.into();
let on_click = move |_| {
let value = value.get();
if nav_drawer.0.with(|key| key != &value) {
nav_drawer.0.set(value);
let value = value.get_untracked();
if nav_drawer
.selected_value
.with_untracked(|selected_value| selected_value != Some(&value))
{
nav_drawer.selected_value.set(Some(value));
}
};
@ -36,10 +40,16 @@ pub fn NavItem(
}
};
let selected = Memo::new(move |_| {
nav_drawer.selected_value.with_untracked(|selected_value| {
value.with_untracked(|value| selected_value == Some(value))
})
});
if let Some(href) = href {
Either::Left(view! {
<a
class=class_list!["thaw-nav-item", ("thaw-nav-item--selected", move || nav_drawer.0.get() == value.get()), class]
class=class_list!["thaw-nav-item", ("thaw-nav-item--selected", move || selected.get()), class]
href=move || href.get()
on:click=on_click
>
@ -49,7 +59,7 @@ pub fn NavItem(
} else {
Either::Right(view! {
<button
class=class_list!["thaw-nav-item", ("thaw-nav-item--selected", move || nav_drawer.0.get() == value.get()), class]
class=class_list!["thaw-nav-item", ("thaw-nav-item--selected", move || selected.get()), class]
on:click=on_click
>
{children()}
@ -57,3 +67,22 @@ pub fn NavItem(
})
}
}
#[component]
pub fn NavSubItem(
#[prop(optional, into)] class: MaybeProp<String>,
#[prop(optional, into)] icon: MaybeProp<icondata_core::Icon>,
#[prop(into)] value: MaybeSignal<String>,
#[prop(optional, into)] href: Option<MaybeSignal<String>>,
children: Children,
) -> impl IntoView {
let class = MaybeProp::derive(move || {
format!("thaw-nav-sub-item {}", class.get().unwrap_or_default()).into()
});
if let Some(href) = href {
Either::Left(view! { <NavItem class href icon value children/> })
} else {
Either::Right(view! { <NavItem class icon value children/> })
}
}