1use std::collections::HashMap;
7use std::hash::Hash;
8
9#[derive(Clone, Debug, Default)]
30pub struct SemanticClassNames<S: Hash + Eq> {
31 values: HashMap<S, String>,
32}
33
34impl<S: Hash + Eq> SemanticClassNames<S> {
35 pub fn new() -> Self {
37 Self {
38 values: HashMap::new(),
39 }
40 }
41
42 pub fn set(&mut self, slot: S, class_name: impl Into<String>) {
44 self.values.insert(slot, class_name.into());
45 }
46
47 pub fn get(&self, slot: &S) -> Option<&str> {
49 self.values.get(slot).map(|s| s.as_str())
50 }
51
52 pub fn get_or_empty(&self, slot: &S) -> &str {
54 self.values.get(slot).map(|s| s.as_str()).unwrap_or("")
55 }
56
57 pub fn contains(&self, slot: &S) -> bool {
59 self.values.contains_key(slot)
60 }
61
62 pub fn from_iter<I, N>(iter: I) -> Self
64 where
65 I: IntoIterator<Item = (S, N)>,
66 N: Into<String>,
67 {
68 Self {
69 values: iter.into_iter().map(|(k, v)| (k, v.into())).collect(),
70 }
71 }
72}
73
74impl<S: Hash + Eq> PartialEq for SemanticClassNames<S> {
75 fn eq(&self, other: &Self) -> bool {
76 self.values == other.values
77 }
78}
79
80#[derive(Clone, Debug, Default)]
101pub struct SemanticStyles<S: Hash + Eq> {
102 values: HashMap<S, String>,
103}
104
105impl<S: Hash + Eq> SemanticStyles<S> {
106 pub fn new() -> Self {
108 Self {
109 values: HashMap::new(),
110 }
111 }
112
113 pub fn set(&mut self, slot: S, style: impl Into<String>) {
115 self.values.insert(slot, style.into());
116 }
117
118 pub fn get(&self, slot: &S) -> Option<&str> {
120 self.values.get(slot).map(|s| s.as_str())
121 }
122
123 pub fn get_or_empty(&self, slot: &S) -> &str {
125 self.values.get(slot).map(|s| s.as_str()).unwrap_or("")
126 }
127
128 pub fn contains(&self, slot: &S) -> bool {
130 self.values.contains_key(slot)
131 }
132
133 pub fn from_iter<I, N>(iter: I) -> Self
135 where
136 I: IntoIterator<Item = (S, N)>,
137 N: Into<String>,
138 {
139 Self {
140 values: iter.into_iter().map(|(k, v)| (k, v.into())).collect(),
141 }
142 }
143}
144
145impl<S: Hash + Eq> PartialEq for SemanticStyles<S> {
146 fn eq(&self, other: &Self) -> bool {
147 self.values == other.values
148 }
149}
150
151#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
157pub enum ButtonSemantic {
158 Root,
159 Icon,
160 Content,
161}
162
163#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
165pub enum InputSemantic {
166 Root,
167 Prefix,
168 Suffix,
169 Input,
170 Count,
171}
172
173#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
175pub enum SelectSemantic {
176 Root,
177 Prefix,
178 Suffix,
179}
180
181#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
183pub enum SelectPopupSemantic {
184 Root,
185 List,
186 ListItem,
187}
188
189#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
191pub enum ModalSemantic {
192 Root,
193 Header,
194 Body,
195 Footer,
196 Container,
197 Title,
198 Wrapper,
199 Mask,
200}
201
202#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
204pub enum TableSemantic {
205 Section,
206 Title,
207 Footer,
208 Content,
209 Root,
210}
211
212#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
214pub enum TablePartSemantic {
215 Wrapper,
216 Cell,
217 Row,
218}
219
220#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
222pub enum TabsSemantic {
223 Root,
224 Item,
225 Indicator,
226 Content,
227 Header,
228}
229
230#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
232pub enum CollapseSemantic {
233 Root,
234 Header,
235 Title,
236 Body,
237 Icon,
238}
239
240#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
242pub enum FormSemantic {
243 Root,
244 Label,
245 Content,
246}
247
248#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
250pub enum DescriptionsSemantic {
251 Root,
252 Header,
253 Title,
254 Extra,
255 Label,
256 Content,
257}
258
259#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
261pub enum TimelineSemantic {
262 Root,
263 Item,
264 ItemTitle,
265 ItemContent,
266 Indicator,
267}
268
269#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
271pub enum AnchorSemantic {
272 Root,
273 Item,
274 ItemTitle,
275 Indicator,
276}
277
278#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
280pub enum MessageSemantic {
281 Root,
282 Content,
283 Icon,
284}
285
286#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
288pub enum NotificationSemantic {
289 Root,
290 Title,
291 Description,
292 Icon,
293 CloseButton,
294}
295
296pub type ButtonClassNames = SemanticClassNames<ButtonSemantic>;
301pub type ButtonStyles = SemanticStyles<ButtonSemantic>;
302
303pub type InputClassNames = SemanticClassNames<InputSemantic>;
304pub type InputStyles = SemanticStyles<InputSemantic>;
305
306pub type SelectClassNames = SemanticClassNames<SelectSemantic>;
307pub type SelectStyles = SemanticStyles<SelectSemantic>;
308
309pub type ModalClassNames = SemanticClassNames<ModalSemantic>;
310pub type ModalStyles = SemanticStyles<ModalSemantic>;
311
312pub type TableClassNames = SemanticClassNames<TableSemantic>;
313pub type TableStyles = SemanticStyles<TableSemantic>;
314
315pub type TabsClassNames = SemanticClassNames<TabsSemantic>;
316pub type TabsStyles = SemanticStyles<TabsSemantic>;
317
318pub type CollapseClassNames = SemanticClassNames<CollapseSemantic>;
319pub type CollapseStyles = SemanticStyles<CollapseSemantic>;
320
321pub type FormClassNames = SemanticClassNames<FormSemantic>;
322pub type FormStyles = SemanticStyles<FormSemantic>;
323
324pub trait ClassListExt {
330 fn push_semantic<S: Hash + Eq>(&mut self, class_names: &Option<SemanticClassNames<S>>, slot: S);
332}
333
334impl ClassListExt for Vec<String> {
335 fn push_semantic<S: Hash + Eq>(
336 &mut self,
337 class_names: &Option<SemanticClassNames<S>>,
338 slot: S,
339 ) {
340 if let Some(cn) = class_names {
341 if let Some(class) = cn.get(&slot) {
342 if !class.is_empty() {
343 self.push(class.to_string());
344 }
345 }
346 }
347 }
348}
349
350pub trait StyleStringExt {
352 fn append_semantic<S: Hash + Eq>(&mut self, styles: &Option<SemanticStyles<S>>, slot: S);
354}
355
356impl StyleStringExt for String {
357 fn append_semantic<S: Hash + Eq>(&mut self, styles: &Option<SemanticStyles<S>>, slot: S) {
358 if let Some(s) = styles {
359 if let Some(style) = s.get(&slot) {
360 if !style.is_empty() {
361 if !self.is_empty() && !self.ends_with(';') {
362 self.push(';');
363 }
364 self.push_str(style);
365 }
366 }
367 }
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn semantic_class_names_basic_operations() {
377 let mut cn = SemanticClassNames::<ButtonSemantic>::new();
378 assert!(cn.get(&ButtonSemantic::Root).is_none());
379
380 cn.set(ButtonSemantic::Root, "custom-root");
381 assert_eq!(cn.get(&ButtonSemantic::Root), Some("custom-root"));
382 assert!(cn.contains(&ButtonSemantic::Root));
383 assert!(!cn.contains(&ButtonSemantic::Icon));
384 }
385
386 #[test]
387 fn semantic_styles_basic_operations() {
388 let mut styles = SemanticStyles::<ModalSemantic>::new();
389 assert!(styles.get(&ModalSemantic::Body).is_none());
390
391 styles.set(ModalSemantic::Body, "padding: 24px;");
392 assert_eq!(styles.get(&ModalSemantic::Body), Some("padding: 24px;"));
393 }
394
395 #[test]
396 fn class_list_ext_works() {
397 let mut cn = SemanticClassNames::<ButtonSemantic>::new();
398 cn.set(ButtonSemantic::Root, "my-button");
399
400 let mut classes = vec!["adui-btn".to_string()];
401 classes.push_semantic(&Some(cn), ButtonSemantic::Root);
402 assert_eq!(classes, vec!["adui-btn", "my-button"]);
403 }
404
405 #[test]
406 fn style_string_ext_works() {
407 let mut styles = SemanticStyles::<ModalSemantic>::new();
408 styles.set(ModalSemantic::Body, "padding: 24px");
409
410 let mut style_str = "background: white".to_string();
411 style_str.append_semantic(&Some(styles), ModalSemantic::Body);
412 assert_eq!(style_str, "background: white;padding: 24px");
413 }
414}
415
416
417
418