@titan-design/react-ui 0.6.0 → 0.8.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 +956 -128
- package/dist/index.d.ts +956 -128
- package/dist/index.js +2873 -981
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2826 -982
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-DCFBqp79.d.mts} +289 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-_fI3-x7Z.d.ts} +289 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1659 -799
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1660 -800
- 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/CircularTimer/CircularTimer.stories.tsx +90 -0
- package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
- package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
- package/src/components/custom/CircularTimer/index.ts +1 -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 +85 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +86 -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 +193 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.stories.tsx +133 -3
- package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
- package/src/components/custom/Workout/RestTimer.tsx +155 -59
- 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 +70 -1
- package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
- package/src/components/custom/Workout/TempoBar.tsx +107 -22
- 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 +262 -56
- package/src/components/custom/Workout/VelocityStrip.test.tsx +440 -141
- package/src/components/custom/Workout/VelocityStrip.tsx +776 -141
- 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 +139 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +234 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +373 -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 +2 -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 +86 -3
- package/src/components/ui/alert/Alert.test.tsx +52 -0
- package/src/components/ui/alert/Alert.tsx +54 -20
- 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/progress/Progress.tsx +47 -22
- 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
|
|
@@ -38,6 +35,8 @@ var primitiveColors = {
|
|
|
38
35
|
900: "#111827"},
|
|
39
36
|
// Charcoal scale (dark backgrounds)
|
|
40
37
|
charcoal: {
|
|
38
|
+
0: "#6E6E6E",
|
|
39
|
+
100: "#4C4C4C",
|
|
41
40
|
200: "#3C3C3C",
|
|
42
41
|
300: "#2C2C2C",
|
|
43
42
|
400: "#1F1F1F",
|
|
@@ -67,11 +66,16 @@ var primitiveRamps = {
|
|
|
67
66
|
50: "#FFF7DD",
|
|
68
67
|
200: "#FFD352",
|
|
69
68
|
300: "#F9B415",
|
|
69
|
+
// pin
|
|
70
|
+
400: "#E08C00",
|
|
70
71
|
500: "#C27400"},
|
|
71
72
|
green: {
|
|
72
73
|
50: "#E3FFEE",
|
|
73
74
|
200: "#58F69E",
|
|
74
75
|
300: "#2ED573",
|
|
76
|
+
// pin
|
|
77
|
+
400: "#21C05D",
|
|
78
|
+
500: "#22A444",
|
|
75
79
|
600: "#298732"},
|
|
76
80
|
cyan: {
|
|
77
81
|
// pin
|
|
@@ -79,7 +83,8 @@ var primitiveRamps = {
|
|
|
79
83
|
500: "#2697B7",
|
|
80
84
|
600: "#307B9B",
|
|
81
85
|
700: "#2A617F",
|
|
82
|
-
800: "#22465F"
|
|
86
|
+
800: "#22465F",
|
|
87
|
+
900: "#0B3149"},
|
|
83
88
|
blue: {
|
|
84
89
|
50: "#EFF8FF",
|
|
85
90
|
300: "#78C2FF",
|
|
@@ -134,15 +139,22 @@ var WORKOUT_TOKENS = {
|
|
|
134
139
|
// Canonical 4-band performance scale — the single source for BOTH the
|
|
135
140
|
// VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
|
|
136
141
|
// 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
|
-
//
|
|
142
|
+
// fastest/best; for RPE, green = easiest. This is the [0,2,3,4] subsample of
|
|
143
|
+
// the canonical `sequentialEffort` primitive: an EVEN hue walk across the ramp
|
|
144
|
+
// (green → gold → orange → red). The earlier [0,1,2,4] spent two of its four
|
|
145
|
+
// bands on adjacent yellows (amber-200 + amber-300) and skipped orange, reading
|
|
146
|
+
// yellow-heavy with an abrupt jump to red; sampling amber-300 + orange-400
|
|
147
|
+
// instead gives four cleanly separable bands that hold at 3px strip scale.
|
|
148
|
+
// Consumed inline (RN).
|
|
140
149
|
scale: {
|
|
141
150
|
green: sequentialEffort[0],
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// gold
|
|
151
|
+
// green-300
|
|
152
|
+
yellow: sequentialEffort[2],
|
|
153
|
+
// amber-300 (gold)
|
|
154
|
+
orange: sequentialEffort[3],
|
|
155
|
+
// orange-400 (true orange)
|
|
145
156
|
red: sequentialEffort[4]
|
|
157
|
+
// red-600
|
|
146
158
|
},
|
|
147
159
|
// Surface and border colors were removed here: those flip with the theme, so
|
|
148
160
|
// components use the CSS custom properties (var(--color-surface-*/border-*))
|
|
@@ -161,12 +173,6 @@ var WORKOUT_TOKENS = {
|
|
|
161
173
|
function roundRpe(rpe) {
|
|
162
174
|
return Math.round(rpe * 2) / 2;
|
|
163
175
|
}
|
|
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
176
|
function roundWeight(weight) {
|
|
171
177
|
return Math.round(weight);
|
|
172
178
|
}
|
|
@@ -194,14 +200,258 @@ function wrapJsx(fn) {
|
|
|
194
200
|
var jsx = wrapJsx(jsxRuntime.jsx);
|
|
195
201
|
var jsxs = wrapJsx(jsxRuntime.jsxs);
|
|
196
202
|
|
|
203
|
+
// src/components/custom/Workout/SegmentedBar.tsx
|
|
204
|
+
var PULSE_HALF_MS = 950;
|
|
205
|
+
var PULSE_MIN_OPACITY = 0.45;
|
|
206
|
+
var SEGMENTED_BAR_GAP = 5;
|
|
207
|
+
function usePulse(active) {
|
|
208
|
+
const [value] = react.useState(() => new reactNative.Animated.Value(0));
|
|
209
|
+
react.useEffect(() => {
|
|
210
|
+
if (!active) return;
|
|
211
|
+
const loop = reactNative.Animated.loop(
|
|
212
|
+
reactNative.Animated.sequence([
|
|
213
|
+
reactNative.Animated.timing(value, {
|
|
214
|
+
toValue: 1,
|
|
215
|
+
duration: PULSE_HALF_MS,
|
|
216
|
+
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
217
|
+
useNativeDriver: false
|
|
218
|
+
}),
|
|
219
|
+
reactNative.Animated.timing(value, {
|
|
220
|
+
toValue: 0,
|
|
221
|
+
duration: PULSE_HALF_MS,
|
|
222
|
+
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
223
|
+
useNativeDriver: false
|
|
224
|
+
})
|
|
225
|
+
])
|
|
226
|
+
);
|
|
227
|
+
loop.start();
|
|
228
|
+
return () => loop.stop();
|
|
229
|
+
}, [active, value]);
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
function SegmentedBar({
|
|
233
|
+
segments,
|
|
234
|
+
height = 8,
|
|
235
|
+
gap = SEGMENTED_BAR_GAP,
|
|
236
|
+
radius = 2,
|
|
237
|
+
marker = null,
|
|
238
|
+
segmentTestID,
|
|
239
|
+
style,
|
|
240
|
+
...props
|
|
241
|
+
}) {
|
|
242
|
+
const pulse = usePulse(segments.some((s) => s.pulse));
|
|
243
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
|
|
244
|
+
segments.map((seg, i) => {
|
|
245
|
+
const fillStyle = {
|
|
246
|
+
width: `${(seg.fill ?? 1) * 100}%`,
|
|
247
|
+
height: "100%",
|
|
248
|
+
backgroundColor: seg.color,
|
|
249
|
+
opacity: seg.opacity
|
|
250
|
+
};
|
|
251
|
+
const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
|
|
252
|
+
return /* @__PURE__ */ jsx(
|
|
253
|
+
reactNative.View,
|
|
254
|
+
{
|
|
255
|
+
style: {
|
|
256
|
+
flex: seg.weight ?? 1,
|
|
257
|
+
minWidth: 0,
|
|
258
|
+
height: "100%",
|
|
259
|
+
marginLeft: seg.leadingGap,
|
|
260
|
+
borderRadius: radius,
|
|
261
|
+
overflow: "hidden"
|
|
262
|
+
},
|
|
263
|
+
children: seg.pulse ? /* @__PURE__ */ jsx(
|
|
264
|
+
reactNative.Animated.View,
|
|
265
|
+
{
|
|
266
|
+
style: {
|
|
267
|
+
...fillStyle,
|
|
268
|
+
...seg.pulseColor ? {
|
|
269
|
+
backgroundColor: pulse.interpolate({
|
|
270
|
+
inputRange: [0, 1],
|
|
271
|
+
outputRange: seg.pulseColor
|
|
272
|
+
})
|
|
273
|
+
} : {
|
|
274
|
+
opacity: pulse.interpolate({
|
|
275
|
+
inputRange: [0, 1],
|
|
276
|
+
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
accessibilityElementsHidden: true,
|
|
281
|
+
testID
|
|
282
|
+
}
|
|
283
|
+
) : /* @__PURE__ */ jsx(reactNative.View, { style: fillStyle, accessibilityElementsHidden: true, testID })
|
|
284
|
+
},
|
|
285
|
+
i
|
|
286
|
+
);
|
|
287
|
+
}),
|
|
288
|
+
marker ? /* @__PURE__ */ jsx(
|
|
289
|
+
reactNative.View,
|
|
290
|
+
{
|
|
291
|
+
style: {
|
|
292
|
+
position: "absolute",
|
|
293
|
+
left: `${marker.position * 100}%`,
|
|
294
|
+
top: 0,
|
|
295
|
+
bottom: 0,
|
|
296
|
+
width: 2,
|
|
297
|
+
backgroundColor: marker.color
|
|
298
|
+
},
|
|
299
|
+
accessibilityElementsHidden: true,
|
|
300
|
+
testID: "segmented-bar-marker"
|
|
301
|
+
}
|
|
302
|
+
) : null
|
|
303
|
+
] });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/components/custom/Workout/SetBar.tsx
|
|
307
|
+
var SET_STRIP_ZONES = {
|
|
308
|
+
slow: primitiveRamps.red[600],
|
|
309
|
+
moderate: primitiveRamps.orange[400],
|
|
310
|
+
fast: primitiveRamps.amber[300],
|
|
311
|
+
fastest: primitiveRamps.green[300]
|
|
312
|
+
};
|
|
313
|
+
var TODO_COLOR = primitiveColors.charcoal[300];
|
|
314
|
+
var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
|
|
315
|
+
var NOTCH_GAP = 2;
|
|
316
|
+
var CLUSTER_GAP = 3;
|
|
317
|
+
var MYO_UPCOMING_CLUSTERS = 3;
|
|
318
|
+
var MYO_UPCOMING_CLUSTER_LEN = 5;
|
|
319
|
+
var MYO_UPCOMING_FADE = 0.6;
|
|
320
|
+
var PULSE_RANGE = {
|
|
321
|
+
[SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
|
|
322
|
+
[SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
|
|
323
|
+
[SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
|
|
324
|
+
[SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
|
|
325
|
+
};
|
|
326
|
+
function velocityZoneColor(v) {
|
|
327
|
+
if (v < 0.5) return SET_STRIP_ZONES.slow;
|
|
328
|
+
if (v < 0.75) return SET_STRIP_ZONES.moderate;
|
|
329
|
+
if (v < 1) return SET_STRIP_ZONES.fast;
|
|
330
|
+
return SET_STRIP_ZONES.fastest;
|
|
331
|
+
}
|
|
332
|
+
function chunkedSegments(chunks, gap) {
|
|
333
|
+
return chunks.flatMap(
|
|
334
|
+
(vels, ci) => vels.map((v, ri) => ({
|
|
335
|
+
color: velocityZoneColor(v),
|
|
336
|
+
...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
|
|
337
|
+
}))
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
function rangeSegments(floor, max, doneVels) {
|
|
341
|
+
return Array.from({ length: max }, (_, i) => {
|
|
342
|
+
if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
|
|
343
|
+
return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
function myoUpcomingSegments(activationLen, clusterCount) {
|
|
347
|
+
const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
|
|
348
|
+
const trail = Array.from(
|
|
349
|
+
{ length: clusterCount },
|
|
350
|
+
(_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
|
|
351
|
+
color: SET_STRIP_VARIABLE_COLOR,
|
|
352
|
+
opacity: MYO_UPCOMING_FADE ** ci,
|
|
353
|
+
...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
|
|
354
|
+
}))
|
|
355
|
+
).flat();
|
|
356
|
+
return [...activation, ...trail];
|
|
357
|
+
}
|
|
358
|
+
function setSegments(set) {
|
|
359
|
+
if (set.status === "todo") {
|
|
360
|
+
return [{ color: TODO_COLOR }];
|
|
361
|
+
}
|
|
362
|
+
if (set.status === "done") {
|
|
363
|
+
return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
|
|
364
|
+
}
|
|
365
|
+
if (set.status === "range") {
|
|
366
|
+
return rangeSegments(set.floor, set.max, set.doneVels);
|
|
367
|
+
}
|
|
368
|
+
if (set.status === "drop") {
|
|
369
|
+
return chunkedSegments(set.subloads, NOTCH_GAP);
|
|
370
|
+
}
|
|
371
|
+
if (set.status === "myo") {
|
|
372
|
+
return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
|
|
373
|
+
}
|
|
374
|
+
if (set.status === "myo-upcoming") {
|
|
375
|
+
return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
|
|
376
|
+
}
|
|
377
|
+
const performed = set.velocities.map((v) => {
|
|
378
|
+
const color = velocityZoneColor(v);
|
|
379
|
+
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
380
|
+
});
|
|
381
|
+
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
382
|
+
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
383
|
+
}
|
|
384
|
+
function setBarSegmentTestID(seg) {
|
|
385
|
+
if (seg.pulse) return "set-strip-pulse";
|
|
386
|
+
if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
|
|
387
|
+
return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
|
|
388
|
+
}
|
|
389
|
+
function SetBar({ set, height = 8 }) {
|
|
390
|
+
return /* @__PURE__ */ jsx(
|
|
391
|
+
SegmentedBar,
|
|
392
|
+
{
|
|
393
|
+
segments: setSegments(set),
|
|
394
|
+
height,
|
|
395
|
+
gap: 0,
|
|
396
|
+
radius: 0,
|
|
397
|
+
segmentTestID: setBarSegmentTestID,
|
|
398
|
+
style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
|
|
399
|
+
testID: "set-strip-set"
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/components/custom/Workout/SetStrip.tsx
|
|
405
|
+
var SET_STRIP_GAP = 5;
|
|
406
|
+
function setCategory(set) {
|
|
407
|
+
switch (set.status) {
|
|
408
|
+
case "done":
|
|
409
|
+
case "drop":
|
|
410
|
+
case "myo":
|
|
411
|
+
return "done";
|
|
412
|
+
case "active":
|
|
413
|
+
return "active";
|
|
414
|
+
case "range":
|
|
415
|
+
return set.doneVels.length > 0 ? "active" : "upcoming";
|
|
416
|
+
case "todo":
|
|
417
|
+
case "myo-upcoming":
|
|
418
|
+
return "upcoming";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function describeSets(sets) {
|
|
422
|
+
const done = sets.filter((s) => setCategory(s) === "done").length;
|
|
423
|
+
const active = sets.filter((s) => setCategory(s) === "active").length;
|
|
424
|
+
const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
|
|
425
|
+
return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
|
|
426
|
+
}
|
|
427
|
+
function SetStrip({ sets, height = 8, className, ...props }) {
|
|
428
|
+
return /* @__PURE__ */ jsx(
|
|
429
|
+
reactNative.View,
|
|
430
|
+
{
|
|
431
|
+
className,
|
|
432
|
+
style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
|
|
433
|
+
accessibilityRole: "image",
|
|
434
|
+
accessibilityLabel: describeSets(sets),
|
|
435
|
+
testID: "set-strip",
|
|
436
|
+
...props,
|
|
437
|
+
children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
197
442
|
// src/components/custom/Workout/VelocityStrip.tsx
|
|
198
443
|
var VEL_COLORS = WORKOUT_TOKENS.scale;
|
|
199
|
-
var
|
|
200
|
-
speed:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
444
|
+
var bandIdToEffortColor = {
|
|
445
|
+
speed: sequentialEffort[0],
|
|
446
|
+
// green-300
|
|
447
|
+
power: sequentialEffort[2],
|
|
448
|
+
// amber-300 (gold)
|
|
449
|
+
strengthSpeed: sequentialEffort[3],
|
|
450
|
+
// orange-400
|
|
451
|
+
maximalStrength: sequentialEffort[4],
|
|
452
|
+
// red-600
|
|
453
|
+
grinding: sequentialEffort[5]
|
|
454
|
+
// red-700 — the distinct 5th band
|
|
205
455
|
};
|
|
206
456
|
function getVelocityZoneColor(velocity) {
|
|
207
457
|
if (velocity >= 1) return "vel-green";
|
|
@@ -240,14 +490,140 @@ function classifyBand(velocity, bands) {
|
|
|
240
490
|
return bands[bands.length - 1];
|
|
241
491
|
}
|
|
242
492
|
function bandColor(band) {
|
|
243
|
-
|
|
244
|
-
return token ? VEL_COLORS[token] : VEL_COLORS.green;
|
|
493
|
+
return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
|
|
245
494
|
}
|
|
246
495
|
function getLossStyle(loss) {
|
|
247
496
|
if (loss > 25) return { color: VEL_COLORS.red };
|
|
248
497
|
if (loss > 20) return { color: VEL_COLORS.orange };
|
|
249
498
|
return null;
|
|
250
499
|
}
|
|
500
|
+
var REP_GAP = 2;
|
|
501
|
+
var WIDE_GAP = 8;
|
|
502
|
+
var TODO_COLOR2 = primitiveColors.charcoal[300];
|
|
503
|
+
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
504
|
+
function deriveDoneVelocities(set) {
|
|
505
|
+
switch (set.type) {
|
|
506
|
+
case "drop":
|
|
507
|
+
return set.subloads.flat();
|
|
508
|
+
case "myo":
|
|
509
|
+
return [...set.activation, ...set.clusters.flat()];
|
|
510
|
+
default:
|
|
511
|
+
return set.velocities;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function chunkedRepSlots(chunks) {
|
|
515
|
+
const slots = [];
|
|
516
|
+
chunks.forEach((chunk) => {
|
|
517
|
+
chunk.forEach((velocity, ri) => {
|
|
518
|
+
const first = slots.length === 0;
|
|
519
|
+
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP });
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
return slots;
|
|
523
|
+
}
|
|
524
|
+
function buildSlots(set) {
|
|
525
|
+
switch (set.type) {
|
|
526
|
+
case "straight": {
|
|
527
|
+
const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
|
|
528
|
+
return Array.from({ length: total }, (_, i) => {
|
|
529
|
+
const done = i < set.velocities.length;
|
|
530
|
+
return {
|
|
531
|
+
kind: done ? "rep" : "todo",
|
|
532
|
+
velocity: done ? set.velocities[i] : void 0,
|
|
533
|
+
leadingGap: i === 0 ? 0 : REP_GAP
|
|
534
|
+
};
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
case "range": {
|
|
538
|
+
const total = Math.max(set.max, set.velocities.length);
|
|
539
|
+
return Array.from({ length: total }, (_, i) => {
|
|
540
|
+
const kind = i < set.velocities.length ? "rep" : i < set.floor ? "todo" : "variable";
|
|
541
|
+
return {
|
|
542
|
+
kind,
|
|
543
|
+
velocity: kind === "rep" ? set.velocities[i] : void 0,
|
|
544
|
+
leadingGap: i === 0 ? 0 : REP_GAP
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
case "amrap": {
|
|
549
|
+
const reps = set.velocities.map((velocity, i) => ({
|
|
550
|
+
kind: "rep",
|
|
551
|
+
velocity,
|
|
552
|
+
leadingGap: i === 0 ? 0 : REP_GAP
|
|
553
|
+
}));
|
|
554
|
+
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP });
|
|
555
|
+
return reps;
|
|
556
|
+
}
|
|
557
|
+
case "drop":
|
|
558
|
+
return chunkedRepSlots(set.subloads);
|
|
559
|
+
case "myo": {
|
|
560
|
+
const slots = chunkedRepSlots([set.activation, ...set.clusters]);
|
|
561
|
+
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP });
|
|
562
|
+
return slots;
|
|
563
|
+
}
|
|
564
|
+
case "cluster": {
|
|
565
|
+
const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
|
|
566
|
+
return Array.from({ length: total }, (_, i) => {
|
|
567
|
+
const done = i < set.velocities.length;
|
|
568
|
+
const boundary = i > 0 && i % set.groupSize === 0;
|
|
569
|
+
return {
|
|
570
|
+
kind: done ? "rep" : "todo",
|
|
571
|
+
velocity: done ? set.velocities[i] : void 0,
|
|
572
|
+
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP
|
|
573
|
+
};
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function setAccessibilityLabel(set, repCount) {
|
|
579
|
+
switch (set.type) {
|
|
580
|
+
case "straight":
|
|
581
|
+
return `Velocity strip, straight set, ${repCount} reps`;
|
|
582
|
+
case "range":
|
|
583
|
+
return `Velocity strip, rep-range set, floor ${set.floor} to ${set.max}, ${repCount} reps done`;
|
|
584
|
+
case "amrap":
|
|
585
|
+
return `Velocity strip, AMRAP set, ${repCount} reps and counting`;
|
|
586
|
+
case "drop":
|
|
587
|
+
return `Velocity strip, drop set, ${set.subloads.length} loads, ${repCount} reps`;
|
|
588
|
+
case "myo":
|
|
589
|
+
return `Velocity strip, myo-reps set, ${set.clusters.length} clusters${set.open ? ", open" : ""}, ${repCount} reps`;
|
|
590
|
+
case "cluster":
|
|
591
|
+
return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function slotAccessibilityLabel(slot, index) {
|
|
595
|
+
switch (slot.kind) {
|
|
596
|
+
case "rep":
|
|
597
|
+
return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
|
|
598
|
+
case "todo":
|
|
599
|
+
return `Rep ${index + 1}: planned`;
|
|
600
|
+
case "variable":
|
|
601
|
+
return `Rep ${index + 1}: variable`;
|
|
602
|
+
case "continue":
|
|
603
|
+
return "Keep going";
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
var EXPANDED_TODO_STUB_PCT = 16;
|
|
607
|
+
var EXPANDED_ENCODED_PCT = 45;
|
|
608
|
+
var EXPANDED_HEIGHT = 60;
|
|
609
|
+
var FIXED_MAX_VELOCITY = 1.15;
|
|
610
|
+
var BARE_STUB_HEIGHT = 3;
|
|
611
|
+
var HERO_HEIGHT = 220;
|
|
612
|
+
var HERO_LABEL_HEADROOM = 20;
|
|
613
|
+
var HERO_BAR_GAP = 8;
|
|
614
|
+
var HERO_BAR_MAX_WIDTH = 52;
|
|
615
|
+
var HERO_BAR_RADIUS = 5;
|
|
616
|
+
var HERO_MIN_BAR_HEIGHT = 4;
|
|
617
|
+
var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
|
|
618
|
+
var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
|
|
619
|
+
function scaleDenominator(scale, maxVelocity) {
|
|
620
|
+
return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
|
|
621
|
+
}
|
|
622
|
+
function bareSlotHeight(slot, height, denom) {
|
|
623
|
+
if (slot.kind !== "rep" || denom <= 0) return BARE_STUB_HEIGHT;
|
|
624
|
+
const ratio = Math.min(1, (slot.velocity ?? 0) / denom);
|
|
625
|
+
return Math.max(BARE_STUB_HEIGHT, ratio * height);
|
|
626
|
+
}
|
|
251
627
|
function getReducedMotionPreference() {
|
|
252
628
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
253
629
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
@@ -267,33 +643,11 @@ function usePrefersReducedMotion() {
|
|
|
267
643
|
var ANIMATION_DURATION = 400;
|
|
268
644
|
var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
|
|
269
645
|
var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
|
|
270
|
-
function
|
|
271
|
-
velocities,
|
|
272
|
-
zones,
|
|
273
|
-
liveRepIndex,
|
|
274
|
-
expanded = false,
|
|
275
|
-
onToggle,
|
|
276
|
-
onRepPress,
|
|
277
|
-
variant = "full",
|
|
278
|
-
showInfo = true,
|
|
279
|
-
className,
|
|
280
|
-
...props
|
|
281
|
-
}) {
|
|
282
|
-
const maxVelocity = Math.max(...velocities, 0);
|
|
283
|
-
const meanVelocity = calculateMeanVelocity(velocities);
|
|
284
|
-
const loss = calculateVelocityLoss(velocities);
|
|
285
|
-
const hasZones = zones != null && zones.length > 0;
|
|
286
|
-
const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
|
|
287
|
-
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
646
|
+
function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
|
|
288
647
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
289
|
-
const [heightAnim] = react.useState(() => new reactNative.Animated.Value(expanded ? 60 : 3));
|
|
290
|
-
const [labelOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
291
|
-
const [infoOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
292
648
|
const [liveScale] = react.useState(() => new reactNative.Animated.Value(1));
|
|
293
|
-
const liveVelocity = liveRepIndex != null ? velocities[liveRepIndex] : void 0;
|
|
294
|
-
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
295
649
|
react.useEffect(() => {
|
|
296
|
-
if (
|
|
650
|
+
if (!active || liveRepIndex == null || liveVelocity == null) return;
|
|
297
651
|
if (prefersReducedMotion) {
|
|
298
652
|
liveScale.setValue(1);
|
|
299
653
|
return;
|
|
@@ -323,12 +677,201 @@ function VelocityStrip({
|
|
|
323
677
|
useNativeDriver: true
|
|
324
678
|
}).start();
|
|
325
679
|
}
|
|
326
|
-
}, [
|
|
680
|
+
}, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
|
|
681
|
+
return liveScale;
|
|
682
|
+
}
|
|
683
|
+
function HeroVelocityChart({
|
|
684
|
+
doneVelocities,
|
|
685
|
+
barColorFor,
|
|
686
|
+
scaleDenom,
|
|
687
|
+
referenceVelocity,
|
|
688
|
+
liveRepIndex,
|
|
689
|
+
isNewPeak,
|
|
690
|
+
targetReps,
|
|
691
|
+
height,
|
|
692
|
+
className,
|
|
693
|
+
viewProps
|
|
694
|
+
}) {
|
|
695
|
+
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
696
|
+
const liveScale = useLiveRepPop(true, liveRepIndex, liveVelocity, isNewPeak);
|
|
697
|
+
const plotHeight = Math.max(0, height - HERO_LABEL_HEADROOM);
|
|
698
|
+
const barHeight = (velocity) => scaleDenom > 0 ? Math.max(HERO_MIN_BAR_HEIGHT, Math.min(1, velocity / scaleDenom) * plotHeight) : HERO_MIN_BAR_HEIGHT;
|
|
699
|
+
const pendingCount = Math.max(0, (targetReps ?? 0) - doneVelocities.length);
|
|
700
|
+
const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
|
|
701
|
+
const repCount = doneVelocities.length;
|
|
702
|
+
const total = Math.max(repCount, targetReps ?? repCount);
|
|
703
|
+
const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
704
|
+
const { style: externalStyle, ...restProps } = viewProps;
|
|
705
|
+
return /* @__PURE__ */ jsx(
|
|
706
|
+
reactNative.View,
|
|
707
|
+
{
|
|
708
|
+
className,
|
|
709
|
+
style: [{ height }, externalStyle],
|
|
710
|
+
accessibilityRole: "image",
|
|
711
|
+
accessibilityLabel: label,
|
|
712
|
+
testID: "velocity-strip-hero",
|
|
713
|
+
...restProps,
|
|
714
|
+
children: /* @__PURE__ */ jsxs(
|
|
715
|
+
reactNative.View,
|
|
716
|
+
{
|
|
717
|
+
style: {
|
|
718
|
+
flex: 1,
|
|
719
|
+
flexDirection: "row",
|
|
720
|
+
alignItems: "flex-end",
|
|
721
|
+
gap: HERO_BAR_GAP,
|
|
722
|
+
position: "relative",
|
|
723
|
+
borderBottomWidth: 2,
|
|
724
|
+
borderBottomColor: HERO_PENDING_COLOR
|
|
725
|
+
},
|
|
726
|
+
children: [
|
|
727
|
+
referencePx > 0 && /* @__PURE__ */ jsx(
|
|
728
|
+
reactNative.View,
|
|
729
|
+
{
|
|
730
|
+
accessibilityElementsHidden: true,
|
|
731
|
+
style: {
|
|
732
|
+
position: "absolute",
|
|
733
|
+
left: 0,
|
|
734
|
+
right: 0,
|
|
735
|
+
bottom: referencePx,
|
|
736
|
+
borderTopWidth: 1,
|
|
737
|
+
borderStyle: "dashed",
|
|
738
|
+
borderColor: HERO_REFERENCE_COLOR,
|
|
739
|
+
pointerEvents: "none"
|
|
740
|
+
},
|
|
741
|
+
testID: "velocity-hero-reference"
|
|
742
|
+
}
|
|
743
|
+
),
|
|
744
|
+
doneVelocities.map((velocity, i) => {
|
|
745
|
+
const isLive = liveRepIndex === i;
|
|
746
|
+
return /* @__PURE__ */ jsxs(
|
|
747
|
+
reactNative.View,
|
|
748
|
+
{
|
|
749
|
+
accessibilityElementsHidden: true,
|
|
750
|
+
style: {
|
|
751
|
+
flex: 1,
|
|
752
|
+
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
753
|
+
height: "100%",
|
|
754
|
+
alignItems: "center",
|
|
755
|
+
justifyContent: "flex-end"
|
|
756
|
+
},
|
|
757
|
+
children: [
|
|
758
|
+
/* @__PURE__ */ jsx(
|
|
759
|
+
reactNative.Text,
|
|
760
|
+
{
|
|
761
|
+
className: "text-text-primary",
|
|
762
|
+
style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
|
|
763
|
+
testID: `velocity-label-${i}`,
|
|
764
|
+
children: formatVelocity(velocity)
|
|
765
|
+
}
|
|
766
|
+
),
|
|
767
|
+
/* @__PURE__ */ jsx(
|
|
768
|
+
reactNative.Animated.View,
|
|
769
|
+
{
|
|
770
|
+
style: [
|
|
771
|
+
{
|
|
772
|
+
width: "100%",
|
|
773
|
+
height: barHeight(velocity),
|
|
774
|
+
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
775
|
+
borderTopRightRadius: HERO_BAR_RADIUS,
|
|
776
|
+
backgroundColor: barColorFor(velocity)
|
|
777
|
+
},
|
|
778
|
+
isLive ? { transform: [{ scale: liveScale }] } : null
|
|
779
|
+
],
|
|
780
|
+
testID: `velocity-bar-${i}`
|
|
781
|
+
}
|
|
782
|
+
)
|
|
783
|
+
]
|
|
784
|
+
},
|
|
785
|
+
i
|
|
786
|
+
);
|
|
787
|
+
}),
|
|
788
|
+
Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
|
|
789
|
+
reactNative.View,
|
|
790
|
+
{
|
|
791
|
+
accessibilityElementsHidden: true,
|
|
792
|
+
style: {
|
|
793
|
+
flex: 1,
|
|
794
|
+
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
795
|
+
height: "100%",
|
|
796
|
+
alignItems: "center",
|
|
797
|
+
justifyContent: "flex-end"
|
|
798
|
+
},
|
|
799
|
+
children: /* @__PURE__ */ jsx(
|
|
800
|
+
reactNative.View,
|
|
801
|
+
{
|
|
802
|
+
style: {
|
|
803
|
+
width: "100%",
|
|
804
|
+
height: HERO_MIN_BAR_HEIGHT * 3,
|
|
805
|
+
borderWidth: 1,
|
|
806
|
+
borderStyle: "dashed",
|
|
807
|
+
borderColor: HERO_PENDING_COLOR,
|
|
808
|
+
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
809
|
+
borderTopRightRadius: HERO_BAR_RADIUS
|
|
810
|
+
},
|
|
811
|
+
testID: "velocity-slot-todo"
|
|
812
|
+
}
|
|
813
|
+
)
|
|
814
|
+
},
|
|
815
|
+
`pending-${i}`
|
|
816
|
+
))
|
|
817
|
+
]
|
|
818
|
+
}
|
|
819
|
+
)
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
function VelocityStrip({
|
|
824
|
+
velocities,
|
|
825
|
+
set,
|
|
826
|
+
zones,
|
|
827
|
+
liveRepIndex,
|
|
828
|
+
expanded = true,
|
|
829
|
+
onToggle,
|
|
830
|
+
onRepPress,
|
|
831
|
+
variant = "expanded",
|
|
832
|
+
height = EXPANDED_HEIGHT,
|
|
833
|
+
targetReps,
|
|
834
|
+
scale = "peak",
|
|
835
|
+
showNumbers = true,
|
|
836
|
+
showInfo = true,
|
|
837
|
+
className,
|
|
838
|
+
...props
|
|
839
|
+
}) {
|
|
840
|
+
const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
|
|
841
|
+
const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
|
|
842
|
+
kind: "rep",
|
|
843
|
+
velocity: v,
|
|
844
|
+
leadingGap: i === 0 ? 0 : REP_GAP
|
|
845
|
+
}));
|
|
846
|
+
const maxVelocity = Math.max(...doneVelocities, 0);
|
|
847
|
+
const meanVelocity = calculateMeanVelocity(doneVelocities);
|
|
848
|
+
const loss = calculateVelocityLoss(doneVelocities);
|
|
849
|
+
const framed = showNumbers || showInfo;
|
|
850
|
+
const scaleDenom = scaleDenominator(scale, maxVelocity);
|
|
851
|
+
const hasZones = zones != null && zones.length > 0;
|
|
852
|
+
const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
|
|
853
|
+
const slotColor = (slot) => {
|
|
854
|
+
if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
|
|
855
|
+
if (slot.kind === "todo") return TODO_COLOR2;
|
|
856
|
+
return SET_STRIP_VARIABLE_COLOR;
|
|
857
|
+
};
|
|
858
|
+
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
859
|
+
const [heightAnim] = react.useState(() => new reactNative.Animated.Value(expanded ? height : 3));
|
|
860
|
+
const [labelOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
861
|
+
const [infoOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
862
|
+
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
863
|
+
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
864
|
+
const liveScale = useLiveRepPop(
|
|
865
|
+
variant === "expanded" && framed,
|
|
866
|
+
liveRepIndex,
|
|
867
|
+
liveVelocity,
|
|
868
|
+
isNewPeak
|
|
869
|
+
);
|
|
327
870
|
react.useEffect(() => {
|
|
328
|
-
if (variant
|
|
871
|
+
if (variant !== "expanded" || !framed) return;
|
|
329
872
|
reactNative.Animated.parallel([
|
|
330
873
|
reactNative.Animated.timing(heightAnim, {
|
|
331
|
-
toValue: expanded ?
|
|
874
|
+
toValue: expanded ? height : 3,
|
|
332
875
|
duration: ANIMATION_DURATION,
|
|
333
876
|
easing: ANIMATION_EASING,
|
|
334
877
|
useNativeDriver: false
|
|
@@ -346,39 +889,136 @@ function VelocityStrip({
|
|
|
346
889
|
useNativeDriver: false
|
|
347
890
|
})
|
|
348
891
|
]).start();
|
|
349
|
-
}, [expanded, variant, heightAnim, labelOpacity, infoOpacity]);
|
|
892
|
+
}, [expanded, variant, framed, height, heightAnim, labelOpacity, infoOpacity]);
|
|
893
|
+
if (set == null && velocities == null) return null;
|
|
894
|
+
const repCount = doneVelocities.length;
|
|
895
|
+
const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
|
|
896
|
+
if (variant === "hero") {
|
|
897
|
+
const heroHeight = height === EXPANDED_HEIGHT ? HERO_HEIGHT : height;
|
|
898
|
+
return /* @__PURE__ */ jsx(
|
|
899
|
+
HeroVelocityChart,
|
|
900
|
+
{
|
|
901
|
+
doneVelocities,
|
|
902
|
+
barColorFor,
|
|
903
|
+
scaleDenom,
|
|
904
|
+
referenceVelocity: maxVelocity,
|
|
905
|
+
liveRepIndex,
|
|
906
|
+
isNewPeak,
|
|
907
|
+
targetReps,
|
|
908
|
+
height: heroHeight,
|
|
909
|
+
className,
|
|
910
|
+
viewProps: props
|
|
911
|
+
}
|
|
912
|
+
);
|
|
913
|
+
}
|
|
350
914
|
if (variant === "mini") {
|
|
351
915
|
const { style: externalStyle, ...restProps } = props;
|
|
352
916
|
return /* @__PURE__ */ jsx(
|
|
353
917
|
reactNative.View,
|
|
354
918
|
{
|
|
355
919
|
className,
|
|
356
|
-
style: [
|
|
920
|
+
style: [
|
|
921
|
+
{ flexDirection: "row", height: 3, gap: REP_GAP, borderRadius: 2, overflow: "hidden" },
|
|
922
|
+
externalStyle
|
|
923
|
+
],
|
|
357
924
|
accessibilityRole: "image",
|
|
358
|
-
accessibilityLabel:
|
|
925
|
+
accessibilityLabel: miniLabel,
|
|
359
926
|
testID: "velocity-strip-mini",
|
|
360
927
|
...restProps,
|
|
361
|
-
children:
|
|
928
|
+
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
362
929
|
reactNative.View,
|
|
363
930
|
{
|
|
364
931
|
style: {
|
|
365
932
|
flex: 1,
|
|
366
|
-
backgroundColor:
|
|
933
|
+
backgroundColor: slotColor(slot),
|
|
367
934
|
borderRadius: 1,
|
|
368
935
|
minWidth: 4,
|
|
369
|
-
height: "100%"
|
|
936
|
+
height: "100%",
|
|
937
|
+
// The container's uniform 2px gap covers rep spacing; a wide slot adds
|
|
938
|
+
// only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
|
|
939
|
+
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
940
|
+
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
370
941
|
},
|
|
371
942
|
accessibilityElementsHidden: true,
|
|
372
|
-
testID: `velocity-bar-${i}`
|
|
943
|
+
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
373
944
|
},
|
|
374
945
|
i
|
|
375
946
|
))
|
|
376
947
|
}
|
|
377
948
|
);
|
|
378
949
|
}
|
|
379
|
-
|
|
950
|
+
if (!framed) {
|
|
951
|
+
const { style: externalStyle, ...restProps } = props;
|
|
952
|
+
return /* @__PURE__ */ jsx(
|
|
953
|
+
reactNative.View,
|
|
954
|
+
{
|
|
955
|
+
className,
|
|
956
|
+
style: [
|
|
957
|
+
{ flexDirection: "row", height, gap: REP_GAP, alignItems: "flex-end" },
|
|
958
|
+
externalStyle
|
|
959
|
+
],
|
|
960
|
+
accessibilityRole: "image",
|
|
961
|
+
accessibilityLabel: miniLabel,
|
|
962
|
+
testID: "velocity-strip-compact",
|
|
963
|
+
...restProps,
|
|
964
|
+
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
965
|
+
reactNative.View,
|
|
966
|
+
{
|
|
967
|
+
style: {
|
|
968
|
+
flex: 1,
|
|
969
|
+
minWidth: 4,
|
|
970
|
+
height: bareSlotHeight(slot, height, scaleDenom),
|
|
971
|
+
// Top-rounded to match the framed chart's bars (the "rounded tops from
|
|
972
|
+
// the numbers one"); square bottoms since bars sit on the baseline.
|
|
973
|
+
borderTopLeftRadius: 2,
|
|
974
|
+
borderTopRightRadius: 2,
|
|
975
|
+
backgroundColor: slotColor(slot),
|
|
976
|
+
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
977
|
+
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
978
|
+
},
|
|
979
|
+
accessibilityElementsHidden: true,
|
|
980
|
+
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
981
|
+
},
|
|
982
|
+
i
|
|
983
|
+
))
|
|
984
|
+
}
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
|
|
380
988
|
const hasInteractiveContainer = onToggle != null;
|
|
381
989
|
const hasInteractiveReps = onRepPress != null && expanded;
|
|
990
|
+
const setBars = set != null && slots.map((slot, i) => {
|
|
991
|
+
const isStraightRep = set.type === "straight" && slot.kind === "rep";
|
|
992
|
+
const heightPct = isStraightRep ? scaleDenom > 0 ? Math.round((slot.velocity ?? 0) / scaleDenom * 100) : 0 : set.type === "straight" ? EXPANDED_TODO_STUB_PCT : EXPANDED_ENCODED_PCT;
|
|
993
|
+
return /* @__PURE__ */ jsx(
|
|
994
|
+
reactNative.View,
|
|
995
|
+
{
|
|
996
|
+
style: {
|
|
997
|
+
flex: 1,
|
|
998
|
+
height: "100%",
|
|
999
|
+
justifyContent: "flex-end",
|
|
1000
|
+
marginLeft: slot.leadingGap
|
|
1001
|
+
},
|
|
1002
|
+
children: /* @__PURE__ */ jsx(
|
|
1003
|
+
reactNative.View,
|
|
1004
|
+
{
|
|
1005
|
+
style: {
|
|
1006
|
+
height: `${heightPct}%`,
|
|
1007
|
+
minHeight: 2,
|
|
1008
|
+
borderTopLeftRadius: 2,
|
|
1009
|
+
borderTopRightRadius: 2,
|
|
1010
|
+
backgroundColor: slotColor(slot),
|
|
1011
|
+
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
1012
|
+
},
|
|
1013
|
+
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
|
|
1014
|
+
accessibilityRole: "image",
|
|
1015
|
+
accessibilityLabel: slotAccessibilityLabel(slot, i)
|
|
1016
|
+
}
|
|
1017
|
+
)
|
|
1018
|
+
},
|
|
1019
|
+
i
|
|
1020
|
+
);
|
|
1021
|
+
});
|
|
382
1022
|
const stripContent = /* @__PURE__ */ jsxs(
|
|
383
1023
|
reactNative.Animated.View,
|
|
384
1024
|
{
|
|
@@ -391,7 +1031,9 @@ function VelocityStrip({
|
|
|
391
1031
|
borderRadius: expanded ? 6 : 2,
|
|
392
1032
|
paddingTop: expanded ? 16 : 0,
|
|
393
1033
|
paddingBottom: expanded ? 24 : 0,
|
|
394
|
-
|
|
1034
|
+
// No horizontal inset — the framed chart's bars span full width, matching
|
|
1035
|
+
// the bare spotlight and mini. Numbers/info sit in the vertical padding.
|
|
1036
|
+
paddingHorizontal: 0,
|
|
395
1037
|
paddingVertical: expanded ? void 0 : 8,
|
|
396
1038
|
overflow: expanded ? "visible" : "hidden"
|
|
397
1039
|
}
|
|
@@ -401,46 +1043,65 @@ function VelocityStrip({
|
|
|
401
1043
|
testID: "velocity-strip",
|
|
402
1044
|
...props,
|
|
403
1045
|
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
|
-
|
|
1046
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row", flex: 1, gap: set ? 0 : 2, alignItems: "flex-end" }, children: set ? setBars : doneVelocities.map((v, i) => {
|
|
1047
|
+
const barBackground = barColorFor(v);
|
|
1048
|
+
const barHeightPct = scaleDenom > 0 ? Math.round(v / scaleDenom * 100) : 0;
|
|
1049
|
+
const isLive = liveRepIndex === i;
|
|
1050
|
+
const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
|
|
1051
|
+
const barStyle = expanded ? {
|
|
1052
|
+
height: `${barHeightPct}%`,
|
|
1053
|
+
minHeight: 2,
|
|
1054
|
+
borderTopLeftRadius: 2,
|
|
1055
|
+
borderTopRightRadius: 2,
|
|
1056
|
+
backgroundColor: barBackground
|
|
1057
|
+
} : {
|
|
1058
|
+
minHeight: "100%",
|
|
1059
|
+
borderTopLeftRadius: 2,
|
|
1060
|
+
borderTopRightRadius: 2,
|
|
1061
|
+
borderBottomLeftRadius: 0,
|
|
1062
|
+
borderBottomRightRadius: 0,
|
|
1063
|
+
backgroundColor: barBackground
|
|
1064
|
+
};
|
|
1065
|
+
const barInner = isLive ? /* @__PURE__ */ jsx(
|
|
1066
|
+
reactNative.Animated.View,
|
|
1067
|
+
{
|
|
1068
|
+
style: [barStyle, { transform: [{ scale: liveScale }] }],
|
|
1069
|
+
testID: `velocity-bar-${i}`,
|
|
1070
|
+
accessibilityRole: "image",
|
|
1071
|
+
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
|
|
1072
|
+
}
|
|
1073
|
+
) : /* @__PURE__ */ jsx(
|
|
1074
|
+
reactNative.View,
|
|
1075
|
+
{
|
|
1076
|
+
style: barStyle,
|
|
1077
|
+
testID: `velocity-bar-${i}`,
|
|
1078
|
+
accessibilityRole: "image",
|
|
1079
|
+
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
const bar = /* @__PURE__ */ jsxs(
|
|
1083
|
+
reactNative.View,
|
|
1084
|
+
{
|
|
1085
|
+
style: {
|
|
1086
|
+
flex: 1,
|
|
1087
|
+
height: "100%",
|
|
1088
|
+
justifyContent: "flex-end",
|
|
1089
|
+
position: "relative"
|
|
1090
|
+
},
|
|
1091
|
+
children: [
|
|
1092
|
+
expanded && showNumbers && /* @__PURE__ */ jsx(
|
|
1093
|
+
reactNative.Animated.View,
|
|
1094
|
+
{
|
|
1095
|
+
style: {
|
|
1096
|
+
opacity: labelOpacity,
|
|
1097
|
+
alignItems: "center",
|
|
1098
|
+
position: "absolute",
|
|
1099
|
+
top: -13,
|
|
1100
|
+
left: 0,
|
|
1101
|
+
right: 0
|
|
1102
|
+
},
|
|
1103
|
+
accessibilityElementsHidden: true,
|
|
1104
|
+
children: /* @__PURE__ */ jsx(
|
|
444
1105
|
reactNative.Text,
|
|
445
1106
|
{
|
|
446
1107
|
className: "text-text-secondary",
|
|
@@ -448,30 +1109,30 @@ function VelocityStrip({
|
|
|
448
1109
|
testID: `velocity-label-${i}`,
|
|
449
1110
|
children: formatVelocity(v)
|
|
450
1111
|
}
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
})
|
|
1112
|
+
)
|
|
1113
|
+
}
|
|
1114
|
+
),
|
|
1115
|
+
barInner
|
|
1116
|
+
]
|
|
1117
|
+
},
|
|
1118
|
+
i
|
|
1119
|
+
);
|
|
1120
|
+
if (onRepPress && expanded) {
|
|
1121
|
+
return /* @__PURE__ */ jsx(
|
|
1122
|
+
reactNative.Pressable,
|
|
1123
|
+
{
|
|
1124
|
+
style: { flex: 1, height: "100%" },
|
|
1125
|
+
onPress: () => onRepPress(i, v),
|
|
1126
|
+
accessibilityRole: "button",
|
|
1127
|
+
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
|
|
1128
|
+
testID: `velocity-bar-pressable-${i}`,
|
|
1129
|
+
children: bar
|
|
1130
|
+
},
|
|
1131
|
+
i
|
|
1132
|
+
);
|
|
473
1133
|
}
|
|
474
|
-
|
|
1134
|
+
return bar;
|
|
1135
|
+
}) }),
|
|
475
1136
|
expanded && showInfo && /* @__PURE__ */ jsxs(
|
|
476
1137
|
reactNative.Animated.View,
|
|
477
1138
|
{
|
|
@@ -647,17 +1308,49 @@ function SvgIcon({
|
|
|
647
1308
|
}
|
|
648
1309
|
|
|
649
1310
|
// src/components/icons/icons.tsx
|
|
650
|
-
function
|
|
1311
|
+
function StarIcon(props) {
|
|
1312
|
+
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" }) });
|
|
1313
|
+
}
|
|
1314
|
+
function ScaleIcon(props) {
|
|
651
1315
|
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: "
|
|
1316
|
+
/* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1317
|
+
/* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1318
|
+
/* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
|
|
1319
|
+
/* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
|
|
1320
|
+
/* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
|
|
657
1321
|
] });
|
|
658
1322
|
}
|
|
659
|
-
function
|
|
660
|
-
return /* @__PURE__ */
|
|
1323
|
+
function AlertTriangleIcon(props) {
|
|
1324
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1325
|
+
/* @__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" }),
|
|
1326
|
+
/* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
|
|
1327
|
+
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
1328
|
+
] });
|
|
1329
|
+
}
|
|
1330
|
+
function TrendingDownIcon(props) {
|
|
1331
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1332
|
+
/* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
|
|
1333
|
+
/* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
|
|
1334
|
+
] });
|
|
1335
|
+
}
|
|
1336
|
+
function CircleSlashIcon(props) {
|
|
1337
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1338
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1339
|
+
/* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
|
|
1340
|
+
] });
|
|
1341
|
+
}
|
|
1342
|
+
function AwardIcon(props) {
|
|
1343
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1344
|
+
/* @__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" }),
|
|
1345
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
|
|
1346
|
+
] });
|
|
1347
|
+
}
|
|
1348
|
+
function InfoIcon(props) {
|
|
1349
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1350
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1351
|
+
/* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
|
|
1352
|
+
/* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
|
|
1353
|
+
] });
|
|
661
1354
|
}
|
|
662
1355
|
var baseBadgeSizeConfig = {
|
|
663
1356
|
sm: { fontSize: 9, paddingH: 6, paddingV: 2, iconSize: 10 },
|
|
@@ -760,6 +1453,9 @@ var semanticColorsLight = {
|
|
|
760
1453
|
"status-success-light": primitiveRamps.green[200],
|
|
761
1454
|
"status-success-dark": primitiveRamps.green[600],
|
|
762
1455
|
"status-success-subtle": primitiveRamps.green[50],
|
|
1456
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
1457
|
+
"status-live": primitiveRamps.green[300],
|
|
1458
|
+
"status-live-muted": primitiveRamps.green[500],
|
|
763
1459
|
"status-error": primitiveRamps.red[600],
|
|
764
1460
|
"status-error-light": primitiveRamps.red[500],
|
|
765
1461
|
"status-error-dark": primitiveRamps.red[700],
|
|
@@ -893,6 +1589,9 @@ var semanticColorsDark = {
|
|
|
893
1589
|
"status-success-light": primitiveRamps.green[200],
|
|
894
1590
|
"status-success-dark": primitiveRamps.green[600],
|
|
895
1591
|
"status-success-subtle": "rgba(46, 213, 115, 0.12)",
|
|
1592
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
1593
|
+
"status-live": primitiveRamps.green[300],
|
|
1594
|
+
"status-live-muted": primitiveRamps.green[500],
|
|
896
1595
|
"status-error": primitiveRamps.red[600],
|
|
897
1596
|
"status-error-light": primitiveRamps.red[500],
|
|
898
1597
|
"status-error-dark": primitiveRamps.red[700],
|
|
@@ -997,93 +1696,7 @@ function getSemanticColors(mode) {
|
|
|
997
1696
|
return mode === "dark" ? semanticColorsDark : semanticColorsLight;
|
|
998
1697
|
}
|
|
999
1698
|
|
|
1000
|
-
// src/components/custom/Workout/
|
|
1001
|
-
function WeightBadge({
|
|
1002
|
-
value,
|
|
1003
|
-
unit = "lbs",
|
|
1004
|
-
reps,
|
|
1005
|
-
delta,
|
|
1006
|
-
isPr,
|
|
1007
|
-
showIcon = true,
|
|
1008
|
-
size = "md",
|
|
1009
|
-
onPress,
|
|
1010
|
-
className,
|
|
1011
|
-
...props
|
|
1012
|
-
}) {
|
|
1013
|
-
const config = baseBadgeSizeConfig[size];
|
|
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
|
-
}
|
|
1699
|
+
// src/components/custom/Workout/PrBadge.tsx
|
|
1087
1700
|
var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
|
|
1088
1701
|
var typeLabels = {
|
|
1089
1702
|
e1rm: "PR e1RM",
|
|
@@ -1167,6 +1780,204 @@ function PrBadge({
|
|
|
1167
1780
|
}
|
|
1168
1781
|
return badge;
|
|
1169
1782
|
}
|
|
1783
|
+
var DARK = getSemanticColors("dark");
|
|
1784
|
+
var TEXT_ACTIVE = DARK["text-primary"];
|
|
1785
|
+
var TEXT_MUTED = DARK["text-secondary"];
|
|
1786
|
+
var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
|
|
1787
|
+
var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
|
|
1788
|
+
var typeBadge = {
|
|
1789
|
+
fontSize: 10,
|
|
1790
|
+
fontWeight: "700",
|
|
1791
|
+
fontFamily: "Inter, sans-serif",
|
|
1792
|
+
color: WORKOUT_TOKENS.scale.orange,
|
|
1793
|
+
backgroundColor: "rgba(255,165,2,0.12)",
|
|
1794
|
+
paddingVertical: 1,
|
|
1795
|
+
paddingHorizontal: 5,
|
|
1796
|
+
borderRadius: 3,
|
|
1797
|
+
overflow: "hidden"
|
|
1798
|
+
};
|
|
1799
|
+
function displayReps(set) {
|
|
1800
|
+
return set.state === "done" ? set.reps : set.target.reps;
|
|
1801
|
+
}
|
|
1802
|
+
function displayWeight(set) {
|
|
1803
|
+
return set.state === "done" ? set.weight : set.target.weight;
|
|
1804
|
+
}
|
|
1805
|
+
function plannedReps(set) {
|
|
1806
|
+
if (set.state === "done") return set.velocities.length;
|
|
1807
|
+
if (set.state === "live") return set.target.reps;
|
|
1808
|
+
return set.planned ?? set.target.reps;
|
|
1809
|
+
}
|
|
1810
|
+
function accessibilityLabel(set) {
|
|
1811
|
+
const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
|
|
1812
|
+
if (set.state === "live") return `${base}, in progress`;
|
|
1813
|
+
if (set.state === "todo") return `${base}, upcoming`;
|
|
1814
|
+
return base;
|
|
1815
|
+
}
|
|
1816
|
+
function Cell({
|
|
1817
|
+
width,
|
|
1818
|
+
children,
|
|
1819
|
+
testID
|
|
1820
|
+
}) {
|
|
1821
|
+
return /* @__PURE__ */ jsx(reactNative.View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
|
|
1822
|
+
}
|
|
1823
|
+
function RowStrip({ set }) {
|
|
1824
|
+
const zones = set.velocityZones;
|
|
1825
|
+
if (set.state === "live") {
|
|
1826
|
+
return /* @__PURE__ */ jsx(
|
|
1827
|
+
VelocityStrip,
|
|
1828
|
+
{
|
|
1829
|
+
variant: "expanded",
|
|
1830
|
+
showNumbers: false,
|
|
1831
|
+
showInfo: false,
|
|
1832
|
+
height: 24,
|
|
1833
|
+
scale: "fixed",
|
|
1834
|
+
set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
|
|
1835
|
+
zones
|
|
1836
|
+
}
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
const velocities = set.state === "done" ? set.velocities : [];
|
|
1840
|
+
return /* @__PURE__ */ jsx(
|
|
1841
|
+
VelocityStrip,
|
|
1842
|
+
{
|
|
1843
|
+
variant: "mini",
|
|
1844
|
+
set: { type: "straight", velocities, planned: plannedReps(set) },
|
|
1845
|
+
zones
|
|
1846
|
+
}
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
function SetRow(set) {
|
|
1850
|
+
const live = set.state === "live";
|
|
1851
|
+
const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
|
|
1852
|
+
const rpe = set.state === "todo" ? null : set.rpe;
|
|
1853
|
+
return /* @__PURE__ */ jsxs(
|
|
1854
|
+
reactNative.View,
|
|
1855
|
+
{
|
|
1856
|
+
style: { paddingVertical: 6, paddingHorizontal: 8 },
|
|
1857
|
+
accessibilityLabel: accessibilityLabel(set),
|
|
1858
|
+
testID: "set-row",
|
|
1859
|
+
children: [
|
|
1860
|
+
/* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
|
|
1861
|
+
/* @__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 }) }),
|
|
1862
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
|
|
1863
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
|
|
1864
|
+
/* @__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" }) })
|
|
1865
|
+
] }),
|
|
1866
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
|
|
1867
|
+
]
|
|
1868
|
+
}
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
var PREV_COLUMN = { label: "PREV", width: void 0 };
|
|
1872
|
+
function buildColumns(unit, showPrevious) {
|
|
1873
|
+
return [
|
|
1874
|
+
{ label: "SET", width: 36 },
|
|
1875
|
+
...showPrevious ? [PREV_COLUMN] : [],
|
|
1876
|
+
{ label: "REPS", width: 44 },
|
|
1877
|
+
{ label: unit.toUpperCase(), width: 56 },
|
|
1878
|
+
{ label: "RPE", width: 36 }
|
|
1879
|
+
];
|
|
1880
|
+
}
|
|
1881
|
+
function SetTableHeader({
|
|
1882
|
+
unit = "lbs",
|
|
1883
|
+
showPrevious = true,
|
|
1884
|
+
testID = "table-header"
|
|
1885
|
+
}) {
|
|
1886
|
+
const columns = buildColumns(unit, showPrevious);
|
|
1887
|
+
return /* @__PURE__ */ jsx(
|
|
1888
|
+
reactNative.View,
|
|
1889
|
+
{
|
|
1890
|
+
className: "flex-row",
|
|
1891
|
+
style: {
|
|
1892
|
+
paddingVertical: 8,
|
|
1893
|
+
paddingHorizontal: 8,
|
|
1894
|
+
paddingBottom: 4,
|
|
1895
|
+
...showPrevious ? {} : { justifyContent: "space-between" }
|
|
1896
|
+
},
|
|
1897
|
+
testID,
|
|
1898
|
+
children: columns.map(({ label, width }) => {
|
|
1899
|
+
const isFlex = width === void 0;
|
|
1900
|
+
return /* @__PURE__ */ jsx(
|
|
1901
|
+
reactNative.View,
|
|
1902
|
+
{
|
|
1903
|
+
style: {
|
|
1904
|
+
...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
|
|
1905
|
+
alignItems: "center",
|
|
1906
|
+
justifyContent: "center",
|
|
1907
|
+
gap: 8
|
|
1908
|
+
},
|
|
1909
|
+
...isFlex ? { className: "flex-1" } : {},
|
|
1910
|
+
children: /* @__PURE__ */ jsx(
|
|
1911
|
+
reactNative.Text,
|
|
1912
|
+
{
|
|
1913
|
+
className: "text-text-tertiary",
|
|
1914
|
+
style: {
|
|
1915
|
+
fontSize: 10,
|
|
1916
|
+
fontWeight: "600",
|
|
1917
|
+
fontFamily: "Inter, sans-serif",
|
|
1918
|
+
textTransform: "uppercase",
|
|
1919
|
+
letterSpacing: 0.8
|
|
1920
|
+
},
|
|
1921
|
+
children: label
|
|
1922
|
+
}
|
|
1923
|
+
)
|
|
1924
|
+
},
|
|
1925
|
+
label
|
|
1926
|
+
);
|
|
1927
|
+
})
|
|
1928
|
+
}
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
var METRIC_FONT = "Inter, sans-serif";
|
|
1932
|
+
function MetricCell({ children, color, fontSize = 11 }) {
|
|
1933
|
+
return /* @__PURE__ */ jsx(
|
|
1934
|
+
reactNative.Text,
|
|
1935
|
+
{
|
|
1936
|
+
style: {
|
|
1937
|
+
fontFamily: METRIC_FONT,
|
|
1938
|
+
fontSize,
|
|
1939
|
+
color,
|
|
1940
|
+
fontWeight: "600",
|
|
1941
|
+
letterSpacing: 1
|
|
1942
|
+
},
|
|
1943
|
+
children
|
|
1944
|
+
}
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// src/components/custom/Workout/SetsRepsLoad.tsx
|
|
1949
|
+
var t = getSemanticColors("dark");
|
|
1950
|
+
var TIMES = "\xD7";
|
|
1951
|
+
var AT = "@";
|
|
1952
|
+
function SetsRepsLoad({
|
|
1953
|
+
sets,
|
|
1954
|
+
reps,
|
|
1955
|
+
load,
|
|
1956
|
+
unit = "lb",
|
|
1957
|
+
className,
|
|
1958
|
+
...props
|
|
1959
|
+
}) {
|
|
1960
|
+
const value = t["text-primary"];
|
|
1961
|
+
const sep = t["text-tertiary"];
|
|
1962
|
+
return /* @__PURE__ */ jsxs(
|
|
1963
|
+
reactNative.View,
|
|
1964
|
+
{
|
|
1965
|
+
className,
|
|
1966
|
+
style: { flexDirection: "row", alignItems: "baseline" },
|
|
1967
|
+
accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
|
|
1968
|
+
testID: "sets-reps-load",
|
|
1969
|
+
...props,
|
|
1970
|
+
children: [
|
|
1971
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, children: sets }),
|
|
1972
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${TIMES} ` }),
|
|
1973
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, children: reps }),
|
|
1974
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${AT} ` }),
|
|
1975
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, children: load }),
|
|
1976
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${unit}` })
|
|
1977
|
+
]
|
|
1978
|
+
}
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1170
1981
|
function hexToRgb(hex) {
|
|
1171
1982
|
const cleaned = hex.replace("#", "");
|
|
1172
1983
|
let r, g, b;
|
|
@@ -1476,7 +2287,7 @@ function alpha(color, opacity) {
|
|
|
1476
2287
|
}
|
|
1477
2288
|
return color;
|
|
1478
2289
|
}
|
|
1479
|
-
var
|
|
2290
|
+
var t2 = getSemanticColors("dark");
|
|
1480
2291
|
var TEMPO_PACING = {
|
|
1481
2292
|
behindThresholdPct: 0.15,
|
|
1482
2293
|
minPhaseDurationMs: 500
|
|
@@ -1492,20 +2303,20 @@ function getTempoFillPct(elapsedMs, targetMs) {
|
|
|
1492
2303
|
return Math.min(100, elapsedMs / targetMs * 100);
|
|
1493
2304
|
}
|
|
1494
2305
|
({
|
|
1495
|
-
concentric: { color:
|
|
1496
|
-
hold: { color:
|
|
1497
|
-
eccentric: { color:
|
|
2306
|
+
concentric: { color: t2["status-success"]},
|
|
2307
|
+
hold: { color: t2["brand-primary"]},
|
|
2308
|
+
eccentric: { color: t2["status-warning"]}
|
|
1498
2309
|
});
|
|
1499
2310
|
|
|
1500
2311
|
// src/components/custom/Workout/TempoDisplay.tsx
|
|
1501
|
-
var
|
|
2312
|
+
var t3 = getSemanticColors("dark");
|
|
1502
2313
|
var INTER = "Inter, sans-serif";
|
|
1503
2314
|
var TEXT_TERTIARY = "#6B7280";
|
|
1504
|
-
var STATUS_ERROR =
|
|
2315
|
+
var STATUS_ERROR = t3["status-error"];
|
|
1505
2316
|
var phaseColors = {
|
|
1506
|
-
eccentric:
|
|
2317
|
+
eccentric: t3["status-warning"],
|
|
1507
2318
|
pauseBottom: TEXT_TERTIARY,
|
|
1508
|
-
concentric:
|
|
2319
|
+
concentric: t3["status-success"],
|
|
1509
2320
|
pauseTop: TEXT_TERTIARY,
|
|
1510
2321
|
dash: TEXT_TERTIARY
|
|
1511
2322
|
};
|
|
@@ -1514,34 +2325,10 @@ function TempoValue({
|
|
|
1514
2325
|
color,
|
|
1515
2326
|
fontSize
|
|
1516
2327
|
}) {
|
|
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
|
-
);
|
|
2328
|
+
return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: value });
|
|
1530
2329
|
}
|
|
1531
2330
|
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
|
-
);
|
|
2331
|
+
return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: "-" });
|
|
1545
2332
|
}
|
|
1546
2333
|
var LIVE_PHASES = [
|
|
1547
2334
|
{ key: "eccentric", color: phaseColors.eccentric },
|
|
@@ -1603,6 +2390,7 @@ function TempoDisplay({
|
|
|
1603
2390
|
tempo,
|
|
1604
2391
|
size = "md",
|
|
1605
2392
|
colored = true,
|
|
2393
|
+
showLabel = true,
|
|
1606
2394
|
showInfo = true,
|
|
1607
2395
|
live,
|
|
1608
2396
|
onPress,
|
|
@@ -1636,7 +2424,7 @@ function TempoDisplay({
|
|
|
1636
2424
|
},
|
|
1637
2425
|
...props,
|
|
1638
2426
|
children: [
|
|
1639
|
-
/* @__PURE__ */ jsx(
|
|
2427
|
+
showLabel && /* @__PURE__ */ jsx(
|
|
1640
2428
|
reactNative.Text,
|
|
1641
2429
|
{
|
|
1642
2430
|
style: {
|
|
@@ -1770,245 +2558,121 @@ function TempoDisplay({
|
|
|
1770
2558
|
}
|
|
1771
2559
|
);
|
|
1772
2560
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
2561
|
+
var t4 = getSemanticColors("dark");
|
|
2562
|
+
var INDICATOR_CONFIG = {
|
|
2563
|
+
imbalance: { Icon: ScaleIcon, color: t4["status-error"], label: "Left/right imbalance" },
|
|
2564
|
+
overshoot: { Icon: AlertTriangleIcon, color: t4["status-error"], label: "Load overshoot" },
|
|
2565
|
+
"velocity-loss": { Icon: TrendingDownIcon, color: t4["status-warning"], label: "Velocity loss" },
|
|
2566
|
+
"missed-reps": { Icon: CircleSlashIcon, color: t4["status-warning"], label: "Missed reps" },
|
|
2567
|
+
pr: { Icon: AwardIcon, color: t4["status-success"], label: "Personal record" },
|
|
2568
|
+
info: { Icon: InfoIcon, color: t4["status-info"], label: "More info" }
|
|
2569
|
+
};
|
|
2570
|
+
function ExerciseIndicator({ kind, onPress, className, ...props }) {
|
|
2571
|
+
const { Icon, color, label } = INDICATOR_CONFIG[kind];
|
|
2572
|
+
const chip = /* @__PURE__ */ jsx(
|
|
2573
|
+
reactNative.View,
|
|
2574
|
+
{
|
|
2575
|
+
className,
|
|
2576
|
+
style: {
|
|
2577
|
+
width: 16,
|
|
2578
|
+
height: 16,
|
|
2579
|
+
borderRadius: 8,
|
|
2580
|
+
borderWidth: 1,
|
|
2581
|
+
borderColor: color,
|
|
2582
|
+
alignItems: "center",
|
|
2583
|
+
justifyContent: "center"
|
|
2584
|
+
},
|
|
2585
|
+
accessibilityRole: "image",
|
|
2586
|
+
accessibilityLabel: label,
|
|
2587
|
+
testID: "exercise-indicator",
|
|
2588
|
+
...props,
|
|
2589
|
+
children: /* @__PURE__ */ jsx(Icon, { size: 11, color })
|
|
2590
|
+
}
|
|
2591
|
+
);
|
|
2592
|
+
if (onPress) {
|
|
2593
|
+
return /* @__PURE__ */ jsx(reactNative.Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, children: chip });
|
|
2594
|
+
}
|
|
2595
|
+
return chip;
|
|
1777
2596
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
2597
|
+
|
|
2598
|
+
// src/components/custom/Workout/ExerciseHeading.tsx
|
|
2599
|
+
function headingLabel(name, sets, reps, load, unit) {
|
|
2600
|
+
return `${name}, ${sets}\xD7${reps} @ ${roundWeight(load)} ${unit}`;
|
|
2601
|
+
}
|
|
2602
|
+
function ExerciseHeading({
|
|
2603
|
+
name,
|
|
2604
|
+
sets,
|
|
1782
2605
|
reps,
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2606
|
+
load,
|
|
2607
|
+
unit = "lbs",
|
|
2608
|
+
tempo,
|
|
2609
|
+
indicator,
|
|
2610
|
+
dimmed,
|
|
2611
|
+
onPress
|
|
2612
|
+
}) {
|
|
2613
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: { opacity: dimmed ? 0.55 : 1 }, testID: "exercise-heading", children: [
|
|
2614
|
+
/* @__PURE__ */ jsxs(
|
|
2615
|
+
reactNative.Pressable,
|
|
2616
|
+
{
|
|
2617
|
+
onPress,
|
|
2618
|
+
accessibilityRole: "button",
|
|
2619
|
+
accessibilityLabel: headingLabel(name, sets, reps, load, unit),
|
|
2620
|
+
className: "flex-row items-center",
|
|
2621
|
+
style: { gap: 8 },
|
|
2622
|
+
testID: "exercise-card-header",
|
|
2623
|
+
children: [
|
|
2624
|
+
/* @__PURE__ */ jsx(
|
|
2625
|
+
reactNative.Text,
|
|
2626
|
+
{
|
|
2627
|
+
className: "text-text-primary",
|
|
2628
|
+
style: { fontSize: 14, fontFamily: '"Space Grotesk", sans-serif', fontWeight: "700" },
|
|
2629
|
+
testID: "exercise-card-name",
|
|
2630
|
+
children: name
|
|
2631
|
+
}
|
|
2632
|
+
),
|
|
2633
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
|
|
2634
|
+
indicator && /* @__PURE__ */ jsx(ExerciseIndicator, { kind: indicator })
|
|
2635
|
+
]
|
|
2636
|
+
}
|
|
2637
|
+
),
|
|
2638
|
+
/* @__PURE__ */ jsxs(
|
|
2639
|
+
reactNative.View,
|
|
2640
|
+
{
|
|
2641
|
+
className: "flex-row items-center",
|
|
2642
|
+
style: { gap: 8, marginTop: 1 },
|
|
2643
|
+
testID: "exercise-card-summary",
|
|
2644
|
+
children: [
|
|
2645
|
+
/* @__PURE__ */ jsx(SetsRepsLoad, { sets, reps, load, unit }),
|
|
2646
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
|
|
2647
|
+
tempo && /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm", showLabel: false, showInfo: false })
|
|
2648
|
+
]
|
|
2649
|
+
}
|
|
2650
|
+
)
|
|
2651
|
+
] });
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
// src/components/custom/Workout/ExerciseCardHeading.tsx
|
|
2655
|
+
function ExerciseCardHeading({
|
|
2656
|
+
setStates,
|
|
2657
|
+
stripHeight = 8,
|
|
2658
|
+
dimmed,
|
|
2659
|
+
testID = "exercise-card",
|
|
2660
|
+
...heading
|
|
1795
2661
|
}) {
|
|
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
2662
|
return /* @__PURE__ */ jsxs(
|
|
1815
2663
|
reactNative.View,
|
|
1816
2664
|
{
|
|
1817
|
-
style:
|
|
1818
|
-
|
|
1819
|
-
setNumber,
|
|
1820
|
-
reps,
|
|
1821
|
-
weight,
|
|
1822
|
-
unit
|
|
1823
|
-
),
|
|
1824
|
-
testID: "set-row",
|
|
2665
|
+
style: { paddingVertical: 9, paddingHorizontal: 12, opacity: dimmed ? 0.55 : 1 },
|
|
2666
|
+
testID,
|
|
1825
2667
|
children: [
|
|
1826
|
-
/* @__PURE__ */ jsx(
|
|
1827
|
-
|
|
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
|
-
)
|
|
1998
|
-
}
|
|
1999
|
-
)
|
|
2668
|
+
/* @__PURE__ */ jsx(ExerciseHeading, { ...heading }),
|
|
2669
|
+
setStates.length > 0 && /* @__PURE__ */ jsx(reactNative.View, { style: { marginTop: 7 }, testID: "exercise-card-strip", children: /* @__PURE__ */ jsx(SetStrip, { sets: setStates, height: stripHeight }) })
|
|
2000
2670
|
]
|
|
2001
2671
|
}
|
|
2002
2672
|
);
|
|
2003
2673
|
}
|
|
2004
|
-
function resolveColor(token, mode = "dark") {
|
|
2005
|
-
return reactNative.Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
|
|
2006
|
-
}
|
|
2007
2674
|
|
|
2008
2675
|
// src/components/custom/Workout/ExerciseCard.tsx
|
|
2009
|
-
function buildColumnHeaders(unit) {
|
|
2010
|
-
return ["SET", "PREV", "REPS", unit.toUpperCase(), "RPE"];
|
|
2011
|
-
}
|
|
2012
2676
|
function getSupersetBorderRadius(position) {
|
|
2013
2677
|
switch (position) {
|
|
2014
2678
|
case "first":
|
|
@@ -2051,7 +2715,7 @@ function formatSummary(summary) {
|
|
|
2051
2715
|
if (!summary) return "";
|
|
2052
2716
|
return `${summary.sets}\xD7${summary.reps} @ ${roundWeight(summary.weight)} ${summary.unit}`;
|
|
2053
2717
|
}
|
|
2054
|
-
function
|
|
2718
|
+
function formatAccessibilityLabel(name, summary, prescription) {
|
|
2055
2719
|
if (summary) return `${name}, ${formatSummary(summary)}`;
|
|
2056
2720
|
if (prescription) return `${name}, ${prescription}`;
|
|
2057
2721
|
return name;
|
|
@@ -2060,7 +2724,6 @@ function CollapsedCard({
|
|
|
2060
2724
|
name,
|
|
2061
2725
|
onToggle,
|
|
2062
2726
|
summary,
|
|
2063
|
-
e1rm,
|
|
2064
2727
|
isPR,
|
|
2065
2728
|
setVelocities,
|
|
2066
2729
|
velocityZones,
|
|
@@ -2079,7 +2742,7 @@ function CollapsedCard({
|
|
|
2079
2742
|
onPressIn: () => setPressed(true),
|
|
2080
2743
|
onPressOut: () => setPressed(false),
|
|
2081
2744
|
accessibilityRole: "button",
|
|
2082
|
-
accessibilityLabel:
|
|
2745
|
+
accessibilityLabel: formatAccessibilityLabel(name, summary, void 0),
|
|
2083
2746
|
testID: "exercise-card",
|
|
2084
2747
|
children: /* @__PURE__ */ jsxs(
|
|
2085
2748
|
reactNative.View,
|
|
@@ -2093,100 +2756,93 @@ function CollapsedCard({
|
|
|
2093
2756
|
...supersetMargin
|
|
2094
2757
|
},
|
|
2095
2758
|
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
|
-
)
|
|
2759
|
+
/* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", testID: "exercise-card-header", children: [
|
|
2760
|
+
/* @__PURE__ */ jsx(
|
|
2761
|
+
reactNative.Text,
|
|
2762
|
+
{
|
|
2763
|
+
className: "text-text-primary",
|
|
2764
|
+
style: {
|
|
2765
|
+
fontSize: 14,
|
|
2766
|
+
fontFamily: '"Space Grotesk", sans-serif',
|
|
2767
|
+
fontWeight: "700"
|
|
2768
|
+
},
|
|
2769
|
+
testID: "exercise-card-name",
|
|
2770
|
+
children: name
|
|
2771
|
+
}
|
|
2772
|
+
),
|
|
2773
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
|
|
2774
|
+
summary && /* @__PURE__ */ jsx(
|
|
2775
|
+
reactNative.Text,
|
|
2776
|
+
{
|
|
2777
|
+
className: "text-text-secondary",
|
|
2778
|
+
style: {
|
|
2779
|
+
fontSize: 12,
|
|
2780
|
+
fontFamily: "Inter, sans-serif",
|
|
2781
|
+
marginRight: 8
|
|
2782
|
+
},
|
|
2783
|
+
testID: "exercise-card-summary",
|
|
2784
|
+
children: formatSummary(summary)
|
|
2785
|
+
}
|
|
2786
|
+
),
|
|
2787
|
+
isPR && /* @__PURE__ */ jsx(reactNative.View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
|
|
2788
|
+
] }),
|
|
2789
|
+
(completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row", style: { marginTop: 6, gap: 4 }, testID: "exercise-card-strips", children: [
|
|
2790
|
+
setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
|
|
2791
|
+
VelocityStrip,
|
|
2792
|
+
{
|
|
2793
|
+
velocities,
|
|
2794
|
+
zones: velocityZones,
|
|
2795
|
+
variant: "mini",
|
|
2796
|
+
testID: `exercise-card-velocity-strip-${i}`
|
|
2797
|
+
},
|
|
2798
|
+
i
|
|
2799
|
+
)),
|
|
2800
|
+
Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
|
|
2801
|
+
PlaceholderStrip,
|
|
2802
|
+
{
|
|
2803
|
+
testID: `exercise-card-placeholder-${i}`
|
|
2804
|
+
},
|
|
2805
|
+
`placeholder-${i}`
|
|
2806
|
+
))
|
|
2807
|
+
] })
|
|
2170
2808
|
]
|
|
2171
2809
|
}
|
|
2172
2810
|
)
|
|
2173
2811
|
}
|
|
2174
2812
|
);
|
|
2175
2813
|
}
|
|
2814
|
+
var DARK2 = getSemanticColors("dark");
|
|
2815
|
+
var BODY_DIVIDER = DARK2["border-subtle"];
|
|
2816
|
+
function deriveHeaderSetStates(sets) {
|
|
2817
|
+
return sets.map((set) => {
|
|
2818
|
+
switch (set.state) {
|
|
2819
|
+
case "live":
|
|
2820
|
+
return { status: "active", velocities: set.velocities, planned: set.target.reps };
|
|
2821
|
+
case "done":
|
|
2822
|
+
return { status: "done", velocities: set.velocities };
|
|
2823
|
+
case "todo":
|
|
2824
|
+
return { status: "todo", planned: set.planned ?? set.target.reps };
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
}
|
|
2176
2828
|
function ExpandedCard({
|
|
2177
2829
|
name,
|
|
2178
2830
|
onToggle,
|
|
2179
2831
|
summary,
|
|
2180
|
-
e1rm,
|
|
2181
2832
|
isPR,
|
|
2182
2833
|
sets,
|
|
2183
2834
|
tempo,
|
|
2835
|
+
indicator,
|
|
2836
|
+
setStates,
|
|
2837
|
+
stripHeight = 8,
|
|
2838
|
+
velocityZones,
|
|
2184
2839
|
supersetPosition
|
|
2185
2840
|
}) {
|
|
2186
2841
|
const borderRadius = getSupersetBorderRadius(supersetPosition);
|
|
2187
2842
|
const supersetMargin = getSupersetMargin(supersetPosition);
|
|
2188
2843
|
const unit = summary?.unit ?? sets?.[0]?.unit ?? "lbs";
|
|
2189
|
-
const
|
|
2844
|
+
const headerStates = setStates ?? (sets ? deriveHeaderSetStates(sets) : []);
|
|
2845
|
+
const headerIndicator = indicator ?? (isPR ? "pr" : void 0);
|
|
2190
2846
|
return /* @__PURE__ */ jsxs(
|
|
2191
2847
|
reactNative.View,
|
|
2192
2848
|
{
|
|
@@ -2199,114 +2855,32 @@ function ExpandedCard({
|
|
|
2199
2855
|
testID: "exercise-card",
|
|
2200
2856
|
children: [
|
|
2201
2857
|
/* @__PURE__ */ jsx(
|
|
2202
|
-
|
|
2858
|
+
ExerciseCardHeading,
|
|
2203
2859
|
{
|
|
2860
|
+
name,
|
|
2861
|
+
sets: summary?.sets ?? sets?.length ?? 0,
|
|
2862
|
+
reps: summary?.reps ?? 0,
|
|
2863
|
+
load: summary?.weight ?? 0,
|
|
2864
|
+
unit,
|
|
2865
|
+
tempo,
|
|
2866
|
+
indicator: headerIndicator,
|
|
2867
|
+
setStates: headerStates,
|
|
2868
|
+
stripHeight,
|
|
2204
2869
|
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
|
-
)
|
|
2870
|
+
testID: "exercise-card-heading"
|
|
2262
2871
|
}
|
|
2263
2872
|
),
|
|
2264
|
-
|
|
2265
|
-
/* @__PURE__ */ jsx(
|
|
2873
|
+
/* @__PURE__ */ jsxs(
|
|
2266
2874
|
reactNative.View,
|
|
2267
2875
|
{
|
|
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
|
-
})
|
|
2876
|
+
style: { borderTopWidth: 1, borderTopColor: BODY_DIVIDER, paddingBottom: 6 },
|
|
2877
|
+
testID: "exercise-card-body",
|
|
2878
|
+
children: [
|
|
2879
|
+
/* @__PURE__ */ jsx(SetTableHeader, { unit, showPrevious: false, testID: "exercise-card-column-headers" }),
|
|
2880
|
+
sets && /* @__PURE__ */ jsx(reactNative.View, { testID: "exercise-card-sets", children: sets.map((set, i) => /* @__PURE__ */ jsx(SetRow, { ...set, velocityZones: set.velocityZones ?? velocityZones }, i)) })
|
|
2881
|
+
]
|
|
2307
2882
|
}
|
|
2308
|
-
)
|
|
2309
|
-
sets && /* @__PURE__ */ jsx(reactNative.View, { testID: "exercise-card-sets", children: sets.map((setProps, i) => /* @__PURE__ */ jsx(SetRow, { ...setProps }, i)) })
|
|
2883
|
+
)
|
|
2310
2884
|
]
|
|
2311
2885
|
}
|
|
2312
2886
|
);
|
|
@@ -2315,11 +2889,12 @@ function UpcomingCard({
|
|
|
2315
2889
|
name,
|
|
2316
2890
|
prescription,
|
|
2317
2891
|
previousBest,
|
|
2318
|
-
supersetPosition
|
|
2892
|
+
supersetPosition,
|
|
2893
|
+
onToggle
|
|
2319
2894
|
}) {
|
|
2320
2895
|
const borderRadius = getSupersetBorderRadius(supersetPosition);
|
|
2321
2896
|
const supersetMargin = getSupersetMargin(supersetPosition);
|
|
2322
|
-
return /* @__PURE__ */ jsx(
|
|
2897
|
+
return /* @__PURE__ */ jsx(reactNative.Pressable, { onPress: onToggle, accessibilityRole: "button", children: /* @__PURE__ */ jsx(
|
|
2323
2898
|
reactNative.View,
|
|
2324
2899
|
{
|
|
2325
2900
|
style: {
|
|
@@ -2329,7 +2904,7 @@ function UpcomingCard({
|
|
|
2329
2904
|
...borderRadius,
|
|
2330
2905
|
...supersetMargin
|
|
2331
2906
|
},
|
|
2332
|
-
accessibilityLabel:
|
|
2907
|
+
accessibilityLabel: formatAccessibilityLabel(name, void 0, prescription),
|
|
2333
2908
|
testID: "exercise-card",
|
|
2334
2909
|
children: /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", children: [
|
|
2335
2910
|
/* @__PURE__ */ jsx(
|
|
@@ -2337,6 +2912,7 @@ function UpcomingCard({
|
|
|
2337
2912
|
{
|
|
2338
2913
|
className: "text-text-primary",
|
|
2339
2914
|
style: {
|
|
2915
|
+
flexShrink: 0,
|
|
2340
2916
|
fontSize: 14,
|
|
2341
2917
|
fontFamily: '"Space Grotesk", sans-serif',
|
|
2342
2918
|
fontWeight: "700"
|
|
@@ -2349,7 +2925,9 @@ function UpcomingCard({
|
|
|
2349
2925
|
reactNative.Text,
|
|
2350
2926
|
{
|
|
2351
2927
|
className: "text-text-secondary",
|
|
2928
|
+
numberOfLines: 1,
|
|
2352
2929
|
style: {
|
|
2930
|
+
flexShrink: 1,
|
|
2353
2931
|
fontSize: 12,
|
|
2354
2932
|
fontFamily: "Inter, sans-serif",
|
|
2355
2933
|
marginLeft: 8
|
|
@@ -2358,12 +2936,14 @@ function UpcomingCard({
|
|
|
2358
2936
|
children: prescription
|
|
2359
2937
|
}
|
|
2360
2938
|
),
|
|
2361
|
-
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
|
|
2939
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", style: { minWidth: 8 } }),
|
|
2362
2940
|
previousBest && /* @__PURE__ */ jsx(
|
|
2363
2941
|
reactNative.Text,
|
|
2364
2942
|
{
|
|
2365
2943
|
className: "text-text-tertiary",
|
|
2944
|
+
numberOfLines: 1,
|
|
2366
2945
|
style: {
|
|
2946
|
+
flexShrink: 1,
|
|
2367
2947
|
fontSize: 11,
|
|
2368
2948
|
fontFamily: "Inter, sans-serif"
|
|
2369
2949
|
},
|
|
@@ -2373,18 +2953,31 @@ function UpcomingCard({
|
|
|
2373
2953
|
)
|
|
2374
2954
|
] })
|
|
2375
2955
|
}
|
|
2376
|
-
);
|
|
2956
|
+
) });
|
|
2377
2957
|
}
|
|
2378
|
-
function ExerciseCard(
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2958
|
+
function ExerciseCard({
|
|
2959
|
+
upcoming,
|
|
2960
|
+
expanded,
|
|
2961
|
+
defaultExpanded,
|
|
2962
|
+
onExpandedChange,
|
|
2963
|
+
...rest
|
|
2964
|
+
}) {
|
|
2965
|
+
const [internalExpanded, setInternalExpanded] = react.useState(defaultExpanded ?? false);
|
|
2966
|
+
const isControlled = expanded !== void 0;
|
|
2967
|
+
const isExpanded = isControlled ? expanded : internalExpanded;
|
|
2968
|
+
const onToggle = () => {
|
|
2969
|
+
const next = !isExpanded;
|
|
2970
|
+
onExpandedChange?.(next);
|
|
2971
|
+
if (!isControlled) setInternalExpanded(next);
|
|
2972
|
+
};
|
|
2973
|
+
if (upcoming) return /* @__PURE__ */ jsx(UpcomingCard, { ...rest, onToggle });
|
|
2974
|
+
return isExpanded ? /* @__PURE__ */ jsx(ExpandedCard, { ...rest, onToggle }) : /* @__PURE__ */ jsx(CollapsedCard, { ...rest, onToggle });
|
|
2975
|
+
}
|
|
2976
|
+
function resolveColor(token, mode = "dark") {
|
|
2977
|
+
return reactNative.Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
|
|
2387
2978
|
}
|
|
2979
|
+
|
|
2980
|
+
// src/components/custom/Workout/InputBar.tsx
|
|
2388
2981
|
var BRAND_PRIMARY2 = getSemanticColors("dark")["brand-primary"];
|
|
2389
2982
|
var INPUT_CLASSNAME = "bg-surface-raised border-border-strong text-text-primary";
|
|
2390
2983
|
var inputStyle = {
|
|
@@ -2541,7 +3134,307 @@ function InputBar({
|
|
|
2541
3134
|
}
|
|
2542
3135
|
);
|
|
2543
3136
|
}
|
|
3137
|
+
function formatDuration(ms) {
|
|
3138
|
+
const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
|
|
3139
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
3140
|
+
const seconds = totalSeconds % 60;
|
|
3141
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
3142
|
+
}
|
|
3143
|
+
function useTimer(options = {}) {
|
|
3144
|
+
const {
|
|
3145
|
+
mode = "up",
|
|
3146
|
+
durationMs,
|
|
3147
|
+
elapsedMs: controlledElapsedMs,
|
|
3148
|
+
running = false,
|
|
3149
|
+
autoTick = false,
|
|
3150
|
+
tickMs = 1e3
|
|
3151
|
+
} = options;
|
|
3152
|
+
const isControlled = controlledElapsedMs != null;
|
|
3153
|
+
const [internalElapsedMs, setInternalElapsedMs] = react.useState(0);
|
|
3154
|
+
react.useEffect(() => {
|
|
3155
|
+
if (isControlled || !autoTick || !running) return;
|
|
3156
|
+
const id = setInterval(() => {
|
|
3157
|
+
setInternalElapsedMs((prev) => prev + tickMs);
|
|
3158
|
+
}, tickMs);
|
|
3159
|
+
return () => clearInterval(id);
|
|
3160
|
+
}, [isControlled, autoTick, running, tickMs]);
|
|
3161
|
+
const elapsedMs = isControlled ? controlledElapsedMs : internalElapsedMs;
|
|
3162
|
+
const hasDuration = durationMs != null && durationMs > 0;
|
|
3163
|
+
const remainingMs = durationMs != null ? Math.max(0, durationMs - elapsedMs) : 0;
|
|
3164
|
+
let progress;
|
|
3165
|
+
if (hasDuration) {
|
|
3166
|
+
progress = Math.min(1, Math.max(0, elapsedMs / durationMs));
|
|
3167
|
+
} else {
|
|
3168
|
+
progress = mode === "down" ? 1 : 0;
|
|
3169
|
+
}
|
|
3170
|
+
const label = formatDuration(mode === "down" ? remainingMs : elapsedMs);
|
|
3171
|
+
const done = durationMs != null && elapsedMs >= durationMs;
|
|
3172
|
+
return { elapsedMs, remainingMs, progress, label, done };
|
|
3173
|
+
}
|
|
3174
|
+
var colorVarMap = {
|
|
3175
|
+
primary: "var(--color-brand-primary)",
|
|
3176
|
+
secondary: "var(--color-brand-secondary)",
|
|
3177
|
+
success: "var(--color-status-success)",
|
|
3178
|
+
error: "var(--color-status-error)",
|
|
3179
|
+
warning: "var(--color-status-warning)",
|
|
3180
|
+
info: "var(--color-status-info)"
|
|
3181
|
+
};
|
|
3182
|
+
function CircularProgress({
|
|
3183
|
+
value = 0,
|
|
3184
|
+
max = 100,
|
|
3185
|
+
isIndeterminate = false,
|
|
3186
|
+
size = 48,
|
|
3187
|
+
strokeWidth = 4,
|
|
3188
|
+
color = "primary",
|
|
3189
|
+
showValue = false,
|
|
3190
|
+
formatValue: formatValue2 = (v, m) => `${Math.round(v / m * 100)}%`,
|
|
3191
|
+
children,
|
|
3192
|
+
fill = false,
|
|
3193
|
+
className,
|
|
3194
|
+
...props
|
|
3195
|
+
}) {
|
|
3196
|
+
const percentage = Math.min(Math.max(value / max * 100, 0), 100);
|
|
3197
|
+
const radius = (size - strokeWidth) / 2;
|
|
3198
|
+
const circumference = 2 * Math.PI * radius;
|
|
3199
|
+
const strokeDashoffset = circumference - percentage / 100 * circumference;
|
|
3200
|
+
const center = size / 2;
|
|
3201
|
+
const boxStyle = fill ? { width: "100%", aspectRatio: 1 } : { width: size, height: size };
|
|
3202
|
+
return /* @__PURE__ */ jsxs(
|
|
3203
|
+
reactNative.View,
|
|
3204
|
+
{
|
|
3205
|
+
className: cn("items-center justify-center", className),
|
|
3206
|
+
style: boxStyle,
|
|
3207
|
+
accessibilityRole: "progressbar",
|
|
3208
|
+
accessibilityValue: {
|
|
3209
|
+
min: 0,
|
|
3210
|
+
max,
|
|
3211
|
+
now: isIndeterminate ? void 0 : value
|
|
3212
|
+
},
|
|
3213
|
+
...props,
|
|
3214
|
+
children: [
|
|
3215
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: cn(isIndeterminate && "animate-spin"), style: boxStyle, children: /* @__PURE__ */ jsxs(
|
|
3216
|
+
"svg",
|
|
3217
|
+
{
|
|
3218
|
+
width: "100%",
|
|
3219
|
+
height: "100%",
|
|
3220
|
+
viewBox: `0 0 ${size} ${size}`,
|
|
3221
|
+
style: { position: "absolute" },
|
|
3222
|
+
children: [
|
|
3223
|
+
/* @__PURE__ */ jsx(
|
|
3224
|
+
"circle",
|
|
3225
|
+
{
|
|
3226
|
+
cx: center,
|
|
3227
|
+
cy: center,
|
|
3228
|
+
r: radius,
|
|
3229
|
+
fill: "none",
|
|
3230
|
+
stroke: "var(--color-border-default)",
|
|
3231
|
+
strokeWidth
|
|
3232
|
+
}
|
|
3233
|
+
),
|
|
3234
|
+
/* @__PURE__ */ jsx(
|
|
3235
|
+
"circle",
|
|
3236
|
+
{
|
|
3237
|
+
cx: center,
|
|
3238
|
+
cy: center,
|
|
3239
|
+
r: radius,
|
|
3240
|
+
fill: "none",
|
|
3241
|
+
stroke: colorVarMap[color],
|
|
3242
|
+
strokeWidth,
|
|
3243
|
+
strokeLinecap: "round",
|
|
3244
|
+
strokeDasharray: circumference,
|
|
3245
|
+
strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
|
|
3246
|
+
transform: `rotate(-90 ${center} ${center})`,
|
|
3247
|
+
style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
|
|
3248
|
+
}
|
|
3249
|
+
)
|
|
3250
|
+
]
|
|
3251
|
+
}
|
|
3252
|
+
) }),
|
|
3253
|
+
children != null ? /* @__PURE__ */ jsx(
|
|
3254
|
+
reactNative.View,
|
|
3255
|
+
{
|
|
3256
|
+
style: {
|
|
3257
|
+
position: "absolute",
|
|
3258
|
+
alignItems: "center",
|
|
3259
|
+
justifyContent: "center",
|
|
3260
|
+
pointerEvents: "none"
|
|
3261
|
+
},
|
|
3262
|
+
children
|
|
3263
|
+
}
|
|
3264
|
+
) : showValue && !isIndeterminate && /* @__PURE__ */ jsx(
|
|
3265
|
+
reactNative.Text,
|
|
3266
|
+
{
|
|
3267
|
+
className: "text-xs font-semibold text-text-primary absolute",
|
|
3268
|
+
style: { fontSize: Math.max(size * 0.22, 10) },
|
|
3269
|
+
children: formatValue2(value, max)
|
|
3270
|
+
}
|
|
3271
|
+
)
|
|
3272
|
+
]
|
|
3273
|
+
}
|
|
3274
|
+
);
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
// src/components/custom/CircularTimer/CircularTimer.tsx
|
|
3278
|
+
function CircularTimer({
|
|
3279
|
+
durationMs,
|
|
3280
|
+
elapsedMs,
|
|
3281
|
+
mode = "down",
|
|
3282
|
+
size = 160,
|
|
3283
|
+
strokeWidth = 8,
|
|
3284
|
+
fill = false,
|
|
3285
|
+
color = "primary",
|
|
3286
|
+
doneLabel,
|
|
3287
|
+
doneColor = "success",
|
|
3288
|
+
controls,
|
|
3289
|
+
accessibilityLabel: accessibilityLabel2,
|
|
3290
|
+
testID
|
|
3291
|
+
}) {
|
|
3292
|
+
const { remainingMs, progress, label, done } = useTimer({ mode, durationMs, elapsedMs });
|
|
3293
|
+
const isDone = done && mode === "down";
|
|
3294
|
+
const showDone = isDone && doneLabel != null;
|
|
3295
|
+
const remainingFrac = durationMs > 0 ? Math.max(0, Math.min(1, remainingMs / durationMs)) : 0;
|
|
3296
|
+
const arc = mode === "down" ? showDone ? 1 : remainingFrac : progress;
|
|
3297
|
+
const ringColor = showDone ? doneColor : color;
|
|
3298
|
+
const remainingSec = Math.ceil(Math.max(0, remainingMs) / 1e3);
|
|
3299
|
+
const a11y = accessibilityLabel2 ?? (isDone ? "Timer complete" : `${remainingSec} seconds remaining`);
|
|
3300
|
+
return /* @__PURE__ */ jsxs(
|
|
3301
|
+
reactNative.View,
|
|
3302
|
+
{
|
|
3303
|
+
style: { alignItems: "center", gap: 16 },
|
|
3304
|
+
accessibilityRole: "timer",
|
|
3305
|
+
accessibilityLabel: a11y,
|
|
3306
|
+
testID: testID ?? "circular-timer",
|
|
3307
|
+
children: [
|
|
3308
|
+
/* @__PURE__ */ jsx(
|
|
3309
|
+
CircularProgress,
|
|
3310
|
+
{
|
|
3311
|
+
value: arc,
|
|
3312
|
+
max: 1,
|
|
3313
|
+
size,
|
|
3314
|
+
strokeWidth,
|
|
3315
|
+
fill,
|
|
3316
|
+
color: ringColor,
|
|
3317
|
+
accessibilityLabel: "Timer ring",
|
|
3318
|
+
testID: "circular-timer-ring",
|
|
3319
|
+
children: /* @__PURE__ */ jsx(
|
|
3320
|
+
reactNative.Text,
|
|
3321
|
+
{
|
|
3322
|
+
className: showDone ? void 0 : "text-text-primary",
|
|
3323
|
+
style: {
|
|
3324
|
+
fontSize: Math.round(size * (showDone ? 0.28 : 0.24)),
|
|
3325
|
+
fontFamily: '"Space Grotesk", sans-serif',
|
|
3326
|
+
fontWeight: "800",
|
|
3327
|
+
fontVariant: ["tabular-nums"],
|
|
3328
|
+
letterSpacing: showDone ? 1 : -1,
|
|
3329
|
+
...showDone ? { color: colorVarMap[doneColor] } : null
|
|
3330
|
+
},
|
|
3331
|
+
testID: "circular-timer-label",
|
|
3332
|
+
children: showDone ? doneLabel : label
|
|
3333
|
+
}
|
|
3334
|
+
)
|
|
3335
|
+
}
|
|
3336
|
+
),
|
|
3337
|
+
controls
|
|
3338
|
+
]
|
|
3339
|
+
}
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
// src/components/custom/Workout/RestTimer.tsx
|
|
2544
3344
|
var BRAND_PRIMARY3 = getSemanticColors("dark")["brand-primary"];
|
|
3345
|
+
var RING_DEFAULT_SIZE = 180;
|
|
3346
|
+
function RestActions({ onAddTime, onSkip }) {
|
|
3347
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
|
|
3348
|
+
/* @__PURE__ */ jsx(
|
|
3349
|
+
reactNative.Pressable,
|
|
3350
|
+
{
|
|
3351
|
+
onPress: onAddTime,
|
|
3352
|
+
style: {
|
|
3353
|
+
backgroundColor: "rgba(255,255,255,0.06)",
|
|
3354
|
+
paddingVertical: 8,
|
|
3355
|
+
paddingHorizontal: 20,
|
|
3356
|
+
borderRadius: 8
|
|
3357
|
+
},
|
|
3358
|
+
accessibilityRole: "button",
|
|
3359
|
+
accessibilityLabel: "Add 30 seconds",
|
|
3360
|
+
testID: "rest-timer-add-time",
|
|
3361
|
+
children: /* @__PURE__ */ jsx(
|
|
3362
|
+
reactNative.Text,
|
|
3363
|
+
{
|
|
3364
|
+
className: "text-text-secondary",
|
|
3365
|
+
style: { fontSize: 11, fontFamily: "Inter, sans-serif", fontWeight: "600" },
|
|
3366
|
+
children: "+30s"
|
|
3367
|
+
}
|
|
3368
|
+
)
|
|
3369
|
+
}
|
|
3370
|
+
),
|
|
3371
|
+
/* @__PURE__ */ jsx(
|
|
3372
|
+
reactNative.Pressable,
|
|
3373
|
+
{
|
|
3374
|
+
onPress: onSkip,
|
|
3375
|
+
style: {
|
|
3376
|
+
backgroundColor: "rgba(255,121,0,0.12)",
|
|
3377
|
+
paddingVertical: 8,
|
|
3378
|
+
paddingHorizontal: 20,
|
|
3379
|
+
borderRadius: 8
|
|
3380
|
+
},
|
|
3381
|
+
accessibilityRole: "button",
|
|
3382
|
+
accessibilityLabel: "Skip rest",
|
|
3383
|
+
testID: "rest-timer-skip",
|
|
3384
|
+
children: /* @__PURE__ */ jsx(
|
|
3385
|
+
reactNative.Text,
|
|
3386
|
+
{
|
|
3387
|
+
style: {
|
|
3388
|
+
fontSize: 11,
|
|
3389
|
+
fontFamily: "Inter, sans-serif",
|
|
3390
|
+
fontWeight: "600",
|
|
3391
|
+
color: BRAND_PRIMARY3
|
|
3392
|
+
},
|
|
3393
|
+
children: "Skip"
|
|
3394
|
+
}
|
|
3395
|
+
)
|
|
3396
|
+
}
|
|
3397
|
+
)
|
|
3398
|
+
] });
|
|
3399
|
+
}
|
|
3400
|
+
function RestTimerRing({
|
|
3401
|
+
totalSeconds,
|
|
3402
|
+
elapsedMs,
|
|
3403
|
+
remainingSec,
|
|
3404
|
+
done,
|
|
3405
|
+
size,
|
|
3406
|
+
nextSetInfo,
|
|
3407
|
+
displayOnly,
|
|
3408
|
+
onSkip,
|
|
3409
|
+
onAddTime
|
|
3410
|
+
}) {
|
|
3411
|
+
const a11yLabel = done ? "Rest complete, next set ready" : `Rest timer, ${Math.max(0, remainingSec)} seconds remaining`;
|
|
3412
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: { alignItems: "center", gap: 16 }, testID: "rest-timer", children: [
|
|
3413
|
+
/* @__PURE__ */ jsx(
|
|
3414
|
+
CircularTimer,
|
|
3415
|
+
{
|
|
3416
|
+
durationMs: totalSeconds * 1e3,
|
|
3417
|
+
elapsedMs,
|
|
3418
|
+
mode: "down",
|
|
3419
|
+
size,
|
|
3420
|
+
doneLabel: "GO",
|
|
3421
|
+
doneColor: "success",
|
|
3422
|
+
accessibilityLabel: a11yLabel,
|
|
3423
|
+
controls: !displayOnly ? /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip }) : void 0,
|
|
3424
|
+
testID: "rest-timer-ring"
|
|
3425
|
+
}
|
|
3426
|
+
),
|
|
3427
|
+
nextSetInfo != null && /* @__PURE__ */ jsx(
|
|
3428
|
+
reactNative.Text,
|
|
3429
|
+
{
|
|
3430
|
+
className: "text-text-tertiary",
|
|
3431
|
+
style: { fontSize: 13, fontFamily: "Inter, sans-serif" },
|
|
3432
|
+
testID: "rest-timer-next-set",
|
|
3433
|
+
children: nextSetInfo
|
|
3434
|
+
}
|
|
3435
|
+
)
|
|
3436
|
+
] });
|
|
3437
|
+
}
|
|
2545
3438
|
function RestTimer({
|
|
2546
3439
|
totalSeconds,
|
|
2547
3440
|
elapsedMs,
|
|
@@ -2549,16 +3442,39 @@ function RestTimer({
|
|
|
2549
3442
|
onAddTime,
|
|
2550
3443
|
nextSetInfo,
|
|
2551
3444
|
visible,
|
|
2552
|
-
displayOnly = false
|
|
3445
|
+
displayOnly = false,
|
|
3446
|
+
variant = "bar",
|
|
3447
|
+
size = RING_DEFAULT_SIZE
|
|
2553
3448
|
}) {
|
|
2554
|
-
|
|
2555
|
-
|
|
3449
|
+
const {
|
|
3450
|
+
remainingMs,
|
|
3451
|
+
progress,
|
|
3452
|
+
label: timeDisplay,
|
|
3453
|
+
done
|
|
3454
|
+
} = useTimer({
|
|
3455
|
+
mode: "down",
|
|
3456
|
+
durationMs: totalSeconds * 1e3,
|
|
3457
|
+
elapsedMs
|
|
3458
|
+
});
|
|
2556
3459
|
const remainingSec = Math.ceil(remainingMs / 1e3);
|
|
2557
|
-
const
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
3460
|
+
const progressPct = progress * 100;
|
|
3461
|
+
if (!visible) return null;
|
|
3462
|
+
if (variant === "ring") {
|
|
3463
|
+
return /* @__PURE__ */ jsx(
|
|
3464
|
+
RestTimerRing,
|
|
3465
|
+
{
|
|
3466
|
+
totalSeconds,
|
|
3467
|
+
elapsedMs,
|
|
3468
|
+
remainingSec,
|
|
3469
|
+
done,
|
|
3470
|
+
size,
|
|
3471
|
+
nextSetInfo,
|
|
3472
|
+
displayOnly,
|
|
3473
|
+
onSkip,
|
|
3474
|
+
onAddTime
|
|
3475
|
+
}
|
|
3476
|
+
);
|
|
3477
|
+
}
|
|
2562
3478
|
return /* @__PURE__ */ jsxs(
|
|
2563
3479
|
reactNative.View,
|
|
2564
3480
|
{
|
|
@@ -2656,62 +3572,7 @@ function RestTimer({
|
|
|
2656
3572
|
)
|
|
2657
3573
|
}
|
|
2658
3574
|
),
|
|
2659
|
-
!displayOnly && /* @__PURE__ */
|
|
2660
|
-
/* @__PURE__ */ jsx(
|
|
2661
|
-
reactNative.Pressable,
|
|
2662
|
-
{
|
|
2663
|
-
onPress: onAddTime,
|
|
2664
|
-
style: {
|
|
2665
|
-
backgroundColor: "rgba(255,255,255,0.06)",
|
|
2666
|
-
paddingVertical: 8,
|
|
2667
|
-
paddingHorizontal: 20,
|
|
2668
|
-
borderRadius: 8
|
|
2669
|
-
},
|
|
2670
|
-
accessibilityRole: "button",
|
|
2671
|
-
accessibilityLabel: "Add 30 seconds",
|
|
2672
|
-
testID: "rest-timer-add-time",
|
|
2673
|
-
children: /* @__PURE__ */ jsx(
|
|
2674
|
-
reactNative.Text,
|
|
2675
|
-
{
|
|
2676
|
-
className: "text-text-secondary",
|
|
2677
|
-
style: {
|
|
2678
|
-
fontSize: 11,
|
|
2679
|
-
fontFamily: "Inter, sans-serif",
|
|
2680
|
-
fontWeight: "600"
|
|
2681
|
-
},
|
|
2682
|
-
children: "+30s"
|
|
2683
|
-
}
|
|
2684
|
-
)
|
|
2685
|
-
}
|
|
2686
|
-
),
|
|
2687
|
-
/* @__PURE__ */ jsx(
|
|
2688
|
-
reactNative.Pressable,
|
|
2689
|
-
{
|
|
2690
|
-
onPress: onSkip,
|
|
2691
|
-
style: {
|
|
2692
|
-
backgroundColor: "rgba(255,121,0,0.12)",
|
|
2693
|
-
paddingVertical: 8,
|
|
2694
|
-
paddingHorizontal: 20,
|
|
2695
|
-
borderRadius: 8
|
|
2696
|
-
},
|
|
2697
|
-
accessibilityRole: "button",
|
|
2698
|
-
accessibilityLabel: "Skip rest",
|
|
2699
|
-
testID: "rest-timer-skip",
|
|
2700
|
-
children: /* @__PURE__ */ jsx(
|
|
2701
|
-
reactNative.Text,
|
|
2702
|
-
{
|
|
2703
|
-
style: {
|
|
2704
|
-
fontSize: 11,
|
|
2705
|
-
fontFamily: "Inter, sans-serif",
|
|
2706
|
-
fontWeight: "600",
|
|
2707
|
-
color: BRAND_PRIMARY3
|
|
2708
|
-
},
|
|
2709
|
-
children: "Skip"
|
|
2710
|
-
}
|
|
2711
|
-
)
|
|
2712
|
-
}
|
|
2713
|
-
)
|
|
2714
|
-
] })
|
|
3575
|
+
!displayOnly && /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip })
|
|
2715
3576
|
]
|
|
2716
3577
|
}
|
|
2717
3578
|
);
|
|
@@ -2797,23 +3658,22 @@ function deriveWorkoutProgress(exercises) {
|
|
|
2797
3658
|
function findActiveExercise(exercises) {
|
|
2798
3659
|
return exercises.find((exercise) => exercise.status === "active") ?? null;
|
|
2799
3660
|
}
|
|
2800
|
-
function
|
|
2801
|
-
|
|
2802
|
-
return focused ? "expanded" : "collapsed";
|
|
3661
|
+
function isUpcomingExercise(status) {
|
|
3662
|
+
return status === "upcoming";
|
|
2803
3663
|
}
|
|
2804
3664
|
function toActiveCardProps(exercise, focused, onToggle, supersetPosition = null, supersetColor) {
|
|
2805
|
-
const
|
|
3665
|
+
const upcoming = isUpcomingExercise(exercise.status);
|
|
2806
3666
|
return {
|
|
2807
3667
|
name: exercise.name,
|
|
2808
|
-
|
|
2809
|
-
|
|
3668
|
+
upcoming,
|
|
3669
|
+
expanded: focused,
|
|
3670
|
+
onExpandedChange: onToggle,
|
|
2810
3671
|
summary: exercise.summary,
|
|
2811
|
-
e1rm: exercise.e1rm,
|
|
2812
3672
|
isPR: exercise.isPR,
|
|
2813
3673
|
tempo: exercise.tempo,
|
|
2814
3674
|
setVelocities: exercise.setVelocities,
|
|
2815
3675
|
totalPlannedSets: exercise.totalPlannedSets,
|
|
2816
|
-
sets:
|
|
3676
|
+
sets: focused ? exercise.sets : void 0,
|
|
2817
3677
|
prescription: exercise.prescription,
|
|
2818
3678
|
previousBest: exercise.previousBest,
|
|
2819
3679
|
supersetPosition,
|
|
@@ -3502,11 +4362,11 @@ function CardContent({ children, className }) {
|
|
|
3502
4362
|
function CardFooter({ children, className }) {
|
|
3503
4363
|
return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
|
|
3504
4364
|
}
|
|
3505
|
-
var
|
|
4365
|
+
var t5 = getSemanticColors("dark");
|
|
3506
4366
|
var solidVariantColors = {
|
|
3507
|
-
success:
|
|
3508
|
-
warning:
|
|
3509
|
-
error:
|
|
4367
|
+
success: t5["status-success"],
|
|
4368
|
+
warning: t5["status-warning"],
|
|
4369
|
+
error: t5["status-error"],
|
|
3510
4370
|
neutral: "#6B7280"
|
|
3511
4371
|
};
|
|
3512
4372
|
var ringVariantStyles = {
|
|
@@ -3546,8 +4406,8 @@ var iconColors = {
|
|
|
3546
4406
|
warning: "#6B4000",
|
|
3547
4407
|
error: "#5C1A1A",
|
|
3548
4408
|
neutral: "#D1D5DB",
|
|
3549
|
-
"on-track":
|
|
3550
|
-
deviation:
|
|
4409
|
+
"on-track": t5["status-success"],
|
|
4410
|
+
deviation: t5["status-warning"],
|
|
3551
4411
|
future: "#6B7280"
|
|
3552
4412
|
};
|
|
3553
4413
|
var sizeDimensions = {
|
|
@@ -3633,13 +4493,13 @@ var MESO_ACCENT_GRADIENT_LIGHT = primitiveRamps.orange[300];
|
|
|
3633
4493
|
var WORKOUT_PILL_DELOAD = primitiveRamps.magenta[600];
|
|
3634
4494
|
|
|
3635
4495
|
// src/components/custom/Workout/MesoStatusCard.tsx
|
|
3636
|
-
var
|
|
3637
|
-
var BRAND_PRIMARY5 =
|
|
4496
|
+
var t6 = getSemanticColors("dark");
|
|
4497
|
+
var BRAND_PRIMARY5 = t6["brand-primary"];
|
|
3638
4498
|
var BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK;
|
|
3639
4499
|
var BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT;
|
|
3640
|
-
var SUCCESS =
|
|
3641
|
-
var WARNING =
|
|
3642
|
-
var ERROR =
|
|
4500
|
+
var SUCCESS = t6["status-success"];
|
|
4501
|
+
var WARNING = t6["status-warning"];
|
|
4502
|
+
var ERROR = t6["status-error"];
|
|
3643
4503
|
var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY5, BRAND_PRIMARY_LIGHT];
|
|
3644
4504
|
var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
|
|
3645
4505
|
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 +4571,7 @@ function StatusPill({ badge }) {
|
|
|
3711
4571
|
}
|
|
3712
4572
|
);
|
|
3713
4573
|
}
|
|
3714
|
-
function
|
|
4574
|
+
function MetricCell2({ metric }) {
|
|
3715
4575
|
return /* @__PURE__ */ jsxs(reactNative.View, { style: { width: "48%" }, testID: "meso-status-card-metric", children: [
|
|
3716
4576
|
/* @__PURE__ */ jsx(
|
|
3717
4577
|
reactNative.Text,
|
|
@@ -3929,7 +4789,7 @@ function MesoStatusCard({
|
|
|
3929
4789
|
className: "flex-row flex-wrap",
|
|
3930
4790
|
style: { gap: 8 },
|
|
3931
4791
|
testID: "meso-status-card-metrics",
|
|
3932
|
-
children: metrics.map((metric, index) => /* @__PURE__ */ jsx(
|
|
4792
|
+
children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell2, { metric }, `${metric.label}-${index}`))
|
|
3933
4793
|
}
|
|
3934
4794
|
),
|
|
3935
4795
|
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 +4870,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
|
|
|
4010
4870
|
var PLOT_LEFT = 26;
|
|
4011
4871
|
var TOOLTIP_WIDTH = 124;
|
|
4012
4872
|
function timeOf(dateStr) {
|
|
4013
|
-
const
|
|
4014
|
-
return Number.isNaN(
|
|
4873
|
+
const t12 = Date.parse(dateStr);
|
|
4874
|
+
return Number.isNaN(t12) ? 0 : t12;
|
|
4015
4875
|
}
|
|
4016
4876
|
function formatValue(value) {
|
|
4017
4877
|
return `${Math.round(value)}`;
|
|
@@ -4500,10 +5360,10 @@ function StrengthTrendChart({
|
|
|
4500
5360
|
}
|
|
4501
5361
|
);
|
|
4502
5362
|
}
|
|
4503
|
-
var
|
|
4504
|
-
var STATUS_SUCCESS2 =
|
|
4505
|
-
var STATUS_WARNING2 =
|
|
4506
|
-
var STATUS_INFO =
|
|
5363
|
+
var t7 = getSemanticColors("dark");
|
|
5364
|
+
var STATUS_SUCCESS2 = t7["status-success"];
|
|
5365
|
+
var STATUS_WARNING2 = t7["status-warning"];
|
|
5366
|
+
var STATUS_INFO = t7["status-info"];
|
|
4507
5367
|
var DOT_BORDER = "#F3F4F6";
|
|
4508
5368
|
var BAND_FILL = "rgba(46,213,115,0.1)";
|
|
4509
5369
|
var BAND_EDGE = "rgba(46,213,115,0.45)";
|
|
@@ -4546,13 +5406,13 @@ function interpEdge(pixels, x, key) {
|
|
|
4546
5406
|
const a = pixels[i];
|
|
4547
5407
|
const b = pixels[i + 1];
|
|
4548
5408
|
if (x >= a.x && x <= b.x) {
|
|
4549
|
-
const
|
|
4550
|
-
return a[key] +
|
|
5409
|
+
const t12 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
|
|
5410
|
+
return a[key] + t12 * (b[key] - a[key]);
|
|
4551
5411
|
}
|
|
4552
5412
|
}
|
|
4553
5413
|
return pixels[pixels.length - 1][key];
|
|
4554
5414
|
}
|
|
4555
|
-
function
|
|
5415
|
+
function buildColumns2(pixels, step) {
|
|
4556
5416
|
if (pixels.length < 2) return [];
|
|
4557
5417
|
const start = pixels[0].x;
|
|
4558
5418
|
const end = pixels[pixels.length - 1].x;
|
|
@@ -4664,7 +5524,7 @@ function CapacityBandChart({
|
|
|
4664
5524
|
}
|
|
4665
5525
|
const { toX, toY } = scale;
|
|
4666
5526
|
const bandPixels = toPixels(band, toX, toY);
|
|
4667
|
-
const columns =
|
|
5527
|
+
const columns = buildColumns2(bandPixels, COLUMN_STEP);
|
|
4668
5528
|
const topEdge = buildEdges(bandPixels, "yHigh");
|
|
4669
5529
|
const bottomEdge = buildEdges(bandPixels, "yLow");
|
|
4670
5530
|
const lastBandPoint = band[band.length - 1];
|
|
@@ -4739,8 +5599,8 @@ function CapacityBandChart({
|
|
|
4739
5599
|
testID: "capacity-band-chart-reveal",
|
|
4740
5600
|
children: /* @__PURE__ */ jsxs(reactNative.View, { style: { width, height, position: "relative" }, children: [
|
|
4741
5601
|
projection && /* @__PURE__ */ jsxs(reactNative.View, { testID: "capacity-band-chart-projection", children: [
|
|
4742
|
-
renderColumns(
|
|
4743
|
-
renderColumns(
|
|
5602
|
+
renderColumns(buildColumns2(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
|
|
5603
|
+
renderColumns(buildColumns2(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
|
|
4744
5604
|
renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
|
|
4745
5605
|
renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
|
|
4746
5606
|
renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
|
|
@@ -4901,10 +5761,9 @@ function deriveExerciseStats(entries) {
|
|
|
4901
5761
|
function toExerciseCardProps(entry, expanded, onToggle) {
|
|
4902
5762
|
return {
|
|
4903
5763
|
name: entry.title,
|
|
4904
|
-
|
|
4905
|
-
onToggle,
|
|
5764
|
+
expanded,
|
|
5765
|
+
onExpandedChange: onToggle,
|
|
4906
5766
|
summary: entry.summary,
|
|
4907
|
-
e1rm: entry.e1rm,
|
|
4908
5767
|
isPR: entry.isPR,
|
|
4909
5768
|
tempo: entry.tempo,
|
|
4910
5769
|
sets: expanded ? entry.sets : void 0
|
|
@@ -5448,6 +6307,7 @@ var colorStyles = {
|
|
|
5448
6307
|
default: "bg-text-tertiary",
|
|
5449
6308
|
primary: "bg-brand-primary",
|
|
5450
6309
|
success: "bg-status-success",
|
|
6310
|
+
live: "bg-status-live",
|
|
5451
6311
|
error: "bg-status-error",
|
|
5452
6312
|
warning: "bg-status-warning",
|
|
5453
6313
|
info: "bg-status-info",
|
|
@@ -5504,7 +6364,7 @@ function Indicator({
|
|
|
5504
6364
|
colorClass,
|
|
5505
6365
|
pulseMode === "opacity" && "animate-pulse",
|
|
5506
6366
|
ring && "border-2 border-background-base",
|
|
5507
|
-
glow && !customColor && color === "success" && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
|
|
6367
|
+
glow && !customColor && (color === "success" || color === "live") && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
|
|
5508
6368
|
glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
|
|
5509
6369
|
glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
|
|
5510
6370
|
glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
|
|
@@ -5586,7 +6446,7 @@ function Badge({
|
|
|
5586
6446
|
}
|
|
5587
6447
|
);
|
|
5588
6448
|
}
|
|
5589
|
-
var
|
|
6449
|
+
var t8 = getSemanticColors("dark");
|
|
5590
6450
|
var statusBgColors = {
|
|
5591
6451
|
completed: "rgba(46,213,115,0.15)",
|
|
5592
6452
|
current: "rgba(255,121,0,0.15)",
|
|
@@ -5604,14 +6464,14 @@ var statusBorderStyles = {
|
|
|
5604
6464
|
deload: { borderWidth: 1, borderColor: "rgba(186,41,150,0.3)" }
|
|
5605
6465
|
};
|
|
5606
6466
|
var textColors = {
|
|
5607
|
-
completed:
|
|
5608
|
-
current:
|
|
5609
|
-
next:
|
|
6467
|
+
completed: t8["status-success"],
|
|
6468
|
+
current: t8["brand-primary"],
|
|
6469
|
+
next: t8["brand-primary"],
|
|
5610
6470
|
upcoming: "#6B7280",
|
|
5611
6471
|
missed: "rgba(209,67,67,0.7)",
|
|
5612
6472
|
deload: WORKOUT_PILL_DELOAD
|
|
5613
6473
|
};
|
|
5614
|
-
function
|
|
6474
|
+
function usePulse2(enabled) {
|
|
5615
6475
|
const [opacity] = react.useState(() => new reactNative.Animated.Value(1));
|
|
5616
6476
|
react.useEffect(() => {
|
|
5617
6477
|
if (!enabled) {
|
|
@@ -5649,7 +6509,7 @@ function WorkoutPill({
|
|
|
5649
6509
|
...props
|
|
5650
6510
|
}) {
|
|
5651
6511
|
const shouldPulse = pulse ?? status === "current";
|
|
5652
|
-
const pulseOpacity =
|
|
6512
|
+
const pulseOpacity = usePulse2(shouldPulse);
|
|
5653
6513
|
const isCompleted = status === "completed";
|
|
5654
6514
|
const isMissed = status === "missed";
|
|
5655
6515
|
const pill = /* @__PURE__ */ jsx(
|
|
@@ -5739,19 +6599,19 @@ function WorkoutPill({
|
|
|
5739
6599
|
}
|
|
5740
6600
|
return pill;
|
|
5741
6601
|
}
|
|
5742
|
-
var
|
|
6602
|
+
var t9 = getSemanticColors("dark");
|
|
5743
6603
|
var TRACK_BG = "#333333";
|
|
5744
6604
|
var LABEL_COLOR = "#6B7280";
|
|
5745
6605
|
var TARGET_LINE_COLOR = "rgba(33, 150, 243, 0.5)";
|
|
5746
6606
|
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
6607
|
var ZONE = {
|
|
5748
|
-
building:
|
|
6608
|
+
building: t9["status-success"],
|
|
5749
6609
|
// below target ramp-up
|
|
5750
|
-
approaching:
|
|
6610
|
+
approaching: t9["status-warning"],
|
|
5751
6611
|
// nearing target
|
|
5752
|
-
target:
|
|
6612
|
+
target: t9["brand-primary"],
|
|
5753
6613
|
// exactly at target
|
|
5754
|
-
over1:
|
|
6614
|
+
over1: t9["status-error"],
|
|
5755
6615
|
// mild over-target
|
|
5756
6616
|
over2: WORKOUT_TOKENS.intensity.over2,
|
|
5757
6617
|
// moderate over-target
|
|
@@ -6174,13 +7034,13 @@ function MesoCard({
|
|
|
6174
7034
|
}
|
|
6175
7035
|
) });
|
|
6176
7036
|
}
|
|
6177
|
-
var
|
|
7037
|
+
var t10 = getSemanticColors("dark");
|
|
6178
7038
|
var dotColorMap = {
|
|
6179
7039
|
untrained: "#2C2C2C",
|
|
6180
|
-
behind:
|
|
6181
|
-
ontrack:
|
|
6182
|
-
target:
|
|
6183
|
-
over:
|
|
7040
|
+
behind: t10["brand-secondary"],
|
|
7041
|
+
ontrack: t10["status-success"],
|
|
7042
|
+
target: t10["brand-primary"],
|
|
7043
|
+
over: t10["status-error"]
|
|
6184
7044
|
};
|
|
6185
7045
|
function MuscleGroupChip({
|
|
6186
7046
|
name,
|
|
@@ -6254,10 +7114,10 @@ function MuscleGroupChip({
|
|
|
6254
7114
|
}
|
|
6255
7115
|
|
|
6256
7116
|
// src/components/custom/Workout/WorkoutCard.tsx
|
|
6257
|
-
var
|
|
7117
|
+
var t11 = getSemanticColors("dark");
|
|
6258
7118
|
var COLORS = {
|
|
6259
|
-
statusSuccess:
|
|
6260
|
-
brandPrimary:
|
|
7119
|
+
statusSuccess: t11["status-success"],
|
|
7120
|
+
brandPrimary: t11["brand-primary"],
|
|
6261
7121
|
borderDefault: "#1F1F1F",
|
|
6262
7122
|
brandPrimarySubtle: "rgba(255,121,0,0.06)"
|
|
6263
7123
|
};
|
|
@@ -6541,8 +7401,8 @@ function WeekLevel({ week, onSelectWorkout }) {
|
|
|
6541
7401
|
function WorkoutLevel({ workout, expandedExercise, onToggleExercise }) {
|
|
6542
7402
|
const exercises = workout.exercises.map((exercise, index) => ({
|
|
6543
7403
|
...exercise,
|
|
6544
|
-
|
|
6545
|
-
|
|
7404
|
+
expanded: index === expandedExercise,
|
|
7405
|
+
onExpandedChange: () => onToggleExercise(index)
|
|
6546
7406
|
}));
|
|
6547
7407
|
return /* @__PURE__ */ jsx(reactNative.View, { testID: "program-planning-page-workout-level", children: /* @__PURE__ */ jsx(
|
|
6548
7408
|
WorkoutCard,
|
|
@@ -6685,7 +7545,7 @@ exports.findMeso = findMeso;
|
|
|
6685
7545
|
exports.findWeek = findWeek;
|
|
6686
7546
|
exports.findWorkout = findWorkout;
|
|
6687
7547
|
exports.groupExercises = groupExercises;
|
|
6688
|
-
exports.
|
|
7548
|
+
exports.isUpcomingExercise = isUpcomingExercise;
|
|
6689
7549
|
exports.summarizeVbt = summarizeVbt;
|
|
6690
7550
|
exports.toActiveCardProps = toActiveCardProps;
|
|
6691
7551
|
exports.toExerciseCardProps = toExerciseCardProps;
|