@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
@@ -2,11 +2,7 @@ import { describe, it, expect, vi } from 'vitest'
2
2
  import { render, screen, fireEvent } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
4
  import { CapacityBandChart } from './CapacityBandChart'
5
- import type {
6
- CapacityBandDataPoint,
7
- CapacityBandProjection,
8
- WorkoutDot,
9
- } from './CapacityBandChart'
5
+ import type { CapacityBandDataPoint, CapacityBandProjection, WorkoutDot } from './CapacityBandChart'
10
6
 
11
7
  const band: CapacityBandDataPoint[] = [
12
8
  { date: '2026-06-01', bandLow: 40, bandHigh: 70 },
@@ -44,50 +40,36 @@ describe('CapacityBandChart', () => {
44
40
  it('renders the shaded band fill as column cells', () => {
45
41
  render(<CapacityBandChart {...baseProps} />)
46
42
  expect(screen.getByTestId('capacity-band-chart-band')).toBeInTheDocument()
47
- expect(
48
- screen.getAllByTestId('capacity-band-chart-band-cell').length,
49
- ).toBeGreaterThan(0)
43
+ expect(screen.getAllByTestId('capacity-band-chart-band-cell').length).toBeGreaterThan(0)
50
44
  })
51
45
 
52
46
  it('renders the top and bottom band edge lines', () => {
53
47
  render(<CapacityBandChart {...baseProps} />)
54
- expect(
55
- screen.getAllByTestId('capacity-band-chart-edge-top').length,
56
- ).toBe(band.length - 1)
57
- expect(
58
- screen.getAllByTestId('capacity-band-chart-edge-bottom').length,
59
- ).toBe(band.length - 1)
48
+ expect(screen.getAllByTestId('capacity-band-chart-edge-top').length).toBe(band.length - 1)
49
+ expect(screen.getAllByTestId('capacity-band-chart-edge-bottom').length).toBe(band.length - 1)
60
50
  })
61
51
 
62
52
  it('renders a conceptual Load axis label with no numbers', () => {
63
53
  render(<CapacityBandChart {...baseProps} />)
64
- expect(
65
- screen.getByTestId('capacity-band-chart-y-axis-label'),
66
- ).toHaveTextContent('Load')
54
+ expect(screen.getByTestId('capacity-band-chart-y-axis-label')).toHaveTextContent('Load')
67
55
  })
68
56
 
69
57
  it('renders x-axis date labels', () => {
70
58
  render(<CapacityBandChart {...baseProps} />)
71
- expect(
72
- screen.getAllByTestId('capacity-band-chart-x-label').length,
73
- ).toBeGreaterThan(0)
59
+ expect(screen.getAllByTestId('capacity-band-chart-x-label').length).toBeGreaterThan(0)
74
60
  })
75
61
 
76
62
  it('renders the empty state when band has no data', () => {
77
63
  render(<CapacityBandChart band={[]} workouts={[]} width={320} height={200} />)
78
64
  expect(screen.getByTestId('capacity-band-chart-empty')).toBeInTheDocument()
79
- expect(
80
- screen.queryByTestId('capacity-band-chart-band'),
81
- ).not.toBeInTheDocument()
65
+ expect(screen.queryByTestId('capacity-band-chart-band')).not.toBeInTheDocument()
82
66
  })
83
67
  })
84
68
 
85
69
  describe('workout dots', () => {
86
70
  it('renders one dot per workout', () => {
87
71
  render(<CapacityBandChart {...baseProps} />)
88
- expect(screen.getAllByTestId('capacity-band-chart-dot')).toHaveLength(
89
- workouts.length,
90
- )
72
+ expect(screen.getAllByTestId('capacity-band-chart-dot')).toHaveLength(workouts.length)
91
73
  })
92
74
 
93
75
  it('colors dots by status relative to the band', () => {
@@ -125,41 +107,36 @@ describe('CapacityBandChart', () => {
125
107
 
126
108
  it('exposes dots as labeled buttons when interactive', () => {
127
109
  render(<CapacityBandChart {...baseProps} onWorkoutPress={vi.fn()} />)
128
- expect(
129
- screen.getByLabelText('Workout on 6/6, load 80, above range'),
130
- ).toHaveAttribute('role', 'button')
110
+ expect(screen.getByLabelText('Workout on 6/6, load 80, above range')).toHaveAttribute(
111
+ 'role',
112
+ 'button'
113
+ )
131
114
  })
132
115
  })
133
116
 
134
117
  describe('projection', () => {
135
118
  it('renders dashed diverging training and rest edges when provided', () => {
136
119
  render(<CapacityBandChart {...baseProps} projection={projection} />)
120
+ expect(screen.getByTestId('capacity-band-chart-projection')).toBeInTheDocument()
137
121
  expect(
138
- screen.getByTestId('capacity-band-chart-projection'),
139
- ).toBeInTheDocument()
140
- expect(
141
- screen.getAllByTestId('capacity-band-chart-projection-training').length,
142
- ).toBeGreaterThan(0)
143
- expect(
144
- screen.getAllByTestId('capacity-band-chart-projection-rest').length,
122
+ screen.getAllByTestId('capacity-band-chart-projection-training').length
145
123
  ).toBeGreaterThan(0)
124
+ expect(screen.getAllByTestId('capacity-band-chart-projection-rest').length).toBeGreaterThan(0)
146
125
  })
147
126
 
148
127
  it('labels the training and rest projections', () => {
149
128
  render(<CapacityBandChart {...baseProps} projection={projection} />)
150
- expect(
151
- screen.getByTestId('capacity-band-chart-projection-training-label'),
152
- ).toHaveTextContent('Training')
153
- expect(
154
- screen.getByTestId('capacity-band-chart-projection-rest-label'),
155
- ).toHaveTextContent('Rest')
129
+ expect(screen.getByTestId('capacity-band-chart-projection-training-label')).toHaveTextContent(
130
+ 'Training'
131
+ )
132
+ expect(screen.getByTestId('capacity-band-chart-projection-rest-label')).toHaveTextContent(
133
+ 'Rest'
134
+ )
156
135
  })
157
136
 
158
137
  it('omits the projection layer when not provided', () => {
159
138
  render(<CapacityBandChart {...baseProps} />)
160
- expect(
161
- screen.queryByTestId('capacity-band-chart-projection'),
162
- ).not.toBeInTheDocument()
139
+ expect(screen.queryByTestId('capacity-band-chart-projection')).not.toBeInTheDocument()
163
140
  })
164
141
  })
165
142
 
@@ -170,7 +147,7 @@ describe('CapacityBandChart', () => {
170
147
  expect(chart).toHaveAttribute('role', 'img')
171
148
  expect(chart).toHaveAttribute(
172
149
  'aria-label',
173
- 'Training capacity band chart. Current capacity: below range. 3 workouts shown.',
150
+ 'Training capacity band chart. Current capacity: below range. 3 workouts shown.'
174
151
  )
175
152
  })
176
153
 
@@ -181,11 +158,7 @@ describe('CapacityBandChart', () => {
181
158
 
182
159
  it('has no accessibility violations with all features enabled', async () => {
183
160
  const { container } = render(
184
- <CapacityBandChart
185
- {...baseProps}
186
- projection={projection}
187
- onWorkoutPress={vi.fn()}
188
- />,
161
+ <CapacityBandChart {...baseProps} projection={projection} onWorkoutPress={vi.fn()} />
189
162
  )
190
163
  expect(await axe(container)).toHaveNoViolations()
191
164
  })
@@ -101,7 +101,7 @@ function formatDate(date: string): string {
101
101
  function toPixels(
102
102
  points: CapacityBandDataPoint[],
103
103
  toX: (date: string) => number,
104
- toY: (value: number) => number,
104
+ toY: (value: number) => number
105
105
  ): PixelPoint[] {
106
106
  return points
107
107
  .map((p) => ({ x: toX(p.date), yHigh: toY(p.bandHigh), yLow: toY(p.bandLow) }))
@@ -153,7 +153,7 @@ function buildEdges(pixels: PixelPoint[], key: 'yHigh' | 'yLow') {
153
153
  function collectValues(
154
154
  band: CapacityBandDataPoint[],
155
155
  workouts: WorkoutDot[],
156
- projection?: CapacityBandProjection,
156
+ projection?: CapacityBandProjection
157
157
  ): number[] {
158
158
  const values: number[] = []
159
159
  band.forEach((p) => values.push(p.bandLow, p.bandHigh))
@@ -200,7 +200,7 @@ export function CapacityBandChart({
200
200
  const [reveal] = useState(() => new Animated.Value(0))
201
201
  const dotAnims = useMemo(
202
202
  () => Array.from({ length: workouts.length }, () => new Animated.Value(0)),
203
- [workouts.length],
203
+ [workouts.length]
204
204
  )
205
205
 
206
206
  useEffect(() => {
@@ -218,7 +218,7 @@ export function CapacityBandChart({
218
218
  duration: 200,
219
219
  easing: Easing.out(Easing.ease),
220
220
  useNativeDriver: false,
221
- }),
221
+ })
222
222
  )
223
223
  const animation = Animated.sequence([draw, Animated.stagger(200, pops)])
224
224
  animation.start()
@@ -244,10 +244,8 @@ export function CapacityBandChart({
244
244
  const domainMax = maxV + pad
245
245
  const vRange = domainMax - domainMin || 1
246
246
 
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
247
+ const toX = (date: string) => PADDING_LEFT + ((parseTime(date) - minT) / tRange) * plotWidth
248
+ const toY = (value: number) => PADDING_TOP + (1 - (value - domainMin) / vRange) * plotHeight
251
249
  return { toX, toY }
252
250
  }, [band, workouts, projection, plotWidth, plotHeight])
253
251
 
@@ -274,9 +272,7 @@ export function CapacityBandChart({
274
272
  const trainingPixels = projection
275
273
  ? toPixels([lastBandPoint, ...projection.withTraining], toX, toY)
276
274
  : []
277
- const restPixels = projection
278
- ? toPixels([lastBandPoint, ...projection.withRest], toX, toY)
279
- : []
275
+ const restPixels = projection ? toPixels([lastBandPoint, ...projection.withRest], toX, toY) : []
280
276
 
281
277
  const labelStride = Math.max(1, Math.ceil(band.length / 5))
282
278
  const revealWidth = reveal.interpolate({ inputRange: [0, 1], outputRange: [0, width] })
@@ -303,7 +299,7 @@ export function CapacityBandChart({
303
299
  segments: ReturnType<typeof buildEdges>,
304
300
  color: string,
305
301
  dashed: boolean,
306
- key: string,
302
+ key: string
307
303
  ) =>
308
304
  segments.map((seg, i) => (
309
305
  <View
@@ -341,107 +337,142 @@ export function CapacityBandChart({
341
337
  style={{ position: 'absolute', top: 0, left: 0, width, height }}
342
338
  accessibilityRole="image"
343
339
  accessibilityLabel={`Training capacity band chart. Current capacity: ${currentStatus(
344
- workouts,
340
+ workouts
345
341
  )}. ${workouts.length} workout${workouts.length === 1 ? '' : 's'} shown.`}
346
342
  testID="capacity-band-chart"
347
343
  >
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
- )}
344
+ {/* Band + projection draw left-to-right via an animated clip. */}
345
+ <Animated.View
346
+ style={{
347
+ position: 'absolute',
348
+ top: 0,
349
+ left: 0,
350
+ height,
351
+ width: revealWidth,
352
+ overflow: 'hidden',
353
+ }}
354
+ accessibilityElementsHidden
355
+ importantForAccessibility="no-hide-descendants"
356
+ testID="capacity-band-chart-reveal"
357
+ >
358
+ <View style={{ width, height, position: 'relative' }}>
359
+ {projection && (
360
+ <View testID="capacity-band-chart-projection">
361
+ {renderColumns(
362
+ buildColumns(trainingPixels, COLUMN_STEP),
363
+ PROJECTION_FILL,
364
+ 'capacity-band-chart-projection-fill'
365
+ )}
366
+ {renderColumns(
367
+ buildColumns(restPixels, COLUMN_STEP),
368
+ PROJECTION_FILL,
369
+ 'capacity-band-chart-projection-fill'
370
+ )}
371
+ {renderEdges(
372
+ buildEdges(trainingPixels, 'yHigh'),
373
+ STATUS_SUCCESS,
374
+ true,
375
+ 'capacity-band-chart-projection-training'
376
+ )}
377
+ {renderEdges(
378
+ buildEdges(trainingPixels, 'yLow'),
379
+ STATUS_SUCCESS,
380
+ true,
381
+ 'capacity-band-chart-projection-training'
382
+ )}
383
+ {renderEdges(
384
+ buildEdges(restPixels, 'yHigh'),
385
+ STATUS_INFO,
386
+ true,
387
+ 'capacity-band-chart-projection-rest'
388
+ )}
389
+ {renderEdges(
390
+ buildEdges(restPixels, 'yLow'),
391
+ STATUS_INFO,
392
+ true,
393
+ 'capacity-band-chart-projection-rest'
394
+ )}
395
+ <Text
396
+ style={{
397
+ position: 'absolute',
398
+ left: trainingPixels[trainingPixels.length - 1]?.x + 2,
399
+ top: trainingPixels[trainingPixels.length - 1]?.yHigh - 12,
400
+ fontSize: 9,
401
+ fontFamily: 'Inter, sans-serif',
402
+ color: STATUS_SUCCESS,
403
+ }}
404
+ testID="capacity-band-chart-projection-training-label"
405
+ >
406
+ Training
407
+ </Text>
408
+ <Text
409
+ style={{
410
+ position: 'absolute',
411
+ left: restPixels[restPixels.length - 1]?.x + 2,
412
+ top: restPixels[restPixels.length - 1]?.yLow + 2,
413
+ fontSize: 9,
414
+ fontFamily: 'Inter, sans-serif',
415
+ color: STATUS_INFO,
416
+ }}
417
+ testID="capacity-band-chart-projection-rest-label"
418
+ >
419
+ Rest
420
+ </Text>
421
+ </View>
422
+ )}
392
423
 
393
- <View testID="capacity-band-chart-band">
394
- {renderColumns(columns, BAND_FILL, 'capacity-band-chart-band-cell')}
424
+ <View testID="capacity-band-chart-band">
425
+ {renderColumns(columns, BAND_FILL, 'capacity-band-chart-band-cell')}
426
+ </View>
427
+ {renderEdges(topEdge, BAND_EDGE, false, 'capacity-band-chart-edge-top')}
428
+ {renderEdges(bottomEdge, BAND_EDGE, false, 'capacity-band-chart-edge-bottom')}
395
429
  </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
- })}
430
+ </Animated.View>
431
+
432
+ {/* Y axis conceptual label (no numbers). */}
433
+ <Text
434
+ className="text-text-tertiary"
435
+ style={{
436
+ position: 'absolute',
437
+ left: PADDING_LEFT / 2 - 14,
438
+ top: PADDING_TOP + plotHeight / 2 - 7,
439
+ width: 32,
440
+ textAlign: 'center',
441
+ fontSize: 9,
442
+ fontFamily: 'Inter, sans-serif',
443
+ transform: [{ rotate: '-90deg' }],
444
+ }}
445
+ accessibilityElementsHidden
446
+ importantForAccessibility="no-hide-descendants"
447
+ testID="capacity-band-chart-y-axis-label"
448
+ >
449
+ Load
450
+ </Text>
451
+
452
+ {/* X axis date labels (subset to avoid overlap). */}
453
+ {band.map((point, i) => {
454
+ if (i % labelStride !== 0 && i !== band.length - 1) return null
455
+ return (
456
+ <Text
457
+ key={`x-${point.date}`}
458
+ className="text-text-tertiary"
459
+ style={{
460
+ position: 'absolute',
461
+ left: toX(point.date) - 14,
462
+ top: height - PADDING_BOTTOM + 2,
463
+ width: 28,
464
+ textAlign: 'center',
465
+ fontSize: 10,
466
+ fontFamily: 'Inter, sans-serif',
467
+ }}
468
+ accessibilityElementsHidden
469
+ importantForAccessibility="no-hide-descendants"
470
+ testID="capacity-band-chart-x-label"
471
+ >
472
+ {formatDate(point.date)}
473
+ </Text>
474
+ )
475
+ })}
445
476
  </View>
446
477
 
447
478
  {/* 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: {
@@ -59,12 +59,15 @@ describe('DeviationBar', () => {
59
59
  expect(bar).toHaveAttribute('role', 'slider')
60
60
  })
61
61
 
62
- it('renders an 8px dot with a text-primary border and shadow', () => {
62
+ it('renders an 8px dot with a text-primary border and a lift', () => {
63
63
  const { getByTestId } = render(<DeviationBar deviation={0} />)
64
64
  const dot = getByTestId('deviation-dot')
65
65
  expect(dot).toHaveStyle({ width: '8px', height: '8px' })
66
66
  expect(dot).toHaveStyle({ borderTopWidth: '1.5px', borderTopColor: greyRamp[50] })
67
- expect(dot).toHaveStyle({ boxShadow: '0 0 4px rgba(0,0,0,0.5)' })
67
+ // The knob rests on the track: an ambient cast shadow, no rim (the ring is
68
+ // already its edge) and never an inset.
69
+ expect(dot.style.boxShadow).toContain('rgba(0,0,0')
70
+ expect(dot.style.boxShadow).not.toContain('inset')
68
71
  })
69
72
 
70
73
  it('renders the track with the deviation gradient background', () => {
@@ -2,6 +2,7 @@
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'
5
6
 
6
7
  const t = getSemanticColors('dark')
7
8
 
@@ -25,12 +26,7 @@ function getDeviationDescription(deviation: number): string {
25
26
  return 'on plan'
26
27
  }
27
28
 
28
- export function DeviationBar({
29
- deviation,
30
- width,
31
- className,
32
- ...props
33
- }: DeviationBarProps) {
29
+ export function DeviationBar({ deviation, width, className, ...props }: DeviationBarProps) {
34
30
  const clamped = Math.max(-1, Math.min(1, deviation))
35
31
  const resolvedWidth = width ?? 40
36
32
  const dotPosition = ((clamped + 1) / 2) * resolvedWidth
@@ -42,7 +38,12 @@ export function DeviationBar({
42
38
  return (
43
39
  <View
44
40
  className={className}
45
- style={{ flexDirection: 'row', alignItems: 'center', height: containerHeight, width: resolvedWidth }}
41
+ style={{
42
+ flexDirection: 'row',
43
+ alignItems: 'center',
44
+ height: containerHeight,
45
+ width: resolvedWidth,
46
+ }}
46
47
  accessibilityRole="adjustable"
47
48
  accessibilityValue={{ min: -100, max: 100, now: valueNow }}
48
49
  accessibilityLabel={`Session deviation: ${getDeviationDescription(clamped)}`}
@@ -53,14 +54,16 @@ export function DeviationBar({
53
54
  {...props}
54
55
  >
55
56
  <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}
57
+ style={
58
+ {
59
+ height: trackHeight,
60
+ width: resolvedWidth,
61
+ borderRadius: 3,
62
+ // react-native-web renders backgroundImage at runtime; not in RN ViewStyle types
63
+ backgroundImage:
64
+ 'linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(107,114,128,0.15) 50%, rgba(249,180,21,0.25) 100%)',
65
+ } as ViewStyle
66
+ }
64
67
  />
65
68
  <View
66
69
  style={{
@@ -70,7 +73,9 @@ export function DeviationBar({
70
73
  borderRadius: 9999,
71
74
  borderWidth: 1.5,
72
75
  borderColor: greyRamp[50],
73
- boxShadow: '0 0 4px rgba(0,0,0,0.5)',
76
+ // The dot is a knob resting on the track: one plane of lift. Its light
77
+ // ring is already the edge, so the lift contributes the shadow alone.
78
+ ...liftStyle(1, 'dark', { rim: 0 }),
74
79
  backgroundColor: getDotColor(clamped),
75
80
  left: Math.max(0, Math.min(dotPosition - dotSize / 2, resolvedWidth - dotSize)),
76
81
  top: (containerHeight - dotSize) / 2,