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::{ThemeTokens, Color, SpacingScale, RadiusScale, 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 box shadow
609    pub fn shadow(mut self, shadow: &str) -> Self {
610        self.box_shadow = Some(shadow.into());
611        self
612    }
613
614    /// Set box shadow from theme
615    pub fn shadow_themed(mut self, theme: &ThemeTokens, size: &str) -> Self {
616        self.box_shadow = Some(theme.shadows.get(size).clone());
617        self
618    }
619
620    /// Remove box shadow
621    pub fn shadow_none(mut self) -> Self {
622        self.box_shadow = Some("none".into());
623        self
624    }
625
626    // ============================================================================
627    // Sizing
628    // ============================================================================
629
630    /// Set width
631    pub fn w(mut self, width: &str) -> Self {
632        self.width = Some(width.into());
633        self
634    }
635
636    /// Set width in pixels
637    pub fn w_px(mut self, px: u16) -> Self {
638        self.width = Some(format!("{}px", px));
639        self
640    }
641
642    /// Set width as percentage
643    pub fn w_percent(mut self, pct: u8) -> Self {
644        self.width = Some(format!("{}%", pct));
645        self
646    }
647
648    /// Set width to full (100%)
649    pub fn w_full(mut self) -> Self {
650        self.width = Some("100%".into());
651        self
652    }
653
654    /// Set height
655    pub fn h(mut self, height: &str) -> Self {
656        self.height = Some(height.into());
657        self
658    }
659
660    /// Set height in pixels
661    pub fn h_px(mut self, px: u16) -> Self {
662        self.height = Some(format!("{}px", px));
663        self
664    }
665
666    /// Set height as percentage
667    pub fn h_percent(mut self, pct: u8) -> Self {
668        self.height = Some(format!("{}%", pct));
669        self
670    }
671
672    /// Set height to full (100%)
673    pub fn h_full(mut self) -> Self {
674        self.height = Some("100%".into());
675        self
676    }
677
678    /// Set min-width
679    pub fn min_w(mut self, width: &str) -> Self {
680        self.min_width = Some(width.into());
681        self
682    }
683
684    /// Set min-width in pixels
685    pub fn min_w_px(mut self, px: u16) -> Self {
686        self.min_width = Some(format!("{}px", px));
687        self
688    }
689
690    /// Set min-height
691    pub fn min_h(mut self, height: &str) -> Self {
692        self.min_height = Some(height.into());
693        self
694    }
695
696    /// Set min-height in pixels
697    pub fn min_h_px(mut self, px: u16) -> Self {
698        self.min_height = Some(format!("{}px", px));
699        self
700    }
701
702    /// Set max-width
703    pub fn max_w(mut self, width: &str) -> Self {
704        self.max_width = Some(width.into());
705        self
706    }
707
708    /// Set max-width in pixels
709    pub fn max_w_px(mut self, px: u16) -> Self {
710        self.max_width = Some(format!("{}px", px));
711        self
712    }
713
714    /// Set max-height
715    pub fn max_h(mut self, height: &str) -> Self {
716        self.max_height = Some(height.into());
717        self
718    }
719
720    /// Set max-height in pixels
721    pub fn max_h_px(mut self, px: u16) -> Self {
722        self.max_height = Some(format!("{}px", px));
723        self
724    }
725
726    // ============================================================================
727    // Position
728    // ============================================================================
729
730    /// Set position to any value
731    pub fn position(mut self, value: &str) -> Self {
732        self.position = Some(value.into());
733        self
734    }
735    
736    /// Set position to relative
737    pub fn relative(mut self) -> Self {
738        self.position = Some("relative".into());
739        self
740    }
741
742    /// Set position to absolute
743    pub fn absolute(mut self) -> Self {
744        self.position = Some("absolute".into());
745        self
746    }
747
748    /// Set position to fixed
749    pub fn fixed(mut self) -> Self {
750        self.position = Some("fixed".into());
751        self
752    }
753
754    /// Set position to sticky
755    pub fn sticky(mut self) -> Self {
756        self.position = Some("sticky".into());
757        self
758    }
759
760    /// Set top position
761    pub fn top(mut self, value: &str) -> Self {
762        self.top = Some(value.into());
763        self
764    }
765
766    /// Set right position
767    pub fn right(mut self, value: &str) -> Self {
768        self.right = Some(value.into());
769        self
770    }
771
772    /// Set bottom position
773    pub fn bottom(mut self, value: &str) -> Self {
774        self.bottom = Some(value.into());
775        self
776    }
777
778    /// Set left position
779    pub fn left(mut self, value: &str) -> Self {
780        self.left = Some(value.into());
781        self
782    }
783
784    /// Set z-index
785    pub fn z_index(mut self, z: i16) -> Self {
786        self.z_index = Some(z.to_string());
787        self
788    }
789
790    // ============================================================================
791    // Misc
792    // ============================================================================
793
794    /// Set cursor style
795    pub fn cursor(mut self, cursor: &str) -> Self {
796        self.cursor = Some(cursor.into());
797        self
798    }
799
800    /// Set cursor to pointer
801    pub fn cursor_pointer(mut self) -> Self {
802        self.cursor = Some("pointer".into());
803        self
804    }
805
806    /// Set opacity
807    pub fn opacity(mut self, opacity: f32) -> Self {
808        self.opacity = Some(opacity.clamp(0.0, 1.0).to_string());
809        self
810    }
811
812    /// Set transition
813    pub fn transition(mut self, transition: &str) -> Self {
814        self.transition = Some(transition.into());
815        self
816    }
817
818    /// Set transform
819    pub fn transform(mut self, transform: &str) -> Self {
820        self.transform = Some(transform.into());
821        self
822    }
823
824    /// Set overflow to hidden
825    pub fn overflow_hidden(mut self) -> Self {
826        self.overflow = Some("hidden".into());
827        self
828    }
829
830    /// Set overflow to auto
831    pub fn overflow_auto(mut self) -> Self {
832        self.overflow = Some("auto".into());
833        self
834    }
835
836    /// Set overflow to scroll
837    pub fn overflow_scroll(mut self) -> Self {
838        self.overflow = Some("scroll".into());
839        self
840    }
841
842    /// Set visibility to hidden
843    pub fn invisible(mut self) -> Self {
844        self.visibility = Some("hidden".into());
845        self
846    }
847
848    /// Set visibility to visible
849    pub fn visible(mut self) -> Self {
850        self.visibility = Some("visible".into());
851        self
852    }
853
854    /// Disable pointer events
855    pub fn pointer_events_none(mut self) -> Self {
856        self.pointer_events = Some("none".into());
857        self
858    }
859
860    /// Enable pointer events
861    pub fn pointer_events_auto(mut self) -> Self {
862        self.pointer_events = Some("auto".into());
863        self
864    }
865
866    /// Disable user selection
867    pub fn select_none(mut self) -> Self {
868        self.user_select = Some("none".into());
869        self
870    }
871
872    /// Set white-space to nowrap
873    pub fn whitespace_nowrap(mut self) -> Self {
874        self.white_space = Some("nowrap".into());
875        self
876    }
877
878    /// Set word-break to break-all
879    pub fn break_all(mut self) -> Self {
880        self.word_break = Some("break-all".into());
881        self
882    }
883    
884    /// Set outline style
885    pub fn outline(mut self, value: &str) -> Self {
886        self.outline = Some(value.into());
887        self
888    }
889
890    /// Set resize style
891    pub fn resize(mut self, value: &str) -> Self {
892        self.resize = Some(value.into());
893        self
894    }
895
896    /// Set flex-shrink
897    pub fn flex_shrink(mut self, value: u8) -> Self {
898        // Note: flex_shrink is not in the struct, adding as custom for now
899        // We'll use a different approach - add it to the struct
900        self.transform = Some(format!("flex-shrink: {}", value));
901        self
902    }
903
904    /// Set transparent background
905    pub fn bg_transparent(mut self) -> Self {
906        self.background_color = Some("transparent".into());
907        self
908    }
909
910    /// Set flex-grow
911    pub fn flex_grow(mut self, value: u8) -> Self {
912        // We'll add this to the transform field as a workaround since flex_grow isn't a standard field
913        let existing = self.transform.unwrap_or_default();
914        self.transform = Some(format!("{} flex-grow: {};", existing, value));
915        self
916    }
917
918    /// Set min-height to 100%
919    pub fn min_h_full(mut self) -> Self {
920        self.min_height = Some("100%".into());
921        self
922    }
923
924    // ============================================================================
925    // Build
926    // ============================================================================
927
928    /// Build the style string
929    pub fn build(self) -> String {
930        let mut style = String::new();
931        
932        // Layout
933        write_if_some(&mut style, "display", &self.display);
934        write_if_some(&mut style, "flex-direction", &self.flex_direction);
935        write_if_some(&mut style, "flex-wrap", &self.flex_wrap);
936        write_if_some(&mut style, "align-items", &self.align_items);
937        write_if_some(&mut style, "align-self", &self.align_self);
938        write_if_some(&mut style, "justify-content", &self.justify_content);
939        write_if_some(&mut style, "justify-items", &self.justify_items);
940        write_if_some(&mut style, "gap", &self.gap);
941        write_if_some(&mut style, "row-gap", &self.row_gap);
942        write_if_some(&mut style, "column-gap", &self.column_gap);
943        
944        // Spacing
945        write_if_some(&mut style, "padding", &self.padding);
946        write_if_some(&mut style, "padding-top", &self.padding_top);
947        write_if_some(&mut style, "padding-right", &self.padding_right);
948        write_if_some(&mut style, "padding-bottom", &self.padding_bottom);
949        write_if_some(&mut style, "padding-left", &self.padding_left);
950        write_if_some(&mut style, "margin", &self.margin);
951        write_if_some(&mut style, "margin-top", &self.margin_top);
952        write_if_some(&mut style, "margin-right", &self.margin_right);
953        write_if_some(&mut style, "margin-bottom", &self.margin_bottom);
954        write_if_some(&mut style, "margin-left", &self.margin_left);
955        
956        // Colors
957        write_if_some(&mut style, "background-color", &self.background_color);
958        write_if_some(&mut style, "color", &self.color);
959        write_if_some(&mut style, "border-color", &self.border_color);
960        
961        // Typography
962        write_if_some(&mut style, "font-size", &self.font_size);
963        write_if_some(&mut style, "font-weight", &self.font_weight);
964        write_if_some(&mut style, "font-family", &self.font_family);
965        write_if_some(&mut style, "line-height", &self.line_height);
966        write_if_some(&mut style, "text-align", &self.text_align);
967        write_if_some(&mut style, "text-decoration", &self.text_decoration);
968        write_if_some(&mut style, "letter-spacing", &self.letter_spacing);
969        
970        // Effects
971        write_if_some(&mut style, "border-radius", &self.border_radius);
972        write_if_some(&mut style, "border", &self.border);
973        write_if_some(&mut style, "border-top", &self.border_top);
974        write_if_some(&mut style, "border-right", &self.border_right);
975        write_if_some(&mut style, "border-bottom", &self.border_bottom);
976        write_if_some(&mut style, "border-left", &self.border_left);
977        write_if_some(&mut style, "border-width", &self.border_width);
978        write_if_some(&mut style, "box-shadow", &self.box_shadow);
979        
980        // Sizing
981        write_if_some(&mut style, "width", &self.width);
982        write_if_some(&mut style, "height", &self.height);
983        write_if_some(&mut style, "min-width", &self.min_width);
984        write_if_some(&mut style, "min-height", &self.min_height);
985        write_if_some(&mut style, "max-width", &self.max_width);
986        write_if_some(&mut style, "max-height", &self.max_height);
987        
988        // Position
989        write_if_some(&mut style, "position", &self.position);
990        write_if_some(&mut style, "top", &self.top);
991        write_if_some(&mut style, "right", &self.right);
992        write_if_some(&mut style, "bottom", &self.bottom);
993        write_if_some(&mut style, "left", &self.left);
994        write_if_some(&mut style, "z-index", &self.z_index);
995        
996        // Misc
997        write_if_some(&mut style, "cursor", &self.cursor);
998        write_if_some(&mut style, "opacity", &self.opacity);
999        write_if_some(&mut style, "transition", &self.transition);
1000        write_if_some(&mut style, "transform", &self.transform);
1001        write_if_some(&mut style, "overflow", &self.overflow);
1002        write_if_some(&mut style, "visibility", &self.visibility);
1003        write_if_some(&mut style, "pointer-events", &self.pointer_events);
1004        write_if_some(&mut style, "user-select", &self.user_select);
1005        write_if_some(&mut style, "white-space", &self.white_space);
1006        write_if_some(&mut style, "word-break", &self.word_break);
1007        write_if_some(&mut style, "outline", &self.outline);
1008        write_if_some(&mut style, "resize", &self.resize);
1009        
1010        style
1011    }
1012
1013    /// Merge another style into this one (other takes precedence)
1014    pub fn merge(mut self, other: Style) -> Self {
1015        // Layout
1016        if other.display.is_some() { self.display = other.display; }
1017        if other.flex_direction.is_some() { self.flex_direction = other.flex_direction; }
1018        if other.flex_wrap.is_some() { self.flex_wrap = other.flex_wrap; }
1019        if other.align_items.is_some() { self.align_items = other.align_items; }
1020        if other.align_self.is_some() { self.align_self = other.align_self; }
1021        if other.justify_content.is_some() { self.justify_content = other.justify_content; }
1022        if other.justify_items.is_some() { self.justify_items = other.justify_items; }
1023        if other.gap.is_some() { self.gap = other.gap; }
1024        if other.row_gap.is_some() { self.row_gap = other.row_gap; }
1025        if other.column_gap.is_some() { self.column_gap = other.column_gap; }
1026        
1027        // Spacing
1028        if other.padding.is_some() { self.padding = other.padding; }
1029        if other.padding_top.is_some() { self.padding_top = other.padding_top; }
1030        if other.padding_right.is_some() { self.padding_right = other.padding_right; }
1031        if other.padding_bottom.is_some() { self.padding_bottom = other.padding_bottom; }
1032        if other.padding_left.is_some() { self.padding_left = other.padding_left; }
1033        if other.margin.is_some() { self.margin = other.margin; }
1034        if other.margin_top.is_some() { self.margin_top = other.margin_top; }
1035        if other.margin_right.is_some() { self.margin_right = other.margin_right; }
1036        if other.margin_bottom.is_some() { self.margin_bottom = other.margin_bottom; }
1037        if other.margin_left.is_some() { self.margin_left = other.margin_left; }
1038        
1039        // Colors
1040        if other.background_color.is_some() { self.background_color = other.background_color; }
1041        if other.color.is_some() { self.color = other.color; }
1042        if other.border_color.is_some() { self.border_color = other.border_color; }
1043        
1044        // Typography
1045        if other.font_size.is_some() { self.font_size = other.font_size; }
1046        if other.font_weight.is_some() { self.font_weight = other.font_weight; }
1047        if other.font_family.is_some() { self.font_family = other.font_family; }
1048        if other.line_height.is_some() { self.line_height = other.line_height; }
1049        if other.text_align.is_some() { self.text_align = other.text_align; }
1050        if other.text_decoration.is_some() { self.text_decoration = other.text_decoration; }
1051        if other.letter_spacing.is_some() { self.letter_spacing = other.letter_spacing; }
1052        
1053        // Effects
1054        if other.border_radius.is_some() { self.border_radius = other.border_radius; }
1055        if other.border.is_some() { self.border = other.border; }
1056        if other.border_top.is_some() { self.border_top = other.border_top; }
1057        if other.border_right.is_some() { self.border_right = other.border_right; }
1058        if other.border_bottom.is_some() { self.border_bottom = other.border_bottom; }
1059        if other.border_left.is_some() { self.border_left = other.border_left; }
1060        if other.border_width.is_some() { self.border_width = other.border_width; }
1061        if other.box_shadow.is_some() { self.box_shadow = other.box_shadow; }
1062        
1063        // Sizing
1064        if other.width.is_some() { self.width = other.width; }
1065        if other.height.is_some() { self.height = other.height; }
1066        if other.min_width.is_some() { self.min_width = other.min_width; }
1067        if other.min_height.is_some() { self.min_height = other.min_height; }
1068        if other.max_width.is_some() { self.max_width = other.max_width; }
1069        if other.max_height.is_some() { self.max_height = other.max_height; }
1070        
1071        // Position
1072        if other.position.is_some() { self.position = other.position; }
1073        if other.top.is_some() { self.top = other.top; }
1074        if other.right.is_some() { self.right = other.right; }
1075        if other.bottom.is_some() { self.bottom = other.bottom; }
1076        if other.left.is_some() { self.left = other.left; }
1077        if other.z_index.is_some() { self.z_index = other.z_index; }
1078        
1079        // Misc
1080        if other.cursor.is_some() { self.cursor = other.cursor; }
1081        if other.opacity.is_some() { self.opacity = other.opacity; }
1082        if other.transition.is_some() { self.transition = other.transition; }
1083        if other.transform.is_some() { self.transform = other.transform; }
1084        if other.overflow.is_some() { self.overflow = other.overflow; }
1085        if other.visibility.is_some() { self.visibility = other.visibility; }
1086        if other.pointer_events.is_some() { self.pointer_events = other.pointer_events; }
1087        if other.user_select.is_some() { self.user_select = other.user_select; }
1088        if other.white_space.is_some() { self.white_space = other.white_space; }
1089        if other.word_break.is_some() { self.word_break = other.word_break; }
1090        if other.outline.is_some() { self.outline = other.outline; }
1091        if other.resize.is_some() { self.resize = other.resize; }
1092        
1093        self
1094    }
1095}
1096
1097/// Helper function to write CSS property if value exists
1098fn write_if_some(style: &mut String, property: &str, value: &Option<String>) {
1099    if let Some(v) = value {
1100        write!(style, "{}:{};", property, v).unwrap();
1101    }
1102}
1103
1104#[cfg(test)]
1105mod tests {
1106    use super::*;
1107    use crate::theme::tokens::ThemeTokens;
1108
1109    #[test]
1110    fn test_basic_style() {
1111        let style = Style::new()
1112            .flex()
1113            .items_center()
1114            .build();
1115        
1116        assert!(style.contains("display:flex"));
1117        assert!(style.contains("align-items:center"));
1118    }
1119
1120    #[test]
1121    fn test_style_with_theme() {
1122        let theme = ThemeTokens::light();
1123        let style = Style::new()
1124            .flex()
1125            .gap(&theme.spacing, "md")
1126            .bg(&theme.colors.primary)
1127            .rounded(&theme.radius, "md")
1128            .build();
1129        
1130        assert!(style.contains("display:flex"));
1131        assert!(style.contains("gap:16px"));
1132        assert!(style.contains("background-color:"));
1133        assert!(style.contains("border-radius:8px"));
1134    }
1135
1136    #[test]
1137    fn test_style_merge() {
1138        let base = Style::new().flex().items_center();
1139        let override_style = Style::new().justify_center();
1140        
1141        let merged = base.merge(override_style);
1142        let style_str = merged.build();
1143        
1144        assert!(style_str.contains("display:flex"));
1145        assert!(style_str.contains("align-items:center"));
1146        assert!(style_str.contains("justify-content:center"));
1147    }
1148}