Skip to main content

dioxus_ui_system/styles/
builder.rs

1//! Style builder for inline CSS generation
2//!
3//! Provides a fluent API for building CSS style strings in pure Rust.
4//! Similar to Tailwind's utility classes but type-safe and compiled.
5
6use crate::theme::tokens::{Color, RadiusScale, SpacingScale, ThemeTokens, TypographyScale};
7use std::fmt::Write;
8
9/// CSS Style builder with fluent API
10///
11/// # Example
12/// ```rust,ignore
13/// use dioxus_ui_system::styles::Style;
14/// use dioxus_ui_system::theme::ThemeTokens;
15///
16/// let theme = ThemeTokens::light();
17/// let style = Style::new()
18///     .flex()
19///     .flex_col()
20///     .items_center()
21///     .gap(&theme.spacing, "md")
22///     .bg(&theme.colors.primary)
23///     .rounded(&theme.radius, "md")
24///     .build();
25/// ```
26#[derive(Default, Clone)]
27pub struct Style {
28    // Layout
29    pub display: Option<String>,
30    pub flex_direction: Option<String>,
31    pub flex_wrap: Option<String>,
32    pub align_items: Option<String>,
33    pub align_self: Option<String>,
34    pub justify_content: Option<String>,
35    pub justify_items: Option<String>,
36    pub gap: Option<String>,
37    pub row_gap: Option<String>,
38    pub column_gap: Option<String>,
39
40    // Spacing
41    pub padding: Option<String>,
42    pub padding_top: Option<String>,
43    pub padding_right: Option<String>,
44    pub padding_bottom: Option<String>,
45    pub padding_left: Option<String>,
46    pub margin: Option<String>,
47    pub margin_top: Option<String>,
48    pub margin_right: Option<String>,
49    pub margin_bottom: Option<String>,
50    pub margin_left: Option<String>,
51
52    // Colors
53    pub background_color: Option<String>,
54    pub color: Option<String>,
55    pub border_color: Option<String>,
56
57    // Typography
58    pub font_size: Option<String>,
59    pub font_weight: Option<String>,
60    pub font_family: Option<String>,
61    pub line_height: Option<String>,
62    pub text_align: Option<String>,
63    pub text_decoration: Option<String>,
64    pub letter_spacing: Option<String>,
65
66    // Effects
67    pub border_radius: Option<String>,
68    pub border: Option<String>,
69    pub border_top: Option<String>,
70    pub border_right: Option<String>,
71    pub border_bottom: Option<String>,
72    pub border_left: Option<String>,
73    pub border_width: Option<String>,
74    pub box_shadow: Option<String>,
75
76    // Sizing
77    pub width: Option<String>,
78    pub height: Option<String>,
79    pub min_width: Option<String>,
80    pub min_height: Option<String>,
81    pub max_width: Option<String>,
82    pub max_height: Option<String>,
83
84    // Position
85    pub position: Option<String>,
86    pub top: Option<String>,
87    pub right: Option<String>,
88    pub bottom: Option<String>,
89    pub left: Option<String>,
90    pub z_index: Option<String>,
91
92    // Misc
93    pub cursor: Option<String>,
94    pub opacity: Option<String>,
95    pub transition: Option<String>,
96    pub transform: Option<String>,
97    pub overflow: Option<String>,
98    pub visibility: Option<String>,
99    pub pointer_events: Option<String>,
100    pub user_select: Option<String>,
101    pub white_space: Option<String>,
102    pub word_break: Option<String>,
103    pub outline: Option<String>,
104    pub resize: Option<String>,
105}
106
107impl Style {
108    /// Create a new empty style
109    pub fn new() -> Self {
110        Self::default()
111    }
112
113    // ============================================================================
114    // Layout
115    // ============================================================================
116
117    /// Set display to flex
118    pub fn flex(mut self) -> Self {
119        self.display = Some("flex".into());
120        self
121    }
122
123    /// Set display to block
124    pub fn block(mut self) -> Self {
125        self.display = Some("block".into());
126        self
127    }
128
129    /// Set display to inline-block
130    pub fn inline_block(mut self) -> Self {
131        self.display = Some("inline-block".into());
132        self
133    }
134
135    /// Set display to inline-flex
136    pub fn inline_flex(mut self) -> Self {
137        self.display = Some("inline-flex".into());
138        self
139    }
140
141    /// Set display to grid
142    pub fn grid(mut self) -> Self {
143        self.display = Some("grid".into());
144        self
145    }
146
147    /// Set display to none
148    pub fn hidden(mut self) -> Self {
149        self.display = Some("none".into());
150        self
151    }
152
153    /// Set flex-direction to column
154    pub fn flex_col(mut self) -> Self {
155        self.flex_direction = Some("column".into());
156        self
157    }
158
159    /// Set flex-direction to row
160    pub fn flex_row(mut self) -> Self {
161        self.flex_direction = Some("row".into());
162        self
163    }
164
165    /// Set flex-wrap to wrap
166    pub fn flex_wrap(mut self) -> Self {
167        self.flex_wrap = Some("wrap".into());
168        self
169    }
170
171    /// Set flex-wrap to nowrap
172    pub fn flex_nowrap(mut self) -> Self {
173        self.flex_wrap = Some("nowrap".into());
174        self
175    }
176
177    /// Set align-items to center
178    pub fn items_center(mut self) -> Self {
179        self.align_items = Some("center".into());
180        self
181    }
182
183    /// Set align-items to start
184    pub fn items_start(mut self) -> Self {
185        self.align_items = Some("flex-start".into());
186        self
187    }
188
189    /// Set align-items to end
190    pub fn items_end(mut self) -> Self {
191        self.align_items = Some("flex-end".into());
192        self
193    }
194
195    /// Set align-items to stretch
196    pub fn items_stretch(mut self) -> Self {
197        self.align_items = Some("stretch".into());
198        self
199    }
200
201    /// Set align-self to center
202    pub fn self_center(mut self) -> Self {
203        self.align_self = Some("center".into());
204        self
205    }
206
207    /// Set justify-content to center
208    pub fn justify_center(mut self) -> Self {
209        self.justify_content = Some("center".into());
210        self
211    }
212
213    /// Set justify-content to start
214    pub fn justify_start(mut self) -> Self {
215        self.justify_content = Some("flex-start".into());
216        self
217    }
218
219    /// Set justify-content to end
220    pub fn justify_end(mut self) -> Self {
221        self.justify_content = Some("flex-end".into());
222        self
223    }
224
225    /// Set justify-content to space-between
226    pub fn justify_between(mut self) -> Self {
227        self.justify_content = Some("space-between".into());
228        self
229    }
230
231    /// Set justify-content to space-around
232    pub fn justify_around(mut self) -> Self {
233        self.justify_content = Some("space-around".into());
234        self
235    }
236
237    /// Set justify-content to space-evenly
238    pub fn justify_evenly(mut self) -> Self {
239        self.justify_content = Some("space-evenly".into());
240        self
241    }
242
243    /// Set gap from spacing scale
244    pub fn gap(mut self, spacing: &SpacingScale, size: &str) -> Self {
245        let val = spacing.get(size);
246        self.gap = Some(format!("{}px", val));
247        self
248    }
249
250    /// Set gap to a specific pixel value
251    pub fn gap_px(mut self, px: u16) -> Self {
252        self.gap = Some(format!("{}px", px));
253        self
254    }
255
256    /// Set row gap from spacing scale
257    pub fn row_gap(mut self, spacing: &SpacingScale, size: &str) -> Self {
258        let val = spacing.get(size);
259        self.row_gap = Some(format!("{}px", val));
260        self
261    }
262
263    /// Set column gap from spacing scale
264    pub fn column_gap(mut self, spacing: &SpacingScale, size: &str) -> Self {
265        let val = spacing.get(size);
266        self.column_gap = Some(format!("{}px", val));
267        self
268    }
269
270    // ============================================================================
271    // Spacing
272    // ============================================================================
273
274    /// Set padding from spacing scale
275    pub fn p(mut self, spacing: &SpacingScale, size: &str) -> Self {
276        let val = spacing.get(size);
277        self.padding = Some(format!("{}px", val));
278        self
279    }
280
281    /// Set padding to specific value
282    pub fn p_px(mut self, px: u16) -> Self {
283        self.padding = Some(format!("{}px", px));
284        self
285    }
286
287    /// Set horizontal padding (left and right)
288    pub fn px(mut self, spacing: &SpacingScale, size: &str) -> Self {
289        let val = spacing.get(size);
290        self.padding_left = Some(format!("{}px", val));
291        self.padding_right = Some(format!("{}px", val));
292        self
293    }
294
295    /// Set horizontal padding to specific value
296    pub fn px_px(mut self, px: u16) -> Self {
297        self.padding_left = Some(format!("{}px", px));
298        self.padding_right = Some(format!("{}px", px));
299        self
300    }
301
302    /// Set vertical padding (top and bottom)
303    pub fn py(mut self, spacing: &SpacingScale, size: &str) -> Self {
304        let val = spacing.get(size);
305        self.padding_top = Some(format!("{}px", val));
306        self.padding_bottom = Some(format!("{}px", val));
307        self
308    }
309
310    /// Set vertical padding to specific value
311    pub fn py_px(mut self, px: u16) -> Self {
312        self.padding_top = Some(format!("{}px", px));
313        self.padding_bottom = Some(format!("{}px", px));
314        self
315    }
316
317    /// Set top padding
318    pub fn pt(mut self, spacing: &SpacingScale, size: &str) -> Self {
319        let val = spacing.get(size);
320        self.padding_top = Some(format!("{}px", val));
321        self
322    }
323
324    /// Set right padding
325    pub fn pr(mut self, spacing: &SpacingScale, size: &str) -> Self {
326        let val = spacing.get(size);
327        self.padding_right = Some(format!("{}px", val));
328        self
329    }
330
331    /// Set bottom padding
332    pub fn pb(mut self, spacing: &SpacingScale, size: &str) -> Self {
333        let val = spacing.get(size);
334        self.padding_bottom = Some(format!("{}px", val));
335        self
336    }
337
338    /// Set left padding
339    pub fn pl(mut self, spacing: &SpacingScale, size: &str) -> Self {
340        let val = spacing.get(size);
341        self.padding_left = Some(format!("{}px", val));
342        self
343    }
344
345    /// Set top padding in pixels
346    pub fn pt_px(mut self, px: u16) -> Self {
347        self.padding_top = Some(format!("{}px", px));
348        self
349    }
350
351    /// Set right padding in pixels
352    pub fn pr_px(mut self, px: u16) -> Self {
353        self.padding_right = Some(format!("{}px", px));
354        self
355    }
356
357    /// Set bottom padding in pixels
358    pub fn pb_px(mut self, px: u16) -> Self {
359        self.padding_bottom = Some(format!("{}px", px));
360        self
361    }
362
363    /// Set left padding in pixels
364    pub fn pl_px(mut self, px: u16) -> Self {
365        self.padding_left = Some(format!("{}px", px));
366        self
367    }
368
369    /// Set margin from spacing scale
370    pub fn m(mut self, spacing: &SpacingScale, size: &str) -> Self {
371        let val = spacing.get(size);
372        self.margin = Some(format!("{}px", val));
373        self
374    }
375
376    /// Set margin to specific value
377    pub fn m_px(mut self, px: u16) -> Self {
378        self.margin = Some(format!("{}px", px));
379        self
380    }
381
382    /// Set horizontal margin
383    pub fn mx(mut self, spacing: &SpacingScale, size: &str) -> Self {
384        let val = spacing.get(size);
385        self.margin_left = Some(format!("{}px", val));
386        self.margin_right = Some(format!("{}px", val));
387        self
388    }
389
390    /// Set vertical margin
391    pub fn my(mut self, spacing: &SpacingScale, size: &str) -> Self {
392        let val = spacing.get(size);
393        self.margin_top = Some(format!("{}px", val));
394        self.margin_bottom = Some(format!("{}px", val));
395        self
396    }
397
398    /// Set top margin
399    pub fn mt(mut self, spacing: &SpacingScale, size: &str) -> Self {
400        let val = spacing.get(size);
401        self.margin_top = Some(format!("{}px", val));
402        self
403    }
404
405    /// Set right margin
406    pub fn mr(mut self, spacing: &SpacingScale, size: &str) -> Self {
407        let val = spacing.get(size);
408        self.margin_right = Some(format!("{}px", val));
409        self
410    }
411
412    /// Set bottom margin
413    pub fn mb(mut self, spacing: &SpacingScale, size: &str) -> Self {
414        let val = spacing.get(size);
415        self.margin_bottom = Some(format!("{}px", val));
416        self
417    }
418
419    /// Set bottom margin in pixels (can be negative)
420    pub fn mb_px(mut self, px: i16) -> Self {
421        self.margin_bottom = Some(format!("{}px", px));
422        self
423    }
424
425    /// Set left margin
426    pub fn ml(mut self, spacing: &SpacingScale, size: &str) -> Self {
427        let val = spacing.get(size);
428        self.margin_left = Some(format!("{}px", val));
429        self
430    }
431
432    // ============================================================================
433    // Colors
434    // ============================================================================
435
436    /// Set background color
437    pub fn bg(mut self, color: &Color) -> Self {
438        self.background_color = Some(color.to_rgba());
439        self
440    }
441
442    /// Set background color from hex string
443    pub fn bg_hex(mut self, hex: &str) -> Self {
444        self.background_color = Some(hex.into());
445        self
446    }
447
448    /// Set text color
449    pub fn text_color(mut self, color: &Color) -> Self {
450        self.color = Some(color.to_rgba());
451        self
452    }
453
454    /// Set text color from hex string
455    pub fn text_hex(mut self, hex: &str) -> Self {
456        self.color = Some(hex.into());
457        self
458    }
459
460    /// Set border color
461    pub fn border_color(mut self, color: &Color) -> Self {
462        self.border_color = Some(color.to_rgba());
463        self
464    }
465
466    // ============================================================================
467    // Typography
468    // ============================================================================
469
470    /// Set typography from scale
471    pub fn text(mut self, typography: &TypographyScale, size: &str) -> Self {
472        let t = typography.get(size);
473        self.font_size = Some(format!("{}px", t.size));
474        self.font_weight = Some(t.weight.to_string());
475        self.font_family = Some(t.family.clone());
476        self.line_height = Some(t.line_height.to_string());
477        if let Some(ls) = t.letter_spacing {
478            self.letter_spacing = Some(format!("{}em", ls));
479        }
480        self
481    }
482
483    /// Set font size
484    pub fn font_size(mut self, size: u16) -> Self {
485        self.font_size = Some(format!("{}px", size));
486        self
487    }
488
489    /// Set font weight
490    pub fn font_weight(mut self, weight: u16) -> Self {
491        self.font_weight = Some(weight.to_string());
492        self
493    }
494
495    /// Set font family
496    pub fn font_family(mut self, family: &str) -> Self {
497        self.font_family = Some(family.into());
498        self
499    }
500
501    /// Set line height
502    pub fn line_height(mut self, height: f32) -> Self {
503        self.line_height = Some(height.to_string());
504        self
505    }
506
507    /// Set text align
508    pub fn text_align(mut self, align: &str) -> Self {
509        self.text_align = Some(align.into());
510        self
511    }
512
513    /// Set text align to center
514    pub fn text_center(mut self) -> Self {
515        self.text_align = Some("center".into());
516        self
517    }
518
519    /// Set text align to left
520    pub fn text_left(mut self) -> Self {
521        self.text_align = Some("left".into());
522        self
523    }
524
525    /// Set text align to right
526    pub fn text_right(mut self) -> Self {
527        self.text_align = Some("right".into());
528        self
529    }
530
531    /// Set text align to left
532    pub fn text_align_left(mut self) -> Self {
533        self.text_align = Some("left".into());
534        self
535    }
536
537    /// Set text decoration to none
538    pub fn no_underline(mut self) -> Self {
539        self.text_decoration = Some("none".into());
540        self
541    }
542
543    /// Set text decoration to underline
544    pub fn underline(mut self) -> Self {
545        self.text_decoration = Some("underline".into());
546        self
547    }
548
549    // ============================================================================
550    // Effects
551    // ============================================================================
552
553    /// Set border radius from scale
554    pub fn rounded(mut self, radius: &RadiusScale, size: &str) -> Self {
555        let val = radius.get(size);
556        self.border_radius = Some(format!("{}px", val));
557        self
558    }
559
560    /// Set border radius to specific value
561    pub fn rounded_px(mut self, px: u16) -> Self {
562        self.border_radius = Some(format!("{}px", px));
563        self
564    }
565
566    /// Set border radius to full (circle/pill)
567    pub fn rounded_full(mut self) -> Self {
568        self.border_radius = Some("9999px".into());
569        self
570    }
571
572    /// Set border with width and color
573    pub fn border(mut self, width: u8, color: &Color) -> Self {
574        self.border = Some(format!("{}px solid {}", width, color.to_rgba()));
575        self
576    }
577
578    /// Set border width only
579    pub fn border_width(mut self, width: u8) -> Self {
580        self.border_width = Some(format!("{}px", width));
581        self
582    }
583
584    /// Set top border
585    pub fn border_top(mut self, width: u8, color: &Color) -> Self {
586        self.border_top = Some(format!("{}px solid {}", width, color.to_rgba()));
587        self
588    }
589
590    /// Set right border
591    pub fn border_right(mut self, width: u8, color: &Color) -> Self {
592        self.border_right = Some(format!("{}px solid {}", width, color.to_rgba()));
593        self
594    }
595
596    /// Set bottom border
597    pub fn border_bottom(mut self, width: u8, color: &Color) -> Self {
598        self.border_bottom = Some(format!("{}px solid {}", width, color.to_rgba()));
599        self
600    }
601
602    /// Set left border
603    pub fn border_left(mut self, width: u8, color: &Color) -> Self {
604        self.border_left = Some(format!("{}px solid {}", width, color.to_rgba()));
605        self
606    }
607
608    /// Set border style (dashed, dotted, etc.)
609    pub fn border_style(mut self, style: &str) -> Self {
610        // This is a simplified implementation - border_style would need proper field
611        // For now, we'll append it to the existing border or set it as custom
612        let existing = self.transform.clone().unwrap_or_default();
613        self.transform = Some(format!("{} border-style: {};", existing, style));
614        self
615    }
616
617    /// Set box shadow
618    pub fn shadow(mut self, shadow: &str) -> Self {
619        self.box_shadow = Some(shadow.into());
620        self
621    }
622
623    /// Set box shadow from theme
624    pub fn shadow_themed(mut self, theme: &ThemeTokens, size: &str) -> Self {
625        self.box_shadow = Some(theme.shadows.get(size).clone());
626        self
627    }
628
629    /// Remove box shadow
630    pub fn shadow_none(mut self) -> Self {
631        self.box_shadow = Some("none".into());
632        self
633    }
634
635    // ============================================================================
636    // Sizing
637    // ============================================================================
638
639    /// Set width
640    pub fn w(mut self, width: &str) -> Self {
641        self.width = Some(width.into());
642        self
643    }
644
645    /// Set width in pixels
646    pub fn w_px(mut self, px: u16) -> Self {
647        self.width = Some(format!("{}px", px));
648        self
649    }
650
651    /// Set width as percentage
652    pub fn w_percent(mut self, pct: u8) -> Self {
653        self.width = Some(format!("{}%", pct));
654        self
655    }
656
657    /// Set width to full (100%)
658    pub fn w_full(mut self) -> Self {
659        self.width = Some("100%".into());
660        self
661    }
662
663    /// Set height
664    pub fn h(mut self, height: &str) -> Self {
665        self.height = Some(height.into());
666        self
667    }
668
669    /// Set height in pixels
670    pub fn h_px(mut self, px: u16) -> Self {
671        self.height = Some(format!("{}px", px));
672        self
673    }
674
675    /// Set height as percentage
676    pub fn h_percent(mut self, pct: u8) -> Self {
677        self.height = Some(format!("{}%", pct));
678        self
679    }
680
681    /// Set height to full (100%)
682    pub fn h_full(mut self) -> Self {
683        self.height = Some("100%".into());
684        self
685    }
686
687    /// Set min-width
688    pub fn min_w(mut self, width: &str) -> Self {
689        self.min_width = Some(width.into());
690        self
691    }
692
693    /// Set min-width in pixels
694    pub fn min_w_px(mut self, px: u16) -> Self {
695        self.min_width = Some(format!("{}px", px));
696        self
697    }
698
699    /// Set min-height
700    pub fn min_h(mut self, height: &str) -> Self {
701        self.min_height = Some(height.into());
702        self
703    }
704
705    /// Set min-height in pixels
706    pub fn min_h_px(mut self, px: u16) -> Self {
707        self.min_height = Some(format!("{}px", px));
708        self
709    }
710
711    /// Set max-width
712    pub fn max_w(mut self, width: &str) -> Self {
713        self.max_width = Some(width.into());
714        self
715    }
716
717    /// Set max-width in pixels
718    pub fn max_w_px(mut self, px: u16) -> Self {
719        self.max_width = Some(format!("{}px", px));
720        self
721    }
722
723    /// Set max-height
724    pub fn max_h(mut self, height: &str) -> Self {
725        self.max_height = Some(height.into());
726        self
727    }
728
729    /// Set max-height in pixels
730    pub fn max_h_px(mut self, px: u16) -> Self {
731        self.max_height = Some(format!("{}px", px));
732        self
733    }
734
735    // ============================================================================
736    // Position
737    // ============================================================================
738
739    /// Set position to any value
740    pub fn position(mut self, value: &str) -> Self {
741        self.position = Some(value.into());
742        self
743    }
744
745    /// Set position to relative
746    pub fn relative(mut self) -> Self {
747        self.position = Some("relative".into());
748        self
749    }
750
751    /// Set position to absolute
752    pub fn absolute(mut self) -> Self {
753        self.position = Some("absolute".into());
754        self
755    }
756
757    /// Set position to fixed
758    pub fn fixed(mut self) -> Self {
759        self.position = Some("fixed".into());
760        self
761    }
762
763    /// Set position to sticky
764    pub fn sticky(mut self) -> Self {
765        self.position = Some("sticky".into());
766        self
767    }
768
769    /// Set top position
770    pub fn top(mut self, value: &str) -> Self {
771        self.top = Some(value.into());
772        self
773    }
774
775    /// Set right position
776    pub fn right(mut self, value: &str) -> Self {
777        self.right = Some(value.into());
778        self
779    }
780
781    /// Set bottom position
782    pub fn bottom(mut self, value: &str) -> Self {
783        self.bottom = Some(value.into());
784        self
785    }
786
787    /// Set left position
788    pub fn left(mut self, value: &str) -> Self {
789        self.left = Some(value.into());
790        self
791    }
792
793    /// Set z-index
794    pub fn z_index(mut self, z: i16) -> Self {
795        self.z_index = Some(z.to_string());
796        self
797    }
798
799    // ============================================================================
800    // Misc
801    // ============================================================================
802
803    /// Set cursor style
804    pub fn cursor(mut self, cursor: &str) -> Self {
805        self.cursor = Some(cursor.into());
806        self
807    }
808
809    /// Set cursor to pointer
810    pub fn cursor_pointer(mut self) -> Self {
811        self.cursor = Some("pointer".into());
812        self
813    }
814
815    /// Set opacity
816    pub fn opacity(mut self, opacity: f32) -> Self {
817        self.opacity = Some(opacity.clamp(0.0, 1.0).to_string());
818        self
819    }
820
821    /// Set transition
822    pub fn transition(mut self, transition: &str) -> Self {
823        self.transition = Some(transition.into());
824        self
825    }
826
827    /// Set transform
828    pub fn transform(mut self, transform: &str) -> Self {
829        self.transform = Some(transform.into());
830        self
831    }
832
833    /// Set overflow to hidden
834    pub fn overflow_hidden(mut self) -> Self {
835        self.overflow = Some("hidden".into());
836        self
837    }
838
839    /// Set overflow to auto
840    pub fn overflow_auto(mut self) -> Self {
841        self.overflow = Some("auto".into());
842        self
843    }
844
845    /// Set overflow to scroll
846    pub fn overflow_scroll(mut self) -> Self {
847        self.overflow = Some("scroll".into());
848        self
849    }
850
851    /// Set visibility to hidden
852    pub fn invisible(mut self) -> Self {
853        self.visibility = Some("hidden".into());
854        self
855    }
856
857    /// Set visibility to visible
858    pub fn visible(mut self) -> Self {
859        self.visibility = Some("visible".into());
860        self
861    }
862
863    /// Disable pointer events
864    pub fn pointer_events_none(mut self) -> Self {
865        self.pointer_events = Some("none".into());
866        self
867    }
868
869    /// Enable pointer events
870    pub fn pointer_events_auto(mut self) -> Self {
871        self.pointer_events = Some("auto".into());
872        self
873    }
874
875    /// Disable user selection
876    pub fn select_none(mut self) -> Self {
877        self.user_select = Some("none".into());
878        self
879    }
880
881    /// Set white-space to nowrap
882    pub fn whitespace_nowrap(mut self) -> Self {
883        self.white_space = Some("nowrap".into());
884        self
885    }
886
887    /// Set word-break to break-all
888    pub fn break_all(mut self) -> Self {
889        self.word_break = Some("break-all".into());
890        self
891    }
892
893    /// Set outline style
894    pub fn outline(mut self, value: &str) -> Self {
895        self.outline = Some(value.into());
896        self
897    }
898
899    /// Set resize style
900    pub fn resize(mut self, value: &str) -> Self {
901        self.resize = Some(value.into());
902        self
903    }
904
905    /// Set flex-shrink
906    pub fn flex_shrink(mut self, value: u8) -> Self {
907        // Note: flex_shrink is not in the struct, adding as custom for now
908        // We'll use a different approach - add it to the struct
909        self.transform = Some(format!("flex-shrink: {}", value));
910        self
911    }
912
913    /// Set transparent background
914    pub fn bg_transparent(mut self) -> Self {
915        self.background_color = Some("transparent".into());
916        self
917    }
918
919    /// Set flex-grow
920    pub fn flex_grow(mut self, value: u8) -> Self {
921        // We'll add this to the transform field as a workaround since flex_grow isn't a standard field
922        let existing = self.transform.unwrap_or_default();
923        self.transform = Some(format!("{} flex-grow: {};", existing, value));
924        self
925    }
926
927    /// Set min-height to 100%
928    pub fn min_h_full(mut self) -> Self {
929        self.min_height = Some("100%".into());
930        self
931    }
932
933    /// Set inset (top, right, bottom, left) - shorthand for absolute positioning
934    pub fn inset(mut self, value: &str) -> Self {
935        self.top = Some(value.into());
936        self.right = Some(value.into());
937        self.bottom = Some(value.into());
938        self.left = Some(value.into());
939        self
940    }
941
942    /// Set flex-grow to 1 (flex-1)
943    pub fn flex_1(mut self) -> Self {
944        let existing = self.transform.unwrap_or_default();
945        self.transform = Some(format!("{} flex: 1 1 0%;", existing));
946        self
947    }
948
949    /// Set overflow-x to auto
950    pub fn overflow_x_auto(mut self) -> Self {
951        let existing = self.transform.unwrap_or_default();
952        self.transform = Some(format!("{} overflow-x: auto;", existing));
953        self
954    }
955
956    /// Set overflow-y to auto
957    pub fn overflow_y_auto(mut self) -> Self {
958        let existing = self.transform.unwrap_or_default();
959        self.transform = Some(format!("{} overflow-y: auto;", existing));
960        self
961    }
962
963    /// Add custom/raw CSS
964    pub fn custom(mut self, css: &str) -> Self {
965        let existing = self.transform.unwrap_or_default();
966        self.transform = Some(format!("{} {}", existing, css));
967        self
968    }
969
970    // ============================================================================
971    // Build
972    // ============================================================================
973
974    /// Build the style string
975    pub fn build(self) -> String {
976        let mut style = String::new();
977
978        // Layout
979        write_if_some(&mut style, "display", &self.display);
980        write_if_some(&mut style, "flex-direction", &self.flex_direction);
981        write_if_some(&mut style, "flex-wrap", &self.flex_wrap);
982        write_if_some(&mut style, "align-items", &self.align_items);
983        write_if_some(&mut style, "align-self", &self.align_self);
984        write_if_some(&mut style, "justify-content", &self.justify_content);
985        write_if_some(&mut style, "justify-items", &self.justify_items);
986        write_if_some(&mut style, "gap", &self.gap);
987        write_if_some(&mut style, "row-gap", &self.row_gap);
988        write_if_some(&mut style, "column-gap", &self.column_gap);
989
990        // Spacing
991        write_if_some(&mut style, "padding", &self.padding);
992        write_if_some(&mut style, "padding-top", &self.padding_top);
993        write_if_some(&mut style, "padding-right", &self.padding_right);
994        write_if_some(&mut style, "padding-bottom", &self.padding_bottom);
995        write_if_some(&mut style, "padding-left", &self.padding_left);
996        write_if_some(&mut style, "margin", &self.margin);
997        write_if_some(&mut style, "margin-top", &self.margin_top);
998        write_if_some(&mut style, "margin-right", &self.margin_right);
999        write_if_some(&mut style, "margin-bottom", &self.margin_bottom);
1000        write_if_some(&mut style, "margin-left", &self.margin_left);
1001
1002        // Colors
1003        write_if_some(&mut style, "background-color", &self.background_color);
1004        write_if_some(&mut style, "color", &self.color);
1005        write_if_some(&mut style, "border-color", &self.border_color);
1006
1007        // Typography
1008        write_if_some(&mut style, "font-size", &self.font_size);
1009        write_if_some(&mut style, "font-weight", &self.font_weight);
1010        write_if_some(&mut style, "font-family", &self.font_family);
1011        write_if_some(&mut style, "line-height", &self.line_height);
1012        write_if_some(&mut style, "text-align", &self.text_align);
1013        write_if_some(&mut style, "text-decoration", &self.text_decoration);
1014        write_if_some(&mut style, "letter-spacing", &self.letter_spacing);
1015
1016        // Effects
1017        write_if_some(&mut style, "border-radius", &self.border_radius);
1018        write_if_some(&mut style, "border", &self.border);
1019        write_if_some(&mut style, "border-top", &self.border_top);
1020        write_if_some(&mut style, "border-right", &self.border_right);
1021        write_if_some(&mut style, "border-bottom", &self.border_bottom);
1022        write_if_some(&mut style, "border-left", &self.border_left);
1023        write_if_some(&mut style, "border-width", &self.border_width);
1024        write_if_some(&mut style, "box-shadow", &self.box_shadow);
1025
1026        // Sizing
1027        write_if_some(&mut style, "width", &self.width);
1028        write_if_some(&mut style, "height", &self.height);
1029        write_if_some(&mut style, "min-width", &self.min_width);
1030        write_if_some(&mut style, "min-height", &self.min_height);
1031        write_if_some(&mut style, "max-width", &self.max_width);
1032        write_if_some(&mut style, "max-height", &self.max_height);
1033
1034        // Position
1035        write_if_some(&mut style, "position", &self.position);
1036        write_if_some(&mut style, "top", &self.top);
1037        write_if_some(&mut style, "right", &self.right);
1038        write_if_some(&mut style, "bottom", &self.bottom);
1039        write_if_some(&mut style, "left", &self.left);
1040        write_if_some(&mut style, "z-index", &self.z_index);
1041
1042        // Misc
1043        write_if_some(&mut style, "cursor", &self.cursor);
1044        write_if_some(&mut style, "opacity", &self.opacity);
1045        write_if_some(&mut style, "transition", &self.transition);
1046        write_if_some(&mut style, "transform", &self.transform);
1047        write_if_some(&mut style, "overflow", &self.overflow);
1048        write_if_some(&mut style, "visibility", &self.visibility);
1049        write_if_some(&mut style, "pointer-events", &self.pointer_events);
1050        write_if_some(&mut style, "user-select", &self.user_select);
1051        write_if_some(&mut style, "white-space", &self.white_space);
1052        write_if_some(&mut style, "word-break", &self.word_break);
1053        write_if_some(&mut style, "outline", &self.outline);
1054        write_if_some(&mut style, "resize", &self.resize);
1055
1056        style
1057    }
1058
1059    /// Merge another style into this one (other takes precedence)
1060    pub fn merge(mut self, other: Style) -> Self {
1061        // Layout
1062        if other.display.is_some() {
1063            self.display = other.display;
1064        }
1065        if other.flex_direction.is_some() {
1066            self.flex_direction = other.flex_direction;
1067        }
1068        if other.flex_wrap.is_some() {
1069            self.flex_wrap = other.flex_wrap;
1070        }
1071        if other.align_items.is_some() {
1072            self.align_items = other.align_items;
1073        }
1074        if other.align_self.is_some() {
1075            self.align_self = other.align_self;
1076        }
1077        if other.justify_content.is_some() {
1078            self.justify_content = other.justify_content;
1079        }
1080        if other.justify_items.is_some() {
1081            self.justify_items = other.justify_items;
1082        }
1083        if other.gap.is_some() {
1084            self.gap = other.gap;
1085        }
1086        if other.row_gap.is_some() {
1087            self.row_gap = other.row_gap;
1088        }
1089        if other.column_gap.is_some() {
1090            self.column_gap = other.column_gap;
1091        }
1092
1093        // Spacing
1094        if other.padding.is_some() {
1095            self.padding = other.padding;
1096        }
1097        if other.padding_top.is_some() {
1098            self.padding_top = other.padding_top;
1099        }
1100        if other.padding_right.is_some() {
1101            self.padding_right = other.padding_right;
1102        }
1103        if other.padding_bottom.is_some() {
1104            self.padding_bottom = other.padding_bottom;
1105        }
1106        if other.padding_left.is_some() {
1107            self.padding_left = other.padding_left;
1108        }
1109        if other.margin.is_some() {
1110            self.margin = other.margin;
1111        }
1112        if other.margin_top.is_some() {
1113            self.margin_top = other.margin_top;
1114        }
1115        if other.margin_right.is_some() {
1116            self.margin_right = other.margin_right;
1117        }
1118        if other.margin_bottom.is_some() {
1119            self.margin_bottom = other.margin_bottom;
1120        }
1121        if other.margin_left.is_some() {
1122            self.margin_left = other.margin_left;
1123        }
1124
1125        // Colors
1126        if other.background_color.is_some() {
1127            self.background_color = other.background_color;
1128        }
1129        if other.color.is_some() {
1130            self.color = other.color;
1131        }
1132        if other.border_color.is_some() {
1133            self.border_color = other.border_color;
1134        }
1135
1136        // Typography
1137        if other.font_size.is_some() {
1138            self.font_size = other.font_size;
1139        }
1140        if other.font_weight.is_some() {
1141            self.font_weight = other.font_weight;
1142        }
1143        if other.font_family.is_some() {
1144            self.font_family = other.font_family;
1145        }
1146        if other.line_height.is_some() {
1147            self.line_height = other.line_height;
1148        }
1149        if other.text_align.is_some() {
1150            self.text_align = other.text_align;
1151        }
1152        if other.text_decoration.is_some() {
1153            self.text_decoration = other.text_decoration;
1154        }
1155        if other.letter_spacing.is_some() {
1156            self.letter_spacing = other.letter_spacing;
1157        }
1158
1159        // Effects
1160        if other.border_radius.is_some() {
1161            self.border_radius = other.border_radius;
1162        }
1163        if other.border.is_some() {
1164            self.border = other.border;
1165        }
1166        if other.border_top.is_some() {
1167            self.border_top = other.border_top;
1168        }
1169        if other.border_right.is_some() {
1170            self.border_right = other.border_right;
1171        }
1172        if other.border_bottom.is_some() {
1173            self.border_bottom = other.border_bottom;
1174        }
1175        if other.border_left.is_some() {
1176            self.border_left = other.border_left;
1177        }
1178        if other.border_width.is_some() {
1179            self.border_width = other.border_width;
1180        }
1181        if other.box_shadow.is_some() {
1182            self.box_shadow = other.box_shadow;
1183        }
1184
1185        // Sizing
1186        if other.width.is_some() {
1187            self.width = other.width;
1188        }
1189        if other.height.is_some() {
1190            self.height = other.height;
1191        }
1192        if other.min_width.is_some() {
1193            self.min_width = other.min_width;
1194        }
1195        if other.min_height.is_some() {
1196            self.min_height = other.min_height;
1197        }
1198        if other.max_width.is_some() {
1199            self.max_width = other.max_width;
1200        }
1201        if other.max_height.is_some() {
1202            self.max_height = other.max_height;
1203        }
1204
1205        // Position
1206        if other.position.is_some() {
1207            self.position = other.position;
1208        }
1209        if other.top.is_some() {
1210            self.top = other.top;
1211        }
1212        if other.right.is_some() {
1213            self.right = other.right;
1214        }
1215        if other.bottom.is_some() {
1216            self.bottom = other.bottom;
1217        }
1218        if other.left.is_some() {
1219            self.left = other.left;
1220        }
1221        if other.z_index.is_some() {
1222            self.z_index = other.z_index;
1223        }
1224
1225        // Misc
1226        if other.cursor.is_some() {
1227            self.cursor = other.cursor;
1228        }
1229        if other.opacity.is_some() {
1230            self.opacity = other.opacity;
1231        }
1232        if other.transition.is_some() {
1233            self.transition = other.transition;
1234        }
1235        if other.transform.is_some() {
1236            self.transform = other.transform;
1237        }
1238        if other.overflow.is_some() {
1239            self.overflow = other.overflow;
1240        }
1241        if other.visibility.is_some() {
1242            self.visibility = other.visibility;
1243        }
1244        if other.pointer_events.is_some() {
1245            self.pointer_events = other.pointer_events;
1246        }
1247        if other.user_select.is_some() {
1248            self.user_select = other.user_select;
1249        }
1250        if other.white_space.is_some() {
1251            self.white_space = other.white_space;
1252        }
1253        if other.word_break.is_some() {
1254            self.word_break = other.word_break;
1255        }
1256        if other.outline.is_some() {
1257            self.outline = other.outline;
1258        }
1259        if other.resize.is_some() {
1260            self.resize = other.resize;
1261        }
1262
1263        self
1264    }
1265}
1266
1267/// Helper function to write CSS property if value exists
1268fn write_if_some(style: &mut String, property: &str, value: &Option<String>) {
1269    if let Some(v) = value {
1270        write!(style, "{}:{};", property, v).unwrap();
1271    }
1272}
1273
1274#[cfg(test)]
1275mod tests {
1276    use super::*;
1277    use crate::theme::tokens::ThemeTokens;
1278
1279    #[test]
1280    fn test_basic_style() {
1281        let style = Style::new().flex().items_center().build();
1282
1283        assert!(style.contains("display:flex"));
1284        assert!(style.contains("align-items:center"));
1285    }
1286
1287    #[test]
1288    fn test_style_with_theme() {
1289        let theme = ThemeTokens::light();
1290        let style = Style::new()
1291            .flex()
1292            .gap(&theme.spacing, "md")
1293            .bg(&theme.colors.primary)
1294            .rounded(&theme.radius, "md")
1295            .build();
1296
1297        assert!(style.contains("display:flex"));
1298        assert!(style.contains("gap:16px"));
1299        assert!(style.contains("background-color:"));
1300        assert!(style.contains("border-radius:8px"));
1301    }
1302
1303    #[test]
1304    fn test_style_merge() {
1305        let base = Style::new().flex().items_center();
1306        let override_style = Style::new().justify_center();
1307
1308        let merged = base.merge(override_style);
1309        let style_str = merged.build();
1310
1311        assert!(style_str.contains("display:flex"));
1312        assert!(style_str.contains("align-items:center"));
1313        assert!(style_str.contains("justify-content:center"));
1314    }
1315}