Archived
138 lines
3.5 KiB
Rust
138 lines
3.5 KiB
Rust
/// Spacing scale — 4px base grid.
|
|
///
|
|
/// Use named scale values, not raw numbers. This ensures visual consistency
|
|
/// and makes it easy to tweak the entire system by changing the base unit.
|
|
|
|
/// Named spacing scale values.
|
|
///
|
|
/// The base unit is 4dp/pt. All values are multiples of 4.
|
|
/// Use these for padding, margin, gap, and any other spatial measurement.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum Spacing {
|
|
/// 0 — no spacing
|
|
None,
|
|
/// 4dp — hairline gap, tight list items
|
|
Xs,
|
|
/// 8dp — default tight padding, icon margins
|
|
Sm,
|
|
/// 12dp — compact component padding
|
|
Md,
|
|
/// 16dp — standard component padding (the workhorse)
|
|
Lg,
|
|
/// 24dp — generous padding, card internal spacing
|
|
Xl,
|
|
/// 32dp — section separation
|
|
Xxl,
|
|
/// 48dp — hero sections, major layout gaps
|
|
Xxxl,
|
|
/// 64dp — page-level margins, maximum separation
|
|
Max,
|
|
/// Custom value in dp (escape hatch)
|
|
Custom(u32),
|
|
}
|
|
|
|
impl Spacing {
|
|
/// Resolve to a concrete dp/pt value.
|
|
pub fn dp(&self) -> u32 {
|
|
match self {
|
|
Spacing::None => 0,
|
|
Spacing::Xs => 4,
|
|
Spacing::Sm => 8,
|
|
Spacing::Md => 12,
|
|
Spacing::Lg => 16,
|
|
Spacing::Xl => 24,
|
|
Spacing::Xxl => 32,
|
|
Spacing::Xxxl => 48,
|
|
Spacing::Max => 64,
|
|
Spacing::Custom(v) => *v,
|
|
}
|
|
}
|
|
|
|
/// Resolve to a concrete CSS pixel string.
|
|
pub fn to_css(&self) -> String {
|
|
format!("{}px", self.dp())
|
|
}
|
|
}
|
|
|
|
/// The spacing scale exposed by a theme.
|
|
/// Provides the mapping from scale names to concrete values.
|
|
#[derive(Debug, Clone)]
|
|
pub struct SpacingScale {
|
|
/// Base unit in dp (default: 4).
|
|
pub base: u32,
|
|
}
|
|
|
|
impl SpacingScale {
|
|
pub fn default() -> Self {
|
|
Self { base: 4 }
|
|
}
|
|
|
|
/// Resolve a Spacing token to dp.
|
|
pub fn resolve(&self, spacing: &Spacing) -> u32 {
|
|
match spacing {
|
|
Spacing::Custom(v) => *v,
|
|
other => {
|
|
let multiplier = match other {
|
|
Spacing::None => 0,
|
|
Spacing::Xs => 1,
|
|
Spacing::Sm => 2,
|
|
Spacing::Md => 3,
|
|
Spacing::Lg => 4,
|
|
Spacing::Xl => 6,
|
|
Spacing::Xxl => 8,
|
|
Spacing::Xxxl => 12,
|
|
Spacing::Max => 16,
|
|
Spacing::Custom(_) => unreachable!(),
|
|
};
|
|
self.base * multiplier
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn spacing_none_is_zero() {
|
|
assert_eq!(Spacing::None.dp(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn spacing_lg_is_16() {
|
|
assert_eq!(Spacing::Lg.dp(), 16);
|
|
}
|
|
|
|
#[test]
|
|
fn spacing_max_is_64() {
|
|
assert_eq!(Spacing::Max.dp(), 64);
|
|
}
|
|
|
|
#[test]
|
|
fn spacing_custom() {
|
|
assert_eq!(Spacing::Custom(20).dp(), 20);
|
|
}
|
|
|
|
#[test]
|
|
fn spacing_scale_resolves_base_unit() {
|
|
let scale = SpacingScale { base: 4 };
|
|
assert_eq!(scale.resolve(&Spacing::Xs), 4);
|
|
assert_eq!(scale.resolve(&Spacing::Sm), 8);
|
|
assert_eq!(scale.resolve(&Spacing::Lg), 16);
|
|
}
|
|
|
|
#[test]
|
|
fn spacing_to_css() {
|
|
assert_eq!(Spacing::Lg.to_css(), "16px");
|
|
assert_eq!(Spacing::None.to_css(), "0px");
|
|
}
|
|
|
|
#[test]
|
|
fn spacing_scale_custom_scale() {
|
|
let scale = SpacingScale { base: 8 };
|
|
assert_eq!(scale.resolve(&Spacing::Xs), 8);
|
|
assert_eq!(scale.resolve(&Spacing::Sm), 16);
|
|
}
|
|
}
|