@titan-design/react-ui 0.6.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 (248) hide show
  1. package/dist/bodymap.js +12 -7
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +12 -7
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +855 -124
  6. package/dist/index.d.ts +855 -124
  7. package/dist/index.js +2276 -801
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2230 -802
  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 +1255 -798
  16. package/dist/pages.js.map +1 -1
  17. package/dist/pages.mjs +1255 -798
  18. package/dist/pages.mjs.map +1 -1
  19. package/dist/theme/index.d.mts +16 -82
  20. package/dist/theme/index.d.ts +16 -82
  21. package/dist/theme/index.js +13 -115
  22. package/dist/theme/index.js.map +1 -1
  23. package/dist/theme/index.mjs +13 -115
  24. package/dist/theme/index.mjs.map +1 -1
  25. package/dist/theme/tokens-css.js +14 -6
  26. package/dist/theme/tokens-css.js.map +1 -1
  27. package/dist/theme/tokens-css.mjs +14 -6
  28. package/dist/theme/tokens-css.mjs.map +1 -1
  29. package/package.json +1 -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 +1 -1
  34. package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
  35. package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
  36. package/src/components/custom/Metric/Metric.stories.tsx +1 -1
  37. package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
  38. package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
  39. package/src/components/custom/Table/Table.stories.tsx +1 -1
  40. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
  41. package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
  42. package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
  43. package/src/components/custom/TimerReadout/index.ts +1 -0
  44. package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
  45. package/src/components/custom/Typography/Typography.stories.tsx +1 -1
  46. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
  47. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
  48. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
  49. package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
  50. package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
  51. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
  52. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
  53. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  54. package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
  55. package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
  56. package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
  57. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
  58. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
  59. package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
  60. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
  61. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
  62. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
  63. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
  64. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
  65. package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
  66. package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
  67. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
  68. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
  69. package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
  70. package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
  71. package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
  72. package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
  73. package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
  74. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  75. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
  76. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
  77. package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
  78. package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
  79. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  80. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
  81. package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
  82. package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
  83. package/src/components/custom/Workout/MetricTiles.tsx +48 -0
  84. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
  85. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  86. package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
  87. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
  88. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
  89. package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
  90. package/src/components/custom/Workout/README.md +146 -4
  91. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
  92. package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
  93. package/src/components/custom/Workout/RestTimer.tsx +15 -10
  94. package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
  95. package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
  96. package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
  97. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
  98. package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
  99. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
  100. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
  101. package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
  102. package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
  103. package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
  104. package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
  105. package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
  106. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
  107. package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
  108. package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
  109. package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
  110. package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
  111. package/src/components/custom/Workout/SessionHeader.tsx +152 -0
  112. package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
  113. package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
  114. package/src/components/custom/Workout/SessionRail.tsx +126 -0
  115. package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
  116. package/src/components/custom/Workout/SetBar.test.tsx +123 -0
  117. package/src/components/custom/Workout/SetBar.tsx +171 -0
  118. package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
  119. package/src/components/custom/Workout/SetRow.test.tsx +94 -166
  120. package/src/components/custom/Workout/SetRow.tsx +169 -232
  121. package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
  122. package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
  123. package/src/components/custom/Workout/SetStrip.tsx +71 -0
  124. package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
  125. package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
  126. package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
  127. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
  128. package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
  129. package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
  130. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  131. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  132. package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
  133. package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
  134. package/src/components/custom/Workout/StatusPill.tsx +88 -0
  135. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
  136. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
  137. package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
  138. package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
  139. package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
  140. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
  141. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
  142. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
  143. package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
  144. package/src/components/custom/Workout/VelocityStrip.test.tsx +376 -141
  145. package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
  146. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
  147. package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
  148. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
  149. package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
  150. package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
  151. package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
  152. package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
  153. package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
  154. package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
  155. package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
  156. package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
  157. package/src/components/custom/Workout/index.ts +55 -2
  158. package/src/components/custom/Workout/metricText.stories.tsx +69 -0
  159. package/src/components/custom/Workout/metricText.tsx +34 -0
  160. package/src/components/custom/Workout/paceTone.test.ts +29 -0
  161. package/src/components/custom/Workout/paceTone.ts +27 -0
  162. package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
  163. package/src/components/custom/index.ts +1 -0
  164. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  165. package/src/components/icons/icons.stories.tsx +67 -2
  166. package/src/components/icons/icons.test.tsx +32 -2
  167. package/src/components/icons/icons.tsx +108 -0
  168. package/src/components/shell/BrandLockup.stories.tsx +3 -3
  169. package/src/components/shell/DashboardShell.stories.tsx +65 -0
  170. package/src/components/shell/DashboardShell.test.tsx +42 -0
  171. package/src/components/shell/DashboardShell.tsx +74 -0
  172. package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
  173. package/src/components/shell/DeviceMenu.stories.tsx +5 -5
  174. package/src/components/shell/DeviceRow.stories.tsx +3 -3
  175. package/src/components/shell/NavItem.stories.tsx +52 -0
  176. package/src/components/shell/NavItem.test.tsx +35 -0
  177. package/src/components/shell/NavItem.tsx +75 -0
  178. package/src/components/shell/README.md +30 -0
  179. package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
  180. package/src/components/shell/SessionStatePill.stories.tsx +3 -3
  181. package/src/components/shell/SessionStatePill.tsx +1 -1
  182. package/src/components/shell/SideNav.stories.tsx +81 -0
  183. package/src/components/shell/SideNav.test.tsx +51 -0
  184. package/src/components/shell/SideNav.tsx +74 -0
  185. package/src/components/shell/TopBar.stories.tsx +5 -5
  186. package/src/components/shell/index.ts +5 -0
  187. package/src/components/ui/alert/Alert.stories.tsx +1 -1
  188. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  189. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  190. package/src/components/ui/badge/Badge.stories.tsx +1 -1
  191. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  192. package/src/components/ui/button/Button.stories.tsx +1 -1
  193. package/src/components/ui/card/Card.stories.tsx +1 -1
  194. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  195. package/src/components/ui/chip/Chip.stories.tsx +1 -1
  196. package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
  197. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  198. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  199. package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
  200. package/src/components/ui/form-field/FormField.stories.tsx +1 -1
  201. package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
  202. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  203. package/src/components/ui/index.ts +1 -0
  204. package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
  205. package/src/components/ui/indicator/Indicator.test.tsx +1 -0
  206. package/src/components/ui/indicator/Indicator.tsx +6 -1
  207. package/src/components/ui/input/Input.stories.tsx +1 -1
  208. package/src/components/ui/link/Link.stories.tsx +1 -1
  209. package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
  210. package/src/components/ui/menu/Menu.stories.tsx +1 -1
  211. package/src/components/ui/modal/Modal.stories.tsx +1 -1
  212. package/src/components/ui/pill/Pill.stories.tsx +1 -1
  213. package/src/components/ui/popover/Popover.stories.tsx +1 -1
  214. package/src/components/ui/progress/Progress.stories.tsx +1 -1
  215. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  216. package/src/components/ui/section/Section.stories.tsx +1 -1
  217. package/src/components/ui/select/Select.stories.tsx +1 -1
  218. package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
  219. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  220. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  221. package/src/components/ui/surface/Surface.stories.tsx +1 -1
  222. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  223. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  224. package/src/components/ui/tile/Tile.stories.tsx +79 -0
  225. package/src/components/ui/tile/Tile.test.tsx +48 -0
  226. package/src/components/ui/tile/Tile.tsx +64 -0
  227. package/src/components/ui/tile/index.ts +2 -0
  228. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  229. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  230. package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
  231. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
  232. package/src/hooks/index.ts +2 -0
  233. package/src/hooks/useTimer.test.ts +150 -0
  234. package/src/hooks/useTimer.ts +77 -0
  235. package/src/index.ts +3 -0
  236. package/src/pages.ts +1 -1
  237. package/src/stories/PresetShowcase.stories.tsx +1 -1
  238. package/src/stories/ThemePresets.stories.tsx +1 -1
  239. package/src/theme/ColorSystem.stories.tsx +1 -1
  240. package/src/theme/Colors.stories.tsx +5 -8
  241. package/src/theme/config.ts +4 -0
  242. package/src/theme/elevation.stories.tsx +1 -1
  243. package/src/theme/global.css +5 -1
  244. package/src/theme/shadows.stories.tsx +1 -1
  245. package/src/theme/tokens/primitives.ts +11 -107
  246. package/src/theme/tokens/semantic.ts +8 -0
  247. package/src/theme/workout-tokens.ts +11 -7
  248. package/tailwind.config.js +5 -0
@@ -1,260 +1,197 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import { type ReactNode } from 'react'
2
3
  import { View, Text } from 'react-native'
3
4
  import { VelocityStrip, type VelocityZoneBandProp } from './VelocityStrip'
4
- import { PrBadge, type PRType } from './PrBadge'
5
- import { roundRpe, rpeColor, roundWeight } from '../../../utils/workout-format'
5
+ import { roundWeight, roundRpe } from '../../../utils/workout-format'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
6
7
  import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
7
8
 
8
- export type SetRowMode = 'active' | 'completed' | 'history'
9
+ export type SetRowUnit = 'lbs' | 'kg'
9
10
 
10
- export interface SetRowProps {
11
- mode: SetRowMode
11
+ /** A set's lifecycle: logged (`done`), being performed now (`live`), or planned (`todo`). */
12
+ export type SetRowState = 'done' | 'live' | 'todo'
13
+
14
+ interface SetRowBase {
12
15
  setNumber: number
13
- previous?: { reps: number; weight: number } | null
14
- reps: number | null
15
- weight: number | null
16
- rpe?: number | null
17
- unit: 'lbs' | 'kg'
18
- /** Per-rep MEAN concentric velocities (m/s). */
19
- velocities?: number[]
16
+ unit: SetRowUnit
17
+ /** Optional set-type marker (e.g. "DROP", "AMRAP") shown in the SET cell. */
18
+ setType?: string
20
19
  /** Optional velocity-zone bands (WA `VelocityZones.bands`); default scale when absent. */
21
20
  velocityZones?: readonly VelocityZoneBandProp[]
22
- /** Live mode: index of the newest rep bar to animate (pop / new-peak bounce). */
23
- velocityLiveRepIndex?: number
24
- velocityExpanded?: boolean
25
- onVelocityToggle?: () => void
26
- setType?: string
27
- prBadges?: Array<{ type: PRType; label: string }>
28
- isNextSet?: boolean
29
- targets?: { reps: number; weight: number }
30
21
  }
31
22
 
32
- function formatAccessibilityLabel(
33
- setNumber: number,
34
- reps: number | null,
35
- weight: number | null,
36
- unit: string,
37
- ): string {
38
- const repsText = reps != null ? `${reps} reps` : 'no reps'
39
- const weightText = weight != null ? `at ${roundWeight(weight)} ${unit}` : ''
40
- return `Set ${setNumber}: ${repsText}${weightText ? ` ${weightText}` : ''}`
23
+ /**
24
+ * One row of the unified expanded exercise table (SET · REPS · LBS · RPE + a
25
+ * per-row velocity strip), as a lifecycle discriminated union:
26
+ * - `done` — a logged set: recorded reps / weight / rpe, a flat `mini` strip.
27
+ * - `live` — performed right now: shows its TARGET (never "reps-done/target"),
28
+ * stands out by brightness, and a compact velocity-HEIGHT spotlight strip.
29
+ * - `todo` planned: shows its target, muted, a flat grey `mini` stub strip.
30
+ *
31
+ * `done` + `todo` share ONE muted treatment; only `live` is brightened. There is
32
+ * no PREV column (dropped in the unified design).
33
+ */
34
+ export type SetRowProps =
35
+ | (SetRowBase & {
36
+ state: 'done'
37
+ reps: number
38
+ weight: number
39
+ rpe?: number | null
40
+ /** Per-rep MEAN concentric velocities (m/s). */
41
+ velocities: number[]
42
+ })
43
+ | (SetRowBase & {
44
+ state: 'live'
45
+ /** The prescribed target shown in the REPS / LBS cells (no live "5/10"). */
46
+ target: { reps: number; weight: number }
47
+ /** Reps / weight committed so far (available to hosts; the display shows `target`). */
48
+ reps: number
49
+ weight: number
50
+ rpe?: number | null
51
+ /** Per-rep MEAN concentric velocities logged so far this set. */
52
+ velocities: number[]
53
+ /** Newest rep index — reserved; the compact spotlight strip is static. */
54
+ liveRepIndex?: number
55
+ })
56
+ | (SetRowBase & {
57
+ state: 'todo'
58
+ /** The prescribed target shown in the REPS / LBS cells. */
59
+ target: { reps: number; weight: number }
60
+ /** Planned rep count backing the grey stub strip. Default `target.reps`. */
61
+ planned?: number
62
+ })
63
+
64
+ const DARK = getSemanticColors('dark')
65
+ /** Live set — brightest (neutral 100). Done + upcoming share the muted neutral 400. */
66
+ const TEXT_ACTIVE = DARK['text-primary']
67
+ const TEXT_MUTED = DARK['text-secondary']
68
+
69
+ /** Column widths mirror SetTableHeader(showPrevious=false) so cells align under it. */
70
+ const COL = { set: 36, reps: 44, load: 56, rpe: 36 } as const
71
+ const cellText = { fontFamily: 'Inter, sans-serif', fontSize: 13, fontWeight: '600' as const }
72
+ const typeBadge = {
73
+ fontSize: 10,
74
+ fontWeight: '700' as const,
75
+ fontFamily: 'Inter, sans-serif',
76
+ color: WORKOUT_TOKENS.scale.orange,
77
+ backgroundColor: 'rgba(255,165,2,0.12)',
78
+ paddingVertical: 1,
79
+ paddingHorizontal: 5,
80
+ borderRadius: 3,
81
+ overflow: 'hidden' as const,
41
82
  }
42
83
 
43
- export function SetRow({
44
- mode,
45
- setNumber,
46
- previous,
47
- reps,
48
- weight,
49
- rpe,
50
- unit,
51
- velocities,
52
- velocityZones,
53
- velocityLiveRepIndex,
54
- velocityExpanded,
55
- onVelocityToggle,
56
- setType,
57
- prBadges,
58
- isNextSet,
59
- targets,
60
- }: SetRowProps) {
61
- const isActive = mode === 'active'
62
- const isCompleted = mode === 'completed'
84
+ /** Reps to show: recorded for a `done` set, else the prescribed target. */
85
+ function displayReps(set: SetRowProps): number {
86
+ return set.state === 'done' ? set.reps : set.target.reps
87
+ }
63
88
 
64
- const rowStyle: Record<string, unknown> = {
65
- flexDirection: 'row' as const,
66
- alignItems: 'center' as const,
67
- padding: 7,
68
- paddingHorizontal: 8,
69
- gap: 8,
70
- borderRadius: 8,
71
- }
89
+ function displayWeight(set: SetRowProps): number {
90
+ return set.state === 'done' ? set.weight : set.target.weight
91
+ }
72
92
 
73
- if (isActive && isNextSet) {
74
- rowStyle.backgroundColor = 'rgba(255,121,0,0.06)'
75
- rowStyle.borderWidth = 1
76
- rowStyle.borderColor = 'rgba(255,121,0,0.15)'
77
- }
93
+ /** Planned rep count backing the row's strip. */
94
+ function plannedReps(set: SetRowProps): number {
95
+ if (set.state === 'done') return set.velocities.length
96
+ if (set.state === 'live') return set.target.reps
97
+ return set.planned ?? set.target.reps
98
+ }
99
+
100
+ function accessibilityLabel(set: SetRowProps): string {
101
+ const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`
102
+ if (set.state === 'live') return `${base}, in progress`
103
+ if (set.state === 'todo') return `${base}, upcoming`
104
+ return base
105
+ }
78
106
 
79
- if (isCompleted && !isNextSet) {
80
- rowStyle.opacity = 0.55
107
+ function Cell({
108
+ width,
109
+ children,
110
+ testID,
111
+ }: {
112
+ width: number
113
+ children: ReactNode
114
+ testID?: string
115
+ }) {
116
+ return (
117
+ <View style={{ width, alignItems: 'center', justifyContent: 'center' }} testID={testID}>
118
+ {children}
119
+ </View>
120
+ )
121
+ }
122
+
123
+ /** The per-row velocity strip: compact spotlight for the live set, flat mini otherwise. */
124
+ function RowStrip({ set }: { set: SetRowProps }) {
125
+ const zones = set.velocityZones
126
+ if (set.state === 'live') {
127
+ // The active-set spotlight: the bare velocity-height `expanded` chart at 24px,
128
+ // fixed scale, no labels / info (both chrome flags off → the bare strip).
129
+ return (
130
+ <VelocityStrip
131
+ variant="expanded"
132
+ showNumbers={false}
133
+ showInfo={false}
134
+ height={24}
135
+ scale="fixed"
136
+ set={{ type: 'straight', velocities: set.velocities, planned: set.target.reps }}
137
+ zones={zones}
138
+ />
139
+ )
81
140
  }
141
+ const velocities = set.state === 'done' ? set.velocities : []
142
+ return (
143
+ <VelocityStrip
144
+ variant="mini"
145
+ set={{ type: 'straight', velocities, planned: plannedReps(set) }}
146
+ zones={zones}
147
+ />
148
+ )
149
+ }
150
+
151
+ /**
152
+ * ONE set row of the unified expanded exercise table. SET · REPS · LBS · RPE over
153
+ * a per-row {@link VelocityStrip}; `live` stands out by brightness with a compact
154
+ * velocity-height spotlight, `done`/`todo` are muted with a flat mini strip. Its
155
+ * column widths mirror {@link SetTableHeader}(`showPrevious={false}`).
156
+ */
157
+ export function SetRow(set: SetRowProps) {
158
+ const live = set.state === 'live'
159
+ const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED
160
+ const rpe = set.state === 'todo' ? null : set.rpe
82
161
 
83
162
  return (
84
163
  <View
85
- style={rowStyle}
86
- accessibilityLabel={formatAccessibilityLabel(
87
- setNumber,
88
- reps,
89
- weight,
90
- unit,
91
- )}
164
+ style={{ paddingVertical: 6, paddingHorizontal: 8 }}
165
+ accessibilityLabel={accessibilityLabel(set)}
92
166
  testID="set-row"
93
167
  >
94
- <View
95
- style={{ width: 36, alignItems: 'center', justifyContent: 'center' }}
96
- testID="set-row-set-number"
97
- >
98
- {setType ? (
99
- <Text
100
- style={{
101
- fontSize: 10,
102
- fontWeight: '700',
103
- fontFamily: 'Inter, sans-serif',
104
- color: WORKOUT_TOKENS.scale.orange,
105
- backgroundColor: 'rgba(255,165,2,0.12)',
106
- paddingVertical: 1,
107
- paddingHorizontal: 5,
108
- borderRadius: 3,
109
- overflow: 'hidden',
110
- }}
111
- testID="set-row-type-badge"
112
- >
113
- {setType}
168
+ <View className="flex-row items-center" style={{ justifyContent: 'space-between' }}>
169
+ <Cell width={COL.set} testID="set-row-set-number">
170
+ {set.setType ? (
171
+ <Text style={typeBadge} testID="set-row-type-badge">
172
+ {set.setType}
173
+ </Text>
174
+ ) : (
175
+ <Text style={{ ...cellText, color: valueColor, fontWeight: live ? '700' : '600' }}>
176
+ {set.setNumber}
177
+ </Text>
178
+ )}
179
+ </Cell>
180
+ <Cell width={COL.reps} testID="set-row-reps">
181
+ <Text style={{ ...cellText, color: valueColor }}>{displayReps(set)}</Text>
182
+ </Cell>
183
+ <Cell width={COL.load} testID="set-row-weight">
184
+ <Text style={{ ...cellText, color: valueColor }}>{roundWeight(displayWeight(set))}</Text>
185
+ </Cell>
186
+ <Cell width={COL.rpe} testID="set-row-rpe">
187
+ <Text style={{ ...cellText, color: TEXT_MUTED }}>
188
+ {rpe != null ? roundRpe(rpe) : '—'}
114
189
  </Text>
115
- ) : (
116
- <Text
117
- className="text-text-secondary"
118
- style={{
119
- fontSize: 13,
120
- fontWeight: '600',
121
- fontFamily: 'Inter, sans-serif',
122
- }}
123
- >
124
- {setNumber}
125
- </Text>
126
- )}
190
+ </Cell>
127
191
  </View>
128
-
129
- <View
130
- className="flex-1"
131
- testID="set-row-previous"
132
- >
133
- <Text
134
- className="text-text-secondary"
135
- style={{
136
- fontSize: 12,
137
- fontFamily: 'Inter, sans-serif',
138
- }}
139
- >
140
- {previous
141
- ? `${previous.reps} x ${roundWeight(previous.weight)}`
142
- : '\u2014'}
143
- </Text>
144
- </View>
145
-
146
- <View
147
- style={{ width: 44, alignItems: 'center', justifyContent: 'center' }}
148
- testID="set-row-reps"
149
- >
150
- {isActive && reps == null && targets ? (
151
- <Text
152
- className="text-text-tertiary"
153
- style={{
154
- fontSize: 13,
155
- fontStyle: 'italic',
156
- fontFamily: 'Inter, sans-serif',
157
- }}
158
- testID="set-row-target-reps"
159
- >
160
- {targets.reps}
161
- </Text>
162
- ) : (
163
- <Text
164
- className={reps != null ? 'text-text-primary' : 'text-text-tertiary'}
165
- style={{
166
- fontSize: 14,
167
- fontWeight: '600',
168
- fontFamily: 'Inter, sans-serif',
169
- }}
170
- >
171
- {reps != null ? reps : '\u2014'}
172
- </Text>
173
- )}
192
+ <View style={{ marginTop: live ? 6 : 4 }} testID="set-row-strip">
193
+ <RowStrip set={set} />
174
194
  </View>
175
-
176
- <View
177
- style={{ width: 56, alignItems: 'center', justifyContent: 'center' }}
178
- testID="set-row-weight"
179
- >
180
- {isActive && weight == null && targets ? (
181
- <Text
182
- className="text-text-tertiary"
183
- style={{
184
- fontSize: 13,
185
- fontStyle: 'italic',
186
- fontFamily: 'Inter, sans-serif',
187
- }}
188
- testID="set-row-target-weight"
189
- >
190
- {roundWeight(targets.weight)}
191
- </Text>
192
- ) : (
193
- <Text
194
- className={weight != null ? 'text-text-primary' : 'text-text-tertiary'}
195
- style={{
196
- fontSize: 14,
197
- fontWeight: '600',
198
- fontFamily: 'Inter, sans-serif',
199
- }}
200
- >
201
- {weight != null ? roundWeight(weight) : '\u2014'}
202
- </Text>
203
- )}
204
- </View>
205
-
206
- <View
207
- style={{ width: 36, alignItems: 'center', justifyContent: 'center' }}
208
- testID="set-row-rpe"
209
- >
210
- <Text
211
- className={rpe != null ? undefined : 'text-text-tertiary'}
212
- style={{
213
- fontSize: 13,
214
- fontWeight: '600',
215
- fontFamily: 'Inter, sans-serif',
216
- // rpeColor returns a concrete hex (native-safe); only the tertiary
217
- // fallback needs the className token.
218
- color: rpe != null ? rpeColor(rpe) : undefined,
219
- }}
220
- >
221
- {rpe != null ? roundRpe(rpe) : '\u2014'}
222
- </Text>
223
- </View>
224
-
225
- {prBadges && prBadges.length > 0 && (
226
- <View
227
- className="flex-row items-center"
228
- style={{ gap: 4 }}
229
- testID="set-row-pr-badges"
230
- >
231
- {prBadges.map((badge, i) => (
232
- <PrBadge
233
- key={i}
234
- type={badge.type}
235
- label={badge.label}
236
- animate={false}
237
- compact
238
- />
239
- ))}
240
- </View>
241
- )}
242
-
243
- {velocities && velocities.length > 0 && (
244
- <View
245
- style={{ position: 'absolute', bottom: 0, left: 8, right: 8 }}
246
- testID="set-row-velocity-strip"
247
- >
248
- <VelocityStrip
249
- velocities={velocities}
250
- zones={velocityZones}
251
- liveRepIndex={velocityLiveRepIndex}
252
- expanded={velocityExpanded}
253
- onToggle={onVelocityToggle}
254
- variant={onVelocityToggle ? 'full' : 'mini'}
255
- />
256
- </View>
257
- )}
258
195
  </View>
259
196
  )
260
197
  }
@@ -0,0 +1,154 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View, Text } from 'react-native'
3
+ import { SetStrip, type SetStripSet } from './SetStrip'
4
+
5
+ /**
6
+ * `SetStrip` — the per-set segmented performance strip. One continuous bar per
7
+ * set (rep intensities as butted color segments, no rep gaps); sets separated by
8
+ * a fixed gap. Colors are the real titan ramp pins (red-600 · orange-400 ·
9
+ * amber-300 · green-300).
10
+ */
11
+ const meta: Meta<typeof SetStrip> = {
12
+ title: 'Workout/SetStrip',
13
+ component: SetStrip,
14
+ tags: ['autodocs'],
15
+ parameters: {
16
+ docs: {
17
+ description: {
18
+ component:
19
+ '**Molecule.** The per-set segmented performance strip — one bar per set, rep ' +
20
+ 'intensities as butted ramp-pin segments (no rep gaps), active set pulses. Composes ' +
21
+ '[SetBar](?path=/docs/workout-setbar--docs) × N. ' +
22
+ 'Used-by ↑ [ExerciseCardHeading](?path=/docs/workout-exercisecardheading--docs).',
23
+ },
24
+ },
25
+ },
26
+ argTypes: {
27
+ height: { control: { type: 'range', min: 2, max: 16, step: 1 } },
28
+ },
29
+ decorators: [
30
+ (Story) => (
31
+ <View style={{ width: 210, padding: 16, backgroundColor: '#131313' }}>
32
+ <Story />
33
+ </View>
34
+ ),
35
+ ],
36
+ }
37
+
38
+ export default meta
39
+ type Story = StoryObj<typeof SetStrip>
40
+
41
+ const decay = (n: number, start: number, span = 0.4): number[] =>
42
+ Array.from({ length: n }, (_, r) => +(start - (span * r) / Math.max(1, n - 1)).toFixed(3))
43
+
44
+ export const Completed: Story = {
45
+ args: {
46
+ height: 8,
47
+ sets: [1.0, 0.9, 0.8, 0.7, 0.6].map(
48
+ (s): SetStripSet => ({ status: 'done', velocities: decay(8, s) })
49
+ ),
50
+ },
51
+ }
52
+
53
+ export const InProgress: Story = {
54
+ args: {
55
+ height: 8,
56
+ sets: [
57
+ { status: 'done', velocities: decay(10, 0.72) },
58
+ { status: 'active', velocities: decay(5, 0.62), planned: 10 },
59
+ { status: 'todo', planned: 10 },
60
+ ],
61
+ },
62
+ parameters: {
63
+ docs: { description: { story: 'Active set: performed reps pulse; the remainder is greyed.' } },
64
+ },
65
+ }
66
+
67
+ export const Upcoming: Story = {
68
+ args: {
69
+ height: 8,
70
+ sets: [1, 2, 3, 4, 5].map((): SetStripSet => ({ status: 'todo', planned: 20 })),
71
+ },
72
+ }
73
+
74
+ export const ShortStrip: Story = {
75
+ args: { ...Completed.args, height: 4 },
76
+ parameters: {
77
+ docs: { description: { story: 'The 4px height alternative (default is 8px).' } },
78
+ },
79
+ }
80
+
81
+ // ---- set-type variant sheet: every variant at the real rail width, for eyeballing
82
+ function SheetRow({ label, meta, sets }: { label: string; meta: string; sets: SetStripSet[] }) {
83
+ return (
84
+ <View style={{ gap: 6 }}>
85
+ <View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 8 }}>
86
+ <Text style={{ color: '#F3F4F6', fontSize: 13, fontWeight: '800' }}>{label}</Text>
87
+ <Text style={{ color: '#6B7280', fontSize: 10, fontFamily: 'monospace' }}>{meta}</Text>
88
+ </View>
89
+ <SetStrip sets={sets} />
90
+ </View>
91
+ )
92
+ }
93
+
94
+ /**
95
+ * Every set-type variant at the real 232px rail width, alongside the flat sets — so the
96
+ * operator can eyeball the gap hierarchy (butted reps < 2px notch < 3px cluster < 5px set gap)
97
+ * and the shared cyan "variable / unknown / opportunity" pin across range + myo-upcoming.
98
+ */
99
+ export const SetTypes: Story = {
100
+ render: () => (
101
+ <View style={{ width: 232, gap: 18 }}>
102
+ <SheetRow
103
+ label="Flat sets"
104
+ meta="5 × 8 — done + active + todo"
105
+ sets={[
106
+ { status: 'done', velocities: decay(8, 0.9) },
107
+ { status: 'active', velocities: decay(5, 0.62), planned: 8 },
108
+ { status: 'todo', planned: 8 },
109
+ ]}
110
+ />
111
+ <SheetRow
112
+ label="Variable range"
113
+ meta="3 × 15–20 — todo · 12 · 17"
114
+ sets={[
115
+ { status: 'range', floor: 15, max: 20, doneVels: [] },
116
+ { status: 'range', floor: 15, max: 20, doneVels: decay(12, 0.9) },
117
+ { status: 'range', floor: 15, max: 20, doneVels: decay(17, 0.8) },
118
+ ]}
119
+ />
120
+ <SheetRow
121
+ label="Drop set"
122
+ meta="1 drop — 10→8→6"
123
+ sets={[{ status: 'drop', subloads: [decay(10, 0.85), decay(8, 0.7), decay(6, 0.6)] }]}
124
+ />
125
+ <SheetRow
126
+ label="Myo-rep"
127
+ meta="done — 15 + 5 + 5 + 5"
128
+ sets={[
129
+ {
130
+ status: 'myo',
131
+ activation: decay(15, 0.75),
132
+ clusters: [decay(5, 0.6), decay(5, 0.55), decay(5, 0.5)],
133
+ },
134
+ ]}
135
+ />
136
+ <SheetRow
137
+ label="Myo — upcoming"
138
+ meta="~15 + clusters to failure"
139
+ sets={[{ status: 'myo-upcoming', activationLen: 15 }]}
140
+ />
141
+ </View>
142
+ ),
143
+ parameters: {
144
+ layout: 'padded',
145
+ docs: {
146
+ description: {
147
+ story:
148
+ 'The four set-type variants (range / drop / myo / myo-upcoming) plus the flat sets, ' +
149
+ 'at rail width. Gap hierarchy: butted reps (0) < notch (2px, drop) < cluster (3px, myo) ' +
150
+ '< set gap (5px). Cyan is the shared variable/unknown/opportunity pin.',
151
+ },
152
+ },
153
+ },
154
+ }