@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.
Files changed (40) hide show
  1. package/dist/index.d.mts +109 -12
  2. package/dist/index.d.ts +109 -12
  3. package/dist/index.js +648 -231
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +648 -232
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/{pages-8u_4WbL-.d.mts → pages-DCFBqp79.d.mts} +16 -7
  8. package/dist/{pages-DaWiBOGa.d.ts → pages-_fI3-x7Z.d.ts} +16 -7
  9. package/dist/pages.d.mts +1 -1
  10. package/dist/pages.d.ts +1 -1
  11. package/dist/pages.js +495 -92
  12. package/dist/pages.js.map +1 -1
  13. package/dist/pages.mjs +496 -93
  14. package/dist/pages.mjs.map +1 -1
  15. package/package.json +1 -1
  16. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +90 -0
  17. package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
  18. package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
  19. package/src/components/custom/CircularTimer/index.ts +1 -0
  20. package/src/components/custom/Workout/FatigueMeter.stories.tsx +2 -2
  21. package/src/components/custom/Workout/FatigueMeter.test.tsx +12 -0
  22. package/src/components/custom/Workout/FatigueMeter.tsx +7 -3
  23. package/src/components/custom/Workout/README.md +47 -0
  24. package/src/components/custom/Workout/RestTimer.stories.tsx +132 -2
  25. package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
  26. package/src/components/custom/Workout/RestTimer.tsx +141 -50
  27. package/src/components/custom/Workout/TempoBar.stories.tsx +69 -0
  28. package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
  29. package/src/components/custom/Workout/TempoBar.tsx +107 -22
  30. package/src/components/custom/Workout/VelocityStrip.stories.tsx +152 -9
  31. package/src/components/custom/Workout/VelocityStrip.test.tsx +64 -0
  32. package/src/components/custom/Workout/VelocityStrip.tsx +287 -40
  33. package/src/components/custom/Workout/ZoneTrack.stories.tsx +2 -3
  34. package/src/components/custom/Workout/ZoneTrack.test.tsx +34 -0
  35. package/src/components/custom/Workout/ZoneTrack.tsx +76 -18
  36. package/src/components/custom/index.ts +1 -0
  37. package/src/components/ui/alert/Alert.stories.tsx +85 -2
  38. package/src/components/ui/alert/Alert.test.tsx +52 -0
  39. package/src/components/ui/alert/Alert.tsx +54 -20
  40. package/src/components/ui/progress/Progress.tsx +47 -22
package/dist/index.js CHANGED
@@ -158,6 +158,7 @@ var statusColors = {
158
158
  subtle: "bg-status-success-subtle",
159
159
  outline: "border-2 border-status-success bg-transparent",
160
160
  solid: "bg-status-success",
161
+ border: "border-status-success",
161
162
  icon: "text-status-success",
162
163
  text: "text-status-success"
163
164
  },
@@ -165,6 +166,7 @@ var statusColors = {
165
166
  subtle: "bg-status-info-subtle",
166
167
  outline: "border-2 border-status-info bg-transparent",
167
168
  solid: "bg-status-info",
169
+ border: "border-status-info",
168
170
  icon: "text-status-info",
169
171
  text: "text-status-info"
170
172
  },
@@ -172,6 +174,7 @@ var statusColors = {
172
174
  subtle: "bg-status-warning-subtle",
173
175
  outline: "border-2 border-status-warning bg-transparent",
174
176
  solid: "bg-status-warning",
177
+ border: "border-status-warning",
175
178
  icon: "text-status-warning",
176
179
  text: "text-status-warning"
177
180
  },
@@ -179,6 +182,7 @@ var statusColors = {
179
182
  subtle: "bg-status-error-subtle",
180
183
  outline: "border-2 border-status-error bg-transparent",
181
184
  solid: "bg-status-error",
185
+ border: "border-status-error",
182
186
  icon: "text-status-error",
183
187
  text: "text-status-error"
184
188
  }
@@ -192,6 +196,8 @@ var defaultIcons = {
192
196
  function Alert({
193
197
  status = "info",
194
198
  variant = "subtle",
199
+ size = "default",
200
+ message,
195
201
  icon,
196
202
  showIcon = true,
197
203
  onClose,
@@ -201,28 +207,43 @@ function Alert({
201
207
  }) {
202
208
  const colors = statusColors[status];
203
209
  const isSolid = variant === "solid";
210
+ const isCompact = size === "compact";
204
211
  return /* @__PURE__ */ jsxs(
205
212
  reactNative.View,
206
213
  {
207
214
  accessibilityRole: "alert",
208
215
  className: cn(
209
- "flex-row items-start p-4 rounded-lg",
216
+ isCompact ? "flex-row items-center px-3 py-2 rounded-lg" : "flex-row items-start p-4 rounded-lg",
210
217
  colors[variant],
218
+ // A compact `subtle` pill gets a hairline status border so it reads as a
219
+ // defined cue on a dark wall (the CueFlag look); other variants carry their own.
220
+ isCompact && variant === "subtle" && cn("border", colors.border),
211
221
  className
212
222
  ),
213
223
  ...props,
214
224
  children: [
215
- showIcon && /* @__PURE__ */ jsx(reactNative.View, { className: "mr-3 mt-0.5", children: icon || /* @__PURE__ */ jsx(
225
+ showIcon && /* @__PURE__ */ jsx(reactNative.View, { className: isCompact ? "mr-2" : "mr-3 mt-0.5", children: icon || /* @__PURE__ */ jsx(
216
226
  reactNative.Text,
217
227
  {
218
228
  className: cn(
219
- "text-lg font-bold",
229
+ "font-bold",
230
+ isCompact ? "text-base" : "text-lg",
220
231
  isSolid ? "text-white" : colors.icon
221
232
  ),
222
233
  children: defaultIcons[status]
223
234
  }
224
235
  ) }),
225
- /* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", children }),
236
+ /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-1", children: [
237
+ message != null && /* @__PURE__ */ jsx(
238
+ reactNative.Text,
239
+ {
240
+ className: cn("text-sm font-semibold", isSolid ? "text-white" : colors.text),
241
+ testID: "alert-message",
242
+ children: message
243
+ }
244
+ ),
245
+ children
246
+ ] }),
226
247
  onClose && /* @__PURE__ */ jsx(
227
248
  reactNative.Pressable,
228
249
  {
@@ -5072,6 +5093,8 @@ function CircularProgress({
5072
5093
  color = "primary",
5073
5094
  showValue = false,
5074
5095
  formatValue: formatValue3 = (v, m) => `${Math.round(v / m * 100)}%`,
5096
+ children,
5097
+ fill = false,
5075
5098
  className,
5076
5099
  ...props
5077
5100
  }) {
@@ -5080,11 +5103,12 @@ function CircularProgress({
5080
5103
  const circumference = 2 * Math.PI * radius;
5081
5104
  const strokeDashoffset = circumference - percentage / 100 * circumference;
5082
5105
  const center = size / 2;
5106
+ const boxStyle = fill ? { width: "100%", aspectRatio: 1 } : { width: size, height: size };
5083
5107
  return /* @__PURE__ */ jsxs(
5084
5108
  reactNative.View,
5085
5109
  {
5086
5110
  className: cn("items-center justify-center", className),
5087
- style: { width: size, height: size },
5111
+ style: boxStyle,
5088
5112
  accessibilityRole: "progressbar",
5089
5113
  accessibilityValue: {
5090
5114
  min: 0,
@@ -5093,52 +5117,56 @@ function CircularProgress({
5093
5117
  },
5094
5118
  ...props,
5095
5119
  children: [
5096
- /* @__PURE__ */ jsx(
5120
+ /* @__PURE__ */ jsx(reactNative.View, { className: cn(isIndeterminate && "animate-spin"), style: boxStyle, children: /* @__PURE__ */ jsxs(
5121
+ "svg",
5122
+ {
5123
+ width: "100%",
5124
+ height: "100%",
5125
+ viewBox: `0 0 ${size} ${size}`,
5126
+ style: { position: "absolute" },
5127
+ children: [
5128
+ /* @__PURE__ */ jsx(
5129
+ "circle",
5130
+ {
5131
+ cx: center,
5132
+ cy: center,
5133
+ r: radius,
5134
+ fill: "none",
5135
+ stroke: "var(--color-border-default)",
5136
+ strokeWidth
5137
+ }
5138
+ ),
5139
+ /* @__PURE__ */ jsx(
5140
+ "circle",
5141
+ {
5142
+ cx: center,
5143
+ cy: center,
5144
+ r: radius,
5145
+ fill: "none",
5146
+ stroke: colorVarMap[color],
5147
+ strokeWidth,
5148
+ strokeLinecap: "round",
5149
+ strokeDasharray: circumference,
5150
+ strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
5151
+ transform: `rotate(-90 ${center} ${center})`,
5152
+ style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
5153
+ }
5154
+ )
5155
+ ]
5156
+ }
5157
+ ) }),
5158
+ children != null ? /* @__PURE__ */ jsx(
5097
5159
  reactNative.View,
5098
5160
  {
5099
- className: cn(isIndeterminate && "animate-spin"),
5100
- style: { width: size, height: size },
5101
- children: /* @__PURE__ */ jsxs(
5102
- "svg",
5103
- {
5104
- width: size,
5105
- height: size,
5106
- viewBox: `0 0 ${size} ${size}`,
5107
- style: { position: "absolute" },
5108
- children: [
5109
- /* @__PURE__ */ jsx(
5110
- "circle",
5111
- {
5112
- cx: center,
5113
- cy: center,
5114
- r: radius,
5115
- fill: "none",
5116
- stroke: "var(--color-border-default)",
5117
- strokeWidth
5118
- }
5119
- ),
5120
- /* @__PURE__ */ jsx(
5121
- "circle",
5122
- {
5123
- cx: center,
5124
- cy: center,
5125
- r: radius,
5126
- fill: "none",
5127
- stroke: colorVarMap[color],
5128
- strokeWidth,
5129
- strokeLinecap: "round",
5130
- strokeDasharray: circumference,
5131
- strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
5132
- transform: `rotate(-90 ${center} ${center})`,
5133
- style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
5134
- }
5135
- )
5136
- ]
5137
- }
5138
- )
5161
+ style: {
5162
+ position: "absolute",
5163
+ alignItems: "center",
5164
+ justifyContent: "center",
5165
+ pointerEvents: "none"
5166
+ },
5167
+ children
5139
5168
  }
5140
- ),
5141
- showValue && !isIndeterminate && /* @__PURE__ */ jsx(
5169
+ ) : showValue && !isIndeterminate && /* @__PURE__ */ jsx(
5142
5170
  reactNative.Text,
5143
5171
  {
5144
5172
  className: "text-xs font-semibold text-text-primary absolute",
@@ -7079,6 +7107,70 @@ function TimerReadout({
7079
7107
  }
7080
7108
  );
7081
7109
  }
7110
+ function CircularTimer({
7111
+ durationMs,
7112
+ elapsedMs,
7113
+ mode = "down",
7114
+ size = 160,
7115
+ strokeWidth = 8,
7116
+ fill = false,
7117
+ color = "primary",
7118
+ doneLabel,
7119
+ doneColor = "success",
7120
+ controls,
7121
+ accessibilityLabel: accessibilityLabel2,
7122
+ testID
7123
+ }) {
7124
+ const { remainingMs, progress, label, done } = useTimer({ mode, durationMs, elapsedMs });
7125
+ const isDone = done && mode === "down";
7126
+ const showDone = isDone && doneLabel != null;
7127
+ const remainingFrac = durationMs > 0 ? Math.max(0, Math.min(1, remainingMs / durationMs)) : 0;
7128
+ const arc = mode === "down" ? showDone ? 1 : remainingFrac : progress;
7129
+ const ringColor = showDone ? doneColor : color;
7130
+ const remainingSec = Math.ceil(Math.max(0, remainingMs) / 1e3);
7131
+ const a11y = accessibilityLabel2 ?? (isDone ? "Timer complete" : `${remainingSec} seconds remaining`);
7132
+ return /* @__PURE__ */ jsxs(
7133
+ reactNative.View,
7134
+ {
7135
+ style: { alignItems: "center", gap: 16 },
7136
+ accessibilityRole: "timer",
7137
+ accessibilityLabel: a11y,
7138
+ testID: testID ?? "circular-timer",
7139
+ children: [
7140
+ /* @__PURE__ */ jsx(
7141
+ CircularProgress,
7142
+ {
7143
+ value: arc,
7144
+ max: 1,
7145
+ size,
7146
+ strokeWidth,
7147
+ fill,
7148
+ color: ringColor,
7149
+ accessibilityLabel: "Timer ring",
7150
+ testID: "circular-timer-ring",
7151
+ children: /* @__PURE__ */ jsx(
7152
+ reactNative.Text,
7153
+ {
7154
+ className: showDone ? void 0 : "text-text-primary",
7155
+ style: {
7156
+ fontSize: Math.round(size * (showDone ? 0.28 : 0.24)),
7157
+ fontFamily: '"Space Grotesk", sans-serif',
7158
+ fontWeight: "800",
7159
+ fontVariant: ["tabular-nums"],
7160
+ letterSpacing: showDone ? 1 : -1,
7161
+ ...showDone ? { color: colorVarMap[doneColor] } : null
7162
+ },
7163
+ testID: "circular-timer-label",
7164
+ children: showDone ? doneLabel : label
7165
+ }
7166
+ )
7167
+ }
7168
+ ),
7169
+ controls
7170
+ ]
7171
+ }
7172
+ );
7173
+ }
7082
7174
  var sizeConfig2 = {
7083
7175
  sm: { value: "text-lg font-bold", label: "text-xs", unit: "text-xs" },
7084
7176
  md: { value: "text-2xl font-bold", label: "text-xs", unit: "text-sm" },
@@ -7729,6 +7821,11 @@ var TEMPO_PACING = {
7729
7821
  behindThresholdPct: 0.15,
7730
7822
  minPhaseDurationMs: 500
7731
7823
  };
7824
+ function formatTempoDelta(remainingMs) {
7825
+ const s = remainingMs / 1e3;
7826
+ const rounded = Math.abs(s) < 0.05 ? 0 : s;
7827
+ return rounded.toFixed(1);
7828
+ }
7732
7829
  function getTempoPacingState(elapsedMs, targetMs) {
7733
7830
  if (!targetMs || targetMs < TEMPO_PACING.minPhaseDurationMs) return "none";
7734
7831
  const ratio = elapsedMs / targetMs;
@@ -7741,24 +7838,33 @@ function getTempoFillPct(elapsedMs, targetMs) {
7741
7838
  }
7742
7839
  var PHASE_ORDER = ["concentric", "hold", "eccentric"];
7743
7840
  var PHASE_CONFIG = {
7744
- concentric: { label: "Con", color: t2["status-success"], flex: 2 },
7745
- hold: { label: "Hold", color: t2["brand-primary"], flex: 1 },
7746
- eccentric: { label: "Ecc", color: t2["status-warning"], flex: 3 }
7841
+ concentric: { label: "Con", wallLabel: "Concentric", color: t2["status-success"], flex: 2 },
7842
+ hold: { label: "Hold", wallLabel: "Hold", color: t2["brand-primary"], flex: 1 },
7843
+ eccentric: { label: "Ecc", wallLabel: "Eccentric", color: t2["status-warning"], flex: 3 }
7844
+ };
7845
+ var TEMPO_SIZES = {
7846
+ default: { labelMargin: 4, labelFont: 10, barGap: 2, barHeight: 20, radius: 4, segFont: 12 },
7847
+ wall: { labelMargin: 8, labelFont: 15, barGap: 4, barHeight: 40, radius: 6, segFont: 22 }
7747
7848
  };
7748
7849
  function TempoBar({
7749
7850
  activePhase,
7750
7851
  phaseElapsedMs,
7751
7852
  completed,
7752
7853
  target,
7854
+ size = "default",
7855
+ activeDisplay = "time",
7753
7856
  className,
7754
7857
  ...props
7755
7858
  }) {
7859
+ const s = TEMPO_SIZES[size];
7860
+ const [display, setDisplay] = React24.useState(activeDisplay);
7861
+ const toggleDisplay = () => setDisplay((d) => d === "time" ? "delta" : "time");
7756
7862
  return /* @__PURE__ */ jsxs(reactNative.View, { className, testID: "tempo-bar", ...props, children: [
7757
- /* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row", marginBottom: 4 }, children: PHASE_ORDER.map((phase) => {
7863
+ /* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row", marginBottom: s.labelMargin }, children: PHASE_ORDER.map((phase) => {
7758
7864
  const config = PHASE_CONFIG[phase];
7759
- return /* @__PURE__ */ jsx(reactNative.View, { style: { flex: config.flex, alignItems: "center" }, children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 10, color: t2["text-disabled"] }, children: config.label }) }, phase);
7865
+ return /* @__PURE__ */ jsx(reactNative.View, { style: { flex: config.flex, alignItems: "center" }, children: /* @__PURE__ */ jsx(reactNative.Text, { numberOfLines: 1, style: { fontSize: s.labelFont, color: t2["text-disabled"] }, children: size === "wall" ? config.wallLabel : config.label }) }, phase);
7760
7866
  }) }),
7761
- /* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row", gap: 2, height: 20 }, children: PHASE_ORDER.map((phase) => {
7867
+ /* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row", gap: s.barGap, height: s.barHeight }, children: PHASE_ORDER.map((phase) => {
7762
7868
  const config = PHASE_CONFIG[phase];
7763
7869
  const isActive = activePhase === phase;
7764
7870
  const completedMs = completed?.[phase];
@@ -7770,7 +7876,7 @@ function TempoBar({
7770
7876
  style: {
7771
7877
  flex: config.flex,
7772
7878
  backgroundColor: alpha("#ffffff", 0.06),
7773
- borderRadius: 4,
7879
+ borderRadius: s.radius,
7774
7880
  overflow: "hidden"
7775
7881
  },
7776
7882
  children: isActive ? /* @__PURE__ */ jsx(
@@ -7778,9 +7884,21 @@ function TempoBar({
7778
7884
  {
7779
7885
  config,
7780
7886
  phaseElapsedMs,
7781
- targetMs
7887
+ targetMs,
7888
+ radius: s.radius,
7889
+ font: s.segFont,
7890
+ display,
7891
+ onToggleDisplay: toggleDisplay
7782
7892
  }
7783
- ) : completedMs != null ? /* @__PURE__ */ jsx(CompletedSegment, { config, completedMs, targetMs }) : null
7893
+ ) : completedMs != null ? /* @__PURE__ */ jsx(
7894
+ CompletedSegment,
7895
+ {
7896
+ config,
7897
+ completedMs,
7898
+ targetMs,
7899
+ font: s.segFont
7900
+ }
7901
+ ) : null
7784
7902
  },
7785
7903
  phase
7786
7904
  );
@@ -7790,49 +7908,65 @@ function TempoBar({
7790
7908
  function ActiveSegment({
7791
7909
  config,
7792
7910
  phaseElapsedMs,
7793
- targetMs
7911
+ targetMs,
7912
+ radius,
7913
+ font,
7914
+ display,
7915
+ onToggleDisplay
7794
7916
  }) {
7795
7917
  const pacing = getTempoPacingState(phaseElapsedMs, targetMs);
7796
7918
  const barColor = pacing === "behind" ? t2["status-error"] : config.color;
7797
7919
  const fillPct = getTempoFillPct(phaseElapsedMs, targetMs);
7798
- const label = targetMs ? `${formatDuration2(phaseElapsedMs)} / ${formatDuration2(targetMs)}` : formatDuration2(phaseElapsedMs);
7799
- return /* @__PURE__ */ jsxs(reactNative.View, { style: { height: "100%", position: "relative" }, testID: `tempo-segment-active-${config.label}`, children: [
7800
- /* @__PURE__ */ jsx(
7801
- reactNative.View,
7802
- {
7803
- style: {
7804
- position: "absolute",
7805
- left: 0,
7806
- top: 0,
7807
- bottom: 0,
7808
- width: `${fillPct}%`,
7809
- backgroundColor: alpha(barColor, 0.3),
7810
- borderRadius: 4
7811
- }
7812
- }
7813
- ),
7814
- /* @__PURE__ */ jsx(
7815
- reactNative.View,
7816
- {
7817
- style: {
7818
- height: "100%",
7819
- flexDirection: "row",
7820
- alignItems: "center",
7821
- justifyContent: "center"
7822
- },
7823
- children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 12, fontWeight: "700", color: barColor }, children: label })
7824
- }
7825
- )
7826
- ] });
7920
+ const label = display === "delta" && targetMs != null ? formatTempoDelta(targetMs - phaseElapsedMs) : targetMs != null ? `${formatDuration2(phaseElapsedMs)} / ${formatDuration2(targetMs)}` : formatDuration2(phaseElapsedMs);
7921
+ return /* @__PURE__ */ jsxs(
7922
+ reactNative.Pressable,
7923
+ {
7924
+ onPress: onToggleDisplay,
7925
+ style: { height: "100%", position: "relative" },
7926
+ accessibilityRole: "button",
7927
+ accessibilityLabel: `Tempo readout: ${display === "delta" ? "delta" : "time"} \u2014 tap to switch`,
7928
+ testID: `tempo-segment-active-${config.label}`,
7929
+ children: [
7930
+ /* @__PURE__ */ jsx(
7931
+ reactNative.View,
7932
+ {
7933
+ style: {
7934
+ position: "absolute",
7935
+ left: 0,
7936
+ top: 0,
7937
+ bottom: 0,
7938
+ width: `${fillPct}%`,
7939
+ backgroundColor: alpha(barColor, 0.3),
7940
+ borderRadius: radius
7941
+ }
7942
+ }
7943
+ ),
7944
+ /* @__PURE__ */ jsx(
7945
+ reactNative.View,
7946
+ {
7947
+ style: {
7948
+ height: "100%",
7949
+ flexDirection: "row",
7950
+ alignItems: "center",
7951
+ justifyContent: "center"
7952
+ },
7953
+ children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: font, fontWeight: "700", color: barColor }, children: label })
7954
+ }
7955
+ )
7956
+ ]
7957
+ }
7958
+ );
7827
7959
  }
7828
7960
  function CompletedSegment({
7829
7961
  config,
7830
7962
  completedMs,
7831
- targetMs
7963
+ targetMs,
7964
+ font
7832
7965
  }) {
7833
7966
  const pacing = getTempoPacingState(completedMs, targetMs);
7834
7967
  const hitTarget = pacing !== "behind";
7835
7968
  const indicator = targetMs && targetMs >= TEMPO_PACING.minPhaseDurationMs ? hitTarget ? " \u2713" : " \u2717" : "";
7969
+ const cueColor = hitTarget ? config.color : t2["status-error"];
7836
7970
  return /* @__PURE__ */ jsx(
7837
7971
  reactNative.View,
7838
7972
  {
@@ -7841,13 +7975,23 @@ function CompletedSegment({
7841
7975
  flexDirection: "row",
7842
7976
  alignItems: "center",
7843
7977
  justifyContent: "center",
7844
- backgroundColor: alpha(config.color, 0.15)
7978
+ backgroundColor: alpha(cueColor, 0.15)
7845
7979
  },
7846
7980
  testID: `tempo-segment-completed-${config.label}`,
7847
- children: /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 12, fontWeight: "500", color: alpha(config.color, 0.7) }, children: [
7848
- formatDuration2(completedMs),
7849
- indicator
7850
- ] })
7981
+ children: /* @__PURE__ */ jsxs(
7982
+ reactNative.Text,
7983
+ {
7984
+ style: {
7985
+ fontSize: font,
7986
+ fontWeight: "500",
7987
+ color: hitTarget ? alpha(config.color, 0.7) : cueColor
7988
+ },
7989
+ children: [
7990
+ formatDuration2(completedMs),
7991
+ indicator
7992
+ ]
7993
+ }
7994
+ )
7851
7995
  }
7852
7996
  );
7853
7997
  }
@@ -8937,6 +9081,14 @@ var EXPANDED_ENCODED_PCT = 45;
8937
9081
  var EXPANDED_HEIGHT = 60;
8938
9082
  var FIXED_MAX_VELOCITY = 1.15;
8939
9083
  var BARE_STUB_HEIGHT = 3;
9084
+ var HERO_HEIGHT = 220;
9085
+ var HERO_LABEL_HEADROOM = 20;
9086
+ var HERO_BAR_GAP = 8;
9087
+ var HERO_BAR_MAX_WIDTH = 52;
9088
+ var HERO_BAR_RADIUS = 5;
9089
+ var HERO_MIN_BAR_HEIGHT = 4;
9090
+ var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
9091
+ var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
8940
9092
  function scaleDenominator(scale, maxVelocity) {
8941
9093
  return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
8942
9094
  }
@@ -8964,6 +9116,183 @@ function usePrefersReducedMotion() {
8964
9116
  var ANIMATION_DURATION = 400;
8965
9117
  var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
8966
9118
  var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
9119
+ function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
9120
+ const prefersReducedMotion = usePrefersReducedMotion();
9121
+ const [liveScale] = React24.useState(() => new reactNative.Animated.Value(1));
9122
+ React24.useEffect(() => {
9123
+ if (!active || liveRepIndex == null || liveVelocity == null) return;
9124
+ if (prefersReducedMotion) {
9125
+ liveScale.setValue(1);
9126
+ return;
9127
+ }
9128
+ if (isNewPeak) {
9129
+ liveScale.setValue(1);
9130
+ reactNative.Animated.sequence([
9131
+ reactNative.Animated.timing(liveScale, {
9132
+ toValue: 1.25,
9133
+ duration: 150,
9134
+ easing: reactNative.Easing.out(reactNative.Easing.quad),
9135
+ useNativeDriver: true
9136
+ }),
9137
+ reactNative.Animated.spring(liveScale, {
9138
+ toValue: 1,
9139
+ friction: 3,
9140
+ tension: 140,
9141
+ useNativeDriver: true
9142
+ })
9143
+ ]).start();
9144
+ } else {
9145
+ liveScale.setValue(0.8);
9146
+ reactNative.Animated.timing(liveScale, {
9147
+ toValue: 1,
9148
+ duration: 300,
9149
+ easing: POP_EASING,
9150
+ useNativeDriver: true
9151
+ }).start();
9152
+ }
9153
+ }, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
9154
+ return liveScale;
9155
+ }
9156
+ function HeroVelocityChart({
9157
+ doneVelocities,
9158
+ barColorFor,
9159
+ scaleDenom,
9160
+ referenceVelocity,
9161
+ liveRepIndex,
9162
+ isNewPeak,
9163
+ targetReps,
9164
+ height,
9165
+ className,
9166
+ viewProps
9167
+ }) {
9168
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
9169
+ const liveScale = useLiveRepPop(true, liveRepIndex, liveVelocity, isNewPeak);
9170
+ const plotHeight = Math.max(0, height - HERO_LABEL_HEADROOM);
9171
+ const barHeight = (velocity) => scaleDenom > 0 ? Math.max(HERO_MIN_BAR_HEIGHT, Math.min(1, velocity / scaleDenom) * plotHeight) : HERO_MIN_BAR_HEIGHT;
9172
+ const pendingCount = Math.max(0, (targetReps ?? 0) - doneVelocities.length);
9173
+ const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
9174
+ const repCount = doneVelocities.length;
9175
+ const total = Math.max(repCount, targetReps ?? repCount);
9176
+ const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
9177
+ const { style: externalStyle, ...restProps } = viewProps;
9178
+ return /* @__PURE__ */ jsx(
9179
+ reactNative.View,
9180
+ {
9181
+ className,
9182
+ style: [{ height }, externalStyle],
9183
+ accessibilityRole: "image",
9184
+ accessibilityLabel: label,
9185
+ testID: "velocity-strip-hero",
9186
+ ...restProps,
9187
+ children: /* @__PURE__ */ jsxs(
9188
+ reactNative.View,
9189
+ {
9190
+ style: {
9191
+ flex: 1,
9192
+ flexDirection: "row",
9193
+ alignItems: "flex-end",
9194
+ gap: HERO_BAR_GAP,
9195
+ position: "relative",
9196
+ borderBottomWidth: 2,
9197
+ borderBottomColor: HERO_PENDING_COLOR
9198
+ },
9199
+ children: [
9200
+ referencePx > 0 && /* @__PURE__ */ jsx(
9201
+ reactNative.View,
9202
+ {
9203
+ accessibilityElementsHidden: true,
9204
+ style: {
9205
+ position: "absolute",
9206
+ left: 0,
9207
+ right: 0,
9208
+ bottom: referencePx,
9209
+ borderTopWidth: 1,
9210
+ borderStyle: "dashed",
9211
+ borderColor: HERO_REFERENCE_COLOR,
9212
+ pointerEvents: "none"
9213
+ },
9214
+ testID: "velocity-hero-reference"
9215
+ }
9216
+ ),
9217
+ doneVelocities.map((velocity, i) => {
9218
+ const isLive = liveRepIndex === i;
9219
+ return /* @__PURE__ */ jsxs(
9220
+ reactNative.View,
9221
+ {
9222
+ accessibilityElementsHidden: true,
9223
+ style: {
9224
+ flex: 1,
9225
+ maxWidth: HERO_BAR_MAX_WIDTH,
9226
+ height: "100%",
9227
+ alignItems: "center",
9228
+ justifyContent: "flex-end"
9229
+ },
9230
+ children: [
9231
+ /* @__PURE__ */ jsx(
9232
+ reactNative.Text,
9233
+ {
9234
+ className: "text-text-primary",
9235
+ style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
9236
+ testID: `velocity-label-${i}`,
9237
+ children: formatVelocity(velocity)
9238
+ }
9239
+ ),
9240
+ /* @__PURE__ */ jsx(
9241
+ reactNative.Animated.View,
9242
+ {
9243
+ style: [
9244
+ {
9245
+ width: "100%",
9246
+ height: barHeight(velocity),
9247
+ borderTopLeftRadius: HERO_BAR_RADIUS,
9248
+ borderTopRightRadius: HERO_BAR_RADIUS,
9249
+ backgroundColor: barColorFor(velocity)
9250
+ },
9251
+ isLive ? { transform: [{ scale: liveScale }] } : null
9252
+ ],
9253
+ testID: `velocity-bar-${i}`
9254
+ }
9255
+ )
9256
+ ]
9257
+ },
9258
+ i
9259
+ );
9260
+ }),
9261
+ Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
9262
+ reactNative.View,
9263
+ {
9264
+ accessibilityElementsHidden: true,
9265
+ style: {
9266
+ flex: 1,
9267
+ maxWidth: HERO_BAR_MAX_WIDTH,
9268
+ height: "100%",
9269
+ alignItems: "center",
9270
+ justifyContent: "flex-end"
9271
+ },
9272
+ children: /* @__PURE__ */ jsx(
9273
+ reactNative.View,
9274
+ {
9275
+ style: {
9276
+ width: "100%",
9277
+ height: HERO_MIN_BAR_HEIGHT * 3,
9278
+ borderWidth: 1,
9279
+ borderStyle: "dashed",
9280
+ borderColor: HERO_PENDING_COLOR,
9281
+ borderTopLeftRadius: HERO_BAR_RADIUS,
9282
+ borderTopRightRadius: HERO_BAR_RADIUS
9283
+ },
9284
+ testID: "velocity-slot-todo"
9285
+ }
9286
+ )
9287
+ },
9288
+ `pending-${i}`
9289
+ ))
9290
+ ]
9291
+ }
9292
+ )
9293
+ }
9294
+ );
9295
+ }
8967
9296
  function VelocityStrip({
8968
9297
  velocities,
8969
9298
  set,
@@ -8974,6 +9303,7 @@ function VelocityStrip({
8974
9303
  onRepPress,
8975
9304
  variant = "expanded",
8976
9305
  height = EXPANDED_HEIGHT,
9306
+ targetReps,
8977
9307
  scale = "peak",
8978
9308
  showNumbers = true,
8979
9309
  showInfo = true,
@@ -8999,45 +9329,17 @@ function VelocityStrip({
8999
9329
  return SET_STRIP_VARIABLE_COLOR;
9000
9330
  };
9001
9331
  const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
9002
- const prefersReducedMotion = usePrefersReducedMotion();
9003
9332
  const [heightAnim] = React24.useState(() => new reactNative.Animated.Value(expanded ? height : 3));
9004
9333
  const [labelOpacity] = React24.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
9005
9334
  const [infoOpacity] = React24.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
9006
- const [liveScale] = React24.useState(() => new reactNative.Animated.Value(1));
9007
9335
  const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
9008
9336
  const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
9009
- React24.useEffect(() => {
9010
- if (variant !== "expanded" || !framed || liveRepIndex == null || liveVelocity == null) return;
9011
- if (prefersReducedMotion) {
9012
- liveScale.setValue(1);
9013
- return;
9014
- }
9015
- if (isNewPeak) {
9016
- liveScale.setValue(1);
9017
- reactNative.Animated.sequence([
9018
- reactNative.Animated.timing(liveScale, {
9019
- toValue: 1.25,
9020
- duration: 150,
9021
- easing: reactNative.Easing.out(reactNative.Easing.quad),
9022
- useNativeDriver: true
9023
- }),
9024
- reactNative.Animated.spring(liveScale, {
9025
- toValue: 1,
9026
- friction: 3,
9027
- tension: 140,
9028
- useNativeDriver: true
9029
- })
9030
- ]).start();
9031
- } else {
9032
- liveScale.setValue(0.8);
9033
- reactNative.Animated.timing(liveScale, {
9034
- toValue: 1,
9035
- duration: 300,
9036
- easing: POP_EASING,
9037
- useNativeDriver: true
9038
- }).start();
9039
- }
9040
- }, [variant, framed, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
9337
+ const liveScale = useLiveRepPop(
9338
+ variant === "expanded" && framed,
9339
+ liveRepIndex,
9340
+ liveVelocity,
9341
+ isNewPeak
9342
+ );
9041
9343
  React24.useEffect(() => {
9042
9344
  if (variant !== "expanded" || !framed) return;
9043
9345
  reactNative.Animated.parallel([
@@ -9064,6 +9366,24 @@ function VelocityStrip({
9064
9366
  if (set == null && velocities == null) return null;
9065
9367
  const repCount = doneVelocities.length;
9066
9368
  const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
9369
+ if (variant === "hero") {
9370
+ const heroHeight = height === EXPANDED_HEIGHT ? HERO_HEIGHT : height;
9371
+ return /* @__PURE__ */ jsx(
9372
+ HeroVelocityChart,
9373
+ {
9374
+ doneVelocities,
9375
+ barColorFor,
9376
+ scaleDenom,
9377
+ referenceVelocity: maxVelocity,
9378
+ liveRepIndex,
9379
+ isNewPeak,
9380
+ targetReps,
9381
+ height: heroHeight,
9382
+ className,
9383
+ viewProps: props
9384
+ }
9385
+ );
9386
+ }
9067
9387
  if (variant === "mini") {
9068
9388
  const { style: externalStyle, ...restProps } = props;
9069
9389
  return /* @__PURE__ */ jsx(
@@ -9828,6 +10148,99 @@ function InputBar({
9828
10148
  );
9829
10149
  }
9830
10150
  var BRAND_PRIMARY4 = getSemanticColors("dark")["brand-primary"];
10151
+ var RING_DEFAULT_SIZE = 180;
10152
+ function RestActions({ onAddTime, onSkip }) {
10153
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
10154
+ /* @__PURE__ */ jsx(
10155
+ reactNative.Pressable,
10156
+ {
10157
+ onPress: onAddTime,
10158
+ style: {
10159
+ backgroundColor: "rgba(255,255,255,0.06)",
10160
+ paddingVertical: 8,
10161
+ paddingHorizontal: 20,
10162
+ borderRadius: 8
10163
+ },
10164
+ accessibilityRole: "button",
10165
+ accessibilityLabel: "Add 30 seconds",
10166
+ testID: "rest-timer-add-time",
10167
+ children: /* @__PURE__ */ jsx(
10168
+ reactNative.Text,
10169
+ {
10170
+ className: "text-text-secondary",
10171
+ style: { fontSize: 11, fontFamily: "Inter, sans-serif", fontWeight: "600" },
10172
+ children: "+30s"
10173
+ }
10174
+ )
10175
+ }
10176
+ ),
10177
+ /* @__PURE__ */ jsx(
10178
+ reactNative.Pressable,
10179
+ {
10180
+ onPress: onSkip,
10181
+ style: {
10182
+ backgroundColor: "rgba(255,121,0,0.12)",
10183
+ paddingVertical: 8,
10184
+ paddingHorizontal: 20,
10185
+ borderRadius: 8
10186
+ },
10187
+ accessibilityRole: "button",
10188
+ accessibilityLabel: "Skip rest",
10189
+ testID: "rest-timer-skip",
10190
+ children: /* @__PURE__ */ jsx(
10191
+ reactNative.Text,
10192
+ {
10193
+ style: {
10194
+ fontSize: 11,
10195
+ fontFamily: "Inter, sans-serif",
10196
+ fontWeight: "600",
10197
+ color: BRAND_PRIMARY4
10198
+ },
10199
+ children: "Skip"
10200
+ }
10201
+ )
10202
+ }
10203
+ )
10204
+ ] });
10205
+ }
10206
+ function RestTimerRing({
10207
+ totalSeconds,
10208
+ elapsedMs,
10209
+ remainingSec,
10210
+ done,
10211
+ size,
10212
+ nextSetInfo,
10213
+ displayOnly,
10214
+ onSkip,
10215
+ onAddTime
10216
+ }) {
10217
+ const a11yLabel = done ? "Rest complete, next set ready" : `Rest timer, ${Math.max(0, remainingSec)} seconds remaining`;
10218
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: { alignItems: "center", gap: 16 }, testID: "rest-timer", children: [
10219
+ /* @__PURE__ */ jsx(
10220
+ CircularTimer,
10221
+ {
10222
+ durationMs: totalSeconds * 1e3,
10223
+ elapsedMs,
10224
+ mode: "down",
10225
+ size,
10226
+ doneLabel: "GO",
10227
+ doneColor: "success",
10228
+ accessibilityLabel: a11yLabel,
10229
+ controls: !displayOnly ? /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip }) : void 0,
10230
+ testID: "rest-timer-ring"
10231
+ }
10232
+ ),
10233
+ nextSetInfo != null && /* @__PURE__ */ jsx(
10234
+ reactNative.Text,
10235
+ {
10236
+ className: "text-text-tertiary",
10237
+ style: { fontSize: 13, fontFamily: "Inter, sans-serif" },
10238
+ testID: "rest-timer-next-set",
10239
+ children: nextSetInfo
10240
+ }
10241
+ )
10242
+ ] });
10243
+ }
9831
10244
  function RestTimer({
9832
10245
  totalSeconds,
9833
10246
  elapsedMs,
@@ -9835,12 +10248,15 @@ function RestTimer({
9835
10248
  onAddTime,
9836
10249
  nextSetInfo,
9837
10250
  visible,
9838
- displayOnly = false
10251
+ displayOnly = false,
10252
+ variant = "bar",
10253
+ size = RING_DEFAULT_SIZE
9839
10254
  }) {
9840
10255
  const {
9841
10256
  remainingMs,
9842
10257
  progress,
9843
- label: timeDisplay
10258
+ label: timeDisplay,
10259
+ done
9844
10260
  } = useTimer({
9845
10261
  mode: "down",
9846
10262
  durationMs: totalSeconds * 1e3,
@@ -9849,6 +10265,22 @@ function RestTimer({
9849
10265
  const remainingSec = Math.ceil(remainingMs / 1e3);
9850
10266
  const progressPct = progress * 100;
9851
10267
  if (!visible) return null;
10268
+ if (variant === "ring") {
10269
+ return /* @__PURE__ */ jsx(
10270
+ RestTimerRing,
10271
+ {
10272
+ totalSeconds,
10273
+ elapsedMs,
10274
+ remainingSec,
10275
+ done,
10276
+ size,
10277
+ nextSetInfo,
10278
+ displayOnly,
10279
+ onSkip,
10280
+ onAddTime
10281
+ }
10282
+ );
10283
+ }
9852
10284
  return /* @__PURE__ */ jsxs(
9853
10285
  reactNative.View,
9854
10286
  {
@@ -9946,62 +10378,7 @@ function RestTimer({
9946
10378
  )
9947
10379
  }
9948
10380
  ),
9949
- !displayOnly && /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
9950
- /* @__PURE__ */ jsx(
9951
- reactNative.Pressable,
9952
- {
9953
- onPress: onAddTime,
9954
- style: {
9955
- backgroundColor: "rgba(255,255,255,0.06)",
9956
- paddingVertical: 8,
9957
- paddingHorizontal: 20,
9958
- borderRadius: 8
9959
- },
9960
- accessibilityRole: "button",
9961
- accessibilityLabel: "Add 30 seconds",
9962
- testID: "rest-timer-add-time",
9963
- children: /* @__PURE__ */ jsx(
9964
- reactNative.Text,
9965
- {
9966
- className: "text-text-secondary",
9967
- style: {
9968
- fontSize: 11,
9969
- fontFamily: "Inter, sans-serif",
9970
- fontWeight: "600"
9971
- },
9972
- children: "+30s"
9973
- }
9974
- )
9975
- }
9976
- ),
9977
- /* @__PURE__ */ jsx(
9978
- reactNative.Pressable,
9979
- {
9980
- onPress: onSkip,
9981
- style: {
9982
- backgroundColor: "rgba(255,121,0,0.12)",
9983
- paddingVertical: 8,
9984
- paddingHorizontal: 20,
9985
- borderRadius: 8
9986
- },
9987
- accessibilityRole: "button",
9988
- accessibilityLabel: "Skip rest",
9989
- testID: "rest-timer-skip",
9990
- children: /* @__PURE__ */ jsx(
9991
- reactNative.Text,
9992
- {
9993
- style: {
9994
- fontSize: 11,
9995
- fontFamily: "Inter, sans-serif",
9996
- fontWeight: "600",
9997
- color: BRAND_PRIMARY4
9998
- },
9999
- children: "Skip"
10000
- }
10001
- )
10002
- }
10003
- )
10004
- ] })
10381
+ !displayOnly && /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip })
10005
10382
  ]
10006
10383
  }
10007
10384
  );
@@ -12991,9 +13368,36 @@ function glowShadow(color) {
12991
13368
  var DEFAULT_TRACK_COLOR = primitiveColors.charcoal[200];
12992
13369
  var DEFAULT_MARKER_COLOR2 = primitiveColors.white;
12993
13370
  var TICK_COLOR = t15["text-tertiary"];
12994
- var DEFAULT_TRACK_HEIGHT = 14;
12995
- var DEFAULT_NEEDLE_OVERHANG = 6;
12996
- var NEEDLE_WIDTH = 4;
13371
+ var ZONE_SIZES = {
13372
+ default: {
13373
+ trackHeight: 14,
13374
+ needleOverhang: 6,
13375
+ needleWidth: 4,
13376
+ tickLineNormal: 1.5,
13377
+ tickLineEmphasized: 2,
13378
+ tickLineHeightPad: 4,
13379
+ tickLineTopPad: 2,
13380
+ labelRowHeight: 16,
13381
+ labelMarginTop: 5,
13382
+ tickFont: 9,
13383
+ tickLetterSpacing: 0.5,
13384
+ tickCellWidth: 28
13385
+ },
13386
+ wall: {
13387
+ trackHeight: 24,
13388
+ needleOverhang: 9,
13389
+ needleWidth: 7,
13390
+ tickLineNormal: 2.5,
13391
+ tickLineEmphasized: 3.5,
13392
+ tickLineHeightPad: 6,
13393
+ tickLineTopPad: 3,
13394
+ labelRowHeight: 24,
13395
+ labelMarginTop: 8,
13396
+ tickFont: 14,
13397
+ tickLetterSpacing: 0.5,
13398
+ tickCellWidth: 48
13399
+ }
13400
+ };
12997
13401
  function fraction(value, min, max) {
12998
13402
  if (max <= min) return 0;
12999
13403
  return Math.max(0, Math.min(1, (value - min) / (max - min)));
@@ -13008,14 +13412,18 @@ function ZoneTrack({
13008
13412
  marker = null,
13009
13413
  ticks,
13010
13414
  band = null,
13011
- trackHeight = DEFAULT_TRACK_HEIGHT,
13012
- needleOverhang = DEFAULT_NEEDLE_OVERHANG,
13415
+ size = "default",
13416
+ trackHeight: trackHeightProp,
13417
+ needleOverhang: needleOverhangProp,
13013
13418
  trackColor = DEFAULT_TRACK_COLOR,
13014
13419
  className,
13015
13420
  style,
13016
13421
  accessibilityLabel: accessibilityLabel2,
13017
13422
  ...props
13018
13423
  }) {
13424
+ const s = ZONE_SIZES[size];
13425
+ const trackHeight = trackHeightProp ?? s.trackHeight;
13426
+ const needleOverhang = needleOverhangProp ?? s.needleOverhang;
13019
13427
  const isNeedle = marker?.type === "needle";
13020
13428
  const markerHeight = isNeedle ? trackHeight + needleOverhang * 2 : trackHeight;
13021
13429
  const markerFrac = marker != null ? fraction(marker.value, min, max) : 0;
@@ -13125,18 +13533,18 @@ function ZoneTrack({
13125
13533
  position: "absolute",
13126
13534
  top: 0,
13127
13535
  left: pct(markerFrac),
13128
- width: NEEDLE_WIDTH,
13536
+ width: s.needleWidth,
13129
13537
  height: markerHeight,
13130
- borderRadius: NEEDLE_WIDTH / 2,
13538
+ borderRadius: s.needleWidth / 2,
13131
13539
  backgroundColor: marker.color ?? DEFAULT_MARKER_COLOR2,
13132
- transform: [{ translateX: -NEEDLE_WIDTH / 2 }]
13540
+ transform: [{ translateX: -s.needleWidth / 2 }]
13133
13541
  }
13134
13542
  }
13135
13543
  ),
13136
13544
  ticks?.map((tick, i) => {
13137
13545
  const emphasized = tick.emphasized === true;
13138
13546
  const lineColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13139
- const lineWidth = emphasized ? 2 : 1.5;
13547
+ const lineWidth = emphasized ? s.tickLineEmphasized : s.tickLineNormal;
13140
13548
  return /* @__PURE__ */ jsx(
13141
13549
  reactNative.View,
13142
13550
  {
@@ -13144,10 +13552,10 @@ function ZoneTrack({
13144
13552
  accessibilityElementsHidden: true,
13145
13553
  style: {
13146
13554
  position: "absolute",
13147
- top: (markerHeight - trackHeight) / 2 - 2,
13555
+ top: (markerHeight - trackHeight) / 2 - s.tickLineTopPad,
13148
13556
  left: pct(fraction(tick.value, min, max)),
13149
13557
  width: lineWidth,
13150
- height: trackHeight + 4,
13558
+ height: trackHeight + s.tickLineHeightPad,
13151
13559
  borderRadius: lineWidth / 2,
13152
13560
  backgroundColor: lineColor,
13153
13561
  transform: [{ translateX: -lineWidth / 2 }],
@@ -13158,40 +13566,46 @@ function ZoneTrack({
13158
13566
  );
13159
13567
  })
13160
13568
  ] }),
13161
- ticks != null && ticks.length > 0 && /* @__PURE__ */ jsx(reactNative.View, { style: { position: "relative", height: 16, marginTop: 5 }, children: ticks.map((tick, i) => {
13162
- if (tick.label == null) return null;
13163
- const emphasized = tick.emphasized === true;
13164
- const labelColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13165
- const labelNode = /* @__PURE__ */ jsx(
13166
- reactNative.Text,
13167
- {
13168
- testID: "zone-track-tick-label",
13169
- style: {
13170
- fontSize: 9,
13171
- letterSpacing: 0.5,
13172
- color: labelColor,
13173
- fontFamily: "monospace",
13174
- fontWeight: emphasized ? "700" : "400"
13175
- },
13176
- children: tick.label
13177
- }
13178
- );
13179
- return /* @__PURE__ */ jsx(
13180
- reactNative.View,
13181
- {
13182
- testID: "zone-track-tick",
13183
- style: {
13184
- position: "absolute",
13185
- left: pct(fraction(tick.value, min, max)),
13186
- transform: [{ translateX: -14 }],
13187
- width: 28,
13188
- alignItems: "center"
13189
- },
13190
- children: tick.tooltip != null ? /* @__PURE__ */ jsx(Tooltip, { label: tick.tooltip, placement: "top", children: labelNode }) : labelNode
13191
- },
13192
- `label-${i}`
13193
- );
13194
- }) })
13569
+ ticks != null && ticks.length > 0 && /* @__PURE__ */ jsx(
13570
+ reactNative.View,
13571
+ {
13572
+ style: { position: "relative", height: s.labelRowHeight, marginTop: s.labelMarginTop },
13573
+ children: ticks.map((tick, i) => {
13574
+ if (tick.label == null) return null;
13575
+ const emphasized = tick.emphasized === true;
13576
+ const labelColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13577
+ const labelNode = /* @__PURE__ */ jsx(
13578
+ reactNative.Text,
13579
+ {
13580
+ testID: "zone-track-tick-label",
13581
+ style: {
13582
+ fontSize: s.tickFont,
13583
+ letterSpacing: s.tickLetterSpacing,
13584
+ color: labelColor,
13585
+ fontFamily: "monospace",
13586
+ fontWeight: emphasized ? "700" : "400"
13587
+ },
13588
+ children: tick.label
13589
+ }
13590
+ );
13591
+ return /* @__PURE__ */ jsx(
13592
+ reactNative.View,
13593
+ {
13594
+ testID: "zone-track-tick",
13595
+ style: {
13596
+ position: "absolute",
13597
+ left: pct(fraction(tick.value, min, max)),
13598
+ transform: [{ translateX: -s.tickCellWidth / 2 }],
13599
+ width: s.tickCellWidth,
13600
+ alignItems: "center"
13601
+ },
13602
+ children: tick.tooltip != null ? /* @__PURE__ */ jsx(Tooltip, { label: tick.tooltip, placement: "top", children: labelNode }) : labelNode
13603
+ },
13604
+ `label-${i}`
13605
+ );
13606
+ })
13607
+ }
13608
+ )
13195
13609
  ]
13196
13610
  }
13197
13611
  );
@@ -13209,7 +13623,8 @@ function FatigueMeter({
13209
13623
  thresholds = DEFAULT_THRESHOLDS,
13210
13624
  zoneColors = DEFAULT_ZONE_COLORS,
13211
13625
  labels = DEFAULT_LABELS,
13212
- trackHeight = 14,
13626
+ size = "default",
13627
+ trackHeight,
13213
13628
  needleColor,
13214
13629
  className,
13215
13630
  ...props
@@ -13230,6 +13645,7 @@ function FatigueMeter({
13230
13645
  zones,
13231
13646
  min: 0,
13232
13647
  max,
13648
+ size,
13233
13649
  trackHeight,
13234
13650
  ticks,
13235
13651
  marker: { type: "needle", value, color: needleColor },
@@ -14296,6 +14712,7 @@ exports.CheckboxGroup = CheckboxGroup;
14296
14712
  exports.Chip = Chip;
14297
14713
  exports.CircleSlashIcon = CircleSlashIcon;
14298
14714
  exports.CircularProgress = CircularProgress;
14715
+ exports.CircularTimer = CircularTimer;
14299
14716
  exports.Collapse = Collapse;
14300
14717
  exports.CollapseButton = CollapseButton;
14301
14718
  exports.CollapseContent = CollapseContent;