@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,7 @@
2
2
  import { useEffect, useState, useMemo } from 'react'
3
3
  import { View, Text, Pressable, Animated, Easing, type ViewProps } from 'react-native'
4
4
  import { getSemanticColors } from '../../../theme/tokens/semantic'
5
+ import { alpha } from '../../../utils/colors'
5
6
 
6
7
  const t = getSemanticColors('dark')
7
8
 
@@ -10,10 +11,11 @@ const STATUS_WARNING = t['status-warning']
10
11
  const STATUS_INFO = t['status-info']
11
12
  /** Dot outline: near-white ring so load dots read on the band fill and any
12
13
  * surface (matches the MesoStatusCard gauge-marker convention). */
14
+ // VW-82: no token at this value (nearest text-primary #F9F6F3). Left raw, proposed.
13
15
  const DOT_BORDER = '#F3F4F6'
14
- const BAND_FILL = 'rgba(46,213,115,0.1)'
15
- const BAND_EDGE = 'rgba(46,213,115,0.45)'
16
- const PROJECTION_FILL = 'rgba(46,213,115,0.05)'
16
+ const BAND_FILL = alpha(STATUS_SUCCESS, 0.1)
17
+ const BAND_EDGE = alpha(STATUS_SUCCESS, 0.45)
18
+ const PROJECTION_FILL = alpha(STATUS_SUCCESS, 0.05)
17
19
 
18
20
  const PADDING_LEFT = 28
19
21
  const PADDING_RIGHT = 10
@@ -101,7 +103,7 @@ function formatDate(date: string): string {
101
103
  function toPixels(
102
104
  points: CapacityBandDataPoint[],
103
105
  toX: (date: string) => number,
104
- toY: (value: number) => number,
106
+ toY: (value: number) => number
105
107
  ): PixelPoint[] {
106
108
  return points
107
109
  .map((p) => ({ x: toX(p.date), yHigh: toY(p.bandHigh), yLow: toY(p.bandLow) }))
@@ -153,7 +155,7 @@ function buildEdges(pixels: PixelPoint[], key: 'yHigh' | 'yLow') {
153
155
  function collectValues(
154
156
  band: CapacityBandDataPoint[],
155
157
  workouts: WorkoutDot[],
156
- projection?: CapacityBandProjection,
158
+ projection?: CapacityBandProjection
157
159
  ): number[] {
158
160
  const values: number[] = []
159
161
  band.forEach((p) => values.push(p.bandLow, p.bandHigh))
@@ -200,7 +202,7 @@ export function CapacityBandChart({
200
202
  const [reveal] = useState(() => new Animated.Value(0))
201
203
  const dotAnims = useMemo(
202
204
  () => Array.from({ length: workouts.length }, () => new Animated.Value(0)),
203
- [workouts.length],
205
+ [workouts.length]
204
206
  )
205
207
 
206
208
  useEffect(() => {
@@ -218,7 +220,7 @@ export function CapacityBandChart({
218
220
  duration: 200,
219
221
  easing: Easing.out(Easing.ease),
220
222
  useNativeDriver: false,
221
- }),
223
+ })
222
224
  )
223
225
  const animation = Animated.sequence([draw, Animated.stagger(200, pops)])
224
226
  animation.start()
@@ -244,10 +246,8 @@ export function CapacityBandChart({
244
246
  const domainMax = maxV + pad
245
247
  const vRange = domainMax - domainMin || 1
246
248
 
247
- const toX = (date: string) =>
248
- PADDING_LEFT + ((parseTime(date) - minT) / tRange) * plotWidth
249
- const toY = (value: number) =>
250
- PADDING_TOP + (1 - (value - domainMin) / vRange) * plotHeight
249
+ const toX = (date: string) => PADDING_LEFT + ((parseTime(date) - minT) / tRange) * plotWidth
250
+ const toY = (value: number) => PADDING_TOP + (1 - (value - domainMin) / vRange) * plotHeight
251
251
  return { toX, toY }
252
252
  }, [band, workouts, projection, plotWidth, plotHeight])
253
253
 
@@ -274,9 +274,7 @@ export function CapacityBandChart({
274
274
  const trainingPixels = projection
275
275
  ? toPixels([lastBandPoint, ...projection.withTraining], toX, toY)
276
276
  : []
277
- const restPixels = projection
278
- ? toPixels([lastBandPoint, ...projection.withRest], toX, toY)
279
- : []
277
+ const restPixels = projection ? toPixels([lastBandPoint, ...projection.withRest], toX, toY) : []
280
278
 
281
279
  const labelStride = Math.max(1, Math.ceil(band.length / 5))
282
280
  const revealWidth = reveal.interpolate({ inputRange: [0, 1], outputRange: [0, width] })
@@ -303,7 +301,7 @@ export function CapacityBandChart({
303
301
  segments: ReturnType<typeof buildEdges>,
304
302
  color: string,
305
303
  dashed: boolean,
306
- key: string,
304
+ key: string
307
305
  ) =>
308
306
  segments.map((seg, i) => (
309
307
  <View
@@ -341,107 +339,142 @@ export function CapacityBandChart({
341
339
  style={{ position: 'absolute', top: 0, left: 0, width, height }}
342
340
  accessibilityRole="image"
343
341
  accessibilityLabel={`Training capacity band chart. Current capacity: ${currentStatus(
344
- workouts,
342
+ workouts
345
343
  )}. ${workouts.length} workout${workouts.length === 1 ? '' : 's'} shown.`}
346
344
  testID="capacity-band-chart"
347
345
  >
348
- {/* Band + projection draw left-to-right via an animated clip. */}
349
- <Animated.View
350
- style={{ position: 'absolute', top: 0, left: 0, height, width: revealWidth, overflow: 'hidden' }}
351
- accessibilityElementsHidden
352
- importantForAccessibility="no-hide-descendants"
353
- testID="capacity-band-chart-reveal"
354
- >
355
- <View style={{ width, height, position: 'relative' }}>
356
- {projection && (
357
- <View testID="capacity-band-chart-projection">
358
- {renderColumns(buildColumns(trainingPixels, COLUMN_STEP), PROJECTION_FILL, 'capacity-band-chart-projection-fill')}
359
- {renderColumns(buildColumns(restPixels, COLUMN_STEP), PROJECTION_FILL, 'capacity-band-chart-projection-fill')}
360
- {renderEdges(buildEdges(trainingPixels, 'yHigh'), STATUS_SUCCESS, true, 'capacity-band-chart-projection-training')}
361
- {renderEdges(buildEdges(trainingPixels, 'yLow'), STATUS_SUCCESS, true, 'capacity-band-chart-projection-training')}
362
- {renderEdges(buildEdges(restPixels, 'yHigh'), STATUS_INFO, true, 'capacity-band-chart-projection-rest')}
363
- {renderEdges(buildEdges(restPixels, 'yLow'), STATUS_INFO, true, 'capacity-band-chart-projection-rest')}
364
- <Text
365
- style={{
366
- position: 'absolute',
367
- left: trainingPixels[trainingPixels.length - 1]?.x + 2,
368
- top: trainingPixels[trainingPixels.length - 1]?.yHigh - 12,
369
- fontSize: 9,
370
- fontFamily: 'Inter, sans-serif',
371
- color: STATUS_SUCCESS,
372
- }}
373
- testID="capacity-band-chart-projection-training-label"
374
- >
375
- Training
376
- </Text>
377
- <Text
378
- style={{
379
- position: 'absolute',
380
- left: restPixels[restPixels.length - 1]?.x + 2,
381
- top: restPixels[restPixels.length - 1]?.yLow + 2,
382
- fontSize: 9,
383
- fontFamily: 'Inter, sans-serif',
384
- color: STATUS_INFO,
385
- }}
386
- testID="capacity-band-chart-projection-rest-label"
387
- >
388
- Rest
389
- </Text>
390
- </View>
391
- )}
346
+ {/* Band + projection draw left-to-right via an animated clip. */}
347
+ <Animated.View
348
+ style={{
349
+ position: 'absolute',
350
+ top: 0,
351
+ left: 0,
352
+ height,
353
+ width: revealWidth,
354
+ overflow: 'hidden',
355
+ }}
356
+ accessibilityElementsHidden
357
+ importantForAccessibility="no-hide-descendants"
358
+ testID="capacity-band-chart-reveal"
359
+ >
360
+ <View style={{ width, height, position: 'relative' }}>
361
+ {projection && (
362
+ <View testID="capacity-band-chart-projection">
363
+ {renderColumns(
364
+ buildColumns(trainingPixels, COLUMN_STEP),
365
+ PROJECTION_FILL,
366
+ 'capacity-band-chart-projection-fill'
367
+ )}
368
+ {renderColumns(
369
+ buildColumns(restPixels, COLUMN_STEP),
370
+ PROJECTION_FILL,
371
+ 'capacity-band-chart-projection-fill'
372
+ )}
373
+ {renderEdges(
374
+ buildEdges(trainingPixels, 'yHigh'),
375
+ STATUS_SUCCESS,
376
+ true,
377
+ 'capacity-band-chart-projection-training'
378
+ )}
379
+ {renderEdges(
380
+ buildEdges(trainingPixels, 'yLow'),
381
+ STATUS_SUCCESS,
382
+ true,
383
+ 'capacity-band-chart-projection-training'
384
+ )}
385
+ {renderEdges(
386
+ buildEdges(restPixels, 'yHigh'),
387
+ STATUS_INFO,
388
+ true,
389
+ 'capacity-band-chart-projection-rest'
390
+ )}
391
+ {renderEdges(
392
+ buildEdges(restPixels, 'yLow'),
393
+ STATUS_INFO,
394
+ true,
395
+ 'capacity-band-chart-projection-rest'
396
+ )}
397
+ <Text
398
+ style={{
399
+ position: 'absolute',
400
+ left: trainingPixels[trainingPixels.length - 1]?.x + 2,
401
+ top: trainingPixels[trainingPixels.length - 1]?.yHigh - 12,
402
+ fontSize: 9,
403
+ fontFamily: 'Inter, sans-serif',
404
+ color: STATUS_SUCCESS,
405
+ }}
406
+ testID="capacity-band-chart-projection-training-label"
407
+ >
408
+ Training
409
+ </Text>
410
+ <Text
411
+ style={{
412
+ position: 'absolute',
413
+ left: restPixels[restPixels.length - 1]?.x + 2,
414
+ top: restPixels[restPixels.length - 1]?.yLow + 2,
415
+ fontSize: 9,
416
+ fontFamily: 'Inter, sans-serif',
417
+ color: STATUS_INFO,
418
+ }}
419
+ testID="capacity-band-chart-projection-rest-label"
420
+ >
421
+ Rest
422
+ </Text>
423
+ </View>
424
+ )}
392
425
 
393
- <View testID="capacity-band-chart-band">
394
- {renderColumns(columns, BAND_FILL, 'capacity-band-chart-band-cell')}
426
+ <View testID="capacity-band-chart-band">
427
+ {renderColumns(columns, BAND_FILL, 'capacity-band-chart-band-cell')}
428
+ </View>
429
+ {renderEdges(topEdge, BAND_EDGE, false, 'capacity-band-chart-edge-top')}
430
+ {renderEdges(bottomEdge, BAND_EDGE, false, 'capacity-band-chart-edge-bottom')}
395
431
  </View>
396
- {renderEdges(topEdge, BAND_EDGE, false, 'capacity-band-chart-edge-top')}
397
- {renderEdges(bottomEdge, BAND_EDGE, false, 'capacity-band-chart-edge-bottom')}
398
- </View>
399
- </Animated.View>
400
-
401
- {/* Y axis conceptual label (no numbers). */}
402
- <Text
403
- className="text-text-tertiary"
404
- style={{
405
- position: 'absolute',
406
- left: PADDING_LEFT / 2 - 14,
407
- top: PADDING_TOP + plotHeight / 2 - 7,
408
- width: 32,
409
- textAlign: 'center',
410
- fontSize: 9,
411
- fontFamily: 'Inter, sans-serif',
412
- transform: [{ rotate: '-90deg' }],
413
- }}
414
- accessibilityElementsHidden
415
- importantForAccessibility="no-hide-descendants"
416
- testID="capacity-band-chart-y-axis-label"
417
- >
418
- Load
419
- </Text>
420
-
421
- {/* X axis date labels (subset to avoid overlap). */}
422
- {band.map((point, i) => {
423
- if (i % labelStride !== 0 && i !== band.length - 1) return null
424
- return (
425
- <Text
426
- key={`x-${point.date}`}
427
- className="text-text-tertiary"
428
- style={{
429
- position: 'absolute',
430
- left: toX(point.date) - 14,
431
- top: height - PADDING_BOTTOM + 2,
432
- width: 28,
433
- textAlign: 'center',
434
- fontSize: 10,
435
- fontFamily: 'Inter, sans-serif',
436
- }}
437
- accessibilityElementsHidden
438
- importantForAccessibility="no-hide-descendants"
439
- testID="capacity-band-chart-x-label"
440
- >
441
- {formatDate(point.date)}
442
- </Text>
443
- )
444
- })}
432
+ </Animated.View>
433
+
434
+ {/* Y axis conceptual label (no numbers). */}
435
+ <Text
436
+ className="text-text-tertiary"
437
+ style={{
438
+ position: 'absolute',
439
+ left: PADDING_LEFT / 2 - 14,
440
+ top: PADDING_TOP + plotHeight / 2 - 7,
441
+ width: 32,
442
+ textAlign: 'center',
443
+ fontSize: 9,
444
+ fontFamily: 'Inter, sans-serif',
445
+ transform: [{ rotate: '-90deg' }],
446
+ }}
447
+ accessibilityElementsHidden
448
+ importantForAccessibility="no-hide-descendants"
449
+ testID="capacity-band-chart-y-axis-label"
450
+ >
451
+ Load
452
+ </Text>
453
+
454
+ {/* X axis date labels (subset to avoid overlap). */}
455
+ {band.map((point, i) => {
456
+ if (i % labelStride !== 0 && i !== band.length - 1) return null
457
+ return (
458
+ <Text
459
+ key={`x-${point.date}`}
460
+ className="text-text-tertiary"
461
+ style={{
462
+ position: 'absolute',
463
+ left: toX(point.date) - 14,
464
+ top: height - PADDING_BOTTOM + 2,
465
+ width: 28,
466
+ textAlign: 'center',
467
+ fontSize: 10,
468
+ fontFamily: 'Inter, sans-serif',
469
+ }}
470
+ accessibilityElementsHidden
471
+ importantForAccessibility="no-hide-descendants"
472
+ testID="capacity-band-chart-x-label"
473
+ >
474
+ {formatDate(point.date)}
475
+ </Text>
476
+ )
477
+ })}
445
478
  </View>
446
479
 
447
480
  {/* Workout dots — scale in after the band draws. */}
@@ -3,7 +3,7 @@ import { View } from 'react-native'
3
3
  import { DeviationBar } from './DeviationBar'
4
4
 
5
5
  const meta: Meta<typeof DeviationBar> = {
6
- title: 'Workout/DataViz/DeviationBar',
6
+ title: 'Custom/Workout/DataViz/DeviationBar',
7
7
  component: DeviationBar,
8
8
  tags: ['autodocs'],
9
9
  argTypes: {
@@ -3,6 +3,10 @@ import { render, screen } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
4
  import { DeviationBar } from './DeviationBar'
5
5
  import { greyRamp } from '../../../theme/tokens/primitives'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
7
+ import { alpha } from '../../../utils/colors'
8
+
9
+ const t = getSemanticColors('dark')
6
10
 
7
11
  describe('DeviationBar', () => {
8
12
  it('renders the bar and dot', () => {
@@ -59,12 +63,15 @@ describe('DeviationBar', () => {
59
63
  expect(bar).toHaveAttribute('role', 'slider')
60
64
  })
61
65
 
62
- it('renders an 8px dot with a text-primary border and shadow', () => {
66
+ it('renders an 8px dot with a text-primary border and a lift', () => {
63
67
  const { getByTestId } = render(<DeviationBar deviation={0} />)
64
68
  const dot = getByTestId('deviation-dot')
65
69
  expect(dot).toHaveStyle({ width: '8px', height: '8px' })
66
70
  expect(dot).toHaveStyle({ borderTopWidth: '1.5px', borderTopColor: greyRamp[50] })
67
- expect(dot).toHaveStyle({ boxShadow: '0 0 4px rgba(0,0,0,0.5)' })
71
+ // The knob rests on the track: an ambient cast shadow, no rim (the ring is
72
+ // already its edge) and never an inset.
73
+ expect(dot.style.boxShadow).toContain('rgba(0,0,0')
74
+ expect(dot.style.boxShadow).not.toContain('inset')
68
75
  })
69
76
 
70
77
  it('renders the track with the deviation gradient background', () => {
@@ -72,8 +79,7 @@ describe('DeviationBar', () => {
72
79
  const bar = getByTestId('deviation-bar')
73
80
  const track = bar.firstElementChild as HTMLElement
74
81
  expect(track).toHaveStyle({
75
- backgroundImage:
76
- 'linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(107,114,128,0.15) 50%, rgba(249,180,21,0.25) 100%)',
82
+ backgroundImage: `linear-gradient(90deg, ${alpha(t['status-success'], 0.25)} 0%, rgba(107,114,128,0.15) 50%, ${alpha(t['status-warning'], 0.25)} 100%)`,
77
83
  })
78
84
  })
79
85
 
@@ -2,6 +2,8 @@
2
2
  import { View, type ViewProps, type ViewStyle } from 'react-native'
3
3
  import { getSemanticColors } from '../../../theme/tokens/semantic'
4
4
  import { greyRamp } from '../../../theme/tokens/primitives'
5
+ import { liftStyle } from '../../../theme/lift'
6
+ import { alpha } from '../../../utils/colors'
5
7
 
6
8
  const t = getSemanticColors('dark')
7
9
 
@@ -25,12 +27,7 @@ function getDeviationDescription(deviation: number): string {
25
27
  return 'on plan'
26
28
  }
27
29
 
28
- export function DeviationBar({
29
- deviation,
30
- width,
31
- className,
32
- ...props
33
- }: DeviationBarProps) {
30
+ export function DeviationBar({ deviation, width, className, ...props }: DeviationBarProps) {
34
31
  const clamped = Math.max(-1, Math.min(1, deviation))
35
32
  const resolvedWidth = width ?? 40
36
33
  const dotPosition = ((clamped + 1) / 2) * resolvedWidth
@@ -42,7 +39,12 @@ export function DeviationBar({
42
39
  return (
43
40
  <View
44
41
  className={className}
45
- style={{ flexDirection: 'row', alignItems: 'center', height: containerHeight, width: resolvedWidth }}
42
+ style={{
43
+ flexDirection: 'row',
44
+ alignItems: 'center',
45
+ height: containerHeight,
46
+ width: resolvedWidth,
47
+ }}
46
48
  accessibilityRole="adjustable"
47
49
  accessibilityValue={{ min: -100, max: 100, now: valueNow }}
48
50
  accessibilityLabel={`Session deviation: ${getDeviationDescription(clamped)}`}
@@ -53,14 +55,18 @@ export function DeviationBar({
53
55
  {...props}
54
56
  >
55
57
  <View
56
- style={{
57
- height: trackHeight,
58
- width: resolvedWidth,
59
- borderRadius: 3,
60
- // react-native-web renders backgroundImage at runtime; not in RN ViewStyle types
61
- backgroundImage:
62
- 'linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(107,114,128,0.15) 50%, rgba(249,180,21,0.25) 100%)',
63
- } as ViewStyle}
58
+ style={
59
+ {
60
+ height: trackHeight,
61
+ width: resolvedWidth,
62
+ borderRadius: 3,
63
+ // react-native-web renders backgroundImage at runtime; not in RN ViewStyle types
64
+ // rgba(107,114,128,0.15) at 50% has no exact token match (nearest:
65
+ // semanticPins.textSecondaryLight #65748B, ΔRGB ~12.7) — left as a
66
+ // raw literal (VW-78).
67
+ backgroundImage: `linear-gradient(90deg, ${alpha(t['status-success'], 0.25)} 0%, rgba(107,114,128,0.15) 50%, ${alpha(t['status-warning'], 0.25)} 100%)`,
68
+ } as ViewStyle
69
+ }
64
70
  />
65
71
  <View
66
72
  style={{
@@ -70,7 +76,9 @@ export function DeviationBar({
70
76
  borderRadius: 9999,
71
77
  borderWidth: 1.5,
72
78
  borderColor: greyRamp[50],
73
- boxShadow: '0 0 4px rgba(0,0,0,0.5)',
79
+ // The dot is a knob resting on the track: one plane of lift. Its light
80
+ // ring is already the edge, so the lift contributes the shadow alone.
81
+ ...liftStyle(1, 'dark', { rim: 0 }),
74
82
  backgroundColor: getDotColor(clamped),
75
83
  left: Math.max(0, Math.min(dotPosition - dotSize / 2, resolvedWidth - dotSize)),
76
84
  top: (containerHeight - dotSize) / 2,
@@ -0,0 +1,183 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import type { Meta, StoryObj } from '@storybook/react-vite'
3
+ import { View, Text } from 'react-native'
4
+ import { DualVelocityStrip } from './VelocityStrip'
5
+ import { greyRamp, primitiveColors } from '../../../theme/tokens/primitives'
6
+
7
+ const PAGE_BG = greyRamp[975]
8
+ const PANEL_BG = greyRamp[950]
9
+ const LIST_BG = greyRamp[925]
10
+
11
+ /** Bilateral cable chest press, LEFT dominant / RIGHT lagging, 6 of 8 reps done. */
12
+ const LEFT = [0.54, 0.52, 0.5, 0.48, 0.46, 0.44]
13
+ const RIGHT = [0.49, 0.47, 0.45, 0.42, 0.4, 0.38]
14
+
15
+ const meta: Meta<typeof DualVelocityStrip> = {
16
+ title: 'Custom/Workout/DataViz/DualVelocityStrip',
17
+ component: DualVelocityStrip,
18
+ tags: ['autodocs'],
19
+ parameters: {
20
+ layout: 'fullscreen',
21
+ docs: {
22
+ description: {
23
+ component:
24
+ 'The dual-voltra (bilateral) DIVERGING per-rep velocity chart. LEFT reps grow UP, ' +
25
+ 'RIGHT reps grow DOWN from one shared centre axis — one mirrored pair per rep index — ' +
26
+ 'so a left-dominant / right-lagging set reads pre-attentively as an asymmetric ' +
27
+ 'silhouette. Reuses VelocityStrip’s slot vocabulary (rep / todo / variable / ' +
28
+ 'continue), its velocity-zone colors, hero geometry, and the live-rep pop. Color is ' +
29
+ 'ALWAYS the velocity zone; SIDE is encoded by position only, never hue. `variant="hero"` ' +
30
+ 'is the wall scale (value labels + per-side running-best reference lines); ' +
31
+ '`variant="dual-expanded"` is the compact rail-expanded scale (`"rail"` is a deprecated ' +
32
+ 'alias for the same renderer). Single-voltra sets keep using VelocityStrip.',
33
+ },
34
+ },
35
+ },
36
+ argTypes: {
37
+ variant: { control: 'inline-radio', options: ['hero', 'dual-expanded'] },
38
+ targetReps: { control: 'number' },
39
+ liveRepIndex: { control: 'number' },
40
+ height: { control: { type: 'number', min: 48, max: 320, step: 4 } },
41
+ scale: { control: 'inline-radio', options: ['peak', 'fixed'] },
42
+ },
43
+ }
44
+ export default meta
45
+ type Story = StoryObj<typeof DualVelocityStrip>
46
+
47
+ function Panel({ children, width }: { children: React.ReactNode; width?: number }) {
48
+ return (
49
+ <View style={{ width, backgroundColor: PANEL_BG, borderRadius: 12, padding: 20, gap: 12 }}>
50
+ {children}
51
+ </View>
52
+ )
53
+ }
54
+
55
+ function Page({ children }: { children: React.ReactNode }) {
56
+ return (
57
+ <View style={{ padding: 28, backgroundColor: PAGE_BG, minHeight: '100%', gap: 24 }}>
58
+ {children}
59
+ </View>
60
+ )
61
+ }
62
+
63
+ /** HERO — the across-the-room bilateral live chart, L up / R down from the centre axis. */
64
+ export const Hero: Story = {
65
+ args: { variant: 'hero', targetReps: 8, liveRepIndex: 5, height: 280, scale: 'peak' },
66
+ render: (args) => (
67
+ <Page>
68
+ <Panel width={760}>
69
+ <Text
70
+ style={{
71
+ color: primitiveColors.white,
72
+ fontFamily: '"Space Grotesk", sans-serif',
73
+ fontSize: 15,
74
+ fontWeight: '700',
75
+ }}
76
+ >
77
+ Hero — dual diverging velocity
78
+ </Text>
79
+ <DualVelocityStrip {...args} left={{ velocities: LEFT }} right={{ velocities: RIGHT }} />
80
+ </Panel>
81
+ </Page>
82
+ ),
83
+ }
84
+
85
+ /** DUAL-EXPANDED — the same diverging language at the compact rail-expanded scale (no labels/refs). */
86
+ export const DualExpanded: Story = {
87
+ args: { variant: 'dual-expanded', targetReps: 8, height: 84 },
88
+ render: (args) => (
89
+ <Page>
90
+ <Panel width={320}>
91
+ <Text
92
+ style={{
93
+ color: primitiveColors.white,
94
+ fontFamily: '"Space Grotesk", sans-serif',
95
+ fontSize: 13,
96
+ fontWeight: '700',
97
+ }}
98
+ >
99
+ Rail expanded — same language, smaller
100
+ </Text>
101
+ <View style={{ backgroundColor: LIST_BG, borderRadius: 8, padding: 12 }}>
102
+ <DualVelocityStrip {...args} left={{ velocities: LEFT }} right={{ velocities: RIGHT }} />
103
+ </View>
104
+ </Panel>
105
+ </Page>
106
+ ),
107
+ }
108
+
109
+ /**
110
+ * A completed, narrow-panel set ending on its OWN peak on both sides (TD-07.10 repro): the last
111
+ * rep's value label has no sibling column to its right, and its bar sits at the running-best
112
+ * reference-line height — the two crowd together at the plot's right edge. Narrow enough
113
+ * (~420px panel, 8 reps/side) that the last column's bar width is under the label's estimated
114
+ * width, so the flip rule fires: the last label anchors right (grows left) instead of centering
115
+ * and bleeding past the edge.
116
+ */
117
+ const ASCENDING_LEFT = [
118
+ 0.55, 0.58, 0.6, 0.63, 0.66, 0.68, 0.71, 0.74, 0.77, 0.79, 0.82, 0.85, 0.87, 0.9,
119
+ ]
120
+ const ASCENDING_RIGHT = [
121
+ 0.53, 0.56, 0.58, 0.61, 0.64, 0.66, 0.69, 0.72, 0.75, 0.77, 0.8, 0.83, 0.85, 0.88,
122
+ ]
123
+
124
+ export const CrowdedRightEdgeLabel: Story = {
125
+ args: { variant: 'hero', height: 300, scale: 'peak' },
126
+ render: (args) => (
127
+ <Page>
128
+ <Panel width={420}>
129
+ <Text
130
+ style={{
131
+ color: primitiveColors.white,
132
+ fontFamily: '"Space Grotesk", sans-serif',
133
+ fontSize: 15,
134
+ fontWeight: '700',
135
+ }}
136
+ >
137
+ Peak on the last rep, narrow panel — the flip rule (TD-07.10)
138
+ </Text>
139
+ <DualVelocityStrip
140
+ {...args}
141
+ left={{ velocities: ASCENDING_LEFT }}
142
+ right={{ velocities: ASCENDING_RIGHT }}
143
+ />
144
+ </Panel>
145
+ </Page>
146
+ ),
147
+ parameters: {
148
+ docs: {
149
+ description: {
150
+ story:
151
+ "Before the fix, the last rep's centered label bled past the plot's right edge and sat " +
152
+ "on top of the running-best reference line. The flip rule right-anchors that column's " +
153
+ 'label instead, so it clears both.',
154
+ },
155
+ },
156
+ },
157
+ }
158
+
159
+ /** A range set on both sides — shows the cyan variable-window slots mirrored past the floor. */
160
+ export const RangeSet: Story = {
161
+ args: { variant: 'hero', height: 260, scale: 'peak' },
162
+ render: (args) => (
163
+ <Page>
164
+ <Panel width={760}>
165
+ <Text
166
+ style={{
167
+ color: primitiveColors.white,
168
+ fontFamily: '"Space Grotesk", sans-serif',
169
+ fontSize: 15,
170
+ fontWeight: '700',
171
+ }}
172
+ >
173
+ Range set — variable window mirrored both sides
174
+ </Text>
175
+ <DualVelocityStrip
176
+ {...args}
177
+ left={{ set: { type: 'range', velocities: LEFT.slice(0, 5), floor: 6, max: 10 } }}
178
+ right={{ set: { type: 'range', velocities: RIGHT.slice(0, 5), floor: 6, max: 10 } }}
179
+ />
180
+ </Panel>
181
+ </Page>
182
+ ),
183
+ }