@titan-design/react-ui 0.10.0 → 0.11.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/index.d.mts +399 -4
- package/dist/index.d.ts +399 -4
- package/dist/index.js +2295 -895
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2279 -905
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
- package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1974 -1740
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1975 -1741
- package/dist/pages.mjs.map +1 -1
- package/package.json +8 -3
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
- package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
- package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
- package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
- package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
- package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
- package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
- package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
- package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
- package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
- package/src/components/custom/Fatigue/README.md +93 -0
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
- package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
- package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
- package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
- package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
- package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
- package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
- package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
- package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
- package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
- package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
- package/src/components/custom/Fatigue/index.ts +41 -0
- package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
- package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseCard.tsx +3 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +7 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +50 -13
- package/src/components/custom/Workout/SetRow.test.tsx +6 -6
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
- package/src/components/custom/Workout/VelocityStrip.test.tsx +196 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
- package/src/components/custom/charts/SetBarChart.tsx +651 -0
- package/src/components/custom/charts/live-rep-growth.ts +91 -0
- package/src/components/custom/index.ts +1 -0
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +871 -0
- package/src/theme/ColorPrimitives.stories.tsx +300 -0
- package/src/theme/bar-paper.ts +59 -0
- package/src/theme/color-stories.coverage.test.ts +130 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
- package/src/theme/ColorSystem.stories.tsx +0 -508
- package/src/theme/Colors.stories.tsx +0 -323
package/dist/pages.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo, useEffect, Fragment as Fragment$1, useSyncExternalStore, useCallback } from 'react';
|
|
1
|
+
import { createContext, useState, useMemo, useEffect, Fragment as Fragment$1, useSyncExternalStore, useContext, useCallback } from 'react';
|
|
2
2
|
import { Easing, Platform, View, Text, TextInput, Pressable, Animated, ScrollView } from 'react-native';
|
|
3
3
|
import { Fragment, jsx as jsx$1, jsxs as jsxs$1 } from 'react/jsx-runtime';
|
|
4
4
|
import { clsx } from 'clsx';
|
|
@@ -34,7 +34,6 @@ var primitiveColors = {
|
|
|
34
34
|
// Charcoal scale (dark backgrounds)
|
|
35
35
|
charcoal: {
|
|
36
36
|
0: "#6E6E6E",
|
|
37
|
-
100: "#4C4C4C",
|
|
38
37
|
200: "#3C3C3C",
|
|
39
38
|
300: "#2C2C2C",
|
|
40
39
|
400: "#1F1F1F",
|
|
@@ -180,1204 +179,6 @@ var WORKOUT_TOKENS = {
|
|
|
180
179
|
fill: "#3A3A3A"
|
|
181
180
|
}};
|
|
182
181
|
|
|
183
|
-
// src/utils/workout-format.ts
|
|
184
|
-
function roundRpe(rpe) {
|
|
185
|
-
return Math.round(rpe * 2) / 2;
|
|
186
|
-
}
|
|
187
|
-
function roundWeight(weight) {
|
|
188
|
-
return Math.round(weight);
|
|
189
|
-
}
|
|
190
|
-
function formatVelocity(velocity) {
|
|
191
|
-
return velocity.toFixed(2);
|
|
192
|
-
}
|
|
193
|
-
function roundTempo(tempo) {
|
|
194
|
-
return tempo.map((v) => Math.round(v * 10) / 10);
|
|
195
|
-
}
|
|
196
|
-
function wrapJsx(fn) {
|
|
197
|
-
return function(type, props, ...rest) {
|
|
198
|
-
if (props && typeof props.className === "string" && props.className) {
|
|
199
|
-
const cn2 = props.className;
|
|
200
|
-
const cssStyle = { $$css: true, [cn2]: cn2 };
|
|
201
|
-
const existing = props.style;
|
|
202
|
-
props = {
|
|
203
|
-
...props,
|
|
204
|
-
style: existing ? [cssStyle, existing] : cssStyle
|
|
205
|
-
};
|
|
206
|
-
delete props.className;
|
|
207
|
-
}
|
|
208
|
-
return fn(type, props, ...rest);
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
var jsx = wrapJsx(jsx$1);
|
|
212
|
-
var jsxs = wrapJsx(jsxs$1);
|
|
213
|
-
|
|
214
|
-
// src/components/custom/Workout/SegmentedBar.tsx
|
|
215
|
-
var PULSE_HALF_MS = 950;
|
|
216
|
-
var PULSE_MIN_OPACITY = 0.45;
|
|
217
|
-
var SEGMENTED_BAR_GAP = 5;
|
|
218
|
-
function usePulse(active) {
|
|
219
|
-
const [value] = useState(() => new Animated.Value(0));
|
|
220
|
-
useEffect(() => {
|
|
221
|
-
if (!active) return;
|
|
222
|
-
const loop = Animated.loop(
|
|
223
|
-
Animated.sequence([
|
|
224
|
-
Animated.timing(value, {
|
|
225
|
-
toValue: 1,
|
|
226
|
-
duration: PULSE_HALF_MS,
|
|
227
|
-
easing: Easing.inOut(Easing.ease),
|
|
228
|
-
useNativeDriver: false
|
|
229
|
-
}),
|
|
230
|
-
Animated.timing(value, {
|
|
231
|
-
toValue: 0,
|
|
232
|
-
duration: PULSE_HALF_MS,
|
|
233
|
-
easing: Easing.inOut(Easing.ease),
|
|
234
|
-
useNativeDriver: false
|
|
235
|
-
})
|
|
236
|
-
])
|
|
237
|
-
);
|
|
238
|
-
loop.start();
|
|
239
|
-
return () => loop.stop();
|
|
240
|
-
}, [active, value]);
|
|
241
|
-
return value;
|
|
242
|
-
}
|
|
243
|
-
function SegmentedBar({
|
|
244
|
-
segments,
|
|
245
|
-
height = 8,
|
|
246
|
-
gap = SEGMENTED_BAR_GAP,
|
|
247
|
-
radius = 2,
|
|
248
|
-
marker = null,
|
|
249
|
-
segmentTestID,
|
|
250
|
-
style,
|
|
251
|
-
...props
|
|
252
|
-
}) {
|
|
253
|
-
const pulse = usePulse(segments.some((s) => s.pulse));
|
|
254
|
-
return /* @__PURE__ */ jsxs(View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
|
|
255
|
-
segments.map((seg, i) => {
|
|
256
|
-
const fillStyle = {
|
|
257
|
-
width: `${(seg.fill ?? 1) * 100}%`,
|
|
258
|
-
height: "100%",
|
|
259
|
-
backgroundColor: seg.color,
|
|
260
|
-
opacity: seg.opacity
|
|
261
|
-
};
|
|
262
|
-
const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
|
|
263
|
-
return /* @__PURE__ */ jsx(
|
|
264
|
-
View,
|
|
265
|
-
{
|
|
266
|
-
style: {
|
|
267
|
-
flex: seg.weight ?? 1,
|
|
268
|
-
minWidth: 0,
|
|
269
|
-
height: "100%",
|
|
270
|
-
marginLeft: seg.leadingGap,
|
|
271
|
-
borderRadius: radius,
|
|
272
|
-
overflow: "hidden"
|
|
273
|
-
},
|
|
274
|
-
children: seg.pulse ? /* @__PURE__ */ jsx(
|
|
275
|
-
Animated.View,
|
|
276
|
-
{
|
|
277
|
-
style: {
|
|
278
|
-
...fillStyle,
|
|
279
|
-
...seg.pulseColor ? {
|
|
280
|
-
backgroundColor: pulse.interpolate({
|
|
281
|
-
inputRange: [0, 1],
|
|
282
|
-
outputRange: seg.pulseColor
|
|
283
|
-
})
|
|
284
|
-
} : {
|
|
285
|
-
opacity: pulse.interpolate({
|
|
286
|
-
inputRange: [0, 1],
|
|
287
|
-
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
288
|
-
})
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
accessibilityElementsHidden: true,
|
|
292
|
-
testID
|
|
293
|
-
}
|
|
294
|
-
) : /* @__PURE__ */ jsx(View, { style: fillStyle, accessibilityElementsHidden: true, testID })
|
|
295
|
-
},
|
|
296
|
-
i
|
|
297
|
-
);
|
|
298
|
-
}),
|
|
299
|
-
marker ? /* @__PURE__ */ jsx(
|
|
300
|
-
View,
|
|
301
|
-
{
|
|
302
|
-
style: {
|
|
303
|
-
position: "absolute",
|
|
304
|
-
left: `${marker.position * 100}%`,
|
|
305
|
-
top: 0,
|
|
306
|
-
bottom: 0,
|
|
307
|
-
width: 2,
|
|
308
|
-
backgroundColor: marker.color
|
|
309
|
-
},
|
|
310
|
-
accessibilityElementsHidden: true,
|
|
311
|
-
testID: "segmented-bar-marker"
|
|
312
|
-
}
|
|
313
|
-
) : null
|
|
314
|
-
] });
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// src/components/custom/Workout/SetBar.tsx
|
|
318
|
-
var SET_STRIP_ZONES = {
|
|
319
|
-
slow: primitiveRamps.red[600],
|
|
320
|
-
moderate: primitiveRamps.orange[400],
|
|
321
|
-
fast: primitiveRamps.amber[300],
|
|
322
|
-
fastest: primitiveRamps.green[300]
|
|
323
|
-
};
|
|
324
|
-
var TODO_COLOR = primitiveColors.charcoal[300];
|
|
325
|
-
var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
|
|
326
|
-
var NOTCH_GAP = 2;
|
|
327
|
-
var CLUSTER_GAP = 3;
|
|
328
|
-
var MYO_UPCOMING_CLUSTERS = 3;
|
|
329
|
-
var MYO_UPCOMING_CLUSTER_LEN = 5;
|
|
330
|
-
var MYO_UPCOMING_FADE = 0.6;
|
|
331
|
-
var PULSE_RANGE = {
|
|
332
|
-
[SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
|
|
333
|
-
[SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
|
|
334
|
-
[SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
|
|
335
|
-
[SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
|
|
336
|
-
};
|
|
337
|
-
function velocityZoneColor(v) {
|
|
338
|
-
if (v < 0.5) return SET_STRIP_ZONES.slow;
|
|
339
|
-
if (v < 0.75) return SET_STRIP_ZONES.moderate;
|
|
340
|
-
if (v < 1) return SET_STRIP_ZONES.fast;
|
|
341
|
-
return SET_STRIP_ZONES.fastest;
|
|
342
|
-
}
|
|
343
|
-
function chunkedSegments(chunks, gap) {
|
|
344
|
-
return chunks.flatMap(
|
|
345
|
-
(vels, ci) => vels.map((v, ri) => ({
|
|
346
|
-
color: velocityZoneColor(v),
|
|
347
|
-
...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
|
|
348
|
-
}))
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
function rangeSegments(floor, max, doneVels) {
|
|
352
|
-
return Array.from({ length: max }, (_, i) => {
|
|
353
|
-
if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
|
|
354
|
-
return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
function myoUpcomingSegments(activationLen, clusterCount) {
|
|
358
|
-
const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
|
|
359
|
-
const trail = Array.from(
|
|
360
|
-
{ length: clusterCount },
|
|
361
|
-
(_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
|
|
362
|
-
color: SET_STRIP_VARIABLE_COLOR,
|
|
363
|
-
opacity: MYO_UPCOMING_FADE ** ci,
|
|
364
|
-
...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
|
|
365
|
-
}))
|
|
366
|
-
).flat();
|
|
367
|
-
return [...activation, ...trail];
|
|
368
|
-
}
|
|
369
|
-
function setSegments(set) {
|
|
370
|
-
if (set.status === "todo") {
|
|
371
|
-
return [{ color: TODO_COLOR }];
|
|
372
|
-
}
|
|
373
|
-
if (set.status === "done") {
|
|
374
|
-
return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
|
|
375
|
-
}
|
|
376
|
-
if (set.status === "range") {
|
|
377
|
-
return rangeSegments(set.floor, set.max, set.doneVels);
|
|
378
|
-
}
|
|
379
|
-
if (set.status === "drop") {
|
|
380
|
-
return chunkedSegments(set.subloads, NOTCH_GAP);
|
|
381
|
-
}
|
|
382
|
-
if (set.status === "myo") {
|
|
383
|
-
return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
|
|
384
|
-
}
|
|
385
|
-
if (set.status === "myo-upcoming") {
|
|
386
|
-
return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
|
|
387
|
-
}
|
|
388
|
-
const performed = set.velocities.map((v) => {
|
|
389
|
-
const color = velocityZoneColor(v);
|
|
390
|
-
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
391
|
-
});
|
|
392
|
-
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
393
|
-
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
394
|
-
}
|
|
395
|
-
function setBarSegmentTestID(seg) {
|
|
396
|
-
if (seg.pulse) return "set-strip-pulse";
|
|
397
|
-
if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
|
|
398
|
-
return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
|
|
399
|
-
}
|
|
400
|
-
function SetBar({ set, height = 8 }) {
|
|
401
|
-
return /* @__PURE__ */ jsx(
|
|
402
|
-
SegmentedBar,
|
|
403
|
-
{
|
|
404
|
-
segments: setSegments(set),
|
|
405
|
-
height,
|
|
406
|
-
gap: 0,
|
|
407
|
-
radius: 0,
|
|
408
|
-
segmentTestID: setBarSegmentTestID,
|
|
409
|
-
style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
|
|
410
|
-
testID: "set-strip-set"
|
|
411
|
-
}
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// src/components/custom/Workout/SetStrip.tsx
|
|
416
|
-
var SET_STRIP_GAP = 5;
|
|
417
|
-
function setCategory(set) {
|
|
418
|
-
switch (set.status) {
|
|
419
|
-
case "done":
|
|
420
|
-
case "drop":
|
|
421
|
-
case "myo":
|
|
422
|
-
return "done";
|
|
423
|
-
case "active":
|
|
424
|
-
return "active";
|
|
425
|
-
case "range":
|
|
426
|
-
return set.doneVels.length > 0 ? "active" : "upcoming";
|
|
427
|
-
case "todo":
|
|
428
|
-
case "myo-upcoming":
|
|
429
|
-
return "upcoming";
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
function describeSets(sets) {
|
|
433
|
-
const done = sets.filter((s) => setCategory(s) === "done").length;
|
|
434
|
-
const active = sets.filter((s) => setCategory(s) === "active").length;
|
|
435
|
-
const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
|
|
436
|
-
return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
|
|
437
|
-
}
|
|
438
|
-
function SetStrip({ sets, height = 8, className, ...props }) {
|
|
439
|
-
return /* @__PURE__ */ jsx(
|
|
440
|
-
View,
|
|
441
|
-
{
|
|
442
|
-
className,
|
|
443
|
-
style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
|
|
444
|
-
accessibilityRole: "image",
|
|
445
|
-
accessibilityLabel: describeSets(sets),
|
|
446
|
-
testID: "set-strip",
|
|
447
|
-
...props,
|
|
448
|
-
children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
|
|
449
|
-
}
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// src/components/custom/Workout/VelocityStrip.tsx
|
|
454
|
-
var VEL_COLORS = WORKOUT_TOKENS.scale;
|
|
455
|
-
var bandIdToEffortColor = {
|
|
456
|
-
speed: sequentialEffort[0],
|
|
457
|
-
// green-300
|
|
458
|
-
power: sequentialEffort[2],
|
|
459
|
-
// amber-300 (gold)
|
|
460
|
-
strengthSpeed: sequentialEffort[3],
|
|
461
|
-
// orange-400
|
|
462
|
-
maximalStrength: sequentialEffort[4],
|
|
463
|
-
// red-600
|
|
464
|
-
grinding: sequentialEffort[5]
|
|
465
|
-
// red-700 — the distinct 5th band
|
|
466
|
-
};
|
|
467
|
-
function getVelocityZoneColor(velocity) {
|
|
468
|
-
if (velocity >= 1) return "vel-green";
|
|
469
|
-
if (velocity >= 0.75) return "vel-yellow";
|
|
470
|
-
if (velocity >= 0.5) return "vel-orange";
|
|
471
|
-
return "vel-red";
|
|
472
|
-
}
|
|
473
|
-
function getVelocityZoneName(velocity) {
|
|
474
|
-
if (velocity >= 1) return "Speed";
|
|
475
|
-
if (velocity >= 0.75) return "Power";
|
|
476
|
-
if (velocity >= 0.5) return "Strength-Speed";
|
|
477
|
-
return "Strength";
|
|
478
|
-
}
|
|
479
|
-
var zoneHexMap = {
|
|
480
|
-
"vel-green": VEL_COLORS.green,
|
|
481
|
-
"vel-yellow": VEL_COLORS.yellow,
|
|
482
|
-
"vel-orange": VEL_COLORS.orange,
|
|
483
|
-
"vel-red": VEL_COLORS.red
|
|
484
|
-
};
|
|
485
|
-
function calculateVelocityLoss(velocities) {
|
|
486
|
-
if (velocities.length < 2) return 0;
|
|
487
|
-
const best = Math.max(...velocities);
|
|
488
|
-
if (best <= 0) return 0;
|
|
489
|
-
const last = velocities[velocities.length - 1];
|
|
490
|
-
return Math.max(0, Math.round((best - last) / best * 100));
|
|
491
|
-
}
|
|
492
|
-
function calculateMeanVelocity(velocities) {
|
|
493
|
-
if (velocities.length === 0) return 0;
|
|
494
|
-
const sum = velocities.reduce((acc, v) => acc + v, 0);
|
|
495
|
-
return sum / velocities.length;
|
|
496
|
-
}
|
|
497
|
-
function classifyBand(velocity, bands) {
|
|
498
|
-
for (const band of bands) {
|
|
499
|
-
if (band.max === null || velocity < band.max) return band;
|
|
500
|
-
}
|
|
501
|
-
return bands[bands.length - 1];
|
|
502
|
-
}
|
|
503
|
-
function bandColor(band) {
|
|
504
|
-
return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
|
|
505
|
-
}
|
|
506
|
-
function getLossStyle(loss) {
|
|
507
|
-
if (loss > 25) return { color: VEL_COLORS.red };
|
|
508
|
-
if (loss > 20) return { color: VEL_COLORS.orange };
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
var REP_GAP = 2;
|
|
512
|
-
var WIDE_GAP = 8;
|
|
513
|
-
var TODO_COLOR2 = primitiveColors.charcoal[300];
|
|
514
|
-
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
515
|
-
function deriveDoneVelocities(set) {
|
|
516
|
-
switch (set.type) {
|
|
517
|
-
case "drop":
|
|
518
|
-
return set.subloads.flat();
|
|
519
|
-
case "myo":
|
|
520
|
-
return [...set.activation, ...set.clusters.flat()];
|
|
521
|
-
default:
|
|
522
|
-
return set.velocities;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
function chunkedRepSlots(chunks) {
|
|
526
|
-
const slots = [];
|
|
527
|
-
chunks.forEach((chunk) => {
|
|
528
|
-
chunk.forEach((velocity, ri) => {
|
|
529
|
-
const first = slots.length === 0;
|
|
530
|
-
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP });
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
return slots;
|
|
534
|
-
}
|
|
535
|
-
function buildSlots(set) {
|
|
536
|
-
switch (set.type) {
|
|
537
|
-
case "straight": {
|
|
538
|
-
const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
|
|
539
|
-
return Array.from({ length: total }, (_, i) => {
|
|
540
|
-
const done = i < set.velocities.length;
|
|
541
|
-
return {
|
|
542
|
-
kind: done ? "rep" : "todo",
|
|
543
|
-
velocity: done ? set.velocities[i] : void 0,
|
|
544
|
-
leadingGap: i === 0 ? 0 : REP_GAP
|
|
545
|
-
};
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
case "range": {
|
|
549
|
-
const total = Math.max(set.max, set.velocities.length);
|
|
550
|
-
return Array.from({ length: total }, (_, i) => {
|
|
551
|
-
const kind = i < set.velocities.length ? "rep" : i < set.floor ? "todo" : "variable";
|
|
552
|
-
return {
|
|
553
|
-
kind,
|
|
554
|
-
velocity: kind === "rep" ? set.velocities[i] : void 0,
|
|
555
|
-
leadingGap: i === 0 ? 0 : REP_GAP
|
|
556
|
-
};
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
case "amrap": {
|
|
560
|
-
const reps = set.velocities.map((velocity, i) => ({
|
|
561
|
-
kind: "rep",
|
|
562
|
-
velocity,
|
|
563
|
-
leadingGap: i === 0 ? 0 : REP_GAP
|
|
564
|
-
}));
|
|
565
|
-
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP });
|
|
566
|
-
return reps;
|
|
567
|
-
}
|
|
568
|
-
case "drop":
|
|
569
|
-
return chunkedRepSlots(set.subloads);
|
|
570
|
-
case "myo": {
|
|
571
|
-
const slots = chunkedRepSlots([set.activation, ...set.clusters]);
|
|
572
|
-
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP });
|
|
573
|
-
return slots;
|
|
574
|
-
}
|
|
575
|
-
case "cluster": {
|
|
576
|
-
const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
|
|
577
|
-
return Array.from({ length: total }, (_, i) => {
|
|
578
|
-
const done = i < set.velocities.length;
|
|
579
|
-
const boundary = i > 0 && i % set.groupSize === 0;
|
|
580
|
-
return {
|
|
581
|
-
kind: done ? "rep" : "todo",
|
|
582
|
-
velocity: done ? set.velocities[i] : void 0,
|
|
583
|
-
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP
|
|
584
|
-
};
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
function setAccessibilityLabel(set, repCount) {
|
|
590
|
-
switch (set.type) {
|
|
591
|
-
case "straight":
|
|
592
|
-
return `Velocity strip, straight set, ${repCount} reps`;
|
|
593
|
-
case "range":
|
|
594
|
-
return `Velocity strip, rep-range set, floor ${set.floor} to ${set.max}, ${repCount} reps done`;
|
|
595
|
-
case "amrap":
|
|
596
|
-
return `Velocity strip, AMRAP set, ${repCount} reps and counting`;
|
|
597
|
-
case "drop":
|
|
598
|
-
return `Velocity strip, drop set, ${set.subloads.length} loads, ${repCount} reps`;
|
|
599
|
-
case "myo":
|
|
600
|
-
return `Velocity strip, myo-reps set, ${set.clusters.length} clusters${set.open ? ", open" : ""}, ${repCount} reps`;
|
|
601
|
-
case "cluster":
|
|
602
|
-
return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
function slotAccessibilityLabel(slot, index) {
|
|
606
|
-
switch (slot.kind) {
|
|
607
|
-
case "rep":
|
|
608
|
-
return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
|
|
609
|
-
case "todo":
|
|
610
|
-
return `Rep ${index + 1}: planned`;
|
|
611
|
-
case "variable":
|
|
612
|
-
return `Rep ${index + 1}: variable`;
|
|
613
|
-
case "continue":
|
|
614
|
-
return "Keep going";
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
var EXPANDED_TODO_STUB_PCT = 16;
|
|
618
|
-
var EXPANDED_ENCODED_PCT = 45;
|
|
619
|
-
var EXPANDED_HEIGHT = 60;
|
|
620
|
-
var FIXED_MAX_VELOCITY = 1.15;
|
|
621
|
-
var BARE_STUB_HEIGHT = 3;
|
|
622
|
-
var HERO_HEIGHT = 220;
|
|
623
|
-
var HERO_LABEL_HEADROOM = 20;
|
|
624
|
-
var HERO_BAR_GAP = 8;
|
|
625
|
-
var HERO_BAR_MAX_WIDTH = 120;
|
|
626
|
-
var HERO_LABEL_MIN_BAR_WIDTH = 30;
|
|
627
|
-
var HERO_BAR_RADIUS = 5;
|
|
628
|
-
var HERO_MIN_BAR_HEIGHT = 4;
|
|
629
|
-
var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
|
|
630
|
-
var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
|
|
631
|
-
var PEAK_HEADROOM = 1.06;
|
|
632
|
-
function scaleDenominator(scale, maxVelocity) {
|
|
633
|
-
return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * PEAK_HEADROOM;
|
|
634
|
-
}
|
|
635
|
-
function bareSlotHeight(slot, height, denom) {
|
|
636
|
-
if (slot.kind !== "rep" || denom <= 0) return BARE_STUB_HEIGHT;
|
|
637
|
-
const ratio = Math.min(1, (slot.velocity ?? 0) / denom);
|
|
638
|
-
return Math.max(BARE_STUB_HEIGHT, ratio * height);
|
|
639
|
-
}
|
|
640
|
-
function getReducedMotionPreference() {
|
|
641
|
-
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
642
|
-
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
643
|
-
}
|
|
644
|
-
function usePrefersReducedMotion() {
|
|
645
|
-
const [reduced, setReduced] = useState(getReducedMotionPreference);
|
|
646
|
-
useEffect(() => {
|
|
647
|
-
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
648
|
-
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
649
|
-
const handler = () => setReduced(mq.matches);
|
|
650
|
-
handler();
|
|
651
|
-
mq.addEventListener?.("change", handler);
|
|
652
|
-
return () => mq.removeEventListener?.("change", handler);
|
|
653
|
-
}, []);
|
|
654
|
-
return reduced;
|
|
655
|
-
}
|
|
656
|
-
var ANIMATION_DURATION = 400;
|
|
657
|
-
var ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1);
|
|
658
|
-
var POP_EASING = Easing.bezier(0.34, 1.56, 0.64, 1);
|
|
659
|
-
function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
|
|
660
|
-
const prefersReducedMotion = usePrefersReducedMotion();
|
|
661
|
-
const [liveScale] = useState(() => new Animated.Value(1));
|
|
662
|
-
useEffect(() => {
|
|
663
|
-
if (!active || liveRepIndex == null || liveVelocity == null) return;
|
|
664
|
-
if (prefersReducedMotion) {
|
|
665
|
-
liveScale.setValue(1);
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
if (isNewPeak) {
|
|
669
|
-
liveScale.setValue(1);
|
|
670
|
-
Animated.sequence([
|
|
671
|
-
Animated.timing(liveScale, {
|
|
672
|
-
toValue: 1.25,
|
|
673
|
-
duration: 150,
|
|
674
|
-
easing: Easing.out(Easing.quad),
|
|
675
|
-
useNativeDriver: true
|
|
676
|
-
}),
|
|
677
|
-
Animated.spring(liveScale, {
|
|
678
|
-
toValue: 1,
|
|
679
|
-
friction: 3,
|
|
680
|
-
tension: 140,
|
|
681
|
-
useNativeDriver: true
|
|
682
|
-
})
|
|
683
|
-
]).start();
|
|
684
|
-
} else {
|
|
685
|
-
liveScale.setValue(0.8);
|
|
686
|
-
Animated.timing(liveScale, {
|
|
687
|
-
toValue: 1,
|
|
688
|
-
duration: 300,
|
|
689
|
-
easing: POP_EASING,
|
|
690
|
-
useNativeDriver: true
|
|
691
|
-
}).start();
|
|
692
|
-
}
|
|
693
|
-
}, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
|
|
694
|
-
return liveScale;
|
|
695
|
-
}
|
|
696
|
-
function HeroVelocityChart({
|
|
697
|
-
doneVelocities,
|
|
698
|
-
barColorFor,
|
|
699
|
-
scaleDenom,
|
|
700
|
-
referenceVelocity,
|
|
701
|
-
liveRepIndex,
|
|
702
|
-
isNewPeak,
|
|
703
|
-
targetReps,
|
|
704
|
-
height,
|
|
705
|
-
className,
|
|
706
|
-
viewProps
|
|
707
|
-
}) {
|
|
708
|
-
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
709
|
-
const liveScale = useLiveRepPop(true, liveRepIndex, liveVelocity, isNewPeak);
|
|
710
|
-
const [plotW, setPlotW] = useState(0);
|
|
711
|
-
const onPlotLayout = (e) => setPlotW(e.nativeEvent.layout.width);
|
|
712
|
-
const plotHeight = Math.max(0, height - HERO_LABEL_HEADROOM);
|
|
713
|
-
const barHeight = (velocity) => scaleDenom > 0 ? Math.max(HERO_MIN_BAR_HEIGHT, Math.min(1, velocity / scaleDenom) * plotHeight) : HERO_MIN_BAR_HEIGHT;
|
|
714
|
-
const pendingCount = Math.max(0, (targetReps ?? 0) - doneVelocities.length);
|
|
715
|
-
const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
|
|
716
|
-
const totalSlots = doneVelocities.length + pendingCount;
|
|
717
|
-
const barWidth = plotW > 0 && totalSlots > 0 ? Math.min(HERO_BAR_MAX_WIDTH, (plotW - HERO_BAR_GAP * (totalSlots - 1)) / totalSlots) : HERO_BAR_MAX_WIDTH;
|
|
718
|
-
const labelsCrowded = plotW > 0 && barWidth < HERO_LABEL_MIN_BAR_WIDTH;
|
|
719
|
-
const heroGap = plotW === 0 ? HERO_BAR_GAP : barWidth < 16 ? 2 : barWidth < 28 ? 4 : HERO_BAR_GAP;
|
|
720
|
-
const peakIndex = doneVelocities.reduce(
|
|
721
|
-
(best, v, i) => v > doneVelocities[best] ? i : best,
|
|
722
|
-
0
|
|
723
|
-
);
|
|
724
|
-
const showBarLabel = (i) => !labelsCrowded || i === peakIndex || i === liveRepIndex;
|
|
725
|
-
const repCount = doneVelocities.length;
|
|
726
|
-
const total = Math.max(repCount, targetReps ?? repCount);
|
|
727
|
-
const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
728
|
-
const { style: externalStyle, ...restProps } = viewProps;
|
|
729
|
-
return /* @__PURE__ */ jsx(
|
|
730
|
-
View,
|
|
731
|
-
{
|
|
732
|
-
className,
|
|
733
|
-
style: [{ height }, externalStyle],
|
|
734
|
-
accessibilityRole: "image",
|
|
735
|
-
accessibilityLabel: label,
|
|
736
|
-
testID: "velocity-strip-hero",
|
|
737
|
-
...restProps,
|
|
738
|
-
children: /* @__PURE__ */ jsxs(
|
|
739
|
-
View,
|
|
740
|
-
{
|
|
741
|
-
onLayout: onPlotLayout,
|
|
742
|
-
style: {
|
|
743
|
-
flex: 1,
|
|
744
|
-
flexDirection: "row",
|
|
745
|
-
alignItems: "flex-end",
|
|
746
|
-
gap: heroGap,
|
|
747
|
-
position: "relative",
|
|
748
|
-
borderBottomWidth: 2,
|
|
749
|
-
borderBottomColor: HERO_PENDING_COLOR
|
|
750
|
-
},
|
|
751
|
-
children: [
|
|
752
|
-
referencePx > 0 && /* @__PURE__ */ jsx(
|
|
753
|
-
View,
|
|
754
|
-
{
|
|
755
|
-
accessibilityElementsHidden: true,
|
|
756
|
-
style: {
|
|
757
|
-
position: "absolute",
|
|
758
|
-
left: 0,
|
|
759
|
-
right: 0,
|
|
760
|
-
bottom: referencePx,
|
|
761
|
-
borderTopWidth: 1,
|
|
762
|
-
borderStyle: "dashed",
|
|
763
|
-
borderColor: HERO_REFERENCE_COLOR,
|
|
764
|
-
pointerEvents: "none"
|
|
765
|
-
},
|
|
766
|
-
testID: "velocity-hero-reference"
|
|
767
|
-
}
|
|
768
|
-
),
|
|
769
|
-
doneVelocities.map((velocity, i) => {
|
|
770
|
-
const isLive = liveRepIndex === i;
|
|
771
|
-
return /* @__PURE__ */ jsxs(
|
|
772
|
-
View,
|
|
773
|
-
{
|
|
774
|
-
accessibilityElementsHidden: true,
|
|
775
|
-
style: {
|
|
776
|
-
flex: 1,
|
|
777
|
-
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
778
|
-
height: "100%",
|
|
779
|
-
alignItems: "center",
|
|
780
|
-
justifyContent: "flex-end"
|
|
781
|
-
},
|
|
782
|
-
children: [
|
|
783
|
-
showBarLabel(i) && /* @__PURE__ */ jsx(
|
|
784
|
-
Text,
|
|
785
|
-
{
|
|
786
|
-
className: "text-text-primary",
|
|
787
|
-
style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
|
|
788
|
-
testID: `velocity-label-${i}`,
|
|
789
|
-
children: formatVelocity(velocity)
|
|
790
|
-
}
|
|
791
|
-
),
|
|
792
|
-
/* @__PURE__ */ jsx(
|
|
793
|
-
Animated.View,
|
|
794
|
-
{
|
|
795
|
-
style: [
|
|
796
|
-
{
|
|
797
|
-
width: "100%",
|
|
798
|
-
height: barHeight(velocity),
|
|
799
|
-
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
800
|
-
borderTopRightRadius: HERO_BAR_RADIUS,
|
|
801
|
-
backgroundColor: barColorFor(velocity)
|
|
802
|
-
},
|
|
803
|
-
isLive ? { transform: [{ scale: liveScale }] } : null
|
|
804
|
-
],
|
|
805
|
-
testID: `velocity-bar-${i}`
|
|
806
|
-
}
|
|
807
|
-
)
|
|
808
|
-
]
|
|
809
|
-
},
|
|
810
|
-
i
|
|
811
|
-
);
|
|
812
|
-
}),
|
|
813
|
-
Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
|
|
814
|
-
View,
|
|
815
|
-
{
|
|
816
|
-
accessibilityElementsHidden: true,
|
|
817
|
-
style: {
|
|
818
|
-
flex: 1,
|
|
819
|
-
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
820
|
-
height: "100%",
|
|
821
|
-
alignItems: "center",
|
|
822
|
-
justifyContent: "flex-end"
|
|
823
|
-
},
|
|
824
|
-
children: /* @__PURE__ */ jsx(
|
|
825
|
-
View,
|
|
826
|
-
{
|
|
827
|
-
style: {
|
|
828
|
-
width: "100%",
|
|
829
|
-
height: HERO_MIN_BAR_HEIGHT * 3,
|
|
830
|
-
borderWidth: 1,
|
|
831
|
-
borderStyle: "dashed",
|
|
832
|
-
borderColor: HERO_PENDING_COLOR,
|
|
833
|
-
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
834
|
-
borderTopRightRadius: HERO_BAR_RADIUS
|
|
835
|
-
},
|
|
836
|
-
testID: "velocity-slot-todo"
|
|
837
|
-
}
|
|
838
|
-
)
|
|
839
|
-
},
|
|
840
|
-
`pending-${i}`
|
|
841
|
-
))
|
|
842
|
-
]
|
|
843
|
-
}
|
|
844
|
-
)
|
|
845
|
-
}
|
|
846
|
-
);
|
|
847
|
-
}
|
|
848
|
-
function VelocityStrip({
|
|
849
|
-
velocities,
|
|
850
|
-
set,
|
|
851
|
-
zones,
|
|
852
|
-
liveRepIndex,
|
|
853
|
-
expanded = true,
|
|
854
|
-
onToggle,
|
|
855
|
-
onRepPress,
|
|
856
|
-
variant = "expanded",
|
|
857
|
-
height = EXPANDED_HEIGHT,
|
|
858
|
-
targetReps,
|
|
859
|
-
scale = "peak",
|
|
860
|
-
showNumbers = true,
|
|
861
|
-
showInfo = true,
|
|
862
|
-
className,
|
|
863
|
-
...props
|
|
864
|
-
}) {
|
|
865
|
-
const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
|
|
866
|
-
const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
|
|
867
|
-
kind: "rep",
|
|
868
|
-
velocity: v,
|
|
869
|
-
leadingGap: i === 0 ? 0 : REP_GAP
|
|
870
|
-
}));
|
|
871
|
-
const maxVelocity = Math.max(...doneVelocities, 0);
|
|
872
|
-
const meanVelocity = calculateMeanVelocity(doneVelocities);
|
|
873
|
-
const loss = calculateVelocityLoss(doneVelocities);
|
|
874
|
-
const framed = showNumbers || showInfo;
|
|
875
|
-
const scaleDenom = scaleDenominator(scale, maxVelocity);
|
|
876
|
-
const hasZones = zones != null && zones.length > 0;
|
|
877
|
-
const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
|
|
878
|
-
const slotColor = (slot) => {
|
|
879
|
-
if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
|
|
880
|
-
if (slot.kind === "todo") return TODO_COLOR2;
|
|
881
|
-
return SET_STRIP_VARIABLE_COLOR;
|
|
882
|
-
};
|
|
883
|
-
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
884
|
-
const [heightAnim] = useState(() => new Animated.Value(expanded ? height : 3));
|
|
885
|
-
const [labelOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
|
|
886
|
-
const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
|
|
887
|
-
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
888
|
-
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
889
|
-
const liveScale = useLiveRepPop(
|
|
890
|
-
variant === "expanded" && framed,
|
|
891
|
-
liveRepIndex,
|
|
892
|
-
liveVelocity,
|
|
893
|
-
isNewPeak
|
|
894
|
-
);
|
|
895
|
-
useEffect(() => {
|
|
896
|
-
if (variant !== "expanded" || !framed) return;
|
|
897
|
-
Animated.parallel([
|
|
898
|
-
Animated.timing(heightAnim, {
|
|
899
|
-
toValue: expanded ? height : 3,
|
|
900
|
-
duration: ANIMATION_DURATION,
|
|
901
|
-
easing: ANIMATION_EASING,
|
|
902
|
-
useNativeDriver: false
|
|
903
|
-
}),
|
|
904
|
-
Animated.timing(labelOpacity, {
|
|
905
|
-
toValue: expanded ? 1 : 0,
|
|
906
|
-
duration: 300,
|
|
907
|
-
delay: expanded ? 150 : 0,
|
|
908
|
-
useNativeDriver: false
|
|
909
|
-
}),
|
|
910
|
-
Animated.timing(infoOpacity, {
|
|
911
|
-
toValue: expanded ? 1 : 0,
|
|
912
|
-
duration: 300,
|
|
913
|
-
delay: expanded ? 200 : 0,
|
|
914
|
-
useNativeDriver: false
|
|
915
|
-
})
|
|
916
|
-
]).start();
|
|
917
|
-
}, [expanded, variant, framed, height, heightAnim, labelOpacity, infoOpacity]);
|
|
918
|
-
if (set == null && velocities == null) return null;
|
|
919
|
-
const repCount = doneVelocities.length;
|
|
920
|
-
const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
|
|
921
|
-
if (variant === "hero") {
|
|
922
|
-
const heroHeight = height === EXPANDED_HEIGHT ? HERO_HEIGHT : height;
|
|
923
|
-
return /* @__PURE__ */ jsx(
|
|
924
|
-
HeroVelocityChart,
|
|
925
|
-
{
|
|
926
|
-
doneVelocities,
|
|
927
|
-
barColorFor,
|
|
928
|
-
scaleDenom,
|
|
929
|
-
referenceVelocity: maxVelocity,
|
|
930
|
-
liveRepIndex,
|
|
931
|
-
isNewPeak,
|
|
932
|
-
targetReps,
|
|
933
|
-
height: heroHeight,
|
|
934
|
-
className,
|
|
935
|
-
viewProps: props
|
|
936
|
-
}
|
|
937
|
-
);
|
|
938
|
-
}
|
|
939
|
-
if (variant === "mini") {
|
|
940
|
-
const { style: externalStyle, ...restProps } = props;
|
|
941
|
-
return /* @__PURE__ */ jsx(
|
|
942
|
-
View,
|
|
943
|
-
{
|
|
944
|
-
className,
|
|
945
|
-
style: [
|
|
946
|
-
{ flexDirection: "row", height: 3, gap: REP_GAP, borderRadius: 2, overflow: "hidden" },
|
|
947
|
-
externalStyle
|
|
948
|
-
],
|
|
949
|
-
accessibilityRole: "image",
|
|
950
|
-
accessibilityLabel: miniLabel,
|
|
951
|
-
testID: "velocity-strip-mini",
|
|
952
|
-
...restProps,
|
|
953
|
-
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
954
|
-
View,
|
|
955
|
-
{
|
|
956
|
-
style: {
|
|
957
|
-
flex: 1,
|
|
958
|
-
backgroundColor: slotColor(slot),
|
|
959
|
-
borderRadius: 1,
|
|
960
|
-
minWidth: 4,
|
|
961
|
-
height: "100%",
|
|
962
|
-
// The container's uniform 2px gap covers rep spacing; a wide slot adds
|
|
963
|
-
// only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
|
|
964
|
-
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
965
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
966
|
-
},
|
|
967
|
-
accessibilityElementsHidden: true,
|
|
968
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
969
|
-
},
|
|
970
|
-
i
|
|
971
|
-
))
|
|
972
|
-
}
|
|
973
|
-
);
|
|
974
|
-
}
|
|
975
|
-
if (!framed) {
|
|
976
|
-
const { style: externalStyle, ...restProps } = props;
|
|
977
|
-
return /* @__PURE__ */ jsx(
|
|
978
|
-
View,
|
|
979
|
-
{
|
|
980
|
-
className,
|
|
981
|
-
style: [
|
|
982
|
-
{ flexDirection: "row", height, gap: REP_GAP, alignItems: "flex-end" },
|
|
983
|
-
externalStyle
|
|
984
|
-
],
|
|
985
|
-
accessibilityRole: "image",
|
|
986
|
-
accessibilityLabel: miniLabel,
|
|
987
|
-
testID: "velocity-strip-compact",
|
|
988
|
-
...restProps,
|
|
989
|
-
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
990
|
-
View,
|
|
991
|
-
{
|
|
992
|
-
style: {
|
|
993
|
-
flex: 1,
|
|
994
|
-
minWidth: 4,
|
|
995
|
-
height: bareSlotHeight(slot, height, scaleDenom),
|
|
996
|
-
// Top-rounded to match the framed chart's bars (the "rounded tops from
|
|
997
|
-
// the numbers one"); square bottoms since bars sit on the baseline.
|
|
998
|
-
borderTopLeftRadius: 2,
|
|
999
|
-
borderTopRightRadius: 2,
|
|
1000
|
-
backgroundColor: slotColor(slot),
|
|
1001
|
-
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
1002
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
1003
|
-
},
|
|
1004
|
-
accessibilityElementsHidden: true,
|
|
1005
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
1006
|
-
},
|
|
1007
|
-
i
|
|
1008
|
-
))
|
|
1009
|
-
}
|
|
1010
|
-
);
|
|
1011
|
-
}
|
|
1012
|
-
const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
|
|
1013
|
-
const hasInteractiveContainer = onToggle != null;
|
|
1014
|
-
const hasInteractiveReps = onRepPress != null && expanded;
|
|
1015
|
-
const setBars = set != null && slots.map((slot, i) => {
|
|
1016
|
-
const isStraightRep = set.type === "straight" && slot.kind === "rep";
|
|
1017
|
-
const heightPct = isStraightRep ? scaleDenom > 0 ? Math.round((slot.velocity ?? 0) / scaleDenom * 100) : 0 : set.type === "straight" ? EXPANDED_TODO_STUB_PCT : EXPANDED_ENCODED_PCT;
|
|
1018
|
-
return /* @__PURE__ */ jsx(
|
|
1019
|
-
View,
|
|
1020
|
-
{
|
|
1021
|
-
style: {
|
|
1022
|
-
flex: 1,
|
|
1023
|
-
height: "100%",
|
|
1024
|
-
justifyContent: "flex-end",
|
|
1025
|
-
marginLeft: slot.leadingGap
|
|
1026
|
-
},
|
|
1027
|
-
children: /* @__PURE__ */ jsx(
|
|
1028
|
-
View,
|
|
1029
|
-
{
|
|
1030
|
-
style: {
|
|
1031
|
-
height: `${heightPct}%`,
|
|
1032
|
-
minHeight: 2,
|
|
1033
|
-
borderTopLeftRadius: 2,
|
|
1034
|
-
borderTopRightRadius: 2,
|
|
1035
|
-
backgroundColor: slotColor(slot),
|
|
1036
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
1037
|
-
},
|
|
1038
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
|
|
1039
|
-
accessibilityRole: "image",
|
|
1040
|
-
accessibilityLabel: slotAccessibilityLabel(slot, i)
|
|
1041
|
-
}
|
|
1042
|
-
)
|
|
1043
|
-
},
|
|
1044
|
-
i
|
|
1045
|
-
);
|
|
1046
|
-
});
|
|
1047
|
-
const stripContent = /* @__PURE__ */ jsxs(
|
|
1048
|
-
Animated.View,
|
|
1049
|
-
{
|
|
1050
|
-
className: [className, expanded ? "bg-surface-raised" : "bg-transparent"].filter(Boolean).join(" "),
|
|
1051
|
-
style: [
|
|
1052
|
-
{
|
|
1053
|
-
height: heightAnim,
|
|
1054
|
-
width: "100%",
|
|
1055
|
-
gap: 2,
|
|
1056
|
-
borderRadius: expanded ? 6 : 2,
|
|
1057
|
-
paddingTop: expanded ? 16 : 0,
|
|
1058
|
-
paddingBottom: expanded ? 24 : 0,
|
|
1059
|
-
// No horizontal inset — the framed chart's bars span full width, matching
|
|
1060
|
-
// the bare spotlight and mini. Numbers/info sit in the vertical padding.
|
|
1061
|
-
paddingHorizontal: 0,
|
|
1062
|
-
paddingVertical: expanded ? void 0 : 8,
|
|
1063
|
-
overflow: expanded ? "visible" : "hidden"
|
|
1064
|
-
}
|
|
1065
|
-
],
|
|
1066
|
-
accessibilityRole: hasInteractiveContainer || hasInteractiveReps ? "none" : "button",
|
|
1067
|
-
accessibilityLabel: hasInteractiveContainer || hasInteractiveReps ? void 0 : stripLabel,
|
|
1068
|
-
testID: "velocity-strip",
|
|
1069
|
-
...props,
|
|
1070
|
-
children: [
|
|
1071
|
-
/* @__PURE__ */ jsx(View, { style: { flexDirection: "row", flex: 1, gap: set ? 0 : 2, alignItems: "flex-end" }, children: set ? setBars : doneVelocities.map((v, i) => {
|
|
1072
|
-
const barBackground = barColorFor(v);
|
|
1073
|
-
const barHeightPct = scaleDenom > 0 ? Math.round(v / scaleDenom * 100) : 0;
|
|
1074
|
-
const isLive = liveRepIndex === i;
|
|
1075
|
-
const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
|
|
1076
|
-
const barStyle = expanded ? {
|
|
1077
|
-
height: `${barHeightPct}%`,
|
|
1078
|
-
minHeight: 2,
|
|
1079
|
-
borderTopLeftRadius: 2,
|
|
1080
|
-
borderTopRightRadius: 2,
|
|
1081
|
-
backgroundColor: barBackground
|
|
1082
|
-
} : {
|
|
1083
|
-
minHeight: "100%",
|
|
1084
|
-
borderTopLeftRadius: 2,
|
|
1085
|
-
borderTopRightRadius: 2,
|
|
1086
|
-
borderBottomLeftRadius: 0,
|
|
1087
|
-
borderBottomRightRadius: 0,
|
|
1088
|
-
backgroundColor: barBackground
|
|
1089
|
-
};
|
|
1090
|
-
const barInner = isLive ? /* @__PURE__ */ jsx(
|
|
1091
|
-
Animated.View,
|
|
1092
|
-
{
|
|
1093
|
-
style: [barStyle, { transform: [{ scale: liveScale }] }],
|
|
1094
|
-
testID: `velocity-bar-${i}`,
|
|
1095
|
-
accessibilityRole: "image",
|
|
1096
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
|
|
1097
|
-
}
|
|
1098
|
-
) : /* @__PURE__ */ jsx(
|
|
1099
|
-
View,
|
|
1100
|
-
{
|
|
1101
|
-
style: barStyle,
|
|
1102
|
-
testID: `velocity-bar-${i}`,
|
|
1103
|
-
accessibilityRole: "image",
|
|
1104
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
|
|
1105
|
-
}
|
|
1106
|
-
);
|
|
1107
|
-
const bar = /* @__PURE__ */ jsxs(
|
|
1108
|
-
View,
|
|
1109
|
-
{
|
|
1110
|
-
style: {
|
|
1111
|
-
flex: 1,
|
|
1112
|
-
height: "100%",
|
|
1113
|
-
justifyContent: "flex-end",
|
|
1114
|
-
position: "relative"
|
|
1115
|
-
},
|
|
1116
|
-
children: [
|
|
1117
|
-
expanded && showNumbers && /* @__PURE__ */ jsx(
|
|
1118
|
-
Animated.View,
|
|
1119
|
-
{
|
|
1120
|
-
style: {
|
|
1121
|
-
opacity: labelOpacity,
|
|
1122
|
-
alignItems: "center",
|
|
1123
|
-
position: "absolute",
|
|
1124
|
-
top: -13,
|
|
1125
|
-
left: 0,
|
|
1126
|
-
right: 0
|
|
1127
|
-
},
|
|
1128
|
-
accessibilityElementsHidden: true,
|
|
1129
|
-
children: /* @__PURE__ */ jsx(
|
|
1130
|
-
Text,
|
|
1131
|
-
{
|
|
1132
|
-
className: "text-text-secondary",
|
|
1133
|
-
style: { fontSize: 8, fontWeight: "600" },
|
|
1134
|
-
testID: `velocity-label-${i}`,
|
|
1135
|
-
children: formatVelocity(v)
|
|
1136
|
-
}
|
|
1137
|
-
)
|
|
1138
|
-
}
|
|
1139
|
-
),
|
|
1140
|
-
barInner
|
|
1141
|
-
]
|
|
1142
|
-
},
|
|
1143
|
-
i
|
|
1144
|
-
);
|
|
1145
|
-
if (onRepPress && expanded) {
|
|
1146
|
-
return /* @__PURE__ */ jsx(
|
|
1147
|
-
Pressable,
|
|
1148
|
-
{
|
|
1149
|
-
style: { flex: 1, height: "100%" },
|
|
1150
|
-
onPress: () => onRepPress(i, v),
|
|
1151
|
-
accessibilityRole: "button",
|
|
1152
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
|
|
1153
|
-
testID: `velocity-bar-pressable-${i}`,
|
|
1154
|
-
children: bar
|
|
1155
|
-
},
|
|
1156
|
-
i
|
|
1157
|
-
);
|
|
1158
|
-
}
|
|
1159
|
-
return bar;
|
|
1160
|
-
}) }),
|
|
1161
|
-
expanded && showInfo && /* @__PURE__ */ jsxs(
|
|
1162
|
-
Animated.View,
|
|
1163
|
-
{
|
|
1164
|
-
style: {
|
|
1165
|
-
flexDirection: "row",
|
|
1166
|
-
justifyContent: "space-between",
|
|
1167
|
-
opacity: infoOpacity,
|
|
1168
|
-
position: "absolute",
|
|
1169
|
-
bottom: 4,
|
|
1170
|
-
left: 6,
|
|
1171
|
-
right: 6
|
|
1172
|
-
},
|
|
1173
|
-
testID: "velocity-info-row",
|
|
1174
|
-
children: [
|
|
1175
|
-
/* @__PURE__ */ jsxs(
|
|
1176
|
-
Text,
|
|
1177
|
-
{
|
|
1178
|
-
className: "text-text-secondary",
|
|
1179
|
-
style: {
|
|
1180
|
-
fontSize: 10,
|
|
1181
|
-
fontFamily: "Inter, sans-serif"
|
|
1182
|
-
},
|
|
1183
|
-
children: [
|
|
1184
|
-
meanZone,
|
|
1185
|
-
" ",
|
|
1186
|
-
"\xB7",
|
|
1187
|
-
" ",
|
|
1188
|
-
formatVelocity(meanVelocity),
|
|
1189
|
-
" m/s"
|
|
1190
|
-
]
|
|
1191
|
-
}
|
|
1192
|
-
),
|
|
1193
|
-
/* @__PURE__ */ jsxs(
|
|
1194
|
-
Text,
|
|
1195
|
-
{
|
|
1196
|
-
className: "text-text-secondary",
|
|
1197
|
-
style: {
|
|
1198
|
-
fontSize: 10,
|
|
1199
|
-
fontFamily: "Inter, sans-serif",
|
|
1200
|
-
...getLossStyle(loss)
|
|
1201
|
-
},
|
|
1202
|
-
children: [
|
|
1203
|
-
"Loss: ",
|
|
1204
|
-
loss,
|
|
1205
|
-
"%"
|
|
1206
|
-
]
|
|
1207
|
-
}
|
|
1208
|
-
)
|
|
1209
|
-
]
|
|
1210
|
-
}
|
|
1211
|
-
)
|
|
1212
|
-
]
|
|
1213
|
-
}
|
|
1214
|
-
);
|
|
1215
|
-
if (onToggle) {
|
|
1216
|
-
return /* @__PURE__ */ jsx(
|
|
1217
|
-
Pressable,
|
|
1218
|
-
{
|
|
1219
|
-
onPress: onToggle,
|
|
1220
|
-
accessibilityRole: "button",
|
|
1221
|
-
accessibilityLabel: stripLabel,
|
|
1222
|
-
testID: "velocity-strip-pressable",
|
|
1223
|
-
children: stripContent
|
|
1224
|
-
}
|
|
1225
|
-
);
|
|
1226
|
-
}
|
|
1227
|
-
return stripContent;
|
|
1228
|
-
}
|
|
1229
|
-
function cn(...inputs) {
|
|
1230
|
-
return twMerge(clsx(inputs));
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
// src/components/custom/Workout/PlaceholderStrip.tsx
|
|
1234
|
-
function SingleStrip({
|
|
1235
|
-
width,
|
|
1236
|
-
className,
|
|
1237
|
-
...props
|
|
1238
|
-
}) {
|
|
1239
|
-
return /* @__PURE__ */ jsx(
|
|
1240
|
-
View,
|
|
1241
|
-
{
|
|
1242
|
-
className,
|
|
1243
|
-
style: [
|
|
1244
|
-
{
|
|
1245
|
-
height: 3,
|
|
1246
|
-
backgroundColor: WORKOUT_TOKENS.placeholder.fill,
|
|
1247
|
-
borderRadius: 2,
|
|
1248
|
-
opacity: 0.5
|
|
1249
|
-
},
|
|
1250
|
-
width != null ? { width } : { flex: 1 }
|
|
1251
|
-
],
|
|
1252
|
-
accessibilityRole: "image",
|
|
1253
|
-
accessibilityLabel: "Planned set, not yet completed",
|
|
1254
|
-
testID: "placeholder-strip",
|
|
1255
|
-
...props
|
|
1256
|
-
}
|
|
1257
|
-
);
|
|
1258
|
-
}
|
|
1259
|
-
function SegmentedStrip({
|
|
1260
|
-
segments = 3,
|
|
1261
|
-
width,
|
|
1262
|
-
className,
|
|
1263
|
-
...props
|
|
1264
|
-
}) {
|
|
1265
|
-
return /* @__PURE__ */ jsx(
|
|
1266
|
-
View,
|
|
1267
|
-
{
|
|
1268
|
-
className: cn("flex-row", className),
|
|
1269
|
-
style: [
|
|
1270
|
-
{ height: 3, gap: 2, opacity: 0.5 },
|
|
1271
|
-
width != null ? { width } : { flex: 1 }
|
|
1272
|
-
],
|
|
1273
|
-
accessibilityRole: "image",
|
|
1274
|
-
accessibilityLabel: "Planned set, not yet completed",
|
|
1275
|
-
testID: "placeholder-strip",
|
|
1276
|
-
...props,
|
|
1277
|
-
children: Array.from({ length: segments }, (_, i) => /* @__PURE__ */ jsx(
|
|
1278
|
-
View,
|
|
1279
|
-
{
|
|
1280
|
-
style: {
|
|
1281
|
-
flex: 1,
|
|
1282
|
-
height: 3,
|
|
1283
|
-
backgroundColor: WORKOUT_TOKENS.placeholder.fill,
|
|
1284
|
-
borderRadius: 1,
|
|
1285
|
-
minWidth: 4
|
|
1286
|
-
},
|
|
1287
|
-
accessibilityElementsHidden: true,
|
|
1288
|
-
testID: "placeholder-segment"
|
|
1289
|
-
},
|
|
1290
|
-
i
|
|
1291
|
-
))
|
|
1292
|
-
}
|
|
1293
|
-
);
|
|
1294
|
-
}
|
|
1295
|
-
function PlaceholderStrip({
|
|
1296
|
-
mode = "single",
|
|
1297
|
-
segments,
|
|
1298
|
-
...props
|
|
1299
|
-
}) {
|
|
1300
|
-
if (mode === "segmented") {
|
|
1301
|
-
return /* @__PURE__ */ jsx(SegmentedStrip, { segments, ...props });
|
|
1302
|
-
}
|
|
1303
|
-
return /* @__PURE__ */ jsx(SingleStrip, { ...props });
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
// src/components/icons/SvgIcon.tsx
|
|
1307
|
-
function SvgIcon({
|
|
1308
|
-
size = 24,
|
|
1309
|
-
color = "currentColor",
|
|
1310
|
-
strokeWidth = 2,
|
|
1311
|
-
fill = "none",
|
|
1312
|
-
title,
|
|
1313
|
-
children
|
|
1314
|
-
}) {
|
|
1315
|
-
return /* @__PURE__ */ jsxs(
|
|
1316
|
-
"svg",
|
|
1317
|
-
{
|
|
1318
|
-
width: size,
|
|
1319
|
-
height: size,
|
|
1320
|
-
viewBox: "0 0 24 24",
|
|
1321
|
-
fill,
|
|
1322
|
-
stroke: color,
|
|
1323
|
-
strokeWidth,
|
|
1324
|
-
strokeLinecap: "round",
|
|
1325
|
-
strokeLinejoin: "round",
|
|
1326
|
-
...title ? { role: "img", "aria-label": title } : { "aria-hidden": true },
|
|
1327
|
-
children: [
|
|
1328
|
-
title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
|
|
1329
|
-
children
|
|
1330
|
-
]
|
|
1331
|
-
}
|
|
1332
|
-
);
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
// src/components/icons/icons.tsx
|
|
1336
|
-
function StarIcon(props) {
|
|
1337
|
-
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" }) });
|
|
1338
|
-
}
|
|
1339
|
-
function ScaleIcon(props) {
|
|
1340
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1341
|
-
/* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1342
|
-
/* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1343
|
-
/* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
|
|
1344
|
-
/* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
|
|
1345
|
-
/* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
|
|
1346
|
-
] });
|
|
1347
|
-
}
|
|
1348
|
-
function AlertTriangleIcon(props) {
|
|
1349
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1350
|
-
/* @__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" }),
|
|
1351
|
-
/* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
|
|
1352
|
-
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
1353
|
-
] });
|
|
1354
|
-
}
|
|
1355
|
-
function TrendingDownIcon(props) {
|
|
1356
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1357
|
-
/* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
|
|
1358
|
-
/* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
|
|
1359
|
-
] });
|
|
1360
|
-
}
|
|
1361
|
-
function CircleSlashIcon(props) {
|
|
1362
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1363
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1364
|
-
/* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
|
|
1365
|
-
] });
|
|
1366
|
-
}
|
|
1367
|
-
function AwardIcon(props) {
|
|
1368
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1369
|
-
/* @__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" }),
|
|
1370
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
|
|
1371
|
-
] });
|
|
1372
|
-
}
|
|
1373
|
-
function InfoIcon(props) {
|
|
1374
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1375
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1376
|
-
/* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
|
|
1377
|
-
/* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
|
|
1378
|
-
] });
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
182
|
// src/theme/tokens/semantic.ts
|
|
1382
183
|
var semanticColorsLight = {
|
|
1383
184
|
// Brand colors (brand-*)
|
|
@@ -1683,11 +484,1504 @@ var semanticColorsDark = {
|
|
|
1683
484
|
"avatar-background": primitiveColors.neutral[600],
|
|
1684
485
|
"avatar-text": primitiveColors.white
|
|
1685
486
|
};
|
|
1686
|
-
function getSemanticColors(mode) {
|
|
1687
|
-
return mode === "dark" ? semanticColorsDark : semanticColorsLight;
|
|
487
|
+
function getSemanticColors(mode) {
|
|
488
|
+
return mode === "dark" ? semanticColorsDark : semanticColorsLight;
|
|
489
|
+
}
|
|
490
|
+
function hexToRgb(hex) {
|
|
491
|
+
const cleaned = hex.replace("#", "");
|
|
492
|
+
let r, g, b;
|
|
493
|
+
if (cleaned.length === 3) {
|
|
494
|
+
r = parseInt(cleaned[0] + cleaned[0], 16);
|
|
495
|
+
g = parseInt(cleaned[1] + cleaned[1], 16);
|
|
496
|
+
b = parseInt(cleaned[2] + cleaned[2], 16);
|
|
497
|
+
} else if (cleaned.length === 6) {
|
|
498
|
+
r = parseInt(cleaned.slice(0, 2), 16);
|
|
499
|
+
g = parseInt(cleaned.slice(2, 4), 16);
|
|
500
|
+
b = parseInt(cleaned.slice(4, 6), 16);
|
|
501
|
+
} else {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
return { r, g, b };
|
|
505
|
+
}
|
|
506
|
+
function rgbToHsv(r, g, b) {
|
|
507
|
+
const rNorm = r / 255;
|
|
508
|
+
const gNorm = g / 255;
|
|
509
|
+
const bNorm = b / 255;
|
|
510
|
+
const max = Math.max(rNorm, gNorm, bNorm);
|
|
511
|
+
const min = Math.min(rNorm, gNorm, bNorm);
|
|
512
|
+
const delta = max - min;
|
|
513
|
+
const v = max;
|
|
514
|
+
const s = max === 0 ? 0 : delta / max;
|
|
515
|
+
let h = 0;
|
|
516
|
+
if (delta !== 0) {
|
|
517
|
+
if (max === rNorm) {
|
|
518
|
+
h = 60 * ((gNorm - bNorm) / delta % 6);
|
|
519
|
+
} else if (max === gNorm) {
|
|
520
|
+
h = 60 * ((bNorm - rNorm) / delta + 2);
|
|
521
|
+
} else {
|
|
522
|
+
h = 60 * ((rNorm - gNorm) / delta + 4);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (h < 0) h += 360;
|
|
526
|
+
return { h, s, v };
|
|
527
|
+
}
|
|
528
|
+
function hsvToRgb(h, s, v) {
|
|
529
|
+
const c = v * s;
|
|
530
|
+
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
531
|
+
const m = v - c;
|
|
532
|
+
let rPrime, gPrime, bPrime;
|
|
533
|
+
if (h >= 0 && h < 60) {
|
|
534
|
+
rPrime = c;
|
|
535
|
+
gPrime = x;
|
|
536
|
+
bPrime = 0;
|
|
537
|
+
} else if (h >= 60 && h < 120) {
|
|
538
|
+
rPrime = x;
|
|
539
|
+
gPrime = c;
|
|
540
|
+
bPrime = 0;
|
|
541
|
+
} else if (h >= 120 && h < 180) {
|
|
542
|
+
rPrime = 0;
|
|
543
|
+
gPrime = c;
|
|
544
|
+
bPrime = x;
|
|
545
|
+
} else if (h >= 180 && h < 240) {
|
|
546
|
+
rPrime = 0;
|
|
547
|
+
gPrime = x;
|
|
548
|
+
bPrime = c;
|
|
549
|
+
} else if (h >= 240 && h < 300) {
|
|
550
|
+
rPrime = x;
|
|
551
|
+
gPrime = 0;
|
|
552
|
+
bPrime = c;
|
|
553
|
+
} else {
|
|
554
|
+
rPrime = c;
|
|
555
|
+
gPrime = 0;
|
|
556
|
+
bPrime = x;
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
r: Math.round((rPrime + m) * 255),
|
|
560
|
+
g: Math.round((gPrime + m) * 255),
|
|
561
|
+
b: Math.round((bPrime + m) * 255)
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function rgbToHex(r, g, b) {
|
|
565
|
+
const toHex = (n) => {
|
|
566
|
+
const clamped = Math.max(0, Math.min(255, Math.round(n)));
|
|
567
|
+
return clamped.toString(16).padStart(2, "0");
|
|
568
|
+
};
|
|
569
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
570
|
+
}
|
|
571
|
+
function lighten(hex, amount = 0.1) {
|
|
572
|
+
const rgb = hexToRgb(hex);
|
|
573
|
+
if (!rgb) return hex;
|
|
574
|
+
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
575
|
+
const newV = Math.min(1, hsv.v + amount);
|
|
576
|
+
const newRgb = hsvToRgb(hsv.h, hsv.s, newV);
|
|
577
|
+
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
|
|
578
|
+
}
|
|
579
|
+
var shadowColors = {
|
|
580
|
+
// For charcoal surfaces (~#2C2C2C to #3C3C3C)
|
|
581
|
+
charcoal: {
|
|
582
|
+
dark: "rgba(0, 0, 0, 0.5)",
|
|
583
|
+
light: "rgba(255, 255, 255, 0.12)",
|
|
584
|
+
// brighter for crisp rim light
|
|
585
|
+
darker: "rgba(0, 0, 0, 0.6)"
|
|
586
|
+
},
|
|
587
|
+
// For neutral dark surfaces (~#1F2937)
|
|
588
|
+
neutralDark: {
|
|
589
|
+
dark: "rgba(0, 0, 0, 0.4)",
|
|
590
|
+
light: "rgba(255, 255, 255, 0.08)",
|
|
591
|
+
darker: "rgba(0, 0, 0, 0.5)"
|
|
592
|
+
},
|
|
593
|
+
// For light surfaces (white/light gray backgrounds)
|
|
594
|
+
neutralLight: {
|
|
595
|
+
dark: "rgba(0, 0, 0, 0.15)",
|
|
596
|
+
// Visible dark shadow for depth on light backgrounds
|
|
597
|
+
light: "rgba(255, 255, 255, 1)",
|
|
598
|
+
// Bright highlight for raised effect (visible on light gray)
|
|
599
|
+
darker: "rgba(0, 0, 0, 0.2)"
|
|
600
|
+
// Deeper shadow for pressed states
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
var intensityConfig = {
|
|
604
|
+
subtle: { offset: 1, blur: 2 },
|
|
605
|
+
// very light, barely visible
|
|
606
|
+
medium: { offset: 2, blur: 4 },
|
|
607
|
+
// noticeable but restrained
|
|
608
|
+
strong: { offset: 2, blur: 5 }
|
|
609
|
+
// slightly deeper blur, same offset as medium
|
|
610
|
+
};
|
|
611
|
+
function createRaisedShadow(surface = "charcoal", intensity = "medium") {
|
|
612
|
+
const colors = shadowColors[surface];
|
|
613
|
+
const { offset, blur } = intensityConfig[intensity];
|
|
614
|
+
const rimBlur = 1;
|
|
615
|
+
return Platform.select({
|
|
616
|
+
web: {
|
|
617
|
+
boxShadow: `${offset}px ${offset}px ${blur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
|
|
618
|
+
},
|
|
619
|
+
default: {
|
|
620
|
+
// React Native shadow approximation (limited compared to web)
|
|
621
|
+
shadowColor: "#000",
|
|
622
|
+
shadowOffset: { width: offset, height: offset },
|
|
623
|
+
shadowOpacity: 0.3,
|
|
624
|
+
shadowRadius: blur / 2,
|
|
625
|
+
elevation: offset
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
function createPressedShadow(surface = "charcoal", intensity = "medium") {
|
|
630
|
+
const colors = shadowColors[surface];
|
|
631
|
+
const { offset, blur } = intensityConfig[intensity];
|
|
632
|
+
const rimOffset = 1;
|
|
633
|
+
const rimBlur = 1;
|
|
634
|
+
return Platform.select({
|
|
635
|
+
web: {
|
|
636
|
+
boxShadow: `inset ${offset}px ${offset}px ${blur}px ${colors.darker}, inset -${rimOffset}px -${rimOffset}px ${rimBlur}px ${colors.light}`
|
|
637
|
+
},
|
|
638
|
+
default: {
|
|
639
|
+
// React Native doesn't support inset shadows, approximate with darker bg
|
|
640
|
+
shadowOpacity: 0,
|
|
641
|
+
elevation: 0
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
function createFlatShadow() {
|
|
646
|
+
return Platform.select({
|
|
647
|
+
web: {
|
|
648
|
+
boxShadow: "none"
|
|
649
|
+
},
|
|
650
|
+
default: {
|
|
651
|
+
shadowOpacity: 0,
|
|
652
|
+
elevation: 0
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function createRaisedHoverShadow(surface = "charcoal", intensity = "medium") {
|
|
657
|
+
const colors = shadowColors[surface];
|
|
658
|
+
const { offset, blur } = intensityConfig[intensity];
|
|
659
|
+
const hoverOffset = Math.max(1, Math.round(offset / 2));
|
|
660
|
+
const hoverBlur = Math.max(1, Math.round(blur / 2));
|
|
661
|
+
const rimBlur = 1;
|
|
662
|
+
return Platform.select({
|
|
663
|
+
web: {
|
|
664
|
+
// Keep same offsets as normal state for rim to prevent "shrinking" effect
|
|
665
|
+
boxShadow: `${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
|
|
666
|
+
},
|
|
667
|
+
default: {
|
|
668
|
+
shadowColor: "#000",
|
|
669
|
+
shadowOffset: { width: hoverOffset, height: hoverOffset },
|
|
670
|
+
shadowOpacity: 0.2,
|
|
671
|
+
shadowRadius: hoverBlur / 2,
|
|
672
|
+
elevation: hoverOffset
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
function createPressedHoverShadow(surface = "charcoal", intensity = "medium") {
|
|
677
|
+
const colors = shadowColors[surface];
|
|
678
|
+
const { offset, blur } = intensityConfig[intensity];
|
|
679
|
+
const hoverOffset = Math.max(1, Math.round(offset / 2));
|
|
680
|
+
const hoverBlur = Math.max(1, Math.round(blur / 2));
|
|
681
|
+
const rimBlur = 1;
|
|
682
|
+
return Platform.select({
|
|
683
|
+
web: {
|
|
684
|
+
// Keep rim offset at 1px (same as pressed normal) to prevent "shrinking" effect
|
|
685
|
+
boxShadow: `inset ${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.darker}, inset -1px -1px ${rimBlur}px ${colors.light}`
|
|
686
|
+
},
|
|
687
|
+
default: {
|
|
688
|
+
shadowOpacity: 0,
|
|
689
|
+
elevation: 0
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
({
|
|
694
|
+
// Charcoal surface shadows (for dark toolbars, cards)
|
|
695
|
+
charcoal: {
|
|
696
|
+
raised: {
|
|
697
|
+
subtle: createRaisedShadow("charcoal", "subtle"),
|
|
698
|
+
medium: createRaisedShadow("charcoal", "medium"),
|
|
699
|
+
strong: createRaisedShadow("charcoal", "strong")
|
|
700
|
+
},
|
|
701
|
+
raisedHover: {
|
|
702
|
+
subtle: createRaisedHoverShadow("charcoal", "subtle"),
|
|
703
|
+
medium: createRaisedHoverShadow("charcoal", "medium"),
|
|
704
|
+
strong: createRaisedHoverShadow("charcoal", "strong")
|
|
705
|
+
},
|
|
706
|
+
pressed: {
|
|
707
|
+
subtle: createPressedShadow("charcoal", "subtle"),
|
|
708
|
+
medium: createPressedShadow("charcoal", "medium"),
|
|
709
|
+
strong: createPressedShadow("charcoal", "strong")
|
|
710
|
+
},
|
|
711
|
+
pressedHover: {
|
|
712
|
+
subtle: createPressedHoverShadow("charcoal", "subtle"),
|
|
713
|
+
medium: createPressedHoverShadow("charcoal", "medium"),
|
|
714
|
+
strong: createPressedHoverShadow("charcoal", "strong")
|
|
715
|
+
},
|
|
716
|
+
flat: createFlatShadow()
|
|
717
|
+
},
|
|
718
|
+
// Neutral dark surface shadows
|
|
719
|
+
neutralDark: {
|
|
720
|
+
raised: {
|
|
721
|
+
subtle: createRaisedShadow("neutralDark", "subtle"),
|
|
722
|
+
medium: createRaisedShadow("neutralDark", "medium"),
|
|
723
|
+
strong: createRaisedShadow("neutralDark", "strong")
|
|
724
|
+
},
|
|
725
|
+
raisedHover: {
|
|
726
|
+
subtle: createRaisedHoverShadow("neutralDark", "subtle"),
|
|
727
|
+
medium: createRaisedHoverShadow("neutralDark", "medium"),
|
|
728
|
+
strong: createRaisedHoverShadow("neutralDark", "strong")
|
|
729
|
+
},
|
|
730
|
+
pressed: {
|
|
731
|
+
subtle: createPressedShadow("neutralDark", "subtle"),
|
|
732
|
+
medium: createPressedShadow("neutralDark", "medium"),
|
|
733
|
+
strong: createPressedShadow("neutralDark", "strong")
|
|
734
|
+
},
|
|
735
|
+
pressedHover: {
|
|
736
|
+
subtle: createPressedHoverShadow("neutralDark", "subtle"),
|
|
737
|
+
medium: createPressedHoverShadow("neutralDark", "medium"),
|
|
738
|
+
strong: createPressedHoverShadow("neutralDark", "strong")
|
|
739
|
+
},
|
|
740
|
+
flat: createFlatShadow()
|
|
741
|
+
},
|
|
742
|
+
// Neutral light surface shadows
|
|
743
|
+
neutralLight: {
|
|
744
|
+
raised: {
|
|
745
|
+
subtle: createRaisedShadow("neutralLight", "subtle"),
|
|
746
|
+
medium: createRaisedShadow("neutralLight", "medium"),
|
|
747
|
+
strong: createRaisedShadow("neutralLight", "strong")
|
|
748
|
+
},
|
|
749
|
+
raisedHover: {
|
|
750
|
+
subtle: createRaisedHoverShadow("neutralLight", "subtle"),
|
|
751
|
+
medium: createRaisedHoverShadow("neutralLight", "medium"),
|
|
752
|
+
strong: createRaisedHoverShadow("neutralLight", "strong")
|
|
753
|
+
},
|
|
754
|
+
pressed: {
|
|
755
|
+
subtle: createPressedShadow("neutralLight", "subtle"),
|
|
756
|
+
medium: createPressedShadow("neutralLight", "medium"),
|
|
757
|
+
strong: createPressedShadow("neutralLight", "strong")
|
|
758
|
+
},
|
|
759
|
+
pressedHover: {
|
|
760
|
+
subtle: createPressedHoverShadow("neutralLight", "subtle"),
|
|
761
|
+
medium: createPressedHoverShadow("neutralLight", "medium"),
|
|
762
|
+
strong: createPressedHoverShadow("neutralLight", "strong")
|
|
763
|
+
},
|
|
764
|
+
flat: createFlatShadow()
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
// src/utils/colors.ts
|
|
769
|
+
function alpha(color, opacity) {
|
|
770
|
+
const clampedOpacity = Math.max(0, Math.min(1, opacity));
|
|
771
|
+
if (color.startsWith("#")) {
|
|
772
|
+
const hex = color.slice(1);
|
|
773
|
+
let r, g, b;
|
|
774
|
+
if (hex.length === 3) {
|
|
775
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
776
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
777
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
778
|
+
} else if (hex.length === 6) {
|
|
779
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
780
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
781
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
782
|
+
} else {
|
|
783
|
+
return color;
|
|
784
|
+
}
|
|
785
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
786
|
+
}
|
|
787
|
+
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
788
|
+
if (rgbMatch) {
|
|
789
|
+
const [, r, g, b] = rgbMatch;
|
|
790
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
791
|
+
}
|
|
792
|
+
const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
|
|
793
|
+
if (rgbaMatch) {
|
|
794
|
+
const [, r, g, b] = rgbaMatch;
|
|
795
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
796
|
+
}
|
|
797
|
+
return color;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/utils/workout-format.ts
|
|
801
|
+
function roundRpe(rpe) {
|
|
802
|
+
return Math.round(rpe * 2) / 2;
|
|
803
|
+
}
|
|
804
|
+
function roundWeight(weight) {
|
|
805
|
+
return Math.round(weight);
|
|
806
|
+
}
|
|
807
|
+
function formatVelocity(velocity) {
|
|
808
|
+
return velocity.toFixed(2);
|
|
809
|
+
}
|
|
810
|
+
function roundTempo(tempo) {
|
|
811
|
+
return tempo.map((v) => Math.round(v * 10) / 10);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// src/theme/bar-paper.ts
|
|
815
|
+
function grainOpacityForColor(hex) {
|
|
816
|
+
const h = hex.replace("#", "");
|
|
817
|
+
const full = h.length === 3 ? h.split("").map((ch) => ch + ch).join("") : h;
|
|
818
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
819
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
820
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
821
|
+
if ([r, g, b].some(Number.isNaN)) return 0.14;
|
|
822
|
+
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
823
|
+
return Math.min(0.24, Math.max(0.04, 0.02 + 0.31 * lum));
|
|
824
|
+
}
|
|
825
|
+
function barGrain(color) {
|
|
826
|
+
const op = grainOpacityForColor(color).toFixed(3);
|
|
827
|
+
return `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='${op}'/%3E%3C/svg%3E")`;
|
|
828
|
+
}
|
|
829
|
+
function barPaper(color, flip = false) {
|
|
830
|
+
return {
|
|
831
|
+
backgroundImage: barGrain(color),
|
|
832
|
+
boxShadow: flip ? "0 -6px 16px rgba(0,0,0,0.45)" : "inset 0 1.5px 0 rgba(255,255,255,0.22), 0 6px 16px rgba(0,0,0,0.45)"
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
var SURFACE_LEVEL_TOKEN = {
|
|
836
|
+
background: "background-base",
|
|
837
|
+
base: "surface-base",
|
|
838
|
+
elevated: "surface-elevated",
|
|
839
|
+
raised: "surface-raised",
|
|
840
|
+
overlay: "surface-overlay"
|
|
841
|
+
};
|
|
842
|
+
var ON_SURFACE_TOKEN = {
|
|
843
|
+
primary: "text-primary",
|
|
844
|
+
secondary: "text-secondary",
|
|
845
|
+
tertiary: "text-tertiary"
|
|
846
|
+
};
|
|
847
|
+
var DEFAULT_CONTEXT = { mode: "dark", level: "base" };
|
|
848
|
+
var SurfaceContext = createContext(DEFAULT_CONTEXT);
|
|
849
|
+
function useSurface() {
|
|
850
|
+
return useContext(SurfaceContext);
|
|
851
|
+
}
|
|
852
|
+
function useSurfaceMode() {
|
|
853
|
+
return useContext(SurfaceContext).mode;
|
|
854
|
+
}
|
|
855
|
+
function useOnSurfaceColor(role = "primary") {
|
|
856
|
+
return getSemanticColors(useSurfaceMode())[ON_SURFACE_TOKEN[role]];
|
|
857
|
+
}
|
|
858
|
+
function insetFloor(mode) {
|
|
859
|
+
const colors = getSemanticColors(mode);
|
|
860
|
+
return colors["surface-inset"] ?? surfaceRampDark.inset;
|
|
861
|
+
}
|
|
862
|
+
function surfaceBackground(level, mode) {
|
|
863
|
+
if (level === "inset") return insetFloor(mode);
|
|
864
|
+
return getSemanticColors(mode)[SURFACE_LEVEL_TOKEN[level]];
|
|
865
|
+
}
|
|
866
|
+
var ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1);
|
|
867
|
+
var PEAK_OVERSHOOT = 1.12;
|
|
868
|
+
function getReducedMotionPreference() {
|
|
869
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
870
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
871
|
+
}
|
|
872
|
+
function usePrefersReducedMotion() {
|
|
873
|
+
const [reduced, setReduced] = useState(getReducedMotionPreference);
|
|
874
|
+
useEffect(() => {
|
|
875
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
876
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
877
|
+
const handler = () => setReduced(mq.matches);
|
|
878
|
+
handler();
|
|
879
|
+
mq.addEventListener?.("change", handler);
|
|
880
|
+
return () => mq.removeEventListener?.("change", handler);
|
|
881
|
+
}, []);
|
|
882
|
+
return reduced;
|
|
883
|
+
}
|
|
884
|
+
function useLiveRepGrowth(active, liveRepIndex, liveVelocity, isNewPeak) {
|
|
885
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
886
|
+
const [liveGrowth] = useState(() => new Animated.Value(1));
|
|
887
|
+
useEffect(() => {
|
|
888
|
+
if (liveRepIndex == null || liveVelocity == null) return;
|
|
889
|
+
if (prefersReducedMotion) {
|
|
890
|
+
liveGrowth.setValue(1);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
liveGrowth.setValue(0);
|
|
894
|
+
if (isNewPeak) {
|
|
895
|
+
Animated.sequence([
|
|
896
|
+
Animated.timing(liveGrowth, {
|
|
897
|
+
toValue: PEAK_OVERSHOOT,
|
|
898
|
+
duration: 220,
|
|
899
|
+
easing: Easing.out(Easing.quad),
|
|
900
|
+
useNativeDriver: false
|
|
901
|
+
}),
|
|
902
|
+
Animated.spring(liveGrowth, {
|
|
903
|
+
toValue: 1,
|
|
904
|
+
friction: 5,
|
|
905
|
+
tension: 140,
|
|
906
|
+
useNativeDriver: false
|
|
907
|
+
})
|
|
908
|
+
]).start();
|
|
909
|
+
} else {
|
|
910
|
+
Animated.timing(liveGrowth, {
|
|
911
|
+
toValue: 1,
|
|
912
|
+
duration: 300,
|
|
913
|
+
easing: ANIMATION_EASING,
|
|
914
|
+
useNativeDriver: false
|
|
915
|
+
}).start();
|
|
916
|
+
}
|
|
917
|
+
}, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveGrowth]);
|
|
918
|
+
return liveGrowth;
|
|
919
|
+
}
|
|
920
|
+
function wrapJsx(fn) {
|
|
921
|
+
return function(type, props, ...rest) {
|
|
922
|
+
if (props && typeof props.className === "string" && props.className) {
|
|
923
|
+
const cn2 = props.className;
|
|
924
|
+
const cssStyle = { $$css: true, [cn2]: cn2 };
|
|
925
|
+
const existing = props.style;
|
|
926
|
+
props = {
|
|
927
|
+
...props,
|
|
928
|
+
style: existing ? [cssStyle, existing] : cssStyle
|
|
929
|
+
};
|
|
930
|
+
delete props.className;
|
|
931
|
+
}
|
|
932
|
+
return fn(type, props, ...rest);
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
var jsx = wrapJsx(jsx$1);
|
|
936
|
+
var jsxs = wrapJsx(jsxs$1);
|
|
937
|
+
|
|
938
|
+
// src/components/custom/charts/SetBarChart.tsx
|
|
939
|
+
function mixHex(a, b, t12) {
|
|
940
|
+
const parse = (h) => {
|
|
941
|
+
const s = h.replace("#", "");
|
|
942
|
+
return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
|
|
943
|
+
};
|
|
944
|
+
const [ar, ag, ab] = parse(a);
|
|
945
|
+
const [br, bg, bb] = parse(b);
|
|
946
|
+
const ch = (x, y) => Math.round(x + (y - x) * t12).toString(16).padStart(2, "0");
|
|
947
|
+
return `#${ch(ar, br)}${ch(ag, bg)}${ch(ab, bb)}`;
|
|
948
|
+
}
|
|
949
|
+
var VARIABLE_FILL = primitiveRamps.cyan[900];
|
|
950
|
+
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
951
|
+
var FIXED_MAX_VALUE = 1.15;
|
|
952
|
+
var PEAK_HEADROOM = 1.03;
|
|
953
|
+
function scaleDenominator(scale, maxValue) {
|
|
954
|
+
return scale === "fixed" ? FIXED_MAX_VALUE : maxValue * PEAK_HEADROOM;
|
|
955
|
+
}
|
|
956
|
+
function valueLabelFontSize(height) {
|
|
957
|
+
return Math.round(Math.max(8, Math.min(12, height * 0.11)));
|
|
958
|
+
}
|
|
959
|
+
var SET_BAR_DEFAULT_HEIGHT = 220;
|
|
960
|
+
var LABEL_ROW_HEIGHT = 16;
|
|
961
|
+
var LABEL_GAP = 3;
|
|
962
|
+
var GAP_RATIO = 0.08;
|
|
963
|
+
var MIN_BAR_GAP = 2;
|
|
964
|
+
var CHUNK_NOTCH_RATIO = 0.25;
|
|
965
|
+
var CHUNK_NOTCH_MIN_PX = 10;
|
|
966
|
+
var BAR_MAX_WIDTH = 120;
|
|
967
|
+
var LABEL_MIN_BAR_WIDTH = 30;
|
|
968
|
+
var DEFAULT_BAR_RADIUS = 5;
|
|
969
|
+
var MIN_BAR_HEIGHT = 4;
|
|
970
|
+
var FLAT_BAR_FRACTION = 0.5;
|
|
971
|
+
var STUB_HEIGHT = 9;
|
|
972
|
+
var REP_GAP = 2;
|
|
973
|
+
function computeBarLayout(plotWidth, count, gapRatio = GAP_RATIO) {
|
|
974
|
+
const rawBarWidth = plotWidth > 0 && count > 0 ? plotWidth / (count + (count - 1) * gapRatio) : BAR_MAX_WIDTH;
|
|
975
|
+
const barWidth = Math.min(BAR_MAX_WIDTH, rawBarWidth);
|
|
976
|
+
const gap = plotWidth === 0 ? MIN_BAR_GAP : Math.max(MIN_BAR_GAP, gapRatio * barWidth);
|
|
977
|
+
return { barWidth, gap };
|
|
978
|
+
}
|
|
979
|
+
function isRep(slot) {
|
|
980
|
+
return slot.kind === "rep";
|
|
981
|
+
}
|
|
982
|
+
function repIndexByCell(cells) {
|
|
983
|
+
let count = 0;
|
|
984
|
+
return cells.map((slot) => isRep(slot) ? count++ : -1);
|
|
985
|
+
}
|
|
986
|
+
function SetBarChart({
|
|
987
|
+
slots,
|
|
988
|
+
colorFor,
|
|
989
|
+
height,
|
|
990
|
+
scale = "peak",
|
|
991
|
+
scaleMax,
|
|
992
|
+
orientation = "up",
|
|
993
|
+
liveRepIndex,
|
|
994
|
+
isNewPeak = false,
|
|
995
|
+
targetReps,
|
|
996
|
+
gapRatio = GAP_RATIO,
|
|
997
|
+
barRadius = DEFAULT_BAR_RADIUS,
|
|
998
|
+
cornerStyle = "top",
|
|
999
|
+
flat = false,
|
|
1000
|
+
showValueLabels: showValueLabelsProp = false,
|
|
1001
|
+
formatValue: formatValue2 = String,
|
|
1002
|
+
todoVariant = "solid",
|
|
1003
|
+
minColumns,
|
|
1004
|
+
renderReference,
|
|
1005
|
+
renderBarOverlay,
|
|
1006
|
+
expandProgress,
|
|
1007
|
+
hideBaseline = false,
|
|
1008
|
+
testID,
|
|
1009
|
+
testIDPrefix = "setbar",
|
|
1010
|
+
accessibilityLabel: accessibilityLabel2,
|
|
1011
|
+
label,
|
|
1012
|
+
className,
|
|
1013
|
+
viewProps = {}
|
|
1014
|
+
}) {
|
|
1015
|
+
const targetPadded = Math.max(slots.length, targetReps ?? 0, minColumns ?? 0);
|
|
1016
|
+
const cells = targetPadded > slots.length ? [
|
|
1017
|
+
...slots,
|
|
1018
|
+
...Array.from({ length: targetPadded - slots.length }, () => ({ kind: "todo" }))
|
|
1019
|
+
] : slots;
|
|
1020
|
+
const repValues = cells.filter(isRep).map((s) => s.value ?? 0);
|
|
1021
|
+
const best = repValues.length > 0 ? Math.max(...repValues) : 0;
|
|
1022
|
+
const flip = orientation === "down";
|
|
1023
|
+
const flipStyle = flip ? { transform: [{ scaleY: -1 }] } : null;
|
|
1024
|
+
const placeholderColor = useOnSurfaceColor("tertiary");
|
|
1025
|
+
const surface = useSurface();
|
|
1026
|
+
const surfaceBg = surfaceBackground(surface.level, surface.mode);
|
|
1027
|
+
const solidTodoColor = mixHex(surfaceBg, placeholderColor, 0.55);
|
|
1028
|
+
const emptyColor = mixHex(surfaceBg, placeholderColor, 0.28);
|
|
1029
|
+
const barCorners = cornerStyle === "all" ? { borderRadius: barRadius } : { borderTopLeftRadius: barRadius, borderTopRightRadius: barRadius };
|
|
1030
|
+
const scaleDenom = scaleMax != null && scaleMax > 0 ? scaleMax * PEAK_HEADROOM : scaleDenominator(scale, best);
|
|
1031
|
+
const showValueLabels = showValueLabelsProp && !flat;
|
|
1032
|
+
const plotHeight = Math.max(0, height - (showValueLabels ? LABEL_ROW_HEIGHT + LABEL_GAP : 0));
|
|
1033
|
+
const yOf = (value) => scaleDenom > 0 ? value / scaleDenom * plotHeight : 0;
|
|
1034
|
+
const flatBarHeight = Math.max(MIN_BAR_HEIGHT, FLAT_BAR_FRACTION * plotHeight);
|
|
1035
|
+
const compactBarHeight = Math.max(MIN_BAR_HEIGHT, plotHeight);
|
|
1036
|
+
const valueBarHeight = (value) => scaleDenom > 0 ? Math.max(MIN_BAR_HEIGHT, Math.min(1, value / scaleDenom) * plotHeight) : MIN_BAR_HEIGHT;
|
|
1037
|
+
const barHeight = (value) => flat ? compactBarHeight : valueBarHeight(value);
|
|
1038
|
+
const liveValue = liveRepIndex != null && liveRepIndex < repValues.length ? repValues[liveRepIndex] : void 0;
|
|
1039
|
+
const liveGrowth = useLiveRepGrowth(true, liveRepIndex, liveValue, isNewPeak);
|
|
1040
|
+
const [plotW, setPlotW] = useState(0);
|
|
1041
|
+
const onPlotLayout = (e) => setPlotW(e.nativeEvent.layout.width);
|
|
1042
|
+
const totalCells = cells.length;
|
|
1043
|
+
const { barWidth, gap } = computeBarLayout(plotW, totalCells, gapRatio);
|
|
1044
|
+
const labelsCrowded = plotW > 0 && barWidth < LABEL_MIN_BAR_WIDTH;
|
|
1045
|
+
const peakRepIndex = repValues.reduce((b, v, i) => v > repValues[b] ? i : b, 0);
|
|
1046
|
+
const showBarLabel = (repIndex) => !labelsCrowded || repIndex === peakRepIndex || repIndex === liveRepIndex;
|
|
1047
|
+
const geometry = { scaleDenom, plotHeight, yOf, best, flip };
|
|
1048
|
+
const { style: externalStyle, ...restProps } = viewProps;
|
|
1049
|
+
const a11y = accessibilityLabel2 != null ? { accessibilityRole: "image", accessibilityLabel: accessibilityLabel2 } : {};
|
|
1050
|
+
const repIndices = repIndexByCell(cells);
|
|
1051
|
+
return /* @__PURE__ */ jsxs(
|
|
1052
|
+
View,
|
|
1053
|
+
{
|
|
1054
|
+
className,
|
|
1055
|
+
style: [{ height, flexDirection: "row" }, externalStyle],
|
|
1056
|
+
testID,
|
|
1057
|
+
...a11y,
|
|
1058
|
+
...restProps,
|
|
1059
|
+
children: [
|
|
1060
|
+
label ? /* @__PURE__ */ jsx(
|
|
1061
|
+
ChartSideRail,
|
|
1062
|
+
{
|
|
1063
|
+
sections: [{ label, testID: `${testIDPrefix}-side-label` }],
|
|
1064
|
+
sectionExtent: height
|
|
1065
|
+
}
|
|
1066
|
+
) : null,
|
|
1067
|
+
/* @__PURE__ */ jsxs(
|
|
1068
|
+
View,
|
|
1069
|
+
{
|
|
1070
|
+
onLayout: onPlotLayout,
|
|
1071
|
+
style: {
|
|
1072
|
+
flex: 1,
|
|
1073
|
+
flexDirection: "row",
|
|
1074
|
+
alignItems: "flex-end",
|
|
1075
|
+
gap,
|
|
1076
|
+
position: "relative",
|
|
1077
|
+
borderBottomWidth: hideBaseline ? 0 : 2,
|
|
1078
|
+
borderBottomColor: placeholderColor,
|
|
1079
|
+
...flipStyle
|
|
1080
|
+
},
|
|
1081
|
+
children: [
|
|
1082
|
+
renderReference?.(geometry),
|
|
1083
|
+
cells.map((slot, i) => {
|
|
1084
|
+
const isChunkBoundary = (slot.leadingGap ?? REP_GAP) > REP_GAP;
|
|
1085
|
+
const extraGap = isChunkBoundary ? Math.max(CHUNK_NOTCH_MIN_PX, CHUNK_NOTCH_RATIO * barWidth) : 0;
|
|
1086
|
+
const column = {
|
|
1087
|
+
flex: 1,
|
|
1088
|
+
maxWidth: BAR_MAX_WIDTH,
|
|
1089
|
+
height: "100%",
|
|
1090
|
+
alignItems: "center",
|
|
1091
|
+
justifyContent: "flex-end",
|
|
1092
|
+
marginLeft: extraGap
|
|
1093
|
+
};
|
|
1094
|
+
if (!isRep(slot)) {
|
|
1095
|
+
return /* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, style: column, children: renderStub(
|
|
1096
|
+
slot.kind,
|
|
1097
|
+
barCorners,
|
|
1098
|
+
// Flat (compact) stubs fill the plot like the compact bars; else the fixed short stub.
|
|
1099
|
+
flat ? compactBarHeight : STUB_HEIGHT,
|
|
1100
|
+
placeholderColor,
|
|
1101
|
+
testIDPrefix,
|
|
1102
|
+
todoVariant,
|
|
1103
|
+
solidTodoColor,
|
|
1104
|
+
emptyColor
|
|
1105
|
+
) }, i);
|
|
1106
|
+
}
|
|
1107
|
+
const repIndex = repIndices[i];
|
|
1108
|
+
const value = slot.value ?? 0;
|
|
1109
|
+
const isLive = liveRepIndex === repIndex;
|
|
1110
|
+
const color = colorFor(value);
|
|
1111
|
+
const barHeightStyle = expandProgress ? expandProgress.interpolate({
|
|
1112
|
+
inputRange: [0, 1],
|
|
1113
|
+
outputRange: [flatBarHeight, valueBarHeight(value)]
|
|
1114
|
+
}) : isLive ? liveGrowth.interpolate({ inputRange: [0, 1], outputRange: [0, barHeight(value)] }) : barHeight(value);
|
|
1115
|
+
const ownLabel = showValueLabels && !renderBarOverlay && showBarLabel(repIndex);
|
|
1116
|
+
const labelBottom = (expandProgress ? valueBarHeight(best) : barHeight(best)) + LABEL_GAP;
|
|
1117
|
+
return /* @__PURE__ */ jsxs(View, { accessibilityElementsHidden: renderBarOverlay == null, style: column, children: [
|
|
1118
|
+
ownLabel && // All labels pin to one aligned row JUST ABOVE THE PEAK BAR (not the container top),
|
|
1119
|
+
// in the muted on-surface-secondary tone — so the row hugs the bars, no floating void.
|
|
1120
|
+
/* @__PURE__ */ jsx(
|
|
1121
|
+
Animated.View,
|
|
1122
|
+
{
|
|
1123
|
+
style: {
|
|
1124
|
+
position: "absolute",
|
|
1125
|
+
bottom: labelBottom,
|
|
1126
|
+
left: 0,
|
|
1127
|
+
right: 0,
|
|
1128
|
+
alignItems: "center",
|
|
1129
|
+
...expandProgress ? { opacity: expandProgress } : null
|
|
1130
|
+
},
|
|
1131
|
+
pointerEvents: "none",
|
|
1132
|
+
children: /* @__PURE__ */ jsx(
|
|
1133
|
+
Text,
|
|
1134
|
+
{
|
|
1135
|
+
className: "text-text-secondary",
|
|
1136
|
+
style: [{ fontSize: valueLabelFontSize(height), fontWeight: "700" }, flipStyle],
|
|
1137
|
+
testID: `${testIDPrefix}-label-${repIndex}`,
|
|
1138
|
+
children: formatValue2(value)
|
|
1139
|
+
}
|
|
1140
|
+
)
|
|
1141
|
+
}
|
|
1142
|
+
),
|
|
1143
|
+
/* @__PURE__ */ jsx(
|
|
1144
|
+
Animated.View,
|
|
1145
|
+
{
|
|
1146
|
+
style: [
|
|
1147
|
+
{ width: "100%", height: barHeightStyle, backgroundColor: color },
|
|
1148
|
+
barCorners,
|
|
1149
|
+
barPaper(color, flip)
|
|
1150
|
+
],
|
|
1151
|
+
testID: `${testIDPrefix}-bar-${repIndex}`
|
|
1152
|
+
}
|
|
1153
|
+
),
|
|
1154
|
+
renderBarOverlay?.(repIndex, value)
|
|
1155
|
+
] }, i);
|
|
1156
|
+
})
|
|
1157
|
+
]
|
|
1158
|
+
}
|
|
1159
|
+
)
|
|
1160
|
+
]
|
|
1161
|
+
}
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
function renderStub(kind, barCorners, stubHeight, placeholderColor, testIDPrefix, todoVariant, solidTodoColor, emptyColor) {
|
|
1165
|
+
const base = {
|
|
1166
|
+
width: "100%",
|
|
1167
|
+
height: stubHeight,
|
|
1168
|
+
...barCorners
|
|
1169
|
+
};
|
|
1170
|
+
if (kind === "variable") {
|
|
1171
|
+
return /* @__PURE__ */ jsx(
|
|
1172
|
+
View,
|
|
1173
|
+
{
|
|
1174
|
+
style: { ...base, backgroundColor: VARIABLE_FILL },
|
|
1175
|
+
testID: `${testIDPrefix}-slot-variable`
|
|
1176
|
+
}
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
if (kind === "continue") {
|
|
1180
|
+
return /* @__PURE__ */ jsx(
|
|
1181
|
+
View,
|
|
1182
|
+
{
|
|
1183
|
+
style: { ...base, borderWidth: 1, borderColor: CONTINUE_OUTLINE },
|
|
1184
|
+
testID: `${testIDPrefix}-slot-continue`
|
|
1185
|
+
}
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
if (kind === "empty") {
|
|
1189
|
+
return /* @__PURE__ */ jsx(
|
|
1190
|
+
View,
|
|
1191
|
+
{
|
|
1192
|
+
style: { ...base, backgroundColor: emptyColor },
|
|
1193
|
+
testID: `${testIDPrefix}-slot-empty`
|
|
1194
|
+
}
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
const todoStyle = todoVariant === "solid" ? { ...base, backgroundColor: solidTodoColor } : { ...base, borderWidth: 1, borderStyle: "dashed", borderColor: placeholderColor };
|
|
1198
|
+
return /* @__PURE__ */ jsx(View, { style: todoStyle, testID: `${testIDPrefix}-slot-todo` });
|
|
1199
|
+
}
|
|
1200
|
+
function sideLabelFontSize(height) {
|
|
1201
|
+
return Math.round(Math.max(7, Math.min(13, height * 0.06)));
|
|
1202
|
+
}
|
|
1203
|
+
var SIDE_LABEL_INITIALS_BELOW = 70;
|
|
1204
|
+
function sideLabelText(label, extent) {
|
|
1205
|
+
if (extent >= SIDE_LABEL_INITIALS_BELOW) return label;
|
|
1206
|
+
return label.trim().split(/\s+/).map((word) => word.charAt(0)).join(" ").toUpperCase();
|
|
1207
|
+
}
|
|
1208
|
+
function ChartSideRail({
|
|
1209
|
+
sections,
|
|
1210
|
+
sectionExtent,
|
|
1211
|
+
variant = "hero"
|
|
1212
|
+
}) {
|
|
1213
|
+
if (!sections.some((s) => s.label)) return null;
|
|
1214
|
+
const isRail = variant === "rail";
|
|
1215
|
+
const labelLength = Math.max(0, sectionExtent - (isRail ? 0 : 6));
|
|
1216
|
+
const fontSize = isRail ? 7 : sideLabelFontSize(sectionExtent);
|
|
1217
|
+
const textStyle = {
|
|
1218
|
+
maxWidth: "100%",
|
|
1219
|
+
textAlign: "center",
|
|
1220
|
+
fontSize,
|
|
1221
|
+
fontWeight: "700",
|
|
1222
|
+
letterSpacing: isRail || fontSize < 10 ? 0 : 2,
|
|
1223
|
+
textTransform: "uppercase"
|
|
1224
|
+
};
|
|
1225
|
+
return /* @__PURE__ */ jsx(View, { style: { width: isRail ? 18 : 34 }, accessibilityElementsHidden: true, children: sections.map((s) => /* @__PURE__ */ jsx(
|
|
1226
|
+
View,
|
|
1227
|
+
{
|
|
1228
|
+
style: { height: sectionExtent, alignItems: "center", justifyContent: "center" },
|
|
1229
|
+
children: s.label ? /* @__PURE__ */ jsx(
|
|
1230
|
+
View,
|
|
1231
|
+
{
|
|
1232
|
+
style: {
|
|
1233
|
+
width: labelLength,
|
|
1234
|
+
alignItems: "center",
|
|
1235
|
+
transform: [{ rotate: "-90deg" }]
|
|
1236
|
+
},
|
|
1237
|
+
children: /* @__PURE__ */ jsx(
|
|
1238
|
+
Text,
|
|
1239
|
+
{
|
|
1240
|
+
className: "text-text-tertiary",
|
|
1241
|
+
style: textStyle,
|
|
1242
|
+
numberOfLines: 1,
|
|
1243
|
+
testID: s.testID,
|
|
1244
|
+
children: isRail ? s.label : sideLabelText(s.label, sectionExtent)
|
|
1245
|
+
}
|
|
1246
|
+
)
|
|
1247
|
+
}
|
|
1248
|
+
) : null
|
|
1249
|
+
},
|
|
1250
|
+
s.testID
|
|
1251
|
+
)) });
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// src/components/custom/Workout/VelocityStrip.tsx
|
|
1255
|
+
var VEL_COLORS = WORKOUT_TOKENS.scale;
|
|
1256
|
+
var bandIdToEffortColor = {
|
|
1257
|
+
speed: sequentialEffort[0],
|
|
1258
|
+
// green-300
|
|
1259
|
+
power: sequentialEffort[2],
|
|
1260
|
+
// amber-300 (gold)
|
|
1261
|
+
strengthSpeed: sequentialEffort[3],
|
|
1262
|
+
// orange-400
|
|
1263
|
+
maximalStrength: sequentialEffort[4],
|
|
1264
|
+
// red-600
|
|
1265
|
+
grinding: sequentialEffort[5]
|
|
1266
|
+
// red-700 — the distinct 5th band
|
|
1267
|
+
};
|
|
1268
|
+
function getVelocityZoneColor(velocity) {
|
|
1269
|
+
if (velocity >= 1) return "vel-green";
|
|
1270
|
+
if (velocity >= 0.75) return "vel-yellow";
|
|
1271
|
+
if (velocity >= 0.5) return "vel-orange";
|
|
1272
|
+
return "vel-red";
|
|
1273
|
+
}
|
|
1274
|
+
function getVelocityZoneName(velocity) {
|
|
1275
|
+
if (velocity >= 1) return "Speed";
|
|
1276
|
+
if (velocity >= 0.75) return "Power";
|
|
1277
|
+
if (velocity >= 0.5) return "Strength-Speed";
|
|
1278
|
+
return "Strength";
|
|
1279
|
+
}
|
|
1280
|
+
var zoneHexMap = {
|
|
1281
|
+
"vel-green": VEL_COLORS.green,
|
|
1282
|
+
"vel-yellow": VEL_COLORS.yellow,
|
|
1283
|
+
"vel-orange": VEL_COLORS.orange,
|
|
1284
|
+
"vel-red": VEL_COLORS.red
|
|
1285
|
+
};
|
|
1286
|
+
function calculateVelocityLoss(velocities) {
|
|
1287
|
+
if (velocities.length < 2) return 0;
|
|
1288
|
+
const best = Math.max(...velocities);
|
|
1289
|
+
if (best <= 0) return 0;
|
|
1290
|
+
const last = velocities[velocities.length - 1];
|
|
1291
|
+
return Math.max(0, Math.round((best - last) / best * 100));
|
|
1292
|
+
}
|
|
1293
|
+
function calculateMeanVelocity(velocities) {
|
|
1294
|
+
if (velocities.length === 0) return 0;
|
|
1295
|
+
const sum = velocities.reduce((acc, v) => acc + v, 0);
|
|
1296
|
+
return sum / velocities.length;
|
|
1297
|
+
}
|
|
1298
|
+
var VL_LOSS_THRESHOLDS = [10, 20, 30];
|
|
1299
|
+
function getVelocityLossColor(lossPct) {
|
|
1300
|
+
const [t1, t22, t32] = VL_LOSS_THRESHOLDS;
|
|
1301
|
+
if (lossPct < t1) return VEL_COLORS.green;
|
|
1302
|
+
if (lossPct < t22) return VEL_COLORS.yellow;
|
|
1303
|
+
if (lossPct < t32) return VEL_COLORS.orange;
|
|
1304
|
+
return VEL_COLORS.red;
|
|
1305
|
+
}
|
|
1306
|
+
function velocityLossForRep(velocity, best) {
|
|
1307
|
+
if (best <= 0) return 0;
|
|
1308
|
+
return Math.max(0, Math.round((best - velocity) / best * 100));
|
|
1309
|
+
}
|
|
1310
|
+
function classifyBand(velocity, bands) {
|
|
1311
|
+
for (const band of bands) {
|
|
1312
|
+
if (band.max === null || velocity < band.max) return band;
|
|
1313
|
+
}
|
|
1314
|
+
return bands[bands.length - 1];
|
|
1315
|
+
}
|
|
1316
|
+
function bandColor(band) {
|
|
1317
|
+
return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
|
|
1318
|
+
}
|
|
1319
|
+
function makeBarColorFor(zones) {
|
|
1320
|
+
const hasZones = zones != null && zones.length > 0;
|
|
1321
|
+
return (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
|
|
1322
|
+
}
|
|
1323
|
+
function getLossStyle(loss) {
|
|
1324
|
+
if (loss > 25) return { color: VEL_COLORS.red };
|
|
1325
|
+
if (loss > 20) return { color: VEL_COLORS.orange };
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
var REP_GAP2 = 2;
|
|
1329
|
+
var WIDE_GAP = 8;
|
|
1330
|
+
function deriveDoneVelocities(set) {
|
|
1331
|
+
switch (set.type) {
|
|
1332
|
+
case "drop":
|
|
1333
|
+
return set.subloads.flat();
|
|
1334
|
+
case "myo":
|
|
1335
|
+
return [...set.activation, ...set.clusters.flat()];
|
|
1336
|
+
default:
|
|
1337
|
+
return set.velocities;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
function chunkedRepSlots(chunks) {
|
|
1341
|
+
const slots = [];
|
|
1342
|
+
chunks.forEach((chunk) => {
|
|
1343
|
+
chunk.forEach((velocity, ri) => {
|
|
1344
|
+
const first = slots.length === 0;
|
|
1345
|
+
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP2 });
|
|
1346
|
+
});
|
|
1347
|
+
});
|
|
1348
|
+
return slots;
|
|
1349
|
+
}
|
|
1350
|
+
function buildSlots(set) {
|
|
1351
|
+
switch (set.type) {
|
|
1352
|
+
case "straight": {
|
|
1353
|
+
const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
|
|
1354
|
+
return Array.from({ length: total }, (_, i) => {
|
|
1355
|
+
const done = i < set.velocities.length;
|
|
1356
|
+
return {
|
|
1357
|
+
kind: done ? "rep" : "todo",
|
|
1358
|
+
velocity: done ? set.velocities[i] : void 0,
|
|
1359
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
1360
|
+
};
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
case "range": {
|
|
1364
|
+
const total = Math.max(set.max, set.velocities.length);
|
|
1365
|
+
return Array.from({ length: total }, (_, i) => {
|
|
1366
|
+
const kind = i < set.velocities.length ? "rep" : i < set.floor ? "todo" : "variable";
|
|
1367
|
+
return {
|
|
1368
|
+
kind,
|
|
1369
|
+
velocity: kind === "rep" ? set.velocities[i] : void 0,
|
|
1370
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
1371
|
+
};
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
case "amrap": {
|
|
1375
|
+
const reps = set.velocities.map((velocity, i) => ({
|
|
1376
|
+
kind: "rep",
|
|
1377
|
+
velocity,
|
|
1378
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
1379
|
+
}));
|
|
1380
|
+
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP2 });
|
|
1381
|
+
return reps;
|
|
1382
|
+
}
|
|
1383
|
+
case "drop":
|
|
1384
|
+
return chunkedRepSlots(set.subloads);
|
|
1385
|
+
case "myo": {
|
|
1386
|
+
const slots = chunkedRepSlots([set.activation, ...set.clusters]);
|
|
1387
|
+
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP2 });
|
|
1388
|
+
return slots;
|
|
1389
|
+
}
|
|
1390
|
+
case "cluster": {
|
|
1391
|
+
const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
|
|
1392
|
+
return Array.from({ length: total }, (_, i) => {
|
|
1393
|
+
const done = i < set.velocities.length;
|
|
1394
|
+
const boundary = i > 0 && i % set.groupSize === 0;
|
|
1395
|
+
return {
|
|
1396
|
+
kind: done ? "rep" : "todo",
|
|
1397
|
+
velocity: done ? set.velocities[i] : void 0,
|
|
1398
|
+
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP2
|
|
1399
|
+
};
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function setAccessibilityLabel(set, repCount) {
|
|
1405
|
+
switch (set.type) {
|
|
1406
|
+
case "straight":
|
|
1407
|
+
return `Velocity strip, straight set, ${repCount} reps`;
|
|
1408
|
+
case "range":
|
|
1409
|
+
return `Velocity strip, rep-range set, floor ${set.floor} to ${set.max}, ${repCount} reps done`;
|
|
1410
|
+
case "amrap":
|
|
1411
|
+
return `Velocity strip, AMRAP set, ${repCount} reps and counting`;
|
|
1412
|
+
case "drop":
|
|
1413
|
+
return `Velocity strip, drop set, ${set.subloads.length} loads, ${repCount} reps`;
|
|
1414
|
+
case "myo":
|
|
1415
|
+
return `Velocity strip, myo-reps set, ${set.clusters.length} clusters${set.open ? ", open" : ""}, ${repCount} reps`;
|
|
1416
|
+
case "cluster":
|
|
1417
|
+
return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
var EXPANDED_HEIGHT = 60;
|
|
1421
|
+
var COMPACT_HEIGHT = 8;
|
|
1422
|
+
var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
|
|
1423
|
+
function DashedReferenceLine({
|
|
1424
|
+
anchor,
|
|
1425
|
+
offset,
|
|
1426
|
+
testID
|
|
1427
|
+
}) {
|
|
1428
|
+
const edge = anchor === "top" ? { top: offset } : { bottom: offset };
|
|
1429
|
+
return /* @__PURE__ */ jsx(
|
|
1430
|
+
View,
|
|
1431
|
+
{
|
|
1432
|
+
accessibilityElementsHidden: true,
|
|
1433
|
+
style: {
|
|
1434
|
+
position: "absolute",
|
|
1435
|
+
left: 0,
|
|
1436
|
+
right: 0,
|
|
1437
|
+
borderTopWidth: 1,
|
|
1438
|
+
borderStyle: "dashed",
|
|
1439
|
+
borderColor: HERO_REFERENCE_COLOR,
|
|
1440
|
+
pointerEvents: "none",
|
|
1441
|
+
...edge
|
|
1442
|
+
},
|
|
1443
|
+
testID
|
|
1444
|
+
}
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
var BAND_LABEL_FONT = "monospace";
|
|
1448
|
+
var VL_LABEL_MIN_PLOT = 65;
|
|
1449
|
+
var VL_SEMANTIC = getSemanticColors("dark");
|
|
1450
|
+
function VelocityLossBands({
|
|
1451
|
+
best,
|
|
1452
|
+
scaleDenom,
|
|
1453
|
+
plotHeight,
|
|
1454
|
+
flip = false
|
|
1455
|
+
}) {
|
|
1456
|
+
if (best <= 0 || scaleDenom <= 0 || plotHeight <= 0) return null;
|
|
1457
|
+
const yOf = (v) => v / scaleDenom * plotHeight;
|
|
1458
|
+
const vl20 = best * 0.8;
|
|
1459
|
+
const vl30 = best * 0.7;
|
|
1460
|
+
const vlFont = Math.round(Math.max(7, Math.min(9, plotHeight * 0.05)));
|
|
1461
|
+
const showLabels = plotHeight >= VL_LABEL_MIN_PLOT;
|
|
1462
|
+
const band = (loV, hiV, color) => /* @__PURE__ */ jsx(
|
|
1463
|
+
View,
|
|
1464
|
+
{
|
|
1465
|
+
style: {
|
|
1466
|
+
position: "absolute",
|
|
1467
|
+
left: 0,
|
|
1468
|
+
right: 0,
|
|
1469
|
+
bottom: yOf(loV),
|
|
1470
|
+
height: Math.max(0, yOf(hiV) - yOf(loV)),
|
|
1471
|
+
backgroundColor: color
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
);
|
|
1475
|
+
const threshold = (v, color, label) => /* @__PURE__ */ jsxs(
|
|
1476
|
+
View,
|
|
1477
|
+
{
|
|
1478
|
+
style: {
|
|
1479
|
+
position: "absolute",
|
|
1480
|
+
left: 0,
|
|
1481
|
+
right: 0,
|
|
1482
|
+
bottom: yOf(v),
|
|
1483
|
+
// Collapse to zero height so the dashed border lands EXACTLY on `yOf(v)` — the
|
|
1484
|
+
// band edge — instead of floating up half a text-row (the label then centres on
|
|
1485
|
+
// the line, breaking it). Matches DashedReferenceLine's bare-border anchoring.
|
|
1486
|
+
height: 0,
|
|
1487
|
+
flexDirection: "row",
|
|
1488
|
+
alignItems: "center"
|
|
1489
|
+
},
|
|
1490
|
+
children: [
|
|
1491
|
+
/* @__PURE__ */ jsx(View, { style: { flex: 9, borderTopWidth: 1, borderStyle: "dashed", borderColor: color } }),
|
|
1492
|
+
showLabels ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1493
|
+
/* @__PURE__ */ jsx(
|
|
1494
|
+
Text,
|
|
1495
|
+
{
|
|
1496
|
+
style: {
|
|
1497
|
+
marginHorizontal: 6,
|
|
1498
|
+
fontSize: vlFont,
|
|
1499
|
+
fontWeight: "800",
|
|
1500
|
+
fontFamily: BAND_LABEL_FONT,
|
|
1501
|
+
color,
|
|
1502
|
+
...flip ? { transform: [{ scaleY: -1 }] } : null
|
|
1503
|
+
},
|
|
1504
|
+
children: label
|
|
1505
|
+
}
|
|
1506
|
+
),
|
|
1507
|
+
/* @__PURE__ */ jsx(View, { style: { flex: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: color } })
|
|
1508
|
+
] }) : null
|
|
1509
|
+
]
|
|
1510
|
+
}
|
|
1511
|
+
);
|
|
1512
|
+
return /* @__PURE__ */ jsxs(
|
|
1513
|
+
View,
|
|
1514
|
+
{
|
|
1515
|
+
accessibilityElementsHidden: true,
|
|
1516
|
+
pointerEvents: "none",
|
|
1517
|
+
style: { position: "absolute", left: 0, right: 0, bottom: 0, top: 0 },
|
|
1518
|
+
testID: "velocity-loss-bands",
|
|
1519
|
+
children: [
|
|
1520
|
+
band(0, vl30, alpha(VL_SEMANTIC["status-error"], 0.09)),
|
|
1521
|
+
band(vl30, vl20, alpha(VL_SEMANTIC["status-warning"], 0.08)),
|
|
1522
|
+
threshold(vl20, alpha(VL_SEMANTIC["status-warning"], 0.75), "VL 20%"),
|
|
1523
|
+
threshold(vl30, alpha(VL_SEMANTIC["status-error"], 0.75), "VL 30%")
|
|
1524
|
+
]
|
|
1525
|
+
}
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
function velocityReferenceOverlay(g, showLossBands) {
|
|
1529
|
+
const referencePx = g.best > 0 && g.scaleDenom > 0 ? Math.min(g.plotHeight, g.yOf(g.best)) : 0;
|
|
1530
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1531
|
+
showLossBands && /* @__PURE__ */ jsx(
|
|
1532
|
+
VelocityLossBands,
|
|
1533
|
+
{
|
|
1534
|
+
best: g.best,
|
|
1535
|
+
scaleDenom: g.scaleDenom,
|
|
1536
|
+
plotHeight: g.plotHeight,
|
|
1537
|
+
flip: g.flip
|
|
1538
|
+
}
|
|
1539
|
+
),
|
|
1540
|
+
referencePx > 0 && /* @__PURE__ */ jsx(
|
|
1541
|
+
DashedReferenceLine,
|
|
1542
|
+
{
|
|
1543
|
+
anchor: "bottom",
|
|
1544
|
+
offset: referencePx,
|
|
1545
|
+
testID: "velocity-hero-reference"
|
|
1546
|
+
}
|
|
1547
|
+
)
|
|
1548
|
+
] });
|
|
1549
|
+
}
|
|
1550
|
+
var ANIMATION_DURATION = 400;
|
|
1551
|
+
function VelocityStrip({
|
|
1552
|
+
velocities,
|
|
1553
|
+
set,
|
|
1554
|
+
zones,
|
|
1555
|
+
barColor = "loss",
|
|
1556
|
+
showLossBands,
|
|
1557
|
+
orientation = "up",
|
|
1558
|
+
scaleMax,
|
|
1559
|
+
hideBaseline,
|
|
1560
|
+
columnSlots,
|
|
1561
|
+
label,
|
|
1562
|
+
liveRepIndex,
|
|
1563
|
+
expanded = true,
|
|
1564
|
+
onToggle,
|
|
1565
|
+
onRepPress,
|
|
1566
|
+
variant = "expanded",
|
|
1567
|
+
height = EXPANDED_HEIGHT,
|
|
1568
|
+
targetReps,
|
|
1569
|
+
scale = "peak",
|
|
1570
|
+
showNumbers = true,
|
|
1571
|
+
showInfo = true,
|
|
1572
|
+
className,
|
|
1573
|
+
...props
|
|
1574
|
+
}) {
|
|
1575
|
+
const lossBandsOn = showLossBands ?? barColor === "loss";
|
|
1576
|
+
const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
|
|
1577
|
+
const maxVelocity = Math.max(...doneVelocities, 0);
|
|
1578
|
+
const meanVelocity = calculateMeanVelocity(doneVelocities);
|
|
1579
|
+
const loss = calculateVelocityLoss(doneVelocities);
|
|
1580
|
+
const framed = showNumbers || showInfo;
|
|
1581
|
+
const hasZones = zones != null && zones.length > 0;
|
|
1582
|
+
const zoneColorFor = makeBarColorFor(zones);
|
|
1583
|
+
const barColorFor = (v) => barColor === "loss" ? getVelocityLossColor(velocityLossForRep(v, maxVelocity)) : zoneColorFor(v);
|
|
1584
|
+
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
1585
|
+
const [expandProgress] = useState(() => new Animated.Value(expanded ? 1 : 0));
|
|
1586
|
+
const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
|
|
1587
|
+
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
1588
|
+
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
1589
|
+
useEffect(() => {
|
|
1590
|
+
if (variant !== "expanded" || !framed) return;
|
|
1591
|
+
Animated.parallel([
|
|
1592
|
+
Animated.timing(expandProgress, {
|
|
1593
|
+
toValue: expanded ? 1 : 0,
|
|
1594
|
+
duration: ANIMATION_DURATION,
|
|
1595
|
+
easing: ANIMATION_EASING,
|
|
1596
|
+
useNativeDriver: false
|
|
1597
|
+
}),
|
|
1598
|
+
Animated.timing(infoOpacity, {
|
|
1599
|
+
toValue: expanded ? 1 : 0,
|
|
1600
|
+
duration: 300,
|
|
1601
|
+
delay: expanded ? 200 : 0,
|
|
1602
|
+
useNativeDriver: false
|
|
1603
|
+
})
|
|
1604
|
+
]).start();
|
|
1605
|
+
}, [expanded, variant, framed, expandProgress, infoOpacity]);
|
|
1606
|
+
if (set == null && velocities == null) return null;
|
|
1607
|
+
const repCount = doneVelocities.length;
|
|
1608
|
+
const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
|
|
1609
|
+
if (variant === "hero") {
|
|
1610
|
+
const heroHeight = height === EXPANDED_HEIGHT && columnSlots == null ? SET_BAR_DEFAULT_HEIGHT : height;
|
|
1611
|
+
const heroSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
1612
|
+
kind: s.kind,
|
|
1613
|
+
value: s.velocity,
|
|
1614
|
+
leadingGap: s.leadingGap
|
|
1615
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
1616
|
+
const total = Math.max(repCount, targetReps ?? repCount);
|
|
1617
|
+
const heroLabel = maxVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(maxVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
1618
|
+
return /* @__PURE__ */ jsx(
|
|
1619
|
+
SetBarChart,
|
|
1620
|
+
{
|
|
1621
|
+
slots: heroSlots,
|
|
1622
|
+
colorFor: barColorFor,
|
|
1623
|
+
height: heroHeight,
|
|
1624
|
+
scale,
|
|
1625
|
+
scaleMax,
|
|
1626
|
+
orientation,
|
|
1627
|
+
liveRepIndex,
|
|
1628
|
+
isNewPeak,
|
|
1629
|
+
targetReps: set || columnSlots ? void 0 : targetReps,
|
|
1630
|
+
label,
|
|
1631
|
+
showValueLabels: true,
|
|
1632
|
+
formatValue: formatVelocity,
|
|
1633
|
+
renderReference: (g) => velocityReferenceOverlay(g, lossBandsOn),
|
|
1634
|
+
hideBaseline: true,
|
|
1635
|
+
testID: "velocity-strip-hero",
|
|
1636
|
+
testIDPrefix: "velocity",
|
|
1637
|
+
accessibilityLabel: heroLabel,
|
|
1638
|
+
className,
|
|
1639
|
+
viewProps: props
|
|
1640
|
+
}
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
if (variant === "compact") {
|
|
1644
|
+
const compactSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
1645
|
+
kind: s.kind,
|
|
1646
|
+
value: s.velocity,
|
|
1647
|
+
leadingGap: s.leadingGap
|
|
1648
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
1649
|
+
const compactHeight = height === EXPANDED_HEIGHT ? COMPACT_HEIGHT : height;
|
|
1650
|
+
return /* @__PURE__ */ jsx(
|
|
1651
|
+
SetBarChart,
|
|
1652
|
+
{
|
|
1653
|
+
slots: compactSlots,
|
|
1654
|
+
colorFor: barColorFor,
|
|
1655
|
+
height: compactHeight,
|
|
1656
|
+
scale,
|
|
1657
|
+
scaleMax,
|
|
1658
|
+
orientation,
|
|
1659
|
+
flat: true,
|
|
1660
|
+
barRadius: 2,
|
|
1661
|
+
cornerStyle: "all",
|
|
1662
|
+
targetReps: set || columnSlots ? void 0 : targetReps,
|
|
1663
|
+
label,
|
|
1664
|
+
hideBaseline: true,
|
|
1665
|
+
testID: "velocity-strip-compact",
|
|
1666
|
+
testIDPrefix: "velocity",
|
|
1667
|
+
accessibilityLabel: miniLabel,
|
|
1668
|
+
className,
|
|
1669
|
+
viewProps: props
|
|
1670
|
+
}
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
if (!framed) {
|
|
1674
|
+
const spotlightSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
1675
|
+
kind: s.kind,
|
|
1676
|
+
value: s.velocity,
|
|
1677
|
+
leadingGap: s.leadingGap
|
|
1678
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
1679
|
+
return /* @__PURE__ */ jsx(
|
|
1680
|
+
SetBarChart,
|
|
1681
|
+
{
|
|
1682
|
+
slots: spotlightSlots,
|
|
1683
|
+
colorFor: barColorFor,
|
|
1684
|
+
height,
|
|
1685
|
+
scale,
|
|
1686
|
+
scaleMax,
|
|
1687
|
+
orientation,
|
|
1688
|
+
liveRepIndex,
|
|
1689
|
+
isNewPeak,
|
|
1690
|
+
barRadius: 2,
|
|
1691
|
+
cornerStyle: "top",
|
|
1692
|
+
targetReps: set ? void 0 : targetReps,
|
|
1693
|
+
label,
|
|
1694
|
+
hideBaseline: true,
|
|
1695
|
+
testID: "velocity-strip-spotlight",
|
|
1696
|
+
testIDPrefix: "velocity",
|
|
1697
|
+
accessibilityLabel: miniLabel,
|
|
1698
|
+
className,
|
|
1699
|
+
viewProps: props
|
|
1700
|
+
}
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
|
|
1704
|
+
const hasInteractiveContainer = onToggle != null;
|
|
1705
|
+
const hasInteractiveReps = onRepPress != null && expanded;
|
|
1706
|
+
const framedSlots = set ? buildSlots(set).map((sl) => ({
|
|
1707
|
+
kind: sl.kind,
|
|
1708
|
+
value: sl.velocity,
|
|
1709
|
+
leadingGap: sl.leadingGap
|
|
1710
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v }));
|
|
1711
|
+
const needsBarOverlay = showNumbers || onRepPress != null;
|
|
1712
|
+
const renderFramedBarOverlay = needsBarOverlay ? (repIndex, value) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1713
|
+
showNumbers && /* @__PURE__ */ jsx(
|
|
1714
|
+
Animated.View,
|
|
1715
|
+
{
|
|
1716
|
+
style: {
|
|
1717
|
+
opacity: expandProgress,
|
|
1718
|
+
position: "absolute",
|
|
1719
|
+
top: -13,
|
|
1720
|
+
left: 0,
|
|
1721
|
+
right: 0,
|
|
1722
|
+
alignItems: "center"
|
|
1723
|
+
},
|
|
1724
|
+
accessibilityElementsHidden: true,
|
|
1725
|
+
pointerEvents: "none",
|
|
1726
|
+
children: /* @__PURE__ */ jsx(
|
|
1727
|
+
Text,
|
|
1728
|
+
{
|
|
1729
|
+
className: "text-text-secondary",
|
|
1730
|
+
style: { fontSize: 8, fontWeight: "600" },
|
|
1731
|
+
testID: `velocity-label-${repIndex}`,
|
|
1732
|
+
children: formatVelocity(value)
|
|
1733
|
+
}
|
|
1734
|
+
)
|
|
1735
|
+
}
|
|
1736
|
+
),
|
|
1737
|
+
onRepPress && expanded && /* @__PURE__ */ jsx(
|
|
1738
|
+
Pressable,
|
|
1739
|
+
{
|
|
1740
|
+
style: { position: "absolute", top: 0, bottom: 0, left: 0, right: 0 },
|
|
1741
|
+
onPress: () => onRepPress(repIndex, value),
|
|
1742
|
+
accessibilityRole: "button",
|
|
1743
|
+
accessibilityLabel: `Rep ${repIndex + 1}: ${formatVelocity(value)} meters per second, tap for details`,
|
|
1744
|
+
testID: `velocity-bar-pressable-${repIndex}`
|
|
1745
|
+
}
|
|
1746
|
+
)
|
|
1747
|
+
] }) : void 0;
|
|
1748
|
+
const stripContent = /* @__PURE__ */ jsxs(
|
|
1749
|
+
Animated.View,
|
|
1750
|
+
{
|
|
1751
|
+
className: [className, "bg-surface-raised"].filter(Boolean).join(" "),
|
|
1752
|
+
style: { width: "100%", borderRadius: 6, paddingTop: 16, paddingBottom: showInfo ? 8 : 4 },
|
|
1753
|
+
accessibilityRole: hasInteractiveContainer || hasInteractiveReps ? "none" : "button",
|
|
1754
|
+
accessibilityLabel: hasInteractiveContainer || hasInteractiveReps ? void 0 : stripLabel,
|
|
1755
|
+
testID: "velocity-strip",
|
|
1756
|
+
...props,
|
|
1757
|
+
children: [
|
|
1758
|
+
/* @__PURE__ */ jsx(
|
|
1759
|
+
SetBarChart,
|
|
1760
|
+
{
|
|
1761
|
+
slots: framedSlots,
|
|
1762
|
+
colorFor: barColorFor,
|
|
1763
|
+
height,
|
|
1764
|
+
scale,
|
|
1765
|
+
scaleMax,
|
|
1766
|
+
expandProgress,
|
|
1767
|
+
renderBarOverlay: renderFramedBarOverlay,
|
|
1768
|
+
targetReps: set ? void 0 : targetReps,
|
|
1769
|
+
barRadius: 2,
|
|
1770
|
+
cornerStyle: "top",
|
|
1771
|
+
hideBaseline: true,
|
|
1772
|
+
testIDPrefix: "velocity"
|
|
1773
|
+
}
|
|
1774
|
+
),
|
|
1775
|
+
expanded && showInfo && /* @__PURE__ */ jsxs(
|
|
1776
|
+
Animated.View,
|
|
1777
|
+
{
|
|
1778
|
+
style: {
|
|
1779
|
+
flexDirection: "row",
|
|
1780
|
+
justifyContent: "space-between",
|
|
1781
|
+
opacity: infoOpacity,
|
|
1782
|
+
marginTop: 6,
|
|
1783
|
+
paddingHorizontal: 6
|
|
1784
|
+
},
|
|
1785
|
+
testID: "velocity-info-row",
|
|
1786
|
+
children: [
|
|
1787
|
+
/* @__PURE__ */ jsxs(
|
|
1788
|
+
Text,
|
|
1789
|
+
{
|
|
1790
|
+
className: "text-text-secondary",
|
|
1791
|
+
style: { fontSize: 10, fontFamily: "Inter, sans-serif" },
|
|
1792
|
+
children: [
|
|
1793
|
+
meanZone,
|
|
1794
|
+
" ",
|
|
1795
|
+
"\xB7",
|
|
1796
|
+
" ",
|
|
1797
|
+
formatVelocity(meanVelocity),
|
|
1798
|
+
" m/s"
|
|
1799
|
+
]
|
|
1800
|
+
}
|
|
1801
|
+
),
|
|
1802
|
+
/* @__PURE__ */ jsxs(
|
|
1803
|
+
Text,
|
|
1804
|
+
{
|
|
1805
|
+
className: "text-text-secondary",
|
|
1806
|
+
style: { fontSize: 10, fontFamily: "Inter, sans-serif", ...getLossStyle(loss) },
|
|
1807
|
+
children: [
|
|
1808
|
+
"Loss: ",
|
|
1809
|
+
loss,
|
|
1810
|
+
"%"
|
|
1811
|
+
]
|
|
1812
|
+
}
|
|
1813
|
+
)
|
|
1814
|
+
]
|
|
1815
|
+
}
|
|
1816
|
+
)
|
|
1817
|
+
]
|
|
1818
|
+
}
|
|
1819
|
+
);
|
|
1820
|
+
if (onToggle) {
|
|
1821
|
+
return /* @__PURE__ */ jsx(
|
|
1822
|
+
Pressable,
|
|
1823
|
+
{
|
|
1824
|
+
onPress: onToggle,
|
|
1825
|
+
accessibilityRole: "button",
|
|
1826
|
+
accessibilityLabel: stripLabel,
|
|
1827
|
+
testID: "velocity-strip-pressable",
|
|
1828
|
+
children: stripContent
|
|
1829
|
+
}
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
return stripContent;
|
|
1833
|
+
}
|
|
1834
|
+
function cn(...inputs) {
|
|
1835
|
+
return twMerge(clsx(inputs));
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// src/components/custom/Workout/PlaceholderStrip.tsx
|
|
1839
|
+
function SingleStrip({
|
|
1840
|
+
width,
|
|
1841
|
+
className,
|
|
1842
|
+
...props
|
|
1843
|
+
}) {
|
|
1844
|
+
return /* @__PURE__ */ jsx(
|
|
1845
|
+
View,
|
|
1846
|
+
{
|
|
1847
|
+
className,
|
|
1848
|
+
style: [
|
|
1849
|
+
{
|
|
1850
|
+
height: 3,
|
|
1851
|
+
backgroundColor: WORKOUT_TOKENS.placeholder.fill,
|
|
1852
|
+
borderRadius: 2,
|
|
1853
|
+
opacity: 0.5
|
|
1854
|
+
},
|
|
1855
|
+
width != null ? { width } : { flex: 1 }
|
|
1856
|
+
],
|
|
1857
|
+
accessibilityRole: "image",
|
|
1858
|
+
accessibilityLabel: "Planned set, not yet completed",
|
|
1859
|
+
testID: "placeholder-strip",
|
|
1860
|
+
...props
|
|
1861
|
+
}
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
function SegmentedStrip({
|
|
1865
|
+
segments = 3,
|
|
1866
|
+
width,
|
|
1867
|
+
className,
|
|
1868
|
+
...props
|
|
1869
|
+
}) {
|
|
1870
|
+
return /* @__PURE__ */ jsx(
|
|
1871
|
+
View,
|
|
1872
|
+
{
|
|
1873
|
+
className: cn("flex-row", className),
|
|
1874
|
+
style: [
|
|
1875
|
+
{ height: 3, gap: 2, opacity: 0.5 },
|
|
1876
|
+
width != null ? { width } : { flex: 1 }
|
|
1877
|
+
],
|
|
1878
|
+
accessibilityRole: "image",
|
|
1879
|
+
accessibilityLabel: "Planned set, not yet completed",
|
|
1880
|
+
testID: "placeholder-strip",
|
|
1881
|
+
...props,
|
|
1882
|
+
children: Array.from({ length: segments }, (_, i) => /* @__PURE__ */ jsx(
|
|
1883
|
+
View,
|
|
1884
|
+
{
|
|
1885
|
+
style: {
|
|
1886
|
+
flex: 1,
|
|
1887
|
+
height: 3,
|
|
1888
|
+
backgroundColor: WORKOUT_TOKENS.placeholder.fill,
|
|
1889
|
+
borderRadius: 1,
|
|
1890
|
+
minWidth: 4
|
|
1891
|
+
},
|
|
1892
|
+
accessibilityElementsHidden: true,
|
|
1893
|
+
testID: "placeholder-segment"
|
|
1894
|
+
},
|
|
1895
|
+
i
|
|
1896
|
+
))
|
|
1897
|
+
}
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
function PlaceholderStrip({
|
|
1901
|
+
mode = "single",
|
|
1902
|
+
segments,
|
|
1903
|
+
...props
|
|
1904
|
+
}) {
|
|
1905
|
+
if (mode === "segmented") {
|
|
1906
|
+
return /* @__PURE__ */ jsx(SegmentedStrip, { segments, ...props });
|
|
1907
|
+
}
|
|
1908
|
+
return /* @__PURE__ */ jsx(SingleStrip, { ...props });
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// src/components/icons/SvgIcon.tsx
|
|
1912
|
+
function SvgIcon({
|
|
1913
|
+
size = 24,
|
|
1914
|
+
color = "currentColor",
|
|
1915
|
+
strokeWidth = 2,
|
|
1916
|
+
fill = "none",
|
|
1917
|
+
title,
|
|
1918
|
+
children
|
|
1919
|
+
}) {
|
|
1920
|
+
return /* @__PURE__ */ jsxs(
|
|
1921
|
+
"svg",
|
|
1922
|
+
{
|
|
1923
|
+
width: size,
|
|
1924
|
+
height: size,
|
|
1925
|
+
viewBox: "0 0 24 24",
|
|
1926
|
+
fill,
|
|
1927
|
+
stroke: color,
|
|
1928
|
+
strokeWidth,
|
|
1929
|
+
strokeLinecap: "round",
|
|
1930
|
+
strokeLinejoin: "round",
|
|
1931
|
+
...title ? { role: "img", "aria-label": title } : { "aria-hidden": true },
|
|
1932
|
+
children: [
|
|
1933
|
+
title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
|
|
1934
|
+
children
|
|
1935
|
+
]
|
|
1936
|
+
}
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
// src/components/icons/icons.tsx
|
|
1941
|
+
function StarIcon(props) {
|
|
1942
|
+
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" }) });
|
|
1943
|
+
}
|
|
1944
|
+
function ScaleIcon(props) {
|
|
1945
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1946
|
+
/* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1947
|
+
/* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1948
|
+
/* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
|
|
1949
|
+
/* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
|
|
1950
|
+
/* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
|
|
1951
|
+
] });
|
|
1952
|
+
}
|
|
1953
|
+
function AlertTriangleIcon(props) {
|
|
1954
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1955
|
+
/* @__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" }),
|
|
1956
|
+
/* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
|
|
1957
|
+
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
1958
|
+
] });
|
|
1959
|
+
}
|
|
1960
|
+
function TrendingDownIcon(props) {
|
|
1961
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1962
|
+
/* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
|
|
1963
|
+
/* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
|
|
1964
|
+
] });
|
|
1965
|
+
}
|
|
1966
|
+
function CircleSlashIcon(props) {
|
|
1967
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1968
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1969
|
+
/* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
|
|
1970
|
+
] });
|
|
1971
|
+
}
|
|
1972
|
+
function AwardIcon(props) {
|
|
1973
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1974
|
+
/* @__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" }),
|
|
1975
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
|
|
1976
|
+
] });
|
|
1977
|
+
}
|
|
1978
|
+
function InfoIcon(props) {
|
|
1979
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1980
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1981
|
+
/* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
|
|
1982
|
+
/* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
|
|
1983
|
+
] });
|
|
1688
1984
|
}
|
|
1689
|
-
|
|
1690
|
-
// src/components/custom/Workout/BaseBadge.tsx
|
|
1691
1985
|
var t = getSemanticColors("dark");
|
|
1692
1986
|
var baseBadgeSizeConfig = {
|
|
1693
1987
|
sm: { fontSize: 9, paddingH: 6, paddingV: 2, iconSize: 10 },
|
|
@@ -1802,565 +2096,257 @@ function PrBadge({
|
|
|
1802
2096
|
easing: Easing.bezier(0.34, 1.56, 0.64, 1),
|
|
1803
2097
|
useNativeDriver: true
|
|
1804
2098
|
})
|
|
1805
|
-
]).start();
|
|
1806
|
-
}, [animate, scale, opacity]);
|
|
1807
|
-
const badge = compact ? /* @__PURE__ */ jsx(
|
|
1808
|
-
View,
|
|
1809
|
-
{
|
|
1810
|
-
className,
|
|
1811
|
-
accessibilityRole: "image",
|
|
1812
|
-
accessibilityLabel: `Personal record: ${resolvedLabel}`,
|
|
1813
|
-
testID: "pr-badge-star",
|
|
1814
|
-
...props,
|
|
1815
|
-
children: /* @__PURE__ */ jsx(StarIcon, { size: 14, color: BRAND_PRIMARY, fill: BRAND_PRIMARY, strokeWidth: 2 })
|
|
1816
|
-
}
|
|
1817
|
-
) : /* @__PURE__ */ jsx(
|
|
1818
|
-
BaseBadge,
|
|
1819
|
-
{
|
|
1820
|
-
variant: "pr",
|
|
1821
|
-
className,
|
|
1822
|
-
accessibilityLabel: `Personal record: ${resolvedLabel}`,
|
|
1823
|
-
...props,
|
|
1824
|
-
children: /* @__PURE__ */ jsxs(
|
|
1825
|
-
Text,
|
|
1826
|
-
{
|
|
1827
|
-
style: {
|
|
1828
|
-
fontSize: 12,
|
|
1829
|
-
color: BRAND_PRIMARY,
|
|
1830
|
-
fontWeight: "700",
|
|
1831
|
-
fontFamily: "Inter, sans-serif"
|
|
1832
|
-
},
|
|
1833
|
-
children: [
|
|
1834
|
-
"\u2605",
|
|
1835
|
-
" ",
|
|
1836
|
-
resolvedLabel
|
|
1837
|
-
]
|
|
1838
|
-
}
|
|
1839
|
-
)
|
|
1840
|
-
}
|
|
1841
|
-
);
|
|
1842
|
-
if (animate) {
|
|
1843
|
-
return /* @__PURE__ */ jsx(
|
|
1844
|
-
Animated.View,
|
|
1845
|
-
{
|
|
1846
|
-
style: { transform: [{ scale }], opacity },
|
|
1847
|
-
testID: "pr-badge-animated",
|
|
1848
|
-
children: badge
|
|
1849
|
-
}
|
|
1850
|
-
);
|
|
1851
|
-
}
|
|
1852
|
-
return badge;
|
|
1853
|
-
}
|
|
1854
|
-
var DARK = getSemanticColors("dark");
|
|
1855
|
-
var TEXT_ACTIVE = DARK["text-primary"];
|
|
1856
|
-
var TEXT_MUTED = DARK["text-secondary"];
|
|
1857
|
-
var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
|
|
1858
|
-
var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
|
|
1859
|
-
var typeBadge = {
|
|
1860
|
-
fontSize: 10,
|
|
1861
|
-
fontWeight: "700",
|
|
1862
|
-
fontFamily: "Inter, sans-serif",
|
|
1863
|
-
color: WORKOUT_TOKENS.scale.orange,
|
|
1864
|
-
backgroundColor: "rgba(255,165,2,0.12)",
|
|
1865
|
-
paddingVertical: 1,
|
|
1866
|
-
paddingHorizontal: 5,
|
|
1867
|
-
borderRadius: 3,
|
|
1868
|
-
overflow: "hidden"
|
|
1869
|
-
};
|
|
1870
|
-
function displayReps(set) {
|
|
1871
|
-
return set.state === "done" ? set.reps : set.target.reps;
|
|
1872
|
-
}
|
|
1873
|
-
function displayWeight(set) {
|
|
1874
|
-
return set.state === "done" ? set.weight : set.target.weight;
|
|
1875
|
-
}
|
|
1876
|
-
function plannedReps(set) {
|
|
1877
|
-
if (set.state === "done") return set.velocities.length;
|
|
1878
|
-
if (set.state === "live") return set.target.reps;
|
|
1879
|
-
return set.planned ?? set.target.reps;
|
|
1880
|
-
}
|
|
1881
|
-
function accessibilityLabel(set) {
|
|
1882
|
-
const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
|
|
1883
|
-
if (set.state === "live") return `${base}, in progress`;
|
|
1884
|
-
if (set.state === "todo") return `${base}, upcoming`;
|
|
1885
|
-
return base;
|
|
1886
|
-
}
|
|
1887
|
-
function Cell({
|
|
1888
|
-
width,
|
|
1889
|
-
children,
|
|
1890
|
-
testID
|
|
1891
|
-
}) {
|
|
1892
|
-
return /* @__PURE__ */ jsx(View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
|
|
1893
|
-
}
|
|
1894
|
-
function RowStrip({ set }) {
|
|
1895
|
-
const zones = set.velocityZones;
|
|
1896
|
-
if (set.state === "live") {
|
|
1897
|
-
return /* @__PURE__ */ jsx(
|
|
1898
|
-
VelocityStrip,
|
|
1899
|
-
{
|
|
1900
|
-
variant: "expanded",
|
|
1901
|
-
showNumbers: false,
|
|
1902
|
-
showInfo: false,
|
|
1903
|
-
height: 24,
|
|
1904
|
-
scale: "fixed",
|
|
1905
|
-
set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
|
|
1906
|
-
zones
|
|
1907
|
-
}
|
|
1908
|
-
);
|
|
1909
|
-
}
|
|
1910
|
-
const velocities = set.state === "done" ? set.velocities : [];
|
|
1911
|
-
return /* @__PURE__ */ jsx(
|
|
1912
|
-
VelocityStrip,
|
|
1913
|
-
{
|
|
1914
|
-
variant: "mini",
|
|
1915
|
-
set: { type: "straight", velocities, planned: plannedReps(set) },
|
|
1916
|
-
zones
|
|
1917
|
-
}
|
|
1918
|
-
);
|
|
1919
|
-
}
|
|
1920
|
-
function SetRow(set) {
|
|
1921
|
-
const live = set.state === "live";
|
|
1922
|
-
const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
|
|
1923
|
-
const rpe = set.state === "todo" ? null : set.rpe;
|
|
1924
|
-
return /* @__PURE__ */ jsxs(
|
|
1925
|
-
View,
|
|
1926
|
-
{
|
|
1927
|
-
style: { paddingVertical: 6, paddingHorizontal: 8 },
|
|
1928
|
-
accessibilityLabel: accessibilityLabel(set),
|
|
1929
|
-
testID: "set-row",
|
|
1930
|
-
children: [
|
|
1931
|
-
/* @__PURE__ */ jsxs(View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
|
|
1932
|
-
/* @__PURE__ */ jsx(Cell, { width: COL.set, testID: "set-row-set-number", children: set.setType ? /* @__PURE__ */ jsx(Text, { style: typeBadge, testID: "set-row-type-badge", children: set.setType }) : /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor, fontWeight: live ? "700" : "600" }, children: set.setNumber }) }),
|
|
1933
|
-
/* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
|
|
1934
|
-
/* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
|
|
1935
|
-
/* @__PURE__ */ jsx(Cell, { width: COL.rpe, testID: "set-row-rpe", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: TEXT_MUTED }, children: rpe != null ? roundRpe(rpe) : "\u2014" }) })
|
|
1936
|
-
] }),
|
|
1937
|
-
/* @__PURE__ */ jsx(View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
|
|
1938
|
-
]
|
|
1939
|
-
}
|
|
1940
|
-
);
|
|
1941
|
-
}
|
|
1942
|
-
var PREV_COLUMN = { label: "PREV", width: void 0 };
|
|
1943
|
-
function buildColumns(unit, showPrevious) {
|
|
1944
|
-
return [
|
|
1945
|
-
{ label: "SET", width: 36 },
|
|
1946
|
-
...showPrevious ? [PREV_COLUMN] : [],
|
|
1947
|
-
{ label: "REPS", width: 44 },
|
|
1948
|
-
{ label: unit.toUpperCase(), width: 56 },
|
|
1949
|
-
{ label: "RPE", width: 36 }
|
|
1950
|
-
];
|
|
1951
|
-
}
|
|
1952
|
-
function SetTableHeader({
|
|
1953
|
-
unit = "lbs",
|
|
1954
|
-
showPrevious = true,
|
|
1955
|
-
testID = "table-header"
|
|
1956
|
-
}) {
|
|
1957
|
-
const columns = buildColumns(unit, showPrevious);
|
|
1958
|
-
return /* @__PURE__ */ jsx(
|
|
1959
|
-
View,
|
|
1960
|
-
{
|
|
1961
|
-
className: "flex-row",
|
|
1962
|
-
style: {
|
|
1963
|
-
paddingVertical: 8,
|
|
1964
|
-
paddingHorizontal: 8,
|
|
1965
|
-
paddingBottom: 4,
|
|
1966
|
-
...showPrevious ? {} : { justifyContent: "space-between" }
|
|
1967
|
-
},
|
|
1968
|
-
testID,
|
|
1969
|
-
children: columns.map(({ label, width }) => {
|
|
1970
|
-
const isFlex = width === void 0;
|
|
1971
|
-
return /* @__PURE__ */ jsx(
|
|
1972
|
-
View,
|
|
1973
|
-
{
|
|
1974
|
-
style: {
|
|
1975
|
-
...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
|
|
1976
|
-
alignItems: "center",
|
|
1977
|
-
justifyContent: "center",
|
|
1978
|
-
gap: 8
|
|
1979
|
-
},
|
|
1980
|
-
...isFlex ? { className: "flex-1" } : {},
|
|
1981
|
-
children: /* @__PURE__ */ jsx(
|
|
1982
|
-
Text,
|
|
1983
|
-
{
|
|
1984
|
-
className: "text-text-tertiary",
|
|
1985
|
-
style: {
|
|
1986
|
-
fontSize: 10,
|
|
1987
|
-
fontWeight: "600",
|
|
1988
|
-
fontFamily: "Inter, sans-serif",
|
|
1989
|
-
textTransform: "uppercase",
|
|
1990
|
-
letterSpacing: 0.8
|
|
1991
|
-
},
|
|
1992
|
-
children: label
|
|
1993
|
-
}
|
|
1994
|
-
)
|
|
1995
|
-
},
|
|
1996
|
-
label
|
|
1997
|
-
);
|
|
1998
|
-
})
|
|
1999
|
-
}
|
|
2000
|
-
);
|
|
2001
|
-
}
|
|
2002
|
-
var METRIC_FONT = "Inter, sans-serif";
|
|
2003
|
-
function MetricCell({ children, color, fontSize = 11 }) {
|
|
2004
|
-
return /* @__PURE__ */ jsx(
|
|
2005
|
-
Text,
|
|
2006
|
-
{
|
|
2007
|
-
style: {
|
|
2008
|
-
fontFamily: METRIC_FONT,
|
|
2009
|
-
fontSize,
|
|
2010
|
-
color,
|
|
2011
|
-
fontWeight: "600",
|
|
2012
|
-
letterSpacing: 1
|
|
2013
|
-
},
|
|
2014
|
-
children
|
|
2015
|
-
}
|
|
2016
|
-
);
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
// src/components/custom/Workout/SetsRepsLoad.tsx
|
|
2020
|
-
var t2 = getSemanticColors("dark");
|
|
2021
|
-
var TIMES = "\xD7";
|
|
2022
|
-
var AT = "@";
|
|
2023
|
-
function SetsRepsLoad({
|
|
2024
|
-
sets,
|
|
2025
|
-
reps,
|
|
2026
|
-
load,
|
|
2027
|
-
unit = "lb",
|
|
2028
|
-
fontSize,
|
|
2029
|
-
className,
|
|
2030
|
-
...props
|
|
2031
|
-
}) {
|
|
2032
|
-
const value = t2["text-primary"];
|
|
2033
|
-
const sep = t2["text-tertiary"];
|
|
2034
|
-
return /* @__PURE__ */ jsxs(
|
|
2099
|
+
]).start();
|
|
2100
|
+
}, [animate, scale, opacity]);
|
|
2101
|
+
const badge = compact ? /* @__PURE__ */ jsx(
|
|
2035
2102
|
View,
|
|
2036
2103
|
{
|
|
2037
2104
|
className,
|
|
2038
|
-
|
|
2039
|
-
accessibilityLabel:
|
|
2040
|
-
testID: "
|
|
2105
|
+
accessibilityRole: "image",
|
|
2106
|
+
accessibilityLabel: `Personal record: ${resolvedLabel}`,
|
|
2107
|
+
testID: "pr-badge-star",
|
|
2041
2108
|
...props,
|
|
2042
|
-
children:
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2109
|
+
children: /* @__PURE__ */ jsx(StarIcon, { size: 14, color: BRAND_PRIMARY, fill: BRAND_PRIMARY, strokeWidth: 2 })
|
|
2110
|
+
}
|
|
2111
|
+
) : /* @__PURE__ */ jsx(
|
|
2112
|
+
BaseBadge,
|
|
2113
|
+
{
|
|
2114
|
+
variant: "pr",
|
|
2115
|
+
className,
|
|
2116
|
+
accessibilityLabel: `Personal record: ${resolvedLabel}`,
|
|
2117
|
+
...props,
|
|
2118
|
+
children: /* @__PURE__ */ jsxs(
|
|
2119
|
+
Text,
|
|
2120
|
+
{
|
|
2121
|
+
style: {
|
|
2122
|
+
fontSize: 12,
|
|
2123
|
+
color: BRAND_PRIMARY,
|
|
2124
|
+
fontWeight: "700",
|
|
2125
|
+
fontFamily: "Inter, sans-serif"
|
|
2126
|
+
},
|
|
2127
|
+
children: [
|
|
2128
|
+
"\u2605",
|
|
2129
|
+
" ",
|
|
2130
|
+
resolvedLabel
|
|
2131
|
+
]
|
|
2132
|
+
}
|
|
2133
|
+
)
|
|
2050
2134
|
}
|
|
2051
2135
|
);
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
r = parseInt(cleaned.slice(0, 2), 16);
|
|
2062
|
-
g = parseInt(cleaned.slice(2, 4), 16);
|
|
2063
|
-
b = parseInt(cleaned.slice(4, 6), 16);
|
|
2064
|
-
} else {
|
|
2065
|
-
return null;
|
|
2136
|
+
if (animate) {
|
|
2137
|
+
return /* @__PURE__ */ jsx(
|
|
2138
|
+
Animated.View,
|
|
2139
|
+
{
|
|
2140
|
+
style: { transform: [{ scale }], opacity },
|
|
2141
|
+
testID: "pr-badge-animated",
|
|
2142
|
+
children: badge
|
|
2143
|
+
}
|
|
2144
|
+
);
|
|
2066
2145
|
}
|
|
2067
|
-
return
|
|
2146
|
+
return badge;
|
|
2068
2147
|
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
}
|
|
2088
|
-
if (h < 0) h += 360;
|
|
2089
|
-
return { h, s, v };
|
|
2148
|
+
var DARK = getSemanticColors("dark");
|
|
2149
|
+
var TEXT_ACTIVE = DARK["text-primary"];
|
|
2150
|
+
var TEXT_MUTED = DARK["text-secondary"];
|
|
2151
|
+
var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
|
|
2152
|
+
var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
|
|
2153
|
+
var typeBadge = {
|
|
2154
|
+
fontSize: 10,
|
|
2155
|
+
fontWeight: "700",
|
|
2156
|
+
fontFamily: "Inter, sans-serif",
|
|
2157
|
+
color: WORKOUT_TOKENS.scale.orange,
|
|
2158
|
+
backgroundColor: "rgba(255,165,2,0.12)",
|
|
2159
|
+
paddingVertical: 1,
|
|
2160
|
+
paddingHorizontal: 5,
|
|
2161
|
+
borderRadius: 3,
|
|
2162
|
+
overflow: "hidden"
|
|
2163
|
+
};
|
|
2164
|
+
function displayReps(set) {
|
|
2165
|
+
return set.state === "done" ? set.reps : set.target.reps;
|
|
2090
2166
|
}
|
|
2091
|
-
function
|
|
2092
|
-
|
|
2093
|
-
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
2094
|
-
const m = v - c;
|
|
2095
|
-
let rPrime, gPrime, bPrime;
|
|
2096
|
-
if (h >= 0 && h < 60) {
|
|
2097
|
-
rPrime = c;
|
|
2098
|
-
gPrime = x;
|
|
2099
|
-
bPrime = 0;
|
|
2100
|
-
} else if (h >= 60 && h < 120) {
|
|
2101
|
-
rPrime = x;
|
|
2102
|
-
gPrime = c;
|
|
2103
|
-
bPrime = 0;
|
|
2104
|
-
} else if (h >= 120 && h < 180) {
|
|
2105
|
-
rPrime = 0;
|
|
2106
|
-
gPrime = c;
|
|
2107
|
-
bPrime = x;
|
|
2108
|
-
} else if (h >= 180 && h < 240) {
|
|
2109
|
-
rPrime = 0;
|
|
2110
|
-
gPrime = x;
|
|
2111
|
-
bPrime = c;
|
|
2112
|
-
} else if (h >= 240 && h < 300) {
|
|
2113
|
-
rPrime = x;
|
|
2114
|
-
gPrime = 0;
|
|
2115
|
-
bPrime = c;
|
|
2116
|
-
} else {
|
|
2117
|
-
rPrime = c;
|
|
2118
|
-
gPrime = 0;
|
|
2119
|
-
bPrime = x;
|
|
2120
|
-
}
|
|
2121
|
-
return {
|
|
2122
|
-
r: Math.round((rPrime + m) * 255),
|
|
2123
|
-
g: Math.round((gPrime + m) * 255),
|
|
2124
|
-
b: Math.round((bPrime + m) * 255)
|
|
2125
|
-
};
|
|
2167
|
+
function displayWeight(set) {
|
|
2168
|
+
return set.state === "done" ? set.weight : set.target.weight;
|
|
2126
2169
|
}
|
|
2127
|
-
function
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
};
|
|
2132
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
2170
|
+
function plannedReps(set) {
|
|
2171
|
+
if (set.state === "done") return set.velocities.length;
|
|
2172
|
+
if (set.state === "live") return set.target.reps;
|
|
2173
|
+
return set.planned ?? set.target.reps;
|
|
2133
2174
|
}
|
|
2134
|
-
function
|
|
2135
|
-
const
|
|
2136
|
-
if (
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
const newRgb = hsvToRgb(hsv.h, hsv.s, newV);
|
|
2140
|
-
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
|
|
2175
|
+
function accessibilityLabel(set) {
|
|
2176
|
+
const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
|
|
2177
|
+
if (set.state === "live") return `${base}, in progress`;
|
|
2178
|
+
if (set.state === "todo") return `${base}, upcoming`;
|
|
2179
|
+
return base;
|
|
2141
2180
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
darker: "rgba(0, 0, 0, 0.6)"
|
|
2149
|
-
},
|
|
2150
|
-
// For neutral dark surfaces (~#1F2937)
|
|
2151
|
-
neutralDark: {
|
|
2152
|
-
dark: "rgba(0, 0, 0, 0.4)",
|
|
2153
|
-
light: "rgba(255, 255, 255, 0.08)",
|
|
2154
|
-
darker: "rgba(0, 0, 0, 0.5)"
|
|
2155
|
-
},
|
|
2156
|
-
// For light surfaces (white/light gray backgrounds)
|
|
2157
|
-
neutralLight: {
|
|
2158
|
-
dark: "rgba(0, 0, 0, 0.15)",
|
|
2159
|
-
// Visible dark shadow for depth on light backgrounds
|
|
2160
|
-
light: "rgba(255, 255, 255, 1)",
|
|
2161
|
-
// Bright highlight for raised effect (visible on light gray)
|
|
2162
|
-
darker: "rgba(0, 0, 0, 0.2)"
|
|
2163
|
-
// Deeper shadow for pressed states
|
|
2164
|
-
}
|
|
2165
|
-
};
|
|
2166
|
-
var intensityConfig = {
|
|
2167
|
-
subtle: { offset: 1, blur: 2 },
|
|
2168
|
-
// very light, barely visible
|
|
2169
|
-
medium: { offset: 2, blur: 4 },
|
|
2170
|
-
// noticeable but restrained
|
|
2171
|
-
strong: { offset: 2, blur: 5 }
|
|
2172
|
-
// slightly deeper blur, same offset as medium
|
|
2173
|
-
};
|
|
2174
|
-
function createRaisedShadow(surface = "charcoal", intensity = "medium") {
|
|
2175
|
-
const colors = shadowColors[surface];
|
|
2176
|
-
const { offset, blur } = intensityConfig[intensity];
|
|
2177
|
-
const rimBlur = 1;
|
|
2178
|
-
return Platform.select({
|
|
2179
|
-
web: {
|
|
2180
|
-
boxShadow: `${offset}px ${offset}px ${blur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
|
|
2181
|
-
},
|
|
2182
|
-
default: {
|
|
2183
|
-
// React Native shadow approximation (limited compared to web)
|
|
2184
|
-
shadowColor: "#000",
|
|
2185
|
-
shadowOffset: { width: offset, height: offset },
|
|
2186
|
-
shadowOpacity: 0.3,
|
|
2187
|
-
shadowRadius: blur / 2,
|
|
2188
|
-
elevation: offset
|
|
2189
|
-
}
|
|
2190
|
-
});
|
|
2181
|
+
function Cell({
|
|
2182
|
+
width,
|
|
2183
|
+
children,
|
|
2184
|
+
testID
|
|
2185
|
+
}) {
|
|
2186
|
+
return /* @__PURE__ */ jsx(View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
|
|
2191
2187
|
}
|
|
2192
|
-
function
|
|
2193
|
-
const
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2188
|
+
function RowStrip({ set }) {
|
|
2189
|
+
const zones = set.velocityZones;
|
|
2190
|
+
if (set.state === "live") {
|
|
2191
|
+
return /* @__PURE__ */ jsx(
|
|
2192
|
+
VelocityStrip,
|
|
2193
|
+
{
|
|
2194
|
+
variant: "expanded",
|
|
2195
|
+
showNumbers: false,
|
|
2196
|
+
showInfo: false,
|
|
2197
|
+
height: 24,
|
|
2198
|
+
scale: "fixed",
|
|
2199
|
+
liveRepIndex: set.velocities.length - 1,
|
|
2200
|
+
set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
|
|
2201
|
+
zones
|
|
2202
|
+
}
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
const velocities = set.state === "done" ? set.velocities : [];
|
|
2206
|
+
return /* @__PURE__ */ jsx(
|
|
2207
|
+
VelocityStrip,
|
|
2208
|
+
{
|
|
2209
|
+
variant: "compact",
|
|
2210
|
+
height: 8,
|
|
2211
|
+
hideBaseline: true,
|
|
2212
|
+
set: { type: "straight", velocities, planned: plannedReps(set) },
|
|
2213
|
+
zones
|
|
2205
2214
|
}
|
|
2206
|
-
|
|
2215
|
+
);
|
|
2207
2216
|
}
|
|
2208
|
-
function
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2217
|
+
function SetRow(set) {
|
|
2218
|
+
const live = set.state === "live";
|
|
2219
|
+
const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
|
|
2220
|
+
const rpe = set.state === "todo" ? null : set.rpe;
|
|
2221
|
+
return /* @__PURE__ */ jsxs(
|
|
2222
|
+
View,
|
|
2223
|
+
{
|
|
2224
|
+
style: { paddingVertical: 6, paddingHorizontal: 8 },
|
|
2225
|
+
accessibilityLabel: accessibilityLabel(set),
|
|
2226
|
+
testID: "set-row",
|
|
2227
|
+
children: [
|
|
2228
|
+
/* @__PURE__ */ jsxs(View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
|
|
2229
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.set, testID: "set-row-set-number", children: set.setType ? /* @__PURE__ */ jsx(Text, { style: typeBadge, testID: "set-row-type-badge", children: set.setType }) : /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor, fontWeight: live ? "700" : "600" }, children: set.setNumber }) }),
|
|
2230
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
|
|
2231
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
|
|
2232
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.rpe, testID: "set-row-rpe", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: TEXT_MUTED }, children: rpe != null ? roundRpe(rpe) : "\u2014" }) })
|
|
2233
|
+
] }),
|
|
2234
|
+
/* @__PURE__ */ jsx(View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
|
|
2235
|
+
]
|
|
2216
2236
|
}
|
|
2217
|
-
|
|
2237
|
+
);
|
|
2218
2238
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2239
|
+
var PREV_COLUMN = { label: "PREV", width: void 0 };
|
|
2240
|
+
function buildColumns(unit, showPrevious) {
|
|
2241
|
+
return [
|
|
2242
|
+
{ label: "SET", width: 36 },
|
|
2243
|
+
...showPrevious ? [PREV_COLUMN] : [],
|
|
2244
|
+
{ label: "REPS", width: 44 },
|
|
2245
|
+
{ label: unit.toUpperCase(), width: 56 },
|
|
2246
|
+
{ label: "RPE", width: 36 }
|
|
2247
|
+
];
|
|
2248
|
+
}
|
|
2249
|
+
function SetTableHeader({
|
|
2250
|
+
unit = "lbs",
|
|
2251
|
+
showPrevious = true,
|
|
2252
|
+
testID = "table-header"
|
|
2253
|
+
}) {
|
|
2254
|
+
const columns = buildColumns(unit, showPrevious);
|
|
2255
|
+
return /* @__PURE__ */ jsx(
|
|
2256
|
+
View,
|
|
2257
|
+
{
|
|
2258
|
+
className: "flex-row",
|
|
2259
|
+
style: {
|
|
2260
|
+
paddingVertical: 8,
|
|
2261
|
+
paddingHorizontal: 8,
|
|
2262
|
+
paddingBottom: 4,
|
|
2263
|
+
...showPrevious ? {} : { justifyContent: "space-between" }
|
|
2264
|
+
},
|
|
2265
|
+
testID,
|
|
2266
|
+
children: columns.map(({ label, width }) => {
|
|
2267
|
+
const isFlex = width === void 0;
|
|
2268
|
+
return /* @__PURE__ */ jsx(
|
|
2269
|
+
View,
|
|
2270
|
+
{
|
|
2271
|
+
style: {
|
|
2272
|
+
...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
|
|
2273
|
+
alignItems: "center",
|
|
2274
|
+
justifyContent: "center",
|
|
2275
|
+
gap: 8
|
|
2276
|
+
},
|
|
2277
|
+
...isFlex ? { className: "flex-1" } : {},
|
|
2278
|
+
children: /* @__PURE__ */ jsx(
|
|
2279
|
+
Text,
|
|
2280
|
+
{
|
|
2281
|
+
className: "text-text-tertiary",
|
|
2282
|
+
style: {
|
|
2283
|
+
fontSize: 10,
|
|
2284
|
+
fontWeight: "600",
|
|
2285
|
+
fontFamily: "Inter, sans-serif",
|
|
2286
|
+
textTransform: "uppercase",
|
|
2287
|
+
letterSpacing: 0.8
|
|
2288
|
+
},
|
|
2289
|
+
children: label
|
|
2290
|
+
}
|
|
2291
|
+
)
|
|
2292
|
+
},
|
|
2293
|
+
label
|
|
2294
|
+
);
|
|
2295
|
+
})
|
|
2236
2296
|
}
|
|
2237
|
-
|
|
2297
|
+
);
|
|
2238
2298
|
}
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
elevation: 0
|
|
2299
|
+
var METRIC_FONT = "Inter, sans-serif";
|
|
2300
|
+
function MetricCell({ children, color, fontSize = 11 }) {
|
|
2301
|
+
return /* @__PURE__ */ jsx(
|
|
2302
|
+
Text,
|
|
2303
|
+
{
|
|
2304
|
+
style: {
|
|
2305
|
+
fontFamily: METRIC_FONT,
|
|
2306
|
+
fontSize,
|
|
2307
|
+
color,
|
|
2308
|
+
fontWeight: "600",
|
|
2309
|
+
letterSpacing: 1
|
|
2310
|
+
},
|
|
2311
|
+
children
|
|
2253
2312
|
}
|
|
2254
|
-
|
|
2313
|
+
);
|
|
2255
2314
|
}
|
|
2256
|
-
({
|
|
2257
|
-
// Charcoal surface shadows (for dark toolbars, cards)
|
|
2258
|
-
charcoal: {
|
|
2259
|
-
raised: {
|
|
2260
|
-
subtle: createRaisedShadow("charcoal", "subtle"),
|
|
2261
|
-
medium: createRaisedShadow("charcoal", "medium"),
|
|
2262
|
-
strong: createRaisedShadow("charcoal", "strong")
|
|
2263
|
-
},
|
|
2264
|
-
raisedHover: {
|
|
2265
|
-
subtle: createRaisedHoverShadow("charcoal", "subtle"),
|
|
2266
|
-
medium: createRaisedHoverShadow("charcoal", "medium"),
|
|
2267
|
-
strong: createRaisedHoverShadow("charcoal", "strong")
|
|
2268
|
-
},
|
|
2269
|
-
pressed: {
|
|
2270
|
-
subtle: createPressedShadow("charcoal", "subtle"),
|
|
2271
|
-
medium: createPressedShadow("charcoal", "medium"),
|
|
2272
|
-
strong: createPressedShadow("charcoal", "strong")
|
|
2273
|
-
},
|
|
2274
|
-
pressedHover: {
|
|
2275
|
-
subtle: createPressedHoverShadow("charcoal", "subtle"),
|
|
2276
|
-
medium: createPressedHoverShadow("charcoal", "medium"),
|
|
2277
|
-
strong: createPressedHoverShadow("charcoal", "strong")
|
|
2278
|
-
},
|
|
2279
|
-
flat: createFlatShadow()
|
|
2280
|
-
},
|
|
2281
|
-
// Neutral dark surface shadows
|
|
2282
|
-
neutralDark: {
|
|
2283
|
-
raised: {
|
|
2284
|
-
subtle: createRaisedShadow("neutralDark", "subtle"),
|
|
2285
|
-
medium: createRaisedShadow("neutralDark", "medium"),
|
|
2286
|
-
strong: createRaisedShadow("neutralDark", "strong")
|
|
2287
|
-
},
|
|
2288
|
-
raisedHover: {
|
|
2289
|
-
subtle: createRaisedHoverShadow("neutralDark", "subtle"),
|
|
2290
|
-
medium: createRaisedHoverShadow("neutralDark", "medium"),
|
|
2291
|
-
strong: createRaisedHoverShadow("neutralDark", "strong")
|
|
2292
|
-
},
|
|
2293
|
-
pressed: {
|
|
2294
|
-
subtle: createPressedShadow("neutralDark", "subtle"),
|
|
2295
|
-
medium: createPressedShadow("neutralDark", "medium"),
|
|
2296
|
-
strong: createPressedShadow("neutralDark", "strong")
|
|
2297
|
-
},
|
|
2298
|
-
pressedHover: {
|
|
2299
|
-
subtle: createPressedHoverShadow("neutralDark", "subtle"),
|
|
2300
|
-
medium: createPressedHoverShadow("neutralDark", "medium"),
|
|
2301
|
-
strong: createPressedHoverShadow("neutralDark", "strong")
|
|
2302
|
-
},
|
|
2303
|
-
flat: createFlatShadow()
|
|
2304
|
-
},
|
|
2305
|
-
// Neutral light surface shadows
|
|
2306
|
-
neutralLight: {
|
|
2307
|
-
raised: {
|
|
2308
|
-
subtle: createRaisedShadow("neutralLight", "subtle"),
|
|
2309
|
-
medium: createRaisedShadow("neutralLight", "medium"),
|
|
2310
|
-
strong: createRaisedShadow("neutralLight", "strong")
|
|
2311
|
-
},
|
|
2312
|
-
raisedHover: {
|
|
2313
|
-
subtle: createRaisedHoverShadow("neutralLight", "subtle"),
|
|
2314
|
-
medium: createRaisedHoverShadow("neutralLight", "medium"),
|
|
2315
|
-
strong: createRaisedHoverShadow("neutralLight", "strong")
|
|
2316
|
-
},
|
|
2317
|
-
pressed: {
|
|
2318
|
-
subtle: createPressedShadow("neutralLight", "subtle"),
|
|
2319
|
-
medium: createPressedShadow("neutralLight", "medium"),
|
|
2320
|
-
strong: createPressedShadow("neutralLight", "strong")
|
|
2321
|
-
},
|
|
2322
|
-
pressedHover: {
|
|
2323
|
-
subtle: createPressedHoverShadow("neutralLight", "subtle"),
|
|
2324
|
-
medium: createPressedHoverShadow("neutralLight", "medium"),
|
|
2325
|
-
strong: createPressedHoverShadow("neutralLight", "strong")
|
|
2326
|
-
},
|
|
2327
|
-
flat: createFlatShadow()
|
|
2328
|
-
}
|
|
2329
|
-
});
|
|
2330
2315
|
|
|
2331
|
-
// src/
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2316
|
+
// src/components/custom/Workout/SetsRepsLoad.tsx
|
|
2317
|
+
var t2 = getSemanticColors("dark");
|
|
2318
|
+
var TIMES = "\xD7";
|
|
2319
|
+
var AT = "@";
|
|
2320
|
+
function SetsRepsLoad({
|
|
2321
|
+
sets,
|
|
2322
|
+
reps,
|
|
2323
|
+
load,
|
|
2324
|
+
unit = "lb",
|
|
2325
|
+
fontSize,
|
|
2326
|
+
className,
|
|
2327
|
+
...props
|
|
2328
|
+
}) {
|
|
2329
|
+
const value = t2["text-primary"];
|
|
2330
|
+
const sep = t2["text-tertiary"];
|
|
2331
|
+
return /* @__PURE__ */ jsxs(
|
|
2332
|
+
View,
|
|
2333
|
+
{
|
|
2334
|
+
className,
|
|
2335
|
+
style: { flexDirection: "row", alignItems: "baseline" },
|
|
2336
|
+
accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
|
|
2337
|
+
testID: "sets-reps-load",
|
|
2338
|
+
...props,
|
|
2339
|
+
children: [
|
|
2340
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: sets }),
|
|
2341
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${TIMES} ` }),
|
|
2342
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: reps }),
|
|
2343
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${AT} ` }),
|
|
2344
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: load }),
|
|
2345
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${unit}` })
|
|
2346
|
+
]
|
|
2347
2347
|
}
|
|
2348
|
-
|
|
2349
|
-
}
|
|
2350
|
-
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
2351
|
-
if (rgbMatch) {
|
|
2352
|
-
const [, r, g, b] = rgbMatch;
|
|
2353
|
-
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
2354
|
-
}
|
|
2355
|
-
const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
|
|
2356
|
-
if (rgbaMatch) {
|
|
2357
|
-
const [, r, g, b] = rgbaMatch;
|
|
2358
|
-
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
2359
|
-
}
|
|
2360
|
-
return color;
|
|
2348
|
+
);
|
|
2361
2349
|
}
|
|
2362
|
-
|
|
2363
|
-
// src/components/custom/Workout/TempoDisplay.tsx
|
|
2364
2350
|
var t3 = getSemanticColors("dark");
|
|
2365
2351
|
var INTER = "Inter, sans-serif";
|
|
2366
2352
|
var TEXT_TERTIARY = "#6B7280";
|
|
@@ -2744,6 +2730,243 @@ function ExerciseHeading({
|
|
|
2744
2730
|
)
|
|
2745
2731
|
] });
|
|
2746
2732
|
}
|
|
2733
|
+
var PULSE_HALF_MS = 950;
|
|
2734
|
+
var PULSE_MIN_OPACITY = 0.45;
|
|
2735
|
+
var SEGMENTED_BAR_GAP = 5;
|
|
2736
|
+
function usePulse(active) {
|
|
2737
|
+
const [value] = useState(() => new Animated.Value(0));
|
|
2738
|
+
useEffect(() => {
|
|
2739
|
+
if (!active) return;
|
|
2740
|
+
const loop = Animated.loop(
|
|
2741
|
+
Animated.sequence([
|
|
2742
|
+
Animated.timing(value, {
|
|
2743
|
+
toValue: 1,
|
|
2744
|
+
duration: PULSE_HALF_MS,
|
|
2745
|
+
easing: Easing.inOut(Easing.ease),
|
|
2746
|
+
useNativeDriver: false
|
|
2747
|
+
}),
|
|
2748
|
+
Animated.timing(value, {
|
|
2749
|
+
toValue: 0,
|
|
2750
|
+
duration: PULSE_HALF_MS,
|
|
2751
|
+
easing: Easing.inOut(Easing.ease),
|
|
2752
|
+
useNativeDriver: false
|
|
2753
|
+
})
|
|
2754
|
+
])
|
|
2755
|
+
);
|
|
2756
|
+
loop.start();
|
|
2757
|
+
return () => loop.stop();
|
|
2758
|
+
}, [active, value]);
|
|
2759
|
+
return value;
|
|
2760
|
+
}
|
|
2761
|
+
function SegmentedBar({
|
|
2762
|
+
segments,
|
|
2763
|
+
height = 8,
|
|
2764
|
+
gap = SEGMENTED_BAR_GAP,
|
|
2765
|
+
radius = 2,
|
|
2766
|
+
marker = null,
|
|
2767
|
+
segmentTestID,
|
|
2768
|
+
style,
|
|
2769
|
+
...props
|
|
2770
|
+
}) {
|
|
2771
|
+
const pulse = usePulse(segments.some((s) => s.pulse));
|
|
2772
|
+
return /* @__PURE__ */ jsxs(View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
|
|
2773
|
+
segments.map((seg, i) => {
|
|
2774
|
+
const fillStyle = {
|
|
2775
|
+
width: `${(seg.fill ?? 1) * 100}%`,
|
|
2776
|
+
height: "100%",
|
|
2777
|
+
backgroundColor: seg.color,
|
|
2778
|
+
opacity: seg.opacity
|
|
2779
|
+
};
|
|
2780
|
+
const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
|
|
2781
|
+
return /* @__PURE__ */ jsx(
|
|
2782
|
+
View,
|
|
2783
|
+
{
|
|
2784
|
+
style: {
|
|
2785
|
+
flex: seg.weight ?? 1,
|
|
2786
|
+
minWidth: 0,
|
|
2787
|
+
height: "100%",
|
|
2788
|
+
marginLeft: seg.leadingGap,
|
|
2789
|
+
borderRadius: radius,
|
|
2790
|
+
overflow: "hidden"
|
|
2791
|
+
},
|
|
2792
|
+
children: seg.pulse ? /* @__PURE__ */ jsx(
|
|
2793
|
+
Animated.View,
|
|
2794
|
+
{
|
|
2795
|
+
style: {
|
|
2796
|
+
...fillStyle,
|
|
2797
|
+
...seg.pulseColor ? {
|
|
2798
|
+
backgroundColor: pulse.interpolate({
|
|
2799
|
+
inputRange: [0, 1],
|
|
2800
|
+
outputRange: seg.pulseColor
|
|
2801
|
+
})
|
|
2802
|
+
} : {
|
|
2803
|
+
opacity: pulse.interpolate({
|
|
2804
|
+
inputRange: [0, 1],
|
|
2805
|
+
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
2806
|
+
})
|
|
2807
|
+
}
|
|
2808
|
+
},
|
|
2809
|
+
accessibilityElementsHidden: true,
|
|
2810
|
+
testID
|
|
2811
|
+
}
|
|
2812
|
+
) : /* @__PURE__ */ jsx(View, { style: fillStyle, accessibilityElementsHidden: true, testID })
|
|
2813
|
+
},
|
|
2814
|
+
i
|
|
2815
|
+
);
|
|
2816
|
+
}),
|
|
2817
|
+
marker ? /* @__PURE__ */ jsx(
|
|
2818
|
+
View,
|
|
2819
|
+
{
|
|
2820
|
+
style: {
|
|
2821
|
+
position: "absolute",
|
|
2822
|
+
left: `${marker.position * 100}%`,
|
|
2823
|
+
top: 0,
|
|
2824
|
+
bottom: 0,
|
|
2825
|
+
width: 2,
|
|
2826
|
+
backgroundColor: marker.color
|
|
2827
|
+
},
|
|
2828
|
+
accessibilityElementsHidden: true,
|
|
2829
|
+
testID: "segmented-bar-marker"
|
|
2830
|
+
}
|
|
2831
|
+
) : null
|
|
2832
|
+
] });
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
// src/components/custom/Workout/SetBar.tsx
|
|
2836
|
+
var SET_STRIP_ZONES = {
|
|
2837
|
+
slow: primitiveRamps.red[600],
|
|
2838
|
+
moderate: primitiveRamps.orange[400],
|
|
2839
|
+
fast: primitiveRamps.amber[300],
|
|
2840
|
+
fastest: primitiveRamps.green[300]
|
|
2841
|
+
};
|
|
2842
|
+
var TODO_COLOR = primitiveColors.charcoal[300];
|
|
2843
|
+
var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
|
|
2844
|
+
var NOTCH_GAP = 2;
|
|
2845
|
+
var CLUSTER_GAP = 3;
|
|
2846
|
+
var MYO_UPCOMING_CLUSTERS = 3;
|
|
2847
|
+
var MYO_UPCOMING_CLUSTER_LEN = 5;
|
|
2848
|
+
var MYO_UPCOMING_FADE = 0.6;
|
|
2849
|
+
var PULSE_RANGE = {
|
|
2850
|
+
[SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
|
|
2851
|
+
[SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
|
|
2852
|
+
[SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
|
|
2853
|
+
[SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
|
|
2854
|
+
};
|
|
2855
|
+
function velocityZoneColor(v) {
|
|
2856
|
+
if (v < 0.5) return SET_STRIP_ZONES.slow;
|
|
2857
|
+
if (v < 0.75) return SET_STRIP_ZONES.moderate;
|
|
2858
|
+
if (v < 1) return SET_STRIP_ZONES.fast;
|
|
2859
|
+
return SET_STRIP_ZONES.fastest;
|
|
2860
|
+
}
|
|
2861
|
+
function chunkedSegments(chunks, gap) {
|
|
2862
|
+
return chunks.flatMap(
|
|
2863
|
+
(vels, ci) => vels.map((v, ri) => ({
|
|
2864
|
+
color: velocityZoneColor(v),
|
|
2865
|
+
...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
|
|
2866
|
+
}))
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
function rangeSegments(floor, max, doneVels) {
|
|
2870
|
+
return Array.from({ length: max }, (_, i) => {
|
|
2871
|
+
if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
|
|
2872
|
+
return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
function myoUpcomingSegments(activationLen, clusterCount) {
|
|
2876
|
+
const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
|
|
2877
|
+
const trail = Array.from(
|
|
2878
|
+
{ length: clusterCount },
|
|
2879
|
+
(_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
|
|
2880
|
+
color: SET_STRIP_VARIABLE_COLOR,
|
|
2881
|
+
opacity: MYO_UPCOMING_FADE ** ci,
|
|
2882
|
+
...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
|
|
2883
|
+
}))
|
|
2884
|
+
).flat();
|
|
2885
|
+
return [...activation, ...trail];
|
|
2886
|
+
}
|
|
2887
|
+
function setSegments(set) {
|
|
2888
|
+
if (set.status === "todo") {
|
|
2889
|
+
return [{ color: TODO_COLOR }];
|
|
2890
|
+
}
|
|
2891
|
+
if (set.status === "done") {
|
|
2892
|
+
return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
|
|
2893
|
+
}
|
|
2894
|
+
if (set.status === "range") {
|
|
2895
|
+
return rangeSegments(set.floor, set.max, set.doneVels);
|
|
2896
|
+
}
|
|
2897
|
+
if (set.status === "drop") {
|
|
2898
|
+
return chunkedSegments(set.subloads, NOTCH_GAP);
|
|
2899
|
+
}
|
|
2900
|
+
if (set.status === "myo") {
|
|
2901
|
+
return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
|
|
2902
|
+
}
|
|
2903
|
+
if (set.status === "myo-upcoming") {
|
|
2904
|
+
return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
|
|
2905
|
+
}
|
|
2906
|
+
const performed = set.velocities.map((v) => {
|
|
2907
|
+
const color = velocityZoneColor(v);
|
|
2908
|
+
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
2909
|
+
});
|
|
2910
|
+
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
2911
|
+
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
2912
|
+
}
|
|
2913
|
+
function setBarSegmentTestID(seg) {
|
|
2914
|
+
if (seg.pulse) return "set-strip-pulse";
|
|
2915
|
+
if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
|
|
2916
|
+
return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
|
|
2917
|
+
}
|
|
2918
|
+
function SetBar({ set, height = 8 }) {
|
|
2919
|
+
return /* @__PURE__ */ jsx(
|
|
2920
|
+
SegmentedBar,
|
|
2921
|
+
{
|
|
2922
|
+
segments: setSegments(set),
|
|
2923
|
+
height,
|
|
2924
|
+
gap: 0,
|
|
2925
|
+
radius: 0,
|
|
2926
|
+
segmentTestID: setBarSegmentTestID,
|
|
2927
|
+
style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
|
|
2928
|
+
testID: "set-strip-set"
|
|
2929
|
+
}
|
|
2930
|
+
);
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
// src/components/custom/Workout/SetStrip.tsx
|
|
2934
|
+
var SET_STRIP_GAP = 5;
|
|
2935
|
+
function setCategory(set) {
|
|
2936
|
+
switch (set.status) {
|
|
2937
|
+
case "done":
|
|
2938
|
+
case "drop":
|
|
2939
|
+
case "myo":
|
|
2940
|
+
return "done";
|
|
2941
|
+
case "active":
|
|
2942
|
+
return "active";
|
|
2943
|
+
case "range":
|
|
2944
|
+
return set.doneVels.length > 0 ? "active" : "upcoming";
|
|
2945
|
+
case "todo":
|
|
2946
|
+
case "myo-upcoming":
|
|
2947
|
+
return "upcoming";
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
function describeSets(sets) {
|
|
2951
|
+
const done = sets.filter((s) => setCategory(s) === "done").length;
|
|
2952
|
+
const active = sets.filter((s) => setCategory(s) === "active").length;
|
|
2953
|
+
const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
|
|
2954
|
+
return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
|
|
2955
|
+
}
|
|
2956
|
+
function SetStrip({ sets, height = 8, className, ...props }) {
|
|
2957
|
+
return /* @__PURE__ */ jsx(
|
|
2958
|
+
View,
|
|
2959
|
+
{
|
|
2960
|
+
className,
|
|
2961
|
+
style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
|
|
2962
|
+
accessibilityRole: "image",
|
|
2963
|
+
accessibilityLabel: describeSets(sets),
|
|
2964
|
+
testID: "set-strip",
|
|
2965
|
+
...props,
|
|
2966
|
+
children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
|
|
2967
|
+
}
|
|
2968
|
+
);
|
|
2969
|
+
}
|
|
2747
2970
|
|
|
2748
2971
|
// src/components/custom/Workout/ExerciseCardHeading.tsx
|
|
2749
2972
|
function ExerciseCardHeading({
|
|
@@ -2886,7 +3109,9 @@ function CollapsedCard({
|
|
|
2886
3109
|
{
|
|
2887
3110
|
velocities,
|
|
2888
3111
|
zones: velocityZones,
|
|
2889
|
-
variant: "
|
|
3112
|
+
variant: "compact",
|
|
3113
|
+
height: 8,
|
|
3114
|
+
hideBaseline: true,
|
|
2890
3115
|
testID: `exercise-card-velocity-strip-${i}`
|
|
2891
3116
|
},
|
|
2892
3117
|
i
|
|
@@ -6055,7 +6280,16 @@ function VbtBreakdown({
|
|
|
6055
6280
|
children: set.label
|
|
6056
6281
|
}
|
|
6057
6282
|
),
|
|
6058
|
-
/* @__PURE__ */ jsx(View, { className: "flex-1", style: { height: 8, justifyContent: "center" }, children: /* @__PURE__ */ jsx(
|
|
6283
|
+
/* @__PURE__ */ jsx(View, { className: "flex-1", style: { height: 8, justifyContent: "center" }, children: /* @__PURE__ */ jsx(
|
|
6284
|
+
VelocityStrip,
|
|
6285
|
+
{
|
|
6286
|
+
velocities: set.velocities,
|
|
6287
|
+
zones,
|
|
6288
|
+
variant: "compact",
|
|
6289
|
+
height: 8,
|
|
6290
|
+
hideBaseline: true
|
|
6291
|
+
}
|
|
6292
|
+
) }),
|
|
6059
6293
|
/* @__PURE__ */ jsx(
|
|
6060
6294
|
Text,
|
|
6061
6295
|
{
|