feat: accordion

This commit is contained in:
luoxiao 2024-05-15 23:03:35 +08:00
parent eeff259208
commit 7231e0cac1
14 changed files with 214 additions and 256 deletions

View file

@ -46,6 +46,7 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
<Route path="/tabbar" view=TabbarPage/>
<Route path="/nav-bar" view=NavBarPage/>
<Route path="/toast" view=ToastPage/>
<Route path="/accordion" view=AccordionMdPage/>
<Route path="/alert" view=AlertMdPage/>
<Route path="/anchor" view=AnchorMdPage/>
<Route path="/auto-complete" view=AutoCompleteMdPage/>
@ -57,7 +58,6 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
<Route path="/calendar" view=CalendarMdPage/>
<Route path="/card" view=CardMdPage/>
<Route path="/checkbox" view=CheckboxMdPage/>
<Route path="/collapse" view=CollapseMdPage/>
<Route path="/color-picker" view=ColorPickerMdPage/>
<Route path="/date-picker" view=DatePickerMdPage/>
<Route path="/divider" view=DividerMdPage/>

View file

@ -109,6 +109,10 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
MenuGroupOption {
label: "Common Components".into(),
children: vec![
MenuItemOption {
value: "accordion".into(),
label: "Accordion".into(),
},
MenuItemOption {
value: "avatar".into(),
label: "Avatar".into(),
@ -121,10 +125,6 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
value: "card".into(),
label: "Card".into(),
},
MenuItemOption {
value: "collapse".into(),
label: "Collapse".into(),
},
MenuItemOption {
value: "divider".into(),
label: "Divider".into(),

View file

@ -0,0 +1,21 @@
# Accordion
```rust demo
view! {
<Accordion>
<AccordionItem value="leptos">
<AccordionHeader slot>
"Leptos"
</AccordionHeader>
"Build fast web applications with Rust."
</AccordionItem>
<AccordionItem value="thaw">
<AccordionHeader slot>
"Thaw"
</AccordionHeader>
"An easy to use leptos component library"
</AccordionItem>
</Accordion>
}
```

View file

@ -1,56 +0,0 @@
# Collapse
```rust demo
use std::collections::HashSet;
let value = create_rw_signal(HashSet::from(["thaw".to_string()]));
view! {
<Collapse value>
<CollapseItem title="Leptos" key="leptos">
"Build fast web applications with Rust."
</CollapseItem>
<CollapseItem title="Thaw" key="thaw">
"An easy to use leptos component library"
</CollapseItem>
</Collapse>
}
```
### Accordion
Like an accordion.
```rust demo
view! {
<Collapse accordion=true>
<CollapseItem title="Leptos" key="leptos">
"Build fast web applications with Rust."
</CollapseItem>
<CollapseItem title="Thaw" key="thaw">
"An easy to use leptos component library."
</CollapseItem>
<CollapseItem title="Naive UI" key="naive-ui">
"A Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast."
</CollapseItem>
</Collapse>
}
```
### Collapse Props
| Name | Type | Default | Description |
| --------- | ----------------------------------- | -------------------- | ------------------------------------------- |
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the collapse element. |
| value | `Model<HashSet<String>>` | `Default::default()` | Currently active panel. |
| accordion | `bool` | `false` | Only allow one panel open at a time. |
| children | `Children` | | Collapse's content. |
### CollapseItem Props
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the collapse item element. |
| title | `MaybeSignal<String>` | | The title of the CollapseItem. |
| key | `MaybeSignal<String>` | | The indentifier of CollapseItem. |
| chilren | `Children` | | CollapseItem's content. |

View file

@ -28,6 +28,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
"NavBarMdPage" => "../docs/_mobile/nav_bar/mod.md",
"TabbarMdPage" => "../docs/_mobile/tabbar/mod.md",
"ToastMdPage" => "../docs/_mobile/toast/mod.md",
"AccordionMdPage" => "../docs/accordion/mod.md",
"AlertMdPage" => "../docs/alert/mod.md",
"AnchorMdPage" => "../docs/anchor/mod.md",
"AutoCompleteMdPage" => "../docs/auto_complete/mod.md",
@ -39,7 +40,6 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
"CalendarMdPage" => "../docs/calendar/mod.md",
"CardMdPage" => "../docs/card/mod.md",
"CheckboxMdPage" => "../docs/checkbox/mod.md",
"CollapseMdPage" => "../docs/collapse/mod.md",
"ColorPickerMdPage" => "../docs/color_picker/mod.md",
"DatePickerMdPage" => "../docs/date_picker/mod.md",
"DividerMdPage" => "../docs/divider/mod.md",

View file

@ -0,0 +1,60 @@
.thaw-accordion-header {
margin: 0;
background-color: var(--colorTransparentBackground);
color: var(--colorNeutralForeground1);
border-radius: var(--borderRadiusMedium);
}
.thaw-accordion-header__button {
display: flex;
align-items: center;
position: relative;
padding-left: var(--spacingHorizontalMNudge);
padding-right: var(--spacingHorizontalM);
padding-top: 0px;
padding-bottom: 0px;
width: 100%;
min-height: 44px;
text-align: unset;
line-height: var(--lineHeightBase300);
font-family: var(--fontFamilyBase);
font-size: var(--fontSizeBase300);
font-weight: var(--fontWeightRegular);
background-color: inherit;
color: inherit;
border-width: 0px;
appearance: button;
overflow: visible;
box-sizing: border-box;
cursor: pointer;
}
.thaw-accordion-panel {
margin: 0 var(--spacingHorizontalM);
}
.thaw-accordion-panel-enter-from,
.thaw-accordion-panel-enter-to {
opacity: 1;
}
.thaw-accordion-panel-leave-to,
.thaw-accordion-panel-enter-from {
opacity: 0;
max-height: 0;
}
.thaw-accordion-panel-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-accordion-panel-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);
}

View file

@ -0,0 +1,79 @@
use crate::AccordionInjection;
use leptos::*;
use thaw_components::CSSTransition;
use thaw_utils::{mount_style, StoredMaybeSignal};
#[component]
pub fn AccordionItem(
#[prop(into)] value: MaybeSignal<String>,
accordion_header: AccordionHeader,
children: Children,
) -> impl IntoView {
mount_style("accordion-item", include_str!("./accordion-item.css"));
let AccordionInjection {
open_items,
multiple,
collapsible,
} = AccordionInjection::use_();
let panel_ref = NodeRef::<html::Div>::new();
let value: StoredMaybeSignal<_> = value.into();
let is_show_panel = Memo::new(move |_| with!(|open_items, value| open_items.contains(value)));
let on_click = move |_| {
let is_show_panel = is_show_panel.get_untracked();
update!(move |open_items| {
if is_show_panel {
if collapsible {
with!(|value| open_items.remove(value));
} else if multiple {
with!(|value| open_items.remove(value));
}
} else {
if !multiple {
open_items.clear();
}
open_items.insert(value.get_untracked());
}
});
};
view! {
<div class="thaw-accordion-item">
<div class="thaw-accordion-header">
<button
class="thaw-accordion-header__button"
aria-expanded=move || is_show_panel.get().to_string()
type="button"
on:click=on_click
>
{(accordion_header.children)()}
</button>
</div>
<CSSTransition
node_ref=panel_ref
show=is_show_panel
name="thaw-accordion-panel"
let:display
>
<div
class="thaw-accordion-panel"
ref=panel_ref
style=move || display.get()
>
{children()}
</div>
</CSSTransition>
</div>
}
}
#[slot]
pub struct AccordionHeader {
children: Children,
}
#[slot]
pub struct AccordionPanel {
children: Children,
}

43
thaw/src/accordion/mod.rs Normal file
View file

@ -0,0 +1,43 @@
mod accordion_item;
pub use accordion_item::*;
use leptos::*;
use std::collections::HashSet;
use thaw_utils::Model;
#[component]
pub fn Accordion(
/// Controls the state of the panel.
#[prop(optional, into)] open_items: Model<HashSet<String>>,
/// Indicates if Accordion support multiple Panels opened at the same time.
#[prop(optional)] multiple: bool,
/// Indicates if Accordion support multiple Panels closed at the same time.
#[prop(optional)] collapsible: bool,
children: Children,
) -> impl IntoView {
view! {
<Provider value=AccordionInjection {
open_items,
collapsible,
multiple
}>
<div class="thaw-accordion">
{children()}
</div>
</Provider>
}
}
#[derive(Clone)]
pub(crate) struct AccordionInjection {
pub open_items: Model<HashSet<String>>,
pub multiple: bool,
pub collapsible: bool,
}
impl AccordionInjection {
pub fn use_() -> AccordionInjection {
expect_context()
}
}

View file

@ -1,55 +0,0 @@
.thaw-collapse .thaw-collapse-item:not(:first-child) {
margin-top: 16px;
border-top: 1px solid var(--thaw-border-color);
}
.thaw-collapse-item__header {
display: flex;
align-items: center;
cursor: pointer;
}
.thaw-collapse
.thaw-collapse-item:not(:first-child)
.thaw-collapse-item__header {
padding-top: 16px;
}
.thaw-collapse-item-arrow {
margin-right: 4px;
transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.thaw-collapse-item--active .thaw-collapse-item-arrow {
transform: rotate(90deg);
}
.thaw-collapse-item__content {
padding-top: 16px;
}
.thaw-collapse-item-enter-from,
.thaw-collapse-item-enter-to {
opacity: 1;
}
.thaw-collapse-item-leave-to,
.thaw-collapse-item-enter-from {
opacity: 0;
padding-top: 0;
max-height: 0;
}
.thaw-collapse-item-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-collapse-item-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);
}

View file

@ -1,63 +0,0 @@
use super::use_collapse;
use crate::Icon;
use leptos::*;
use thaw_components::CSSTransition;
use thaw_utils::{class_list, OptionalProp, StoredMaybeSignal};
#[component]
pub fn CollapseItem(
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
#[prop(into)] title: MaybeSignal<String>,
#[prop(into)] key: MaybeSignal<String>,
children: Children,
) -> impl IntoView {
let collapse = use_collapse();
let key: StoredMaybeSignal<_> = key.into();
let content_ref = create_node_ref::<html::Div>();
let is_show_content = create_memo(move |_| {
collapse
.value
.with(|keys| key.with(|key| keys.contains(key)))
});
let on_click = move |_| {
collapse.value.update(|keys| {
if collapse.accordion {
keys.clear();
}
let key = key.get_untracked();
if is_show_content.get_untracked() {
keys.remove(&key);
} else {
keys.insert(key);
}
});
};
view! {
<div class=class_list![
"thaw-collapse-item", ("thaw-collapse-item--active", move || is_show_content.get()),
class.map(| c | move || c.get())
]>
<div class="thaw-collapse-item__header" on:click=on_click>
<Icon icon=icondata_ai::AiRightOutlined class="thaw-collapse-item-arrow"/>
{move || title.get()}
</div>
<CSSTransition
node_ref=content_ref
show=is_show_content
name="thaw-collapse-item"
let:display
>
<div
class="thaw-collapse-item__content"
ref=content_ref
style=move || display.get()
>
{children()}
</div>
</CSSTransition>
</div>
}
}

View file

@ -1,48 +0,0 @@
mod collapse_item;
mod theme;
pub use collapse_item::CollapseItem;
pub use theme::CollapseTheme;
use crate::{use_theme, Theme};
use leptos::*;
use std::collections::HashSet;
use thaw_utils::{class_list, mount_style, Model, OptionalProp};
#[component]
pub fn Collapse(
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
#[prop(optional, into)] value: Model<HashSet<String>>,
#[prop(optional)] accordion: bool,
children: Children,
) -> impl IntoView {
mount_style("collapser", include_str!("./collapse.css"));
let theme = use_theme(Theme::light);
let css_vars = create_memo(move |_| {
theme.with(|theme| format!("--thaw-border-color: {};", theme.collapse.border_color))
});
view! {
<Provider value=CollapseInjection {
value,
accordion,
}>
<div
class=class_list!["thaw-collapse", class.map(| c | move || c.get())]
style=move || css_vars.get()
>
{children()}
</div>
</Provider>
}
}
#[derive(Clone)]
pub(crate) struct CollapseInjection {
pub value: Model<HashSet<String>>,
pub accordion: bool,
}
pub(crate) fn use_collapse() -> CollapseInjection {
expect_context()
}

View file

@ -1,20 +0,0 @@
use crate::theme::ThemeMethod;
#[derive(Clone)]
pub struct CollapseTheme {
pub border_color: String,
}
impl ThemeMethod for CollapseTheme {
fn light() -> Self {
Self {
border_color: "#efeff5".into(),
}
}
fn dark() -> Self {
Self {
border_color: "#ffffff17".into(),
}
}
}

View file

@ -1,3 +1,4 @@
mod accordion;
mod alert;
mod anchor;
mod auto_complete;
@ -10,7 +11,6 @@ mod calendar;
mod card;
mod checkbox;
mod code;
mod collapse;
mod color_picker;
mod config_provider;
mod date_picker;
@ -46,6 +46,7 @@ mod theme;
mod time_picker;
mod upload;
pub use accordion::*;
pub use alert::*;
pub use anchor::*;
pub use auto_complete::*;
@ -58,7 +59,6 @@ pub use calendar::*;
pub use card::*;
pub use checkbox::*;
pub use code::*;
pub use collapse::*;
pub use color_picker::*;
pub use config_provider::*;
pub use date_picker::*;

View file

@ -5,9 +5,9 @@ use self::common::CommonTheme;
use crate::{
mobile::{NavBarTheme, TabbarTheme},
AlertTheme, AnchorTheme, AutoCompleteTheme, BackTopTheme, BreadcrumbTheme, CalendarTheme,
CollapseTheme, ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme, MessageTheme,
PopoverTheme, ProgressTheme, ScrollbarTheme, SelectTheme, SkeletionTheme, SliderTheme,
SpinnerTheme, SwitchTheme, TableTheme, TagTheme, TimePickerTheme, UploadTheme,
ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme, MessageTheme, PopoverTheme,
ProgressTheme, ScrollbarTheme, SelectTheme, SkeletionTheme, SliderTheme, SpinnerTheme,
SwitchTheme, TableTheme, TagTheme, TimePickerTheme, UploadTheme,
};
pub use color::ColorTheme;
use leptos::*;
@ -44,7 +44,6 @@ pub struct Theme {
pub time_picker: TimePickerTheme,
pub date_picker: DatePickerTheme,
pub popover: PopoverTheme,
pub collapse: CollapseTheme,
pub scrollbar: ScrollbarTheme,
pub back_top: BackTopTheme,
pub anchor: AnchorTheme,
@ -78,7 +77,6 @@ impl Theme {
time_picker: TimePickerTheme::light(),
date_picker: DatePickerTheme::light(),
popover: PopoverTheme::light(),
collapse: CollapseTheme::light(),
scrollbar: ScrollbarTheme::light(),
back_top: BackTopTheme::light(),
anchor: AnchorTheme::light(),
@ -111,7 +109,6 @@ impl Theme {
time_picker: TimePickerTheme::dark(),
date_picker: DatePickerTheme::dark(),
popover: PopoverTheme::dark(),
collapse: CollapseTheme::dark(),
scrollbar: ScrollbarTheme::dark(),
back_top: BackTopTheme::dark(),
anchor: AnchorTheme::dark(),