1use crate::{
3 core::{
4 assets,
5 attrs::component_attributes,
6 measure::{custom_property, CustomProperty},
7 },
8 prelude::*,
9 theme::use_stylesheets,
10};
11const SKELETON: Bem = Bem::new("fc-skeleton");
12const PROGRESS: Bem = Bem::new("fc-progress");
13const RING: Bem = Bem::new("fc-progress-ring");
14fc_modifier! {
15 pub enum SkeletonShape for SKELETON {
17 Default => None,
19 Text => "text",
21 Block => "block",
23 Circle => "circle",
25 }
26}
27fc_modifier! { enum ProgressSize for PROGRESS { Md => None, Sm => "sm" } }
28fc_modifier! {
29 pub enum RingSize for RING {
31 Md => None,
33 Sm => "sm",
35 Lg => "lg",
37 }
38}
39fc_modifier! { enum RingTone for RING { Normal => None, Error => "error" } }
40
41#[component]
44pub fn Skeleton(
45 #[props(default)] shape: SkeletonShape,
46 #[props(default)] class: Option<String>,
47 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
48) -> Element {
49 let attributes = component_attributes(
50 attributes,
51 ClassList::new(SKELETON.block())
52 .with_modifier(SKELETON, shape)
53 .extend(class.as_deref()),
54 &[],
55 );
56 rsx! {
57 if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_LOADING_CSS } }
58 span { ..attributes }
59 }
60}
61
62#[component]
67pub fn Loading(
68 label: AccessibleName,
69 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
70 children: Element,
71) -> Element {
72 let attributes: Vec<_> = attributes
73 .into_iter()
74 .filter(|attr| !["role", "aria-busy", "aria-label"].contains(&attr.name))
75 .collect();
76 rsx! {
77 div { role: "group", aria_busy: "true", aria_label: label.to_string(), ..attributes, {children} }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq)]
83pub enum ProgressValue {
84 Indeterminate,
86 Of {
88 value: f64,
90 max: f64,
92 },
93}
94
95#[component]
97pub fn Progress(
98 label: AccessibleName,
99 value: ProgressValue,
100 #[props(default)] small: bool,
101 #[props(default)] class: Option<String>,
102 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
103) -> Element {
104 let mut attributes = component_attributes(
105 attributes,
106 ClassList::new(PROGRESS.block())
107 .with_modifier(
108 PROGRESS,
109 if small {
110 ProgressSize::Sm
111 } else {
112 ProgressSize::Md
113 },
114 )
115 .extend(class.as_deref()),
116 &["value", "max", "aria-label"],
117 );
118 if let ProgressValue::Of { value, max } = value {
119 let max = if max > 0.0 { max } else { 1.0 };
120 attributes.push(Attribute::new(
121 "value",
122 value.clamp(0.0, max).to_string(),
123 None,
124 false,
125 ));
126 attributes.push(Attribute::new("max", max.to_string(), None, false));
127 }
128 rsx! {
129 if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_LOADING_CSS } }
130 progress { aria_label: label.to_string(), ..attributes }
131 }
132}
133
134#[component]
137pub fn ProgressRing(
138 label: AccessibleName,
139 percent: Percent,
140 #[props(default)] size: RingSize,
141 #[props(default)]
143 show_label: bool,
144 #[props(default)] error: bool,
145 #[props(default)] class: Option<String>,
146 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
147) -> Element {
148 let mut attributes = component_attributes(
149 attributes,
150 ClassList::new(RING.block())
151 .with_modifier(RING, size)
152 .with_modifier(
153 RING,
154 if error {
155 RingTone::Error
156 } else {
157 RingTone::Normal
158 },
159 )
160 .extend(class.as_deref()),
161 &[
162 "role",
163 "aria-label",
164 "aria-valuemin",
165 "aria-valuemax",
166 "aria-valuenow",
167 "style",
168 ],
169 );
170 attributes.push(custom_property(CustomProperty::ProgressValue, percent));
172 rsx! {
173 if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_LOADING_CSS } }
174 span { role: "progressbar", aria_label: label.to_string(), aria_valuemin: "0", aria_valuemax: "100",
175 aria_valuenow: "{percent}", ..attributes,
176 if show_label && size != RingSize::Sm { span { class: RING.element("label"), "{percent}%" } }
177 }
178 }
179}