Skip to main content

use_design_token/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4/// A design-token name segment.
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
6pub struct TokenName(String);
7
8impl TokenName {
9    pub fn new(value: impl Into<String>) -> Self {
10        Self(value.into())
11    }
12
13    pub fn as_str(&self) -> &str {
14        &self.0
15    }
16}
17
18impl AsRef<str> for TokenName {
19    fn as_ref(&self) -> &str {
20        self.as_str()
21    }
22}
23
24/// A hierarchical token path such as `color.background.primary`.
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26pub struct TokenPath(Vec<TokenName>);
27
28impl TokenPath {
29    pub fn new(segments: Vec<TokenName>) -> Self {
30        Self(segments)
31    }
32
33    pub fn from_segments<I, S>(segments: I) -> Self
34    where
35        I: IntoIterator<Item = S>,
36        S: Into<String>,
37    {
38        Self(segments.into_iter().map(TokenName::new).collect())
39    }
40
41    pub fn segments(&self) -> &[TokenName] {
42        &self.0
43    }
44
45    pub fn is_empty(&self) -> bool {
46        self.0.is_empty()
47    }
48
49    pub fn len(&self) -> usize {
50        self.0.len()
51    }
52}
53
54/// Broad design-token categories.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
56pub enum TokenCategory {
57    Color,
58    Typography,
59    Spacing,
60    Radius,
61    Shadow,
62    Motion,
63    Breakpoint,
64    Layer,
65    Component,
66    Semantic,
67}
68
69impl TokenCategory {
70    pub fn as_str(self) -> &'static str {
71        match self {
72            Self::Color => "color",
73            Self::Typography => "typography",
74            Self::Spacing => "spacing",
75            Self::Radius => "radius",
76            Self::Shadow => "shadow",
77            Self::Motion => "motion",
78            Self::Breakpoint => "breakpoint",
79            Self::Layer => "layer",
80            Self::Component => "component",
81            Self::Semantic => "semantic",
82        }
83    }
84}
85
86/// A reference to another design token.
87#[derive(Debug, Clone, PartialEq, Eq, Hash)]
88pub struct TokenReference {
89    path: TokenPath,
90}
91
92impl TokenReference {
93    pub fn new(path: TokenPath) -> Self {
94        Self { path }
95    }
96
97    pub fn path(&self) -> &TokenPath {
98        &self.path
99    }
100}
101
102/// A simple design-token value.
103#[derive(Debug, Clone, PartialEq, Eq, Hash)]
104pub enum TokenValue {
105    Text(String),
106    Integer(i64),
107    Boolean(bool),
108    Reference(TokenReference),
109}
110
111impl TokenValue {
112    pub fn text(value: impl Into<String>) -> Self {
113        Self::Text(value.into())
114    }
115}
116
117/// Primitive design-token metadata.
118#[derive(Debug, Clone, PartialEq, Eq, Hash)]
119pub struct DesignToken {
120    name: TokenName,
121    category: TokenCategory,
122    value: TokenValue,
123    path: Option<TokenPath>,
124}
125
126impl DesignToken {
127    pub fn new(name: TokenName, category: TokenCategory, value: TokenValue) -> Self {
128        Self {
129            name,
130            category,
131            value,
132            path: None,
133        }
134    }
135
136    pub fn with_path(mut self, path: TokenPath) -> Self {
137        self.path = Some(path);
138        self
139    }
140
141    pub fn name(&self) -> &TokenName {
142        &self.name
143    }
144
145    pub fn category(&self) -> TokenCategory {
146        self.category
147    }
148
149    pub fn value(&self) -> &TokenValue {
150        &self.value
151    }
152
153    pub fn path(&self) -> Option<&TokenPath> {
154        self.path.as_ref()
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::{DesignToken, TokenCategory, TokenName, TokenPath, TokenReference, TokenValue};
161
162    #[test]
163    fn creates_token_categories_and_values() {
164        let token = DesignToken::new(
165            TokenName::new("primary"),
166            TokenCategory::Color,
167            TokenValue::text("#3366cc"),
168        );
169
170        assert_eq!(token.name().as_str(), "primary");
171        assert_eq!(token.category().as_str(), "color");
172        assert_eq!(token.value(), &TokenValue::Text(String::from("#3366cc")));
173    }
174
175    #[test]
176    fn creates_paths_and_references() {
177        let path = TokenPath::from_segments(["color", "background", "primary"]);
178        let reference = TokenReference::new(path.clone());
179        let token = DesignToken::new(
180            TokenName::new("surface"),
181            TokenCategory::Semantic,
182            TokenValue::Reference(reference),
183        )
184        .with_path(path);
185
186        assert_eq!(token.path().map(TokenPath::len), Some(3));
187        assert!(!token.path().is_some_and(TokenPath::is_empty));
188    }
189}