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}