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

1from __future__ import annotations 

2 

3"""ShadCN-compatible design tokens for Lexigram UI. 

4 

5All values use oklch color space for perceptually-uniform interpolation. 

6Overridable via CSS custom properties. 

7""" 

8 

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} 

48 

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} 

87 

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} 

104 

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} 

127 

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} 

138 

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} 

148 

149 

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) 

167 

168 

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} 

263 

264 

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)