1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum SpacingStep {
9 Xs,
11 Sm,
13 Md,
15 Lg,
17 Xl,
19 Xxl,
21 Xxxl,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum RadiusStep {
28 None,
30 Sm,
32 Md,
34 Lg,
36 Xl,
38 Full,
40}
41
42#[derive(Clone, Debug, PartialEq)]
46pub struct DesignTokens {
47 pub spacing: [f32; 7],
49 pub radius: [f32; 6],
51 pub elevation: [f32; 6],
53 pub opacity: [f32; 5],
55}
56
57impl DesignTokens {
58 pub fn spacing(&self, step: SpacingStep) -> f32 {
60 let i = match step {
61 SpacingStep::Xs => 0,
62 SpacingStep::Sm => 1,
63 SpacingStep::Md => 2,
64 SpacingStep::Lg => 3,
65 SpacingStep::Xl => 4,
66 SpacingStep::Xxl => 5,
67 SpacingStep::Xxxl => 6,
68 };
69 self.spacing[i]
70 }
71
72 pub fn radius(&self, step: RadiusStep) -> f32 {
74 let i = match step {
75 RadiusStep::None => 0,
76 RadiusStep::Sm => 1,
77 RadiusStep::Md => 2,
78 RadiusStep::Lg => 3,
79 RadiusStep::Xl => 4,
80 RadiusStep::Full => 5,
81 };
82 self.radius[i]
83 }
84
85 pub fn elevation(&self, level: usize) -> f32 {
87 self.elevation[level.min(self.elevation.len() - 1)]
88 }
89}
90
91impl Default for DesignTokens {
92 fn default() -> Self {
94 Self {
95 spacing: [4.0, 8.0, 12.0, 16.0, 24.0, 32.0, 48.0],
97 radius: [0.0, 2.0, 4.0, 8.0, 16.0, 9999.0],
99 elevation: [0.0, 1.0, 3.0, 6.0, 12.0, 24.0],
101 opacity: [0.38, 0.60, 0.74, 0.87, 1.0],
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn spacing_is_multiple_of_four() {
113 let t = DesignTokens::default();
114 for v in t.spacing {
115 assert_eq!(v % 4.0, 0.0, "spacing {v} not a multiple of 4");
116 }
117 }
118
119 #[test]
120 fn spacing_is_monotonic() {
121 let t = DesignTokens::default();
122 for w in t.spacing.windows(2) {
123 assert!(w[1] > w[0], "spacing scale must increase");
124 }
125 }
126
127 #[test]
128 fn radius_non_negative_and_named_lookup() {
129 let t = DesignTokens::default();
130 for v in t.radius {
131 assert!(v >= 0.0);
132 }
133 assert_eq!(t.radius(RadiusStep::None), 0.0);
134 assert!(t.radius(RadiusStep::Full) > t.radius(RadiusStep::Lg));
135 }
136
137 #[test]
138 fn named_spacing_lookup() {
139 let t = DesignTokens::default();
140 assert_eq!(t.spacing(SpacingStep::Xs), 4.0);
141 assert_eq!(t.spacing(SpacingStep::Md), 12.0);
142 assert_eq!(t.spacing(SpacingStep::Xxxl), 48.0);
143 }
144
145 #[test]
146 fn elevation_clamps() {
147 let t = DesignTokens::default();
148 assert_eq!(t.elevation(0), 0.0);
149 assert_eq!(t.elevation(5), 24.0);
150 assert_eq!(t.elevation(99), 24.0);
152 }
153
154 #[test]
155 fn opacity_in_range_and_monotonic() {
156 let t = DesignTokens::default();
157 for v in t.opacity {
158 assert!((0.0..=1.0).contains(&v));
159 }
160 for w in t.opacity.windows(2) {
161 assert!(w[1] > w[0]);
162 }
163 }
164}