@titan-design/react-ui 0.5.0 → 0.7.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 (307) hide show
  1. package/dist/bodymap.js +241 -189
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +241 -189
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +1017 -130
  6. package/dist/index.d.ts +1017 -130
  7. package/dist/index.js +3058 -1090
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2992 -1091
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{pages-MO0JCySL.d.mts → pages-8u_4WbL-.d.mts} +280 -57
  12. package/dist/{pages-D7Jlssvp.d.ts → pages-DaWiBOGa.d.ts} +280 -57
  13. package/dist/pages.d.mts +1 -1
  14. package/dist/pages.d.ts +1 -1
  15. package/dist/pages.js +1797 -1292
  16. package/dist/pages.js.map +1 -1
  17. package/dist/pages.mjs +1797 -1292
  18. package/dist/pages.mjs.map +1 -1
  19. package/dist/theme/index.d.mts +464 -268
  20. package/dist/theme/index.d.ts +464 -268
  21. package/dist/theme/index.js +303 -191
  22. package/dist/theme/index.js.map +1 -1
  23. package/dist/theme/index.mjs +295 -192
  24. package/dist/theme/index.mjs.map +1 -1
  25. package/dist/theme/tokens-css.js +160 -143
  26. package/dist/theme/tokens-css.js.map +1 -1
  27. package/dist/theme/tokens-css.mjs +160 -143
  28. package/dist/theme/tokens-css.mjs.map +1 -1
  29. package/package.json +3 -1
  30. package/src/arch/Architecture.stories.tsx +870 -0
  31. package/src/arch/annotations.json +48 -0
  32. package/src/arch/arch-graph.json +4301 -0
  33. package/src/components/custom/DateTime/DateTime.stories.tsx +83 -7
  34. package/src/components/custom/DateTime/DateTime.test.tsx +29 -3
  35. package/src/components/custom/DateTime/DateTime.tsx +55 -15
  36. package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
  37. package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
  38. package/src/components/custom/Gauge/Gauge.test.tsx +2 -2
  39. package/src/components/custom/Gauge/Gauge.tsx +6 -3
  40. package/src/components/custom/Metric/Metric.stories.tsx +1 -1
  41. package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
  42. package/src/components/custom/Scatter/Scatter.tsx +3 -5
  43. package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
  44. package/src/components/custom/Table/Table.stories.tsx +1 -1
  45. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
  46. package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
  47. package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
  48. package/src/components/custom/TimerReadout/index.ts +1 -0
  49. package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
  50. package/src/components/custom/Treemap/Treemap.tsx +3 -5
  51. package/src/components/custom/Typography/Typography.stories.tsx +46 -28
  52. package/src/components/custom/Typography/Typography.test.tsx +13 -3
  53. package/src/components/custom/Typography/Typography.tsx +5 -0
  54. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
  55. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
  56. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +11 -16
  57. package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
  58. package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
  59. package/src/components/custom/Workout/BodyMap.tsx +6 -3
  60. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
  61. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +6 -3
  62. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
  63. package/src/components/custom/Workout/CapacityBandChart.test.tsx +2 -2
  64. package/src/components/custom/Workout/CapacityBandChart.tsx +9 -6
  65. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  66. package/src/components/custom/Workout/DeviationBar.test.tsx +1 -1
  67. package/src/components/custom/Workout/DeviationBar.tsx +7 -4
  68. package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
  69. package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
  70. package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
  71. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
  72. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
  73. package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
  74. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
  75. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
  76. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
  77. package/src/components/custom/Workout/ExerciseDetailPage.tsx +6 -6
  78. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
  79. package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
  80. package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
  81. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
  82. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
  83. package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
  84. package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
  85. package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
  86. package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
  87. package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
  88. package/src/components/custom/Workout/InputBar.tsx +2 -1
  89. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  90. package/src/components/custom/Workout/IntensityBar.test.tsx +8 -8
  91. package/src/components/custom/Workout/IntensityBar.tsx +10 -6
  92. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
  93. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
  94. package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
  95. package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
  96. package/src/components/custom/Workout/MesoCard.tsx +8 -3
  97. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  98. package/src/components/custom/Workout/MesoProgressBar.test.tsx +1 -1
  99. package/src/components/custom/Workout/MesoProgressBar.tsx +3 -2
  100. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
  101. package/src/components/custom/Workout/MesoStatusCard.tsx +20 -13
  102. package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
  103. package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
  104. package/src/components/custom/Workout/MetricTiles.tsx +48 -0
  105. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
  106. package/src/components/custom/Workout/MuscleGroupChip.tsx +7 -4
  107. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  108. package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
  109. package/src/components/custom/Workout/PrBadge.tsx +5 -2
  110. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
  111. package/src/components/custom/Workout/PrHistoryModal.tsx +4 -3
  112. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
  113. package/src/components/custom/Workout/ProgramPlanningPage.tsx +10 -7
  114. package/src/components/custom/Workout/README.md +146 -4
  115. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
  116. package/src/components/custom/Workout/ReadinessCheck.tsx +7 -4
  117. package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
  118. package/src/components/custom/Workout/RestTimer.tsx +17 -11
  119. package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
  120. package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
  121. package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
  122. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
  123. package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
  124. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
  125. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
  126. package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
  127. package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
  128. package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
  129. package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
  130. package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
  131. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
  132. package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
  133. package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
  134. package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
  135. package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
  136. package/src/components/custom/Workout/SessionHeader.tsx +152 -0
  137. package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
  138. package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
  139. package/src/components/custom/Workout/SessionRail.tsx +126 -0
  140. package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
  141. package/src/components/custom/Workout/SetBar.test.tsx +123 -0
  142. package/src/components/custom/Workout/SetBar.tsx +171 -0
  143. package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
  144. package/src/components/custom/Workout/SetRow.test.tsx +94 -166
  145. package/src/components/custom/Workout/SetRow.tsx +170 -232
  146. package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
  147. package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
  148. package/src/components/custom/Workout/SetStrip.tsx +71 -0
  149. package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
  150. package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
  151. package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
  152. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
  153. package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
  154. package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
  155. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  156. package/src/components/custom/Workout/Sparkline.tsx +2 -1
  157. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  158. package/src/components/custom/Workout/StatusDot.test.tsx +2 -2
  159. package/src/components/custom/Workout/StatusDot.tsx +12 -9
  160. package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
  161. package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
  162. package/src/components/custom/Workout/StatusPill.tsx +88 -0
  163. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
  164. package/src/components/custom/Workout/StrengthTrendChart.tsx +9 -6
  165. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
  166. package/src/components/custom/Workout/SupersetWrapper.tsx +2 -1
  167. package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
  168. package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
  169. package/src/components/custom/Workout/TempoDisplay.tsx +29 -36
  170. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
  171. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
  172. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
  173. package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
  174. package/src/components/custom/Workout/VelocityStrip.test.tsx +379 -144
  175. package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
  176. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
  177. package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
  178. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
  179. package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
  180. package/src/components/custom/Workout/WeekRow.tsx +3 -2
  181. package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
  182. package/src/components/custom/Workout/WeightBadge.tsx +6 -2
  183. package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
  184. package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
  185. package/src/components/custom/Workout/WorkoutCard.tsx +5 -2
  186. package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
  187. package/src/components/custom/Workout/WorkoutPill.tsx +12 -8
  188. package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
  189. package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
  190. package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
  191. package/src/components/custom/Workout/icons.tsx +6 -67
  192. package/src/components/custom/Workout/index.ts +55 -2
  193. package/src/components/custom/Workout/metricText.stories.tsx +69 -0
  194. package/src/components/custom/Workout/metricText.tsx +34 -0
  195. package/src/components/custom/Workout/paceTone.test.ts +29 -0
  196. package/src/components/custom/Workout/paceTone.ts +27 -0
  197. package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
  198. package/src/components/custom/index.ts +1 -0
  199. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  200. package/src/components/icons/SvgIcon.tsx +50 -0
  201. package/src/components/icons/icons.stories.tsx +122 -0
  202. package/src/components/icons/icons.test.tsx +73 -0
  203. package/src/components/icons/icons.tsx +153 -0
  204. package/src/components/icons/index.ts +4 -0
  205. package/src/components/index.ts +6 -0
  206. package/src/components/shell/BrandLockup.stories.tsx +27 -0
  207. package/src/components/shell/BrandLockup.test.tsx +23 -0
  208. package/src/components/shell/BrandLockup.tsx +47 -0
  209. package/src/components/shell/DashboardShell.stories.tsx +65 -0
  210. package/src/components/shell/DashboardShell.test.tsx +42 -0
  211. package/src/components/shell/DashboardShell.tsx +74 -0
  212. package/src/components/shell/DeviceIndicator.stories.tsx +28 -0
  213. package/src/components/shell/DeviceIndicator.test.tsx +26 -0
  214. package/src/components/shell/DeviceIndicator.tsx +52 -0
  215. package/src/components/shell/DeviceMenu.stories.tsx +47 -0
  216. package/src/components/shell/DeviceMenu.test.tsx +40 -0
  217. package/src/components/shell/DeviceMenu.tsx +65 -0
  218. package/src/components/shell/DeviceRow.stories.tsx +38 -0
  219. package/src/components/shell/DeviceRow.test.tsx +37 -0
  220. package/src/components/shell/DeviceRow.tsx +58 -0
  221. package/src/components/shell/NavItem.stories.tsx +52 -0
  222. package/src/components/shell/NavItem.test.tsx +35 -0
  223. package/src/components/shell/NavItem.tsx +75 -0
  224. package/src/components/shell/README.md +116 -0
  225. package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
  226. package/src/components/shell/SessionStatePill.stories.tsx +27 -0
  227. package/src/components/shell/SessionStatePill.test.tsx +25 -0
  228. package/src/components/shell/SessionStatePill.tsx +46 -0
  229. package/src/components/shell/SideNav.stories.tsx +81 -0
  230. package/src/components/shell/SideNav.test.tsx +51 -0
  231. package/src/components/shell/SideNav.tsx +74 -0
  232. package/src/components/shell/TopBar.stories.tsx +49 -0
  233. package/src/components/shell/TopBar.test.tsx +36 -0
  234. package/src/components/shell/TopBar.tsx +89 -0
  235. package/src/components/shell/index.ts +13 -0
  236. package/src/components/ui/alert/Alert.stories.tsx +1 -1
  237. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  238. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  239. package/src/components/ui/badge/Badge.stories.tsx +1 -1
  240. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  241. package/src/components/ui/button/Button.stories.tsx +1 -1
  242. package/src/components/ui/card/Card.stories.tsx +1 -1
  243. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  244. package/src/components/ui/chip/Chip.stories.tsx +1 -1
  245. package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
  246. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  247. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  248. package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
  249. package/src/components/ui/form-field/FormField.stories.tsx +1 -1
  250. package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
  251. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  252. package/src/components/ui/index.ts +1 -0
  253. package/src/components/ui/indicator/Indicator.stories.tsx +38 -2
  254. package/src/components/ui/indicator/Indicator.test.tsx +20 -1
  255. package/src/components/ui/indicator/Indicator.tsx +50 -6
  256. package/src/components/ui/indicator/index.ts +1 -1
  257. package/src/components/ui/input/Input.stories.tsx +1 -1
  258. package/src/components/ui/link/Link.stories.tsx +1 -1
  259. package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
  260. package/src/components/ui/menu/Menu.stories.tsx +1 -1
  261. package/src/components/ui/modal/Modal.stories.tsx +1 -1
  262. package/src/components/ui/pill/Pill.stories.tsx +1 -1
  263. package/src/components/ui/popover/Popover.stories.tsx +1 -1
  264. package/src/components/ui/progress/Progress.stories.tsx +1 -1
  265. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  266. package/src/components/ui/section/Section.stories.tsx +1 -1
  267. package/src/components/ui/select/Select.stories.tsx +1 -1
  268. package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
  269. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  270. package/src/components/ui/spinner/Spinner.tsx +3 -2
  271. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  272. package/src/components/ui/surface/Surface.stories.tsx +1 -1
  273. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  274. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  275. package/src/components/ui/tile/Tile.stories.tsx +79 -0
  276. package/src/components/ui/tile/Tile.test.tsx +48 -0
  277. package/src/components/ui/tile/Tile.tsx +64 -0
  278. package/src/components/ui/tile/index.ts +2 -0
  279. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  280. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  281. package/src/components/ui/toolbar-button/ToolbarButton.tsx +2 -1
  282. package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
  283. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
  284. package/src/hooks/index.ts +2 -0
  285. package/src/hooks/useTimer.test.ts +150 -0
  286. package/src/hooks/useTimer.ts +77 -0
  287. package/src/index.ts +3 -0
  288. package/src/pages.ts +1 -1
  289. package/src/stories/PresetShowcase.stories.tsx +1 -1
  290. package/src/stories/ThemePresets.stories.tsx +1 -1
  291. package/src/theme/ColorSystem.stories.tsx +323 -0
  292. package/src/theme/Colors.stories.tsx +74 -9
  293. package/src/theme/barrel.ts +1 -0
  294. package/src/theme/config.ts +20 -3
  295. package/src/theme/elevation.stories.tsx +1 -1
  296. package/src/theme/extracted-colors-dataviz.ts +21 -0
  297. package/src/theme/extracted-colors-ui.ts +20 -0
  298. package/src/theme/global.css +69 -53
  299. package/src/theme/gradients.test.ts +25 -0
  300. package/src/theme/gradients.ts +36 -0
  301. package/src/theme/shadows.stories.tsx +1 -1
  302. package/src/theme/tokens/primitives.test.ts +232 -0
  303. package/src/theme/tokens/primitives.ts +238 -87
  304. package/src/theme/tokens/semantic.ts +101 -81
  305. package/src/theme/workout-tokens.ts +26 -21
  306. package/src/utils/avatar-color.ts +3 -3
  307. package/tailwind.config.js +15 -3
@@ -1,5 +1,8 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { View, Text, Pressable, type ViewProps } from 'react-native'
3
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
+
5
+ const t = getSemanticColors('dark')
3
6
 
4
7
  // Aliases for spec compatibility: under=behind, maintenance=ontrack, productive=target
5
8
  export type VolumeStatus = 'untrained' | 'behind' | 'ontrack' | 'target' | 'over'
@@ -13,10 +16,10 @@ export interface MuscleGroupChipProps extends ViewProps {
13
16
 
14
17
  const dotColorMap: Record<VolumeStatus, string> = {
15
18
  untrained: '#2C2C2C',
16
- behind: '#406D87',
17
- ontrack: '#14B8A6',
18
- target: '#FF7900',
19
- over: '#D14343',
19
+ behind: t['brand-secondary'],
20
+ ontrack: t['status-success'],
21
+ target: t['brand-primary'],
22
+ over: t['status-error'],
20
23
  }
21
24
 
22
25
  export function MuscleGroupChip({
@@ -3,7 +3,7 @@ import { View } from 'react-native'
3
3
  import { PlaceholderStrip } from './PlaceholderStrip'
4
4
 
5
5
  const meta: Meta<typeof PlaceholderStrip> = {
6
- title: 'Custom/Workout/PlaceholderStrip',
6
+ title: 'Workout/PlaceholderStrip',
7
7
  component: PlaceholderStrip,
8
8
  tags: ['autodocs'],
9
9
  argTypes: {
@@ -3,7 +3,7 @@ import { View } from 'react-native'
3
3
  import { PrBadge } from './PrBadge'
4
4
 
5
5
  const meta: Meta<typeof PrBadge> = {
6
- title: 'Custom/Workout/PrBadge',
6
+ title: 'Components/Atoms/PrBadge',
7
7
  component: PrBadge,
8
8
  tags: ['autodocs'],
9
9
  argTypes: {
@@ -3,6 +3,9 @@ import { useEffect, useState } from 'react'
3
3
  import { View, Text, Animated, Easing, type ViewProps } from 'react-native'
4
4
  import { StarIcon } from './icons'
5
5
  import { BaseBadge } from './BaseBadge'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
7
+
8
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
6
9
 
7
10
  export type PRType = 'e1rm' | 'weight' | 'reps' | 'volume' | 'velocity'
8
11
 
@@ -66,7 +69,7 @@ export function PrBadge({
66
69
  testID="pr-badge-star"
67
70
  {...props}
68
71
  >
69
- <StarIcon size={14} color="#FF7900" fill="#FF7900" strokeWidth={2} />
72
+ <StarIcon size={14} color={BRAND_PRIMARY} fill={BRAND_PRIMARY} strokeWidth={2} />
70
73
  </View>
71
74
  ) : (
72
75
  <BaseBadge
@@ -78,7 +81,7 @@ export function PrBadge({
78
81
  <Text
79
82
  style={{
80
83
  fontSize: 12,
81
- color: '#FF7900',
84
+ color: BRAND_PRIMARY,
82
85
  fontWeight: '700',
83
86
  fontFamily: 'Inter, sans-serif',
84
87
  }}
@@ -12,7 +12,7 @@ const records: PrRecord[] = [
12
12
  ]
13
13
 
14
14
  const meta: Meta<typeof PrHistoryModal> = {
15
- title: 'Custom/Workout/PrHistoryModal',
15
+ title: 'Workout/PrHistoryModal',
16
16
  component: PrHistoryModal,
17
17
  tags: ['autodocs'],
18
18
  argTypes: {
@@ -3,10 +3,11 @@ import { View, Text, Pressable, type ViewProps } from 'react-native'
3
3
  import { StarIcon } from './icons'
4
4
  import { Drawer, DrawerBody } from '../../ui/drawer'
5
5
  import { resolveColor } from '../../../theme/resolve-color'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
6
7
 
7
- const BRAND_PRIMARY = '#FF7900'
8
- const RECENT_BORDER = 'rgba(255,176,32,0.3)'
9
- const RECENT_GLOW = '0 0 12px rgba(255,176,32,0.06)'
8
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
9
+ const RECENT_BORDER = 'rgba(249,180,21,0.3)'
10
+ const RECENT_GLOW = '0 0 12px rgba(249,180,21,0.06)'
10
11
  // Native-safe fallback for the conditional row border (recent uses a computed
11
12
  // rgba, so className can't express both branches).
12
13
  const BORDER_DEFAULT = resolveColor('border-default')
@@ -18,10 +18,7 @@ const lowerMuscles: WorkoutMuscleGroup[] = [
18
18
  const upperExercises: ExerciseCardProps[] = [
19
19
  {
20
20
  name: 'Barbell Bench Press',
21
- state: 'collapsed',
22
- onToggle: () => {},
23
21
  summary: { sets: 4, reps: 8, weight: 185, unit: 'lbs' },
24
- e1rm: { value: 235, unit: 'lbs' },
25
22
  isPR: true,
26
23
  setVelocities: [
27
24
  [1.05, 0.94, 0.82, 0.74],
@@ -31,16 +28,12 @@ const upperExercises: ExerciseCardProps[] = [
31
28
  },
32
29
  {
33
30
  name: 'Incline Dumbbell Press',
34
- state: 'collapsed',
35
- onToggle: () => {},
36
31
  summary: { sets: 3, reps: 10, weight: 70, unit: 'lbs' },
37
- e1rm: { value: 92, unit: 'lbs' },
38
32
  totalPlannedSets: 3,
39
33
  },
40
34
  {
41
35
  name: 'Cable Triceps Pushdown',
42
- state: 'upcoming',
43
- onToggle: () => {},
36
+ upcoming: true,
44
37
  prescription: '3×12-15 @ RPE 8',
45
38
  previousBest: '50 lbs × 14',
46
39
  },
@@ -49,23 +42,18 @@ const upperExercises: ExerciseCardProps[] = [
49
42
  const lowerExercises: ExerciseCardProps[] = [
50
43
  {
51
44
  name: 'Back Squat',
52
- state: 'collapsed',
53
- onToggle: () => {},
54
45
  summary: { sets: 5, reps: 5, weight: 275, unit: 'lbs' },
55
- e1rm: { value: 335, unit: 'lbs' },
56
46
  totalPlannedSets: 5,
57
47
  },
58
48
  {
59
49
  name: 'Romanian Deadlift',
60
- state: 'upcoming',
61
- onToggle: () => {},
50
+ upcoming: true,
62
51
  prescription: '3×8-10 @ RPE 8',
63
52
  previousBest: '225 lbs × 9',
64
53
  },
65
54
  {
66
55
  name: 'Leg Press',
67
- state: 'upcoming',
68
- onToggle: () => {},
56
+ upcoming: true,
69
57
  prescription: '3×12 @ RPE 9',
70
58
  previousBest: '450 lbs × 12',
71
59
  },
@@ -240,7 +228,7 @@ const accumulationDone: PlanMeso = {
240
228
  const mesos: PlanMeso[] = [accumulationDone, accumulation, intensification, peak]
241
229
 
242
230
  const meta: Meta<typeof ProgramPlanningPage> = {
243
- title: 'Custom/Workout/ProgramPlanningPage',
231
+ title: 'Pages/Program Planning',
244
232
  component: ProgramPlanningPage,
245
233
  parameters: { layout: 'fullscreen' },
246
234
  tags: ['autodocs'],
@@ -8,8 +8,9 @@ import { WorkoutCard, type WorkoutStatus, type WorkoutMuscleGroup } from './Work
8
8
  import { type WorkoutPillStatus } from './WorkoutPill'
9
9
  import { type ExerciseCardProps } from './ExerciseCard'
10
10
  import { cn } from '../../../utils/cn'
11
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
11
12
 
12
- const BRAND_PRIMARY = '#FF7900'
13
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
13
14
 
14
15
  /** A single workout within a planned week, plus its exercise breakdown. */
15
16
  export interface PlanWorkout {
@@ -153,7 +154,11 @@ function Breadcrumbs({ crumbs, onNavigate }: BreadcrumbsProps) {
153
154
  const isLast = index === crumbs.length - 1
154
155
  return (
155
156
  <View key={crumb.key} className="flex-row items-center" style={{ gap: 4 }}>
156
- {index > 0 && <Text className="text-text-tertiary" style={{ fontSize: 12 }}>{'›'}</Text>}
157
+ {index > 0 && (
158
+ <Text className="text-text-tertiary" style={{ fontSize: 12 }}>
159
+ {'›'}
160
+ </Text>
161
+ )}
157
162
  <Pressable
158
163
  onPress={() => onNavigate(crumb.level)}
159
164
  disabled={isLast}
@@ -269,13 +274,11 @@ interface WorkoutLevelProps {
269
274
  }
270
275
 
271
276
  function WorkoutLevel({ workout, expandedExercise, onToggleExercise }: WorkoutLevelProps) {
277
+ // `upcoming` cards ignore `expanded`, so it's safe to wire the toggle uniformly.
272
278
  const exercises = workout.exercises.map((exercise, index) => ({
273
279
  ...exercise,
274
- state:
275
- exercise.state === 'upcoming'
276
- ? exercise.state
277
- : ((index === expandedExercise ? 'expanded' : 'collapsed') as ExerciseCardProps['state']),
278
- onToggle: () => onToggleExercise(index),
280
+ expanded: index === expandedExercise,
281
+ onExpandedChange: () => onToggleExercise(index),
279
282
  }))
280
283
  return (
281
284
  <View testID="program-planning-page-workout-level">
@@ -10,15 +10,15 @@ component is pure import churn with no file moves.
10
10
  **Atoms** — single-purpose, no cross-component state:
11
11
  BaseBadge · WeightBadge · PrBadge · StatusDot · PlaceholderStrip · TempoBar ·
12
12
  DeviationBar · IntensityBar · WorkoutPill · MuscleGroupChip · Sparkline ·
13
- SupersetWrapper · InputBar
13
+ SupersetWrapper · InputBar · MetricCell · SetsRepsLoad · ExerciseIndicator · SetBar
14
14
 
15
15
  **Molecules** — compose atoms, own a little local state:
16
16
  VelocityStrip · SetRow · TempoDisplay · RestTimer · MesoProgressBar · WeekRow ·
17
- WorkoutCard
17
+ WorkoutCard · SetStrip · ExerciseHeading · ExerciseCardHeading
18
18
 
19
19
  **Organisms** — full features, often with their own data contract:
20
- ExerciseCard · MesoCard · MesoStatusCard · PrHistoryModal · ReadinessCheck ·
21
- StrengthTrendChart · CapacityBandChart · BodyMap · BodyMapDetailPanel
20
+ ExerciseCard · SessionRail · MesoCard · MesoStatusCard · PrHistoryModal ·
21
+ ReadinessCheck · StrengthTrendChart · CapacityBandChart · BodyMap · BodyMapDetailPanel
22
22
 
23
23
  **Pages** — phone-shaped reference screens (whole-screen compositions):
24
24
  ActiveWorkoutPage · ExerciseDetailPage · ProgramPlanningPage · TrainingStatusPage
@@ -44,7 +44,149 @@ type props without pulling the dependency.
44
44
  - **BaseBadge is an internal composition primitive** — the shared shell that
45
45
  WeightBadge and PrBadge build on. It is exempt from orphan accounting; it is not
46
46
  meant to be consumed directly even though it is exported for composition.
47
+ - **MetricCell is an internal composition primitive** — the shared Inter · 600 ·
48
+ letter-spacing-1 value/separator cell. `TempoDisplay` (tempo digits) and
49
+ `SetsRepsLoad` (sets × reps @ load) both compose it so the two read as one
50
+ visual language. Exported for composition, not for direct app use.
51
+ - **S3 session-rail family** — `SessionRail` (organism) composes the standalone
52
+ `ExerciseCardHeading` molecule per exercise, which composes an `ExerciseHeading`
53
+ info block + a `SetStrip`:
54
+
55
+ ```
56
+ SessionRail
57
+ └─ ExerciseCardHeading (complete standalone heading)
58
+ ├─ ExerciseHeading (name/indicator + metrics, no strip)
59
+ │ ├─ ExerciseIndicator
60
+ │ ├─ SetsRepsLoad → MetricCell
61
+ │ └─ TempoDisplay (showLabel={false}) → MetricCell
62
+ └─ SetStrip
63
+ └─ SetBar × N (one set's per-rep colour bar)
64
+
65
+ ExerciseCard state="rail" ──delegates to──▶ ExerciseCardHeading
66
+ ```
67
+
68
+ `ExerciseCard`'s `rail` state is now a thin adapter that delegates to
69
+ `ExerciseCardHeading` (so the heading is reusable without the card). `SetBar`
70
+ owns the per-set colour/pulse logic; `SetStrip` lays several side by side.
71
+ Beyond the flat `done`/`active`/`todo` sets, `SetStripSet` models set-type
72
+ prescriptions: `range` (variable rep-range — range-max segments, committed-todo grey
73
+ vs. variable-todo cyan), `drop` (one set, sub-loads split by 2px notches), `myo`
74
+ (done rest-pause — activation + clusters split by 3px cluster gaps), and
75
+ `myo-upcoming` (planned myo, length unknown — grey activation + a fading, right-open
76
+ cyan "clusters-to-failure" trail). The chunk-size *pattern* carries set-type identity:
77
+ **butted reps (0) < notch (2px) < cluster gap (3px) < set gap (5px)**. `cyan-900`
78
+ (`SET_STRIP_VARIABLE_COLOR`) is the shared "variable / unknown / opportunity" pin.
79
+ `SegmentedBar` carries these via additive `leadingGap` (per-segment left margin) and
80
+ static `opacity` props — the flat sets stay byte-identical.
81
+ `SetStrip`/`SetBar` colors are the real titan ramp pins (`primitiveRamps` red-600 /
82
+ orange-400 / amber-300 / green-300); the rail surfaces bind to the charcoal ramp +
83
+ a subtle neumorphic inset (`neumorphicShadows.charcoal.pressed.subtle`). The heading
84
+ design is locked in `coordination/.../S3-sessionrail/DECISIONS-ExerciseRow.md`; the
85
+ exploration specimens live under `Custom/Workout/Explorations/*` (do not repoint yet).
86
+ In Storybook the family nests by composition under **`Shell/SessionRail/…`**
87
+ (organism → `ExerciseCardHeading` → `ExerciseHeading` → its atoms/molecules →
88
+ `MetricCell`, and `SetStrip` → `SetBar`; `ExerciseCard` sits as a leaf whose
89
+ Composes link points at `ExerciseCardHeading`), each node's autodocs carrying a
90
+ **Composes** link down the tree — matching the S1/S2 shell families. (The component
91
+ files stay flat on disk in `custom/Workout/`; only the story `title`s build the tree.)
92
+
93
+ - **VelocityStrip set-type modes (`set` prop)** — beyond the flat `velocities`
94
+ array (unchanged, still the source of truth for `SetRow` / `ExerciseCard`), the
95
+ strip accepts an optional structured `VelocitySet` descriptor and renders the
96
+ strength set-type vocabulary as a typed slot list. Slots are
97
+ `rep` (velocity-coloured) · `todo` (charcoal-300 grey) · `variable` /
98
+ `continue` (`SET_STRIP_VARIABLE_COLOR` cyan-900; `continue` adds a cyan-800
99
+ outline to read as "keep going"). Types: `straight` (done + grey todo to
100
+ `planned`), `range` (committed grey + a cyan variable window `floor..max`),
101
+ `amrap` (reps + a trailing cyan continue), `drop` (sub-loads split by a wide
102
+ notch), `myo` (activation + clusters split by wide gaps, `open` adds a continue),
103
+ `cluster` (fixed count grouped by wide intra-rest gaps). The gap hierarchy is
104
+ **butted reps (`REP_GAP` 2px) < chunk boundary (`WIDE_GAP` 7px)**, applied as
105
+ per-slot `marginLeft` (the mini container drops its uniform `gap` so wide gaps
106
+ render; a no-`set` strip is `[0, 2, 2, …]`, i.e. byte-identical to before). The
107
+ done-velocity array (`velocities`, or subloads/activation+clusters flattened) is
108
+ derived from the `set` and drives the mean · loss · zone summary either way. The
109
+ `mini` variant is the primary set-type surface; `expanded` gives `straight` the
110
+ active-set spotlight (velocity-height done reps + short grey stubs) and renders
111
+ the advanced types as a short mini-style encoding. Every modality is documented
112
+ with a copy-paste `set` config in **`Workout/DataViz/VelocityStrip/Set
113
+ Modalities`** (each card carries a `Collapse` accordion; promoted from the
114
+ now-deleted `S3SetModalities` Lab specimen).
47
115
  - Badge icons (WeightBadge's dumbbell, PrBadge / PrHistoryModal's star) are inline
48
116
  SVGs (`./icons.tsx`), not `lucide-react` — that dependency was dropped in 0.5.0
49
117
  to keep the root barrel light. The SVG paths mirror lucide's glyphs so rendering
50
118
  is unchanged.
119
+ - **`ExerciseIndicator` taxonomy (LOCKED, TD-03.51)** — one precedence-ranked slot
120
+ per exercise, six distinct kinds over four tier colors (bound to titan status
121
+ tokens, read as literal hex via `getSemanticColors('dark')` — the TempoBar
122
+ pattern). Glyph = the specific signal; color = the severity tier. Chips are
123
+ outlined (border + glyph, no fill) so they never collide with the *filled*
124
+ velocity strip, and static (no pulse — they are chrome). Precedence, high → low:
125
+
126
+ | # | kind | tier | glyph (mirrors lucide) |
127
+ |---|------|------|------------------------|
128
+ | 1 | `imbalance` | danger (`status-error`) | `Scale` |
129
+ | 2 | `overshoot` | danger (`status-error`) | `AlertTriangle` |
130
+ | 3 | `velocity-loss` | warning (`status-warning`) | `TrendingDown` |
131
+ | 4 | `missed-reps` | warning (`status-warning`) | `CircleSlash` |
132
+ | 5 | `pr` | success (`status-success`) | `Award` |
133
+ | 6 | `info` | info (`status-info`) | `Info` |
134
+
135
+ `resolveIndicator(candidates)` collapses the active signals to the single
136
+ highest-precedence kind to show (`undefined` when none). Alerts outrank `pr`. The
137
+ glyphs are new inline SVGs in the shared `components/icons` set (not the local
138
+ `./icons.tsx` badge glyphs). The old generic `issue` kind is superseded by the
139
+ specific danger/warning kinds and was removed.
140
+
141
+ ## Shell/SessionRail feature area
142
+
143
+ The live-workout rail is a **feature assembly**: the genuinely rail-specific parts
144
+ (header / pace / expand drawer / set-table header) nest under `Shell/SessionRail/*`,
145
+ while reused leaves stay in their `Workout/*` tier and are **referenced via Composes
146
+ links, not re-homed** (`SetRow`, `ExerciseCardHeading`). This is the one deliberate
147
+ exception to the flat `Shell` taxonomy — the NEW components below are the only
148
+ ones that nest.
149
+
150
+ ```
151
+ Shell/SessionRail (organism — SessionRail.tsx)
152
+ ├─ SessionHeader (NEW · real — title, stat tiles, chunked pace bar, ⏱ readout)
153
+ └─ ExerciseCardHeading × N (Workout/ · linked, NOT re-homed)
154
+
155
+ Shell/SessionRail/ExpandedDrawer (NEW · 🚧 WIP — opens when an exercise expands)
156
+ ├─ TableHeader (NEW · 🚧 WIP — exported as `SetTableHeader`; the generic
157
+ │ `TableHeader` name is owned by the DataViz `Table`)
158
+ └─ SetRow × N (Workout/ · linked, NOT re-homed)
159
+ ```
160
+
161
+ - `SessionHeader` is the rail's session glance: the title over a stat row (Volume/Load/
162
+ Fatigue live, Date/Time/Until upcoming) and a per-exercise chunked pace bar with a
163
+ mono sets label + ⏱ readout. It composes the merged primitives (`MetricTiles` /
164
+ `ScheduleTiles` + `SegmentedProgressBar` + `TimerReadout`); the old footer pace tile is
165
+ gone — the header carries the glance. It is the REAL (non-WIP) new component.
166
+ - `SetTableHeader` (story leaf `.../ExpandedDrawer/TableHeader`) is the expanded-set
167
+ column-header row extracted from `ExerciseCard`'s `expanded` state; `ExerciseCard`
168
+ now consumes it too (backward-compatible — it passes its own
169
+ `testID="exercise-card-column-headers"`).
170
+ - Each new node's autodocs carry a `**Tier.** Composes […] / Used-by ↑ […]` line; the
171
+ WIP two (`ExpandedDrawer`, `TableHeader`) are prefixed `**🚧 WIP / placeholder.**`.
172
+
173
+ ## Dead candidates (do NOT remove yet — pending TD-03.56 responsive unification)
174
+
175
+ Superseded by the target Shell/SessionRail scaffold; flagged now, removed only when the
176
+ responsive level views land (removal is out of scope for this scaffold ticket):
177
+
178
+ - **`setHeadingKit.tsx`** — the throwaway CSS R&D kit (raw HTML `<div>`s at the rail
179
+ width). Its own header says it moves into real components "when these decisions
180
+ harden" — which has now happened (`SessionRail` / `SessionHeader` /
181
+ `ExerciseCardHeading` / `ExerciseHeading` / `SetStrip` / `SetBar` /
182
+ `SetTableHeader`).
183
+ - **The five `Lab/Explorations` specimens that import it** — `S3FullRail`
184
+ (Full Rail), `S3SessionRailHeading` (Session Rail Heading), `S3WorkoutExpansion`
185
+ (Workout Expansion), `S3SetTypes` (Set Types), `S3SessionPace` (Session Pace).
186
+ Each is superseded by a real Shell/SessionRail component + its Storybook stories;
187
+ `S3SessionPace` in particular is superseded by the `SessionHeader` pace glance.
188
+ - **`ExerciseCard`'s `collapsed` / `upcoming` state representations** — the rail now
189
+ owns the live-list heading (`rail` state → `ExerciseCardHeading`) and the expanded
190
+ view is moving to `ExpandedDrawer`. Once the responsive unification (TD-03.56)
191
+ lands a single responsive card, the `collapsed`/`upcoming` branches of
192
+ `ExerciseCard` are the superseded representations to retire.
@@ -16,7 +16,7 @@ const staticFactors: ReadinessFactor[] = FACTOR_SEED.map((f) => ({
16
16
  }))
17
17
 
18
18
  const meta: Meta<typeof ReadinessCheck> = {
19
- title: 'Custom/Workout/ReadinessCheck',
19
+ title: 'Workout/ReadinessCheck',
20
20
  component: ReadinessCheck,
21
21
  tags: ['autodocs'],
22
22
  argTypes: {
@@ -2,12 +2,15 @@
2
2
  import { View, Text, Pressable } from 'react-native'
3
3
  import { Card } from '../../ui/card'
4
4
  import { Badge, type BadgeColor } from '../../ui/badge'
5
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
5
6
 
6
- const BRAND_PRIMARY = '#FF7900'
7
+ const t = getSemanticColors('dark')
8
+
9
+ const BRAND_PRIMARY = t['brand-primary']
7
10
  const BRAND_PRIMARY_SUBTLE = 'rgba(255,121,0,0.12)'
8
- const STATUS_SUCCESS = '#14B8A6'
9
- const STATUS_WARNING = '#FFB020'
10
- const STATUS_ERROR = '#D14343'
11
+ const STATUS_SUCCESS = t['status-success']
12
+ const STATUS_WARNING = t['status-warning']
13
+ const STATUS_ERROR = t['status-error']
11
14
 
12
15
  /** Emoji option sets per known factor, keyed by lowercase id/label. */
13
16
  const EMOJI_SETS: Record<string, readonly string[]> = {
@@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
3
3
  import { RestTimer } from './RestTimer'
4
4
 
5
5
  const meta: Meta<typeof RestTimer> = {
6
- title: 'Custom/Workout/RestTimer',
6
+ title: 'Workout/RestTimer',
7
7
  component: RestTimer,
8
8
  tags: ['autodocs'],
9
9
  argTypes: {
@@ -1,8 +1,10 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { View, Text, Pressable } from 'react-native'
3
3
  import { resolveColor } from '../../../theme/resolve-color'
4
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
5
+ import { useTimer } from '../../../hooks/useTimer'
4
6
 
5
- const BRAND_PRIMARY = '#FF7900'
7
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
6
8
 
7
9
  export interface RestTimerProps {
8
10
  totalSeconds: number
@@ -24,17 +26,21 @@ export function RestTimer({
24
26
  visible,
25
27
  displayOnly = false,
26
28
  }: RestTimerProps) {
27
- if (!visible) return null
28
-
29
- const remainingMs = Math.max(0, totalSeconds * 1000 - elapsedMs)
29
+ // useTimer owns the countdown math (remaining/progress/mm:ss); a zero-duration
30
+ // timer is complete, which keeps the width out of the 0/0 === NaN case.
31
+ const {
32
+ remainingMs,
33
+ progress,
34
+ label: timeDisplay,
35
+ } = useTimer({
36
+ mode: 'down',
37
+ durationMs: totalSeconds * 1000,
38
+ elapsedMs,
39
+ })
30
40
  const remainingSec = Math.ceil(remainingMs / 1000)
31
- const minutes = Math.floor(remainingSec / 60)
32
- const seconds = remainingSec % 60
33
- const timeDisplay = `${minutes}:${String(seconds).padStart(2, '0')}`
34
- // A zero-duration timer is already complete; guard the 0 / 0 === NaN case
35
- // that would otherwise emit width:'NaN%'.
36
- const totalMs = totalSeconds * 1000
37
- const progressPct = totalMs > 0 ? Math.min(100, (elapsedMs / totalMs) * 100) : 100
41
+ const progressPct = progress * 100
42
+
43
+ if (!visible) return null
38
44
 
39
45
  return (
40
46
  <View