Coverage for src / lexigram / ui / styles / design_tokens.py: 100%
28 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1from __future__ import annotations
3"""ShadCN-compatible design tokens for Lexigram UI.
5All values use oklch color space for perceptually-uniform interpolation.
6Overridable via CSS custom properties.
7"""
9SHADCN_DEFAULT_COLORS: dict[str, str] = {
10 "--background": "oklch(1 0 0)",
11 "--foreground": "oklch(0.145 0 0)",
12 "--card": "oklch(0.992 0 0)",
13 "--card-foreground": "oklch(0.145 0 0)",
14 "--popover": "oklch(0.992 0 0)",
15 "--popover-foreground": "oklch(0.145 0 0)",
16 "--primary": "oklch(0.546 0.245 262.881)",
17 "--primary-foreground": "oklch(1 0 0)",
18 "--secondary": "oklch(0.94 0 0)",
19 "--secondary-foreground": "oklch(0.205 0 0)",
20 "--muted": "oklch(0.95 0 0)",
21 "--muted-foreground": "oklch(0.45 0 0)",
22 "--accent": "oklch(0.93 0 0)",
23 "--accent-foreground": "oklch(0.205 0 0)",
24 "--destructive": "oklch(0.577 0.245 27.325)",
25 "--destructive-foreground": "oklch(1 0 0)",
26 "--border": "oklch(0.85 0 0)",
27 "--input": "oklch(0.82 0 0)",
28 "--ring": "oklch(0.546 0.245 262.881)",
29 "--color-success": "oklch(0.627 0.194 149.214)",
30 "--color-success-foreground": "oklch(1 0 0)",
31 "--color-warning": "oklch(0.795 0.184 86.047)",
32 "--color-warning-foreground": "oklch(0.145 0 0)",
33 "--color-info": "oklch(0.585 0.233 277.117)",
34 "--color-info-foreground": "oklch(1 0 0)",
35 "--color-gray-50": "oklch(0.985 0 0)",
36 "--color-gray-100": "oklch(0.967 0 0)",
37 "--color-gray-200": "oklch(0.922 0 0)",
38 "--color-gray-300": "oklch(0.87 0 0)",
39 "--color-gray-400": "oklch(0.708 0 0)",
40 "--color-gray-500": "oklch(0.556 0 0)",
41 "--color-gray-600": "oklch(0.439 0 0)",
42 "--color-gray-700": "oklch(0.371 0 0)",
43 "--color-gray-800": "oklch(0.269 0 0)",
44 "--color-gray-900": "oklch(0.205 0 0)",
45 "--color-gray-950": "oklch(0.145 0 0)",
46 "--radius": "0.5rem",
47}
49SHADCN_DARK_COLORS: dict[str, str] = {
50 "--background": "oklch(0.145 0 0)",
51 "--foreground": "oklch(0.985 0 0)",
52 "--card": "oklch(0.18 0 0)",
53 "--card-foreground": "oklch(0.985 0 0)",
54 "--popover": "oklch(0.20 0 0)",
55 "--popover-foreground": "oklch(0.985 0 0)",
56 "--primary": "oklch(0.546 0.245 262.881)",
57 "--primary-foreground": "oklch(1 0 0)",
58 "--secondary": "oklch(0.22 0 0)",
59 "--secondary-foreground": "oklch(0.985 0 0)",
60 "--muted": "oklch(0.22 0 0)",
61 "--muted-foreground": "oklch(0.65 0 0)",
62 "--accent": "oklch(0.22 0 0)",
63 "--accent-foreground": "oklch(0.985 0 0)",
64 "--destructive": "oklch(0.577 0.245 27.325)",
65 "--destructive-foreground": "oklch(1 0 0)",
66 "--border": "oklch(0.32 0 0)",
67 "--input": "oklch(0.37 0 0)",
68 "--ring": "oklch(0.546 0.245 262.881)",
69 "--color-success": "oklch(0.627 0.194 149.214)",
70 "--color-success-foreground": "oklch(1 0 0)",
71 "--color-warning": "oklch(0.795 0.184 86.047)",
72 "--color-warning-foreground": "oklch(0.145 0 0)",
73 "--color-info": "oklch(0.585 0.233 277.117)",
74 "--color-info-foreground": "oklch(1 0 0)",
75 "--color-gray-50": "oklch(0.145 0 0)",
76 "--color-gray-100": "oklch(0.205 0 0)",
77 "--color-gray-200": "oklch(0.269 0 0)",
78 "--color-gray-300": "oklch(0.371 0 0)",
79 "--color-gray-400": "oklch(0.439 0 0)",
80 "--color-gray-500": "oklch(0.556 0 0)",
81 "--color-gray-600": "oklch(0.708 0 0)",
82 "--color-gray-700": "oklch(0.87 0 0)",
83 "--color-gray-800": "oklch(0.922 0 0)",
84 "--color-gray-900": "oklch(0.967 0 0)",
85 "--color-gray-950": "oklch(0.985 0 0)",
86}
88# ── Spacing tokens ─────────────────────────────────────────────
89SPACING_TOKENS: dict[str, str] = {
90 "--spacing-0": "0px",
91 "--spacing-1": "0.25rem",
92 "--spacing-2": "0.5rem",
93 "--spacing-3": "0.75rem",
94 "--spacing-4": "1rem",
95 "--spacing-5": "1.25rem",
96 "--spacing-6": "1.5rem",
97 "--spacing-8": "2rem",
98 "--spacing-10": "2.5rem",
99 "--spacing-12": "3rem",
100 "--spacing-16": "4rem",
101 "--spacing-20": "5rem",
102 "--spacing-24": "6rem",
103}
105# ── Typography tokens ──────────────────────────────────────────
106TYPOGRAPHY_TOKENS: dict[str, str] = {
107 "--font-sans": "Inter, ui-sans-serif, system-ui, sans-serif",
108 "--font-mono": "JetBrains Mono, ui-monospace, SFMono-Regular, monospace",
109 "--font-size-xs": "0.75rem",
110 "--font-size-sm": "0.875rem",
111 "--font-size-base": "1rem",
112 "--font-size-lg": "1.125rem",
113 "--font-size-xl": "1.25rem",
114 "--font-size-2xl": "1.5rem",
115 "--font-size-3xl": "1.875rem",
116 "--font-weight-normal": "400",
117 "--font-weight-medium": "500",
118 "--font-weight-semibold": "600",
119 "--font-weight-bold": "700",
120 "--line-height-tight": "1.25",
121 "--line-height-normal": "1.5",
122 "--line-height-relaxed": "1.625",
123 "--letter-spacing-tight": "-0.025em",
124 "--letter-spacing-normal": "0em",
125 "--letter-spacing-wide": "0.025em",
126}
128# ── Shadow tokens ──────────────────────────────────────────────
129SHADOW_TOKENS: dict[str, str] = {
130 "--shadow-sm": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
131 "--shadow": "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
132 "--shadow-md": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
133 "--shadow-lg": "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
134 "--shadow-xl": "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
135 "--shadow-2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)",
136 "--shadow-inner": "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)",
137}
139# ── Animation tokens ───────────────────────────────────────────
140ANIMATION_TOKENS: dict[str, str] = {
141 "--duration-fast": "150ms",
142 "--duration-normal": "200ms",
143 "--duration-slow": "300ms",
144 "--ease-in": "cubic-bezier(0.4, 0, 1, 1)",
145 "--ease-out": "cubic-bezier(0, 0, 0.2, 1)",
146 "--ease-in-out": "cubic-bezier(0.4, 0, 0.2, 1)",
147}
150def render_all_tokens(
151 colors: dict[str, str] | None = None,
152 dark_colors: dict[str, str] | None = None,
153) -> str:
154 """Render complete :root and .dark CSS blocks with all token types."""
155 c = colors if colors is not None else SHADCN_DEFAULT_COLORS
156 dc = dark_colors if dark_colors is not None else SHADCN_DARK_COLORS
157 lines = [":root {"]
158 for var, value in {**c, **SPACING_TOKENS, **TYPOGRAPHY_TOKENS, **SHADOW_TOKENS, **ANIMATION_TOKENS}.items():
159 lines.append(f" {var}: {value};")
160 lines.append("}")
161 lines.append("")
162 lines.append(".dark {")
163 for var, value in dc.items():
164 lines.append(f" {var}: {value};")
165 lines.append("}")
166 return "\n".join(lines)
169SEMANTIC_UTILITY_CLASSES: dict[str, str] = {
170 # ── Backgrounds ──
171 "bg-background": "background-color: var(--background);",
172 "bg-foreground": "background-color: var(--foreground);",
173 "bg-card": "background-color: var(--card);",
174 "bg-card-foreground": "background-color: var(--card-foreground);",
175 "bg-popover": "background-color: var(--popover);",
176 "bg-popover-foreground": "background-color: var(--popover-foreground);",
177 "bg-primary": "background-color: var(--primary);",
178 "bg-primary-foreground": "background-color: var(--primary-foreground);",
179 "bg-secondary": "background-color: var(--secondary);",
180 "bg-secondary-foreground": "background-color: var(--secondary-foreground);",
181 "bg-muted": "background-color: var(--muted);",
182 "bg-muted-foreground": "background-color: var(--muted-foreground);",
183 "bg-accent": "background-color: var(--accent);",
184 "bg-accent-foreground": "background-color: var(--accent-foreground);",
185 "bg-destructive": "background-color: var(--destructive);",
186 "bg-destructive-foreground": "background-color: var(--destructive-foreground);",
187 "bg-border": "background-color: var(--border);",
188 "bg-input": "background-color: var(--input);",
189 "bg-ring": "background-color: var(--ring);",
190 "bg-success": "background-color: var(--color-success);",
191 "bg-success-foreground": "background-color: var(--color-success-foreground);",
192 "bg-warning": "background-color: var(--color-warning);",
193 "bg-warning-foreground": "background-color: var(--color-warning-foreground);",
194 "bg-info": "background-color: var(--color-info);",
195 "bg-info-foreground": "background-color: var(--color-info-foreground);",
196 # ── Text colors ──
197 "text-background": "color: var(--background);",
198 "text-foreground": "color: var(--foreground);",
199 "text-card-foreground": "color: var(--card-foreground);",
200 "text-popover-foreground": "color: var(--popover-foreground);",
201 "text-primary": "color: var(--primary);",
202 "text-primary-foreground": "color: var(--primary-foreground);",
203 "text-secondary": "color: var(--secondary);",
204 "text-secondary-foreground": "color: var(--secondary-foreground);",
205 "text-muted": "color: var(--muted);",
206 "text-muted-foreground": "color: var(--muted-foreground);",
207 "text-accent": "color: var(--accent);",
208 "text-accent-foreground": "color: var(--accent-foreground);",
209 "text-destructive": "color: var(--destructive);",
210 "text-destructive-foreground": "color: var(--destructive-foreground);",
211 "text-success": "color: var(--color-success);",
212 "text-success-foreground": "color: var(--color-success-foreground);",
213 "text-warning": "color: var(--color-warning);",
214 "text-warning-foreground": "color: var(--color-warning-foreground);",
215 "text-info": "color: var(--color-info);",
216 "text-info-foreground": "color: var(--color-info-foreground);",
217 # ── Border colors ──
218 "border-background": "border-color: var(--background);",
219 "border-foreground": "border-color: var(--foreground);",
220 "border-card": "border-color: var(--card);",
221 "border-popover": "border-color: var(--popover);",
222 "border-primary": "border-color: var(--primary);",
223 "border-secondary": "border-color: var(--secondary);",
224 "border-muted": "border-color: var(--muted);",
225 "border-accent": "border-color: var(--accent);",
226 "border-destructive": "border-color: var(--destructive);",
227 "border-border": "border-color: var(--border);",
228 "border-input": "border-color: var(--input);",
229 "border-ring": "border-color: var(--ring);",
230 "border-success": "border-color: var(--color-success);",
231 "border-success-foreground": "border-color: var(--color-success-foreground);",
232 "border-warning": "border-color: var(--color-warning);",
233 "border-warning-foreground": "border-color: var(--color-warning-foreground);",
234 "border-info": "border-color: var(--color-info);",
235 "border-info-foreground": "border-color: var(--color-info-foreground);",
236 # ── Ring / Focus ──
237 "ring-ring": "box-shadow: 0 0 0 2px var(--ring);",
238 "ring-destructive": "box-shadow: 0 0 0 2px var(--destructive);",
239 "ring-offset-background": "--tw-ring-offset-color: var(--background);",
240 # ── Radius ──
241 "rounded-sm": "border-radius: calc(var(--radius) - 0.25rem);",
242 "rounded-md": "border-radius: calc(var(--radius) - 0.125rem);",
243 "rounded-lg": "border-radius: var(--radius);",
244 "rounded-xl": "border-radius: calc(var(--radius) + 0.25rem);",
245 # ── Shadows ──
246 "shadow-sm": "box-shadow: var(--shadow-sm);",
247 "shadow": "box-shadow: var(--shadow);",
248 "shadow-md": "box-shadow: var(--shadow-md);",
249 "shadow-lg": "box-shadow: var(--shadow-lg);",
250 "shadow-xl": "box-shadow: var(--shadow-xl);",
251 "shadow-2xl": "box-shadow: var(--shadow-2xl);",
252 "shadow-inner": "box-shadow: var(--shadow-inner);",
253 # ── Transitions ──
254 "transition-all": "transition-property: all; transition-timing-function: var(--ease-in-out); transition-duration: var(--duration-normal);",
255 "transition-colors": "transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: var(--ease-in-out); transition-duration: var(--duration-normal);",
256 "transition-opacity": "transition-property: opacity; transition-timing-function: var(--ease-in-out); transition-duration: var(--duration-normal);",
257 "transition-shadow": "transition-property: box-shadow; transition-timing-function: var(--ease-in-out); transition-duration: var(--duration-normal);",
258 "transition-transform": "transition-property: transform; transition-timing-function: var(--ease-in-out); transition-duration: var(--duration-normal);",
259 # ── Ring focus (ShadCN standard focus pattern) ──
260 "focus-ring": "outline: none; box-shadow: 0 0 0 2px var(--ring); outline: 2px solid transparent; outline-offset: 2px;",
261 "focus-ring-destructive": "outline: none; box-shadow: 0 0 0 2px var(--destructive); outline: 2px solid transparent; outline-offset: 2px;",
262}
265def render_utility_classes() -> str:
266 """Render CSS utility classes that map to design tokens (dark-aware)."""
267 lines: list[str] = []
268 for cls, rule in sorted(SEMANTIC_UTILITY_CLASSES.items()):
269 lines.append(f".{cls} {{ {rule} }}")
270 lines.append(f".dark .{cls} {{ {rule} }}")
271 return "\n".join(lines)