@titan-design/react-ui 0.12.1 → 0.14.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 (531) hide show
  1. package/README.md +60 -47
  2. package/dist/bodymap.js +856 -736
  3. package/dist/bodymap.js.map +1 -1
  4. package/dist/bodymap.mjs +858 -738
  5. package/dist/bodymap.mjs.map +1 -1
  6. package/dist/index.d.mts +1431 -237
  7. package/dist/index.d.ts +1431 -237
  8. package/dist/index.js +6506 -4781
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +6280 -4642
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{pages-B1hqnGIC.d.ts → pages-CsjsUn0G.d.ts} +51 -16
  13. package/dist/{pages-CvsFqNtx.d.mts → pages-ZbT_pD_U.d.mts} +51 -16
  14. package/dist/pages.d.mts +1 -1
  15. package/dist/pages.d.ts +1 -1
  16. package/dist/pages.js +1919 -1937
  17. package/dist/pages.js.map +1 -1
  18. package/dist/pages.mjs +1927 -1945
  19. package/dist/pages.mjs.map +1 -1
  20. package/dist/theme/index.d.mts +225 -140
  21. package/dist/theme/index.d.ts +225 -140
  22. package/dist/theme/index.js +298 -319
  23. package/dist/theme/index.js.map +1 -1
  24. package/dist/theme/index.mjs +283 -315
  25. package/dist/theme/index.mjs.map +1 -1
  26. package/dist/theme/tokens-css.js +145 -24
  27. package/dist/theme/tokens-css.js.map +1 -1
  28. package/dist/theme/tokens-css.mjs +145 -24
  29. package/dist/theme/tokens-css.mjs.map +1 -1
  30. package/dist/theme/tokens.d.mts +128 -11
  31. package/dist/theme/tokens.d.ts +128 -11
  32. package/dist/theme/tokens.js +126 -26
  33. package/dist/theme/tokens.js.map +1 -1
  34. package/dist/theme/tokens.mjs +124 -27
  35. package/dist/theme/tokens.mjs.map +1 -1
  36. package/docs/audits/2026-09-08-critique-addenda.md +51 -0
  37. package/docs/audits/2026-09-08-library-critique.md +212 -0
  38. package/docs/library-roadmap.md +99 -0
  39. package/docs/render-testing-pattern.md +130 -0
  40. package/package.json +1 -1
  41. package/src/arch/Architecture.stories.tsx +3 -1
  42. package/src/arch/arch-graph.freshness.test.ts +52 -0
  43. package/src/arch/arch-graph.json +4989 -1583
  44. package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
  45. package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
  46. package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
  47. package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
  48. package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
  49. package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
  50. package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
  51. package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
  52. package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
  53. package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
  54. package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
  55. package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
  56. package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
  57. package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
  58. package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
  59. package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
  60. package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
  61. package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
  62. package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
  63. package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
  64. package/src/components/custom/ActiveWork/InitiativeBrief.tsx +182 -0
  65. package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
  66. package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
  67. package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
  68. package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
  69. package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
  70. package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
  71. package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
  72. package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
  73. package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
  74. package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
  75. package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
  76. package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
  77. package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
  78. package/src/components/custom/ActiveWork/README.md +305 -0
  79. package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
  80. package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
  81. package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
  82. package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
  83. package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
  84. package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
  85. package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
  86. package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
  87. package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
  88. package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
  89. package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
  90. package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
  91. package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
  92. package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
  93. package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
  94. package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
  95. package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
  96. package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
  97. package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
  98. package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
  99. package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
  100. package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
  101. package/src/components/custom/ActiveWork/format-time.ts +32 -0
  102. package/src/components/custom/ActiveWork/index.ts +19 -0
  103. package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
  104. package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
  105. package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
  106. package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
  107. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +12 -6
  108. package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
  109. package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
  110. package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
  111. package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
  112. package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
  113. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
  114. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  115. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
  116. package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
  117. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
  118. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
  119. package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
  120. package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
  121. package/src/components/custom/Fatigue/README.md +40 -3
  122. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  123. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
  124. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
  125. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  126. package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
  127. package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
  128. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
  129. package/src/components/custom/Fatigue/index.ts +17 -5
  130. package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
  131. package/src/components/custom/Fatigue/panel-layout.ts +185 -0
  132. package/src/components/custom/Gauge/Gauge.stories.tsx +6 -3
  133. package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
  134. package/src/components/custom/Gauge/Gauge.tsx +7 -2
  135. package/src/components/custom/Metric/Metric.stories.tsx +10 -1
  136. package/src/components/custom/Metric/Metric.test.tsx +1 -3
  137. package/src/components/custom/Metric/Metric.tsx +5 -20
  138. package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
  139. package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
  140. package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
  141. package/src/components/custom/Prose/README.md +58 -0
  142. package/src/components/custom/Prose/index.ts +1 -0
  143. package/src/components/custom/Scatter/Scatter.stories.tsx +55 -14
  144. package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
  145. package/src/components/custom/Scatter/Scatter.tsx +122 -18
  146. package/src/components/custom/Sidebar/Sidebar.stories.tsx +41 -8
  147. package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
  148. package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
  149. package/src/components/custom/Table/Table.stories.tsx +99 -16
  150. package/src/components/custom/Table/Table.test.tsx +273 -86
  151. package/src/components/custom/Table/Table.tsx +159 -67
  152. package/src/components/custom/Table/column-fit.test.ts +75 -0
  153. package/src/components/custom/Table/column-fit.ts +116 -0
  154. package/src/components/custom/Table/index.ts +9 -0
  155. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
  156. package/src/components/custom/Treemap/Treemap.stories.tsx +15 -12
  157. package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
  158. package/src/components/custom/Treemap/Treemap.tsx +11 -3
  159. package/src/components/custom/Typography/Typography.stories.tsx +28 -1
  160. package/src/components/custom/Typography/Typography.test.tsx +34 -0
  161. package/src/components/custom/Typography/Typography.tsx +6 -0
  162. package/src/components/custom/Typography/index.ts +1 -8
  163. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +11 -2
  164. package/src/components/custom/Workout/BaseBadge.stories.tsx +27 -11
  165. package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
  166. package/src/components/custom/Workout/BaseBadge.tsx +6 -2
  167. package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
  168. package/src/components/custom/Workout/BodyMap.tsx +8 -4
  169. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +28 -6
  170. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
  171. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +50 -12
  172. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +12 -9
  173. package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
  174. package/src/components/custom/Workout/CapacityBandChart.tsx +144 -111
  175. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  176. package/src/components/custom/Workout/DeviationBar.test.tsx +10 -4
  177. package/src/components/custom/Workout/DeviationBar.tsx +24 -16
  178. package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +183 -0
  179. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +32 -18
  180. package/src/components/custom/Workout/ExerciseCard.stories.tsx +12 -11
  181. package/src/components/custom/Workout/ExerciseCard.test.tsx +52 -22
  182. package/src/components/custom/Workout/ExerciseCard.tsx +99 -183
  183. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +95 -12
  184. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
  185. package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
  186. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
  187. package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
  188. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
  189. package/src/components/custom/Workout/ExerciseHeading.tsx +194 -46
  190. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +12 -9
  191. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
  192. package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
  193. package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
  194. package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
  195. package/src/components/custom/Workout/InputBar.tsx +4 -2
  196. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  197. package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
  198. package/src/components/custom/Workout/IntensityBar.tsx +8 -3
  199. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
  200. package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
  201. package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
  202. package/src/components/custom/Workout/MesoCard.tsx +14 -10
  203. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  204. package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
  205. package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
  206. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
  207. package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
  208. package/src/components/custom/Workout/MesoStatusCard.tsx +20 -33
  209. package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
  210. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
  211. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
  212. package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
  213. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +9 -4
  214. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
  215. package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
  216. package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
  217. package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
  218. package/src/components/custom/Workout/PrBadge.tsx +13 -17
  219. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +16 -3
  220. package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
  221. package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
  222. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
  223. package/src/components/custom/Workout/README.md +68 -17
  224. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
  225. package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
  226. package/src/components/custom/Workout/ReadinessCheck.tsx +22 -4
  227. package/src/components/custom/Workout/RestTimer.stories.tsx +16 -4
  228. package/src/components/custom/Workout/RestTimer.tsx +4 -2
  229. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
  230. package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
  231. package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
  232. package/src/components/custom/Workout/SegmentedBar.stories.tsx +21 -17
  233. package/src/components/custom/Workout/SegmentedBar.tsx +10 -5
  234. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
  235. package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
  236. package/src/components/custom/Workout/SessionHeader.tsx +12 -12
  237. package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
  238. package/src/components/custom/Workout/SessionRail.tsx +7 -9
  239. package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
  240. package/src/components/custom/Workout/SetBar.test.tsx +49 -4
  241. package/src/components/custom/Workout/SetBar.tsx +38 -12
  242. package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
  243. package/src/components/custom/Workout/SetRow.test.tsx +16 -2
  244. package/src/components/custom/Workout/SetRow.tsx +61 -23
  245. package/src/components/custom/Workout/SetStrip.stories.tsx +63 -7
  246. package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
  247. package/src/components/custom/Workout/SetStrip.tsx +24 -4
  248. package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
  249. package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
  250. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
  251. package/src/components/custom/Workout/SetsRepsLoad.tsx +24 -12
  252. package/src/components/custom/Workout/Sparkline.stories.tsx +14 -11
  253. package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
  254. package/src/components/custom/Workout/Sparkline.tsx +14 -18
  255. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  256. package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
  257. package/src/components/custom/Workout/StatusDot.tsx +93 -63
  258. package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
  259. package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
  260. package/src/components/custom/Workout/StatusPill.tsx +27 -38
  261. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +11 -4
  262. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
  263. package/src/components/custom/Workout/StrengthTrendChart.tsx +39 -36
  264. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
  265. package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
  266. package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
  267. package/src/components/custom/Workout/TempoDisplay.stories.tsx +26 -10
  268. package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
  269. package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
  270. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
  271. package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
  272. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +10 -6
  273. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +5 -2
  274. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +44 -1
  275. package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
  276. package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
  277. package/src/components/custom/Workout/VelocityStrip.tsx +26 -14
  278. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
  279. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
  280. package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
  281. package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
  282. package/src/components/custom/Workout/WeekRow.tsx +23 -16
  283. package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
  284. package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
  285. package/src/components/custom/Workout/WeightBadge.tsx +28 -27
  286. package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
  287. package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
  288. package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
  289. package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
  290. package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
  291. package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
  292. package/src/components/custom/Workout/ZoneTrack.stories.tsx +10 -7
  293. package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
  294. package/src/components/custom/Workout/exerciseRowState.ts +29 -0
  295. package/src/components/custom/Workout/index.ts +17 -2
  296. package/src/components/custom/Workout/metricText.stories.tsx +15 -13
  297. package/src/components/custom/Workout/metricText.tsx +8 -2
  298. package/src/components/custom/Workout/setHeadingKit.tsx +219 -36
  299. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  300. package/src/components/custom/charts/README.md +66 -0
  301. package/src/components/custom/charts/SetBarChart.test.tsx +46 -1
  302. package/src/components/custom/charts/SetBarChart.tsx +56 -9
  303. package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
  304. package/src/components/custom/charts/SparkBars.test.tsx +69 -0
  305. package/src/components/custom/charts/SparkBars.tsx +85 -0
  306. package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
  307. package/src/components/custom/charts/flatBarGeometry.ts +32 -0
  308. package/src/components/custom/charts/index.ts +3 -0
  309. package/src/components/custom/index.ts +3 -0
  310. package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
  311. package/src/components/custom/stepper/Stepper.test.tsx +1 -7
  312. package/src/components/custom/stepper/Stepper.tsx +21 -34
  313. package/src/components/icons/icons.stories.tsx +1 -1
  314. package/src/components/icons/icons.tsx +51 -0
  315. package/src/components/shell/AppShell.stories.tsx +150 -0
  316. package/src/components/shell/AppShell.test.tsx +70 -0
  317. package/src/components/shell/AppShell.tsx +90 -0
  318. package/src/components/shell/BrandLockup.stories.tsx +66 -5
  319. package/src/components/shell/BrandLockup.test.tsx +34 -1
  320. package/src/components/shell/BrandLockup.tsx +27 -11
  321. package/src/components/shell/NavItem.stories.tsx +4 -4
  322. package/src/components/shell/NavItem.test.tsx +17 -0
  323. package/src/components/shell/NavItem.tsx +17 -5
  324. package/src/components/shell/README.md +89 -26
  325. package/src/components/shell/SideNav.stories.tsx +38 -7
  326. package/src/components/shell/SideNav.test.tsx +16 -8
  327. package/src/components/shell/SideNav.tsx +17 -17
  328. package/src/components/shell/TopBar.stories.tsx +44 -19
  329. package/src/components/shell/TopBar.test.tsx +34 -12
  330. package/src/components/shell/TopBar.tsx +61 -40
  331. package/src/components/shell/brands.tsx +66 -0
  332. package/src/components/shell/index.ts +9 -8
  333. package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
  334. package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
  335. package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
  336. package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
  337. package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
  338. package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
  339. package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
  340. package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
  341. package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
  342. package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
  343. package/src/components/shell/workout/WorkoutShell.tsx +77 -0
  344. package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
  345. package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
  346. package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
  347. package/src/components/shell/workout/index.ts +9 -0
  348. package/src/components/shell/workout/workoutNavItems.tsx +16 -0
  349. package/src/components/ui/README.md +110 -0
  350. package/src/components/ui/alert/Alert.stories.tsx +15 -5
  351. package/src/components/ui/alert/Alert.tsx +24 -3
  352. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
  353. package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
  354. package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
  355. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  356. package/src/components/ui/avatar/Avatar.tsx +2 -10
  357. package/src/components/ui/badge/Badge.stories.tsx +83 -28
  358. package/src/components/ui/badge/Badge.test.tsx +19 -3
  359. package/src/components/ui/badge/Badge.tsx +48 -55
  360. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  361. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
  362. package/src/components/ui/button/Button.stories.tsx +46 -12
  363. package/src/components/ui/button/Button.tsx +44 -37
  364. package/src/components/ui/card/Card.stories.tsx +90 -94
  365. package/src/components/ui/card/Card.test.tsx +80 -6
  366. package/src/components/ui/card/Card.tsx +150 -203
  367. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  368. package/src/components/ui/checkbox/Checkbox.tsx +7 -27
  369. package/src/components/ui/chip/Chip.stories.tsx +37 -13
  370. package/src/components/ui/chip/Chip.test.tsx +10 -6
  371. package/src/components/ui/chip/Chip.tsx +76 -91
  372. package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
  373. package/src/components/ui/collapse/Collapse.tsx +6 -11
  374. package/src/components/ui/data-row/DataRow.stories.tsx +14 -3
  375. package/src/components/ui/data-row/DataRow.test.tsx +5 -15
  376. package/src/components/ui/data-row/DataRow.tsx +18 -7
  377. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  378. package/src/components/ui/divider/Divider.tsx +1 -5
  379. package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
  380. package/src/components/ui/drawer/Drawer.tsx +13 -27
  381. package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
  382. package/src/components/ui/form-field/FormField.stories.tsx +6 -20
  383. package/src/components/ui/form-field/FormField.test.tsx +1 -5
  384. package/src/components/ui/form-field/FormField.tsx +10 -46
  385. package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
  386. package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
  387. package/src/components/ui/help-tip/HelpTip.tsx +21 -22
  388. package/src/components/ui/help-tip/index.ts +7 -1
  389. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  390. package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
  391. package/src/components/ui/icon-box/IconBox.tsx +10 -18
  392. package/src/components/ui/indicator/Indicator.stories.tsx +5 -4
  393. package/src/components/ui/indicator/Indicator.tsx +20 -14
  394. package/src/components/ui/input/Input.stories.tsx +3 -11
  395. package/src/components/ui/input/Input.test.tsx +2 -10
  396. package/src/components/ui/input/Input.tsx +10 -25
  397. package/src/components/ui/input/index.ts +7 -1
  398. package/src/components/ui/link/Link.stories.tsx +19 -7
  399. package/src/components/ui/list-item/ListItem.stories.tsx +17 -5
  400. package/src/components/ui/list-item/ListItem.tsx +2 -9
  401. package/src/components/ui/menu/Menu.stories.tsx +21 -15
  402. package/src/components/ui/menu/Menu.test.tsx +2 -11
  403. package/src/components/ui/menu/Menu.tsx +12 -14
  404. package/src/components/ui/menu/index.ts +8 -1
  405. package/src/components/ui/modal/Modal.stories.tsx +2 -2
  406. package/src/components/ui/modal/Modal.tsx +31 -19
  407. package/src/components/ui/pill/Pill.stories.tsx +111 -23
  408. package/src/components/ui/pill/Pill.test.tsx +101 -3
  409. package/src/components/ui/pill/Pill.tsx +168 -49
  410. package/src/components/ui/pill/index.ts +1 -1
  411. package/src/components/ui/popover/Popover.stories.tsx +6 -8
  412. package/src/components/ui/popover/Popover.test.tsx +1 -6
  413. package/src/components/ui/popover/Popover.tsx +20 -15
  414. package/src/components/ui/popover/index.ts +7 -1
  415. package/src/components/ui/progress/Progress.stories.tsx +3 -12
  416. package/src/components/ui/progress/Progress.test.tsx +1 -5
  417. package/src/components/ui/progress/Progress.tsx +9 -6
  418. package/src/components/ui/radio/Radio.stories.tsx +42 -21
  419. package/src/components/ui/radio/Radio.test.tsx +6 -2
  420. package/src/components/ui/radio/Radio.tsx +23 -22
  421. package/src/components/ui/section/Section.stories.tsx +12 -21
  422. package/src/components/ui/section/Section.test.tsx +1 -6
  423. package/src/components/ui/section/Section.tsx +1 -3
  424. package/src/components/ui/section/index.ts +1 -5
  425. package/src/components/ui/select/Select.stories.tsx +3 -13
  426. package/src/components/ui/select/Select.test.tsx +2 -11
  427. package/src/components/ui/select/Select.tsx +16 -15
  428. package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
  429. package/src/components/ui/skeleton/Skeleton.tsx +3 -14
  430. package/src/components/ui/skeleton/index.ts +1 -7
  431. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  432. package/src/components/ui/spinner/Spinner.tsx +4 -5
  433. package/src/components/ui/stack/Stack.stories.tsx +8 -8
  434. package/src/components/ui/stack/Stack.tsx +1 -1
  435. package/src/components/ui/surface/Surface.stories.tsx +92 -33
  436. package/src/components/ui/surface/Surface.test.tsx +88 -28
  437. package/src/components/ui/surface/Surface.tsx +37 -87
  438. package/src/components/ui/surface/SurfaceContext.ts +22 -62
  439. package/src/components/ui/surface/index.ts +8 -0
  440. package/src/components/ui/surface/resolveSurface.ts +98 -0
  441. package/src/components/ui/surface/surface.contract.test.ts +6 -1
  442. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  443. package/src/components/ui/switch/Switch.tsx +7 -6
  444. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  445. package/src/components/ui/tabs/Tabs.tsx +8 -21
  446. package/src/components/ui/tabs/index.ts +1 -7
  447. package/src/components/ui/tile/Tile.stories.tsx +1 -1
  448. package/src/components/ui/tile/Tile.tsx +3 -0
  449. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  450. package/src/components/ui/toast/Toast.test.tsx +2 -8
  451. package/src/components/ui/toast/Toast.tsx +49 -57
  452. package/src/components/ui/toast/index.ts +0 -1
  453. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
  454. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
  455. package/src/components/ui/toolbar-button/ToolbarButton.tsx +38 -49
  456. package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
  457. package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
  458. package/src/components/ui/tooltip/Tooltip.tsx +55 -36
  459. package/src/components/ui/tooltip/index.ts +1 -1
  460. package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
  461. package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
  462. package/src/components/ui/trigger/index.ts +2 -0
  463. package/src/stories/PresetShowcase.stories.tsx +101 -48
  464. package/src/test/no-device-internals.test.ts +3 -3
  465. package/src/test/no-raw-color.test.ts +39 -0
  466. package/src/theme/Alpha.stories.tsx +103 -0
  467. package/src/theme/ChoosingTokens.stories.tsx +48 -0
  468. package/src/theme/ColorPalettes.stories.tsx +62 -45
  469. package/src/theme/ColorPrimitives.stories.tsx +35 -35
  470. package/src/theme/Depth.stories.tsx +132 -65
  471. package/src/theme/DepthCalibration.stories.tsx +23 -21
  472. package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
  473. package/src/theme/ThemeProvider.test.tsx +4 -4
  474. package/src/theme/barrel.ts +7 -6
  475. package/src/theme/color-stories.coverage.test.ts +1 -1
  476. package/src/theme/color-utils.ts +41 -29
  477. package/src/theme/config.completeness.test.ts +1 -1
  478. package/src/theme/config.ts +40 -0
  479. package/src/theme/elevation.test.ts +82 -56
  480. package/src/theme/elevation.ts +77 -561
  481. package/src/theme/global.css +45 -5
  482. package/src/theme/grey-ramp.test.ts +24 -8
  483. package/src/theme/lift.test.ts +39 -0
  484. package/src/theme/lift.ts +102 -0
  485. package/src/theme/materials.test.ts +5 -2
  486. package/src/theme/materials.ts +38 -4
  487. package/src/theme/native-theming-guard.test.ts +5 -5
  488. package/src/theme/presets/apply.ts +4 -0
  489. package/src/theme/presets/audiobook.ts +5 -2
  490. package/src/theme/presets/default.ts +4 -0
  491. package/src/theme/presets/index.ts +14 -1
  492. package/src/theme/presets/types.ts +1 -1
  493. package/src/theme/semantic-contrast.test.ts +13 -4
  494. package/src/theme/status-distinctness.test.ts +66 -0
  495. package/src/theme/subtle-tone-weight.test.ts +163 -0
  496. package/src/theme/surface-planes.test.ts +37 -0
  497. package/src/theme/surface-planes.ts +68 -0
  498. package/src/theme/tailwind-var-opacity.test.ts +163 -0
  499. package/src/theme/token-export-guard.test.ts +1 -3
  500. package/src/theme/tokens/primitives.test.ts +15 -7
  501. package/src/theme/tokens/primitives.ts +98 -33
  502. package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
  503. package/src/theme/tokens/semantic.ts +124 -54
  504. package/src/theme/wash-ladder.test.ts +58 -0
  505. package/src/theme/workout-tokens.ts +1 -7
  506. package/src/types/jest-axe.d.ts +2 -6
  507. package/src/types/raw-md.d.ts +5 -0
  508. package/src/utils/colors.alpha-adoption.test.ts +408 -0
  509. package/src/utils/colors.ts +4 -4
  510. package/src/utils/form.ts +1 -3
  511. package/src/utils/index.ts +2 -7
  512. package/src/utils/number-format.ts +17 -0
  513. package/src/utils/workout-format.ts +15 -6
  514. package/src/web-jsx/jsx-runtime.ts +1 -5
  515. package/tailwind.config.js +55 -9
  516. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
  517. package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
  518. package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
  519. package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
  520. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
  521. package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
  522. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
  523. package/src/components/shell/DashboardShell.stories.tsx +0 -65
  524. package/src/components/shell/DashboardShell.tsx +0 -75
  525. package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
  526. package/src/theme/elevation.stories.tsx +0 -346
  527. package/src/utils/useTheme.ts +0 -72
  528. /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
  529. /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
  530. /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
  531. /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
@@ -2,6 +2,16 @@ import { describe, it, expect } from 'vitest'
2
2
  import { render, screen } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
4
  import { StatusDot } from './StatusDot'
5
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
6
+ import { greyRamp } from '../../../theme/tokens/primitives'
7
+ import { hexToRgb } from '../../../theme/color-utils'
8
+ import { resolveColor } from '../../../theme/resolve-color'
9
+
10
+ /** The `rgba(r, g, b` prefix getGlowShadow emits for a token colour. */
11
+ function glowRgb(hex: string): string {
12
+ const { r, g, b } = hexToRgb(hex)!
13
+ return `rgba(${r}, ${g}, ${b}`
14
+ }
5
15
 
6
16
  describe('StatusDot', () => {
7
17
  it('renders a dot', () => {
@@ -50,6 +60,23 @@ describe('StatusDot', () => {
50
60
  })
51
61
 
52
62
  describe('solid variants', () => {
63
+ it.each([
64
+ ['success', 'status-success'],
65
+ ['warning', 'status-warning'],
66
+ ['error', 'status-error'],
67
+ ['neutral', 'text-tertiary'],
68
+ ] as const)('fills the %s dot from the %s token', (variant, token) => {
69
+ render(<StatusDot variant={variant} />)
70
+ expect(screen.getByTestId('status-dot')).toHaveStyle({
71
+ backgroundColor: resolveColor(token),
72
+ })
73
+ })
74
+
75
+ it('draws the glyph on a solid fill in text-inverse', () => {
76
+ render(<StatusDot variant="success" size="md" icon="check" />)
77
+ expect(screen.getByText('✓')).toHaveStyle({ color: resolveColor('text-inverse') })
78
+ })
79
+
53
80
  it('renders all solid variant types without error', () => {
54
81
  const { rerender } = render(<StatusDot variant="success" />)
55
82
  expect(screen.getByTestId('status-dot')).toBeInTheDocument()
@@ -84,6 +111,18 @@ describe('StatusDot', () => {
84
111
  expect(screen.getByLabelText('future status')).toBeInTheDocument()
85
112
  })
86
113
 
114
+ // The rim is a whole rung above the wash. Asserting both rules out the
115
+ // flattening that collapses a two-step ring onto one `-subtle`.
116
+ it.each([
117
+ ['on-track', 'status-success'],
118
+ ['deviation', 'status-warning'],
119
+ ] as const)('washes the %s ring with %s -subtle inside a -muted rim', (variant, role) => {
120
+ render(<StatusDot variant={variant} />)
121
+ const dot = screen.getByTestId('status-dot')
122
+ expect(dot).toHaveStyle({ backgroundColor: resolveColor(`${role}-subtle`) })
123
+ expect(dot.style.borderTopColor).toBe(resolveColor(`${role}-muted`))
124
+ })
125
+
87
126
  it('renders the future variant with a 1px dashed border', () => {
88
127
  render(<StatusDot variant="future" />)
89
128
  const dot = screen.getByTestId('status-dot')
@@ -103,19 +142,19 @@ describe('StatusDot', () => {
103
142
  it('applies a box-shadow when glow is set on a solid variant', () => {
104
143
  render(<StatusDot variant="success" glow />)
105
144
  const dot = screen.getByTestId('status-dot')
106
- expect(dot.style.boxShadow).toContain('rgba(46,213,115,0.4)')
145
+ expect(dot.style.boxShadow).toContain(glowRgb(getSemanticColors('dark')['status-success']))
107
146
  })
108
147
 
109
148
  it('applies a box-shadow when glow is set on a ring variant', () => {
110
149
  render(<StatusDot variant="on-track" glow />)
111
150
  const dot = screen.getByTestId('status-dot')
112
- expect(dot.style.boxShadow).toContain('rgba(46,213,115,0.4)')
151
+ expect(dot.style.boxShadow).toContain(glowRgb(getSemanticColors('dark')['status-success']))
113
152
  })
114
153
 
115
154
  it('applies a gray box-shadow when glow is set on the future variant', () => {
116
155
  render(<StatusDot variant="future" glow />)
117
156
  const dot = screen.getByTestId('status-dot')
118
- expect(dot.style.boxShadow).toContain('rgba(107,114,128,0.4)')
157
+ expect(dot.style.boxShadow).toContain(glowRgb(greyRamp[500]))
119
158
  })
120
159
 
121
160
  it('does not apply a box-shadow when glow is not set', () => {
@@ -128,7 +167,7 @@ describe('StatusDot', () => {
128
167
  describe('accessibility', () => {
129
168
  it('has no accessibility violations', async () => {
130
169
  const { container } = render(
131
- <StatusDot variant="success" size="md" icon="check" label="On track" />,
170
+ <StatusDot variant="success" size="md" icon="check" label="On track" />
132
171
  )
133
172
  const results = await axe(container)
134
173
  expect(results).toHaveNoViolations()
@@ -136,7 +175,7 @@ describe('StatusDot', () => {
136
175
 
137
176
  it('has no accessibility violations for ring variants', async () => {
138
177
  const { container } = render(
139
- <StatusDot variant="on-track" size="md" icon="check" label="On track" />,
178
+ <StatusDot variant="on-track" size="md" icon="check" label="On track" />
140
179
  )
141
180
  const results = await axe(container)
142
181
  expect(results).toHaveNoViolations()
@@ -1,9 +1,11 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
- import { View, Text, type ViewProps } from 'react-native'
3
- import { getSemanticColors } from '../../../theme/tokens/semantic'
2
+ import { View, type ViewProps, type ViewStyle } from 'react-native'
3
+ import { Typography } from '../Typography'
4
+ import { resolveColor } from '../../../theme/resolve-color'
5
+ import { semanticColorsDark } from '../../../theme/tokens/semantic'
4
6
  import { greyRamp } from '../../../theme/tokens/primitives'
5
-
6
- const t = getSemanticColors('dark')
7
+ import { alpha } from '../../../utils/colors'
8
+ import { getGlowShadow } from '../../../theme/elevation'
7
9
 
8
10
  export type StatusDotVariant =
9
11
  | 'success'
@@ -25,66 +27,91 @@ export interface StatusDotProps extends ViewProps {
25
27
  className?: string
26
28
  }
27
29
 
28
- const solidVariantColors: Record<string, string> = {
29
- success: t['status-success'],
30
- warning: t['status-warning'],
31
- error: t['status-error'],
32
- neutral: greyRamp[500],
33
- }
30
+ type SolidVariant = 'success' | 'warning' | 'error' | 'neutral'
34
31
 
35
- const ringVariantStyles: Record<string, Record<string, unknown>> = {
36
- 'on-track': {
37
- backgroundColor: 'rgba(46,213,115,0.15)',
38
- borderWidth: 1,
39
- borderColor: 'rgba(46,213,115,0.3)',
40
- },
41
- deviation: {
42
- backgroundColor: 'rgba(245,158,11,0.15)',
43
- borderWidth: 1,
44
- borderColor: 'rgba(245,158,11,0.25)',
45
- },
46
- future: {
47
- backgroundColor: 'rgba(107,114,128,0.1)',
48
- borderWidth: 1,
49
- borderStyle: 'dashed',
50
- borderColor: 'rgba(107,114,128,0.2)',
51
- },
52
- }
32
+ /** Solid variants: a filled dot. `neutral` takes `text-tertiary`, as Indicator's `default` does. */
33
+ const solidFillToken = {
34
+ success: 'status-success',
35
+ warning: 'status-warning',
36
+ error: 'status-error',
37
+ neutral: 'text-tertiary',
38
+ } as const
39
+
40
+ /** Ring variants: a `-subtle` wash inside a `-muted` rim, both off the same role. */
41
+ const ringWashToken = {
42
+ 'on-track': { fill: 'status-success-subtle', border: 'status-success-muted' },
43
+ deviation: { fill: 'status-warning-subtle', border: 'status-warning-muted' },
44
+ } as const
53
45
 
54
- const glowStyles: Record<StatusDotVariant, Record<string, unknown>> = {
55
- success: { boxShadow: '0 0 4px rgba(46,213,115,0.4)' },
56
- warning: { boxShadow: '0 0 4px rgba(245,158,11,0.4)' },
57
- error: { boxShadow: '0 0 4px rgba(239,68,68,0.4)' },
58
- neutral: { boxShadow: '0 0 4px rgba(107,114,128,0.4)' },
59
- 'on-track': { boxShadow: '0 0 4px rgba(46,213,115,0.4)' },
60
- deviation: { boxShadow: '0 0 4px rgba(245,158,11,0.4)' },
61
- future: { boxShadow: '0 0 4px rgba(107,114,128,0.4)' },
46
+ // The neutral ring has no wash rungs: `-subtle`/`-muted`/`-strong` were published for
47
+ // the seven status and brand roles, and `text-tertiary` is not one of them. Derived
48
+ // from the same ramp step the token resolves to, at the frozen alphas, rather than
49
+ // mixed independently. FINDING for E3: the neutral role needs the wash ladder too.
50
+ const FUTURE_FILL = alpha(greyRamp[500], 0.1)
51
+ const FUTURE_BORDER = alpha(greyRamp[500], 0.2)
52
+
53
+ /** Glyph colour: dark on a solid fill, the role's own colour inside a ring. */
54
+ const glyphToken = {
55
+ success: 'text-inverse',
56
+ warning: 'text-inverse',
57
+ error: 'text-inverse',
58
+ neutral: 'text-inverse',
59
+ 'on-track': 'status-success',
60
+ deviation: 'status-warning',
61
+ future: 'text-tertiary',
62
+ } as const
63
+
64
+ // Glow is EMPHASIS, not depth: one shared builder, one token colour per variant.
65
+ // Literal hex (not `resolveColor`) because getGlowShadow does colour maths on it —
66
+ // the same exception Indicator documents.
67
+ const glowColors: Record<StatusDotVariant, string> = {
68
+ success: semanticColorsDark['status-success'],
69
+ warning: semanticColorsDark['status-warning'],
70
+ error: semanticColorsDark['status-error'],
71
+ neutral: greyRamp[500],
72
+ 'on-track': semanticColorsDark['status-success'],
73
+ deviation: semanticColorsDark['status-warning'],
74
+ future: greyRamp[500],
62
75
  }
63
76
 
77
+ const glowStyles: Record<StatusDotVariant, ViewStyle> = Object.fromEntries(
78
+ Object.entries(glowColors).map(([variant, color]) => [variant, getGlowShadow(color, 'subtle')])
79
+ ) as Record<StatusDotVariant, ViewStyle>
80
+
64
81
  const iconChars: Record<string, string> = {
65
82
  check: '\u2713',
66
83
  exclamation: '!',
67
84
  dash: '\u2014',
68
85
  }
69
86
 
70
- /** Color-matched icon colors: dark on solid bg, light on transparent bg */
71
- const iconColors: Record<StatusDotVariant, string> = {
72
- success: '#0A5C52',
73
- warning: '#6B4000',
74
- error: '#5C1A1A',
75
- neutral: greyRamp[200],
76
- 'on-track': t['status-success'],
77
- deviation: t['status-warning'],
78
- future: greyRamp[500],
79
- }
80
-
81
87
  const sizeDimensions = {
82
88
  sm: { size: 8, showIcon: false },
83
89
  md: { size: 18, showIcon: true },
84
90
  }
85
91
 
86
- function isSolidVariant(variant: StatusDotVariant): boolean {
87
- return variant in solidVariantColors
92
+ function isSolidVariant(variant: StatusDotVariant): variant is SolidVariant {
93
+ return variant in solidFillToken
94
+ }
95
+
96
+ /** The ring variants' wash + rim, resolved per theme. `future` adds the dashed rim. */
97
+ function ringStyle(variant: StatusDotVariant): ViewStyle | undefined {
98
+ if (variant === 'future') {
99
+ return {
100
+ backgroundColor: FUTURE_FILL,
101
+ borderWidth: 1,
102
+ borderStyle: 'dashed',
103
+ borderColor: FUTURE_BORDER,
104
+ }
105
+ }
106
+ if (variant === 'on-track' || variant === 'deviation') {
107
+ const tokens = ringWashToken[variant]
108
+ return {
109
+ backgroundColor: resolveColor(tokens.fill),
110
+ borderWidth: 1,
111
+ borderColor: resolveColor(tokens.border),
112
+ }
113
+ }
114
+ return undefined
88
115
  }
89
116
 
90
117
  export function StatusDot({
@@ -98,7 +125,7 @@ export function StatusDot({
98
125
  }: StatusDotProps) {
99
126
  const config = sizeDimensions[size]
100
127
  const solid = isSolidVariant(variant)
101
- const ring = ringVariantStyles[variant]
128
+ const ring = ringStyle(variant)
102
129
 
103
130
  const glowStyle = glow ? glowStyles[variant] : null
104
131
 
@@ -113,8 +140,8 @@ export function StatusDot({
113
140
  justifyContent: 'center',
114
141
  flexShrink: 0,
115
142
  },
116
- solid ? { backgroundColor: solidVariantColors[variant] } : undefined,
117
- ring ? (ring as Record<string, unknown>) : undefined,
143
+ solid ? { backgroundColor: resolveColor(solidFillToken[variant]) } : undefined,
144
+ ring,
118
145
  glowStyle ? (glowStyle as Record<string, unknown>) : undefined,
119
146
  ]}
120
147
  accessibilityRole="image"
@@ -122,17 +149,17 @@ export function StatusDot({
122
149
  testID="status-dot"
123
150
  >
124
151
  {config.showIcon && icon && (
125
- <Text
126
- style={{
127
- fontSize: 11,
128
- lineHeight: 11,
129
- fontWeight: '900',
130
- color: iconColors[variant],
131
- }}
152
+ // The glyph was 11px/900, off the type scale (TOKENS.md §4); `boldLabel` is
153
+ // the 12px sans label step and `font-black` keeps the original weight.
154
+ <Typography
155
+ variant="boldLabel"
156
+ color="inherit"
157
+ className="font-black leading-none"
158
+ style={{ color: resolveColor(glyphToken[variant]) }}
132
159
  accessibilityElementsHidden
133
160
  >
134
161
  {iconChars[icon]}
135
- </Text>
162
+ </Typography>
136
163
  )}
137
164
  </View>
138
165
  )
@@ -146,12 +173,15 @@ export function StatusDot({
146
173
  {...props}
147
174
  >
148
175
  <View accessibilityElementsHidden>{dot}</View>
149
- <Text
150
- style={{ fontSize: 12, lineHeight: 16, color: greyRamp[400] }}
176
+ {/* 12px/16 is `caption` at the 4-unit leading step; its colour is `text-secondary`. */}
177
+ <Typography
178
+ variant="caption"
179
+ color="secondary"
180
+ className="leading-4"
151
181
  accessibilityElementsHidden
152
182
  >
153
183
  {label}
154
- </Text>
184
+ </Typography>
155
185
  </View>
156
186
  )
157
187
  }
@@ -3,7 +3,7 @@ import { View } from 'react-native'
3
3
  import { StatusPill } from './StatusPill'
4
4
 
5
5
  const meta: Meta<typeof StatusPill> = {
6
- title: 'Workout/StatusPill',
6
+ title: 'Custom/Workout/StatusPill',
7
7
  component: StatusPill,
8
8
  tags: ['autodocs'],
9
9
  parameters: {
@@ -2,10 +2,7 @@ import { describe, it, expect } from 'vitest'
2
2
  import { render, screen } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
4
  import { StatusPill, statusPillColor, type StatusPillStatus } from './StatusPill'
5
- import { getSemanticColors } from '../../../theme/tokens/semantic'
6
- import { alpha } from '../../../utils/colors'
7
-
8
- const t = getSemanticColors('dark')
5
+ import { resolveColor } from '../../../theme/resolve-color'
9
6
 
10
7
  const cases: Array<{
11
8
  status: StatusPillStatus
@@ -30,25 +27,19 @@ describe('StatusPill', () => {
30
27
 
31
28
  describe('per-status mapping', () => {
32
29
  cases.forEach(({ status, token, label }) => {
33
- it(`maps ${status} to the ${token} color and default label`, () => {
30
+ it(`renders the default ${status} label`, () => {
34
31
  render(<StatusPill status={status} />)
35
- const text = screen.getByTestId('status-pill-label')
36
- expect(text).toHaveTextContent(label)
37
- expect(text).toHaveStyle({ color: t[token] })
32
+ expect(screen.getByText(label)).toBeInTheDocument()
38
33
  })
39
34
 
40
- it(`tints the ${status} capsule from the ${token} token`, () => {
41
- render(<StatusPill status={status} />)
42
- expect(screen.getByTestId('status-pill')).toHaveStyle({
43
- backgroundColor: alpha(t[token], 0.14),
44
- borderTopColor: alpha(t[token], 0.4),
45
- })
35
+ it(`resolves ${status} to the ${token} token`, () => {
36
+ expect(statusPillColor(status)).toBe(resolveColor(token))
46
37
  })
47
38
  })
48
39
  })
49
40
 
50
41
  it('exposes the resolved color via statusPillColor', () => {
51
- expect(statusPillColor('stop')).toBe(t['status-error'])
42
+ expect(statusPillColor('stop')).toBe(resolveColor('status-error'))
52
43
  })
53
44
 
54
45
  it('renders a custom label override', () => {
@@ -1,11 +1,9 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
- import { View, Text, type ViewProps } from 'react-native'
3
- import { getSemanticColors } from '../../../theme/tokens/semantic'
4
- import { alpha } from '../../../utils/colors'
2
+ import { View, type ViewProps } from 'react-native'
3
+ import { resolveColor } from '../../../theme/resolve-color'
4
+ import { Pill, type PillTone } from '../../ui/pill'
5
5
  import { StatusDot, type StatusDotVariant } from './StatusDot'
6
6
 
7
- const t = getSemanticColors('dark')
8
-
9
7
  /**
10
8
  * Auto-regulation verdict — the human-readable read-once summary of a set's
11
9
  * velocity-loss state.
@@ -29,6 +27,12 @@ const statusToken: Record<StatusPillStatus, 'status-success' | 'status-warning'
29
27
  stop: 'status-error',
30
28
  }
31
29
 
30
+ const statusTone: Record<StatusPillStatus, PillTone> = {
31
+ productive: 'success',
32
+ threshold: 'warning',
33
+ stop: 'error',
34
+ }
35
+
32
36
  const statusDotVariant: Record<StatusPillStatus, StatusDotVariant> = {
33
37
  productive: 'success',
34
38
  threshold: 'warning',
@@ -43,46 +47,31 @@ const defaultLabel: Record<StatusPillStatus, string> = {
43
47
 
44
48
  /** Resolved semantic color for a verdict status. */
45
49
  export function statusPillColor(status: StatusPillStatus): string {
46
- return t[statusToken[status]]
50
+ return resolveColor(statusToken[status])
47
51
  }
48
52
 
49
53
  /**
50
- * Verdict pill: a glowing status dot + colored verdict text, in a tinted,
51
- * bordered capsule. Composes {@link StatusDot} for the dot so the glow/color
52
- * stays consistent with the atom. Tint and border are derived from the same
53
- * semantic token via `alpha()` — no per-status raw hex.
54
+ * Verdict pill: a glowing status dot + coloured verdict text in a tinted
55
+ * capsule. A `Pill` preset tone, tint and border all come from the pill's
56
+ * semantic tone, so no per-status colour is computed here.
54
57
  */
55
- export function StatusPill({ status, label, className, style, ...props }: StatusPillProps) {
56
- const color = statusPillColor(status)
57
- const text = label ?? defaultLabel[status]
58
-
58
+ export function StatusPill({ status, label, className, ...props }: StatusPillProps) {
59
59
  return (
60
- <View
60
+ <Pill
61
61
  testID="status-pill"
62
- className={className}
63
- style={[
64
- {
65
- flexDirection: 'row',
66
- alignItems: 'center',
67
- gap: 7,
68
- alignSelf: 'flex-start',
69
- paddingVertical: 7,
70
- paddingHorizontal: 13,
71
- borderRadius: 9,
72
- backgroundColor: alpha(color, 0.14),
73
- borderWidth: 1,
74
- borderColor: alpha(color, 0.4),
75
- },
76
- style,
77
- ]}
62
+ tone={statusTone[status]}
63
+ size="lg"
64
+ rounded={false}
65
+ className={['gap-2 px-3 py-1.5 rounded-lg', className].filter(Boolean).join(' ')}
66
+ textClassName="font-sans font-extrabold"
67
+ leading={
68
+ <View accessibilityElementsHidden>
69
+ <StatusDot variant={statusDotVariant[status]} glow />
70
+ </View>
71
+ }
78
72
  {...props}
79
73
  >
80
- <View accessibilityElementsHidden>
81
- <StatusDot variant={statusDotVariant[status]} glow />
82
- </View>
83
- <Text testID="status-pill-label" style={{ fontSize: 13, fontWeight: '800', color }}>
84
- {text}
85
- </Text>
86
- </View>
74
+ {label ?? defaultLabel[status]}
75
+ </Pill>
87
76
  )
88
77
  }
@@ -3,6 +3,7 @@ import { useState } from 'react'
3
3
  import { View, Text } from 'react-native'
4
4
  import { StrengthTrendChart } from './StrengthTrendChart'
5
5
  import type { StrengthTrendDataPoint } from './StrengthTrendChart'
6
+ import { greyRamp } from '../../../theme/tokens/primitives'
6
7
 
7
8
  const actual: StrengthTrendDataPoint[] = [
8
9
  { date: '2026-01-06', e1rm: 215, sessionLabel: 'Jan 6' },
@@ -32,12 +33,18 @@ const decline: StrengthTrendDataPoint[] = [
32
33
  ]
33
34
 
34
35
  const meta: Meta<typeof StrengthTrendChart> = {
35
- title: 'Workout/DataViz/StrengthTrendChart',
36
+ title: 'Custom/Workout/DataViz/StrengthTrendChart',
36
37
  component: StrengthTrendChart,
37
38
  tags: ['autodocs'],
38
39
  argTypes: {
39
- width: { control: { type: 'range', min: 160, max: 480, step: 10 }, description: 'Plot width in px' },
40
- height: { control: { type: 'range', min: 100, max: 220, step: 10 }, description: 'Plot height in px (120-160 for compact)' },
40
+ width: {
41
+ control: { type: 'range', min: 160, max: 480, step: 10 },
42
+ description: 'Plot width in px',
43
+ },
44
+ height: {
45
+ control: { type: 'range', min: 100, max: 220, step: 10 },
46
+ description: 'Plot height in px (120-160 for compact)',
47
+ },
41
48
  unit: { control: 'inline-radio', options: ['lbs', 'kg'], description: 'Display unit' },
42
49
  animateOnMount: { control: 'boolean', description: 'Left-to-right draw animation on mount' },
43
50
  },
@@ -116,7 +123,7 @@ function InteractiveChart() {
116
123
  unit="lbs"
117
124
  onPointPress={setSelected}
118
125
  />
119
- <Text style={{ marginTop: 12, fontSize: 12, color: '#9CA3AF' }}>
126
+ <Text style={{ marginTop: 12, fontSize: 12, color: greyRamp[400] }}>
120
127
  {selected
121
128
  ? `Tapped ${selected.sessionLabel ?? selected.date}: ${selected.e1rm} lbs`
122
129
  : 'Tap a point on the chart.'}
@@ -44,9 +44,7 @@ describe('StrengthTrendChart', () => {
44
44
  it('renders the actual line as connecting segments', () => {
45
45
  render(<StrengthTrendChart {...baseProps} />)
46
46
  // Two segments connect three points.
47
- expect(
48
- screen.getAllByTestId('strength-trend-chart-actual-segment'),
49
- ).toHaveLength(2)
47
+ expect(screen.getAllByTestId('strength-trend-chart-actual-segment')).toHaveLength(2)
50
48
  })
51
49
 
52
50
  it('renders subtle horizontal gridlines', () => {
@@ -73,26 +71,20 @@ describe('StrengthTrendChart', () => {
73
71
  describe('projection and meso boundaries', () => {
74
72
  it('renders dashed projection segments when projection is provided', () => {
75
73
  render(<StrengthTrendChart {...baseProps} projection={projection} />)
76
- expect(
77
- screen.getAllByTestId('strength-trend-chart-projection-segment'),
78
- ).toHaveLength(1)
74
+ expect(screen.getAllByTestId('strength-trend-chart-projection-segment')).toHaveLength(1)
79
75
  })
80
76
 
81
77
  it('omits projection segments when none provided', () => {
82
78
  render(<StrengthTrendChart {...baseProps} />)
83
79
  expect(
84
- screen.queryByTestId('strength-trend-chart-projection-segment'),
80
+ screen.queryByTestId('strength-trend-chart-projection-segment')
85
81
  ).not.toBeInTheDocument()
86
82
  })
87
83
 
88
84
  it('renders a boundary marker and axis label per meso boundary', () => {
89
85
  render(<StrengthTrendChart {...baseProps} mesoBoundaries={mesoBoundaries} />)
90
- expect(
91
- screen.getAllByTestId('strength-trend-chart-meso-boundary'),
92
- ).toHaveLength(2)
93
- expect(
94
- screen.getAllByTestId('strength-trend-chart-axis-label'),
95
- ).toHaveLength(2)
86
+ expect(screen.getAllByTestId('strength-trend-chart-meso-boundary')).toHaveLength(2)
87
+ expect(screen.getAllByTestId('strength-trend-chart-axis-label')).toHaveLength(2)
96
88
  })
97
89
  })
98
90
 
@@ -112,7 +104,7 @@ describe('StrengthTrendChart', () => {
112
104
  { date: '2026-03-03', e1rm: 248 },
113
105
  { date: '2026-03-17', e1rm: 235 },
114
106
  ]}
115
- />,
107
+ />
116
108
  )
117
109
  const pill = screen.getByTestId('strength-trend-chart-trend-pill')
118
110
  expect(pill.textContent?.startsWith('-')).toBe(true)
@@ -128,7 +120,7 @@ describe('StrengthTrendChart', () => {
128
120
  { date: '2026-03-20', e1rm: 250 },
129
121
  ]}
130
122
  mesoBoundaries={[{ date: '2026-03-01', label: 'Peak' }]}
131
- />,
123
+ />
132
124
  )
133
125
  // Only one point falls in the current meso, so the pill must not report
134
126
  // the whole-series jump (+25%) under the "this meso" label.
@@ -142,16 +134,12 @@ describe('StrengthTrendChart', () => {
142
134
  const onPointPress = vi.fn()
143
135
  render(<StrengthTrendChart {...baseProps} onPointPress={onPointPress} />)
144
136
  fireEvent.click(screen.getAllByTestId('strength-trend-chart-point')[1])
145
- expect(onPointPress).toHaveBeenCalledWith(
146
- expect.objectContaining({ e1rm: 228 }),
147
- )
137
+ expect(onPointPress).toHaveBeenCalledWith(expect.objectContaining({ e1rm: 228 }))
148
138
  })
149
139
 
150
140
  it('shows a tooltip with value and unit after a point is tapped', () => {
151
141
  render(<StrengthTrendChart {...baseProps} />)
152
- expect(
153
- screen.queryByTestId('strength-trend-chart-tooltip'),
154
- ).not.toBeInTheDocument()
142
+ expect(screen.queryByTestId('strength-trend-chart-tooltip')).not.toBeInTheDocument()
155
143
  fireEvent.click(screen.getAllByTestId('strength-trend-chart-point')[2])
156
144
  const tooltip = screen.getByTestId('strength-trend-chart-tooltip')
157
145
  expect(tooltip).toHaveTextContent('244 lbs')
@@ -161,22 +149,14 @@ describe('StrengthTrendChart', () => {
161
149
  describe('empty state', () => {
162
150
  it('renders an empty placeholder when there is no data', () => {
163
151
  render(<StrengthTrendChart {...baseProps} data={[]} />)
164
- expect(
165
- screen.getByTestId('strength-trend-chart-empty'),
166
- ).toBeInTheDocument()
167
- expect(
168
- screen.queryByTestId('strength-trend-chart-canvas'),
169
- ).not.toBeInTheDocument()
152
+ expect(screen.getByTestId('strength-trend-chart-empty')).toBeInTheDocument()
153
+ expect(screen.queryByTestId('strength-trend-chart-canvas')).not.toBeInTheDocument()
170
154
  })
171
155
 
172
156
  it('shows the placeholder when data is empty even if a projection exists', () => {
173
157
  render(<StrengthTrendChart {...baseProps} data={[]} projection={projection} />)
174
- expect(
175
- screen.getByTestId('strength-trend-chart-empty'),
176
- ).toBeInTheDocument()
177
- expect(
178
- screen.queryByTestId('strength-trend-chart-canvas'),
179
- ).not.toBeInTheDocument()
158
+ expect(screen.getByTestId('strength-trend-chart-empty')).toBeInTheDocument()
159
+ expect(screen.queryByTestId('strength-trend-chart-canvas')).not.toBeInTheDocument()
180
160
  })
181
161
  })
182
162
 
@@ -191,9 +171,7 @@ describe('StrengthTrendChart', () => {
191
171
 
192
172
  it('labels the empty state accessibly', () => {
193
173
  render(<StrengthTrendChart {...baseProps} data={[]} />)
194
- expect(
195
- screen.getByLabelText('Strength trend chart, no data'),
196
- ).toBeInTheDocument()
174
+ expect(screen.getByLabelText('Strength trend chart, no data')).toBeInTheDocument()
197
175
  })
198
176
 
199
177
  it('has no accessibility violations', async () => {
@@ -209,7 +187,7 @@ describe('StrengthTrendChart', () => {
209
187
  mesoBoundaries={mesoBoundaries}
210
188
  onPointPress={vi.fn()}
211
189
  unit="kg"
212
- />,
190
+ />
213
191
  )
214
192
  expect(await axe(container)).toHaveNoViolations()
215
193
  })