@titan-design/react-ui 0.12.1 → 0.13.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 (509) hide show
  1. package/README.md +46 -47
  2. package/dist/bodymap.js +768 -701
  3. package/dist/bodymap.js.map +1 -1
  4. package/dist/bodymap.mjs +770 -703
  5. package/dist/bodymap.mjs.map +1 -1
  6. package/dist/index.d.mts +1319 -216
  7. package/dist/index.d.ts +1319 -216
  8. package/dist/index.js +5996 -4498
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +5773 -4357
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{pages-CvsFqNtx.d.mts → pages-CpW3OB3g.d.mts} +21 -3
  13. package/dist/{pages-B1hqnGIC.d.ts → pages-_3Ajpiqu.d.ts} +21 -3
  14. package/dist/pages.d.mts +1 -1
  15. package/dist/pages.d.ts +1 -1
  16. package/dist/pages.js +1435 -1428
  17. package/dist/pages.js.map +1 -1
  18. package/dist/pages.mjs +1443 -1436
  19. package/dist/pages.mjs.map +1 -1
  20. package/dist/theme/index.d.mts +195 -134
  21. package/dist/theme/index.d.ts +195 -134
  22. package/dist/theme/index.js +215 -300
  23. package/dist/theme/index.js.map +1 -1
  24. package/dist/theme/index.mjs +202 -296
  25. package/dist/theme/index.mjs.map +1 -1
  26. package/dist/theme/tokens-css.js +62 -5
  27. package/dist/theme/tokens-css.js.map +1 -1
  28. package/dist/theme/tokens-css.mjs +62 -5
  29. package/dist/theme/tokens-css.mjs.map +1 -1
  30. package/dist/theme/tokens.d.mts +73 -5
  31. package/dist/theme/tokens.d.ts +73 -5
  32. package/dist/theme/tokens.js +55 -7
  33. package/dist/theme/tokens.js.map +1 -1
  34. package/dist/theme/tokens.mjs +55 -8
  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/package.json +1 -1
  40. package/src/arch/arch-graph.freshness.test.ts +52 -0
  41. package/src/arch/arch-graph.json +5021 -1617
  42. package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
  43. package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
  44. package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
  45. package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
  46. package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
  47. package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
  48. package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
  49. package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
  50. package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
  51. package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
  52. package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
  53. package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
  54. package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
  55. package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
  56. package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
  57. package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
  58. package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
  59. package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
  60. package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
  61. package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
  62. package/src/components/custom/ActiveWork/InitiativeBrief.tsx +181 -0
  63. package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
  64. package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
  65. package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
  66. package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
  67. package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
  68. package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
  69. package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
  70. package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
  71. package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
  72. package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
  73. package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
  74. package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
  75. package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
  76. package/src/components/custom/ActiveWork/README.md +305 -0
  77. package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
  78. package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
  79. package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
  80. package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
  81. package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
  82. package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
  83. package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
  84. package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
  85. package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
  86. package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
  87. package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
  88. package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
  89. package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
  90. package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
  91. package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
  92. package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
  93. package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
  94. package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
  95. package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
  96. package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
  97. package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
  98. package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
  99. package/src/components/custom/ActiveWork/format-time.ts +32 -0
  100. package/src/components/custom/ActiveWork/index.ts +19 -0
  101. package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
  102. package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
  103. package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
  104. package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
  105. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +4 -3
  106. package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
  107. package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
  108. package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
  109. package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
  110. package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
  111. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
  112. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  113. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
  114. package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
  115. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
  116. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
  117. package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
  118. package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
  119. package/src/components/custom/Fatigue/README.md +40 -3
  120. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  121. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
  122. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
  123. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  124. package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
  125. package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
  126. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
  127. package/src/components/custom/Fatigue/index.ts +17 -5
  128. package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
  129. package/src/components/custom/Fatigue/panel-layout.ts +185 -0
  130. package/src/components/custom/Gauge/Gauge.stories.tsx +2 -2
  131. package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
  132. package/src/components/custom/Gauge/Gauge.tsx +4 -1
  133. package/src/components/custom/Metric/Metric.stories.tsx +10 -1
  134. package/src/components/custom/Metric/Metric.test.tsx +1 -3
  135. package/src/components/custom/Metric/Metric.tsx +5 -20
  136. package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
  137. package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
  138. package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
  139. package/src/components/custom/Prose/README.md +58 -0
  140. package/src/components/custom/Prose/index.ts +1 -0
  141. package/src/components/custom/Scatter/Scatter.stories.tsx +46 -8
  142. package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
  143. package/src/components/custom/Scatter/Scatter.tsx +116 -14
  144. package/src/components/custom/Sidebar/Sidebar.stories.tsx +40 -7
  145. package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
  146. package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
  147. package/src/components/custom/Table/Table.stories.tsx +98 -15
  148. package/src/components/custom/Table/Table.test.tsx +273 -86
  149. package/src/components/custom/Table/Table.tsx +159 -67
  150. package/src/components/custom/Table/column-fit.test.ts +75 -0
  151. package/src/components/custom/Table/column-fit.ts +116 -0
  152. package/src/components/custom/Table/index.ts +9 -0
  153. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
  154. package/src/components/custom/Treemap/Treemap.stories.tsx +2 -2
  155. package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
  156. package/src/components/custom/Treemap/Treemap.tsx +7 -2
  157. package/src/components/custom/Typography/Typography.stories.tsx +28 -1
  158. package/src/components/custom/Typography/Typography.test.tsx +34 -0
  159. package/src/components/custom/Typography/Typography.tsx +6 -0
  160. package/src/components/custom/Typography/index.ts +1 -8
  161. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +9 -1
  162. package/src/components/custom/Workout/BaseBadge.stories.tsx +19 -7
  163. package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
  164. package/src/components/custom/Workout/BaseBadge.tsx +3 -0
  165. package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
  166. package/src/components/custom/Workout/BodyMap.tsx +3 -1
  167. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +16 -4
  168. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
  169. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +45 -9
  170. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +10 -8
  171. package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
  172. package/src/components/custom/Workout/CapacityBandChart.tsx +139 -108
  173. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  174. package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
  175. package/src/components/custom/Workout/DeviationBar.tsx +21 -16
  176. package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +182 -0
  177. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +3 -2
  178. package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -7
  179. package/src/components/custom/Workout/ExerciseCard.test.tsx +13 -3
  180. package/src/components/custom/Workout/ExerciseCard.tsx +42 -51
  181. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +7 -7
  182. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
  183. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
  184. package/src/components/custom/Workout/ExerciseHeading.tsx +8 -5
  185. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -4
  186. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
  187. package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
  188. package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
  189. package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
  190. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  191. package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
  192. package/src/components/custom/Workout/IntensityBar.tsx +4 -2
  193. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
  194. package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
  195. package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
  196. package/src/components/custom/Workout/MesoCard.tsx +12 -9
  197. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  198. package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
  199. package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
  200. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
  201. package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
  202. package/src/components/custom/Workout/MesoStatusCard.tsx +9 -23
  203. package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
  204. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
  205. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
  206. package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
  207. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  208. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
  209. package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
  210. package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
  211. package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
  212. package/src/components/custom/Workout/PrBadge.tsx +13 -17
  213. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +4 -1
  214. package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
  215. package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
  216. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
  217. package/src/components/custom/Workout/README.md +11 -6
  218. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
  219. package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
  220. package/src/components/custom/Workout/ReadinessCheck.tsx +19 -3
  221. package/src/components/custom/Workout/RestTimer.stories.tsx +3 -2
  222. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
  223. package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
  224. package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
  225. package/src/components/custom/Workout/SegmentedBar.stories.tsx +5 -5
  226. package/src/components/custom/Workout/SegmentedBar.tsx +6 -2
  227. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
  228. package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
  229. package/src/components/custom/Workout/SessionHeader.tsx +12 -12
  230. package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
  231. package/src/components/custom/Workout/SessionRail.tsx +7 -9
  232. package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
  233. package/src/components/custom/Workout/SetBar.test.tsx +49 -4
  234. package/src/components/custom/Workout/SetBar.tsx +38 -12
  235. package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
  236. package/src/components/custom/Workout/SetRow.test.tsx +16 -2
  237. package/src/components/custom/Workout/SetRow.tsx +61 -23
  238. package/src/components/custom/Workout/SetStrip.stories.tsx +60 -5
  239. package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
  240. package/src/components/custom/Workout/SetStrip.tsx +24 -4
  241. package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
  242. package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
  243. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
  244. package/src/components/custom/Workout/SetsRepsLoad.tsx +3 -5
  245. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  246. package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
  247. package/src/components/custom/Workout/Sparkline.tsx +14 -18
  248. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  249. package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
  250. package/src/components/custom/Workout/StatusDot.tsx +93 -63
  251. package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
  252. package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
  253. package/src/components/custom/Workout/StatusPill.tsx +27 -38
  254. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +9 -3
  255. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
  256. package/src/components/custom/Workout/StrengthTrendChart.tsx +31 -30
  257. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
  258. package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
  259. package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
  260. package/src/components/custom/Workout/TempoDisplay.stories.tsx +22 -7
  261. package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
  262. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
  263. package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
  264. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +7 -4
  265. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +4 -1
  266. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +4 -1
  267. package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
  268. package/src/components/custom/Workout/VelocityStrip.tsx +3 -0
  269. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
  270. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
  271. package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
  272. package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
  273. package/src/components/custom/Workout/WeekRow.tsx +23 -16
  274. package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
  275. package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
  276. package/src/components/custom/Workout/WeightBadge.tsx +28 -27
  277. package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
  278. package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
  279. package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
  280. package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
  281. package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
  282. package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
  283. package/src/components/custom/Workout/ZoneTrack.stories.tsx +7 -6
  284. package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
  285. package/src/components/custom/Workout/index.ts +5 -0
  286. package/src/components/custom/Workout/metricText.stories.tsx +6 -5
  287. package/src/components/custom/Workout/metricText.tsx +4 -0
  288. package/src/components/custom/Workout/setHeadingKit.tsx +207 -26
  289. package/src/components/custom/charts/README.md +61 -0
  290. package/src/components/custom/charts/SetBarChart.test.tsx +30 -1
  291. package/src/components/custom/charts/SetBarChart.tsx +42 -4
  292. package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
  293. package/src/components/custom/charts/SparkBars.test.tsx +69 -0
  294. package/src/components/custom/charts/SparkBars.tsx +85 -0
  295. package/src/components/custom/charts/index.ts +3 -0
  296. package/src/components/custom/index.ts +3 -0
  297. package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
  298. package/src/components/custom/stepper/Stepper.test.tsx +1 -7
  299. package/src/components/custom/stepper/Stepper.tsx +13 -28
  300. package/src/components/icons/icons.stories.tsx +1 -1
  301. package/src/components/icons/icons.tsx +51 -0
  302. package/src/components/shell/AppShell.stories.tsx +150 -0
  303. package/src/components/shell/AppShell.test.tsx +70 -0
  304. package/src/components/shell/AppShell.tsx +90 -0
  305. package/src/components/shell/BrandLockup.stories.tsx +66 -5
  306. package/src/components/shell/BrandLockup.test.tsx +34 -1
  307. package/src/components/shell/BrandLockup.tsx +27 -11
  308. package/src/components/shell/NavItem.stories.tsx +4 -4
  309. package/src/components/shell/NavItem.test.tsx +17 -0
  310. package/src/components/shell/NavItem.tsx +17 -5
  311. package/src/components/shell/README.md +89 -26
  312. package/src/components/shell/SideNav.stories.tsx +38 -7
  313. package/src/components/shell/SideNav.test.tsx +16 -8
  314. package/src/components/shell/SideNav.tsx +17 -17
  315. package/src/components/shell/TopBar.stories.tsx +44 -19
  316. package/src/components/shell/TopBar.test.tsx +34 -12
  317. package/src/components/shell/TopBar.tsx +61 -40
  318. package/src/components/shell/brands.tsx +66 -0
  319. package/src/components/shell/index.ts +9 -8
  320. package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
  321. package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
  322. package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
  323. package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
  324. package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
  325. package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
  326. package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
  327. package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
  328. package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
  329. package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
  330. package/src/components/shell/workout/WorkoutShell.tsx +77 -0
  331. package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
  332. package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
  333. package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
  334. package/src/components/shell/workout/index.ts +9 -0
  335. package/src/components/shell/workout/workoutNavItems.tsx +16 -0
  336. package/src/components/ui/README.md +110 -0
  337. package/src/components/ui/alert/Alert.stories.tsx +1 -1
  338. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
  339. package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
  340. package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
  341. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  342. package/src/components/ui/avatar/Avatar.tsx +2 -10
  343. package/src/components/ui/badge/Badge.stories.tsx +83 -28
  344. package/src/components/ui/badge/Badge.test.tsx +19 -3
  345. package/src/components/ui/badge/Badge.tsx +48 -55
  346. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  347. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
  348. package/src/components/ui/button/Button.stories.tsx +46 -12
  349. package/src/components/ui/button/Button.tsx +32 -25
  350. package/src/components/ui/card/Card.stories.tsx +90 -94
  351. package/src/components/ui/card/Card.test.tsx +80 -6
  352. package/src/components/ui/card/Card.tsx +150 -203
  353. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  354. package/src/components/ui/checkbox/Checkbox.tsx +6 -26
  355. package/src/components/ui/chip/Chip.stories.tsx +37 -13
  356. package/src/components/ui/chip/Chip.test.tsx +10 -6
  357. package/src/components/ui/chip/Chip.tsx +76 -91
  358. package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
  359. package/src/components/ui/collapse/Collapse.tsx +6 -11
  360. package/src/components/ui/data-row/DataRow.stories.tsx +9 -2
  361. package/src/components/ui/data-row/DataRow.test.tsx +5 -15
  362. package/src/components/ui/data-row/DataRow.tsx +18 -7
  363. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  364. package/src/components/ui/divider/Divider.tsx +1 -5
  365. package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
  366. package/src/components/ui/drawer/Drawer.tsx +13 -30
  367. package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
  368. package/src/components/ui/form-field/FormField.stories.tsx +6 -20
  369. package/src/components/ui/form-field/FormField.test.tsx +1 -5
  370. package/src/components/ui/form-field/FormField.tsx +10 -46
  371. package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
  372. package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
  373. package/src/components/ui/help-tip/HelpTip.tsx +21 -22
  374. package/src/components/ui/help-tip/index.ts +7 -1
  375. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  376. package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
  377. package/src/components/ui/icon-box/IconBox.tsx +2 -12
  378. package/src/components/ui/indicator/Indicator.stories.tsx +1 -1
  379. package/src/components/ui/indicator/Indicator.tsx +20 -14
  380. package/src/components/ui/input/Input.stories.tsx +3 -11
  381. package/src/components/ui/input/Input.test.tsx +2 -10
  382. package/src/components/ui/input/Input.tsx +10 -25
  383. package/src/components/ui/input/index.ts +7 -1
  384. package/src/components/ui/link/Link.stories.tsx +19 -7
  385. package/src/components/ui/list-item/ListItem.stories.tsx +11 -2
  386. package/src/components/ui/list-item/ListItem.tsx +2 -9
  387. package/src/components/ui/menu/Menu.stories.tsx +21 -15
  388. package/src/components/ui/menu/Menu.test.tsx +2 -11
  389. package/src/components/ui/menu/Menu.tsx +12 -14
  390. package/src/components/ui/menu/index.ts +8 -1
  391. package/src/components/ui/modal/Modal.stories.tsx +2 -2
  392. package/src/components/ui/modal/Modal.tsx +29 -19
  393. package/src/components/ui/pill/Pill.stories.tsx +58 -23
  394. package/src/components/ui/pill/Pill.test.tsx +101 -3
  395. package/src/components/ui/pill/Pill.tsx +164 -49
  396. package/src/components/ui/pill/index.ts +1 -1
  397. package/src/components/ui/popover/Popover.stories.tsx +5 -7
  398. package/src/components/ui/popover/Popover.test.tsx +1 -6
  399. package/src/components/ui/popover/Popover.tsx +20 -15
  400. package/src/components/ui/popover/index.ts +7 -1
  401. package/src/components/ui/progress/Progress.stories.tsx +3 -12
  402. package/src/components/ui/progress/Progress.test.tsx +1 -5
  403. package/src/components/ui/radio/Radio.stories.tsx +42 -21
  404. package/src/components/ui/radio/Radio.test.tsx +6 -2
  405. package/src/components/ui/radio/Radio.tsx +23 -22
  406. package/src/components/ui/section/Section.stories.tsx +5 -14
  407. package/src/components/ui/section/Section.test.tsx +1 -6
  408. package/src/components/ui/section/Section.tsx +1 -3
  409. package/src/components/ui/section/index.ts +1 -5
  410. package/src/components/ui/select/Select.stories.tsx +3 -13
  411. package/src/components/ui/select/Select.test.tsx +2 -11
  412. package/src/components/ui/select/Select.tsx +14 -14
  413. package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
  414. package/src/components/ui/skeleton/Skeleton.tsx +3 -14
  415. package/src/components/ui/skeleton/index.ts +1 -7
  416. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  417. package/src/components/ui/spinner/Spinner.tsx +1 -4
  418. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  419. package/src/components/ui/stack/Stack.tsx +1 -1
  420. package/src/components/ui/surface/Surface.stories.tsx +17 -5
  421. package/src/components/ui/surface/Surface.test.tsx +88 -28
  422. package/src/components/ui/surface/Surface.tsx +37 -87
  423. package/src/components/ui/surface/SurfaceContext.ts +22 -62
  424. package/src/components/ui/surface/index.ts +8 -0
  425. package/src/components/ui/surface/resolveSurface.ts +98 -0
  426. package/src/components/ui/surface/surface.contract.test.ts +6 -1
  427. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  428. package/src/components/ui/switch/Switch.tsx +7 -6
  429. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  430. package/src/components/ui/tabs/Tabs.tsx +7 -22
  431. package/src/components/ui/tabs/index.ts +1 -7
  432. package/src/components/ui/tile/Tile.stories.tsx +1 -1
  433. package/src/components/ui/tile/Tile.tsx +3 -0
  434. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  435. package/src/components/ui/toast/Toast.test.tsx +2 -8
  436. package/src/components/ui/toast/Toast.tsx +43 -53
  437. package/src/components/ui/toast/index.ts +0 -1
  438. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
  439. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
  440. package/src/components/ui/toolbar-button/ToolbarButton.tsx +25 -43
  441. package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
  442. package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
  443. package/src/components/ui/tooltip/Tooltip.tsx +55 -36
  444. package/src/components/ui/tooltip/index.ts +1 -1
  445. package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
  446. package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
  447. package/src/components/ui/trigger/index.ts +2 -0
  448. package/src/stories/PresetShowcase.stories.tsx +100 -47
  449. package/src/test/no-device-internals.test.ts +3 -3
  450. package/src/theme/ChoosingTokens.stories.tsx +48 -0
  451. package/src/theme/ColorPalettes.stories.tsx +62 -45
  452. package/src/theme/ColorPrimitives.stories.tsx +35 -35
  453. package/src/theme/Depth.stories.tsx +132 -65
  454. package/src/theme/DepthCalibration.stories.tsx +23 -21
  455. package/src/theme/ThemeProvider.test.tsx +4 -4
  456. package/src/theme/barrel.ts +7 -6
  457. package/src/theme/color-stories.coverage.test.ts +1 -1
  458. package/src/theme/color-utils.ts +41 -29
  459. package/src/theme/config.completeness.test.ts +1 -1
  460. package/src/theme/config.ts +28 -0
  461. package/src/theme/elevation.test.ts +82 -56
  462. package/src/theme/elevation.ts +77 -561
  463. package/src/theme/global.css +30 -2
  464. package/src/theme/grey-ramp.test.ts +24 -8
  465. package/src/theme/lift.test.ts +39 -0
  466. package/src/theme/lift.ts +102 -0
  467. package/src/theme/materials.test.ts +5 -2
  468. package/src/theme/materials.ts +38 -4
  469. package/src/theme/native-theming-guard.test.ts +5 -5
  470. package/src/theme/presets/apply.ts +4 -0
  471. package/src/theme/presets/audiobook.ts +5 -2
  472. package/src/theme/presets/default.ts +4 -0
  473. package/src/theme/presets/index.ts +14 -1
  474. package/src/theme/presets/types.ts +1 -1
  475. package/src/theme/semantic-contrast.test.ts +13 -4
  476. package/src/theme/status-distinctness.test.ts +66 -0
  477. package/src/theme/surface-planes.test.ts +37 -0
  478. package/src/theme/surface-planes.ts +68 -0
  479. package/src/theme/token-export-guard.test.ts +1 -3
  480. package/src/theme/tokens/primitives.test.ts +15 -7
  481. package/src/theme/tokens/primitives.ts +65 -33
  482. package/src/theme/tokens/semantic.ts +71 -37
  483. package/src/theme/wash-ladder.test.ts +58 -0
  484. package/src/theme/workout-tokens.ts +1 -7
  485. package/src/types/jest-axe.d.ts +2 -6
  486. package/src/types/raw-md.d.ts +5 -0
  487. package/src/utils/colors.ts +4 -4
  488. package/src/utils/form.ts +1 -3
  489. package/src/utils/index.ts +2 -7
  490. package/src/utils/number-format.ts +17 -0
  491. package/src/utils/workout-format.ts +15 -6
  492. package/src/web-jsx/jsx-runtime.ts +1 -5
  493. package/tailwind.config.js +5 -0
  494. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
  495. package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
  496. package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
  497. package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
  498. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
  499. package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
  500. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
  501. package/src/components/shell/DashboardShell.stories.tsx +0 -65
  502. package/src/components/shell/DashboardShell.tsx +0 -75
  503. package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
  504. package/src/theme/elevation.stories.tsx +0 -346
  505. package/src/utils/useTheme.ts +0 -72
  506. /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
  507. /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
  508. /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
  509. /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
@@ -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
  }
@@ -32,12 +32,18 @@ const decline: StrengthTrendDataPoint[] = [
32
32
  ]
33
33
 
34
34
  const meta: Meta<typeof StrengthTrendChart> = {
35
- title: 'Workout/DataViz/StrengthTrendChart',
35
+ title: 'Custom/Workout/DataViz/StrengthTrendChart',
36
36
  component: StrengthTrendChart,
37
37
  tags: ['autodocs'],
38
38
  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)' },
39
+ width: {
40
+ control: { type: 'range', min: 160, max: 480, step: 10 },
41
+ description: 'Plot width in px',
42
+ },
43
+ height: {
44
+ control: { type: 'range', min: 100, max: 220, step: 10 },
45
+ description: 'Plot height in px (120-160 for compact)',
46
+ },
41
47
  unit: { control: 'inline-radio', options: ['lbs', 'kg'], description: 'Display unit' },
42
48
  animateOnMount: { control: 'boolean', description: 'Left-to-right draw animation on mount' },
43
49
  },
@@ -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
  })
@@ -94,7 +94,7 @@ function buildGeometry(
94
94
  projection: StrengthTrendDataPoint[],
95
95
  boundaries: StrengthTrendChartMesoBoundary[],
96
96
  width: number,
97
- height: number,
97
+ height: number
98
98
  ): Geometry {
99
99
  const values = [...data, ...projection].map((p) => p.e1rm)
100
100
  // Empty state keys off actual data only: a projection with no measured
@@ -128,10 +128,8 @@ function buildGeometry(
128
128
  const domainMax = yMax + padV
129
129
 
130
130
  const innerW = Math.max(1, width - PLOT_LEFT)
131
- const toX = (time: number) =>
132
- PLOT_LEFT + ((time - tMin) / (tMax - tMin)) * innerW
133
- const toY = (value: number) =>
134
- height - ((value - domainMin) / (domainMax - domainMin)) * height
131
+ const toX = (time: number) => PLOT_LEFT + ((time - tMin) / (tMax - tMin)) * innerW
132
+ const toY = (value: number) => height - ((value - domainMin) / (domainMax - domainMin)) * height
135
133
 
136
134
  const project = (points: StrengthTrendDataPoint[]): Coord[] =>
137
135
  points.map((point, index) => ({
@@ -141,9 +139,7 @@ function buildGeometry(
141
139
  index,
142
140
  }))
143
141
 
144
- const tickValues = Array.from(
145
- new Set([yMax, (yMin + yMax) / 2, yMin].map((v) => Math.round(v))),
146
- )
142
+ const tickValues = Array.from(new Set([yMax, (yMin + yMax) / 2, yMin].map((v) => Math.round(v))))
147
143
  const gridLines = tickValues.map((v) => ({ y: toY(v), label: formatValue(v) }))
148
144
 
149
145
  return {
@@ -162,7 +158,7 @@ function buildGeometry(
162
158
 
163
159
  function computeTrend(
164
160
  data: StrengthTrendDataPoint[],
165
- boundaries: StrengthTrendChartMesoBoundary[],
161
+ boundaries: StrengthTrendChartMesoBoundary[]
166
162
  ): { percent: number; positive: boolean } {
167
163
  if (data.length < 2) return { percent: 0, positive: true }
168
164
  let series = data
@@ -267,12 +263,9 @@ export function StrengthTrendChart({
267
263
  }: StrengthTrendChartProps) {
268
264
  const geometry = useMemo(
269
265
  () => buildGeometry(data, projection, mesoBoundaries, width, height),
270
- [data, projection, mesoBoundaries, width, height],
271
- )
272
- const trend = useMemo(
273
- () => computeTrend(data, mesoBoundaries),
274
- [data, mesoBoundaries],
266
+ [data, projection, mesoBoundaries, width, height]
275
267
  )
268
+ const trend = useMemo(() => computeTrend(data, mesoBoundaries), [data, mesoBoundaries])
276
269
 
277
270
  const [reveal] = useState(() => new Animated.Value(animateOnMount ? 0 : 1))
278
271
  const [selected, setSelected] = useState<number | null>(null)
@@ -314,7 +307,10 @@ export function StrengthTrendChart({
314
307
  justifyContent: 'center',
315
308
  }}
316
309
  >
317
- <Text className="text-text-tertiary" style={{ fontSize: 12, fontFamily: 'Inter, sans-serif' }}>
310
+ <Text
311
+ className="text-text-tertiary"
312
+ style={{ fontSize: 12, fontFamily: 'Inter, sans-serif' }}
313
+ >
318
314
  No strength data yet
319
315
  </Text>
320
316
  </View>
@@ -327,16 +323,10 @@ export function StrengthTrendChart({
327
323
  outputRange: [0, width],
328
324
  })
329
325
 
330
- const selectedCoord =
331
- selected != null ? geometry.actual[selected] : undefined
326
+ const selectedCoord = selected != null ? geometry.actual[selected] : undefined
332
327
 
333
328
  return (
334
- <View
335
- style={{ width }}
336
- className={cn(className)}
337
- testID="strength-trend-chart"
338
- {...props}
339
- >
329
+ <View style={{ width }} className={cn(className)} testID="strength-trend-chart" {...props}>
340
330
  <View style={{ width, height, position: 'relative' }}>
341
331
  {/* Labelled, non-interactive chart canvas (grid + lines + markers). */}
342
332
  <View
@@ -438,7 +428,7 @@ export function StrengthTrendChart({
438
428
  >
439
429
 
440
430
  </Text>
441
- ) : null,
431
+ ) : null
442
432
  )}
443
433
  </View>
444
434
 
@@ -482,7 +472,7 @@ export function StrengthTrendChart({
482
472
  position: 'absolute',
483
473
  left: Math.max(
484
474
  0,
485
- Math.min(width - TOOLTIP_WIDTH, selectedCoord.x - TOOLTIP_WIDTH / 2),
475
+ Math.min(width - TOOLTIP_WIDTH, selectedCoord.x - TOOLTIP_WIDTH / 2)
486
476
  ),
487
477
  top: Math.max(0, selectedCoord.y - 52),
488
478
  maxWidth: TOOLTIP_WIDTH,
@@ -529,7 +519,7 @@ export function StrengthTrendChart({
529
519
  ? '+'
530
520
  : '-'
531
521
  }${Math.abs(
532
- selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm,
522
+ selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm
533
523
  ).toFixed(1)} ${unit}`}
534
524
  </Text>
535
525
  )}
@@ -598,8 +588,13 @@ export function StrengthTrendChart({
598
588
  testID="strength-trend-chart-legend"
599
589
  >
600
590
  <View className="flex-row items-center" style={{ gap: 5 }}>
601
- <View style={{ width: 14, height: 2.5, borderRadius: 1.5, backgroundColor: BRAND_PRIMARY }} />
602
- <Text className="text-text-secondary" style={{ fontSize: 10, fontFamily: 'Inter, sans-serif' }}>
591
+ <View
592
+ style={{ width: 14, height: 2.5, borderRadius: 1.5, backgroundColor: BRAND_PRIMARY }}
593
+ />
594
+ <Text
595
+ className="text-text-secondary"
596
+ style={{ fontSize: 10, fontFamily: 'Inter, sans-serif' }}
597
+ >
603
598
  Actual
604
599
  </Text>
605
600
  </View>
@@ -613,13 +608,19 @@ export function StrengthTrendChart({
613
608
  borderTopColor: PROJECTION_LINE_COLOR,
614
609
  }}
615
610
  />
616
- <Text className="text-text-secondary" style={{ fontSize: 10, fontFamily: 'Inter, sans-serif' }}>
611
+ <Text
612
+ className="text-text-secondary"
613
+ style={{ fontSize: 10, fontFamily: 'Inter, sans-serif' }}
614
+ >
617
615
  Projected
618
616
  </Text>
619
617
  </View>
620
618
  <View className="flex-row items-center" style={{ gap: 5 }}>
621
619
  <Text style={{ fontSize: 12, color: STATUS_WARNING }}>★</Text>
622
- <Text className="text-text-secondary" style={{ fontSize: 10, fontFamily: 'Inter, sans-serif' }}>
620
+ <Text
621
+ className="text-text-secondary"
622
+ style={{ fontSize: 10, fontFamily: 'Inter, sans-serif' }}
623
+ >
623
624
  PR
624
625
  </Text>
625
626
  </View>