@titan-design/react-ui 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/dist/bodymap-BhG55xHM.d.mts +318 -0
  2. package/dist/bodymap-BhG55xHM.d.ts +318 -0
  3. package/dist/bodymap.d.mts +3 -0
  4. package/dist/bodymap.d.ts +3 -0
  5. package/dist/bodymap.js +2670 -0
  6. package/dist/bodymap.js.map +1 -0
  7. package/dist/bodymap.mjs +1101 -0
  8. package/dist/bodymap.mjs.map +1 -0
  9. package/dist/{chunk-UXVRPOME.mjs → chunk-7XPB4NAO.mjs} +125 -785
  10. package/dist/chunk-7XPB4NAO.mjs.map +1 -0
  11. package/dist/chunk-P7TSQUN6.mjs +846 -0
  12. package/dist/chunk-P7TSQUN6.mjs.map +1 -0
  13. package/dist/chunk-TOD2WQBG.mjs +1048 -0
  14. package/dist/chunk-TOD2WQBG.mjs.map +1 -0
  15. package/dist/index.d.mts +1131 -4
  16. package/dist/index.d.ts +1131 -4
  17. package/dist/index.js +6396 -225
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +5572 -641
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/theme/index.d.mts +317 -1
  22. package/dist/theme/index.d.ts +317 -1
  23. package/dist/theme/index.js +185 -2
  24. package/dist/theme/index.js.map +1 -1
  25. package/dist/theme/index.mjs +2 -1
  26. package/dist/theme/tokens-css.d.mts +20 -0
  27. package/dist/theme/tokens-css.d.ts +20 -0
  28. package/dist/theme/tokens-css.js +598 -0
  29. package/dist/theme/tokens-css.js.map +1 -0
  30. package/dist/theme/tokens-css.mjs +24 -0
  31. package/dist/theme/tokens-css.mjs.map +1 -0
  32. package/package.json +25 -5
  33. package/src/bodymap.ts +37 -0
  34. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  35. package/src/components/custom/Gauge/Gauge.stories.tsx +45 -0
  36. package/src/components/custom/Gauge/Gauge.test.tsx +71 -0
  37. package/src/components/custom/Gauge/Gauge.tsx +167 -0
  38. package/src/components/custom/Gauge/index.ts +2 -0
  39. package/src/components/custom/Scatter/Scatter.stories.tsx +85 -0
  40. package/src/components/custom/Scatter/Scatter.test.tsx +96 -0
  41. package/src/components/custom/Scatter/Scatter.tsx +260 -0
  42. package/src/components/custom/Scatter/index.ts +2 -0
  43. package/src/components/custom/Sidebar/Sidebar.tsx +1 -1
  44. package/src/components/custom/Table/Table.test.tsx +27 -0
  45. package/src/components/custom/Table/Table.tsx +33 -17
  46. package/src/components/custom/Treemap/Treemap.stories.tsx +60 -0
  47. package/src/components/custom/Treemap/Treemap.test.tsx +67 -0
  48. package/src/components/custom/Treemap/Treemap.tsx +192 -0
  49. package/src/components/custom/Treemap/index.ts +2 -0
  50. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +118 -0
  51. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +171 -0
  52. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +447 -0
  53. package/src/components/custom/Workout/BaseBadge.stories.tsx +75 -0
  54. package/src/components/custom/Workout/BaseBadge.test.tsx +102 -0
  55. package/src/components/custom/Workout/BaseBadge.tsx +113 -0
  56. package/src/components/custom/Workout/BodyMap.stories.tsx +121 -0
  57. package/src/components/custom/Workout/BodyMap.test.tsx +124 -0
  58. package/src/components/custom/Workout/BodyMap.tsx +347 -0
  59. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +171 -0
  60. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +225 -0
  61. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +477 -0
  62. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +125 -0
  63. package/src/components/custom/Workout/CapacityBandChart.test.tsx +193 -0
  64. package/src/components/custom/Workout/CapacityBandChart.tsx +493 -0
  65. package/src/components/custom/Workout/DeviationBar.stories.tsx +58 -0
  66. package/src/components/custom/Workout/DeviationBar.test.tsx +86 -0
  67. package/src/components/custom/Workout/DeviationBar.tsx +78 -0
  68. package/src/components/custom/Workout/ExerciseCard.stories.tsx +186 -0
  69. package/src/components/custom/Workout/ExerciseCard.test.tsx +343 -0
  70. package/src/components/custom/Workout/ExerciseCard.tsx +442 -0
  71. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +225 -0
  72. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +128 -0
  73. package/src/components/custom/Workout/ExerciseDetailPage.tsx +630 -0
  74. package/src/components/custom/Workout/InputBar.stories.tsx +75 -0
  75. package/src/components/custom/Workout/InputBar.test.tsx +116 -0
  76. package/src/components/custom/Workout/InputBar.tsx +162 -0
  77. package/src/components/custom/Workout/IntensityBar.stories.tsx +60 -0
  78. package/src/components/custom/Workout/IntensityBar.test.tsx +113 -0
  79. package/src/components/custom/Workout/IntensityBar.tsx +166 -0
  80. package/src/components/custom/Workout/MesoCard.stories.tsx +152 -0
  81. package/src/components/custom/Workout/MesoCard.test.tsx +170 -0
  82. package/src/components/custom/Workout/MesoCard.tsx +237 -0
  83. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +85 -0
  84. package/src/components/custom/Workout/MesoProgressBar.test.tsx +153 -0
  85. package/src/components/custom/Workout/MesoProgressBar.tsx +165 -0
  86. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +126 -0
  87. package/src/components/custom/Workout/MesoStatusCard.test.tsx +209 -0
  88. package/src/components/custom/Workout/MesoStatusCard.tsx +456 -0
  89. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +118 -0
  90. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +91 -0
  91. package/src/components/custom/Workout/MuscleGroupChip.tsx +92 -0
  92. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +94 -0
  93. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +83 -0
  94. package/src/components/custom/Workout/PlaceholderStrip.tsx +90 -0
  95. package/src/components/custom/Workout/PrBadge.stories.tsx +105 -0
  96. package/src/components/custom/Workout/PrBadge.test.tsx +111 -0
  97. package/src/components/custom/Workout/PrBadge.tsx +103 -0
  98. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +116 -0
  99. package/src/components/custom/Workout/PrHistoryModal.test.tsx +161 -0
  100. package/src/components/custom/Workout/PrHistoryModal.tsx +244 -0
  101. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +257 -0
  102. package/src/components/custom/Workout/ProgramPlanningPage.test.tsx +142 -0
  103. package/src/components/custom/Workout/ProgramPlanningPage.tsx +422 -0
  104. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +126 -0
  105. package/src/components/custom/Workout/ReadinessCheck.test.tsx +181 -0
  106. package/src/components/custom/Workout/ReadinessCheck.tsx +265 -0
  107. package/src/components/custom/Workout/RestTimer.test.tsx +10 -0
  108. package/src/components/custom/Workout/RestTimer.tsx +15 -11
  109. package/src/components/custom/Workout/SetRow.stories.tsx +165 -0
  110. package/src/components/custom/Workout/SetRow.test.tsx +222 -0
  111. package/src/components/custom/Workout/SetRow.tsx +247 -0
  112. package/src/components/custom/Workout/Sparkline.stories.tsx +125 -0
  113. package/src/components/custom/Workout/Sparkline.test.tsx +113 -0
  114. package/src/components/custom/Workout/Sparkline.tsx +188 -0
  115. package/src/components/custom/Workout/StatusDot.stories.tsx +150 -0
  116. package/src/components/custom/Workout/StatusDot.test.tsx +165 -0
  117. package/src/components/custom/Workout/StatusDot.tsx +160 -0
  118. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +130 -0
  119. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +217 -0
  120. package/src/components/custom/Workout/StrengthTrendChart.tsx +628 -0
  121. package/src/components/custom/Workout/SupersetWrapper.tsx +1 -2
  122. package/src/components/custom/Workout/TempoDisplay.stories.tsx +66 -0
  123. package/src/components/custom/Workout/TempoDisplay.test.tsx +90 -0
  124. package/src/components/custom/Workout/TempoDisplay.tsx +209 -0
  125. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +212 -0
  126. package/src/components/custom/Workout/TrainingStatusPage.test.tsx +131 -0
  127. package/src/components/custom/Workout/TrainingStatusPage.tsx +321 -0
  128. package/src/components/custom/Workout/VelocityStrip.stories.tsx +125 -0
  129. package/src/components/custom/Workout/VelocityStrip.test.tsx +244 -0
  130. package/src/components/custom/Workout/VelocityStrip.tsx +288 -0
  131. package/src/components/custom/Workout/WeekRow.stories.tsx +121 -0
  132. package/src/components/custom/Workout/WeekRow.test.tsx +117 -0
  133. package/src/components/custom/Workout/WeekRow.tsx +140 -0
  134. package/src/components/custom/Workout/WeightBadge.stories.tsx +87 -0
  135. package/src/components/custom/Workout/WeightBadge.test.tsx +164 -0
  136. package/src/components/custom/Workout/WeightBadge.tsx +113 -0
  137. package/src/components/custom/Workout/WorkoutCard.stories.tsx +135 -0
  138. package/src/components/custom/Workout/WorkoutCard.test.tsx +172 -0
  139. package/src/components/custom/Workout/WorkoutCard.tsx +228 -0
  140. package/src/components/custom/Workout/WorkoutPill.stories.tsx +63 -0
  141. package/src/components/custom/Workout/WorkoutPill.test.tsx +153 -0
  142. package/src/components/custom/Workout/WorkoutPill.tsx +177 -0
  143. package/src/components/custom/Workout/index.ts +152 -0
  144. package/src/components/custom/Workout/muscleTaxonomy.ts +252 -0
  145. package/src/components/custom/index.ts +3 -0
  146. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  147. package/src/components/custom/stepper/Stepper.tsx +2 -2
  148. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  149. package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
  150. package/src/components/ui/card/Card.tsx +1 -1
  151. package/src/components/ui/checkbox/Checkbox.tsx +1 -1
  152. package/src/components/ui/collapse/Collapse.tsx +1 -1
  153. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  154. package/src/components/ui/drawer/Drawer.tsx +3 -3
  155. package/src/components/ui/form-field/FormField.tsx +1 -1
  156. package/src/components/ui/help-tip/HelpTip.tsx +1 -1
  157. package/src/components/ui/menu/Menu.tsx +2 -2
  158. package/src/components/ui/pill/Pill.tsx +1 -1
  159. package/src/components/ui/popover/Popover.stories.tsx +2 -2
  160. package/src/components/ui/popover/Popover.tsx +1 -1
  161. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  162. package/src/components/ui/section/Section.stories.tsx +4 -4
  163. package/src/components/ui/select/Select.tsx +1 -1
  164. package/src/components/ui/stack/Stack.stories.tsx +4 -4
  165. package/src/components/ui/tabs/Tabs.tsx +2 -2
  166. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  167. package/src/components/ui/toolbar-button/ToolbarButton.tsx +1 -1
  168. package/src/examples/react-hook-form/ControlledFields.tsx +171 -0
  169. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +51 -0
  170. package/src/examples/react-hook-form/ReactHookForm.test.tsx +61 -0
  171. package/src/examples/react-hook-form/RhfContactForm.tsx +99 -0
  172. package/src/stories/PresetShowcase.stories.tsx +15 -15
  173. package/src/theme/config.completeness.test.ts +77 -0
  174. package/src/theme/config.ts +151 -0
  175. package/src/theme/index.ts +2 -1
  176. package/src/theme/manifest/css-property-manifest.example.json +79 -0
  177. package/src/theme/manifest/css-property-manifest.schema.json +93 -0
  178. package/src/theme/manifest/css-property-manifest.test.ts +81 -0
  179. package/src/theme/manifest/css-property-manifest.types.ts +49 -0
  180. package/src/theme/manifest/css-property-manifest.validate.ts +84 -0
  181. package/src/theme/manifest/design-freeze.test.ts +155 -0
  182. package/src/theme/manifest/extract-css-properties.fixture.html +29 -0
  183. package/src/theme/manifest/extract-css-properties.test.ts +112 -0
  184. package/src/theme/manifest/index.ts +11 -0
  185. package/src/theme/tokens-css.test.ts +28 -0
  186. package/src/theme/tokens-css.ts +36 -0
  187. package/src/theme/workout-tokens.ts +59 -0
  188. package/src/utils/index.ts +10 -0
  189. package/src/utils/workout-format.test.ts +81 -0
  190. package/src/utils/workout-format.ts +83 -0
  191. package/dist/chunk-UXVRPOME.mjs.map +0 -1
@@ -7,6 +7,35 @@
7
7
 
8
8
  import { semanticColorsLight, semanticColorsDark, type ThemeMode } from './tokens/semantic'
9
9
 
10
+ // Theme-independent custom properties: identical in light and dark in
11
+ // global.css (RGB glow decompositions, font families, animation tokens). Kept
12
+ // as literals since they are not part of the semantic COLOR maps, and spread
13
+ // into both theme maps so codegen mirrors global.css exactly.
14
+ const themeIndependentCSSVars = {
15
+ // RGB decomposed values for glow shadows
16
+ '--color-brand-primary-rgb': '255, 121, 0',
17
+ '--color-brand-secondary-rgb': '64, 109, 135',
18
+ '--color-status-success-rgb': '20, 184, 166',
19
+ '--color-status-error-rgb': '209, 67, 67',
20
+ '--color-status-warning-rgb': '255, 176, 32',
21
+ '--color-status-info-rgb': '33, 150, 243',
22
+ '--color-background-base-rgb': '16, 16, 16',
23
+
24
+ // Font families
25
+ '--font-family-sans': "'Inter'",
26
+ '--font-family-body': "'Nunito Sans'",
27
+ '--font-family-heading': "'Space Grotesk'",
28
+ '--font-family-mono': "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace",
29
+
30
+ // Animation tokens
31
+ '--ease-out': 'cubic-bezier(0.22, 1, 0.36, 1)',
32
+ '--ease-in-out': 'cubic-bezier(0.65, 0, 0.35, 1)',
33
+ '--ease-spring': 'cubic-bezier(0.34, 1.56, 0.64, 1)',
34
+ '--duration-fast': '150ms',
35
+ '--duration-normal': '250ms',
36
+ '--duration-slow': '400ms',
37
+ } as const
38
+
10
39
  // CSS custom properties for light mode
11
40
  export const lightThemeCSSVars = {
12
41
  '--color-brand-primary': semanticColorsLight['brand-primary'],
@@ -57,6 +86,67 @@ export const lightThemeCSSVars = {
57
86
  '--color-interactive-disabled': semanticColorsLight['interactive-disabled'],
58
87
 
59
88
  '--color-divider': semanticColorsLight['divider'],
89
+
90
+ '--color-brand-primary-hover': semanticColorsLight['brand-primary-hover'],
91
+ '--color-brand-primary-active': semanticColorsLight['brand-primary-active'],
92
+ '--color-brand-secondary-hover': semanticColorsLight['brand-secondary-hover'],
93
+ '--color-brand-secondary-active': semanticColorsLight['brand-secondary-active'],
94
+
95
+ '--color-status-success-light': semanticColorsLight['status-success-light'],
96
+ '--color-status-success-dark': semanticColorsLight['status-success-dark'],
97
+ '--color-status-error-light': semanticColorsLight['status-error-light'],
98
+ '--color-status-error-dark': semanticColorsLight['status-error-dark'],
99
+ '--color-status-warning-light': semanticColorsLight['status-warning-light'],
100
+ '--color-status-warning-dark': semanticColorsLight['status-warning-dark'],
101
+ '--color-status-info-light': semanticColorsLight['status-info-light'],
102
+ '--color-status-info-dark': semanticColorsLight['status-info-dark'],
103
+
104
+ '--color-on-status-success': semanticColorsLight['on-status-success'],
105
+ '--color-on-status-error': semanticColorsLight['on-status-error'],
106
+ '--color-on-status-warning': semanticColorsLight['on-status-warning'],
107
+ '--color-on-status-info': semanticColorsLight['on-status-info'],
108
+
109
+ '--color-result-improve': semanticColorsLight['result-improve'],
110
+ '--color-result-improve-light': semanticColorsLight['result-improve-light'],
111
+ '--color-result-improve-dark': semanticColorsLight['result-improve-dark'],
112
+ '--color-result-degrade': semanticColorsLight['result-degrade'],
113
+ '--color-result-degrade-light': semanticColorsLight['result-degrade-light'],
114
+ '--color-result-degrade-dark': semanticColorsLight['result-degrade-dark'],
115
+ '--color-result-inconclusive': semanticColorsLight['result-inconclusive'],
116
+ '--color-result-inconclusive-light': semanticColorsLight['result-inconclusive-light'],
117
+ '--color-result-neutral': semanticColorsLight['result-neutral'],
118
+
119
+ '--color-on-result-improve': semanticColorsLight['on-result-improve'],
120
+ '--color-on-result-degrade': semanticColorsLight['on-result-degrade'],
121
+ '--color-on-result-inconclusive': semanticColorsLight['on-result-inconclusive'],
122
+
123
+ '--color-data-1': semanticColorsLight['data-1'],
124
+ '--color-data-2': semanticColorsLight['data-2'],
125
+ '--color-data-3': semanticColorsLight['data-3'],
126
+ '--color-data-4': semanticColorsLight['data-4'],
127
+ '--color-data-5': semanticColorsLight['data-5'],
128
+ '--color-data-6': semanticColorsLight['data-6'],
129
+ '--color-data-7': semanticColorsLight['data-7'],
130
+ '--color-data-8': semanticColorsLight['data-8'],
131
+ '--color-data-9': semanticColorsLight['data-9'],
132
+ '--color-data-10': semanticColorsLight['data-10'],
133
+
134
+ '--color-text-link-hover': semanticColorsLight['text-link-hover'],
135
+
136
+ '--color-surface-overlay': semanticColorsLight['surface-overlay'],
137
+ '--color-surface-input': semanticColorsLight['surface-input'],
138
+
139
+ '--color-border-input': semanticColorsLight['border-input'],
140
+ '--color-border-input-hover': semanticColorsLight['border-input-hover'],
141
+ '--color-border-input-focus': semanticColorsLight['border-input-focus'],
142
+ '--color-border-input-error': semanticColorsLight['border-input-error'],
143
+
144
+ '--color-interactive-disabled-text': semanticColorsLight['interactive-disabled-text'],
145
+
146
+ '--color-avatar-background': semanticColorsLight['avatar-background'],
147
+ '--color-avatar-text': semanticColorsLight['avatar-text'],
148
+
149
+ ...themeIndependentCSSVars,
60
150
  } as const
61
151
 
62
152
  // CSS custom properties for dark mode (default)
@@ -109,6 +199,67 @@ export const darkThemeCSSVars = {
109
199
  '--color-interactive-disabled': semanticColorsDark['interactive-disabled'],
110
200
 
111
201
  '--color-divider': semanticColorsDark['divider'],
202
+
203
+ '--color-brand-primary-hover': semanticColorsDark['brand-primary-hover'],
204
+ '--color-brand-primary-active': semanticColorsDark['brand-primary-active'],
205
+ '--color-brand-secondary-hover': semanticColorsDark['brand-secondary-hover'],
206
+ '--color-brand-secondary-active': semanticColorsDark['brand-secondary-active'],
207
+
208
+ '--color-status-success-light': semanticColorsDark['status-success-light'],
209
+ '--color-status-success-dark': semanticColorsDark['status-success-dark'],
210
+ '--color-status-error-light': semanticColorsDark['status-error-light'],
211
+ '--color-status-error-dark': semanticColorsDark['status-error-dark'],
212
+ '--color-status-warning-light': semanticColorsDark['status-warning-light'],
213
+ '--color-status-warning-dark': semanticColorsDark['status-warning-dark'],
214
+ '--color-status-info-light': semanticColorsDark['status-info-light'],
215
+ '--color-status-info-dark': semanticColorsDark['status-info-dark'],
216
+
217
+ '--color-on-status-success': semanticColorsDark['on-status-success'],
218
+ '--color-on-status-error': semanticColorsDark['on-status-error'],
219
+ '--color-on-status-warning': semanticColorsDark['on-status-warning'],
220
+ '--color-on-status-info': semanticColorsDark['on-status-info'],
221
+
222
+ '--color-result-improve': semanticColorsDark['result-improve'],
223
+ '--color-result-improve-light': semanticColorsDark['result-improve-light'],
224
+ '--color-result-improve-dark': semanticColorsDark['result-improve-dark'],
225
+ '--color-result-degrade': semanticColorsDark['result-degrade'],
226
+ '--color-result-degrade-light': semanticColorsDark['result-degrade-light'],
227
+ '--color-result-degrade-dark': semanticColorsDark['result-degrade-dark'],
228
+ '--color-result-inconclusive': semanticColorsDark['result-inconclusive'],
229
+ '--color-result-inconclusive-light': semanticColorsDark['result-inconclusive-light'],
230
+ '--color-result-neutral': semanticColorsDark['result-neutral'],
231
+
232
+ '--color-on-result-improve': semanticColorsDark['on-result-improve'],
233
+ '--color-on-result-degrade': semanticColorsDark['on-result-degrade'],
234
+ '--color-on-result-inconclusive': semanticColorsDark['on-result-inconclusive'],
235
+
236
+ '--color-data-1': semanticColorsDark['data-1'],
237
+ '--color-data-2': semanticColorsDark['data-2'],
238
+ '--color-data-3': semanticColorsDark['data-3'],
239
+ '--color-data-4': semanticColorsDark['data-4'],
240
+ '--color-data-5': semanticColorsDark['data-5'],
241
+ '--color-data-6': semanticColorsDark['data-6'],
242
+ '--color-data-7': semanticColorsDark['data-7'],
243
+ '--color-data-8': semanticColorsDark['data-8'],
244
+ '--color-data-9': semanticColorsDark['data-9'],
245
+ '--color-data-10': semanticColorsDark['data-10'],
246
+
247
+ '--color-text-link-hover': semanticColorsDark['text-link-hover'],
248
+
249
+ '--color-surface-overlay': semanticColorsDark['surface-overlay'],
250
+ '--color-surface-input': semanticColorsDark['surface-input'],
251
+
252
+ '--color-border-input': semanticColorsDark['border-input'],
253
+ '--color-border-input-hover': semanticColorsDark['border-input-hover'],
254
+ '--color-border-input-focus': semanticColorsDark['border-input-focus'],
255
+ '--color-border-input-error': semanticColorsDark['border-input-error'],
256
+
257
+ '--color-interactive-disabled-text': semanticColorsDark['interactive-disabled-text'],
258
+
259
+ '--color-avatar-background': semanticColorsDark['avatar-background'],
260
+ '--color-avatar-text': semanticColorsDark['avatar-text'],
261
+
262
+ ...themeIndependentCSSVars,
112
263
  } as const
113
264
 
114
265
  // Helper to get CSS vars for a theme mode
@@ -3,11 +3,12 @@ export * from './tokens'
3
3
  export * from './config'
4
4
  export * from './shadows'
5
5
  export * from './elevation'
6
+ export * from './manifest'
6
7
 
7
8
  // Re-export commonly used items
8
9
  export { getSemanticColors, type ThemeMode } from './tokens/semantic'
9
10
  export { getThemeCSSVars, gluestackConfig } from './config'
10
- export {
11
+ export {
11
12
  neumorphicShadows,
12
13
  // Color math utilities (HSV-based)
13
14
  lighten,
@@ -0,0 +1,79 @@
1
+ {
2
+ "component": "button",
3
+ "description": "Illustrative example conforming to css-property-manifest.schema.json. Values are derived from Button.tsx's variant/color/size style maps and the semantic token definitions they reference, not measured via getComputedStyle — a follow-up ticket generates verified manifests from the rendered Storybook DOM.",
4
+ "baseVariant": { "variant": "solid", "color": "primary", "size": "md" },
5
+ "properties": [
6
+ {
7
+ "property": "backgroundColor",
8
+ "token": "brand-primary",
9
+ "cssVariable": "--color-brand-primary",
10
+ "resolvedValue": "rgb(255, 121, 0)",
11
+ "variantOverrides": [
12
+ {
13
+ "variant": { "variant": "outline" },
14
+ "token": null,
15
+ "cssVariable": null,
16
+ "resolvedValue": "rgba(0, 0, 0, 0)"
17
+ },
18
+ {
19
+ "variant": { "variant": "solid", "color": "error" },
20
+ "token": "status-error",
21
+ "cssVariable": "--color-status-error",
22
+ "resolvedValue": "rgb(209, 67, 67)"
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "property": "color",
28
+ "token": null,
29
+ "resolvedValue": "rgb(255, 255, 255)",
30
+ "variantOverrides": [
31
+ {
32
+ "variant": { "variant": "outline" },
33
+ "token": "brand-primary",
34
+ "cssVariable": "--color-brand-primary",
35
+ "resolvedValue": "rgb(255, 121, 0)"
36
+ }
37
+ ]
38
+ },
39
+ {
40
+ "property": "borderColor",
41
+ "token": null,
42
+ "resolvedValue": "rgba(0, 0, 0, 0)",
43
+ "variantOverrides": [
44
+ {
45
+ "variant": { "variant": "outline" },
46
+ "token": "brand-primary",
47
+ "cssVariable": "--color-brand-primary",
48
+ "resolvedValue": "rgb(255, 121, 0)"
49
+ }
50
+ ]
51
+ },
52
+ {
53
+ "property": "borderWidth",
54
+ "token": null,
55
+ "resolvedValue": "0px",
56
+ "variantOverrides": [
57
+ {
58
+ "variant": { "variant": "outline" },
59
+ "token": null,
60
+ "resolvedValue": "2px"
61
+ }
62
+ ]
63
+ },
64
+ {
65
+ "property": "borderRadius",
66
+ "token": null,
67
+ "resolvedValue": "6px"
68
+ },
69
+ {
70
+ "property": "paddingLeft",
71
+ "token": null,
72
+ "resolvedValue": "20px",
73
+ "variantOverrides": [
74
+ { "variant": { "size": "sm" }, "token": null, "resolvedValue": "16px" },
75
+ { "variant": { "size": "lg" }, "token": null, "resolvedValue": "24px" }
76
+ ]
77
+ }
78
+ ]
79
+ }
@@ -0,0 +1,93 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://titan-design.dev/schemas/css-property-manifest.schema.json",
4
+ "title": "Component CSS Property Manifest",
5
+ "description": "Per-component record of visual CSS properties, the design token each one resolves from, the expected computed style value, and any per-variant overrides. Generated manifests serve as fixtures for Playwright computed-style assertions (see packages/ui/tests/visual).",
6
+ "type": "object",
7
+ "required": ["component", "properties"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "component": {
11
+ "type": "string",
12
+ "pattern": "^[a-z][a-z0-9-]*$",
13
+ "description": "Component identifier matching its src/components/ui/{component} directory and Storybook id (e.g. \"button\")."
14
+ },
15
+ "description": {
16
+ "type": "string",
17
+ "description": "Optional human-readable note about what this manifest covers."
18
+ },
19
+ "baseVariant": {
20
+ "$ref": "#/$defs/variantAxes",
21
+ "description": "The variant axis values (e.g. { \"variant\": \"solid\", \"color\": \"primary\", \"size\": \"md\" }) that each property's top-level resolvedValue was captured against. Axis names must match the component's own prop names."
22
+ },
23
+ "properties": {
24
+ "type": "array",
25
+ "minItems": 1,
26
+ "items": { "$ref": "#/$defs/cssPropertyEntry" },
27
+ "description": "One entry per visual CSS property tracked for this component."
28
+ }
29
+ },
30
+ "$defs": {
31
+ "variantAxes": {
32
+ "type": "object",
33
+ "minProperties": 1,
34
+ "additionalProperties": { "type": "string" },
35
+ "description": "A set of variant axis name/value pairs (e.g. variant, color, size, state) as accepted by the component's props."
36
+ },
37
+ "cssPropertyEntry": {
38
+ "type": "object",
39
+ "required": ["property", "token", "resolvedValue"],
40
+ "additionalProperties": false,
41
+ "properties": {
42
+ "property": {
43
+ "type": "string",
44
+ "pattern": "^[a-zA-Z]+$",
45
+ "description": "CSS property name in camelCase as returned by window.getComputedStyle (e.g. backgroundColor, borderColor, fontSize)."
46
+ },
47
+ "token": {
48
+ "type": ["string", "null"],
49
+ "description": "Semantic design token key this property is sourced from (e.g. \"brand-primary\"), matching a key in packages/ui/src/theme/tokens/semantic.ts. Use null when the value is a literal not backed by a token."
50
+ },
51
+ "cssVariable": {
52
+ "type": ["string", "null"],
53
+ "description": "CSS custom property backing the token (e.g. \"--color-brand-primary\"), as declared in packages/ui/src/theme/global.css. Omit or null when no CSS variable is involved."
54
+ },
55
+ "resolvedValue": {
56
+ "type": "string",
57
+ "minLength": 1,
58
+ "description": "Expected computed style value for baseVariant, in the format getComputedStyle returns it (e.g. \"rgb(255, 121, 0)\", \"8px\")."
59
+ },
60
+ "variantOverrides": {
61
+ "type": "array",
62
+ "default": [],
63
+ "items": { "$ref": "#/$defs/variantOverride" },
64
+ "description": "Overrides of token/resolvedValue for specific variant axis combinations, merged onto baseVariant."
65
+ }
66
+ }
67
+ },
68
+ "variantOverride": {
69
+ "type": "object",
70
+ "required": ["variant", "resolvedValue"],
71
+ "additionalProperties": false,
72
+ "properties": {
73
+ "variant": {
74
+ "$ref": "#/$defs/variantAxes",
75
+ "description": "Partial variant axis values that trigger this override (e.g. { \"variant\": \"outline\" } or { \"color\": \"error\", \"state\": \"hover\" })."
76
+ },
77
+ "token": {
78
+ "type": ["string", "null"],
79
+ "description": "Semantic token key for this override, or null for a literal value."
80
+ },
81
+ "cssVariable": {
82
+ "type": ["string", "null"],
83
+ "description": "CSS custom property backing the token for this override."
84
+ },
85
+ "resolvedValue": {
86
+ "type": "string",
87
+ "minLength": 1,
88
+ "description": "Expected computed style value when the variant axes match."
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { readFileSync } from 'fs'
3
+ import { join } from 'path'
4
+ import {
5
+ isCssPropertyManifest,
6
+ validateCssPropertyManifest,
7
+ } from './css-property-manifest.validate'
8
+
9
+ const readJson = (fileName: string): unknown =>
10
+ JSON.parse(readFileSync(join(__dirname, fileName), 'utf-8'))
11
+
12
+ describe('css-property-manifest.schema.json', () => {
13
+ it('is well-formed JSON Schema with the documented top-level shape', () => {
14
+ const schema = readJson('css-property-manifest.schema.json') as Record<string, unknown>
15
+
16
+ expect(schema.$schema).toBe('https://json-schema.org/draft/2020-12/schema')
17
+ expect(schema.required).toEqual(['component', 'properties'])
18
+ expect(schema.$defs).toHaveProperty('cssPropertyEntry')
19
+ expect(schema.$defs).toHaveProperty('variantOverride')
20
+ })
21
+ })
22
+
23
+ describe('validateCssPropertyManifest', () => {
24
+ it('accepts the button example manifest', () => {
25
+ const example = readJson('css-property-manifest.example.json')
26
+
27
+ const result = validateCssPropertyManifest(example)
28
+
29
+ expect(result).toEqual({ valid: true, errors: [] })
30
+ expect(isCssPropertyManifest(example)).toBe(true)
31
+ })
32
+
33
+ it('rejects a manifest missing required top-level fields', () => {
34
+ const result = validateCssPropertyManifest({ component: 'button' })
35
+
36
+ expect(result.valid).toBe(false)
37
+ expect(result.errors).toContain('properties must be a non-empty array')
38
+ })
39
+
40
+ it('rejects a property entry missing a resolvedValue', () => {
41
+ const candidate = {
42
+ component: 'button',
43
+ properties: [{ property: 'backgroundColor', token: 'brand-primary' }],
44
+ }
45
+
46
+ const result = validateCssPropertyManifest(candidate)
47
+
48
+ expect(result.valid).toBe(false)
49
+ expect(result.errors).toContain('properties[0].resolvedValue must be a non-empty string')
50
+ })
51
+
52
+ it('rejects a variantOverride missing variant axes', () => {
53
+ const candidate = {
54
+ component: 'button',
55
+ properties: [
56
+ {
57
+ property: 'backgroundColor',
58
+ token: 'brand-primary',
59
+ resolvedValue: 'rgb(255, 121, 0)',
60
+ variantOverrides: [{ resolvedValue: 'rgba(0, 0, 0, 0)' }],
61
+ },
62
+ ],
63
+ }
64
+
65
+ const result = validateCssPropertyManifest(candidate)
66
+
67
+ expect(result.valid).toBe(false)
68
+ expect(result.errors).toContain(
69
+ 'properties[0].variantOverrides[0].variant must be a non-empty string-keyed object'
70
+ )
71
+ })
72
+
73
+ it('allows token to be null for literal (non-tokenized) values', () => {
74
+ const candidate = {
75
+ component: 'button',
76
+ properties: [{ property: 'borderRadius', token: null, resolvedValue: '6px' }],
77
+ }
78
+
79
+ expect(validateCssPropertyManifest(candidate)).toEqual({ valid: true, errors: [] })
80
+ })
81
+ })
@@ -0,0 +1,49 @@
1
+ /**
2
+ * TypeScript mirror of css-property-manifest.schema.json.
3
+ *
4
+ * A manifest records, per component, which visual CSS properties are tracked,
5
+ * which design token each one resolves from, the expected computed style
6
+ * value, and any per-variant overrides. Manifests are the fixtures consumed
7
+ * by Playwright computed-style assertions (see packages/ui/tests/visual).
8
+ */
9
+
10
+ /** Variant axis name/value pairs, e.g. { variant: 'solid', color: 'primary', size: 'md' }. */
11
+ export type VariantAxes = Record<string, string>
12
+
13
+ /** A resolvedValue/token override that applies when the given variant axes match. */
14
+ export interface VariantOverride {
15
+ /** Partial variant axis values that trigger this override, merged onto baseVariant. */
16
+ variant: VariantAxes
17
+ /** Semantic token key for this override, or null for a literal (non-tokenized) value. */
18
+ token?: string | null
19
+ /** CSS custom property backing the token for this override, if any. */
20
+ cssVariable?: string | null
21
+ /** Expected getComputedStyle value when the variant axes match. */
22
+ resolvedValue: string
23
+ }
24
+
25
+ /** A single tracked CSS property for a component. */
26
+ export interface CssPropertyEntry {
27
+ /** CSS property name in camelCase, as returned by window.getComputedStyle (e.g. "backgroundColor"). */
28
+ property: string
29
+ /** Semantic token key this property is sourced from, or null if not backed by a token. */
30
+ token: string | null
31
+ /** CSS custom property backing the token (e.g. "--color-brand-primary"), if any. */
32
+ cssVariable?: string | null
33
+ /** Expected computed style value for baseVariant. */
34
+ resolvedValue: string
35
+ /** Overrides of token/resolvedValue for specific variant axis combinations. */
36
+ variantOverrides?: VariantOverride[]
37
+ }
38
+
39
+ /** The full per-component CSS property manifest. */
40
+ export interface CssPropertyManifest {
41
+ /** Component identifier matching its src/components/ui/{component} directory and Storybook id. */
42
+ component: string
43
+ /** Optional human-readable note about what this manifest covers. */
44
+ description?: string
45
+ /** Variant axis values each property's top-level resolvedValue was captured against. */
46
+ baseVariant?: VariantAxes
47
+ /** Tracked CSS properties for this component. */
48
+ properties: CssPropertyEntry[]
49
+ }
@@ -0,0 +1,84 @@
1
+ import type { CssPropertyManifest, VariantAxes } from './css-property-manifest.types'
2
+
3
+ /** Result of validating a candidate manifest: valid iff errors is empty. */
4
+ export interface ManifestValidationResult {
5
+ valid: boolean
6
+ errors: string[]
7
+ }
8
+
9
+ const isPlainObject = (value: unknown): value is Record<string, unknown> =>
10
+ typeof value === 'object' && value !== null && !Array.isArray(value)
11
+
12
+ const isVariantAxes = (value: unknown): value is VariantAxes =>
13
+ isPlainObject(value) &&
14
+ Object.keys(value).length > 0 &&
15
+ Object.values(value).every((axisValue) => typeof axisValue === 'string')
16
+
17
+ function collectVariantOverrideErrors(override: unknown, path: string): string[] {
18
+ if (!isPlainObject(override)) return [`${path} must be an object`]
19
+ const errors: string[] = []
20
+ if (!isVariantAxes(override.variant))
21
+ errors.push(`${path}.variant must be a non-empty string-keyed object`)
22
+ if (
23
+ override.token !== undefined &&
24
+ override.token !== null &&
25
+ typeof override.token !== 'string'
26
+ ) {
27
+ errors.push(`${path}.token must be a string or null`)
28
+ }
29
+ if (typeof override.resolvedValue !== 'string' || override.resolvedValue.length === 0) {
30
+ errors.push(`${path}.resolvedValue must be a non-empty string`)
31
+ }
32
+ return errors
33
+ }
34
+
35
+ function collectPropertyErrors(entry: unknown, path: string): string[] {
36
+ if (!isPlainObject(entry)) return [`${path} must be an object`]
37
+ const errors: string[] = []
38
+ if (typeof entry.property !== 'string' || entry.property.length === 0) {
39
+ errors.push(`${path}.property must be a non-empty string`)
40
+ }
41
+ if (entry.token !== null && typeof entry.token !== 'string') {
42
+ errors.push(`${path}.token must be a string or null`)
43
+ }
44
+ if (typeof entry.resolvedValue !== 'string' || entry.resolvedValue.length === 0) {
45
+ errors.push(`${path}.resolvedValue must be a non-empty string`)
46
+ }
47
+ if (entry.variantOverrides !== undefined) {
48
+ if (!Array.isArray(entry.variantOverrides)) {
49
+ errors.push(`${path}.variantOverrides must be an array`)
50
+ } else {
51
+ entry.variantOverrides.forEach((override, i) =>
52
+ errors.push(...collectVariantOverrideErrors(override, `${path}.variantOverrides[${i}]`))
53
+ )
54
+ }
55
+ }
56
+ return errors
57
+ }
58
+
59
+ /** Structurally validates a candidate value against the CssPropertyManifest shape. */
60
+ export function validateCssPropertyManifest(candidate: unknown): ManifestValidationResult {
61
+ if (!isPlainObject(candidate)) return { valid: false, errors: ['manifest must be an object'] }
62
+
63
+ const errors: string[] = []
64
+ if (typeof candidate.component !== 'string' || candidate.component.length === 0) {
65
+ errors.push('component must be a non-empty string')
66
+ }
67
+ if (candidate.baseVariant !== undefined && !isVariantAxes(candidate.baseVariant)) {
68
+ errors.push('baseVariant must be a non-empty string-keyed object')
69
+ }
70
+ if (!Array.isArray(candidate.properties) || candidate.properties.length === 0) {
71
+ errors.push('properties must be a non-empty array')
72
+ } else {
73
+ candidate.properties.forEach((entry, i) =>
74
+ errors.push(...collectPropertyErrors(entry, `properties[${i}]`))
75
+ )
76
+ }
77
+
78
+ return { valid: errors.length === 0, errors }
79
+ }
80
+
81
+ /** Type guard variant of validateCssPropertyManifest for use in narrowing contexts. */
82
+ export function isCssPropertyManifest(candidate: unknown): candidate is CssPropertyManifest {
83
+ return validateCssPropertyManifest(candidate).valid
84
+ }