cabin_tailwind/
lib.rs

1mod pseudo;
2pub mod registry;
3pub mod utilities;
4
5use std::fmt;
6use std::hash::Hasher;
7
8pub use cabin::html::elements::common::Class;
9use cabin::View;
10pub use cabin_macros::tw;
11pub use utilities as css;
12
13pub mod prelude {
14    pub use crate::{tw, utilities as tw, Responsive, Utility};
15}
16
17pub trait Utility {
18    fn declarations(&self, f: &mut dyn fmt::Write) -> fmt::Result;
19
20    fn selector_prefix(&self, _f: &mut dyn fmt::Write) -> fmt::Result {
21        Ok(())
22    }
23
24    fn selector_suffix(&self, _f: &mut dyn fmt::Write) -> fmt::Result {
25        Ok(())
26    }
27
28    fn suffix(&self, _f: &mut dyn fmt::Write) -> fmt::Result {
29        Ok(())
30    }
31
32    fn write_animate_from(&self, _f: &mut dyn fmt::Write) -> fmt::Result {
33        Ok(())
34    }
35
36    fn write_animate_to(&self, _f: &mut dyn fmt::Write) -> fmt::Result {
37        Ok(())
38    }
39
40    fn hash_modifier(&self, _hasher: &mut dyn Hasher) {}
41
42    fn override_class_name(&self) -> Option<&str> {
43        None
44    }
45
46    /// The higher the returned number the later the style is positioned in the stylesheet to take
47    /// precedence.
48    fn order(&self) -> usize {
49        0
50    }
51
52    /// Apply style only when the element is being pressed (`:active`).
53    fn active(self) -> pseudo::active::Active<Self>
54    where
55        Self: Sized,
56    {
57        pseudo::active::Active(self)
58    }
59
60    /// Apply style to `::after` pseude element.
61    fn after(self) -> pseudo::after::After<Self>
62    where
63        Self: Sized,
64    {
65        pseudo::after::After(self)
66    }
67
68    fn animate_from(self) -> pseudo::animate_from::AnimateFrom<Self>
69    where
70        Self: Sized,
71    {
72        pseudo::animate_from::AnimateFrom(self)
73    }
74
75    fn animate_to(self) -> pseudo::animate_to::AnimateTo<Self>
76    where
77        Self: Sized,
78    {
79        pseudo::animate_to::AnimateTo(self)
80    }
81
82    /// Apply style to all direct children (`> *`).
83    fn apply_to_children(self) -> pseudo::apply_to_children::ApplyToChildren<Self>
84    where
85        Self: Sized,
86    {
87        pseudo::apply_to_children::ApplyToChildren(self)
88    }
89
90    /// Apply style to `::before` pseude element.
91    fn before(self) -> pseudo::before::Before<Self>
92    where
93        Self: Sized,
94    {
95        pseudo::before::Before(self)
96    }
97
98    /// Apply style only when the element is disabled (`:disabled`).
99    fn disabled(self) -> pseudo::disabled::Disabled<Self>
100    where
101        Self: Sized,
102    {
103        pseudo::disabled::Disabled(self)
104    }
105
106    /// Apply style only when the element is not disabled (`:enabled`).
107    fn enabled(self) -> pseudo::enabled::Enabled<Self>
108    where
109        Self: Sized,
110    {
111        pseudo::enabled::Enabled(self)
112    }
113
114    /// Apply style only when the element has focus (`:foucs`).
115    fn focus(self) -> pseudo::focus::Focus<Self>
116    where
117        Self: Sized,
118    {
119        pseudo::focus::Focus(self)
120    }
121
122    /// Apply style only when the element has been focused using the keyboard (`:foucs-visible`).
123    fn focus_visible(self) -> pseudo::focus_visible::FocusVisible<Self>
124    where
125        Self: Sized,
126    {
127        pseudo::focus_visible::FocusVisible(self)
128    }
129
130    /// Apply style only when the element or one of its descendants has focus (`:foucs-within`).
131    fn focus_within(self) -> pseudo::focus_within::FocusWithin<Self>
132    where
133        Self: Sized,
134    {
135        pseudo::focus_within::FocusWithin(self)
136    }
137
138    fn group_hover(self) -> pseudo::group_hover::GroupHover<Self>
139    where
140        Self: Sized,
141    {
142        pseudo::group_hover::GroupHover(self)
143    }
144
145    /// Apply style only when the user hovers over the element (`:hover`).
146    fn hover(self) -> pseudo::hover::Hover<Self>
147    where
148        Self: Sized,
149    {
150        pseudo::hover::Hover(self)
151    }
152
153    /// Apply style only when the link has already been visited (`:visited`).
154    fn visited(self) -> pseudo::visited::Visited<Self>
155    where
156        Self: Sized,
157    {
158        pseudo::visited::Visited(self)
159    }
160
161    /// Apply style only when browser width is at least `min_width_px`.
162    /// `@media (min-width: {min_width_px}px)`
163    fn min_width_px(self, min_width_px: u32) -> pseudo::min_width::MinWidth<Self>
164    where
165        Self: Sized,
166    {
167        pseudo::min_width::MinWidth::new(min_width_px, self)
168    }
169
170    /// Apply style only when browser width does not exceed `max_width_px`.
171    /// `@media (max-width: {max_width_px}px)`
172    fn max_width_px(self, max_width_px: u32) -> pseudo::max_width::MaxWidth<Self>
173    where
174        Self: Sized,
175    {
176        pseudo::max_width::MaxWidth::new(max_width_px, self)
177    }
178}
179
180include!(concat!(env!("OUT_DIR"), "/responsive.rs"));
181
182pub struct Property<V = &'static str>(pub(crate) &'static str, pub(crate) V);
183pub struct PropertyTwice<V = &'static str>(
184    pub(crate) &'static str,
185    pub(crate) &'static str,
186    pub(crate) V,
187);
188
189pub struct StaticClass(pub(crate) &'static str);
190
191#[derive(Debug, Clone, Copy, PartialEq)]
192pub enum Length {
193    Auto,
194    MinContent,
195    MaxContent,
196    FitContent,
197    Vw(u16),
198    Vh(u16),
199    Px(f32),
200    Em(f32),
201    Rem(f32),
202    Percent(f32),
203}
204
205impl Length {
206    fn is_zero(&self) -> bool {
207        match self {
208            Length::Auto | Length::MinContent | Length::MaxContent | Length::FitContent => false,
209            Length::Vw(v) | Length::Vh(v) => *v == 0,
210            Length::Px(v) | Length::Em(v) | Length::Rem(v) | Length::Percent(v) => {
211                v.abs() < f32::EPSILON
212            }
213        }
214    }
215}
216
217impl fmt::Display for Length {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        if self.is_zero() {
220            return f.write_str("0");
221        }
222
223        match self {
224            Length::Auto => f.write_str("auto"),
225            Length::MinContent => f.write_str("min-content"),
226            Length::MaxContent => f.write_str("max-content"),
227            Length::FitContent => f.write_str("fit-content"),
228            Length::Vw(v) => write!(f, "{v}vw"),
229            Length::Vh(v) => write!(f, "{v}vh"),
230            Length::Px(v) => write!(f, "{v}px"),
231            Length::Em(v) => write!(f, "{v}em"),
232            Length::Rem(v) => write!(f, "{v}rem"),
233            Length::Percent(v) => write!(f, "{v}%"),
234        }
235    }
236}
237
238impl<V: fmt::Display> Utility for Property<V> {
239    fn declarations(&self, f: &mut dyn fmt::Write) -> fmt::Result {
240        writeln!(f, "{}: {};", self.0, self.1)
241    }
242}
243
244impl<V: fmt::Display> Utility for PropertyTwice<V> {
245    fn declarations(&self, f: &mut dyn fmt::Write) -> fmt::Result {
246        writeln!(f, "{}: {};", self.0, self.2)?;
247        writeln!(f, "{}: {};", self.1, self.2)?;
248        Ok(())
249    }
250}
251
252impl Utility for StaticClass {
253    fn declarations(&self, _: &mut dyn fmt::Write) -> fmt::Result {
254        Ok(())
255    }
256
257    fn hash_modifier(&self, hasher: &mut dyn Hasher) {
258        hasher.write(self.0.as_bytes());
259    }
260
261    fn override_class_name(&self) -> Option<&str> {
262        Some("group")
263    }
264}
265
266pub fn cabin_stylesheets() -> impl View {
267    use std::sync::OnceLock;
268
269    use cabin::html::Common;
270    use cabin::{content_hash, html};
271    use html::elements::link::Link;
272
273    static HREF: OnceLock<String> = OnceLock::new();
274    let href = HREF.get_or_init(|| {
275        let hash = content_hash(registry::StyleRegistry::global().style_sheet().as_bytes());
276        format!("/styles.css?{hash}")
277    });
278
279    html::link()
280        .id("cabin-styles")
281        .rel(html::elements::link::Rel::StyleSheet)
282        .href(href)
283}