fc_dioxus/components/
list.rs1use crate::{
3 core::{
4 assets,
5 attrs::{availability_attributes, clipped_attributes, component_attributes},
6 },
7 prelude::*,
8 theme::use_stylesheets,
9};
10const BEM: Bem = Bem::new("fc-list");
11fc_modifier! { enum ListRule for BEM { Plain => None, Divided => "divided" } }
12
13#[component]
15pub fn List(
16 #[props(default)] divided: bool,
17 #[props(default)]
19 ordered: bool,
20 #[props(default)] class: Option<String>,
21 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
22 children: Element,
23) -> Element {
24 let attributes = component_attributes(
25 attributes,
26 ClassList::new(BEM.block())
27 .with_modifier(
28 BEM,
29 if divided {
30 ListRule::Divided
31 } else {
32 ListRule::Plain
33 },
34 )
35 .extend(class.as_deref()),
36 &[],
37 );
38 rsx! {
39 if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_LIST_CSS } }
40 if ordered { ol { ..attributes, {children} } } else { ul { ..attributes, {children} } }
41 }
42}
43
44#[derive(Debug, Default, Clone, PartialEq)]
46#[non_exhaustive]
47pub enum ListAction {
48 #[default]
50 None,
51 Link(String),
53 Button(EventHandler<()>),
55 Label,
57}
58
59#[component]
61pub fn ListItem(
62 headline: Element,
63 #[props(default)] overline: Option<Element>,
64 #[props(default)] supporting: Option<Element>,
65 #[props(default)]
68 supporting_clipped: Option<Clipped>,
69 #[props(default)]
71 leading: Option<Element>,
72 #[props(default)] trailing: Option<Element>,
73 #[props(default)]
75 trailing_decorative: bool,
76 #[props(default)] action: ListAction,
77 #[props(default)]
79 current: bool,
80 #[props(default)] availability: Availability,
81 #[props(default)] class: Option<String>,
82 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
83) -> Element {
84 let item = BEM.element("item");
85 let headline_class = ClassList::new(&BEM.element("action"))
86 .with(BEM.element("headline"))
87 .to_string();
88 let blocked = availability != Availability::Enabled;
89 let link_state = match &availability {
91 Availability::Disabled => {
92 crate::spread![Attribute::new("aria-disabled", "true", None, false)]
93 }
94 state => availability_attributes(state),
95 };
96 let button_state = availability_attributes(&availability);
97 let headline = match action.clone() {
98 ListAction::Link(href) => {
99 let link = crate::core::link::use_link((!blocked).then_some(href));
100 let current = current || link.current;
101 let href = link.href.clone();
102 rsx! {
103 a { class: headline_class, href, aria_current: current.then_some("page"),
104 onclick: move |event| { link.follow(&event); }, ..link_state, {headline} }
105 }
106 }
107 ListAction::Button(onclick) => rsx! {
108 button { class: headline_class, r#type: "button", aria_current: current.then_some("true"),
109 onclick: move |_| if !blocked { onclick.call(()); }, ..button_state, {headline} }
110 },
111 ListAction::None | ListAction::Label => {
112 rsx! { span { class: BEM.element("headline"), {headline} } }
113 }
114 };
115 let supporting = match (supporting_clipped, supporting) {
116 (Some(text), _) => {
117 let clipped = clipped_attributes(&text);
118 let shown = text.shown();
119 rsx! { span { class: BEM.element("supporting"), ..clipped, "{shown}" } }
120 }
121 (None, Some(supporting)) => {
122 rsx! { span { class: BEM.element("supporting"), {supporting} } }
123 }
124 (None, None) => rsx! {},
125 };
126 let body = rsx! {
127 if let Some(leading) = leading { span { class: BEM.element("leading"), aria_hidden: "true", {leading} } }
128 span { class: BEM.element("content"),
129 if let Some(overline) = overline { span { class: BEM.element("overline"), {overline} } }
130 {headline}
131 {supporting}
132 }
133 if let Some(trailing) = trailing {
134 span { class: BEM.element("trailing"), aria_hidden: trailing_decorative.then_some("true"), {trailing} }
135 }
136 };
137 if action == ListAction::Label {
138 let attributes = component_attributes(
139 attributes,
140 ClassList::new(&item).extend(class.as_deref()),
141 &[],
142 );
143 rsx! { li { label { ..attributes, {body} } } }
144 } else {
145 let attributes = component_attributes(
146 attributes,
147 ClassList::new(&item).extend(class.as_deref()),
148 &[],
149 );
150 rsx! { li { ..attributes, {body} } }
151 }
152}