@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,68 +1,7 @@
1
- // Inline SVG icons for the Workout badges replaces the `lucide-react`
2
- // dependency (dep hygiene, titan 0.5.0). The paths mirror lucide-react's
3
- // `Dumbbell` and `Star` glyphs verbatim so rendering is pixel-identical to the
4
- // previous `<Dumbbell>` / `<Star>` output (same viewBox, stroke, fill, and
5
- // round line caps). Rendered as raw `<svg>` DOM elements — the same web-SVG
6
- // mechanism `Progress.tsx` uses, and the same surface `lucide-react` produced
7
- // (both are web-only SVG; native icon rendering was never wired here).
1
+ // Workout icons were promoted to the shared icon primitive
2
+ // (`components/icons`). This shim re-exports them so existing Workout consumers
3
+ // keep their local `./icons` import unchanged.
4
+ export { DumbbellIcon, StarIcon } from '../../icons'
8
5
 
9
- export interface WorkoutIconProps {
10
- /** Rendered width/height in px (viewBox is fixed 24×24). */
11
- size?: number
12
- /** Stroke color. */
13
- color?: string
14
- /** Fill color; defaults to `none` (outline only). */
15
- fill?: string
16
- /** Stroke width. */
17
- strokeWidth?: number
18
- }
19
-
20
- /** Dumbbell glyph (mirrors lucide-react `Dumbbell`). Outline-only by default. */
21
- export function DumbbellIcon({
22
- size = 24,
23
- color = 'currentColor',
24
- fill = 'none',
25
- strokeWidth = 2,
26
- }: WorkoutIconProps) {
27
- return (
28
- <svg
29
- width={size}
30
- height={size}
31
- viewBox="0 0 24 24"
32
- fill={fill}
33
- stroke={color}
34
- strokeWidth={strokeWidth}
35
- strokeLinecap="round"
36
- strokeLinejoin="round"
37
- >
38
- <path d="M14.4 14.4 9.6 9.6" />
39
- <path d="M18.657 21.485a2 2 0 1 1-2.829-2.828l-1.767 1.768a2 2 0 1 1-2.829-2.829l6.364-6.364a2 2 0 1 1 2.829 2.829l-1.768 1.767a2 2 0 1 1 2.828 2.829z" />
40
- <path d="m21.5 21.5-1.4-1.4" />
41
- <path d="M3.9 3.9 2.5 2.5" />
42
- <path d="M6.404 12.768a2 2 0 1 1-2.829-2.829l1.768-1.767a2 2 0 1 1-2.828-2.829l2.828-2.828a2 2 0 1 1 2.829 2.828l1.767-1.768a2 2 0 1 1 2.829 2.829z" />
43
- </svg>
44
- )
45
- }
46
-
47
- /** Star glyph (mirrors lucide-react `Star`). Pass `fill` for a solid star. */
48
- export function StarIcon({
49
- size = 24,
50
- color = 'currentColor',
51
- fill = 'none',
52
- strokeWidth = 2,
53
- }: WorkoutIconProps) {
54
- return (
55
- <svg
56
- width={size}
57
- height={size}
58
- viewBox="0 0 24 24"
59
- fill={fill}
60
- stroke={color}
61
- strokeWidth={strokeWidth}
62
- strokeLinecap="round"
63
- strokeLinejoin="round"
64
- >
65
- <path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" />
66
- </svg>
67
- )
68
- }
6
+ /** @deprecated Use `IconProps` from `components/icons`. */
7
+ export type { IconProps as WorkoutIconProps } from '../../icons'
@@ -29,6 +29,7 @@ export { WorkoutPill, type WorkoutPillProps, type WorkoutPillStatus } from './Wo
29
29
  export {
30
30
  VelocityStrip,
31
31
  type VelocityStripProps,
32
+ type VelocitySet,
32
33
  type VelocityZoneBandProp,
33
34
  getVelocityZoneColor,
34
35
  getVelocityZoneName,
@@ -37,10 +38,46 @@ export {
37
38
  } from './VelocityStrip'
38
39
  export { MuscleGroupChip, type MuscleGroupChipProps, type VolumeStatus } from './MuscleGroupChip'
39
40
  export { Sparkline, type SparklineProps } from './Sparkline'
40
- export { SetRow, type SetRowProps, type SetRowMode } from './SetRow'
41
+ export { SetRow, type SetRowProps, type SetRowState, type SetRowUnit } from './SetRow'
41
42
  export { InputBar, type InputBarProps } from './InputBar'
42
43
  export { RestTimer, type RestTimerProps } from './RestTimer'
43
- export { ExerciseCard, type ExerciseCardProps, type ExerciseCardState } from './ExerciseCard'
44
+ export { MetricCell, type MetricCellProps, METRIC_FONT } from './metricText'
45
+ export { SetsRepsLoad, type SetsRepsLoadProps } from './SetsRepsLoad'
46
+ export { SegmentedBar, type SegmentedBarProps, type SegmentedBarSegment } from './SegmentedBar'
47
+ export { paceTone, paceToneColor, type PaceTone } from './paceTone'
48
+ export {
49
+ SegmentedProgressBar,
50
+ type SegmentedProgressBarProps,
51
+ type SegmentedProgressBarSegment,
52
+ } from './SegmentedProgressBar'
53
+ export { MetricTiles, type MetricTilesProps, type MetricTileData } from './MetricTiles'
54
+ export { ScheduleTiles, type ScheduleTilesProps } from './ScheduleTiles'
55
+ export { SetBar, type SetBarProps } from './SetBar'
56
+ export {
57
+ SetStrip,
58
+ type SetStripProps,
59
+ type SetStripSet,
60
+ SET_STRIP_ZONES,
61
+ SET_STRIP_VARIABLE_COLOR,
62
+ velocityZoneColor,
63
+ } from './SetStrip'
64
+ export {
65
+ ExerciseIndicator,
66
+ resolveIndicator,
67
+ INDICATOR_PRECEDENCE,
68
+ type ExerciseIndicatorProps,
69
+ type ExerciseIndicatorKind,
70
+ } from './ExerciseIndicator'
71
+ export { ExerciseHeading, type ExerciseHeadingProps } from './ExerciseHeading'
72
+ export { ExerciseCardHeading, type ExerciseCardHeadingProps } from './ExerciseCardHeading'
73
+ export { ExerciseCard, type ExerciseCardProps } from './ExerciseCard'
74
+ export { SetTableHeader, type SetTableHeaderProps } from './SetTableHeader'
75
+ export {
76
+ SessionHeader,
77
+ type SessionHeaderProps,
78
+ type SessionHeaderPlanEntry,
79
+ } from './SessionHeader'
80
+ export { SessionRail, type SessionRailProps, type SessionRailExercise } from './SessionRail'
44
81
  export { SupersetWrapper, type SupersetWrapperProps } from './SupersetWrapper'
45
82
  export {
46
83
  MesoProgressBar,
@@ -152,3 +189,19 @@ export type {
152
189
  WorkoutProgress,
153
190
  WorkoutGroup,
154
191
  } from './ActiveWorkoutPage'
192
+ export { ZoneTrack } from './ZoneTrack'
193
+ export type {
194
+ ZoneTrackProps,
195
+ ZoneTrackZone,
196
+ ZoneTrackTick,
197
+ ZoneTrackMarker,
198
+ ZoneTrackBand,
199
+ } from './ZoneTrack'
200
+ export { FatigueMeter } from './FatigueMeter'
201
+ export type { FatigueMeterProps } from './FatigueMeter'
202
+ export { VolumeLandmarkBar } from './VolumeLandmarkBar'
203
+ export type { VolumeLandmarkBarProps, VolumeZone } from './VolumeLandmarkBar'
204
+ export { StatusPill, statusPillColor } from './StatusPill'
205
+ export type { StatusPillProps, StatusPillStatus } from './StatusPill'
206
+ export { LiveAuraFrame, liveAuraColor } from './LiveAuraFrame'
207
+ export type { LiveAuraFrameProps, LiveAuraCategory } from './LiveAuraFrame'
@@ -0,0 +1,69 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { MetricCell } from './metricText'
4
+
5
+ /**
6
+ * `MetricCell` — the shared "segmented metric" typography primitive (Inter · 600 ·
7
+ * letter-spacing 1). Value cells take a primary color, separators a muted one. The
8
+ * single source of the value/separator look shared by SetsRepsLoad and TempoDisplay.
9
+ */
10
+ const meta: Meta<typeof MetricCell> = {
11
+ title: 'Workout/MetricCell',
12
+ component: MetricCell,
13
+ tags: ['autodocs'],
14
+ parameters: {
15
+ docs: {
16
+ description: {
17
+ component:
18
+ '**Primitive.** The shared Inter·600·letter-spacing-1 value/separator cell. ' +
19
+ 'Extracted so the two prescription readouts read as one visual language instead of ' +
20
+ 'drifting copies. Used-by ↑ ' +
21
+ '[SetsRepsLoad](?path=/docs/workout-setsrepsload--docs) + ' +
22
+ '[TempoDisplay](?path=/docs/workout-tempodisplay--docs).',
23
+ },
24
+ },
25
+ },
26
+ argTypes: {
27
+ color: { control: 'color' },
28
+ fontSize: { control: { type: 'range', min: 9, max: 24, step: 1 } },
29
+ },
30
+ decorators: [
31
+ (Story) => (
32
+ <View style={{ padding: 20, backgroundColor: '#131313' }}>
33
+ <Story />
34
+ </View>
35
+ ),
36
+ ],
37
+ }
38
+
39
+ export default meta
40
+ type Story = StoryObj<typeof MetricCell>
41
+
42
+ export const ValueCell: Story = {
43
+ args: { children: '145', color: '#F3F4F6', fontSize: 11 },
44
+ }
45
+
46
+ export const Separator: Story = {
47
+ args: { children: '×', color: '#6B7280', fontSize: 11 },
48
+ parameters: { docs: { description: { story: 'A muted separator cell (× / @ / -).' } } },
49
+ }
50
+
51
+ export const InContext: Story = {
52
+ render: () => (
53
+ <View style={{ flexDirection: 'row', alignItems: 'baseline' }}>
54
+ <MetricCell color="#F3F4F6">5</MetricCell>
55
+ <MetricCell color="#6B7280">&nbsp;×&nbsp;</MetricCell>
56
+ <MetricCell color="#F3F4F6">8</MetricCell>
57
+ <MetricCell color="#6B7280">&nbsp;@&nbsp;</MetricCell>
58
+ <MetricCell color="#F3F4F6">145</MetricCell>
59
+ <MetricCell color="#6B7280">&nbsp;lb</MetricCell>
60
+ </View>
61
+ ),
62
+ parameters: {
63
+ docs: {
64
+ description: {
65
+ story: 'Value + separator cells composed into the sets × reps @ load language.',
66
+ },
67
+ },
68
+ },
69
+ }
@@ -0,0 +1,34 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import type { ReactNode } from 'react'
3
+ import { Text } from 'react-native'
4
+
5
+ /**
6
+ * Shared "segmented metric" typography primitive — the Inter · 600 · letter-spacing 1
7
+ * value/separator cell used by both {@link TempoDisplay} (tempo digits + dashes) and
8
+ * {@link SetsRepsLoad} (sets × reps @ load). Extracted so the two read as one visual
9
+ * language instead of drifting copies.
10
+ */
11
+ export const METRIC_FONT = 'Inter, sans-serif'
12
+
13
+ export interface MetricCellProps {
14
+ children: ReactNode
15
+ /** Cell text color — value cells take a primary color, separators a muted one. */
16
+ color: string
17
+ fontSize?: number
18
+ }
19
+
20
+ export function MetricCell({ children, color, fontSize = 11 }: MetricCellProps) {
21
+ return (
22
+ <Text
23
+ style={{
24
+ fontFamily: METRIC_FONT,
25
+ fontSize,
26
+ color,
27
+ fontWeight: '600',
28
+ letterSpacing: 1,
29
+ }}
30
+ >
31
+ {children}
32
+ </Text>
33
+ )
34
+ }
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { paceTone, paceToneColor } from './paceTone'
3
+
4
+ describe('paceTone', () => {
5
+ it('reads as neutral when there is no target', () => {
6
+ expect(paceTone(0.6)).toBe('neutral')
7
+ expect(paceTone(0)).toBe('neutral')
8
+ })
9
+
10
+ it('reads as ahead when progress equals the target (inclusive)', () => {
11
+ expect(paceTone(0.5, 0.5)).toBe('ahead')
12
+ })
13
+
14
+ it('reads as ahead when progress is past the target', () => {
15
+ expect(paceTone(0.8, 0.5)).toBe('ahead')
16
+ })
17
+
18
+ it('reads as behind when progress trails the target', () => {
19
+ expect(paceTone(0.3, 0.5)).toBe('behind')
20
+ })
21
+
22
+ describe('paceToneColor', () => {
23
+ it('maps each tone to its real titan literal hex', () => {
24
+ expect(paceToneColor('ahead')).toBe('#2ED573') // status-success (green 300)
25
+ expect(paceToneColor('behind')).toBe('#F9B415') // status-warning (amber 300)
26
+ expect(paceToneColor('neutral')).toBe('#01B5D1') // cyan 400
27
+ })
28
+ })
29
+ })
@@ -0,0 +1,27 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import { primitiveRamps } from '../../../theme/tokens/primitives'
3
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
+
5
+ /** How progress compares to its target pace: `ahead`, `behind`, or `neutral` (no target). */
6
+ export type PaceTone = 'ahead' | 'behind' | 'neutral'
7
+
8
+ /**
9
+ * Classify `progress` (0..1) against an optional `target` (0..1). No target reads
10
+ * as `neutral`; at or past target is `ahead`, otherwise `behind`.
11
+ */
12
+ export function paceTone(progress: number, target?: number): PaceTone {
13
+ if (target === undefined) return 'neutral'
14
+ return progress >= target ? 'ahead' : 'behind'
15
+ }
16
+
17
+ /** Literal-hex fill for each tone (RNW-safe): success · warning · cyan pin. */
18
+ const PACE_TONE_COLORS: Record<PaceTone, string> = {
19
+ ahead: getSemanticColors('dark')['status-success'],
20
+ behind: getSemanticColors('dark')['status-warning'],
21
+ neutral: primitiveRamps.cyan[400],
22
+ }
23
+
24
+ /** The literal-hex colour for a tone. */
25
+ export function paceToneColor(tone: PaceTone): string {
26
+ return PACE_TONE_COLORS[tone]
27
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Shared kit for the S3 Session-Rail explorations (NOT a stories file — imported by the sheets under
3
+ * `Custom/Workout/Explorations/*`). Throwaway CSS R&D at the real 232px rail width, on real titan tokens.
4
+ * When these decisions harden they move into the real `ExerciseCard` + a titan `SessionRail` organism.
5
+ */
6
+ import type { ReactNode } from 'react'
7
+ import { TempoDisplay } from './TempoDisplay'
8
+
9
+ // ---- geometry
10
+ export const RAIL_W = 232
11
+ export const SET_GAP = 5
12
+
13
+ // ---- surface + text tokens (dark theme; charcoal + neutral ramps)
14
+ export const GREY = '#2C2C2C' // charcoal 300 — upcoming/placeholder
15
+ export const BORDER_SUBTLE = '#1C1C1C' // charcoal 500 — row dividers
16
+ export const T_PRIMARY = '#F3F4F6' // neutral 100
17
+ export const T_SECONDARY = '#9CA3AF' // neutral 400
18
+ export const T_TERTIARY = '#6B7280' // neutral 500
19
+ export const RAISED = '#1F1F1F' // charcoal 400 — nav + heading raised plane
20
+ export const INSET = '#131313' // charcoal 800 — sunk exercise-list column
21
+ // depth lives on the sunk list: inner shadow from the heading (top) + nav (left). Raised plane is flat.
22
+ export const INSET_SHADOW =
23
+ 'inset 0 9px 12px -6px rgba(0,0,0,0.85), inset 7px 0 10px -6px rgba(0,0,0,0.7), inset 0 -1px 0 rgba(255,255,255,0.02)'
24
+
25
+ // ---- zone colors = real titan ramp pins (primitiveRamps)
26
+ export const RED = '#D14343' // red 600
27
+ export const ORANGE = '#FF7900' // orange 400
28
+ export const AMBER = '#F9B415' // amber 300
29
+ export const GREEN = '#2ED573' // green 300
30
+ export const velColor = (v: number): string => (v < 0.5 ? RED : v < 0.75 ? ORANGE : v < 1.0 ? AMBER : GREEN)
31
+
32
+ // active-set pulse: each segment eases along its own ramp (adjacent lighter/darker steps) through the pin.
33
+ export const PULSE_CSS = `
34
+ @keyframes s3pRed{0%,100%{background:#E05254}50%{background:#A4221C}}
35
+ @keyframes s3pOrange{0%,100%{background:#FFA063}50%{background:#DA5F00}}
36
+ @keyframes s3pAmber{0%,100%{background:#FFD352}50%{background:#E08C00}}
37
+ @keyframes s3pGreen{0%,100%{background:#58F69E}50%{background:#21C05D}}`
38
+ export const pulseAnim = (c: string): string | undefined => {
39
+ const name = c === RED ? 's3pRed' : c === ORANGE ? 's3pOrange' : c === AMBER ? 's3pAmber' : c === GREEN ? 's3pGreen' : null
40
+ return name ? `${name} 1.9s ease-in-out infinite` : undefined
41
+ }
42
+
43
+ // ---- sets/reps/load in the TempoDisplay visual language (Inter · 600 · letter-spacing 1 · gray separators)
44
+ const INTER = 'Inter, sans-serif'
45
+ const SRL_SEP = '#6B7280'
46
+ function SRLCell({ children, color = T_PRIMARY }: { children: ReactNode; color?: string }) {
47
+ return <span style={{ fontFamily: INTER, fontSize: 11, fontWeight: 600, letterSpacing: 1, color }}>{children}</span>
48
+ }
49
+ export function SetsRepsLoad({ sets, reps, load, unit = 'lb' }: { sets: number; reps: number | string; load: number; unit?: string }) {
50
+ return (
51
+ <span style={{ display: 'inline-flex', alignItems: 'baseline', whiteSpace: 'nowrap' }}>
52
+ <SRLCell>{sets}</SRLCell>
53
+ <SRLCell color={SRL_SEP}>&nbsp;×&nbsp;</SRLCell>
54
+ <SRLCell>{reps}</SRLCell>
55
+ <SRLCell color={SRL_SEP}>&nbsp;@&nbsp;</SRLCell>
56
+ <SRLCell>{load}</SRLCell>
57
+ <SRLCell color={SRL_SEP}>&nbsp;{unit}</SRLCell>
58
+ </span>
59
+ )
60
+ }
61
+
62
+ // ---- per-set bar: one continuous bar, rep intensities as butted segments (no gaps); active segments pulse
63
+ export type SetState = { status: 'done'; reps: number[] } | { status: 'active'; reps: number[]; planned: number } | { status: 'todo'; planned: number }
64
+ export function setColors(s: SetState): string[] {
65
+ if (s.status === 'todo') return [GREY]
66
+ if (s.status === 'done') return s.reps.map(velColor)
67
+ return [...s.reps.map(velColor), ...Array(Math.max(0, s.planned - s.reps.length)).fill(GREY)]
68
+ }
69
+ function SetBar({ colors, h, pulse }: { colors: string[]; h: number; pulse?: boolean }) {
70
+ return (
71
+ <div style={{ display: 'flex', flex: 1, minWidth: 0, height: h, borderRadius: 2, overflow: 'hidden' }}>
72
+ {colors.map((c, i) => (
73
+ <div key={i} style={{ flex: 1, height: '100%', background: c, animation: pulse && c !== GREY ? pulseAnim(c) : undefined }} />
74
+ ))}
75
+ </div>
76
+ )
77
+ }
78
+ export function SetStrip({ sets, h }: { sets: SetState[]; h: number }) {
79
+ return (
80
+ <div style={{ display: 'flex', width: '100%', height: h, gap: SET_GAP }}>
81
+ {sets.map((s, i) => (
82
+ <SetBar key={i} colors={setColors(s)} h={h} pulse={s.status === 'active'} />
83
+ ))}
84
+ </div>
85
+ )
86
+ }
87
+
88
+ export function Indicator({ kind }: { kind: 'pr' | 'issue' | 'info' }) {
89
+ const map = { pr: ['★', ORANGE], issue: ['!', RED], info: ['i', '#2196F3'] } as const
90
+ const [glyph, color] = map[kind]
91
+ return (
92
+ <span style={{ width: 16, height: 16, borderRadius: 8, border: `1px solid ${color}`, color, fontSize: 10, fontWeight: 800, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>{glyph}</span>
93
+ )
94
+ }
95
+
96
+ const labelStyle = { fontSize: 14, fontWeight: 800, color: T_PRIMARY } as const
97
+
98
+ // ---- the finalized heading unit
99
+ export type Exercise = { name: string; sets: number; reps: number | string; load: number; tempo: [number, number, number, number]; indicator?: 'pr' | 'issue' | 'info'; setStates: SetState[]; upcoming?: boolean }
100
+ export function Heading({ ex, stripH }: { ex: Exercise; stripH: number }) {
101
+ return (
102
+ <div style={{ padding: '9px 12px', display: 'flex', flexDirection: 'column', opacity: ex.upcoming ? 0.55 : 1 }}>
103
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
104
+ <span style={labelStyle}>{ex.name}</span>
105
+ <div style={{ flex: 1 }} />
106
+ {ex.indicator && <Indicator kind={ex.indicator} />}
107
+ </div>
108
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 1 }}>
109
+ <SetsRepsLoad sets={ex.sets} reps={ex.reps} load={ex.load} />
110
+ <div style={{ flex: 1 }} />
111
+ <TempoDisplay tempo={ex.tempo} size="sm" showLabel={false} showInfo={false} />
112
+ </div>
113
+ <div style={{ marginTop: 7 }}>
114
+ <SetStrip sets={ex.setStates} h={stripH} />
115
+ </div>
116
+ </div>
117
+ )
118
+ }
119
+
120
+ // ---- sample session (per-rep mean velocities; sets fatigue across the workout, reps decay in-set)
121
+ export const reps = (n: number, start: number, span = 0.4): number[] => Array.from({ length: n }, (_, r) => +(start - (span * r) / Math.max(1, n - 1)).toFixed(3))
122
+ export const SESSION: Exercise[] = [
123
+ { name: 'Seated Cable Row', sets: 5, reps: 8, load: 145, tempo: [3, 1, 2, 0], indicator: 'pr', setStates: [1.0, 0.9, 0.8, 0.7, 0.6].map((s) => ({ status: 'done', reps: reps(8, s) })) },
124
+ { name: 'Incline DB Press', sets: 3, reps: 8, load: 70, tempo: [3, 0, 3, 0], setStates: [0.72, 0.62, 0.54].map((s) => ({ status: 'done', reps: reps(8, s) })) },
125
+ { name: 'Cable Chest Press', sets: 3, reps: 10, load: 90, tempo: [2, 1, 2, 0], indicator: 'info', setStates: [{ status: 'done', reps: reps(10, 0.72) }, { status: 'active', reps: reps(5, 0.62), planned: 10 }, { status: 'todo', planned: 10 }] },
126
+ { name: 'Standing Calf Raise', sets: 5, reps: 20, load: 25, tempo: [2, 1, 2, 0], upcoming: true, setStates: [1, 2, 3, 4, 5].map(() => ({ status: 'todo', planned: 20 })) },
127
+ { name: 'Face Pull', sets: 3, reps: '15-20', load: 40, tempo: [2, 1, 2, 0], upcoming: true, setStates: [1, 2, 3].map(() => ({ status: 'todo', planned: 18 })) },
128
+ ]
129
+
130
+ // ---- shell chrome (neumorphic): flat raised nav + heading plane; sunk inset exercise list
131
+ export function NavStub() {
132
+ return (
133
+ <div style={{ width: 60, alignSelf: 'stretch', position: 'relative', zIndex: 2, background: RAISED, display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: 12, gap: 8 }}>
134
+ {['Live', 'Plan', 'Body'].map((t) => (
135
+ <span key={t} style={{ fontSize: 8, color: T_TERTIARY, fontWeight: 700 }}>{t}</span>
136
+ ))}
137
+ </div>
138
+ )
139
+ }
140
+ export function Rail({ stripH, footer }: { stripH: number; footer?: ReactNode }) {
141
+ return (
142
+ <div style={{ width: 246, background: INSET, display: 'flex', flexDirection: 'column' }}>
143
+ <div style={{ position: 'relative', zIndex: 2, padding: '11px 12px 10px', background: RAISED }}>
144
+ <div style={{ fontSize: 9, fontWeight: 800, letterSpacing: 1, textTransform: 'uppercase', color: T_TERTIARY, display: 'flex', alignItems: 'center', gap: 5 }}>
145
+ <span style={{ width: 6, height: 6, borderRadius: 3, background: GREEN }} />
146
+ Live session
147
+ </div>
148
+ <div style={{ fontSize: 14, fontWeight: 800, marginTop: 3, color: T_PRIMARY }}>Pull A · Intensification</div>
149
+ <div style={{ fontSize: 10, color: T_SECONDARY, marginTop: 2, fontFamily: 'monospace' }}>ex 3/5 · set 2 live</div>
150
+ </div>
151
+ <div style={{ flex: 1, background: INSET, boxShadow: INSET_SHADOW }}>
152
+ {SESSION.map((ex, i) => (
153
+ <div key={i} style={{ borderBottom: i < SESSION.length - 1 ? `1px solid ${BORDER_SUBTLE}` : undefined }}>
154
+ <Heading ex={ex} stripH={stripH} />
155
+ </div>
156
+ ))}
157
+ </div>
158
+ {footer}
159
+ </div>
160
+ )
161
+ }
162
+
163
+ // ---- page scaffold shared by the sheets
164
+ export function Page({ title, note, children }: { title: string; note: string; children: ReactNode }) {
165
+ return (
166
+ <div style={{ minHeight: '100vh', background: '#0A0A0A', color: T_PRIMARY, padding: 28, display: 'flex', flexDirection: 'column', gap: 22, fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }}>
167
+ <style>{PULSE_CSS}</style>
168
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
169
+ <span style={{ fontSize: 20, fontWeight: 800 }}>{title}</span>
170
+ <span style={{ fontSize: 12, color: T_SECONDARY, maxWidth: 820 }}>{note}</span>
171
+ </div>
172
+ {children}
173
+ </div>
174
+ )
175
+ }
176
+ export const sectionTitle = { fontSize: 11, fontWeight: 800, letterSpacing: 1.2, textTransform: 'uppercase', color: ORANGE } as const
177
+ export const monoTag = { fontFamily: 'monospace', fontSize: 11, color: T_SECONDARY } as const
@@ -5,6 +5,7 @@ export * from './Table'
5
5
  export * from './EmptyState'
6
6
  export * from './stepper'
7
7
  export * from './DateTime'
8
+ export * from './TimerReadout'
8
9
  export * from './Metric'
9
10
  export * from './Workout'
10
11
  export * from './Treemap'
@@ -3,7 +3,7 @@ import { View, Text } from 'react-native'
3
3
  import { Stepper, Step, StepIndicator, StepLabel, StepContent } from './Stepper'
4
4
 
5
5
  const meta: Meta<typeof Stepper> = {
6
- title: 'Custom/Stepper',
6
+ title: 'Components/Molecules/Stepper',
7
7
  component: Stepper,
8
8
  tags: ['autodocs'],
9
9
  argTypes: {
@@ -0,0 +1,50 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export interface IconProps {
4
+ /** Rendered width/height in px (viewBox is fixed 24×24). */
5
+ size?: number
6
+ /** Stroke color (or fill, for solid icons). Defaults to `currentColor` so a wrapper's text token drives it. */
7
+ color?: string
8
+ /** Stroke width for outline icons. */
9
+ strokeWidth?: number
10
+ /** Fill color; defaults to `none` (outline). */
11
+ fill?: string
12
+ /** Accessible title — sets `role="img"` + `<title>`. Omit for a decorative icon (hidden from AT). */
13
+ title?: string
14
+ }
15
+
16
+ export interface SvgIconProps extends IconProps {
17
+ children: ReactNode
18
+ }
19
+
20
+ /**
21
+ * Base SVG wrapper for the titan icon set — a 24×24 web-`<svg>` with the shared
22
+ * accessibility contract (titled → `role="img"` + `<title>`; untitled →
23
+ * `aria-hidden`) and `currentColor` inheritance. Icons pass their paths as
24
+ * children plus their stroke/fill defaults.
25
+ */
26
+ export function SvgIcon({
27
+ size = 24,
28
+ color = 'currentColor',
29
+ strokeWidth = 2,
30
+ fill = 'none',
31
+ title,
32
+ children,
33
+ }: SvgIconProps) {
34
+ return (
35
+ <svg
36
+ width={size}
37
+ height={size}
38
+ viewBox="0 0 24 24"
39
+ fill={fill}
40
+ stroke={color}
41
+ strokeWidth={strokeWidth}
42
+ strokeLinecap="round"
43
+ strokeLinejoin="round"
44
+ {...(title ? { role: 'img', 'aria-label': title } : { 'aria-hidden': true })}
45
+ >
46
+ {title ? <title>{title}</title> : null}
47
+ {children}
48
+ </svg>
49
+ )
50
+ }