mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 08:34:15 -05:00
feat: adds NavCategory
This commit is contained in:
parent
a11538ee5b
commit
5256c9275c
5 changed files with 185 additions and 17 deletions
|
@ -1,5 +1,7 @@
|
||||||
|
mod nav_category;
|
||||||
mod nav_drawer;
|
mod nav_drawer;
|
||||||
mod nav_item;
|
mod nav_item;
|
||||||
|
|
||||||
|
pub use nav_category::*;
|
||||||
pub use nav_drawer::*;
|
pub use nav_drawer::*;
|
||||||
pub use nav_item::*;
|
pub use nav_item::*;
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thaw-nav-item {
|
.thaw-nav-item,
|
||||||
|
.thaw-nav-category-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -58,11 +59,13 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thaw-nav-item:hover {
|
.thaw-nav-item:hover,
|
||||||
|
.thaw-nav-category-item:hover {
|
||||||
background-color: var(--colorNeutralBackground4Hover);
|
background-color: var(--colorNeutralBackground4Hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.thaw-nav-item:active {
|
.thaw-nav-item:active,
|
||||||
|
.thaw-nav-category-item:active {
|
||||||
background-color: var(--colorNeutralBackground4Pressed);
|
background-color: var(--colorNeutralBackground4Pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,3 +78,32 @@
|
||||||
border-radius: var(--borderRadiusCircular);
|
border-radius: var(--borderRadiusCircular);
|
||||||
margin-inline-start: -18px;
|
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;
|
||||||
|
}
|
||||||
|
|
99
thaw/src/nav/nav_category.rs
Normal file
99
thaw/src/nav/nav_category.rs
Normal 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,
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::Scrollbar;
|
use crate::Scrollbar;
|
||||||
use leptos::{context::Provider, prelude::*};
|
use leptos::{context::Provider, prelude::*};
|
||||||
use thaw_components::OptionComp;
|
use thaw_components::OptionComp;
|
||||||
use thaw_utils::{class_list, mount_style, Model};
|
use thaw_utils::{class_list, mount_style, OptionModel};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn NavDrawer(
|
pub fn NavDrawer(
|
||||||
#[prop(optional, into)] class: MaybeProp<String>,
|
#[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,
|
children: Children,
|
||||||
#[prop(optional)] nav_drawer_header: Option<NavDrawerHeader>,
|
#[prop(optional)] nav_drawer_header: Option<NavDrawerHeader>,
|
||||||
#[prop(optional)] nav_drawer_footer: Option<NavDrawerFooter>,
|
#[prop(optional)] nav_drawer_footer: Option<NavDrawerFooter>,
|
||||||
|
@ -14,7 +15,7 @@ pub fn NavDrawer(
|
||||||
mount_style("nav-drawer", include_str!("./nav-drawer.css"));
|
mount_style("nav-drawer", include_str!("./nav-drawer.css"));
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Provider value=NavDrawerInjection(selected_value)>
|
<Provider value=NavDrawerInjection{ selected_value, selected_category_value }>
|
||||||
<div class=class_list!["thaw-nav-drawer", class]>
|
<div class=class_list!["thaw-nav-drawer", class]>
|
||||||
<OptionComp value=nav_drawer_header let:header>
|
<OptionComp value=nav_drawer_header let:header>
|
||||||
<header class="thaw-nav-drawer__header">{(header.children)()}</header>
|
<header class="thaw-nav-drawer__header">{(header.children)()}</header>
|
||||||
|
@ -43,8 +44,13 @@ pub struct NavDrawerFooter {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct NavDrawerInjection(pub Model<String>);
|
pub(crate) struct NavDrawerInjection {
|
||||||
|
pub selected_value: OptionModel<String>,
|
||||||
pub(crate) fn use_nav_drawer() -> NavDrawerInjection {
|
pub selected_category_value: OptionModel<String>,
|
||||||
expect_context()
|
}
|
||||||
|
|
||||||
|
impl NavDrawerInjection {
|
||||||
|
pub fn expect_context() -> Self {
|
||||||
|
expect_context()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{use_nav_drawer, Icon};
|
use super::NavDrawerInjection;
|
||||||
|
use crate::Icon;
|
||||||
use leptos::{either::Either, prelude::*};
|
use leptos::{either::Either, prelude::*};
|
||||||
use thaw_utils::{class_list, StoredMaybeSignal};
|
use thaw_utils::{class_list, StoredMaybeSignal};
|
||||||
|
|
||||||
|
@ -10,12 +11,15 @@ pub fn NavItem(
|
||||||
#[prop(optional, into)] href: Option<MaybeSignal<String>>,
|
#[prop(optional, into)] href: Option<MaybeSignal<String>>,
|
||||||
children: Children,
|
children: Children,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let nav_drawer = use_nav_drawer();
|
let nav_drawer = NavDrawerInjection::expect_context();
|
||||||
let value: StoredMaybeSignal<_> = value.into();
|
let value: StoredMaybeSignal<_> = value.into();
|
||||||
let on_click = move |_| {
|
let on_click = move |_| {
|
||||||
let value = value.get();
|
let value = value.get_untracked();
|
||||||
if nav_drawer.0.with(|key| key != &value) {
|
if nav_drawer
|
||||||
nav_drawer.0.set(value);
|
.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 {
|
if let Some(href) = href {
|
||||||
Either::Left(view! {
|
Either::Left(view! {
|
||||||
<a
|
<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()
|
href=move || href.get()
|
||||||
on:click=on_click
|
on:click=on_click
|
||||||
>
|
>
|
||||||
|
@ -49,7 +59,7 @@ pub fn NavItem(
|
||||||
} else {
|
} else {
|
||||||
Either::Right(view! {
|
Either::Right(view! {
|
||||||
<button
|
<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
|
on:click=on_click
|
||||||
>
|
>
|
||||||
{children()}
|
{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/> })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue