use orbital_style::inject_style;
use crate::tokens::{MotionCurve, MotionDuration};
pub const PROGRESS_FILL_CLASS: &str = "orbital-motion-progress-fill";
pub const PROGRESS_FILL_RESPECTS_REDUCED_CLASS: &str =
"orbital-motion-progress-fill-respects-reduced";
pub const DEFAULT_PROGRESS_FILL: ProgressFillMotion = ProgressFillMotion {
duration: MotionDuration::Slower,
curve: MotionCurve::EasyEase,
};
pub const GLACIAL_PROGRESS_FILL: ProgressFillMotion = ProgressFillMotion {
duration: MotionDuration::Glacial,
curve: MotionCurve::DecelerateMid,
};
pub const EPIC_PROGRESS_FILL: ProgressFillMotion = ProgressFillMotion {
duration: MotionDuration::Epic,
curve: MotionCurve::DecelerateMid,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ProgressFillMotion {
pub duration: MotionDuration,
pub curve: MotionCurve,
}
impl ProgressFillMotion {
#[must_use]
pub const fn new(duration: MotionDuration, curve: MotionCurve) -> Self {
Self { duration, curve }
}
#[must_use]
pub fn width_transition(self) -> String {
format!(
"width var({}, {}) var({}, {})",
self.duration.css_var_name(),
self.duration.ms(),
self.curve.css_var_name(),
self.curve.cubic_bezier(),
)
}
#[must_use]
pub fn transform_transition(self) -> String {
format!(
"transform var({}, {}) var({}, {})",
self.duration.css_var_name(),
self.duration.ms(),
self.curve.css_var_name(),
self.curve.cubic_bezier(),
)
}
pub fn ensure_styles() {
inject_style("orbital-motion-progress-fill", progress_fill_styles());
}
}
pub fn progress_fill_styles() -> &'static str {
r#"
.orbital-motion-progress-fill {
transition-property: width, transform;
transition-duration: var(--orb-motion-duration-2xl, 400ms);
transition-timing-function: var(--orb-motion-ease-standard, cubic-bezier(0.33, 0, 0.67, 1));
}
.orbital-motion-progress-fill-respects-reduced {
transition-property: width, transform;
transition-duration: var(--orb-motion-duration-2xl, 400ms);
transition-timing-function: var(--orb-motion-ease-standard, cubic-bezier(0.33, 0, 0.67, 1));
}
@media (prefers-reduced-motion: reduce) {
.orbital-motion-progress-fill-respects-reduced {
transition-duration: 0.001ms !important;
transition-delay: 0ms !important;
}
}
"#
}
#[must_use]
pub fn clamp_unit_progress(value: f64) -> f64 {
value.clamp(0.0, 1.0)
}
#[must_use]
pub fn progress_width_percent(value: f64) -> String {
format!("{}%", clamp_unit_progress(value) * 100.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clamp_unit_progress_bounds() {
assert!((clamp_unit_progress(-1.0) - 0.0).abs() < f64::EPSILON);
assert!((clamp_unit_progress(0.5) - 0.5).abs() < f64::EPSILON);
assert!((clamp_unit_progress(2.0) - 1.0).abs() < f64::EPSILON);
}
#[test]
fn progress_width_percent_formats() {
assert_eq!(progress_width_percent(0.0), "0%");
assert_eq!(progress_width_percent(0.45), "45%");
assert_eq!(progress_width_percent(1.0), "100%");
}
#[test]
fn glacial_uses_theme_var() {
assert_eq!(
MotionDuration::Glacial.css_var_name(),
"--orb-motion-duration-4xl"
);
let t = GLACIAL_PROGRESS_FILL.width_transition();
assert!(t.contains("--orb-motion-duration-4xl"));
assert!(t.contains("4000ms"));
}
#[test]
fn epic_uses_theme_var_with_fallback() {
let t = EPIC_PROGRESS_FILL.width_transition();
assert!(t.contains("--orb-motion-duration-5xl"));
assert!(t.contains("10000ms"));
}
}