@titan-design/react-ui 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +109 -12
- package/dist/index.d.ts +109 -12
- package/dist/index.js +648 -231
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +648 -232
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-8u_4WbL-.d.mts → pages-DCFBqp79.d.mts} +16 -7
- package/dist/{pages-DaWiBOGa.d.ts → pages-_fI3-x7Z.d.ts} +16 -7
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +495 -92
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +496 -93
- package/dist/pages.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +90 -0
- package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
- package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
- package/src/components/custom/CircularTimer/index.ts +1 -0
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +2 -2
- package/src/components/custom/Workout/FatigueMeter.test.tsx +12 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +7 -3
- package/src/components/custom/Workout/README.md +47 -0
- package/src/components/custom/Workout/RestTimer.stories.tsx +132 -2
- package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
- package/src/components/custom/Workout/RestTimer.tsx +141 -50
- package/src/components/custom/Workout/TempoBar.stories.tsx +69 -0
- package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
- package/src/components/custom/Workout/TempoBar.tsx +107 -22
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +152 -9
- package/src/components/custom/Workout/VelocityStrip.test.tsx +64 -0
- package/src/components/custom/Workout/VelocityStrip.tsx +287 -40
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +2 -3
- package/src/components/custom/Workout/ZoneTrack.test.tsx +34 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +76 -18
- package/src/components/custom/index.ts +1 -0
- package/src/components/ui/alert/Alert.stories.tsx +85 -2
- package/src/components/ui/alert/Alert.test.tsx +52 -0
- package/src/components/ui/alert/Alert.tsx +54 -20
- package/src/components/ui/progress/Progress.tsx +47 -22
package/dist/pages.js
CHANGED
|
@@ -35,6 +35,8 @@ var primitiveColors = {
|
|
|
35
35
|
900: "#111827"},
|
|
36
36
|
// Charcoal scale (dark backgrounds)
|
|
37
37
|
charcoal: {
|
|
38
|
+
0: "#6E6E6E",
|
|
39
|
+
100: "#4C4C4C",
|
|
38
40
|
200: "#3C3C3C",
|
|
39
41
|
300: "#2C2C2C",
|
|
40
42
|
400: "#1F1F1F",
|
|
@@ -606,6 +608,14 @@ var EXPANDED_ENCODED_PCT = 45;
|
|
|
606
608
|
var EXPANDED_HEIGHT = 60;
|
|
607
609
|
var FIXED_MAX_VELOCITY = 1.15;
|
|
608
610
|
var BARE_STUB_HEIGHT = 3;
|
|
611
|
+
var HERO_HEIGHT = 220;
|
|
612
|
+
var HERO_LABEL_HEADROOM = 20;
|
|
613
|
+
var HERO_BAR_GAP = 8;
|
|
614
|
+
var HERO_BAR_MAX_WIDTH = 52;
|
|
615
|
+
var HERO_BAR_RADIUS = 5;
|
|
616
|
+
var HERO_MIN_BAR_HEIGHT = 4;
|
|
617
|
+
var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
|
|
618
|
+
var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
|
|
609
619
|
function scaleDenominator(scale, maxVelocity) {
|
|
610
620
|
return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
|
|
611
621
|
}
|
|
@@ -633,6 +643,183 @@ function usePrefersReducedMotion() {
|
|
|
633
643
|
var ANIMATION_DURATION = 400;
|
|
634
644
|
var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
|
|
635
645
|
var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
|
|
646
|
+
function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
|
|
647
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
648
|
+
const [liveScale] = react.useState(() => new reactNative.Animated.Value(1));
|
|
649
|
+
react.useEffect(() => {
|
|
650
|
+
if (!active || liveRepIndex == null || liveVelocity == null) return;
|
|
651
|
+
if (prefersReducedMotion) {
|
|
652
|
+
liveScale.setValue(1);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (isNewPeak) {
|
|
656
|
+
liveScale.setValue(1);
|
|
657
|
+
reactNative.Animated.sequence([
|
|
658
|
+
reactNative.Animated.timing(liveScale, {
|
|
659
|
+
toValue: 1.25,
|
|
660
|
+
duration: 150,
|
|
661
|
+
easing: reactNative.Easing.out(reactNative.Easing.quad),
|
|
662
|
+
useNativeDriver: true
|
|
663
|
+
}),
|
|
664
|
+
reactNative.Animated.spring(liveScale, {
|
|
665
|
+
toValue: 1,
|
|
666
|
+
friction: 3,
|
|
667
|
+
tension: 140,
|
|
668
|
+
useNativeDriver: true
|
|
669
|
+
})
|
|
670
|
+
]).start();
|
|
671
|
+
} else {
|
|
672
|
+
liveScale.setValue(0.8);
|
|
673
|
+
reactNative.Animated.timing(liveScale, {
|
|
674
|
+
toValue: 1,
|
|
675
|
+
duration: 300,
|
|
676
|
+
easing: POP_EASING,
|
|
677
|
+
useNativeDriver: true
|
|
678
|
+
}).start();
|
|
679
|
+
}
|
|
680
|
+
}, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
|
|
681
|
+
return liveScale;
|
|
682
|
+
}
|
|
683
|
+
function HeroVelocityChart({
|
|
684
|
+
doneVelocities,
|
|
685
|
+
barColorFor,
|
|
686
|
+
scaleDenom,
|
|
687
|
+
referenceVelocity,
|
|
688
|
+
liveRepIndex,
|
|
689
|
+
isNewPeak,
|
|
690
|
+
targetReps,
|
|
691
|
+
height,
|
|
692
|
+
className,
|
|
693
|
+
viewProps
|
|
694
|
+
}) {
|
|
695
|
+
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
696
|
+
const liveScale = useLiveRepPop(true, liveRepIndex, liveVelocity, isNewPeak);
|
|
697
|
+
const plotHeight = Math.max(0, height - HERO_LABEL_HEADROOM);
|
|
698
|
+
const barHeight = (velocity) => scaleDenom > 0 ? Math.max(HERO_MIN_BAR_HEIGHT, Math.min(1, velocity / scaleDenom) * plotHeight) : HERO_MIN_BAR_HEIGHT;
|
|
699
|
+
const pendingCount = Math.max(0, (targetReps ?? 0) - doneVelocities.length);
|
|
700
|
+
const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
|
|
701
|
+
const repCount = doneVelocities.length;
|
|
702
|
+
const total = Math.max(repCount, targetReps ?? repCount);
|
|
703
|
+
const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
704
|
+
const { style: externalStyle, ...restProps } = viewProps;
|
|
705
|
+
return /* @__PURE__ */ jsx(
|
|
706
|
+
reactNative.View,
|
|
707
|
+
{
|
|
708
|
+
className,
|
|
709
|
+
style: [{ height }, externalStyle],
|
|
710
|
+
accessibilityRole: "image",
|
|
711
|
+
accessibilityLabel: label,
|
|
712
|
+
testID: "velocity-strip-hero",
|
|
713
|
+
...restProps,
|
|
714
|
+
children: /* @__PURE__ */ jsxs(
|
|
715
|
+
reactNative.View,
|
|
716
|
+
{
|
|
717
|
+
style: {
|
|
718
|
+
flex: 1,
|
|
719
|
+
flexDirection: "row",
|
|
720
|
+
alignItems: "flex-end",
|
|
721
|
+
gap: HERO_BAR_GAP,
|
|
722
|
+
position: "relative",
|
|
723
|
+
borderBottomWidth: 2,
|
|
724
|
+
borderBottomColor: HERO_PENDING_COLOR
|
|
725
|
+
},
|
|
726
|
+
children: [
|
|
727
|
+
referencePx > 0 && /* @__PURE__ */ jsx(
|
|
728
|
+
reactNative.View,
|
|
729
|
+
{
|
|
730
|
+
accessibilityElementsHidden: true,
|
|
731
|
+
style: {
|
|
732
|
+
position: "absolute",
|
|
733
|
+
left: 0,
|
|
734
|
+
right: 0,
|
|
735
|
+
bottom: referencePx,
|
|
736
|
+
borderTopWidth: 1,
|
|
737
|
+
borderStyle: "dashed",
|
|
738
|
+
borderColor: HERO_REFERENCE_COLOR,
|
|
739
|
+
pointerEvents: "none"
|
|
740
|
+
},
|
|
741
|
+
testID: "velocity-hero-reference"
|
|
742
|
+
}
|
|
743
|
+
),
|
|
744
|
+
doneVelocities.map((velocity, i) => {
|
|
745
|
+
const isLive = liveRepIndex === i;
|
|
746
|
+
return /* @__PURE__ */ jsxs(
|
|
747
|
+
reactNative.View,
|
|
748
|
+
{
|
|
749
|
+
accessibilityElementsHidden: true,
|
|
750
|
+
style: {
|
|
751
|
+
flex: 1,
|
|
752
|
+
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
753
|
+
height: "100%",
|
|
754
|
+
alignItems: "center",
|
|
755
|
+
justifyContent: "flex-end"
|
|
756
|
+
},
|
|
757
|
+
children: [
|
|
758
|
+
/* @__PURE__ */ jsx(
|
|
759
|
+
reactNative.Text,
|
|
760
|
+
{
|
|
761
|
+
className: "text-text-primary",
|
|
762
|
+
style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
|
|
763
|
+
testID: `velocity-label-${i}`,
|
|
764
|
+
children: formatVelocity(velocity)
|
|
765
|
+
}
|
|
766
|
+
),
|
|
767
|
+
/* @__PURE__ */ jsx(
|
|
768
|
+
reactNative.Animated.View,
|
|
769
|
+
{
|
|
770
|
+
style: [
|
|
771
|
+
{
|
|
772
|
+
width: "100%",
|
|
773
|
+
height: barHeight(velocity),
|
|
774
|
+
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
775
|
+
borderTopRightRadius: HERO_BAR_RADIUS,
|
|
776
|
+
backgroundColor: barColorFor(velocity)
|
|
777
|
+
},
|
|
778
|
+
isLive ? { transform: [{ scale: liveScale }] } : null
|
|
779
|
+
],
|
|
780
|
+
testID: `velocity-bar-${i}`
|
|
781
|
+
}
|
|
782
|
+
)
|
|
783
|
+
]
|
|
784
|
+
},
|
|
785
|
+
i
|
|
786
|
+
);
|
|
787
|
+
}),
|
|
788
|
+
Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
|
|
789
|
+
reactNative.View,
|
|
790
|
+
{
|
|
791
|
+
accessibilityElementsHidden: true,
|
|
792
|
+
style: {
|
|
793
|
+
flex: 1,
|
|
794
|
+
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
795
|
+
height: "100%",
|
|
796
|
+
alignItems: "center",
|
|
797
|
+
justifyContent: "flex-end"
|
|
798
|
+
},
|
|
799
|
+
children: /* @__PURE__ */ jsx(
|
|
800
|
+
reactNative.View,
|
|
801
|
+
{
|
|
802
|
+
style: {
|
|
803
|
+
width: "100%",
|
|
804
|
+
height: HERO_MIN_BAR_HEIGHT * 3,
|
|
805
|
+
borderWidth: 1,
|
|
806
|
+
borderStyle: "dashed",
|
|
807
|
+
borderColor: HERO_PENDING_COLOR,
|
|
808
|
+
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
809
|
+
borderTopRightRadius: HERO_BAR_RADIUS
|
|
810
|
+
},
|
|
811
|
+
testID: "velocity-slot-todo"
|
|
812
|
+
}
|
|
813
|
+
)
|
|
814
|
+
},
|
|
815
|
+
`pending-${i}`
|
|
816
|
+
))
|
|
817
|
+
]
|
|
818
|
+
}
|
|
819
|
+
)
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
}
|
|
636
823
|
function VelocityStrip({
|
|
637
824
|
velocities,
|
|
638
825
|
set,
|
|
@@ -643,6 +830,7 @@ function VelocityStrip({
|
|
|
643
830
|
onRepPress,
|
|
644
831
|
variant = "expanded",
|
|
645
832
|
height = EXPANDED_HEIGHT,
|
|
833
|
+
targetReps,
|
|
646
834
|
scale = "peak",
|
|
647
835
|
showNumbers = true,
|
|
648
836
|
showInfo = true,
|
|
@@ -668,45 +856,17 @@ function VelocityStrip({
|
|
|
668
856
|
return SET_STRIP_VARIABLE_COLOR;
|
|
669
857
|
};
|
|
670
858
|
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
671
|
-
const prefersReducedMotion = usePrefersReducedMotion();
|
|
672
859
|
const [heightAnim] = react.useState(() => new reactNative.Animated.Value(expanded ? height : 3));
|
|
673
860
|
const [labelOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
674
861
|
const [infoOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
675
|
-
const [liveScale] = react.useState(() => new reactNative.Animated.Value(1));
|
|
676
862
|
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
677
863
|
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (isNewPeak) {
|
|
685
|
-
liveScale.setValue(1);
|
|
686
|
-
reactNative.Animated.sequence([
|
|
687
|
-
reactNative.Animated.timing(liveScale, {
|
|
688
|
-
toValue: 1.25,
|
|
689
|
-
duration: 150,
|
|
690
|
-
easing: reactNative.Easing.out(reactNative.Easing.quad),
|
|
691
|
-
useNativeDriver: true
|
|
692
|
-
}),
|
|
693
|
-
reactNative.Animated.spring(liveScale, {
|
|
694
|
-
toValue: 1,
|
|
695
|
-
friction: 3,
|
|
696
|
-
tension: 140,
|
|
697
|
-
useNativeDriver: true
|
|
698
|
-
})
|
|
699
|
-
]).start();
|
|
700
|
-
} else {
|
|
701
|
-
liveScale.setValue(0.8);
|
|
702
|
-
reactNative.Animated.timing(liveScale, {
|
|
703
|
-
toValue: 1,
|
|
704
|
-
duration: 300,
|
|
705
|
-
easing: POP_EASING,
|
|
706
|
-
useNativeDriver: true
|
|
707
|
-
}).start();
|
|
708
|
-
}
|
|
709
|
-
}, [variant, framed, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
|
|
864
|
+
const liveScale = useLiveRepPop(
|
|
865
|
+
variant === "expanded" && framed,
|
|
866
|
+
liveRepIndex,
|
|
867
|
+
liveVelocity,
|
|
868
|
+
isNewPeak
|
|
869
|
+
);
|
|
710
870
|
react.useEffect(() => {
|
|
711
871
|
if (variant !== "expanded" || !framed) return;
|
|
712
872
|
reactNative.Animated.parallel([
|
|
@@ -733,6 +893,24 @@ function VelocityStrip({
|
|
|
733
893
|
if (set == null && velocities == null) return null;
|
|
734
894
|
const repCount = doneVelocities.length;
|
|
735
895
|
const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
|
|
896
|
+
if (variant === "hero") {
|
|
897
|
+
const heroHeight = height === EXPANDED_HEIGHT ? HERO_HEIGHT : height;
|
|
898
|
+
return /* @__PURE__ */ jsx(
|
|
899
|
+
HeroVelocityChart,
|
|
900
|
+
{
|
|
901
|
+
doneVelocities,
|
|
902
|
+
barColorFor,
|
|
903
|
+
scaleDenom,
|
|
904
|
+
referenceVelocity: maxVelocity,
|
|
905
|
+
liveRepIndex,
|
|
906
|
+
isNewPeak,
|
|
907
|
+
targetReps,
|
|
908
|
+
height: heroHeight,
|
|
909
|
+
className,
|
|
910
|
+
viewProps: props
|
|
911
|
+
}
|
|
912
|
+
);
|
|
913
|
+
}
|
|
736
914
|
if (variant === "mini") {
|
|
737
915
|
const { style: externalStyle, ...restProps } = props;
|
|
738
916
|
return /* @__PURE__ */ jsx(
|
|
@@ -2993,9 +3171,270 @@ function useTimer(options = {}) {
|
|
|
2993
3171
|
const done = durationMs != null && elapsedMs >= durationMs;
|
|
2994
3172
|
return { elapsedMs, remainingMs, progress, label, done };
|
|
2995
3173
|
}
|
|
3174
|
+
var colorVarMap = {
|
|
3175
|
+
primary: "var(--color-brand-primary)",
|
|
3176
|
+
secondary: "var(--color-brand-secondary)",
|
|
3177
|
+
success: "var(--color-status-success)",
|
|
3178
|
+
error: "var(--color-status-error)",
|
|
3179
|
+
warning: "var(--color-status-warning)",
|
|
3180
|
+
info: "var(--color-status-info)"
|
|
3181
|
+
};
|
|
3182
|
+
function CircularProgress({
|
|
3183
|
+
value = 0,
|
|
3184
|
+
max = 100,
|
|
3185
|
+
isIndeterminate = false,
|
|
3186
|
+
size = 48,
|
|
3187
|
+
strokeWidth = 4,
|
|
3188
|
+
color = "primary",
|
|
3189
|
+
showValue = false,
|
|
3190
|
+
formatValue: formatValue2 = (v, m) => `${Math.round(v / m * 100)}%`,
|
|
3191
|
+
children,
|
|
3192
|
+
fill = false,
|
|
3193
|
+
className,
|
|
3194
|
+
...props
|
|
3195
|
+
}) {
|
|
3196
|
+
const percentage = Math.min(Math.max(value / max * 100, 0), 100);
|
|
3197
|
+
const radius = (size - strokeWidth) / 2;
|
|
3198
|
+
const circumference = 2 * Math.PI * radius;
|
|
3199
|
+
const strokeDashoffset = circumference - percentage / 100 * circumference;
|
|
3200
|
+
const center = size / 2;
|
|
3201
|
+
const boxStyle = fill ? { width: "100%", aspectRatio: 1 } : { width: size, height: size };
|
|
3202
|
+
return /* @__PURE__ */ jsxs(
|
|
3203
|
+
reactNative.View,
|
|
3204
|
+
{
|
|
3205
|
+
className: cn("items-center justify-center", className),
|
|
3206
|
+
style: boxStyle,
|
|
3207
|
+
accessibilityRole: "progressbar",
|
|
3208
|
+
accessibilityValue: {
|
|
3209
|
+
min: 0,
|
|
3210
|
+
max,
|
|
3211
|
+
now: isIndeterminate ? void 0 : value
|
|
3212
|
+
},
|
|
3213
|
+
...props,
|
|
3214
|
+
children: [
|
|
3215
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: cn(isIndeterminate && "animate-spin"), style: boxStyle, children: /* @__PURE__ */ jsxs(
|
|
3216
|
+
"svg",
|
|
3217
|
+
{
|
|
3218
|
+
width: "100%",
|
|
3219
|
+
height: "100%",
|
|
3220
|
+
viewBox: `0 0 ${size} ${size}`,
|
|
3221
|
+
style: { position: "absolute" },
|
|
3222
|
+
children: [
|
|
3223
|
+
/* @__PURE__ */ jsx(
|
|
3224
|
+
"circle",
|
|
3225
|
+
{
|
|
3226
|
+
cx: center,
|
|
3227
|
+
cy: center,
|
|
3228
|
+
r: radius,
|
|
3229
|
+
fill: "none",
|
|
3230
|
+
stroke: "var(--color-border-default)",
|
|
3231
|
+
strokeWidth
|
|
3232
|
+
}
|
|
3233
|
+
),
|
|
3234
|
+
/* @__PURE__ */ jsx(
|
|
3235
|
+
"circle",
|
|
3236
|
+
{
|
|
3237
|
+
cx: center,
|
|
3238
|
+
cy: center,
|
|
3239
|
+
r: radius,
|
|
3240
|
+
fill: "none",
|
|
3241
|
+
stroke: colorVarMap[color],
|
|
3242
|
+
strokeWidth,
|
|
3243
|
+
strokeLinecap: "round",
|
|
3244
|
+
strokeDasharray: circumference,
|
|
3245
|
+
strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
|
|
3246
|
+
transform: `rotate(-90 ${center} ${center})`,
|
|
3247
|
+
style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
|
|
3248
|
+
}
|
|
3249
|
+
)
|
|
3250
|
+
]
|
|
3251
|
+
}
|
|
3252
|
+
) }),
|
|
3253
|
+
children != null ? /* @__PURE__ */ jsx(
|
|
3254
|
+
reactNative.View,
|
|
3255
|
+
{
|
|
3256
|
+
style: {
|
|
3257
|
+
position: "absolute",
|
|
3258
|
+
alignItems: "center",
|
|
3259
|
+
justifyContent: "center",
|
|
3260
|
+
pointerEvents: "none"
|
|
3261
|
+
},
|
|
3262
|
+
children
|
|
3263
|
+
}
|
|
3264
|
+
) : showValue && !isIndeterminate && /* @__PURE__ */ jsx(
|
|
3265
|
+
reactNative.Text,
|
|
3266
|
+
{
|
|
3267
|
+
className: "text-xs font-semibold text-text-primary absolute",
|
|
3268
|
+
style: { fontSize: Math.max(size * 0.22, 10) },
|
|
3269
|
+
children: formatValue2(value, max)
|
|
3270
|
+
}
|
|
3271
|
+
)
|
|
3272
|
+
]
|
|
3273
|
+
}
|
|
3274
|
+
);
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
// src/components/custom/CircularTimer/CircularTimer.tsx
|
|
3278
|
+
function CircularTimer({
|
|
3279
|
+
durationMs,
|
|
3280
|
+
elapsedMs,
|
|
3281
|
+
mode = "down",
|
|
3282
|
+
size = 160,
|
|
3283
|
+
strokeWidth = 8,
|
|
3284
|
+
fill = false,
|
|
3285
|
+
color = "primary",
|
|
3286
|
+
doneLabel,
|
|
3287
|
+
doneColor = "success",
|
|
3288
|
+
controls,
|
|
3289
|
+
accessibilityLabel: accessibilityLabel2,
|
|
3290
|
+
testID
|
|
3291
|
+
}) {
|
|
3292
|
+
const { remainingMs, progress, label, done } = useTimer({ mode, durationMs, elapsedMs });
|
|
3293
|
+
const isDone = done && mode === "down";
|
|
3294
|
+
const showDone = isDone && doneLabel != null;
|
|
3295
|
+
const remainingFrac = durationMs > 0 ? Math.max(0, Math.min(1, remainingMs / durationMs)) : 0;
|
|
3296
|
+
const arc = mode === "down" ? showDone ? 1 : remainingFrac : progress;
|
|
3297
|
+
const ringColor = showDone ? doneColor : color;
|
|
3298
|
+
const remainingSec = Math.ceil(Math.max(0, remainingMs) / 1e3);
|
|
3299
|
+
const a11y = accessibilityLabel2 ?? (isDone ? "Timer complete" : `${remainingSec} seconds remaining`);
|
|
3300
|
+
return /* @__PURE__ */ jsxs(
|
|
3301
|
+
reactNative.View,
|
|
3302
|
+
{
|
|
3303
|
+
style: { alignItems: "center", gap: 16 },
|
|
3304
|
+
accessibilityRole: "timer",
|
|
3305
|
+
accessibilityLabel: a11y,
|
|
3306
|
+
testID: testID ?? "circular-timer",
|
|
3307
|
+
children: [
|
|
3308
|
+
/* @__PURE__ */ jsx(
|
|
3309
|
+
CircularProgress,
|
|
3310
|
+
{
|
|
3311
|
+
value: arc,
|
|
3312
|
+
max: 1,
|
|
3313
|
+
size,
|
|
3314
|
+
strokeWidth,
|
|
3315
|
+
fill,
|
|
3316
|
+
color: ringColor,
|
|
3317
|
+
accessibilityLabel: "Timer ring",
|
|
3318
|
+
testID: "circular-timer-ring",
|
|
3319
|
+
children: /* @__PURE__ */ jsx(
|
|
3320
|
+
reactNative.Text,
|
|
3321
|
+
{
|
|
3322
|
+
className: showDone ? void 0 : "text-text-primary",
|
|
3323
|
+
style: {
|
|
3324
|
+
fontSize: Math.round(size * (showDone ? 0.28 : 0.24)),
|
|
3325
|
+
fontFamily: '"Space Grotesk", sans-serif',
|
|
3326
|
+
fontWeight: "800",
|
|
3327
|
+
fontVariant: ["tabular-nums"],
|
|
3328
|
+
letterSpacing: showDone ? 1 : -1,
|
|
3329
|
+
...showDone ? { color: colorVarMap[doneColor] } : null
|
|
3330
|
+
},
|
|
3331
|
+
testID: "circular-timer-label",
|
|
3332
|
+
children: showDone ? doneLabel : label
|
|
3333
|
+
}
|
|
3334
|
+
)
|
|
3335
|
+
}
|
|
3336
|
+
),
|
|
3337
|
+
controls
|
|
3338
|
+
]
|
|
3339
|
+
}
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
2996
3342
|
|
|
2997
3343
|
// src/components/custom/Workout/RestTimer.tsx
|
|
2998
3344
|
var BRAND_PRIMARY3 = getSemanticColors("dark")["brand-primary"];
|
|
3345
|
+
var RING_DEFAULT_SIZE = 180;
|
|
3346
|
+
function RestActions({ onAddTime, onSkip }) {
|
|
3347
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
|
|
3348
|
+
/* @__PURE__ */ jsx(
|
|
3349
|
+
reactNative.Pressable,
|
|
3350
|
+
{
|
|
3351
|
+
onPress: onAddTime,
|
|
3352
|
+
style: {
|
|
3353
|
+
backgroundColor: "rgba(255,255,255,0.06)",
|
|
3354
|
+
paddingVertical: 8,
|
|
3355
|
+
paddingHorizontal: 20,
|
|
3356
|
+
borderRadius: 8
|
|
3357
|
+
},
|
|
3358
|
+
accessibilityRole: "button",
|
|
3359
|
+
accessibilityLabel: "Add 30 seconds",
|
|
3360
|
+
testID: "rest-timer-add-time",
|
|
3361
|
+
children: /* @__PURE__ */ jsx(
|
|
3362
|
+
reactNative.Text,
|
|
3363
|
+
{
|
|
3364
|
+
className: "text-text-secondary",
|
|
3365
|
+
style: { fontSize: 11, fontFamily: "Inter, sans-serif", fontWeight: "600" },
|
|
3366
|
+
children: "+30s"
|
|
3367
|
+
}
|
|
3368
|
+
)
|
|
3369
|
+
}
|
|
3370
|
+
),
|
|
3371
|
+
/* @__PURE__ */ jsx(
|
|
3372
|
+
reactNative.Pressable,
|
|
3373
|
+
{
|
|
3374
|
+
onPress: onSkip,
|
|
3375
|
+
style: {
|
|
3376
|
+
backgroundColor: "rgba(255,121,0,0.12)",
|
|
3377
|
+
paddingVertical: 8,
|
|
3378
|
+
paddingHorizontal: 20,
|
|
3379
|
+
borderRadius: 8
|
|
3380
|
+
},
|
|
3381
|
+
accessibilityRole: "button",
|
|
3382
|
+
accessibilityLabel: "Skip rest",
|
|
3383
|
+
testID: "rest-timer-skip",
|
|
3384
|
+
children: /* @__PURE__ */ jsx(
|
|
3385
|
+
reactNative.Text,
|
|
3386
|
+
{
|
|
3387
|
+
style: {
|
|
3388
|
+
fontSize: 11,
|
|
3389
|
+
fontFamily: "Inter, sans-serif",
|
|
3390
|
+
fontWeight: "600",
|
|
3391
|
+
color: BRAND_PRIMARY3
|
|
3392
|
+
},
|
|
3393
|
+
children: "Skip"
|
|
3394
|
+
}
|
|
3395
|
+
)
|
|
3396
|
+
}
|
|
3397
|
+
)
|
|
3398
|
+
] });
|
|
3399
|
+
}
|
|
3400
|
+
function RestTimerRing({
|
|
3401
|
+
totalSeconds,
|
|
3402
|
+
elapsedMs,
|
|
3403
|
+
remainingSec,
|
|
3404
|
+
done,
|
|
3405
|
+
size,
|
|
3406
|
+
nextSetInfo,
|
|
3407
|
+
displayOnly,
|
|
3408
|
+
onSkip,
|
|
3409
|
+
onAddTime
|
|
3410
|
+
}) {
|
|
3411
|
+
const a11yLabel = done ? "Rest complete, next set ready" : `Rest timer, ${Math.max(0, remainingSec)} seconds remaining`;
|
|
3412
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: { alignItems: "center", gap: 16 }, testID: "rest-timer", children: [
|
|
3413
|
+
/* @__PURE__ */ jsx(
|
|
3414
|
+
CircularTimer,
|
|
3415
|
+
{
|
|
3416
|
+
durationMs: totalSeconds * 1e3,
|
|
3417
|
+
elapsedMs,
|
|
3418
|
+
mode: "down",
|
|
3419
|
+
size,
|
|
3420
|
+
doneLabel: "GO",
|
|
3421
|
+
doneColor: "success",
|
|
3422
|
+
accessibilityLabel: a11yLabel,
|
|
3423
|
+
controls: !displayOnly ? /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip }) : void 0,
|
|
3424
|
+
testID: "rest-timer-ring"
|
|
3425
|
+
}
|
|
3426
|
+
),
|
|
3427
|
+
nextSetInfo != null && /* @__PURE__ */ jsx(
|
|
3428
|
+
reactNative.Text,
|
|
3429
|
+
{
|
|
3430
|
+
className: "text-text-tertiary",
|
|
3431
|
+
style: { fontSize: 13, fontFamily: "Inter, sans-serif" },
|
|
3432
|
+
testID: "rest-timer-next-set",
|
|
3433
|
+
children: nextSetInfo
|
|
3434
|
+
}
|
|
3435
|
+
)
|
|
3436
|
+
] });
|
|
3437
|
+
}
|
|
2999
3438
|
function RestTimer({
|
|
3000
3439
|
totalSeconds,
|
|
3001
3440
|
elapsedMs,
|
|
@@ -3003,12 +3442,15 @@ function RestTimer({
|
|
|
3003
3442
|
onAddTime,
|
|
3004
3443
|
nextSetInfo,
|
|
3005
3444
|
visible,
|
|
3006
|
-
displayOnly = false
|
|
3445
|
+
displayOnly = false,
|
|
3446
|
+
variant = "bar",
|
|
3447
|
+
size = RING_DEFAULT_SIZE
|
|
3007
3448
|
}) {
|
|
3008
3449
|
const {
|
|
3009
3450
|
remainingMs,
|
|
3010
3451
|
progress,
|
|
3011
|
-
label: timeDisplay
|
|
3452
|
+
label: timeDisplay,
|
|
3453
|
+
done
|
|
3012
3454
|
} = useTimer({
|
|
3013
3455
|
mode: "down",
|
|
3014
3456
|
durationMs: totalSeconds * 1e3,
|
|
@@ -3017,6 +3459,22 @@ function RestTimer({
|
|
|
3017
3459
|
const remainingSec = Math.ceil(remainingMs / 1e3);
|
|
3018
3460
|
const progressPct = progress * 100;
|
|
3019
3461
|
if (!visible) return null;
|
|
3462
|
+
if (variant === "ring") {
|
|
3463
|
+
return /* @__PURE__ */ jsx(
|
|
3464
|
+
RestTimerRing,
|
|
3465
|
+
{
|
|
3466
|
+
totalSeconds,
|
|
3467
|
+
elapsedMs,
|
|
3468
|
+
remainingSec,
|
|
3469
|
+
done,
|
|
3470
|
+
size,
|
|
3471
|
+
nextSetInfo,
|
|
3472
|
+
displayOnly,
|
|
3473
|
+
onSkip,
|
|
3474
|
+
onAddTime
|
|
3475
|
+
}
|
|
3476
|
+
);
|
|
3477
|
+
}
|
|
3020
3478
|
return /* @__PURE__ */ jsxs(
|
|
3021
3479
|
reactNative.View,
|
|
3022
3480
|
{
|
|
@@ -3114,62 +3572,7 @@ function RestTimer({
|
|
|
3114
3572
|
)
|
|
3115
3573
|
}
|
|
3116
3574
|
),
|
|
3117
|
-
!displayOnly && /* @__PURE__ */
|
|
3118
|
-
/* @__PURE__ */ jsx(
|
|
3119
|
-
reactNative.Pressable,
|
|
3120
|
-
{
|
|
3121
|
-
onPress: onAddTime,
|
|
3122
|
-
style: {
|
|
3123
|
-
backgroundColor: "rgba(255,255,255,0.06)",
|
|
3124
|
-
paddingVertical: 8,
|
|
3125
|
-
paddingHorizontal: 20,
|
|
3126
|
-
borderRadius: 8
|
|
3127
|
-
},
|
|
3128
|
-
accessibilityRole: "button",
|
|
3129
|
-
accessibilityLabel: "Add 30 seconds",
|
|
3130
|
-
testID: "rest-timer-add-time",
|
|
3131
|
-
children: /* @__PURE__ */ jsx(
|
|
3132
|
-
reactNative.Text,
|
|
3133
|
-
{
|
|
3134
|
-
className: "text-text-secondary",
|
|
3135
|
-
style: {
|
|
3136
|
-
fontSize: 11,
|
|
3137
|
-
fontFamily: "Inter, sans-serif",
|
|
3138
|
-
fontWeight: "600"
|
|
3139
|
-
},
|
|
3140
|
-
children: "+30s"
|
|
3141
|
-
}
|
|
3142
|
-
)
|
|
3143
|
-
}
|
|
3144
|
-
),
|
|
3145
|
-
/* @__PURE__ */ jsx(
|
|
3146
|
-
reactNative.Pressable,
|
|
3147
|
-
{
|
|
3148
|
-
onPress: onSkip,
|
|
3149
|
-
style: {
|
|
3150
|
-
backgroundColor: "rgba(255,121,0,0.12)",
|
|
3151
|
-
paddingVertical: 8,
|
|
3152
|
-
paddingHorizontal: 20,
|
|
3153
|
-
borderRadius: 8
|
|
3154
|
-
},
|
|
3155
|
-
accessibilityRole: "button",
|
|
3156
|
-
accessibilityLabel: "Skip rest",
|
|
3157
|
-
testID: "rest-timer-skip",
|
|
3158
|
-
children: /* @__PURE__ */ jsx(
|
|
3159
|
-
reactNative.Text,
|
|
3160
|
-
{
|
|
3161
|
-
style: {
|
|
3162
|
-
fontSize: 11,
|
|
3163
|
-
fontFamily: "Inter, sans-serif",
|
|
3164
|
-
fontWeight: "600",
|
|
3165
|
-
color: BRAND_PRIMARY3
|
|
3166
|
-
},
|
|
3167
|
-
children: "Skip"
|
|
3168
|
-
}
|
|
3169
|
-
)
|
|
3170
|
-
}
|
|
3171
|
-
)
|
|
3172
|
-
] })
|
|
3575
|
+
!displayOnly && /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip })
|
|
3173
3576
|
]
|
|
3174
3577
|
}
|
|
3175
3578
|
);
|