1#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
22#[non_exhaustive]
23pub enum Color {
24 #[default]
26 Gray,
27 Primary,
29 Success,
31 Warning,
33 Danger,
35 Info,
37}
38
39impl Color {
40 #[doc(hidden)]
42 pub const fn from_name(name: &str) -> Option<Self> {
43 Some(match name.as_bytes() {
44 b"gray" => Self::Gray,
45 b"primary" => Self::Primary,
46 b"success" => Self::Success,
47 b"warning" => Self::Warning,
48 b"danger" => Self::Danger,
49 b"info" => Self::Info,
50 _ => return None,
51 })
52 }
53
54 pub(crate) const fn name(self) -> &'static str {
56 match self {
57 Self::Gray => "gray",
58 Self::Primary => "primary",
59 Self::Success => "success",
60 Self::Warning => "warning",
61 Self::Danger => "danger",
62 Self::Info => "info",
63 }
64 }
65
66 pub(crate) const fn dot_class(self) -> &'static str {
68 match self {
69 Self::Gray => "size-1.5 shrink-0 rounded-full bg-muted-foreground/60",
70 Self::Primary => "size-1.5 shrink-0 rounded-full bg-primary",
71 Self::Success => "size-1.5 shrink-0 rounded-full bg-success",
72 Self::Warning => "size-1.5 shrink-0 rounded-full bg-warning",
73 Self::Danger => "size-1.5 shrink-0 rounded-full bg-destructive",
74 Self::Info => "size-1.5 shrink-0 rounded-full bg-info",
75 }
76 }
77
78 pub(crate) const fn fill_class(self) -> &'static str {
80 match self {
81 Self::Gray => "bg-muted-foreground",
82 Self::Primary => "bg-primary",
83 Self::Success => "bg-success",
84 Self::Warning => "bg-warning",
85 Self::Danger => "bg-destructive",
86 Self::Info => "bg-info",
87 }
88 }
89
90 pub(crate) const fn row_class(self) -> &'static str {
92 match self {
93 Self::Gray => "bg-muted/50",
94 Self::Primary => "bg-primary/5",
95 Self::Success => "bg-success/10",
96 Self::Warning => "bg-warning/10",
97 Self::Danger => "bg-destructive/10",
98 Self::Info => "bg-info/10",
99 }
100 }
101
102 pub(crate) const fn button_tone(self) -> Option<&'static str> {
105 match self {
106 Self::Gray => None,
107 Self::Primary => Some("text-primary! hover:bg-primary/5! active:bg-primary/10!"),
108 Self::Success => Some("text-success! hover:bg-success/5! active:bg-success/10!"),
109 Self::Warning => Some("text-warning! hover:bg-warning/5! active:bg-warning/10!"),
110 Self::Danger => {
111 Some("text-destructive! hover:bg-destructive/5! active:bg-destructive/10!")
112 }
113 Self::Info => Some("text-info! hover:bg-info/5! active:bg-info/10!"),
114 }
115 }
116
117 pub(crate) const fn text_class(self) -> &'static str {
119 match self {
120 Self::Gray => "text-muted-foreground",
121 Self::Primary => "text-primary",
122 Self::Success => "text-success",
123 Self::Warning => "text-warning",
124 Self::Danger => "text-destructive",
125 Self::Info => "text-info",
126 }
127 }
128}
129
130#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
132pub enum FontWeight {
133 Light,
135 #[default]
137 Normal,
138 Medium,
140 Semibold,
142 Bold,
144}
145
146impl FontWeight {
147 pub(crate) const fn class(self) -> &'static str {
149 match self {
150 Self::Light => "font-light",
151 Self::Normal => "",
152 Self::Medium => "font-medium",
153 Self::Semibold => "font-semibold",
154 Self::Bold => "font-bold",
155 }
156 }
157}
158
159#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
161pub enum Alignment {
162 #[default]
164 Start,
165 Center,
167 End,
169}
170
171impl Alignment {
172 pub(crate) const fn class(self) -> &'static str {
174 match self {
175 Self::Start => "",
176 Self::Center => "text-center",
177 Self::End => "text-end",
178 }
179 }
180}
181
182#[derive(Clone, Copy, Debug, PartialEq)]
189#[non_exhaustive]
190pub enum Number {
191 Int(i128),
193 Float(f64),
195 Decimal {
198 mantissa: i128,
200 scale: u32,
202 },
203}
204
205pub trait Numeric {
209 fn number(&self) -> Option<Number>;
211}
212
213impl<T: Numeric + ?Sized> Numeric for &T {
214 fn number(&self) -> Option<Number> {
215 (**self).number()
216 }
217}
218
219macro_rules! numeric_ints {
220 ($($ty:ty),+) => {
221 $(
222 impl Numeric for $ty {
223 fn number(&self) -> Option<Number> {
224 Some(Number::Int(i128::from(*self)))
225 }
226 }
227 )+
228 };
229}
230
231numeric_ints!(i8, i16, i32, i64, u8, u16, u32, u64);
232
233impl Numeric for f32 {
234 fn number(&self) -> Option<Number> {
235 Some(Number::Float(f64::from(*self)))
236 }
237}
238
239impl Numeric for f64 {
240 fn number(&self) -> Option<Number> {
241 Some(Number::Float(*self))
242 }
243}
244
245#[cfg(feature = "rust_decimal")]
246impl Numeric for rust_decimal::Decimal {
247 fn number(&self) -> Option<Number> {
248 Some(Number::Decimal {
249 mantissa: self.mantissa(),
250 scale: self.scale(),
251 })
252 }
253}
254
255#[cfg(feature = "bigdecimal")]
258impl Numeric for bigdecimal::BigDecimal {
259 fn number(&self) -> Option<Number> {
260 use bigdecimal::{RoundingMode, ToPrimitive};
261
262 const DIGITS: u64 = 38;
263 let exact = || {
264 let (mantissa, scale) = match self.digits().checked_sub(DIGITS) {
265 Some(excess @ 1..) => {
266 let (_, scale) = self.as_bigint_and_scale();
267 let scale = scale.checked_sub(i64::try_from(excess).ok()?)?;
268 self.with_scale_round(scale, RoundingMode::HalfEven)
269 .into_bigint_and_scale()
270 }
271 _ => self.clone().into_bigint_and_scale(),
272 };
273 let mantissa = mantissa.to_i128()?;
274 match u32::try_from(scale) {
275 Ok(scale) => Some(Number::Decimal { mantissa, scale }),
276 Err(_) if scale < 0 => {
277 let shift = 10i128.checked_pow(u32::try_from(scale.unsigned_abs()).ok()?)?;
278 Some(Number::Decimal {
279 mantissa: mantissa.checked_mul(shift)?,
280 scale: 0,
281 })
282 }
283 Err(_) => None,
284 }
285 };
286 exact().or_else(|| {
287 self.to_f64()
288 .filter(|float| float.is_finite())
289 .map(Number::Float)
290 })
291 }
292}
293
294impl<T: Numeric> Numeric for Option<T> {
295 fn number(&self) -> Option<Number> {
296 self.as_ref().and_then(Numeric::number)
297 }
298}
299
300mod number;
301
302pub use number::Currency;
303pub(crate) use number::NumberFormat;
304#[cfg(test)]
305use number::format_number;
306
307#[cfg(test)]
308mod tests;