1use super::layout_utils::{GapPreset, compose_gap_style, push_gap_preset_class};
2use dioxus::prelude::*;
3
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
6pub enum FlexOrientation {
7 #[default]
8 Horizontal,
9 Vertical,
10}
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub enum FlexComponent {
15 #[default]
16 Div,
17 Section,
18 Article,
19 Nav,
20 Span,
21}
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
24pub enum FlexDirection {
25 #[default]
26 Row,
27 RowReverse,
28 Column,
29 ColumnReverse,
30}
31
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
33pub enum FlexJustify {
34 #[default]
35 Start,
36 End,
37 Center,
38 Between,
39 Around,
40 Evenly,
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
44pub enum FlexAlign {
45 Start,
46 End,
47 Center,
48 #[default]
49 Stretch,
50 Baseline,
51}
52
53#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
54pub enum FlexWrap {
55 #[default]
56 NoWrap,
57 Wrap,
58 WrapReverse,
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum FlexGap {
64 Small,
65 Middle,
66 Large,
67}
68
69impl From<FlexGap> for GapPreset {
70 fn from(value: FlexGap) -> Self {
71 match value {
72 FlexGap::Small => GapPreset::Small,
73 FlexGap::Middle => GapPreset::Middle,
74 FlexGap::Large => GapPreset::Large,
75 }
76 }
77}
78
79#[derive(Clone, Debug, Default, PartialEq)]
81pub struct FlexSharedConfig {
82 pub class: Option<String>,
83 pub style: Option<String>,
84 pub vertical: Option<bool>,
85}
86
87#[derive(Props, Clone, PartialEq)]
89pub struct FlexConfigProviderProps {
90 pub value: FlexSharedConfig,
91 pub children: Element,
92}
93
94#[component]
95pub fn FlexConfigProvider(props: FlexConfigProviderProps) -> Element {
96 let FlexConfigProviderProps { value, children } = props;
97 use_context_provider(|| value);
98 children
99}
100
101#[derive(Props, Clone, PartialEq)]
102pub struct FlexProps {
103 #[props(default)]
104 pub direction: FlexDirection,
105 #[props(default)]
106 pub justify: FlexJustify,
107 #[props(default)]
108 pub align: FlexAlign,
109 #[props(default)]
110 pub wrap: FlexWrap,
111 #[props(optional)]
112 pub orientation: Option<FlexOrientation>,
113 #[props(default)]
114 pub vertical: bool,
115 #[props(default)]
116 pub component: FlexComponent,
117 #[props(optional)]
118 pub gap: Option<f32>,
119 #[props(optional)]
120 pub row_gap: Option<f32>,
121 #[props(optional)]
122 pub column_gap: Option<f32>,
123 #[props(optional)]
124 pub gap_size: Option<FlexGap>,
125 #[props(optional)]
126 pub class: Option<String>,
127 #[props(optional)]
128 pub style: Option<String>,
129 pub children: Element,
130}
131
132#[component]
134pub fn Flex(props: FlexProps) -> Element {
135 let FlexProps {
136 direction,
137 justify,
138 align,
139 wrap,
140 orientation,
141 vertical,
142 component,
143 gap,
144 row_gap,
145 column_gap,
146 gap_size,
147 class,
148 style,
149 children,
150 } = props;
151
152 let inherited = try_use_context::<FlexSharedConfig>();
153 let inherited_vertical = inherited.as_ref().and_then(|ctx| ctx.vertical);
154 let resolved_direction =
155 compute_direction(direction, orientation, vertical, inherited_vertical);
156
157 let mut class_list = base_classes(resolved_direction, wrap, justify, align);
158 if let Some(ctx) = inherited.as_ref()
159 && let Some(extra) = ctx.class.as_ref()
160 {
161 class_list.push(extra.clone());
162 }
163 if let Some(extra) = class.as_ref() {
164 class_list.push(extra.clone());
165 }
166 if gap.is_none() && row_gap.is_none() && column_gap.is_none() {
167 let preset = gap_size.map(Into::into);
168 push_gap_preset_class(&mut class_list, "adui-flex-gap", preset);
169 }
170 let class_attr = class_list.join(" ");
171
172 let mut base_style = String::new();
173 if let Some(ctx) = inherited.as_ref()
174 && let Some(st) = ctx.style.as_ref()
175 {
176 base_style.push_str(st);
177 }
178 if let Some(extra) = style {
179 base_style.push_str(&extra);
180 }
181 let base_style_opt = if base_style.is_empty() {
182 None
183 } else {
184 Some(base_style)
185 };
186 let style_attr = compose_gap_style(base_style_opt, gap, row_gap, column_gap);
187
188 render_component(component, &class_attr, &style_attr, &children)
189}
190
191fn render_component(
192 component: FlexComponent,
193 class_attr: &str,
194 style_attr: &str,
195 children: &Element,
196) -> Element {
197 match component {
198 FlexComponent::Div => {
199 rsx!(div { class: "{class_attr}", style: "{style_attr}", {children.clone()} })
200 }
201 FlexComponent::Section => {
202 rsx!(section { class: "{class_attr}", style: "{style_attr}", {children.clone()} })
203 }
204 FlexComponent::Article => {
205 rsx!(article { class: "{class_attr}", style: "{style_attr}", {children.clone()} })
206 }
207 FlexComponent::Nav => {
208 rsx!(nav { class: "{class_attr}", style: "{style_attr}", {children.clone()} })
209 }
210 FlexComponent::Span => {
211 rsx!(span { class: "{class_attr}", style: "{style_attr}", {children.clone()} })
212 }
213 }
214}
215
216fn compute_direction(
217 explicit: FlexDirection,
218 orientation: Option<FlexOrientation>,
219 vertical_flag: bool,
220 inherited_vertical: Option<bool>,
221) -> FlexDirection {
222 if let Some(orientation) = orientation {
223 return match orientation {
224 FlexOrientation::Horizontal => FlexDirection::Row,
225 FlexOrientation::Vertical => FlexDirection::Column,
226 };
227 }
228
229 if let Some(flag) = inherited_vertical
230 && flag
231 && matches!(explicit, FlexDirection::Row | FlexDirection::RowReverse)
232 {
233 return FlexDirection::Column;
234 }
235
236 if vertical_flag && matches!(explicit, FlexDirection::Row | FlexDirection::RowReverse) {
237 return FlexDirection::Column;
238 }
239
240 explicit
241}
242
243fn base_classes(
244 direction: FlexDirection,
245 wrap: FlexWrap,
246 justify: FlexJustify,
247 align: FlexAlign,
248) -> Vec<String> {
249 let mut classes = vec!["adui-flex".to_string()];
250 match direction {
251 FlexDirection::Row => classes.push("adui-flex-horizontal".into()),
252 FlexDirection::RowReverse => {
253 classes.push("adui-flex-horizontal".into());
254 classes.push("adui-flex-row-reverse".into());
255 }
256 FlexDirection::Column => classes.push("adui-flex-vertical".into()),
257 FlexDirection::ColumnReverse => {
258 classes.push("adui-flex-vertical".into());
259 classes.push("adui-flex-column-reverse".into());
260 }
261 }
262
263 classes.push(match wrap {
264 FlexWrap::NoWrap => "adui-flex-wrap-nowrap".into(),
265 FlexWrap::Wrap => "adui-flex-wrap-wrap".into(),
266 FlexWrap::WrapReverse => "adui-flex-wrap-wrap-reverse".into(),
267 });
268
269 classes.push(match justify {
270 FlexJustify::Start => "adui-flex-justify-start".into(),
271 FlexJustify::End => "adui-flex-justify-end".into(),
272 FlexJustify::Center => "adui-flex-justify-center".into(),
273 FlexJustify::Between => "adui-flex-justify-between".into(),
274 FlexJustify::Around => "adui-flex-justify-around".into(),
275 FlexJustify::Evenly => "adui-flex-justify-evenly".into(),
276 });
277
278 classes.push(match align {
279 FlexAlign::Start => "adui-flex-align-start".into(),
280 FlexAlign::End => "adui-flex-align-end".into(),
281 FlexAlign::Center => "adui-flex-align-center".into(),
282 FlexAlign::Stretch => "adui-flex-align-stretch".into(),
283 FlexAlign::Baseline => "adui-flex-align-baseline".into(),
284 });
285
286 classes
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn flex_direction_variants() {
295 assert_eq!(FlexDirection::default(), FlexDirection::Row);
296 assert_ne!(FlexDirection::Row, FlexDirection::Column);
297 assert_ne!(FlexDirection::RowReverse, FlexDirection::ColumnReverse);
298 }
299
300 #[test]
301 fn flex_justify_variants() {
302 assert_eq!(FlexJustify::default(), FlexJustify::Start);
303 assert_ne!(FlexJustify::Start, FlexJustify::End);
304 assert_ne!(FlexJustify::Center, FlexJustify::Between);
305 }
306
307 #[test]
308 fn flex_align_variants() {
309 assert_eq!(FlexAlign::default(), FlexAlign::Stretch);
310 assert_ne!(FlexAlign::Start, FlexAlign::End);
311 assert_ne!(FlexAlign::Center, FlexAlign::Baseline);
312 }
313
314 #[test]
315 fn flex_wrap_variants() {
316 assert_eq!(FlexWrap::default(), FlexWrap::NoWrap);
317 assert_ne!(FlexWrap::NoWrap, FlexWrap::Wrap);
318 assert_ne!(FlexWrap::Wrap, FlexWrap::WrapReverse);
319 }
320
321 #[test]
322 fn flex_component_variants() {
323 assert_eq!(FlexComponent::default(), FlexComponent::Div);
324 assert_ne!(FlexComponent::Div, FlexComponent::Section);
325 assert_ne!(FlexComponent::Article, FlexComponent::Nav);
326 }
327
328 #[test]
329 fn flex_orientation_variants() {
330 assert_eq!(FlexOrientation::default(), FlexOrientation::Horizontal);
331 assert_ne!(FlexOrientation::Horizontal, FlexOrientation::Vertical);
332 }
333
334 #[test]
335 fn flex_gap_conversion() {
336 let _small: GapPreset = FlexGap::Small.into();
338 let _middle: GapPreset = FlexGap::Middle.into();
339 let _large: GapPreset = FlexGap::Large.into();
340 }
341
342 #[test]
343 fn flex_shared_config_defaults() {
344 let config = FlexSharedConfig::default();
345 assert_eq!(config.class, None);
346 assert_eq!(config.style, None);
347 assert_eq!(config.vertical, None);
348 }
349
350 #[test]
351 fn compute_direction_with_orientation() {
352 assert_eq!(
354 compute_direction(
355 FlexDirection::Row,
356 Some(FlexOrientation::Vertical),
357 false,
358 None
359 ),
360 FlexDirection::Column
361 );
362 assert_eq!(
363 compute_direction(
364 FlexDirection::Column,
365 Some(FlexOrientation::Horizontal),
366 true,
367 None
368 ),
369 FlexDirection::Row
370 );
371 }
372
373 #[test]
374 fn compute_direction_with_vertical_flag() {
375 assert_eq!(
377 compute_direction(FlexDirection::Row, None, true, None),
378 FlexDirection::Column
379 );
380 assert_eq!(
382 compute_direction(FlexDirection::Column, None, true, None),
383 FlexDirection::Column
384 );
385 }
386
387 #[test]
388 fn compute_direction_with_inherited_vertical() {
389 assert_eq!(
391 compute_direction(FlexDirection::Row, None, false, Some(true)),
392 FlexDirection::Column
393 );
394 assert_eq!(
396 compute_direction(FlexDirection::Column, None, false, Some(true)),
397 FlexDirection::Column
398 );
399 }
400
401 #[test]
402 fn base_classes_includes_flex_class() {
403 let classes = base_classes(
404 FlexDirection::Row,
405 FlexWrap::NoWrap,
406 FlexJustify::Start,
407 FlexAlign::Stretch,
408 );
409 assert!(classes.contains(&"adui-flex".to_string()));
410 }
411
412 #[test]
413 fn base_classes_direction_mapping() {
414 let row_classes = base_classes(
415 FlexDirection::Row,
416 FlexWrap::NoWrap,
417 FlexJustify::Start,
418 FlexAlign::Stretch,
419 );
420 assert!(row_classes.contains(&"adui-flex-horizontal".to_string()));
421
422 let col_classes = base_classes(
423 FlexDirection::Column,
424 FlexWrap::NoWrap,
425 FlexJustify::Start,
426 FlexAlign::Stretch,
427 );
428 assert!(col_classes.contains(&"adui-flex-vertical".to_string()));
429 }
430
431 #[test]
432 fn base_classes_wrap_mapping() {
433 let nowrap_classes = base_classes(
434 FlexDirection::Row,
435 FlexWrap::NoWrap,
436 FlexJustify::Start,
437 FlexAlign::Stretch,
438 );
439 assert!(nowrap_classes.contains(&"adui-flex-wrap-nowrap".to_string()));
440
441 let wrap_classes = base_classes(
442 FlexDirection::Row,
443 FlexWrap::Wrap,
444 FlexJustify::Start,
445 FlexAlign::Stretch,
446 );
447 assert!(wrap_classes.contains(&"adui-flex-wrap-wrap".to_string()));
448 }
449
450 #[test]
451 fn base_classes_justify_mapping() {
452 let start_classes = base_classes(
453 FlexDirection::Row,
454 FlexWrap::NoWrap,
455 FlexJustify::Start,
456 FlexAlign::Stretch,
457 );
458 assert!(start_classes.contains(&"adui-flex-justify-start".to_string()));
459
460 let center_classes = base_classes(
461 FlexDirection::Row,
462 FlexWrap::NoWrap,
463 FlexJustify::Center,
464 FlexAlign::Stretch,
465 );
466 assert!(center_classes.contains(&"adui-flex-justify-center".to_string()));
467 }
468
469 #[test]
470 fn base_classes_align_mapping() {
471 let stretch_classes = base_classes(
472 FlexDirection::Row,
473 FlexWrap::NoWrap,
474 FlexJustify::Start,
475 FlexAlign::Stretch,
476 );
477 assert!(stretch_classes.contains(&"adui-flex-align-stretch".to_string()));
478
479 let center_classes = base_classes(
480 FlexDirection::Row,
481 FlexWrap::NoWrap,
482 FlexJustify::Start,
483 FlexAlign::Center,
484 );
485 assert!(center_classes.contains(&"adui-flex-align-center".to_string()));
486 }
487}