@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.
- package/dist/bodymap.js +12 -7
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +12 -7
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +855 -124
- package/dist/index.d.ts +855 -124
- package/dist/index.js +2276 -801
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2230 -802
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-8u_4WbL-.d.mts} +280 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-DaWiBOGa.d.ts} +280 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1255 -798
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1255 -798
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +16 -82
- package/dist/theme/index.d.ts +16 -82
- package/dist/theme/index.js +13 -115
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +13 -115
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +14 -6
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +14 -6
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +870 -0
- package/src/arch/annotations.json +48 -0
- package/src/arch/arch-graph.json +4301 -0
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
- package/src/components/custom/Metric/Metric.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
- package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
- package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
- package/src/components/custom/TimerReadout/index.ts +1 -0
- package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
- package/src/components/custom/Typography/Typography.stories.tsx +1 -1
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
- package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
- package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
- package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
- package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
- package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
- package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
- package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
- package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
- package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
- package/src/components/custom/Workout/MetricTiles.tsx +48 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
- package/src/components/custom/Workout/README.md +146 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.tsx +15 -10
- package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
- package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
- package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
- package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
- package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
- package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
- package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
- package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
- package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
- package/src/components/custom/Workout/SessionHeader.tsx +152 -0
- package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
- package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
- package/src/components/custom/Workout/SessionRail.tsx +126 -0
- package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
- package/src/components/custom/Workout/SetBar.test.tsx +123 -0
- package/src/components/custom/Workout/SetBar.tsx +171 -0
- package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
- package/src/components/custom/Workout/SetRow.test.tsx +94 -166
- package/src/components/custom/Workout/SetRow.tsx +169 -232
- package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
- package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
- package/src/components/custom/Workout/SetStrip.tsx +71 -0
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
- package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
- package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
- package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
- package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
- package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
- package/src/components/custom/Workout/StatusPill.tsx +88 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
- package/src/components/custom/Workout/VelocityStrip.test.tsx +376 -141
- package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
- package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
- package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
- package/src/components/custom/Workout/index.ts +55 -2
- package/src/components/custom/Workout/metricText.stories.tsx +69 -0
- package/src/components/custom/Workout/metricText.tsx +34 -0
- package/src/components/custom/Workout/paceTone.test.ts +29 -0
- package/src/components/custom/Workout/paceTone.ts +27 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/icons/icons.stories.tsx +67 -2
- package/src/components/icons/icons.test.tsx +32 -2
- package/src/components/icons/icons.tsx +108 -0
- package/src/components/shell/BrandLockup.stories.tsx +3 -3
- package/src/components/shell/DashboardShell.stories.tsx +65 -0
- package/src/components/shell/DashboardShell.test.tsx +42 -0
- package/src/components/shell/DashboardShell.tsx +74 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
- package/src/components/shell/DeviceMenu.stories.tsx +5 -5
- package/src/components/shell/DeviceRow.stories.tsx +3 -3
- package/src/components/shell/NavItem.stories.tsx +52 -0
- package/src/components/shell/NavItem.test.tsx +35 -0
- package/src/components/shell/NavItem.tsx +75 -0
- package/src/components/shell/README.md +30 -0
- package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
- package/src/components/shell/SessionStatePill.stories.tsx +3 -3
- package/src/components/shell/SessionStatePill.tsx +1 -1
- package/src/components/shell/SideNav.stories.tsx +81 -0
- package/src/components/shell/SideNav.test.tsx +51 -0
- package/src/components/shell/SideNav.tsx +74 -0
- package/src/components/shell/TopBar.stories.tsx +5 -5
- package/src/components/shell/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/badge/Badge.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/button/Button.stories.tsx +1 -1
- package/src/components/ui/card/Card.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/chip/Chip.stories.tsx +1 -1
- package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
- package/src/components/ui/form-field/FormField.stories.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
- package/src/components/ui/indicator/Indicator.test.tsx +1 -0
- package/src/components/ui/indicator/Indicator.tsx +6 -1
- package/src/components/ui/input/Input.stories.tsx +1 -1
- package/src/components/ui/link/Link.stories.tsx +1 -1
- package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
- package/src/components/ui/menu/Menu.stories.tsx +1 -1
- package/src/components/ui/modal/Modal.stories.tsx +1 -1
- package/src/components/ui/pill/Pill.stories.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +1 -1
- package/src/components/ui/select/Select.stories.tsx +1 -1
- package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.stories.tsx +79 -0
- package/src/components/ui/tile/Tile.test.tsx +48 -0
- package/src/components/ui/tile/Tile.tsx +64 -0
- package/src/components/ui/tile/index.ts +2 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTimer.test.ts +150 -0
- package/src/hooks/useTimer.ts +77 -0
- package/src/index.ts +3 -0
- package/src/pages.ts +1 -1
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/stories/ThemePresets.stories.tsx +1 -1
- package/src/theme/ColorSystem.stories.tsx +1 -1
- package/src/theme/Colors.stories.tsx +5 -8
- package/src/theme/config.ts +4 -0
- package/src/theme/elevation.stories.tsx +1 -1
- package/src/theme/global.css +5 -1
- package/src/theme/shadows.stories.tsx +1 -1
- package/src/theme/tokens/primitives.ts +11 -107
- package/src/theme/tokens/semantic.ts +8 -0
- package/src/theme/workout-tokens.ts +11 -7
- package/tailwind.config.js +5 -0
package/dist/pages.js
CHANGED
|
@@ -10,21 +10,18 @@ var tailwindMerge = require('tailwind-merge');
|
|
|
10
10
|
|
|
11
11
|
// src/theme/tokens/primitives.ts
|
|
12
12
|
var primitiveColors = {
|
|
13
|
-
// Blue scale
|
|
13
|
+
// Blue scale
|
|
14
14
|
blue: {
|
|
15
15
|
400: "#60A5FA",
|
|
16
16
|
600: "#5048E5",
|
|
17
|
-
// Primary main
|
|
18
17
|
700: "#3832A0"},
|
|
19
|
-
// Red scale
|
|
18
|
+
// Red scale
|
|
20
19
|
red: {
|
|
21
|
-
// Error light
|
|
22
20
|
500: "#EF4444",
|
|
23
21
|
600: "#D14343"},
|
|
24
|
-
// Vivid red scale
|
|
22
|
+
// Vivid red scale
|
|
25
23
|
redVivid: {
|
|
26
24
|
400: "#FF6070",
|
|
27
|
-
// Error vivid light
|
|
28
25
|
500: "#FF4757",
|
|
29
26
|
700: "#C42539"},
|
|
30
27
|
// Neutral/Gray scale
|
|
@@ -67,11 +64,16 @@ var primitiveRamps = {
|
|
|
67
64
|
50: "#FFF7DD",
|
|
68
65
|
200: "#FFD352",
|
|
69
66
|
300: "#F9B415",
|
|
67
|
+
// pin
|
|
68
|
+
400: "#E08C00",
|
|
70
69
|
500: "#C27400"},
|
|
71
70
|
green: {
|
|
72
71
|
50: "#E3FFEE",
|
|
73
72
|
200: "#58F69E",
|
|
74
73
|
300: "#2ED573",
|
|
74
|
+
// pin
|
|
75
|
+
400: "#21C05D",
|
|
76
|
+
500: "#22A444",
|
|
75
77
|
600: "#298732"},
|
|
76
78
|
cyan: {
|
|
77
79
|
// pin
|
|
@@ -79,7 +81,8 @@ var primitiveRamps = {
|
|
|
79
81
|
500: "#2697B7",
|
|
80
82
|
600: "#307B9B",
|
|
81
83
|
700: "#2A617F",
|
|
82
|
-
800: "#22465F"
|
|
84
|
+
800: "#22465F",
|
|
85
|
+
900: "#0B3149"},
|
|
83
86
|
blue: {
|
|
84
87
|
50: "#EFF8FF",
|
|
85
88
|
300: "#78C2FF",
|
|
@@ -134,15 +137,22 @@ var WORKOUT_TOKENS = {
|
|
|
134
137
|
// Canonical 4-band performance scale — the single source for BOTH the
|
|
135
138
|
// VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
|
|
136
139
|
// is intentionally inverted between the two consumers: for velocity, green =
|
|
137
|
-
// fastest/best; for RPE, green = easiest. This is the [0,
|
|
138
|
-
// the canonical `sequentialEffort` primitive
|
|
139
|
-
//
|
|
140
|
+
// fastest/best; for RPE, green = easiest. This is the [0,2,3,4] subsample of
|
|
141
|
+
// the canonical `sequentialEffort` primitive: an EVEN hue walk across the ramp
|
|
142
|
+
// (green → gold → orange → red). The earlier [0,1,2,4] spent two of its four
|
|
143
|
+
// bands on adjacent yellows (amber-200 + amber-300) and skipped orange, reading
|
|
144
|
+
// yellow-heavy with an abrupt jump to red; sampling amber-300 + orange-400
|
|
145
|
+
// instead gives four cleanly separable bands that hold at 3px strip scale.
|
|
146
|
+
// Consumed inline (RN).
|
|
140
147
|
scale: {
|
|
141
148
|
green: sequentialEffort[0],
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// gold
|
|
149
|
+
// green-300
|
|
150
|
+
yellow: sequentialEffort[2],
|
|
151
|
+
// amber-300 (gold)
|
|
152
|
+
orange: sequentialEffort[3],
|
|
153
|
+
// orange-400 (true orange)
|
|
145
154
|
red: sequentialEffort[4]
|
|
155
|
+
// red-600
|
|
146
156
|
},
|
|
147
157
|
// Surface and border colors were removed here: those flip with the theme, so
|
|
148
158
|
// components use the CSS custom properties (var(--color-surface-*/border-*))
|
|
@@ -161,12 +171,6 @@ var WORKOUT_TOKENS = {
|
|
|
161
171
|
function roundRpe(rpe) {
|
|
162
172
|
return Math.round(rpe * 2) / 2;
|
|
163
173
|
}
|
|
164
|
-
function rpeColor(rpe) {
|
|
165
|
-
if (rpe >= 9.5) return WORKOUT_TOKENS.scale.red;
|
|
166
|
-
if (rpe >= 8.5) return WORKOUT_TOKENS.scale.orange;
|
|
167
|
-
if (rpe >= 7) return WORKOUT_TOKENS.scale.yellow;
|
|
168
|
-
return WORKOUT_TOKENS.scale.green;
|
|
169
|
-
}
|
|
170
174
|
function roundWeight(weight) {
|
|
171
175
|
return Math.round(weight);
|
|
172
176
|
}
|
|
@@ -194,14 +198,258 @@ function wrapJsx(fn) {
|
|
|
194
198
|
var jsx = wrapJsx(jsxRuntime.jsx);
|
|
195
199
|
var jsxs = wrapJsx(jsxRuntime.jsxs);
|
|
196
200
|
|
|
201
|
+
// src/components/custom/Workout/SegmentedBar.tsx
|
|
202
|
+
var PULSE_HALF_MS = 950;
|
|
203
|
+
var PULSE_MIN_OPACITY = 0.45;
|
|
204
|
+
var SEGMENTED_BAR_GAP = 5;
|
|
205
|
+
function usePulse(active) {
|
|
206
|
+
const [value] = react.useState(() => new reactNative.Animated.Value(0));
|
|
207
|
+
react.useEffect(() => {
|
|
208
|
+
if (!active) return;
|
|
209
|
+
const loop = reactNative.Animated.loop(
|
|
210
|
+
reactNative.Animated.sequence([
|
|
211
|
+
reactNative.Animated.timing(value, {
|
|
212
|
+
toValue: 1,
|
|
213
|
+
duration: PULSE_HALF_MS,
|
|
214
|
+
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
215
|
+
useNativeDriver: false
|
|
216
|
+
}),
|
|
217
|
+
reactNative.Animated.timing(value, {
|
|
218
|
+
toValue: 0,
|
|
219
|
+
duration: PULSE_HALF_MS,
|
|
220
|
+
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
221
|
+
useNativeDriver: false
|
|
222
|
+
})
|
|
223
|
+
])
|
|
224
|
+
);
|
|
225
|
+
loop.start();
|
|
226
|
+
return () => loop.stop();
|
|
227
|
+
}, [active, value]);
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
function SegmentedBar({
|
|
231
|
+
segments,
|
|
232
|
+
height = 8,
|
|
233
|
+
gap = SEGMENTED_BAR_GAP,
|
|
234
|
+
radius = 2,
|
|
235
|
+
marker = null,
|
|
236
|
+
segmentTestID,
|
|
237
|
+
style,
|
|
238
|
+
...props
|
|
239
|
+
}) {
|
|
240
|
+
const pulse = usePulse(segments.some((s) => s.pulse));
|
|
241
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
|
|
242
|
+
segments.map((seg, i) => {
|
|
243
|
+
const fillStyle = {
|
|
244
|
+
width: `${(seg.fill ?? 1) * 100}%`,
|
|
245
|
+
height: "100%",
|
|
246
|
+
backgroundColor: seg.color,
|
|
247
|
+
opacity: seg.opacity
|
|
248
|
+
};
|
|
249
|
+
const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
|
|
250
|
+
return /* @__PURE__ */ jsx(
|
|
251
|
+
reactNative.View,
|
|
252
|
+
{
|
|
253
|
+
style: {
|
|
254
|
+
flex: seg.weight ?? 1,
|
|
255
|
+
minWidth: 0,
|
|
256
|
+
height: "100%",
|
|
257
|
+
marginLeft: seg.leadingGap,
|
|
258
|
+
borderRadius: radius,
|
|
259
|
+
overflow: "hidden"
|
|
260
|
+
},
|
|
261
|
+
children: seg.pulse ? /* @__PURE__ */ jsx(
|
|
262
|
+
reactNative.Animated.View,
|
|
263
|
+
{
|
|
264
|
+
style: {
|
|
265
|
+
...fillStyle,
|
|
266
|
+
...seg.pulseColor ? {
|
|
267
|
+
backgroundColor: pulse.interpolate({
|
|
268
|
+
inputRange: [0, 1],
|
|
269
|
+
outputRange: seg.pulseColor
|
|
270
|
+
})
|
|
271
|
+
} : {
|
|
272
|
+
opacity: pulse.interpolate({
|
|
273
|
+
inputRange: [0, 1],
|
|
274
|
+
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
accessibilityElementsHidden: true,
|
|
279
|
+
testID
|
|
280
|
+
}
|
|
281
|
+
) : /* @__PURE__ */ jsx(reactNative.View, { style: fillStyle, accessibilityElementsHidden: true, testID })
|
|
282
|
+
},
|
|
283
|
+
i
|
|
284
|
+
);
|
|
285
|
+
}),
|
|
286
|
+
marker ? /* @__PURE__ */ jsx(
|
|
287
|
+
reactNative.View,
|
|
288
|
+
{
|
|
289
|
+
style: {
|
|
290
|
+
position: "absolute",
|
|
291
|
+
left: `${marker.position * 100}%`,
|
|
292
|
+
top: 0,
|
|
293
|
+
bottom: 0,
|
|
294
|
+
width: 2,
|
|
295
|
+
backgroundColor: marker.color
|
|
296
|
+
},
|
|
297
|
+
accessibilityElementsHidden: true,
|
|
298
|
+
testID: "segmented-bar-marker"
|
|
299
|
+
}
|
|
300
|
+
) : null
|
|
301
|
+
] });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/components/custom/Workout/SetBar.tsx
|
|
305
|
+
var SET_STRIP_ZONES = {
|
|
306
|
+
slow: primitiveRamps.red[600],
|
|
307
|
+
moderate: primitiveRamps.orange[400],
|
|
308
|
+
fast: primitiveRamps.amber[300],
|
|
309
|
+
fastest: primitiveRamps.green[300]
|
|
310
|
+
};
|
|
311
|
+
var TODO_COLOR = primitiveColors.charcoal[300];
|
|
312
|
+
var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
|
|
313
|
+
var NOTCH_GAP = 2;
|
|
314
|
+
var CLUSTER_GAP = 3;
|
|
315
|
+
var MYO_UPCOMING_CLUSTERS = 3;
|
|
316
|
+
var MYO_UPCOMING_CLUSTER_LEN = 5;
|
|
317
|
+
var MYO_UPCOMING_FADE = 0.6;
|
|
318
|
+
var PULSE_RANGE = {
|
|
319
|
+
[SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
|
|
320
|
+
[SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
|
|
321
|
+
[SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
|
|
322
|
+
[SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
|
|
323
|
+
};
|
|
324
|
+
function velocityZoneColor(v) {
|
|
325
|
+
if (v < 0.5) return SET_STRIP_ZONES.slow;
|
|
326
|
+
if (v < 0.75) return SET_STRIP_ZONES.moderate;
|
|
327
|
+
if (v < 1) return SET_STRIP_ZONES.fast;
|
|
328
|
+
return SET_STRIP_ZONES.fastest;
|
|
329
|
+
}
|
|
330
|
+
function chunkedSegments(chunks, gap) {
|
|
331
|
+
return chunks.flatMap(
|
|
332
|
+
(vels, ci) => vels.map((v, ri) => ({
|
|
333
|
+
color: velocityZoneColor(v),
|
|
334
|
+
...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
|
|
335
|
+
}))
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
function rangeSegments(floor, max, doneVels) {
|
|
339
|
+
return Array.from({ length: max }, (_, i) => {
|
|
340
|
+
if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
|
|
341
|
+
return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function myoUpcomingSegments(activationLen, clusterCount) {
|
|
345
|
+
const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
|
|
346
|
+
const trail = Array.from(
|
|
347
|
+
{ length: clusterCount },
|
|
348
|
+
(_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
|
|
349
|
+
color: SET_STRIP_VARIABLE_COLOR,
|
|
350
|
+
opacity: MYO_UPCOMING_FADE ** ci,
|
|
351
|
+
...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
|
|
352
|
+
}))
|
|
353
|
+
).flat();
|
|
354
|
+
return [...activation, ...trail];
|
|
355
|
+
}
|
|
356
|
+
function setSegments(set) {
|
|
357
|
+
if (set.status === "todo") {
|
|
358
|
+
return [{ color: TODO_COLOR }];
|
|
359
|
+
}
|
|
360
|
+
if (set.status === "done") {
|
|
361
|
+
return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
|
|
362
|
+
}
|
|
363
|
+
if (set.status === "range") {
|
|
364
|
+
return rangeSegments(set.floor, set.max, set.doneVels);
|
|
365
|
+
}
|
|
366
|
+
if (set.status === "drop") {
|
|
367
|
+
return chunkedSegments(set.subloads, NOTCH_GAP);
|
|
368
|
+
}
|
|
369
|
+
if (set.status === "myo") {
|
|
370
|
+
return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
|
|
371
|
+
}
|
|
372
|
+
if (set.status === "myo-upcoming") {
|
|
373
|
+
return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
|
|
374
|
+
}
|
|
375
|
+
const performed = set.velocities.map((v) => {
|
|
376
|
+
const color = velocityZoneColor(v);
|
|
377
|
+
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
378
|
+
});
|
|
379
|
+
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
380
|
+
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
381
|
+
}
|
|
382
|
+
function setBarSegmentTestID(seg) {
|
|
383
|
+
if (seg.pulse) return "set-strip-pulse";
|
|
384
|
+
if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
|
|
385
|
+
return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
|
|
386
|
+
}
|
|
387
|
+
function SetBar({ set, height = 8 }) {
|
|
388
|
+
return /* @__PURE__ */ jsx(
|
|
389
|
+
SegmentedBar,
|
|
390
|
+
{
|
|
391
|
+
segments: setSegments(set),
|
|
392
|
+
height,
|
|
393
|
+
gap: 0,
|
|
394
|
+
radius: 0,
|
|
395
|
+
segmentTestID: setBarSegmentTestID,
|
|
396
|
+
style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
|
|
397
|
+
testID: "set-strip-set"
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/components/custom/Workout/SetStrip.tsx
|
|
403
|
+
var SET_STRIP_GAP = 5;
|
|
404
|
+
function setCategory(set) {
|
|
405
|
+
switch (set.status) {
|
|
406
|
+
case "done":
|
|
407
|
+
case "drop":
|
|
408
|
+
case "myo":
|
|
409
|
+
return "done";
|
|
410
|
+
case "active":
|
|
411
|
+
return "active";
|
|
412
|
+
case "range":
|
|
413
|
+
return set.doneVels.length > 0 ? "active" : "upcoming";
|
|
414
|
+
case "todo":
|
|
415
|
+
case "myo-upcoming":
|
|
416
|
+
return "upcoming";
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function describeSets(sets) {
|
|
420
|
+
const done = sets.filter((s) => setCategory(s) === "done").length;
|
|
421
|
+
const active = sets.filter((s) => setCategory(s) === "active").length;
|
|
422
|
+
const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
|
|
423
|
+
return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
|
|
424
|
+
}
|
|
425
|
+
function SetStrip({ sets, height = 8, className, ...props }) {
|
|
426
|
+
return /* @__PURE__ */ jsx(
|
|
427
|
+
reactNative.View,
|
|
428
|
+
{
|
|
429
|
+
className,
|
|
430
|
+
style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
|
|
431
|
+
accessibilityRole: "image",
|
|
432
|
+
accessibilityLabel: describeSets(sets),
|
|
433
|
+
testID: "set-strip",
|
|
434
|
+
...props,
|
|
435
|
+
children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
197
440
|
// src/components/custom/Workout/VelocityStrip.tsx
|
|
198
441
|
var VEL_COLORS = WORKOUT_TOKENS.scale;
|
|
199
|
-
var
|
|
200
|
-
speed:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
442
|
+
var bandIdToEffortColor = {
|
|
443
|
+
speed: sequentialEffort[0],
|
|
444
|
+
// green-300
|
|
445
|
+
power: sequentialEffort[2],
|
|
446
|
+
// amber-300 (gold)
|
|
447
|
+
strengthSpeed: sequentialEffort[3],
|
|
448
|
+
// orange-400
|
|
449
|
+
maximalStrength: sequentialEffort[4],
|
|
450
|
+
// red-600
|
|
451
|
+
grinding: sequentialEffort[5]
|
|
452
|
+
// red-700 — the distinct 5th band
|
|
205
453
|
};
|
|
206
454
|
function getVelocityZoneColor(velocity) {
|
|
207
455
|
if (velocity >= 1) return "vel-green";
|
|
@@ -240,14 +488,132 @@ function classifyBand(velocity, bands) {
|
|
|
240
488
|
return bands[bands.length - 1];
|
|
241
489
|
}
|
|
242
490
|
function bandColor(band) {
|
|
243
|
-
|
|
244
|
-
return token ? VEL_COLORS[token] : VEL_COLORS.green;
|
|
491
|
+
return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
|
|
245
492
|
}
|
|
246
493
|
function getLossStyle(loss) {
|
|
247
494
|
if (loss > 25) return { color: VEL_COLORS.red };
|
|
248
495
|
if (loss > 20) return { color: VEL_COLORS.orange };
|
|
249
496
|
return null;
|
|
250
497
|
}
|
|
498
|
+
var REP_GAP = 2;
|
|
499
|
+
var WIDE_GAP = 8;
|
|
500
|
+
var TODO_COLOR2 = primitiveColors.charcoal[300];
|
|
501
|
+
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
502
|
+
function deriveDoneVelocities(set) {
|
|
503
|
+
switch (set.type) {
|
|
504
|
+
case "drop":
|
|
505
|
+
return set.subloads.flat();
|
|
506
|
+
case "myo":
|
|
507
|
+
return [...set.activation, ...set.clusters.flat()];
|
|
508
|
+
default:
|
|
509
|
+
return set.velocities;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function chunkedRepSlots(chunks) {
|
|
513
|
+
const slots = [];
|
|
514
|
+
chunks.forEach((chunk) => {
|
|
515
|
+
chunk.forEach((velocity, ri) => {
|
|
516
|
+
const first = slots.length === 0;
|
|
517
|
+
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP });
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
return slots;
|
|
521
|
+
}
|
|
522
|
+
function buildSlots(set) {
|
|
523
|
+
switch (set.type) {
|
|
524
|
+
case "straight": {
|
|
525
|
+
const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
|
|
526
|
+
return Array.from({ length: total }, (_, i) => {
|
|
527
|
+
const done = i < set.velocities.length;
|
|
528
|
+
return {
|
|
529
|
+
kind: done ? "rep" : "todo",
|
|
530
|
+
velocity: done ? set.velocities[i] : void 0,
|
|
531
|
+
leadingGap: i === 0 ? 0 : REP_GAP
|
|
532
|
+
};
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
case "range": {
|
|
536
|
+
const total = Math.max(set.max, set.velocities.length);
|
|
537
|
+
return Array.from({ length: total }, (_, i) => {
|
|
538
|
+
const kind = i < set.velocities.length ? "rep" : i < set.floor ? "todo" : "variable";
|
|
539
|
+
return {
|
|
540
|
+
kind,
|
|
541
|
+
velocity: kind === "rep" ? set.velocities[i] : void 0,
|
|
542
|
+
leadingGap: i === 0 ? 0 : REP_GAP
|
|
543
|
+
};
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
case "amrap": {
|
|
547
|
+
const reps = set.velocities.map((velocity, i) => ({
|
|
548
|
+
kind: "rep",
|
|
549
|
+
velocity,
|
|
550
|
+
leadingGap: i === 0 ? 0 : REP_GAP
|
|
551
|
+
}));
|
|
552
|
+
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP });
|
|
553
|
+
return reps;
|
|
554
|
+
}
|
|
555
|
+
case "drop":
|
|
556
|
+
return chunkedRepSlots(set.subloads);
|
|
557
|
+
case "myo": {
|
|
558
|
+
const slots = chunkedRepSlots([set.activation, ...set.clusters]);
|
|
559
|
+
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP });
|
|
560
|
+
return slots;
|
|
561
|
+
}
|
|
562
|
+
case "cluster": {
|
|
563
|
+
const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
|
|
564
|
+
return Array.from({ length: total }, (_, i) => {
|
|
565
|
+
const done = i < set.velocities.length;
|
|
566
|
+
const boundary = i > 0 && i % set.groupSize === 0;
|
|
567
|
+
return {
|
|
568
|
+
kind: done ? "rep" : "todo",
|
|
569
|
+
velocity: done ? set.velocities[i] : void 0,
|
|
570
|
+
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP
|
|
571
|
+
};
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function setAccessibilityLabel(set, repCount) {
|
|
577
|
+
switch (set.type) {
|
|
578
|
+
case "straight":
|
|
579
|
+
return `Velocity strip, straight set, ${repCount} reps`;
|
|
580
|
+
case "range":
|
|
581
|
+
return `Velocity strip, rep-range set, floor ${set.floor} to ${set.max}, ${repCount} reps done`;
|
|
582
|
+
case "amrap":
|
|
583
|
+
return `Velocity strip, AMRAP set, ${repCount} reps and counting`;
|
|
584
|
+
case "drop":
|
|
585
|
+
return `Velocity strip, drop set, ${set.subloads.length} loads, ${repCount} reps`;
|
|
586
|
+
case "myo":
|
|
587
|
+
return `Velocity strip, myo-reps set, ${set.clusters.length} clusters${set.open ? ", open" : ""}, ${repCount} reps`;
|
|
588
|
+
case "cluster":
|
|
589
|
+
return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function slotAccessibilityLabel(slot, index) {
|
|
593
|
+
switch (slot.kind) {
|
|
594
|
+
case "rep":
|
|
595
|
+
return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
|
|
596
|
+
case "todo":
|
|
597
|
+
return `Rep ${index + 1}: planned`;
|
|
598
|
+
case "variable":
|
|
599
|
+
return `Rep ${index + 1}: variable`;
|
|
600
|
+
case "continue":
|
|
601
|
+
return "Keep going";
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
var EXPANDED_TODO_STUB_PCT = 16;
|
|
605
|
+
var EXPANDED_ENCODED_PCT = 45;
|
|
606
|
+
var EXPANDED_HEIGHT = 60;
|
|
607
|
+
var FIXED_MAX_VELOCITY = 1.15;
|
|
608
|
+
var BARE_STUB_HEIGHT = 3;
|
|
609
|
+
function scaleDenominator(scale, maxVelocity) {
|
|
610
|
+
return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
|
|
611
|
+
}
|
|
612
|
+
function bareSlotHeight(slot, height, denom) {
|
|
613
|
+
if (slot.kind !== "rep" || denom <= 0) return BARE_STUB_HEIGHT;
|
|
614
|
+
const ratio = Math.min(1, (slot.velocity ?? 0) / denom);
|
|
615
|
+
return Math.max(BARE_STUB_HEIGHT, ratio * height);
|
|
616
|
+
}
|
|
251
617
|
function getReducedMotionPreference() {
|
|
252
618
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
253
619
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
@@ -269,31 +635,48 @@ var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
|
|
|
269
635
|
var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
|
|
270
636
|
function VelocityStrip({
|
|
271
637
|
velocities,
|
|
638
|
+
set,
|
|
272
639
|
zones,
|
|
273
640
|
liveRepIndex,
|
|
274
|
-
expanded =
|
|
641
|
+
expanded = true,
|
|
275
642
|
onToggle,
|
|
276
643
|
onRepPress,
|
|
277
|
-
variant = "
|
|
644
|
+
variant = "expanded",
|
|
645
|
+
height = EXPANDED_HEIGHT,
|
|
646
|
+
scale = "peak",
|
|
647
|
+
showNumbers = true,
|
|
278
648
|
showInfo = true,
|
|
279
649
|
className,
|
|
280
650
|
...props
|
|
281
651
|
}) {
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
|
|
652
|
+
const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
|
|
653
|
+
const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
|
|
654
|
+
kind: "rep",
|
|
655
|
+
velocity: v,
|
|
656
|
+
leadingGap: i === 0 ? 0 : REP_GAP
|
|
657
|
+
}));
|
|
658
|
+
const maxVelocity = Math.max(...doneVelocities, 0);
|
|
659
|
+
const meanVelocity = calculateMeanVelocity(doneVelocities);
|
|
660
|
+
const loss = calculateVelocityLoss(doneVelocities);
|
|
661
|
+
const framed = showNumbers || showInfo;
|
|
662
|
+
const scaleDenom = scaleDenominator(scale, maxVelocity);
|
|
285
663
|
const hasZones = zones != null && zones.length > 0;
|
|
286
664
|
const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
|
|
665
|
+
const slotColor = (slot) => {
|
|
666
|
+
if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
|
|
667
|
+
if (slot.kind === "todo") return TODO_COLOR2;
|
|
668
|
+
return SET_STRIP_VARIABLE_COLOR;
|
|
669
|
+
};
|
|
287
670
|
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
288
671
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
289
|
-
const [heightAnim] = react.useState(() => new reactNative.Animated.Value(expanded ?
|
|
672
|
+
const [heightAnim] = react.useState(() => new reactNative.Animated.Value(expanded ? height : 3));
|
|
290
673
|
const [labelOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
291
674
|
const [infoOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
292
675
|
const [liveScale] = react.useState(() => new reactNative.Animated.Value(1));
|
|
293
|
-
const liveVelocity = liveRepIndex != null ?
|
|
676
|
+
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
294
677
|
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
295
678
|
react.useEffect(() => {
|
|
296
|
-
if (variant
|
|
679
|
+
if (variant !== "expanded" || !framed || liveRepIndex == null || liveVelocity == null) return;
|
|
297
680
|
if (prefersReducedMotion) {
|
|
298
681
|
liveScale.setValue(1);
|
|
299
682
|
return;
|
|
@@ -323,12 +706,12 @@ function VelocityStrip({
|
|
|
323
706
|
useNativeDriver: true
|
|
324
707
|
}).start();
|
|
325
708
|
}
|
|
326
|
-
}, [variant, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
|
|
709
|
+
}, [variant, framed, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
|
|
327
710
|
react.useEffect(() => {
|
|
328
|
-
if (variant
|
|
711
|
+
if (variant !== "expanded" || !framed) return;
|
|
329
712
|
reactNative.Animated.parallel([
|
|
330
713
|
reactNative.Animated.timing(heightAnim, {
|
|
331
|
-
toValue: expanded ?
|
|
714
|
+
toValue: expanded ? height : 3,
|
|
332
715
|
duration: ANIMATION_DURATION,
|
|
333
716
|
easing: ANIMATION_EASING,
|
|
334
717
|
useNativeDriver: false
|
|
@@ -346,39 +729,118 @@ function VelocityStrip({
|
|
|
346
729
|
useNativeDriver: false
|
|
347
730
|
})
|
|
348
731
|
]).start();
|
|
349
|
-
}, [expanded, variant, heightAnim, labelOpacity, infoOpacity]);
|
|
732
|
+
}, [expanded, variant, framed, height, heightAnim, labelOpacity, infoOpacity]);
|
|
733
|
+
if (set == null && velocities == null) return null;
|
|
734
|
+
const repCount = doneVelocities.length;
|
|
735
|
+
const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
|
|
350
736
|
if (variant === "mini") {
|
|
351
737
|
const { style: externalStyle, ...restProps } = props;
|
|
352
738
|
return /* @__PURE__ */ jsx(
|
|
353
739
|
reactNative.View,
|
|
354
740
|
{
|
|
355
741
|
className,
|
|
356
|
-
style: [
|
|
742
|
+
style: [
|
|
743
|
+
{ flexDirection: "row", height: 3, gap: REP_GAP, borderRadius: 2, overflow: "hidden" },
|
|
744
|
+
externalStyle
|
|
745
|
+
],
|
|
357
746
|
accessibilityRole: "image",
|
|
358
|
-
accessibilityLabel:
|
|
747
|
+
accessibilityLabel: miniLabel,
|
|
359
748
|
testID: "velocity-strip-mini",
|
|
360
749
|
...restProps,
|
|
361
|
-
children:
|
|
750
|
+
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
362
751
|
reactNative.View,
|
|
363
752
|
{
|
|
364
753
|
style: {
|
|
365
754
|
flex: 1,
|
|
366
|
-
backgroundColor:
|
|
755
|
+
backgroundColor: slotColor(slot),
|
|
367
756
|
borderRadius: 1,
|
|
368
757
|
minWidth: 4,
|
|
369
|
-
height: "100%"
|
|
758
|
+
height: "100%",
|
|
759
|
+
// The container's uniform 2px gap covers rep spacing; a wide slot adds
|
|
760
|
+
// only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
|
|
761
|
+
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
762
|
+
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
763
|
+
},
|
|
764
|
+
accessibilityElementsHidden: true,
|
|
765
|
+
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
766
|
+
},
|
|
767
|
+
i
|
|
768
|
+
))
|
|
769
|
+
}
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
if (!framed) {
|
|
773
|
+
const { style: externalStyle, ...restProps } = props;
|
|
774
|
+
return /* @__PURE__ */ jsx(
|
|
775
|
+
reactNative.View,
|
|
776
|
+
{
|
|
777
|
+
className,
|
|
778
|
+
style: [
|
|
779
|
+
{ flexDirection: "row", height, gap: REP_GAP, alignItems: "flex-end" },
|
|
780
|
+
externalStyle
|
|
781
|
+
],
|
|
782
|
+
accessibilityRole: "image",
|
|
783
|
+
accessibilityLabel: miniLabel,
|
|
784
|
+
testID: "velocity-strip-compact",
|
|
785
|
+
...restProps,
|
|
786
|
+
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
787
|
+
reactNative.View,
|
|
788
|
+
{
|
|
789
|
+
style: {
|
|
790
|
+
flex: 1,
|
|
791
|
+
minWidth: 4,
|
|
792
|
+
height: bareSlotHeight(slot, height, scaleDenom),
|
|
793
|
+
// Top-rounded to match the framed chart's bars (the "rounded tops from
|
|
794
|
+
// the numbers one"); square bottoms since bars sit on the baseline.
|
|
795
|
+
borderTopLeftRadius: 2,
|
|
796
|
+
borderTopRightRadius: 2,
|
|
797
|
+
backgroundColor: slotColor(slot),
|
|
798
|
+
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
799
|
+
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
370
800
|
},
|
|
371
801
|
accessibilityElementsHidden: true,
|
|
372
|
-
testID: `velocity-bar-${i}`
|
|
802
|
+
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
373
803
|
},
|
|
374
804
|
i
|
|
375
805
|
))
|
|
376
806
|
}
|
|
377
807
|
);
|
|
378
808
|
}
|
|
379
|
-
const stripLabel = `Velocity chart for set, ${
|
|
809
|
+
const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
|
|
380
810
|
const hasInteractiveContainer = onToggle != null;
|
|
381
811
|
const hasInteractiveReps = onRepPress != null && expanded;
|
|
812
|
+
const setBars = set != null && slots.map((slot, i) => {
|
|
813
|
+
const isStraightRep = set.type === "straight" && slot.kind === "rep";
|
|
814
|
+
const heightPct = isStraightRep ? scaleDenom > 0 ? Math.round((slot.velocity ?? 0) / scaleDenom * 100) : 0 : set.type === "straight" ? EXPANDED_TODO_STUB_PCT : EXPANDED_ENCODED_PCT;
|
|
815
|
+
return /* @__PURE__ */ jsx(
|
|
816
|
+
reactNative.View,
|
|
817
|
+
{
|
|
818
|
+
style: {
|
|
819
|
+
flex: 1,
|
|
820
|
+
height: "100%",
|
|
821
|
+
justifyContent: "flex-end",
|
|
822
|
+
marginLeft: slot.leadingGap
|
|
823
|
+
},
|
|
824
|
+
children: /* @__PURE__ */ jsx(
|
|
825
|
+
reactNative.View,
|
|
826
|
+
{
|
|
827
|
+
style: {
|
|
828
|
+
height: `${heightPct}%`,
|
|
829
|
+
minHeight: 2,
|
|
830
|
+
borderTopLeftRadius: 2,
|
|
831
|
+
borderTopRightRadius: 2,
|
|
832
|
+
backgroundColor: slotColor(slot),
|
|
833
|
+
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
834
|
+
},
|
|
835
|
+
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
|
|
836
|
+
accessibilityRole: "image",
|
|
837
|
+
accessibilityLabel: slotAccessibilityLabel(slot, i)
|
|
838
|
+
}
|
|
839
|
+
)
|
|
840
|
+
},
|
|
841
|
+
i
|
|
842
|
+
);
|
|
843
|
+
});
|
|
382
844
|
const stripContent = /* @__PURE__ */ jsxs(
|
|
383
845
|
reactNative.Animated.View,
|
|
384
846
|
{
|
|
@@ -391,7 +853,9 @@ function VelocityStrip({
|
|
|
391
853
|
borderRadius: expanded ? 6 : 2,
|
|
392
854
|
paddingTop: expanded ? 16 : 0,
|
|
393
855
|
paddingBottom: expanded ? 24 : 0,
|
|
394
|
-
|
|
856
|
+
// No horizontal inset — the framed chart's bars span full width, matching
|
|
857
|
+
// the bare spotlight and mini. Numbers/info sit in the vertical padding.
|
|
858
|
+
paddingHorizontal: 0,
|
|
395
859
|
paddingVertical: expanded ? void 0 : 8,
|
|
396
860
|
overflow: expanded ? "visible" : "hidden"
|
|
397
861
|
}
|
|
@@ -401,46 +865,65 @@ function VelocityStrip({
|
|
|
401
865
|
testID: "velocity-strip",
|
|
402
866
|
...props,
|
|
403
867
|
children: [
|
|
404
|
-
/* @__PURE__ */ jsx(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
868
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row", flex: 1, gap: set ? 0 : 2, alignItems: "flex-end" }, children: set ? setBars : doneVelocities.map((v, i) => {
|
|
869
|
+
const barBackground = barColorFor(v);
|
|
870
|
+
const barHeightPct = scaleDenom > 0 ? Math.round(v / scaleDenom * 100) : 0;
|
|
871
|
+
const isLive = liveRepIndex === i;
|
|
872
|
+
const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
|
|
873
|
+
const barStyle = expanded ? {
|
|
874
|
+
height: `${barHeightPct}%`,
|
|
875
|
+
minHeight: 2,
|
|
876
|
+
borderTopLeftRadius: 2,
|
|
877
|
+
borderTopRightRadius: 2,
|
|
878
|
+
backgroundColor: barBackground
|
|
879
|
+
} : {
|
|
880
|
+
minHeight: "100%",
|
|
881
|
+
borderTopLeftRadius: 2,
|
|
882
|
+
borderTopRightRadius: 2,
|
|
883
|
+
borderBottomLeftRadius: 0,
|
|
884
|
+
borderBottomRightRadius: 0,
|
|
885
|
+
backgroundColor: barBackground
|
|
886
|
+
};
|
|
887
|
+
const barInner = isLive ? /* @__PURE__ */ jsx(
|
|
888
|
+
reactNative.Animated.View,
|
|
889
|
+
{
|
|
890
|
+
style: [barStyle, { transform: [{ scale: liveScale }] }],
|
|
891
|
+
testID: `velocity-bar-${i}`,
|
|
892
|
+
accessibilityRole: "image",
|
|
893
|
+
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
|
|
894
|
+
}
|
|
895
|
+
) : /* @__PURE__ */ jsx(
|
|
896
|
+
reactNative.View,
|
|
897
|
+
{
|
|
898
|
+
style: barStyle,
|
|
899
|
+
testID: `velocity-bar-${i}`,
|
|
900
|
+
accessibilityRole: "image",
|
|
901
|
+
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
|
|
902
|
+
}
|
|
903
|
+
);
|
|
904
|
+
const bar = /* @__PURE__ */ jsxs(
|
|
905
|
+
reactNative.View,
|
|
906
|
+
{
|
|
907
|
+
style: {
|
|
908
|
+
flex: 1,
|
|
909
|
+
height: "100%",
|
|
910
|
+
justifyContent: "flex-end",
|
|
911
|
+
position: "relative"
|
|
912
|
+
},
|
|
913
|
+
children: [
|
|
914
|
+
expanded && showNumbers && /* @__PURE__ */ jsx(
|
|
915
|
+
reactNative.Animated.View,
|
|
916
|
+
{
|
|
917
|
+
style: {
|
|
918
|
+
opacity: labelOpacity,
|
|
919
|
+
alignItems: "center",
|
|
920
|
+
position: "absolute",
|
|
921
|
+
top: -13,
|
|
922
|
+
left: 0,
|
|
923
|
+
right: 0
|
|
924
|
+
},
|
|
925
|
+
accessibilityElementsHidden: true,
|
|
926
|
+
children: /* @__PURE__ */ jsx(
|
|
444
927
|
reactNative.Text,
|
|
445
928
|
{
|
|
446
929
|
className: "text-text-secondary",
|
|
@@ -448,30 +931,30 @@ function VelocityStrip({
|
|
|
448
931
|
testID: `velocity-label-${i}`,
|
|
449
932
|
children: formatVelocity(v)
|
|
450
933
|
}
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
})
|
|
934
|
+
)
|
|
935
|
+
}
|
|
936
|
+
),
|
|
937
|
+
barInner
|
|
938
|
+
]
|
|
939
|
+
},
|
|
940
|
+
i
|
|
941
|
+
);
|
|
942
|
+
if (onRepPress && expanded) {
|
|
943
|
+
return /* @__PURE__ */ jsx(
|
|
944
|
+
reactNative.Pressable,
|
|
945
|
+
{
|
|
946
|
+
style: { flex: 1, height: "100%" },
|
|
947
|
+
onPress: () => onRepPress(i, v),
|
|
948
|
+
accessibilityRole: "button",
|
|
949
|
+
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
|
|
950
|
+
testID: `velocity-bar-pressable-${i}`,
|
|
951
|
+
children: bar
|
|
952
|
+
},
|
|
953
|
+
i
|
|
954
|
+
);
|
|
473
955
|
}
|
|
474
|
-
|
|
956
|
+
return bar;
|
|
957
|
+
}) }),
|
|
475
958
|
expanded && showInfo && /* @__PURE__ */ jsxs(
|
|
476
959
|
reactNative.Animated.View,
|
|
477
960
|
{
|
|
@@ -647,17 +1130,49 @@ function SvgIcon({
|
|
|
647
1130
|
}
|
|
648
1131
|
|
|
649
1132
|
// src/components/icons/icons.tsx
|
|
650
|
-
function
|
|
1133
|
+
function StarIcon(props) {
|
|
1134
|
+
return /* @__PURE__ */ jsx(SvgIcon, { ...props, children: /* @__PURE__ */ jsx("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" }) });
|
|
1135
|
+
}
|
|
1136
|
+
function ScaleIcon(props) {
|
|
651
1137
|
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
652
|
-
/* @__PURE__ */ jsx("path", { d: "
|
|
653
|
-
/* @__PURE__ */ jsx("path", { d: "
|
|
654
|
-
/* @__PURE__ */ jsx("path", { d: "
|
|
655
|
-
/* @__PURE__ */ jsx("path", { d: "
|
|
656
|
-
/* @__PURE__ */ jsx("path", { d: "
|
|
1138
|
+
/* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1139
|
+
/* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1140
|
+
/* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
|
|
1141
|
+
/* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
|
|
1142
|
+
/* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
|
|
657
1143
|
] });
|
|
658
1144
|
}
|
|
659
|
-
function
|
|
660
|
-
return /* @__PURE__ */
|
|
1145
|
+
function AlertTriangleIcon(props) {
|
|
1146
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1147
|
+
/* @__PURE__ */ jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }),
|
|
1148
|
+
/* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
|
|
1149
|
+
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
1150
|
+
] });
|
|
1151
|
+
}
|
|
1152
|
+
function TrendingDownIcon(props) {
|
|
1153
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1154
|
+
/* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
|
|
1155
|
+
/* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
|
|
1156
|
+
] });
|
|
1157
|
+
}
|
|
1158
|
+
function CircleSlashIcon(props) {
|
|
1159
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1160
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1161
|
+
/* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
|
|
1162
|
+
] });
|
|
1163
|
+
}
|
|
1164
|
+
function AwardIcon(props) {
|
|
1165
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1166
|
+
/* @__PURE__ */ jsx("path", { d: "m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526" }),
|
|
1167
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
|
|
1168
|
+
] });
|
|
1169
|
+
}
|
|
1170
|
+
function InfoIcon(props) {
|
|
1171
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1172
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1173
|
+
/* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
|
|
1174
|
+
/* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
|
|
1175
|
+
] });
|
|
661
1176
|
}
|
|
662
1177
|
var baseBadgeSizeConfig = {
|
|
663
1178
|
sm: { fontSize: 9, paddingH: 6, paddingV: 2, iconSize: 10 },
|
|
@@ -760,6 +1275,9 @@ var semanticColorsLight = {
|
|
|
760
1275
|
"status-success-light": primitiveRamps.green[200],
|
|
761
1276
|
"status-success-dark": primitiveRamps.green[600],
|
|
762
1277
|
"status-success-subtle": primitiveRamps.green[50],
|
|
1278
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
1279
|
+
"status-live": primitiveRamps.green[300],
|
|
1280
|
+
"status-live-muted": primitiveRamps.green[500],
|
|
763
1281
|
"status-error": primitiveRamps.red[600],
|
|
764
1282
|
"status-error-light": primitiveRamps.red[500],
|
|
765
1283
|
"status-error-dark": primitiveRamps.red[700],
|
|
@@ -893,6 +1411,9 @@ var semanticColorsDark = {
|
|
|
893
1411
|
"status-success-light": primitiveRamps.green[200],
|
|
894
1412
|
"status-success-dark": primitiveRamps.green[600],
|
|
895
1413
|
"status-success-subtle": "rgba(46, 213, 115, 0.12)",
|
|
1414
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
1415
|
+
"status-live": primitiveRamps.green[300],
|
|
1416
|
+
"status-live-muted": primitiveRamps.green[500],
|
|
896
1417
|
"status-error": primitiveRamps.red[600],
|
|
897
1418
|
"status-error-light": primitiveRamps.red[500],
|
|
898
1419
|
"status-error-dark": primitiveRamps.red[700],
|
|
@@ -997,106 +1518,20 @@ function getSemanticColors(mode) {
|
|
|
997
1518
|
return mode === "dark" ? semanticColorsDark : semanticColorsLight;
|
|
998
1519
|
}
|
|
999
1520
|
|
|
1000
|
-
// src/components/custom/Workout/
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const textColor = isPr ? getSemanticColors("dark")["brand-primary"] : "#9CA3AF";
|
|
1015
|
-
const iconColor = textColor;
|
|
1016
|
-
const repMaxClause = reps != null ? `${reps} rep max` : "Estimated one rep max";
|
|
1017
|
-
const deltaLabel = delta != null ? `, ${delta >= 0 ? "+" : ""}${delta}% change` : "";
|
|
1018
|
-
const fullLabel = `${repMaxClause}: ${value} ${unit}${deltaLabel}`;
|
|
1019
|
-
return /* @__PURE__ */ jsxs(
|
|
1020
|
-
BaseBadge,
|
|
1021
|
-
{
|
|
1022
|
-
variant: isPr ? "pr" : "plain",
|
|
1023
|
-
size,
|
|
1024
|
-
onPress,
|
|
1025
|
-
className,
|
|
1026
|
-
icon: showIcon ? /* @__PURE__ */ jsx(DumbbellIcon, { size: config.iconSize, color: iconColor, strokeWidth: 2 }) : void 0,
|
|
1027
|
-
accessibilityLabel: fullLabel,
|
|
1028
|
-
testID: "weight-badge",
|
|
1029
|
-
...props,
|
|
1030
|
-
children: [
|
|
1031
|
-
/* @__PURE__ */ jsxs(
|
|
1032
|
-
reactNative.Text,
|
|
1033
|
-
{
|
|
1034
|
-
style: {
|
|
1035
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
1036
|
-
fontWeight: "600",
|
|
1037
|
-
fontSize: config.fontSize,
|
|
1038
|
-
color: textColor
|
|
1039
|
-
},
|
|
1040
|
-
children: [
|
|
1041
|
-
isPr ? "\u2733 " : "",
|
|
1042
|
-
value,
|
|
1043
|
-
" ",
|
|
1044
|
-
unit
|
|
1045
|
-
]
|
|
1046
|
-
}
|
|
1047
|
-
),
|
|
1048
|
-
reps != null && /* @__PURE__ */ jsxs(
|
|
1049
|
-
reactNative.Text,
|
|
1050
|
-
{
|
|
1051
|
-
style: {
|
|
1052
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
1053
|
-
fontWeight: "600",
|
|
1054
|
-
fontSize: config.fontSize,
|
|
1055
|
-
marginLeft: 2,
|
|
1056
|
-
color: textColor,
|
|
1057
|
-
opacity: 0.7
|
|
1058
|
-
},
|
|
1059
|
-
testID: "weight-badge-repmax",
|
|
1060
|
-
children: [
|
|
1061
|
-
reps,
|
|
1062
|
-
"RM"
|
|
1063
|
-
]
|
|
1064
|
-
}
|
|
1065
|
-
),
|
|
1066
|
-
delta != null && /* @__PURE__ */ jsxs(
|
|
1067
|
-
reactNative.Text,
|
|
1068
|
-
{
|
|
1069
|
-
style: {
|
|
1070
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
1071
|
-
fontSize: config.fontSize,
|
|
1072
|
-
marginLeft: 4,
|
|
1073
|
-
color: delta >= 0 ? getSemanticColors("dark")["result-improve"] : getSemanticColors("dark")["result-degrade"]
|
|
1074
|
-
},
|
|
1075
|
-
testID: "weight-badge-delta",
|
|
1076
|
-
children: [
|
|
1077
|
-
delta >= 0 ? "+" : "",
|
|
1078
|
-
delta,
|
|
1079
|
-
"%"
|
|
1080
|
-
]
|
|
1081
|
-
}
|
|
1082
|
-
)
|
|
1083
|
-
]
|
|
1084
|
-
}
|
|
1085
|
-
);
|
|
1086
|
-
}
|
|
1087
|
-
var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
|
|
1088
|
-
var typeLabels = {
|
|
1089
|
-
e1rm: "PR e1RM",
|
|
1090
|
-
weight: "PR Weight",
|
|
1091
|
-
reps: "PR Reps",
|
|
1092
|
-
volume: "PR Volume",
|
|
1093
|
-
velocity: "PR Velocity"
|
|
1094
|
-
};
|
|
1095
|
-
function PrBadge({
|
|
1096
|
-
type = "e1rm",
|
|
1097
|
-
label: labelProp,
|
|
1098
|
-
compact = false,
|
|
1099
|
-
animate = true,
|
|
1521
|
+
// src/components/custom/Workout/PrBadge.tsx
|
|
1522
|
+
var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
|
|
1523
|
+
var typeLabels = {
|
|
1524
|
+
e1rm: "PR e1RM",
|
|
1525
|
+
weight: "PR Weight",
|
|
1526
|
+
reps: "PR Reps",
|
|
1527
|
+
volume: "PR Volume",
|
|
1528
|
+
velocity: "PR Velocity"
|
|
1529
|
+
};
|
|
1530
|
+
function PrBadge({
|
|
1531
|
+
type = "e1rm",
|
|
1532
|
+
label: labelProp,
|
|
1533
|
+
compact = false,
|
|
1534
|
+
animate = true,
|
|
1100
1535
|
className,
|
|
1101
1536
|
...props
|
|
1102
1537
|
}) {
|
|
@@ -1167,6 +1602,204 @@ function PrBadge({
|
|
|
1167
1602
|
}
|
|
1168
1603
|
return badge;
|
|
1169
1604
|
}
|
|
1605
|
+
var DARK = getSemanticColors("dark");
|
|
1606
|
+
var TEXT_ACTIVE = DARK["text-primary"];
|
|
1607
|
+
var TEXT_MUTED = DARK["text-secondary"];
|
|
1608
|
+
var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
|
|
1609
|
+
var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
|
|
1610
|
+
var typeBadge = {
|
|
1611
|
+
fontSize: 10,
|
|
1612
|
+
fontWeight: "700",
|
|
1613
|
+
fontFamily: "Inter, sans-serif",
|
|
1614
|
+
color: WORKOUT_TOKENS.scale.orange,
|
|
1615
|
+
backgroundColor: "rgba(255,165,2,0.12)",
|
|
1616
|
+
paddingVertical: 1,
|
|
1617
|
+
paddingHorizontal: 5,
|
|
1618
|
+
borderRadius: 3,
|
|
1619
|
+
overflow: "hidden"
|
|
1620
|
+
};
|
|
1621
|
+
function displayReps(set) {
|
|
1622
|
+
return set.state === "done" ? set.reps : set.target.reps;
|
|
1623
|
+
}
|
|
1624
|
+
function displayWeight(set) {
|
|
1625
|
+
return set.state === "done" ? set.weight : set.target.weight;
|
|
1626
|
+
}
|
|
1627
|
+
function plannedReps(set) {
|
|
1628
|
+
if (set.state === "done") return set.velocities.length;
|
|
1629
|
+
if (set.state === "live") return set.target.reps;
|
|
1630
|
+
return set.planned ?? set.target.reps;
|
|
1631
|
+
}
|
|
1632
|
+
function accessibilityLabel(set) {
|
|
1633
|
+
const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
|
|
1634
|
+
if (set.state === "live") return `${base}, in progress`;
|
|
1635
|
+
if (set.state === "todo") return `${base}, upcoming`;
|
|
1636
|
+
return base;
|
|
1637
|
+
}
|
|
1638
|
+
function Cell({
|
|
1639
|
+
width,
|
|
1640
|
+
children,
|
|
1641
|
+
testID
|
|
1642
|
+
}) {
|
|
1643
|
+
return /* @__PURE__ */ jsx(reactNative.View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
|
|
1644
|
+
}
|
|
1645
|
+
function RowStrip({ set }) {
|
|
1646
|
+
const zones = set.velocityZones;
|
|
1647
|
+
if (set.state === "live") {
|
|
1648
|
+
return /* @__PURE__ */ jsx(
|
|
1649
|
+
VelocityStrip,
|
|
1650
|
+
{
|
|
1651
|
+
variant: "expanded",
|
|
1652
|
+
showNumbers: false,
|
|
1653
|
+
showInfo: false,
|
|
1654
|
+
height: 24,
|
|
1655
|
+
scale: "fixed",
|
|
1656
|
+
set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
|
|
1657
|
+
zones
|
|
1658
|
+
}
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
const velocities = set.state === "done" ? set.velocities : [];
|
|
1662
|
+
return /* @__PURE__ */ jsx(
|
|
1663
|
+
VelocityStrip,
|
|
1664
|
+
{
|
|
1665
|
+
variant: "mini",
|
|
1666
|
+
set: { type: "straight", velocities, planned: plannedReps(set) },
|
|
1667
|
+
zones
|
|
1668
|
+
}
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
function SetRow(set) {
|
|
1672
|
+
const live = set.state === "live";
|
|
1673
|
+
const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
|
|
1674
|
+
const rpe = set.state === "todo" ? null : set.rpe;
|
|
1675
|
+
return /* @__PURE__ */ jsxs(
|
|
1676
|
+
reactNative.View,
|
|
1677
|
+
{
|
|
1678
|
+
style: { paddingVertical: 6, paddingHorizontal: 8 },
|
|
1679
|
+
accessibilityLabel: accessibilityLabel(set),
|
|
1680
|
+
testID: "set-row",
|
|
1681
|
+
children: [
|
|
1682
|
+
/* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
|
|
1683
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.set, testID: "set-row-set-number", children: set.setType ? /* @__PURE__ */ jsx(reactNative.Text, { style: typeBadge, testID: "set-row-type-badge", children: set.setType }) : /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor, fontWeight: live ? "700" : "600" }, children: set.setNumber }) }),
|
|
1684
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
|
|
1685
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
|
|
1686
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.rpe, testID: "set-row-rpe", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: TEXT_MUTED }, children: rpe != null ? roundRpe(rpe) : "\u2014" }) })
|
|
1687
|
+
] }),
|
|
1688
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
|
|
1689
|
+
]
|
|
1690
|
+
}
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
var PREV_COLUMN = { label: "PREV", width: void 0 };
|
|
1694
|
+
function buildColumns(unit, showPrevious) {
|
|
1695
|
+
return [
|
|
1696
|
+
{ label: "SET", width: 36 },
|
|
1697
|
+
...showPrevious ? [PREV_COLUMN] : [],
|
|
1698
|
+
{ label: "REPS", width: 44 },
|
|
1699
|
+
{ label: unit.toUpperCase(), width: 56 },
|
|
1700
|
+
{ label: "RPE", width: 36 }
|
|
1701
|
+
];
|
|
1702
|
+
}
|
|
1703
|
+
function SetTableHeader({
|
|
1704
|
+
unit = "lbs",
|
|
1705
|
+
showPrevious = true,
|
|
1706
|
+
testID = "table-header"
|
|
1707
|
+
}) {
|
|
1708
|
+
const columns = buildColumns(unit, showPrevious);
|
|
1709
|
+
return /* @__PURE__ */ jsx(
|
|
1710
|
+
reactNative.View,
|
|
1711
|
+
{
|
|
1712
|
+
className: "flex-row",
|
|
1713
|
+
style: {
|
|
1714
|
+
paddingVertical: 8,
|
|
1715
|
+
paddingHorizontal: 8,
|
|
1716
|
+
paddingBottom: 4,
|
|
1717
|
+
...showPrevious ? {} : { justifyContent: "space-between" }
|
|
1718
|
+
},
|
|
1719
|
+
testID,
|
|
1720
|
+
children: columns.map(({ label, width }) => {
|
|
1721
|
+
const isFlex = width === void 0;
|
|
1722
|
+
return /* @__PURE__ */ jsx(
|
|
1723
|
+
reactNative.View,
|
|
1724
|
+
{
|
|
1725
|
+
style: {
|
|
1726
|
+
...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
|
|
1727
|
+
alignItems: "center",
|
|
1728
|
+
justifyContent: "center",
|
|
1729
|
+
gap: 8
|
|
1730
|
+
},
|
|
1731
|
+
...isFlex ? { className: "flex-1" } : {},
|
|
1732
|
+
children: /* @__PURE__ */ jsx(
|
|
1733
|
+
reactNative.Text,
|
|
1734
|
+
{
|
|
1735
|
+
className: "text-text-tertiary",
|
|
1736
|
+
style: {
|
|
1737
|
+
fontSize: 10,
|
|
1738
|
+
fontWeight: "600",
|
|
1739
|
+
fontFamily: "Inter, sans-serif",
|
|
1740
|
+
textTransform: "uppercase",
|
|
1741
|
+
letterSpacing: 0.8
|
|
1742
|
+
},
|
|
1743
|
+
children: label
|
|
1744
|
+
}
|
|
1745
|
+
)
|
|
1746
|
+
},
|
|
1747
|
+
label
|
|
1748
|
+
);
|
|
1749
|
+
})
|
|
1750
|
+
}
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
var METRIC_FONT = "Inter, sans-serif";
|
|
1754
|
+
function MetricCell({ children, color, fontSize = 11 }) {
|
|
1755
|
+
return /* @__PURE__ */ jsx(
|
|
1756
|
+
reactNative.Text,
|
|
1757
|
+
{
|
|
1758
|
+
style: {
|
|
1759
|
+
fontFamily: METRIC_FONT,
|
|
1760
|
+
fontSize,
|
|
1761
|
+
color,
|
|
1762
|
+
fontWeight: "600",
|
|
1763
|
+
letterSpacing: 1
|
|
1764
|
+
},
|
|
1765
|
+
children
|
|
1766
|
+
}
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// src/components/custom/Workout/SetsRepsLoad.tsx
|
|
1771
|
+
var t = getSemanticColors("dark");
|
|
1772
|
+
var TIMES = "\xD7";
|
|
1773
|
+
var AT = "@";
|
|
1774
|
+
function SetsRepsLoad({
|
|
1775
|
+
sets,
|
|
1776
|
+
reps,
|
|
1777
|
+
load,
|
|
1778
|
+
unit = "lb",
|
|
1779
|
+
className,
|
|
1780
|
+
...props
|
|
1781
|
+
}) {
|
|
1782
|
+
const value = t["text-primary"];
|
|
1783
|
+
const sep = t["text-tertiary"];
|
|
1784
|
+
return /* @__PURE__ */ jsxs(
|
|
1785
|
+
reactNative.View,
|
|
1786
|
+
{
|
|
1787
|
+
className,
|
|
1788
|
+
style: { flexDirection: "row", alignItems: "baseline" },
|
|
1789
|
+
accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
|
|
1790
|
+
testID: "sets-reps-load",
|
|
1791
|
+
...props,
|
|
1792
|
+
children: [
|
|
1793
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, children: sets }),
|
|
1794
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${TIMES} ` }),
|
|
1795
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, children: reps }),
|
|
1796
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${AT} ` }),
|
|
1797
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, children: load }),
|
|
1798
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${unit}` })
|
|
1799
|
+
]
|
|
1800
|
+
}
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1170
1803
|
function hexToRgb(hex) {
|
|
1171
1804
|
const cleaned = hex.replace("#", "");
|
|
1172
1805
|
let r, g, b;
|
|
@@ -1476,7 +2109,7 @@ function alpha(color, opacity) {
|
|
|
1476
2109
|
}
|
|
1477
2110
|
return color;
|
|
1478
2111
|
}
|
|
1479
|
-
var
|
|
2112
|
+
var t2 = getSemanticColors("dark");
|
|
1480
2113
|
var TEMPO_PACING = {
|
|
1481
2114
|
behindThresholdPct: 0.15,
|
|
1482
2115
|
minPhaseDurationMs: 500
|
|
@@ -1492,20 +2125,20 @@ function getTempoFillPct(elapsedMs, targetMs) {
|
|
|
1492
2125
|
return Math.min(100, elapsedMs / targetMs * 100);
|
|
1493
2126
|
}
|
|
1494
2127
|
({
|
|
1495
|
-
concentric: { color:
|
|
1496
|
-
hold: { color:
|
|
1497
|
-
eccentric: { color:
|
|
2128
|
+
concentric: { color: t2["status-success"]},
|
|
2129
|
+
hold: { color: t2["brand-primary"]},
|
|
2130
|
+
eccentric: { color: t2["status-warning"]}
|
|
1498
2131
|
});
|
|
1499
2132
|
|
|
1500
2133
|
// src/components/custom/Workout/TempoDisplay.tsx
|
|
1501
|
-
var
|
|
2134
|
+
var t3 = getSemanticColors("dark");
|
|
1502
2135
|
var INTER = "Inter, sans-serif";
|
|
1503
2136
|
var TEXT_TERTIARY = "#6B7280";
|
|
1504
|
-
var STATUS_ERROR =
|
|
2137
|
+
var STATUS_ERROR = t3["status-error"];
|
|
1505
2138
|
var phaseColors = {
|
|
1506
|
-
eccentric:
|
|
2139
|
+
eccentric: t3["status-warning"],
|
|
1507
2140
|
pauseBottom: TEXT_TERTIARY,
|
|
1508
|
-
concentric:
|
|
2141
|
+
concentric: t3["status-success"],
|
|
1509
2142
|
pauseTop: TEXT_TERTIARY,
|
|
1510
2143
|
dash: TEXT_TERTIARY
|
|
1511
2144
|
};
|
|
@@ -1514,34 +2147,10 @@ function TempoValue({
|
|
|
1514
2147
|
color,
|
|
1515
2148
|
fontSize
|
|
1516
2149
|
}) {
|
|
1517
|
-
return /* @__PURE__ */ jsx(
|
|
1518
|
-
reactNative.Text,
|
|
1519
|
-
{
|
|
1520
|
-
style: {
|
|
1521
|
-
fontFamily: INTER,
|
|
1522
|
-
fontSize,
|
|
1523
|
-
color,
|
|
1524
|
-
fontWeight: "600",
|
|
1525
|
-
letterSpacing: 1
|
|
1526
|
-
},
|
|
1527
|
-
children: value
|
|
1528
|
-
}
|
|
1529
|
-
);
|
|
2150
|
+
return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: value });
|
|
1530
2151
|
}
|
|
1531
2152
|
function TempoSeparator({ color, fontSize }) {
|
|
1532
|
-
return /* @__PURE__ */ jsx(
|
|
1533
|
-
reactNative.Text,
|
|
1534
|
-
{
|
|
1535
|
-
style: {
|
|
1536
|
-
fontFamily: INTER,
|
|
1537
|
-
fontSize,
|
|
1538
|
-
color,
|
|
1539
|
-
fontWeight: "600",
|
|
1540
|
-
letterSpacing: 1
|
|
1541
|
-
},
|
|
1542
|
-
children: "-"
|
|
1543
|
-
}
|
|
1544
|
-
);
|
|
2153
|
+
return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: "-" });
|
|
1545
2154
|
}
|
|
1546
2155
|
var LIVE_PHASES = [
|
|
1547
2156
|
{ key: "eccentric", color: phaseColors.eccentric },
|
|
@@ -1603,6 +2212,7 @@ function TempoDisplay({
|
|
|
1603
2212
|
tempo,
|
|
1604
2213
|
size = "md",
|
|
1605
2214
|
colored = true,
|
|
2215
|
+
showLabel = true,
|
|
1606
2216
|
showInfo = true,
|
|
1607
2217
|
live,
|
|
1608
2218
|
onPress,
|
|
@@ -1636,7 +2246,7 @@ function TempoDisplay({
|
|
|
1636
2246
|
},
|
|
1637
2247
|
...props,
|
|
1638
2248
|
children: [
|
|
1639
|
-
/* @__PURE__ */ jsx(
|
|
2249
|
+
showLabel && /* @__PURE__ */ jsx(
|
|
1640
2250
|
reactNative.Text,
|
|
1641
2251
|
{
|
|
1642
2252
|
style: {
|
|
@@ -1715,300 +2325,176 @@ function TempoDisplay({
|
|
|
1715
2325
|
children: [
|
|
1716
2326
|
/* @__PURE__ */ jsxs(
|
|
1717
2327
|
reactNative.View,
|
|
1718
|
-
{
|
|
1719
|
-
style: {
|
|
1720
|
-
backgroundColor: "#191919",
|
|
1721
|
-
borderRadius: 6,
|
|
1722
|
-
paddingVertical: 8,
|
|
1723
|
-
paddingHorizontal: 12,
|
|
1724
|
-
borderWidth: 1,
|
|
1725
|
-
borderColor: "#2C2C2C"
|
|
1726
|
-
},
|
|
1727
|
-
children: [
|
|
1728
|
-
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
1729
|
-
"Eccentric: ",
|
|
1730
|
-
eccentric,
|
|
1731
|
-
"s"
|
|
1732
|
-
] }),
|
|
1733
|
-
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
1734
|
-
"Pause (bottom): ",
|
|
1735
|
-
pauseBottom,
|
|
1736
|
-
"s"
|
|
1737
|
-
] }),
|
|
1738
|
-
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
1739
|
-
"Concentric: ",
|
|
1740
|
-
concentric,
|
|
1741
|
-
"s"
|
|
1742
|
-
] }),
|
|
1743
|
-
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
1744
|
-
"Pause (top): ",
|
|
1745
|
-
pauseTop,
|
|
1746
|
-
"s"
|
|
1747
|
-
] })
|
|
1748
|
-
]
|
|
1749
|
-
}
|
|
1750
|
-
),
|
|
1751
|
-
/* @__PURE__ */ jsx(
|
|
1752
|
-
reactNative.View,
|
|
1753
|
-
{
|
|
1754
|
-
style: {
|
|
1755
|
-
width: 0,
|
|
1756
|
-
height: 0,
|
|
1757
|
-
borderLeftWidth: 5,
|
|
1758
|
-
borderRightWidth: 5,
|
|
1759
|
-
borderTopWidth: 5,
|
|
1760
|
-
borderLeftColor: "transparent",
|
|
1761
|
-
borderRightColor: "transparent",
|
|
1762
|
-
borderTopColor: "#2C2C2C"
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
)
|
|
1766
|
-
]
|
|
1767
|
-
}
|
|
1768
|
-
)
|
|
1769
|
-
]
|
|
1770
|
-
}
|
|
1771
|
-
);
|
|
1772
|
-
}
|
|
1773
|
-
function formatAccessibilityLabel(setNumber, reps, weight, unit) {
|
|
1774
|
-
const repsText = reps != null ? `${reps} reps` : "no reps";
|
|
1775
|
-
const weightText = weight != null ? `at ${roundWeight(weight)} ${unit}` : "";
|
|
1776
|
-
return `Set ${setNumber}: ${repsText}${weightText ? ` ${weightText}` : ""}`;
|
|
1777
|
-
}
|
|
1778
|
-
function SetRow({
|
|
1779
|
-
mode,
|
|
1780
|
-
setNumber,
|
|
1781
|
-
previous,
|
|
1782
|
-
reps,
|
|
1783
|
-
weight,
|
|
1784
|
-
rpe,
|
|
1785
|
-
unit,
|
|
1786
|
-
velocities,
|
|
1787
|
-
velocityZones,
|
|
1788
|
-
velocityLiveRepIndex,
|
|
1789
|
-
velocityExpanded,
|
|
1790
|
-
onVelocityToggle,
|
|
1791
|
-
setType,
|
|
1792
|
-
prBadges,
|
|
1793
|
-
isNextSet,
|
|
1794
|
-
targets
|
|
1795
|
-
}) {
|
|
1796
|
-
const isActive = mode === "active";
|
|
1797
|
-
const isCompleted = mode === "completed";
|
|
1798
|
-
const rowStyle = {
|
|
1799
|
-
flexDirection: "row",
|
|
1800
|
-
alignItems: "center",
|
|
1801
|
-
padding: 7,
|
|
1802
|
-
paddingHorizontal: 8,
|
|
1803
|
-
gap: 8,
|
|
1804
|
-
borderRadius: 8
|
|
1805
|
-
};
|
|
1806
|
-
if (isActive && isNextSet) {
|
|
1807
|
-
rowStyle.backgroundColor = "rgba(255,121,0,0.06)";
|
|
1808
|
-
rowStyle.borderWidth = 1;
|
|
1809
|
-
rowStyle.borderColor = "rgba(255,121,0,0.15)";
|
|
1810
|
-
}
|
|
1811
|
-
if (isCompleted && !isNextSet) {
|
|
1812
|
-
rowStyle.opacity = 0.55;
|
|
1813
|
-
}
|
|
1814
|
-
return /* @__PURE__ */ jsxs(
|
|
1815
|
-
reactNative.View,
|
|
1816
|
-
{
|
|
1817
|
-
style: rowStyle,
|
|
1818
|
-
accessibilityLabel: formatAccessibilityLabel(
|
|
1819
|
-
setNumber,
|
|
1820
|
-
reps,
|
|
1821
|
-
weight,
|
|
1822
|
-
unit
|
|
1823
|
-
),
|
|
1824
|
-
testID: "set-row",
|
|
1825
|
-
children: [
|
|
1826
|
-
/* @__PURE__ */ jsx(
|
|
1827
|
-
reactNative.View,
|
|
1828
|
-
{
|
|
1829
|
-
style: { width: 36, alignItems: "center", justifyContent: "center" },
|
|
1830
|
-
testID: "set-row-set-number",
|
|
1831
|
-
children: setType ? /* @__PURE__ */ jsx(
|
|
1832
|
-
reactNative.Text,
|
|
1833
|
-
{
|
|
1834
|
-
style: {
|
|
1835
|
-
fontSize: 10,
|
|
1836
|
-
fontWeight: "700",
|
|
1837
|
-
fontFamily: "Inter, sans-serif",
|
|
1838
|
-
color: WORKOUT_TOKENS.scale.orange,
|
|
1839
|
-
backgroundColor: "rgba(255,165,2,0.12)",
|
|
1840
|
-
paddingVertical: 1,
|
|
1841
|
-
paddingHorizontal: 5,
|
|
1842
|
-
borderRadius: 3,
|
|
1843
|
-
overflow: "hidden"
|
|
1844
|
-
},
|
|
1845
|
-
testID: "set-row-type-badge",
|
|
1846
|
-
children: setType
|
|
1847
|
-
}
|
|
1848
|
-
) : /* @__PURE__ */ jsx(
|
|
1849
|
-
reactNative.Text,
|
|
1850
|
-
{
|
|
1851
|
-
className: "text-text-secondary",
|
|
1852
|
-
style: {
|
|
1853
|
-
fontSize: 13,
|
|
1854
|
-
fontWeight: "600",
|
|
1855
|
-
fontFamily: "Inter, sans-serif"
|
|
1856
|
-
},
|
|
1857
|
-
children: setNumber
|
|
1858
|
-
}
|
|
1859
|
-
)
|
|
1860
|
-
}
|
|
1861
|
-
),
|
|
1862
|
-
/* @__PURE__ */ jsx(
|
|
1863
|
-
reactNative.View,
|
|
1864
|
-
{
|
|
1865
|
-
className: "flex-1",
|
|
1866
|
-
testID: "set-row-previous",
|
|
1867
|
-
children: /* @__PURE__ */ jsx(
|
|
1868
|
-
reactNative.Text,
|
|
1869
|
-
{
|
|
1870
|
-
className: "text-text-secondary",
|
|
1871
|
-
style: {
|
|
1872
|
-
fontSize: 12,
|
|
1873
|
-
fontFamily: "Inter, sans-serif"
|
|
1874
|
-
},
|
|
1875
|
-
children: previous ? `${previous.reps} x ${roundWeight(previous.weight)}` : "\u2014"
|
|
1876
|
-
}
|
|
1877
|
-
)
|
|
1878
|
-
}
|
|
1879
|
-
),
|
|
1880
|
-
/* @__PURE__ */ jsx(
|
|
1881
|
-
reactNative.View,
|
|
1882
|
-
{
|
|
1883
|
-
style: { width: 44, alignItems: "center", justifyContent: "center" },
|
|
1884
|
-
testID: "set-row-reps",
|
|
1885
|
-
children: isActive && reps == null && targets ? /* @__PURE__ */ jsx(
|
|
1886
|
-
reactNative.Text,
|
|
1887
|
-
{
|
|
1888
|
-
className: "text-text-tertiary",
|
|
1889
|
-
style: {
|
|
1890
|
-
fontSize: 13,
|
|
1891
|
-
fontStyle: "italic",
|
|
1892
|
-
fontFamily: "Inter, sans-serif"
|
|
1893
|
-
},
|
|
1894
|
-
testID: "set-row-target-reps",
|
|
1895
|
-
children: targets.reps
|
|
1896
|
-
}
|
|
1897
|
-
) : /* @__PURE__ */ jsx(
|
|
1898
|
-
reactNative.Text,
|
|
1899
|
-
{
|
|
1900
|
-
className: reps != null ? "text-text-primary" : "text-text-tertiary",
|
|
1901
|
-
style: {
|
|
1902
|
-
fontSize: 14,
|
|
1903
|
-
fontWeight: "600",
|
|
1904
|
-
fontFamily: "Inter, sans-serif"
|
|
1905
|
-
},
|
|
1906
|
-
children: reps != null ? reps : "\u2014"
|
|
1907
|
-
}
|
|
1908
|
-
)
|
|
1909
|
-
}
|
|
1910
|
-
),
|
|
1911
|
-
/* @__PURE__ */ jsx(
|
|
1912
|
-
reactNative.View,
|
|
1913
|
-
{
|
|
1914
|
-
style: { width: 56, alignItems: "center", justifyContent: "center" },
|
|
1915
|
-
testID: "set-row-weight",
|
|
1916
|
-
children: isActive && weight == null && targets ? /* @__PURE__ */ jsx(
|
|
1917
|
-
reactNative.Text,
|
|
1918
|
-
{
|
|
1919
|
-
className: "text-text-tertiary",
|
|
1920
|
-
style: {
|
|
1921
|
-
fontSize: 13,
|
|
1922
|
-
fontStyle: "italic",
|
|
1923
|
-
fontFamily: "Inter, sans-serif"
|
|
1924
|
-
},
|
|
1925
|
-
testID: "set-row-target-weight",
|
|
1926
|
-
children: roundWeight(targets.weight)
|
|
1927
|
-
}
|
|
1928
|
-
) : /* @__PURE__ */ jsx(
|
|
1929
|
-
reactNative.Text,
|
|
1930
|
-
{
|
|
1931
|
-
className: weight != null ? "text-text-primary" : "text-text-tertiary",
|
|
1932
|
-
style: {
|
|
1933
|
-
fontSize: 14,
|
|
1934
|
-
fontWeight: "600",
|
|
1935
|
-
fontFamily: "Inter, sans-serif"
|
|
1936
|
-
},
|
|
1937
|
-
children: weight != null ? roundWeight(weight) : "\u2014"
|
|
1938
|
-
}
|
|
1939
|
-
)
|
|
1940
|
-
}
|
|
1941
|
-
),
|
|
1942
|
-
/* @__PURE__ */ jsx(
|
|
1943
|
-
reactNative.View,
|
|
1944
|
-
{
|
|
1945
|
-
style: { width: 36, alignItems: "center", justifyContent: "center" },
|
|
1946
|
-
testID: "set-row-rpe",
|
|
1947
|
-
children: /* @__PURE__ */ jsx(
|
|
1948
|
-
reactNative.Text,
|
|
1949
|
-
{
|
|
1950
|
-
className: rpe != null ? void 0 : "text-text-tertiary",
|
|
1951
|
-
style: {
|
|
1952
|
-
fontSize: 13,
|
|
1953
|
-
fontWeight: "600",
|
|
1954
|
-
fontFamily: "Inter, sans-serif",
|
|
1955
|
-
// rpeColor returns a concrete hex (native-safe); only the tertiary
|
|
1956
|
-
// fallback needs the className token.
|
|
1957
|
-
color: rpe != null ? rpeColor(rpe) : void 0
|
|
1958
|
-
},
|
|
1959
|
-
children: rpe != null ? roundRpe(rpe) : "\u2014"
|
|
1960
|
-
}
|
|
1961
|
-
)
|
|
1962
|
-
}
|
|
1963
|
-
),
|
|
1964
|
-
prBadges && prBadges.length > 0 && /* @__PURE__ */ jsx(
|
|
1965
|
-
reactNative.View,
|
|
1966
|
-
{
|
|
1967
|
-
className: "flex-row items-center",
|
|
1968
|
-
style: { gap: 4 },
|
|
1969
|
-
testID: "set-row-pr-badges",
|
|
1970
|
-
children: prBadges.map((badge, i) => /* @__PURE__ */ jsx(
|
|
1971
|
-
PrBadge,
|
|
1972
|
-
{
|
|
1973
|
-
type: badge.type,
|
|
1974
|
-
label: badge.label,
|
|
1975
|
-
animate: false,
|
|
1976
|
-
compact: true
|
|
1977
|
-
},
|
|
1978
|
-
i
|
|
1979
|
-
))
|
|
1980
|
-
}
|
|
1981
|
-
),
|
|
1982
|
-
velocities && velocities.length > 0 && /* @__PURE__ */ jsx(
|
|
1983
|
-
reactNative.View,
|
|
1984
|
-
{
|
|
1985
|
-
style: { position: "absolute", bottom: 0, left: 8, right: 8 },
|
|
1986
|
-
testID: "set-row-velocity-strip",
|
|
1987
|
-
children: /* @__PURE__ */ jsx(
|
|
1988
|
-
VelocityStrip,
|
|
1989
|
-
{
|
|
1990
|
-
velocities,
|
|
1991
|
-
zones: velocityZones,
|
|
1992
|
-
liveRepIndex: velocityLiveRepIndex,
|
|
1993
|
-
expanded: velocityExpanded,
|
|
1994
|
-
onToggle: onVelocityToggle,
|
|
1995
|
-
variant: onVelocityToggle ? "full" : "mini"
|
|
1996
|
-
}
|
|
1997
|
-
)
|
|
2328
|
+
{
|
|
2329
|
+
style: {
|
|
2330
|
+
backgroundColor: "#191919",
|
|
2331
|
+
borderRadius: 6,
|
|
2332
|
+
paddingVertical: 8,
|
|
2333
|
+
paddingHorizontal: 12,
|
|
2334
|
+
borderWidth: 1,
|
|
2335
|
+
borderColor: "#2C2C2C"
|
|
2336
|
+
},
|
|
2337
|
+
children: [
|
|
2338
|
+
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
2339
|
+
"Eccentric: ",
|
|
2340
|
+
eccentric,
|
|
2341
|
+
"s"
|
|
2342
|
+
] }),
|
|
2343
|
+
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
2344
|
+
"Pause (bottom): ",
|
|
2345
|
+
pauseBottom,
|
|
2346
|
+
"s"
|
|
2347
|
+
] }),
|
|
2348
|
+
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
2349
|
+
"Concentric: ",
|
|
2350
|
+
concentric,
|
|
2351
|
+
"s"
|
|
2352
|
+
] }),
|
|
2353
|
+
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
2354
|
+
"Pause (top): ",
|
|
2355
|
+
pauseTop,
|
|
2356
|
+
"s"
|
|
2357
|
+
] })
|
|
2358
|
+
]
|
|
2359
|
+
}
|
|
2360
|
+
),
|
|
2361
|
+
/* @__PURE__ */ jsx(
|
|
2362
|
+
reactNative.View,
|
|
2363
|
+
{
|
|
2364
|
+
style: {
|
|
2365
|
+
width: 0,
|
|
2366
|
+
height: 0,
|
|
2367
|
+
borderLeftWidth: 5,
|
|
2368
|
+
borderRightWidth: 5,
|
|
2369
|
+
borderTopWidth: 5,
|
|
2370
|
+
borderLeftColor: "transparent",
|
|
2371
|
+
borderRightColor: "transparent",
|
|
2372
|
+
borderTopColor: "#2C2C2C"
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
)
|
|
2376
|
+
]
|
|
1998
2377
|
}
|
|
1999
2378
|
)
|
|
2000
2379
|
]
|
|
2001
2380
|
}
|
|
2002
2381
|
);
|
|
2003
2382
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2383
|
+
var t4 = getSemanticColors("dark");
|
|
2384
|
+
var INDICATOR_CONFIG = {
|
|
2385
|
+
imbalance: { Icon: ScaleIcon, color: t4["status-error"], label: "Left/right imbalance" },
|
|
2386
|
+
overshoot: { Icon: AlertTriangleIcon, color: t4["status-error"], label: "Load overshoot" },
|
|
2387
|
+
"velocity-loss": { Icon: TrendingDownIcon, color: t4["status-warning"], label: "Velocity loss" },
|
|
2388
|
+
"missed-reps": { Icon: CircleSlashIcon, color: t4["status-warning"], label: "Missed reps" },
|
|
2389
|
+
pr: { Icon: AwardIcon, color: t4["status-success"], label: "Personal record" },
|
|
2390
|
+
info: { Icon: InfoIcon, color: t4["status-info"], label: "More info" }
|
|
2391
|
+
};
|
|
2392
|
+
function ExerciseIndicator({ kind, onPress, className, ...props }) {
|
|
2393
|
+
const { Icon, color, label } = INDICATOR_CONFIG[kind];
|
|
2394
|
+
const chip = /* @__PURE__ */ jsx(
|
|
2395
|
+
reactNative.View,
|
|
2396
|
+
{
|
|
2397
|
+
className,
|
|
2398
|
+
style: {
|
|
2399
|
+
width: 16,
|
|
2400
|
+
height: 16,
|
|
2401
|
+
borderRadius: 8,
|
|
2402
|
+
borderWidth: 1,
|
|
2403
|
+
borderColor: color,
|
|
2404
|
+
alignItems: "center",
|
|
2405
|
+
justifyContent: "center"
|
|
2406
|
+
},
|
|
2407
|
+
accessibilityRole: "image",
|
|
2408
|
+
accessibilityLabel: label,
|
|
2409
|
+
testID: "exercise-indicator",
|
|
2410
|
+
...props,
|
|
2411
|
+
children: /* @__PURE__ */ jsx(Icon, { size: 11, color })
|
|
2412
|
+
}
|
|
2413
|
+
);
|
|
2414
|
+
if (onPress) {
|
|
2415
|
+
return /* @__PURE__ */ jsx(reactNative.Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, children: chip });
|
|
2416
|
+
}
|
|
2417
|
+
return chip;
|
|
2006
2418
|
}
|
|
2007
2419
|
|
|
2008
|
-
// src/components/custom/Workout/
|
|
2009
|
-
function
|
|
2010
|
-
return
|
|
2420
|
+
// src/components/custom/Workout/ExerciseHeading.tsx
|
|
2421
|
+
function headingLabel(name, sets, reps, load, unit) {
|
|
2422
|
+
return `${name}, ${sets}\xD7${reps} @ ${roundWeight(load)} ${unit}`;
|
|
2423
|
+
}
|
|
2424
|
+
function ExerciseHeading({
|
|
2425
|
+
name,
|
|
2426
|
+
sets,
|
|
2427
|
+
reps,
|
|
2428
|
+
load,
|
|
2429
|
+
unit = "lbs",
|
|
2430
|
+
tempo,
|
|
2431
|
+
indicator,
|
|
2432
|
+
dimmed,
|
|
2433
|
+
onPress
|
|
2434
|
+
}) {
|
|
2435
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: { opacity: dimmed ? 0.55 : 1 }, testID: "exercise-heading", children: [
|
|
2436
|
+
/* @__PURE__ */ jsxs(
|
|
2437
|
+
reactNative.Pressable,
|
|
2438
|
+
{
|
|
2439
|
+
onPress,
|
|
2440
|
+
accessibilityRole: "button",
|
|
2441
|
+
accessibilityLabel: headingLabel(name, sets, reps, load, unit),
|
|
2442
|
+
className: "flex-row items-center",
|
|
2443
|
+
style: { gap: 8 },
|
|
2444
|
+
testID: "exercise-card-header",
|
|
2445
|
+
children: [
|
|
2446
|
+
/* @__PURE__ */ jsx(
|
|
2447
|
+
reactNative.Text,
|
|
2448
|
+
{
|
|
2449
|
+
className: "text-text-primary",
|
|
2450
|
+
style: { fontSize: 14, fontFamily: '"Space Grotesk", sans-serif', fontWeight: "700" },
|
|
2451
|
+
testID: "exercise-card-name",
|
|
2452
|
+
children: name
|
|
2453
|
+
}
|
|
2454
|
+
),
|
|
2455
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
|
|
2456
|
+
indicator && /* @__PURE__ */ jsx(ExerciseIndicator, { kind: indicator })
|
|
2457
|
+
]
|
|
2458
|
+
}
|
|
2459
|
+
),
|
|
2460
|
+
/* @__PURE__ */ jsxs(
|
|
2461
|
+
reactNative.View,
|
|
2462
|
+
{
|
|
2463
|
+
className: "flex-row items-center",
|
|
2464
|
+
style: { gap: 8, marginTop: 1 },
|
|
2465
|
+
testID: "exercise-card-summary",
|
|
2466
|
+
children: [
|
|
2467
|
+
/* @__PURE__ */ jsx(SetsRepsLoad, { sets, reps, load, unit }),
|
|
2468
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
|
|
2469
|
+
tempo && /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm", showLabel: false, showInfo: false })
|
|
2470
|
+
]
|
|
2471
|
+
}
|
|
2472
|
+
)
|
|
2473
|
+
] });
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
// src/components/custom/Workout/ExerciseCardHeading.tsx
|
|
2477
|
+
function ExerciseCardHeading({
|
|
2478
|
+
setStates,
|
|
2479
|
+
stripHeight = 8,
|
|
2480
|
+
dimmed,
|
|
2481
|
+
testID = "exercise-card",
|
|
2482
|
+
...heading
|
|
2483
|
+
}) {
|
|
2484
|
+
return /* @__PURE__ */ jsxs(
|
|
2485
|
+
reactNative.View,
|
|
2486
|
+
{
|
|
2487
|
+
style: { paddingVertical: 9, paddingHorizontal: 12, opacity: dimmed ? 0.55 : 1 },
|
|
2488
|
+
testID,
|
|
2489
|
+
children: [
|
|
2490
|
+
/* @__PURE__ */ jsx(ExerciseHeading, { ...heading }),
|
|
2491
|
+
setStates.length > 0 && /* @__PURE__ */ jsx(reactNative.View, { style: { marginTop: 7 }, testID: "exercise-card-strip", children: /* @__PURE__ */ jsx(SetStrip, { sets: setStates, height: stripHeight }) })
|
|
2492
|
+
]
|
|
2493
|
+
}
|
|
2494
|
+
);
|
|
2011
2495
|
}
|
|
2496
|
+
|
|
2497
|
+
// src/components/custom/Workout/ExerciseCard.tsx
|
|
2012
2498
|
function getSupersetBorderRadius(position) {
|
|
2013
2499
|
switch (position) {
|
|
2014
2500
|
case "first":
|
|
@@ -2051,7 +2537,7 @@ function formatSummary(summary) {
|
|
|
2051
2537
|
if (!summary) return "";
|
|
2052
2538
|
return `${summary.sets}\xD7${summary.reps} @ ${roundWeight(summary.weight)} ${summary.unit}`;
|
|
2053
2539
|
}
|
|
2054
|
-
function
|
|
2540
|
+
function formatAccessibilityLabel(name, summary, prescription) {
|
|
2055
2541
|
if (summary) return `${name}, ${formatSummary(summary)}`;
|
|
2056
2542
|
if (prescription) return `${name}, ${prescription}`;
|
|
2057
2543
|
return name;
|
|
@@ -2060,7 +2546,6 @@ function CollapsedCard({
|
|
|
2060
2546
|
name,
|
|
2061
2547
|
onToggle,
|
|
2062
2548
|
summary,
|
|
2063
|
-
e1rm,
|
|
2064
2549
|
isPR,
|
|
2065
2550
|
setVelocities,
|
|
2066
2551
|
velocityZones,
|
|
@@ -2079,7 +2564,7 @@ function CollapsedCard({
|
|
|
2079
2564
|
onPressIn: () => setPressed(true),
|
|
2080
2565
|
onPressOut: () => setPressed(false),
|
|
2081
2566
|
accessibilityRole: "button",
|
|
2082
|
-
accessibilityLabel:
|
|
2567
|
+
accessibilityLabel: formatAccessibilityLabel(name, summary, void 0),
|
|
2083
2568
|
testID: "exercise-card",
|
|
2084
2569
|
children: /* @__PURE__ */ jsxs(
|
|
2085
2570
|
reactNative.View,
|
|
@@ -2093,100 +2578,93 @@ function CollapsedCard({
|
|
|
2093
2578
|
...supersetMargin
|
|
2094
2579
|
},
|
|
2095
2580
|
children: [
|
|
2096
|
-
/* @__PURE__ */ jsxs(
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
{
|
|
2146
|
-
className: "flex-row",
|
|
2147
|
-
style: { marginTop: 6, gap: 4 },
|
|
2148
|
-
testID: "exercise-card-strips",
|
|
2149
|
-
children: [
|
|
2150
|
-
setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
|
|
2151
|
-
VelocityStrip,
|
|
2152
|
-
{
|
|
2153
|
-
velocities,
|
|
2154
|
-
zones: velocityZones,
|
|
2155
|
-
variant: "mini",
|
|
2156
|
-
testID: `exercise-card-velocity-strip-${i}`
|
|
2157
|
-
},
|
|
2158
|
-
i
|
|
2159
|
-
)),
|
|
2160
|
-
Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
|
|
2161
|
-
PlaceholderStrip,
|
|
2162
|
-
{
|
|
2163
|
-
testID: `exercise-card-placeholder-${i}`
|
|
2164
|
-
},
|
|
2165
|
-
`placeholder-${i}`
|
|
2166
|
-
))
|
|
2167
|
-
]
|
|
2168
|
-
}
|
|
2169
|
-
)
|
|
2581
|
+
/* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", testID: "exercise-card-header", children: [
|
|
2582
|
+
/* @__PURE__ */ jsx(
|
|
2583
|
+
reactNative.Text,
|
|
2584
|
+
{
|
|
2585
|
+
className: "text-text-primary",
|
|
2586
|
+
style: {
|
|
2587
|
+
fontSize: 14,
|
|
2588
|
+
fontFamily: '"Space Grotesk", sans-serif',
|
|
2589
|
+
fontWeight: "700"
|
|
2590
|
+
},
|
|
2591
|
+
testID: "exercise-card-name",
|
|
2592
|
+
children: name
|
|
2593
|
+
}
|
|
2594
|
+
),
|
|
2595
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
|
|
2596
|
+
summary && /* @__PURE__ */ jsx(
|
|
2597
|
+
reactNative.Text,
|
|
2598
|
+
{
|
|
2599
|
+
className: "text-text-secondary",
|
|
2600
|
+
style: {
|
|
2601
|
+
fontSize: 12,
|
|
2602
|
+
fontFamily: "Inter, sans-serif",
|
|
2603
|
+
marginRight: 8
|
|
2604
|
+
},
|
|
2605
|
+
testID: "exercise-card-summary",
|
|
2606
|
+
children: formatSummary(summary)
|
|
2607
|
+
}
|
|
2608
|
+
),
|
|
2609
|
+
isPR && /* @__PURE__ */ jsx(reactNative.View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
|
|
2610
|
+
] }),
|
|
2611
|
+
(completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row", style: { marginTop: 6, gap: 4 }, testID: "exercise-card-strips", children: [
|
|
2612
|
+
setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
|
|
2613
|
+
VelocityStrip,
|
|
2614
|
+
{
|
|
2615
|
+
velocities,
|
|
2616
|
+
zones: velocityZones,
|
|
2617
|
+
variant: "mini",
|
|
2618
|
+
testID: `exercise-card-velocity-strip-${i}`
|
|
2619
|
+
},
|
|
2620
|
+
i
|
|
2621
|
+
)),
|
|
2622
|
+
Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
|
|
2623
|
+
PlaceholderStrip,
|
|
2624
|
+
{
|
|
2625
|
+
testID: `exercise-card-placeholder-${i}`
|
|
2626
|
+
},
|
|
2627
|
+
`placeholder-${i}`
|
|
2628
|
+
))
|
|
2629
|
+
] })
|
|
2170
2630
|
]
|
|
2171
2631
|
}
|
|
2172
2632
|
)
|
|
2173
2633
|
}
|
|
2174
2634
|
);
|
|
2175
2635
|
}
|
|
2636
|
+
var DARK2 = getSemanticColors("dark");
|
|
2637
|
+
var BODY_DIVIDER = DARK2["border-subtle"];
|
|
2638
|
+
function deriveHeaderSetStates(sets) {
|
|
2639
|
+
return sets.map((set) => {
|
|
2640
|
+
switch (set.state) {
|
|
2641
|
+
case "live":
|
|
2642
|
+
return { status: "active", velocities: set.velocities, planned: set.target.reps };
|
|
2643
|
+
case "done":
|
|
2644
|
+
return { status: "done", velocities: set.velocities };
|
|
2645
|
+
case "todo":
|
|
2646
|
+
return { status: "todo", planned: set.planned ?? set.target.reps };
|
|
2647
|
+
}
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2176
2650
|
function ExpandedCard({
|
|
2177
2651
|
name,
|
|
2178
2652
|
onToggle,
|
|
2179
2653
|
summary,
|
|
2180
|
-
e1rm,
|
|
2181
2654
|
isPR,
|
|
2182
2655
|
sets,
|
|
2183
2656
|
tempo,
|
|
2657
|
+
indicator,
|
|
2658
|
+
setStates,
|
|
2659
|
+
stripHeight = 8,
|
|
2660
|
+
velocityZones,
|
|
2184
2661
|
supersetPosition
|
|
2185
2662
|
}) {
|
|
2186
2663
|
const borderRadius = getSupersetBorderRadius(supersetPosition);
|
|
2187
2664
|
const supersetMargin = getSupersetMargin(supersetPosition);
|
|
2188
2665
|
const unit = summary?.unit ?? sets?.[0]?.unit ?? "lbs";
|
|
2189
|
-
const
|
|
2666
|
+
const headerStates = setStates ?? (sets ? deriveHeaderSetStates(sets) : []);
|
|
2667
|
+
const headerIndicator = indicator ?? (isPR ? "pr" : void 0);
|
|
2190
2668
|
return /* @__PURE__ */ jsxs(
|
|
2191
2669
|
reactNative.View,
|
|
2192
2670
|
{
|
|
@@ -2199,114 +2677,32 @@ function ExpandedCard({
|
|
|
2199
2677
|
testID: "exercise-card",
|
|
2200
2678
|
children: [
|
|
2201
2679
|
/* @__PURE__ */ jsx(
|
|
2202
|
-
|
|
2680
|
+
ExerciseCardHeading,
|
|
2203
2681
|
{
|
|
2682
|
+
name,
|
|
2683
|
+
sets: summary?.sets ?? sets?.length ?? 0,
|
|
2684
|
+
reps: summary?.reps ?? 0,
|
|
2685
|
+
load: summary?.weight ?? 0,
|
|
2686
|
+
unit,
|
|
2687
|
+
tempo,
|
|
2688
|
+
indicator: headerIndicator,
|
|
2689
|
+
setStates: headerStates,
|
|
2690
|
+
stripHeight,
|
|
2204
2691
|
onPress: onToggle,
|
|
2205
|
-
|
|
2206
|
-
accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
|
|
2207
|
-
"aria-expanded": true,
|
|
2208
|
-
testID: "exercise-card-header",
|
|
2209
|
-
children: /* @__PURE__ */ jsxs(
|
|
2210
|
-
reactNative.View,
|
|
2211
|
-
{
|
|
2212
|
-
className: "flex-row items-center",
|
|
2213
|
-
style: {
|
|
2214
|
-
padding: 12,
|
|
2215
|
-
paddingHorizontal: 14,
|
|
2216
|
-
paddingBottom: 10,
|
|
2217
|
-
borderBottomWidth: 1,
|
|
2218
|
-
borderBottomColor: resolveColor("border-default")
|
|
2219
|
-
},
|
|
2220
|
-
children: [
|
|
2221
|
-
/* @__PURE__ */ jsx(
|
|
2222
|
-
reactNative.Text,
|
|
2223
|
-
{
|
|
2224
|
-
className: "text-text-primary",
|
|
2225
|
-
style: {
|
|
2226
|
-
fontSize: 14,
|
|
2227
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
2228
|
-
fontWeight: "700"
|
|
2229
|
-
},
|
|
2230
|
-
testID: "exercise-card-name",
|
|
2231
|
-
children: name
|
|
2232
|
-
}
|
|
2233
|
-
),
|
|
2234
|
-
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
|
|
2235
|
-
summary && /* @__PURE__ */ jsx(
|
|
2236
|
-
reactNative.Text,
|
|
2237
|
-
{
|
|
2238
|
-
className: "text-text-secondary",
|
|
2239
|
-
style: {
|
|
2240
|
-
fontSize: 12,
|
|
2241
|
-
fontFamily: "Inter, sans-serif",
|
|
2242
|
-
marginRight: 8
|
|
2243
|
-
},
|
|
2244
|
-
testID: "exercise-card-summary",
|
|
2245
|
-
children: formatSummary(summary)
|
|
2246
|
-
}
|
|
2247
|
-
),
|
|
2248
|
-
e1rm && /* @__PURE__ */ jsx(
|
|
2249
|
-
WeightBadge,
|
|
2250
|
-
{
|
|
2251
|
-
value: roundWeight(e1rm.value),
|
|
2252
|
-
unit: e1rm.unit,
|
|
2253
|
-
size: "sm",
|
|
2254
|
-
showIcon: false,
|
|
2255
|
-
testID: "exercise-card-e1rm"
|
|
2256
|
-
}
|
|
2257
|
-
),
|
|
2258
|
-
isPR && /* @__PURE__ */ jsx(reactNative.View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
|
|
2259
|
-
]
|
|
2260
|
-
}
|
|
2261
|
-
)
|
|
2692
|
+
testID: "exercise-card-heading"
|
|
2262
2693
|
}
|
|
2263
2694
|
),
|
|
2264
|
-
|
|
2265
|
-
/* @__PURE__ */ jsx(
|
|
2695
|
+
/* @__PURE__ */ jsxs(
|
|
2266
2696
|
reactNative.View,
|
|
2267
2697
|
{
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
testID: "exercise-card-column-headers",
|
|
2275
|
-
children: columnHeaders.map((header, i) => {
|
|
2276
|
-
const widths = [36, void 0, 44, 56, 36];
|
|
2277
|
-
const width = widths[i];
|
|
2278
|
-
const isFlex = width === void 0;
|
|
2279
|
-
return /* @__PURE__ */ jsx(
|
|
2280
|
-
reactNative.View,
|
|
2281
|
-
{
|
|
2282
|
-
style: {
|
|
2283
|
-
...isFlex ? {} : { width },
|
|
2284
|
-
alignItems: "center",
|
|
2285
|
-
justifyContent: "center",
|
|
2286
|
-
gap: 8
|
|
2287
|
-
},
|
|
2288
|
-
...isFlex ? { className: "flex-1" } : {},
|
|
2289
|
-
children: /* @__PURE__ */ jsx(
|
|
2290
|
-
reactNative.Text,
|
|
2291
|
-
{
|
|
2292
|
-
className: "text-text-tertiary",
|
|
2293
|
-
style: {
|
|
2294
|
-
fontSize: 10,
|
|
2295
|
-
fontWeight: "600",
|
|
2296
|
-
fontFamily: "Inter, sans-serif",
|
|
2297
|
-
textTransform: "uppercase",
|
|
2298
|
-
letterSpacing: 0.8
|
|
2299
|
-
},
|
|
2300
|
-
children: header
|
|
2301
|
-
}
|
|
2302
|
-
)
|
|
2303
|
-
},
|
|
2304
|
-
header
|
|
2305
|
-
);
|
|
2306
|
-
})
|
|
2698
|
+
style: { borderTopWidth: 1, borderTopColor: BODY_DIVIDER, paddingBottom: 6 },
|
|
2699
|
+
testID: "exercise-card-body",
|
|
2700
|
+
children: [
|
|
2701
|
+
/* @__PURE__ */ jsx(SetTableHeader, { unit, showPrevious: false, testID: "exercise-card-column-headers" }),
|
|
2702
|
+
sets && /* @__PURE__ */ jsx(reactNative.View, { testID: "exercise-card-sets", children: sets.map((set, i) => /* @__PURE__ */ jsx(SetRow, { ...set, velocityZones: set.velocityZones ?? velocityZones }, i)) })
|
|
2703
|
+
]
|
|
2307
2704
|
}
|
|
2308
|
-
)
|
|
2309
|
-
sets && /* @__PURE__ */ jsx(reactNative.View, { testID: "exercise-card-sets", children: sets.map((setProps, i) => /* @__PURE__ */ jsx(SetRow, { ...setProps }, i)) })
|
|
2705
|
+
)
|
|
2310
2706
|
]
|
|
2311
2707
|
}
|
|
2312
2708
|
);
|
|
@@ -2315,11 +2711,12 @@ function UpcomingCard({
|
|
|
2315
2711
|
name,
|
|
2316
2712
|
prescription,
|
|
2317
2713
|
previousBest,
|
|
2318
|
-
supersetPosition
|
|
2714
|
+
supersetPosition,
|
|
2715
|
+
onToggle
|
|
2319
2716
|
}) {
|
|
2320
2717
|
const borderRadius = getSupersetBorderRadius(supersetPosition);
|
|
2321
2718
|
const supersetMargin = getSupersetMargin(supersetPosition);
|
|
2322
|
-
return /* @__PURE__ */ jsx(
|
|
2719
|
+
return /* @__PURE__ */ jsx(reactNative.Pressable, { onPress: onToggle, accessibilityRole: "button", children: /* @__PURE__ */ jsx(
|
|
2323
2720
|
reactNative.View,
|
|
2324
2721
|
{
|
|
2325
2722
|
style: {
|
|
@@ -2329,7 +2726,7 @@ function UpcomingCard({
|
|
|
2329
2726
|
...borderRadius,
|
|
2330
2727
|
...supersetMargin
|
|
2331
2728
|
},
|
|
2332
|
-
accessibilityLabel:
|
|
2729
|
+
accessibilityLabel: formatAccessibilityLabel(name, void 0, prescription),
|
|
2333
2730
|
testID: "exercise-card",
|
|
2334
2731
|
children: /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", children: [
|
|
2335
2732
|
/* @__PURE__ */ jsx(
|
|
@@ -2337,6 +2734,7 @@ function UpcomingCard({
|
|
|
2337
2734
|
{
|
|
2338
2735
|
className: "text-text-primary",
|
|
2339
2736
|
style: {
|
|
2737
|
+
flexShrink: 0,
|
|
2340
2738
|
fontSize: 14,
|
|
2341
2739
|
fontFamily: '"Space Grotesk", sans-serif',
|
|
2342
2740
|
fontWeight: "700"
|
|
@@ -2349,7 +2747,9 @@ function UpcomingCard({
|
|
|
2349
2747
|
reactNative.Text,
|
|
2350
2748
|
{
|
|
2351
2749
|
className: "text-text-secondary",
|
|
2750
|
+
numberOfLines: 1,
|
|
2352
2751
|
style: {
|
|
2752
|
+
flexShrink: 1,
|
|
2353
2753
|
fontSize: 12,
|
|
2354
2754
|
fontFamily: "Inter, sans-serif",
|
|
2355
2755
|
marginLeft: 8
|
|
@@ -2358,12 +2758,14 @@ function UpcomingCard({
|
|
|
2358
2758
|
children: prescription
|
|
2359
2759
|
}
|
|
2360
2760
|
),
|
|
2361
|
-
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
|
|
2761
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", style: { minWidth: 8 } }),
|
|
2362
2762
|
previousBest && /* @__PURE__ */ jsx(
|
|
2363
2763
|
reactNative.Text,
|
|
2364
2764
|
{
|
|
2365
2765
|
className: "text-text-tertiary",
|
|
2766
|
+
numberOfLines: 1,
|
|
2366
2767
|
style: {
|
|
2768
|
+
flexShrink: 1,
|
|
2367
2769
|
fontSize: 11,
|
|
2368
2770
|
fontFamily: "Inter, sans-serif"
|
|
2369
2771
|
},
|
|
@@ -2373,18 +2775,31 @@ function UpcomingCard({
|
|
|
2373
2775
|
)
|
|
2374
2776
|
] })
|
|
2375
2777
|
}
|
|
2376
|
-
);
|
|
2778
|
+
) });
|
|
2377
2779
|
}
|
|
2378
|
-
function ExerciseCard(
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2780
|
+
function ExerciseCard({
|
|
2781
|
+
upcoming,
|
|
2782
|
+
expanded,
|
|
2783
|
+
defaultExpanded,
|
|
2784
|
+
onExpandedChange,
|
|
2785
|
+
...rest
|
|
2786
|
+
}) {
|
|
2787
|
+
const [internalExpanded, setInternalExpanded] = react.useState(defaultExpanded ?? false);
|
|
2788
|
+
const isControlled = expanded !== void 0;
|
|
2789
|
+
const isExpanded = isControlled ? expanded : internalExpanded;
|
|
2790
|
+
const onToggle = () => {
|
|
2791
|
+
const next = !isExpanded;
|
|
2792
|
+
onExpandedChange?.(next);
|
|
2793
|
+
if (!isControlled) setInternalExpanded(next);
|
|
2794
|
+
};
|
|
2795
|
+
if (upcoming) return /* @__PURE__ */ jsx(UpcomingCard, { ...rest, onToggle });
|
|
2796
|
+
return isExpanded ? /* @__PURE__ */ jsx(ExpandedCard, { ...rest, onToggle }) : /* @__PURE__ */ jsx(CollapsedCard, { ...rest, onToggle });
|
|
2387
2797
|
}
|
|
2798
|
+
function resolveColor(token, mode = "dark") {
|
|
2799
|
+
return reactNative.Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
// src/components/custom/Workout/InputBar.tsx
|
|
2388
2803
|
var BRAND_PRIMARY2 = getSemanticColors("dark")["brand-primary"];
|
|
2389
2804
|
var INPUT_CLASSNAME = "bg-surface-raised border-border-strong text-text-primary";
|
|
2390
2805
|
var inputStyle = {
|
|
@@ -2541,6 +2956,45 @@ function InputBar({
|
|
|
2541
2956
|
}
|
|
2542
2957
|
);
|
|
2543
2958
|
}
|
|
2959
|
+
function formatDuration(ms) {
|
|
2960
|
+
const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
|
|
2961
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
2962
|
+
const seconds = totalSeconds % 60;
|
|
2963
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
2964
|
+
}
|
|
2965
|
+
function useTimer(options = {}) {
|
|
2966
|
+
const {
|
|
2967
|
+
mode = "up",
|
|
2968
|
+
durationMs,
|
|
2969
|
+
elapsedMs: controlledElapsedMs,
|
|
2970
|
+
running = false,
|
|
2971
|
+
autoTick = false,
|
|
2972
|
+
tickMs = 1e3
|
|
2973
|
+
} = options;
|
|
2974
|
+
const isControlled = controlledElapsedMs != null;
|
|
2975
|
+
const [internalElapsedMs, setInternalElapsedMs] = react.useState(0);
|
|
2976
|
+
react.useEffect(() => {
|
|
2977
|
+
if (isControlled || !autoTick || !running) return;
|
|
2978
|
+
const id = setInterval(() => {
|
|
2979
|
+
setInternalElapsedMs((prev) => prev + tickMs);
|
|
2980
|
+
}, tickMs);
|
|
2981
|
+
return () => clearInterval(id);
|
|
2982
|
+
}, [isControlled, autoTick, running, tickMs]);
|
|
2983
|
+
const elapsedMs = isControlled ? controlledElapsedMs : internalElapsedMs;
|
|
2984
|
+
const hasDuration = durationMs != null && durationMs > 0;
|
|
2985
|
+
const remainingMs = durationMs != null ? Math.max(0, durationMs - elapsedMs) : 0;
|
|
2986
|
+
let progress;
|
|
2987
|
+
if (hasDuration) {
|
|
2988
|
+
progress = Math.min(1, Math.max(0, elapsedMs / durationMs));
|
|
2989
|
+
} else {
|
|
2990
|
+
progress = mode === "down" ? 1 : 0;
|
|
2991
|
+
}
|
|
2992
|
+
const label = formatDuration(mode === "down" ? remainingMs : elapsedMs);
|
|
2993
|
+
const done = durationMs != null && elapsedMs >= durationMs;
|
|
2994
|
+
return { elapsedMs, remainingMs, progress, label, done };
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
// src/components/custom/Workout/RestTimer.tsx
|
|
2544
2998
|
var BRAND_PRIMARY3 = getSemanticColors("dark")["brand-primary"];
|
|
2545
2999
|
function RestTimer({
|
|
2546
3000
|
totalSeconds,
|
|
@@ -2551,14 +3005,18 @@ function RestTimer({
|
|
|
2551
3005
|
visible,
|
|
2552
3006
|
displayOnly = false
|
|
2553
3007
|
}) {
|
|
2554
|
-
|
|
2555
|
-
|
|
3008
|
+
const {
|
|
3009
|
+
remainingMs,
|
|
3010
|
+
progress,
|
|
3011
|
+
label: timeDisplay
|
|
3012
|
+
} = useTimer({
|
|
3013
|
+
mode: "down",
|
|
3014
|
+
durationMs: totalSeconds * 1e3,
|
|
3015
|
+
elapsedMs
|
|
3016
|
+
});
|
|
2556
3017
|
const remainingSec = Math.ceil(remainingMs / 1e3);
|
|
2557
|
-
const
|
|
2558
|
-
|
|
2559
|
-
const timeDisplay = `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
2560
|
-
const totalMs = totalSeconds * 1e3;
|
|
2561
|
-
const progressPct = totalMs > 0 ? Math.min(100, elapsedMs / totalMs * 100) : 100;
|
|
3018
|
+
const progressPct = progress * 100;
|
|
3019
|
+
if (!visible) return null;
|
|
2562
3020
|
return /* @__PURE__ */ jsxs(
|
|
2563
3021
|
reactNative.View,
|
|
2564
3022
|
{
|
|
@@ -2797,23 +3255,22 @@ function deriveWorkoutProgress(exercises) {
|
|
|
2797
3255
|
function findActiveExercise(exercises) {
|
|
2798
3256
|
return exercises.find((exercise) => exercise.status === "active") ?? null;
|
|
2799
3257
|
}
|
|
2800
|
-
function
|
|
2801
|
-
|
|
2802
|
-
return focused ? "expanded" : "collapsed";
|
|
3258
|
+
function isUpcomingExercise(status) {
|
|
3259
|
+
return status === "upcoming";
|
|
2803
3260
|
}
|
|
2804
3261
|
function toActiveCardProps(exercise, focused, onToggle, supersetPosition = null, supersetColor) {
|
|
2805
|
-
const
|
|
3262
|
+
const upcoming = isUpcomingExercise(exercise.status);
|
|
2806
3263
|
return {
|
|
2807
3264
|
name: exercise.name,
|
|
2808
|
-
|
|
2809
|
-
|
|
3265
|
+
upcoming,
|
|
3266
|
+
expanded: focused,
|
|
3267
|
+
onExpandedChange: onToggle,
|
|
2810
3268
|
summary: exercise.summary,
|
|
2811
|
-
e1rm: exercise.e1rm,
|
|
2812
3269
|
isPR: exercise.isPR,
|
|
2813
3270
|
tempo: exercise.tempo,
|
|
2814
3271
|
setVelocities: exercise.setVelocities,
|
|
2815
3272
|
totalPlannedSets: exercise.totalPlannedSets,
|
|
2816
|
-
sets:
|
|
3273
|
+
sets: focused ? exercise.sets : void 0,
|
|
2817
3274
|
prescription: exercise.prescription,
|
|
2818
3275
|
previousBest: exercise.previousBest,
|
|
2819
3276
|
supersetPosition,
|
|
@@ -3502,11 +3959,11 @@ function CardContent({ children, className }) {
|
|
|
3502
3959
|
function CardFooter({ children, className }) {
|
|
3503
3960
|
return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
|
|
3504
3961
|
}
|
|
3505
|
-
var
|
|
3962
|
+
var t5 = getSemanticColors("dark");
|
|
3506
3963
|
var solidVariantColors = {
|
|
3507
|
-
success:
|
|
3508
|
-
warning:
|
|
3509
|
-
error:
|
|
3964
|
+
success: t5["status-success"],
|
|
3965
|
+
warning: t5["status-warning"],
|
|
3966
|
+
error: t5["status-error"],
|
|
3510
3967
|
neutral: "#6B7280"
|
|
3511
3968
|
};
|
|
3512
3969
|
var ringVariantStyles = {
|
|
@@ -3546,8 +4003,8 @@ var iconColors = {
|
|
|
3546
4003
|
warning: "#6B4000",
|
|
3547
4004
|
error: "#5C1A1A",
|
|
3548
4005
|
neutral: "#D1D5DB",
|
|
3549
|
-
"on-track":
|
|
3550
|
-
deviation:
|
|
4006
|
+
"on-track": t5["status-success"],
|
|
4007
|
+
deviation: t5["status-warning"],
|
|
3551
4008
|
future: "#6B7280"
|
|
3552
4009
|
};
|
|
3553
4010
|
var sizeDimensions = {
|
|
@@ -3633,13 +4090,13 @@ var MESO_ACCENT_GRADIENT_LIGHT = primitiveRamps.orange[300];
|
|
|
3633
4090
|
var WORKOUT_PILL_DELOAD = primitiveRamps.magenta[600];
|
|
3634
4091
|
|
|
3635
4092
|
// src/components/custom/Workout/MesoStatusCard.tsx
|
|
3636
|
-
var
|
|
3637
|
-
var BRAND_PRIMARY5 =
|
|
4093
|
+
var t6 = getSemanticColors("dark");
|
|
4094
|
+
var BRAND_PRIMARY5 = t6["brand-primary"];
|
|
3638
4095
|
var BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK;
|
|
3639
4096
|
var BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT;
|
|
3640
|
-
var SUCCESS =
|
|
3641
|
-
var WARNING =
|
|
3642
|
-
var ERROR =
|
|
4097
|
+
var SUCCESS = t6["status-success"];
|
|
4098
|
+
var WARNING = t6["status-warning"];
|
|
4099
|
+
var ERROR = t6["status-error"];
|
|
3643
4100
|
var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY5, BRAND_PRIMARY_LIGHT];
|
|
3644
4101
|
var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
|
|
3645
4102
|
var GAUGE_GRADIENT = "linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(249,180,21,0.25) 50%, rgba(209,67,67,0.25) 100%)";
|
|
@@ -3711,7 +4168,7 @@ function StatusPill({ badge }) {
|
|
|
3711
4168
|
}
|
|
3712
4169
|
);
|
|
3713
4170
|
}
|
|
3714
|
-
function
|
|
4171
|
+
function MetricCell2({ metric }) {
|
|
3715
4172
|
return /* @__PURE__ */ jsxs(reactNative.View, { style: { width: "48%" }, testID: "meso-status-card-metric", children: [
|
|
3716
4173
|
/* @__PURE__ */ jsx(
|
|
3717
4174
|
reactNative.Text,
|
|
@@ -3929,7 +4386,7 @@ function MesoStatusCard({
|
|
|
3929
4386
|
className: "flex-row flex-wrap",
|
|
3930
4387
|
style: { gap: 8 },
|
|
3931
4388
|
testID: "meso-status-card-metrics",
|
|
3932
|
-
children: metrics.map((metric, index) => /* @__PURE__ */ jsx(
|
|
4389
|
+
children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell2, { metric }, `${metric.label}-${index}`))
|
|
3933
4390
|
}
|
|
3934
4391
|
),
|
|
3935
4392
|
gauges.length > 0 && /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 12 }, testID: "meso-status-card-gauges", children: gauges.map((gauge, index) => /* @__PURE__ */ jsx(Gauge, { gauge }, `${gauge.label}-${index}`)) }),
|
|
@@ -4010,8 +4467,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
|
|
|
4010
4467
|
var PLOT_LEFT = 26;
|
|
4011
4468
|
var TOOLTIP_WIDTH = 124;
|
|
4012
4469
|
function timeOf(dateStr) {
|
|
4013
|
-
const
|
|
4014
|
-
return Number.isNaN(
|
|
4470
|
+
const t12 = Date.parse(dateStr);
|
|
4471
|
+
return Number.isNaN(t12) ? 0 : t12;
|
|
4015
4472
|
}
|
|
4016
4473
|
function formatValue(value) {
|
|
4017
4474
|
return `${Math.round(value)}`;
|
|
@@ -4500,10 +4957,10 @@ function StrengthTrendChart({
|
|
|
4500
4957
|
}
|
|
4501
4958
|
);
|
|
4502
4959
|
}
|
|
4503
|
-
var
|
|
4504
|
-
var STATUS_SUCCESS2 =
|
|
4505
|
-
var STATUS_WARNING2 =
|
|
4506
|
-
var STATUS_INFO =
|
|
4960
|
+
var t7 = getSemanticColors("dark");
|
|
4961
|
+
var STATUS_SUCCESS2 = t7["status-success"];
|
|
4962
|
+
var STATUS_WARNING2 = t7["status-warning"];
|
|
4963
|
+
var STATUS_INFO = t7["status-info"];
|
|
4507
4964
|
var DOT_BORDER = "#F3F4F6";
|
|
4508
4965
|
var BAND_FILL = "rgba(46,213,115,0.1)";
|
|
4509
4966
|
var BAND_EDGE = "rgba(46,213,115,0.45)";
|
|
@@ -4546,13 +5003,13 @@ function interpEdge(pixels, x, key) {
|
|
|
4546
5003
|
const a = pixels[i];
|
|
4547
5004
|
const b = pixels[i + 1];
|
|
4548
5005
|
if (x >= a.x && x <= b.x) {
|
|
4549
|
-
const
|
|
4550
|
-
return a[key] +
|
|
5006
|
+
const t12 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
|
|
5007
|
+
return a[key] + t12 * (b[key] - a[key]);
|
|
4551
5008
|
}
|
|
4552
5009
|
}
|
|
4553
5010
|
return pixels[pixels.length - 1][key];
|
|
4554
5011
|
}
|
|
4555
|
-
function
|
|
5012
|
+
function buildColumns2(pixels, step) {
|
|
4556
5013
|
if (pixels.length < 2) return [];
|
|
4557
5014
|
const start = pixels[0].x;
|
|
4558
5015
|
const end = pixels[pixels.length - 1].x;
|
|
@@ -4664,7 +5121,7 @@ function CapacityBandChart({
|
|
|
4664
5121
|
}
|
|
4665
5122
|
const { toX, toY } = scale;
|
|
4666
5123
|
const bandPixels = toPixels(band, toX, toY);
|
|
4667
|
-
const columns =
|
|
5124
|
+
const columns = buildColumns2(bandPixels, COLUMN_STEP);
|
|
4668
5125
|
const topEdge = buildEdges(bandPixels, "yHigh");
|
|
4669
5126
|
const bottomEdge = buildEdges(bandPixels, "yLow");
|
|
4670
5127
|
const lastBandPoint = band[band.length - 1];
|
|
@@ -4739,8 +5196,8 @@ function CapacityBandChart({
|
|
|
4739
5196
|
testID: "capacity-band-chart-reveal",
|
|
4740
5197
|
children: /* @__PURE__ */ jsxs(reactNative.View, { style: { width, height, position: "relative" }, children: [
|
|
4741
5198
|
projection && /* @__PURE__ */ jsxs(reactNative.View, { testID: "capacity-band-chart-projection", children: [
|
|
4742
|
-
renderColumns(
|
|
4743
|
-
renderColumns(
|
|
5199
|
+
renderColumns(buildColumns2(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
|
|
5200
|
+
renderColumns(buildColumns2(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
|
|
4744
5201
|
renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
|
|
4745
5202
|
renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
|
|
4746
5203
|
renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
|
|
@@ -4901,10 +5358,9 @@ function deriveExerciseStats(entries) {
|
|
|
4901
5358
|
function toExerciseCardProps(entry, expanded, onToggle) {
|
|
4902
5359
|
return {
|
|
4903
5360
|
name: entry.title,
|
|
4904
|
-
|
|
4905
|
-
onToggle,
|
|
5361
|
+
expanded,
|
|
5362
|
+
onExpandedChange: onToggle,
|
|
4906
5363
|
summary: entry.summary,
|
|
4907
|
-
e1rm: entry.e1rm,
|
|
4908
5364
|
isPR: entry.isPR,
|
|
4909
5365
|
tempo: entry.tempo,
|
|
4910
5366
|
sets: expanded ? entry.sets : void 0
|
|
@@ -5448,6 +5904,7 @@ var colorStyles = {
|
|
|
5448
5904
|
default: "bg-text-tertiary",
|
|
5449
5905
|
primary: "bg-brand-primary",
|
|
5450
5906
|
success: "bg-status-success",
|
|
5907
|
+
live: "bg-status-live",
|
|
5451
5908
|
error: "bg-status-error",
|
|
5452
5909
|
warning: "bg-status-warning",
|
|
5453
5910
|
info: "bg-status-info",
|
|
@@ -5504,7 +5961,7 @@ function Indicator({
|
|
|
5504
5961
|
colorClass,
|
|
5505
5962
|
pulseMode === "opacity" && "animate-pulse",
|
|
5506
5963
|
ring && "border-2 border-background-base",
|
|
5507
|
-
glow && !customColor && color === "success" && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
|
|
5964
|
+
glow && !customColor && (color === "success" || color === "live") && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
|
|
5508
5965
|
glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
|
|
5509
5966
|
glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
|
|
5510
5967
|
glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
|
|
@@ -5586,7 +6043,7 @@ function Badge({
|
|
|
5586
6043
|
}
|
|
5587
6044
|
);
|
|
5588
6045
|
}
|
|
5589
|
-
var
|
|
6046
|
+
var t8 = getSemanticColors("dark");
|
|
5590
6047
|
var statusBgColors = {
|
|
5591
6048
|
completed: "rgba(46,213,115,0.15)",
|
|
5592
6049
|
current: "rgba(255,121,0,0.15)",
|
|
@@ -5604,14 +6061,14 @@ var statusBorderStyles = {
|
|
|
5604
6061
|
deload: { borderWidth: 1, borderColor: "rgba(186,41,150,0.3)" }
|
|
5605
6062
|
};
|
|
5606
6063
|
var textColors = {
|
|
5607
|
-
completed:
|
|
5608
|
-
current:
|
|
5609
|
-
next:
|
|
6064
|
+
completed: t8["status-success"],
|
|
6065
|
+
current: t8["brand-primary"],
|
|
6066
|
+
next: t8["brand-primary"],
|
|
5610
6067
|
upcoming: "#6B7280",
|
|
5611
6068
|
missed: "rgba(209,67,67,0.7)",
|
|
5612
6069
|
deload: WORKOUT_PILL_DELOAD
|
|
5613
6070
|
};
|
|
5614
|
-
function
|
|
6071
|
+
function usePulse2(enabled) {
|
|
5615
6072
|
const [opacity] = react.useState(() => new reactNative.Animated.Value(1));
|
|
5616
6073
|
react.useEffect(() => {
|
|
5617
6074
|
if (!enabled) {
|
|
@@ -5649,7 +6106,7 @@ function WorkoutPill({
|
|
|
5649
6106
|
...props
|
|
5650
6107
|
}) {
|
|
5651
6108
|
const shouldPulse = pulse ?? status === "current";
|
|
5652
|
-
const pulseOpacity =
|
|
6109
|
+
const pulseOpacity = usePulse2(shouldPulse);
|
|
5653
6110
|
const isCompleted = status === "completed";
|
|
5654
6111
|
const isMissed = status === "missed";
|
|
5655
6112
|
const pill = /* @__PURE__ */ jsx(
|
|
@@ -5739,19 +6196,19 @@ function WorkoutPill({
|
|
|
5739
6196
|
}
|
|
5740
6197
|
return pill;
|
|
5741
6198
|
}
|
|
5742
|
-
var
|
|
6199
|
+
var t9 = getSemanticColors("dark");
|
|
5743
6200
|
var TRACK_BG = "#333333";
|
|
5744
6201
|
var LABEL_COLOR = "#6B7280";
|
|
5745
6202
|
var TARGET_LINE_COLOR = "rgba(33, 150, 243, 0.5)";
|
|
5746
6203
|
var AT_TARGET_GLOW = "0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)";
|
|
5747
6204
|
var ZONE = {
|
|
5748
|
-
building:
|
|
6205
|
+
building: t9["status-success"],
|
|
5749
6206
|
// below target ramp-up
|
|
5750
|
-
approaching:
|
|
6207
|
+
approaching: t9["status-warning"],
|
|
5751
6208
|
// nearing target
|
|
5752
|
-
target:
|
|
6209
|
+
target: t9["brand-primary"],
|
|
5753
6210
|
// exactly at target
|
|
5754
|
-
over1:
|
|
6211
|
+
over1: t9["status-error"],
|
|
5755
6212
|
// mild over-target
|
|
5756
6213
|
over2: WORKOUT_TOKENS.intensity.over2,
|
|
5757
6214
|
// moderate over-target
|
|
@@ -6174,13 +6631,13 @@ function MesoCard({
|
|
|
6174
6631
|
}
|
|
6175
6632
|
) });
|
|
6176
6633
|
}
|
|
6177
|
-
var
|
|
6634
|
+
var t10 = getSemanticColors("dark");
|
|
6178
6635
|
var dotColorMap = {
|
|
6179
6636
|
untrained: "#2C2C2C",
|
|
6180
|
-
behind:
|
|
6181
|
-
ontrack:
|
|
6182
|
-
target:
|
|
6183
|
-
over:
|
|
6637
|
+
behind: t10["brand-secondary"],
|
|
6638
|
+
ontrack: t10["status-success"],
|
|
6639
|
+
target: t10["brand-primary"],
|
|
6640
|
+
over: t10["status-error"]
|
|
6184
6641
|
};
|
|
6185
6642
|
function MuscleGroupChip({
|
|
6186
6643
|
name,
|
|
@@ -6254,10 +6711,10 @@ function MuscleGroupChip({
|
|
|
6254
6711
|
}
|
|
6255
6712
|
|
|
6256
6713
|
// src/components/custom/Workout/WorkoutCard.tsx
|
|
6257
|
-
var
|
|
6714
|
+
var t11 = getSemanticColors("dark");
|
|
6258
6715
|
var COLORS = {
|
|
6259
|
-
statusSuccess:
|
|
6260
|
-
brandPrimary:
|
|
6716
|
+
statusSuccess: t11["status-success"],
|
|
6717
|
+
brandPrimary: t11["brand-primary"],
|
|
6261
6718
|
borderDefault: "#1F1F1F",
|
|
6262
6719
|
brandPrimarySubtle: "rgba(255,121,0,0.06)"
|
|
6263
6720
|
};
|
|
@@ -6541,8 +6998,8 @@ function WeekLevel({ week, onSelectWorkout }) {
|
|
|
6541
6998
|
function WorkoutLevel({ workout, expandedExercise, onToggleExercise }) {
|
|
6542
6999
|
const exercises = workout.exercises.map((exercise, index) => ({
|
|
6543
7000
|
...exercise,
|
|
6544
|
-
|
|
6545
|
-
|
|
7001
|
+
expanded: index === expandedExercise,
|
|
7002
|
+
onExpandedChange: () => onToggleExercise(index)
|
|
6546
7003
|
}));
|
|
6547
7004
|
return /* @__PURE__ */ jsx(reactNative.View, { testID: "program-planning-page-workout-level", children: /* @__PURE__ */ jsx(
|
|
6548
7005
|
WorkoutCard,
|
|
@@ -6685,7 +7142,7 @@ exports.findMeso = findMeso;
|
|
|
6685
7142
|
exports.findWeek = findWeek;
|
|
6686
7143
|
exports.findWorkout = findWorkout;
|
|
6687
7144
|
exports.groupExercises = groupExercises;
|
|
6688
|
-
exports.
|
|
7145
|
+
exports.isUpcomingExercise = isUpcomingExercise;
|
|
6689
7146
|
exports.summarizeVbt = summarizeVbt;
|
|
6690
7147
|
exports.toActiveCardProps = toActiveCardProps;
|
|
6691
7148
|
exports.toExerciseCardProps = toExerciseCardProps;
|