@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.mjs CHANGED
@@ -152,6 +152,7 @@ var statusColors = {
152
152
  subtle: "bg-status-success-subtle",
153
153
  outline: "border-2 border-status-success bg-transparent",
154
154
  solid: "bg-status-success",
155
+ border: "border-status-success",
155
156
  icon: "text-status-success",
156
157
  text: "text-status-success"
157
158
  },
@@ -159,6 +160,7 @@ var statusColors = {
159
160
  subtle: "bg-status-info-subtle",
160
161
  outline: "border-2 border-status-info bg-transparent",
161
162
  solid: "bg-status-info",
163
+ border: "border-status-info",
162
164
  icon: "text-status-info",
163
165
  text: "text-status-info"
164
166
  },
@@ -166,6 +168,7 @@ var statusColors = {
166
168
  subtle: "bg-status-warning-subtle",
167
169
  outline: "border-2 border-status-warning bg-transparent",
168
170
  solid: "bg-status-warning",
171
+ border: "border-status-warning",
169
172
  icon: "text-status-warning",
170
173
  text: "text-status-warning"
171
174
  },
@@ -173,6 +176,7 @@ var statusColors = {
173
176
  subtle: "bg-status-error-subtle",
174
177
  outline: "border-2 border-status-error bg-transparent",
175
178
  solid: "bg-status-error",
179
+ border: "border-status-error",
176
180
  icon: "text-status-error",
177
181
  text: "text-status-error"
178
182
  }
@@ -186,6 +190,8 @@ var defaultIcons = {
186
190
  function Alert({
187
191
  status = "info",
188
192
  variant = "subtle",
193
+ size = "default",
194
+ message,
189
195
  icon,
190
196
  showIcon = true,
191
197
  onClose,
@@ -195,28 +201,43 @@ function Alert({
195
201
  }) {
196
202
  const colors = statusColors[status];
197
203
  const isSolid = variant === "solid";
204
+ const isCompact = size === "compact";
198
205
  return /* @__PURE__ */ jsxs(
199
206
  View,
200
207
  {
201
208
  accessibilityRole: "alert",
202
209
  className: cn(
203
- "flex-row items-start p-4 rounded-lg",
210
+ isCompact ? "flex-row items-center px-3 py-2 rounded-lg" : "flex-row items-start p-4 rounded-lg",
204
211
  colors[variant],
212
+ // A compact `subtle` pill gets a hairline status border so it reads as a
213
+ // defined cue on a dark wall (the CueFlag look); other variants carry their own.
214
+ isCompact && variant === "subtle" && cn("border", colors.border),
205
215
  className
206
216
  ),
207
217
  ...props,
208
218
  children: [
209
- showIcon && /* @__PURE__ */ jsx(View, { className: "mr-3 mt-0.5", children: icon || /* @__PURE__ */ jsx(
219
+ showIcon && /* @__PURE__ */ jsx(View, { className: isCompact ? "mr-2" : "mr-3 mt-0.5", children: icon || /* @__PURE__ */ jsx(
210
220
  Text,
211
221
  {
212
222
  className: cn(
213
- "text-lg font-bold",
223
+ "font-bold",
224
+ isCompact ? "text-base" : "text-lg",
214
225
  isSolid ? "text-white" : colors.icon
215
226
  ),
216
227
  children: defaultIcons[status]
217
228
  }
218
229
  ) }),
219
- /* @__PURE__ */ jsx(View, { className: "flex-1", children }),
230
+ /* @__PURE__ */ jsxs(View, { className: "flex-1", children: [
231
+ message != null && /* @__PURE__ */ jsx(
232
+ Text,
233
+ {
234
+ className: cn("text-sm font-semibold", isSolid ? "text-white" : colors.text),
235
+ testID: "alert-message",
236
+ children: message
237
+ }
238
+ ),
239
+ children
240
+ ] }),
220
241
  onClose && /* @__PURE__ */ jsx(
221
242
  Pressable,
222
243
  {
@@ -5066,6 +5087,8 @@ function CircularProgress({
5066
5087
  color = "primary",
5067
5088
  showValue = false,
5068
5089
  formatValue: formatValue3 = (v, m) => `${Math.round(v / m * 100)}%`,
5090
+ children,
5091
+ fill = false,
5069
5092
  className,
5070
5093
  ...props
5071
5094
  }) {
@@ -5074,11 +5097,12 @@ function CircularProgress({
5074
5097
  const circumference = 2 * Math.PI * radius;
5075
5098
  const strokeDashoffset = circumference - percentage / 100 * circumference;
5076
5099
  const center = size / 2;
5100
+ const boxStyle = fill ? { width: "100%", aspectRatio: 1 } : { width: size, height: size };
5077
5101
  return /* @__PURE__ */ jsxs(
5078
5102
  View,
5079
5103
  {
5080
5104
  className: cn("items-center justify-center", className),
5081
- style: { width: size, height: size },
5105
+ style: boxStyle,
5082
5106
  accessibilityRole: "progressbar",
5083
5107
  accessibilityValue: {
5084
5108
  min: 0,
@@ -5087,52 +5111,56 @@ function CircularProgress({
5087
5111
  },
5088
5112
  ...props,
5089
5113
  children: [
5090
- /* @__PURE__ */ jsx(
5114
+ /* @__PURE__ */ jsx(View, { className: cn(isIndeterminate && "animate-spin"), style: boxStyle, children: /* @__PURE__ */ jsxs(
5115
+ "svg",
5116
+ {
5117
+ width: "100%",
5118
+ height: "100%",
5119
+ viewBox: `0 0 ${size} ${size}`,
5120
+ style: { position: "absolute" },
5121
+ children: [
5122
+ /* @__PURE__ */ jsx(
5123
+ "circle",
5124
+ {
5125
+ cx: center,
5126
+ cy: center,
5127
+ r: radius,
5128
+ fill: "none",
5129
+ stroke: "var(--color-border-default)",
5130
+ strokeWidth
5131
+ }
5132
+ ),
5133
+ /* @__PURE__ */ jsx(
5134
+ "circle",
5135
+ {
5136
+ cx: center,
5137
+ cy: center,
5138
+ r: radius,
5139
+ fill: "none",
5140
+ stroke: colorVarMap[color],
5141
+ strokeWidth,
5142
+ strokeLinecap: "round",
5143
+ strokeDasharray: circumference,
5144
+ strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
5145
+ transform: `rotate(-90 ${center} ${center})`,
5146
+ style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
5147
+ }
5148
+ )
5149
+ ]
5150
+ }
5151
+ ) }),
5152
+ children != null ? /* @__PURE__ */ jsx(
5091
5153
  View,
5092
5154
  {
5093
- className: cn(isIndeterminate && "animate-spin"),
5094
- style: { width: size, height: size },
5095
- children: /* @__PURE__ */ jsxs(
5096
- "svg",
5097
- {
5098
- width: size,
5099
- height: size,
5100
- viewBox: `0 0 ${size} ${size}`,
5101
- style: { position: "absolute" },
5102
- children: [
5103
- /* @__PURE__ */ jsx(
5104
- "circle",
5105
- {
5106
- cx: center,
5107
- cy: center,
5108
- r: radius,
5109
- fill: "none",
5110
- stroke: "var(--color-border-default)",
5111
- strokeWidth
5112
- }
5113
- ),
5114
- /* @__PURE__ */ jsx(
5115
- "circle",
5116
- {
5117
- cx: center,
5118
- cy: center,
5119
- r: radius,
5120
- fill: "none",
5121
- stroke: colorVarMap[color],
5122
- strokeWidth,
5123
- strokeLinecap: "round",
5124
- strokeDasharray: circumference,
5125
- strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
5126
- transform: `rotate(-90 ${center} ${center})`,
5127
- style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
5128
- }
5129
- )
5130
- ]
5131
- }
5132
- )
5155
+ style: {
5156
+ position: "absolute",
5157
+ alignItems: "center",
5158
+ justifyContent: "center",
5159
+ pointerEvents: "none"
5160
+ },
5161
+ children
5133
5162
  }
5134
- ),
5135
- showValue && !isIndeterminate && /* @__PURE__ */ jsx(
5163
+ ) : showValue && !isIndeterminate && /* @__PURE__ */ jsx(
5136
5164
  Text,
5137
5165
  {
5138
5166
  className: "text-xs font-semibold text-text-primary absolute",
@@ -7073,6 +7101,70 @@ function TimerReadout({
7073
7101
  }
7074
7102
  );
7075
7103
  }
7104
+ function CircularTimer({
7105
+ durationMs,
7106
+ elapsedMs,
7107
+ mode = "down",
7108
+ size = 160,
7109
+ strokeWidth = 8,
7110
+ fill = false,
7111
+ color = "primary",
7112
+ doneLabel,
7113
+ doneColor = "success",
7114
+ controls,
7115
+ accessibilityLabel: accessibilityLabel2,
7116
+ testID
7117
+ }) {
7118
+ const { remainingMs, progress, label, done } = useTimer({ mode, durationMs, elapsedMs });
7119
+ const isDone = done && mode === "down";
7120
+ const showDone = isDone && doneLabel != null;
7121
+ const remainingFrac = durationMs > 0 ? Math.max(0, Math.min(1, remainingMs / durationMs)) : 0;
7122
+ const arc = mode === "down" ? showDone ? 1 : remainingFrac : progress;
7123
+ const ringColor = showDone ? doneColor : color;
7124
+ const remainingSec = Math.ceil(Math.max(0, remainingMs) / 1e3);
7125
+ const a11y = accessibilityLabel2 ?? (isDone ? "Timer complete" : `${remainingSec} seconds remaining`);
7126
+ return /* @__PURE__ */ jsxs(
7127
+ View,
7128
+ {
7129
+ style: { alignItems: "center", gap: 16 },
7130
+ accessibilityRole: "timer",
7131
+ accessibilityLabel: a11y,
7132
+ testID: testID ?? "circular-timer",
7133
+ children: [
7134
+ /* @__PURE__ */ jsx(
7135
+ CircularProgress,
7136
+ {
7137
+ value: arc,
7138
+ max: 1,
7139
+ size,
7140
+ strokeWidth,
7141
+ fill,
7142
+ color: ringColor,
7143
+ accessibilityLabel: "Timer ring",
7144
+ testID: "circular-timer-ring",
7145
+ children: /* @__PURE__ */ jsx(
7146
+ Text,
7147
+ {
7148
+ className: showDone ? void 0 : "text-text-primary",
7149
+ style: {
7150
+ fontSize: Math.round(size * (showDone ? 0.28 : 0.24)),
7151
+ fontFamily: '"Space Grotesk", sans-serif',
7152
+ fontWeight: "800",
7153
+ fontVariant: ["tabular-nums"],
7154
+ letterSpacing: showDone ? 1 : -1,
7155
+ ...showDone ? { color: colorVarMap[doneColor] } : null
7156
+ },
7157
+ testID: "circular-timer-label",
7158
+ children: showDone ? doneLabel : label
7159
+ }
7160
+ )
7161
+ }
7162
+ ),
7163
+ controls
7164
+ ]
7165
+ }
7166
+ );
7167
+ }
7076
7168
  var sizeConfig2 = {
7077
7169
  sm: { value: "text-lg font-bold", label: "text-xs", unit: "text-xs" },
7078
7170
  md: { value: "text-2xl font-bold", label: "text-xs", unit: "text-sm" },
@@ -7723,6 +7815,11 @@ var TEMPO_PACING = {
7723
7815
  behindThresholdPct: 0.15,
7724
7816
  minPhaseDurationMs: 500
7725
7817
  };
7818
+ function formatTempoDelta(remainingMs) {
7819
+ const s = remainingMs / 1e3;
7820
+ const rounded = Math.abs(s) < 0.05 ? 0 : s;
7821
+ return rounded.toFixed(1);
7822
+ }
7726
7823
  function getTempoPacingState(elapsedMs, targetMs) {
7727
7824
  if (!targetMs || targetMs < TEMPO_PACING.minPhaseDurationMs) return "none";
7728
7825
  const ratio = elapsedMs / targetMs;
@@ -7735,24 +7832,33 @@ function getTempoFillPct(elapsedMs, targetMs) {
7735
7832
  }
7736
7833
  var PHASE_ORDER = ["concentric", "hold", "eccentric"];
7737
7834
  var PHASE_CONFIG = {
7738
- concentric: { label: "Con", color: t2["status-success"], flex: 2 },
7739
- hold: { label: "Hold", color: t2["brand-primary"], flex: 1 },
7740
- eccentric: { label: "Ecc", color: t2["status-warning"], flex: 3 }
7835
+ concentric: { label: "Con", wallLabel: "Concentric", color: t2["status-success"], flex: 2 },
7836
+ hold: { label: "Hold", wallLabel: "Hold", color: t2["brand-primary"], flex: 1 },
7837
+ eccentric: { label: "Ecc", wallLabel: "Eccentric", color: t2["status-warning"], flex: 3 }
7838
+ };
7839
+ var TEMPO_SIZES = {
7840
+ default: { labelMargin: 4, labelFont: 10, barGap: 2, barHeight: 20, radius: 4, segFont: 12 },
7841
+ wall: { labelMargin: 8, labelFont: 15, barGap: 4, barHeight: 40, radius: 6, segFont: 22 }
7741
7842
  };
7742
7843
  function TempoBar({
7743
7844
  activePhase,
7744
7845
  phaseElapsedMs,
7745
7846
  completed,
7746
7847
  target,
7848
+ size = "default",
7849
+ activeDisplay = "time",
7747
7850
  className,
7748
7851
  ...props
7749
7852
  }) {
7853
+ const s = TEMPO_SIZES[size];
7854
+ const [display, setDisplay] = useState(activeDisplay);
7855
+ const toggleDisplay = () => setDisplay((d) => d === "time" ? "delta" : "time");
7750
7856
  return /* @__PURE__ */ jsxs(View, { className, testID: "tempo-bar", ...props, children: [
7751
- /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", marginBottom: 4 }, children: PHASE_ORDER.map((phase) => {
7857
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", marginBottom: s.labelMargin }, children: PHASE_ORDER.map((phase) => {
7752
7858
  const config = PHASE_CONFIG[phase];
7753
- return /* @__PURE__ */ jsx(View, { style: { flex: config.flex, alignItems: "center" }, children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 10, color: t2["text-disabled"] }, children: config.label }) }, phase);
7859
+ return /* @__PURE__ */ jsx(View, { style: { flex: config.flex, alignItems: "center" }, children: /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: { fontSize: s.labelFont, color: t2["text-disabled"] }, children: size === "wall" ? config.wallLabel : config.label }) }, phase);
7754
7860
  }) }),
7755
- /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", gap: 2, height: 20 }, children: PHASE_ORDER.map((phase) => {
7861
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", gap: s.barGap, height: s.barHeight }, children: PHASE_ORDER.map((phase) => {
7756
7862
  const config = PHASE_CONFIG[phase];
7757
7863
  const isActive = activePhase === phase;
7758
7864
  const completedMs = completed?.[phase];
@@ -7764,7 +7870,7 @@ function TempoBar({
7764
7870
  style: {
7765
7871
  flex: config.flex,
7766
7872
  backgroundColor: alpha("#ffffff", 0.06),
7767
- borderRadius: 4,
7873
+ borderRadius: s.radius,
7768
7874
  overflow: "hidden"
7769
7875
  },
7770
7876
  children: isActive ? /* @__PURE__ */ jsx(
@@ -7772,9 +7878,21 @@ function TempoBar({
7772
7878
  {
7773
7879
  config,
7774
7880
  phaseElapsedMs,
7775
- targetMs
7881
+ targetMs,
7882
+ radius: s.radius,
7883
+ font: s.segFont,
7884
+ display,
7885
+ onToggleDisplay: toggleDisplay
7776
7886
  }
7777
- ) : completedMs != null ? /* @__PURE__ */ jsx(CompletedSegment, { config, completedMs, targetMs }) : null
7887
+ ) : completedMs != null ? /* @__PURE__ */ jsx(
7888
+ CompletedSegment,
7889
+ {
7890
+ config,
7891
+ completedMs,
7892
+ targetMs,
7893
+ font: s.segFont
7894
+ }
7895
+ ) : null
7778
7896
  },
7779
7897
  phase
7780
7898
  );
@@ -7784,49 +7902,65 @@ function TempoBar({
7784
7902
  function ActiveSegment({
7785
7903
  config,
7786
7904
  phaseElapsedMs,
7787
- targetMs
7905
+ targetMs,
7906
+ radius,
7907
+ font,
7908
+ display,
7909
+ onToggleDisplay
7788
7910
  }) {
7789
7911
  const pacing = getTempoPacingState(phaseElapsedMs, targetMs);
7790
7912
  const barColor = pacing === "behind" ? t2["status-error"] : config.color;
7791
7913
  const fillPct = getTempoFillPct(phaseElapsedMs, targetMs);
7792
- const label = targetMs ? `${formatDuration2(phaseElapsedMs)} / ${formatDuration2(targetMs)}` : formatDuration2(phaseElapsedMs);
7793
- return /* @__PURE__ */ jsxs(View, { style: { height: "100%", position: "relative" }, testID: `tempo-segment-active-${config.label}`, children: [
7794
- /* @__PURE__ */ jsx(
7795
- View,
7796
- {
7797
- style: {
7798
- position: "absolute",
7799
- left: 0,
7800
- top: 0,
7801
- bottom: 0,
7802
- width: `${fillPct}%`,
7803
- backgroundColor: alpha(barColor, 0.3),
7804
- borderRadius: 4
7805
- }
7806
- }
7807
- ),
7808
- /* @__PURE__ */ jsx(
7809
- View,
7810
- {
7811
- style: {
7812
- height: "100%",
7813
- flexDirection: "row",
7814
- alignItems: "center",
7815
- justifyContent: "center"
7816
- },
7817
- children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 12, fontWeight: "700", color: barColor }, children: label })
7818
- }
7819
- )
7820
- ] });
7914
+ const label = display === "delta" && targetMs != null ? formatTempoDelta(targetMs - phaseElapsedMs) : targetMs != null ? `${formatDuration2(phaseElapsedMs)} / ${formatDuration2(targetMs)}` : formatDuration2(phaseElapsedMs);
7915
+ return /* @__PURE__ */ jsxs(
7916
+ Pressable,
7917
+ {
7918
+ onPress: onToggleDisplay,
7919
+ style: { height: "100%", position: "relative" },
7920
+ accessibilityRole: "button",
7921
+ accessibilityLabel: `Tempo readout: ${display === "delta" ? "delta" : "time"} \u2014 tap to switch`,
7922
+ testID: `tempo-segment-active-${config.label}`,
7923
+ children: [
7924
+ /* @__PURE__ */ jsx(
7925
+ View,
7926
+ {
7927
+ style: {
7928
+ position: "absolute",
7929
+ left: 0,
7930
+ top: 0,
7931
+ bottom: 0,
7932
+ width: `${fillPct}%`,
7933
+ backgroundColor: alpha(barColor, 0.3),
7934
+ borderRadius: radius
7935
+ }
7936
+ }
7937
+ ),
7938
+ /* @__PURE__ */ jsx(
7939
+ View,
7940
+ {
7941
+ style: {
7942
+ height: "100%",
7943
+ flexDirection: "row",
7944
+ alignItems: "center",
7945
+ justifyContent: "center"
7946
+ },
7947
+ children: /* @__PURE__ */ jsx(Text, { style: { fontSize: font, fontWeight: "700", color: barColor }, children: label })
7948
+ }
7949
+ )
7950
+ ]
7951
+ }
7952
+ );
7821
7953
  }
7822
7954
  function CompletedSegment({
7823
7955
  config,
7824
7956
  completedMs,
7825
- targetMs
7957
+ targetMs,
7958
+ font
7826
7959
  }) {
7827
7960
  const pacing = getTempoPacingState(completedMs, targetMs);
7828
7961
  const hitTarget = pacing !== "behind";
7829
7962
  const indicator = targetMs && targetMs >= TEMPO_PACING.minPhaseDurationMs ? hitTarget ? " \u2713" : " \u2717" : "";
7963
+ const cueColor = hitTarget ? config.color : t2["status-error"];
7830
7964
  return /* @__PURE__ */ jsx(
7831
7965
  View,
7832
7966
  {
@@ -7835,13 +7969,23 @@ function CompletedSegment({
7835
7969
  flexDirection: "row",
7836
7970
  alignItems: "center",
7837
7971
  justifyContent: "center",
7838
- backgroundColor: alpha(config.color, 0.15)
7972
+ backgroundColor: alpha(cueColor, 0.15)
7839
7973
  },
7840
7974
  testID: `tempo-segment-completed-${config.label}`,
7841
- children: /* @__PURE__ */ jsxs(Text, { style: { fontSize: 12, fontWeight: "500", color: alpha(config.color, 0.7) }, children: [
7842
- formatDuration2(completedMs),
7843
- indicator
7844
- ] })
7975
+ children: /* @__PURE__ */ jsxs(
7976
+ Text,
7977
+ {
7978
+ style: {
7979
+ fontSize: font,
7980
+ fontWeight: "500",
7981
+ color: hitTarget ? alpha(config.color, 0.7) : cueColor
7982
+ },
7983
+ children: [
7984
+ formatDuration2(completedMs),
7985
+ indicator
7986
+ ]
7987
+ }
7988
+ )
7845
7989
  }
7846
7990
  );
7847
7991
  }
@@ -8931,6 +9075,14 @@ var EXPANDED_ENCODED_PCT = 45;
8931
9075
  var EXPANDED_HEIGHT = 60;
8932
9076
  var FIXED_MAX_VELOCITY = 1.15;
8933
9077
  var BARE_STUB_HEIGHT = 3;
9078
+ var HERO_HEIGHT = 220;
9079
+ var HERO_LABEL_HEADROOM = 20;
9080
+ var HERO_BAR_GAP = 8;
9081
+ var HERO_BAR_MAX_WIDTH = 52;
9082
+ var HERO_BAR_RADIUS = 5;
9083
+ var HERO_MIN_BAR_HEIGHT = 4;
9084
+ var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
9085
+ var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
8934
9086
  function scaleDenominator(scale, maxVelocity) {
8935
9087
  return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
8936
9088
  }
@@ -8958,6 +9110,183 @@ function usePrefersReducedMotion() {
8958
9110
  var ANIMATION_DURATION = 400;
8959
9111
  var ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1);
8960
9112
  var POP_EASING = Easing.bezier(0.34, 1.56, 0.64, 1);
9113
+ function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
9114
+ const prefersReducedMotion = usePrefersReducedMotion();
9115
+ const [liveScale] = useState(() => new Animated.Value(1));
9116
+ useEffect(() => {
9117
+ if (!active || liveRepIndex == null || liveVelocity == null) return;
9118
+ if (prefersReducedMotion) {
9119
+ liveScale.setValue(1);
9120
+ return;
9121
+ }
9122
+ if (isNewPeak) {
9123
+ liveScale.setValue(1);
9124
+ Animated.sequence([
9125
+ Animated.timing(liveScale, {
9126
+ toValue: 1.25,
9127
+ duration: 150,
9128
+ easing: Easing.out(Easing.quad),
9129
+ useNativeDriver: true
9130
+ }),
9131
+ Animated.spring(liveScale, {
9132
+ toValue: 1,
9133
+ friction: 3,
9134
+ tension: 140,
9135
+ useNativeDriver: true
9136
+ })
9137
+ ]).start();
9138
+ } else {
9139
+ liveScale.setValue(0.8);
9140
+ Animated.timing(liveScale, {
9141
+ toValue: 1,
9142
+ duration: 300,
9143
+ easing: POP_EASING,
9144
+ useNativeDriver: true
9145
+ }).start();
9146
+ }
9147
+ }, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
9148
+ return liveScale;
9149
+ }
9150
+ function HeroVelocityChart({
9151
+ doneVelocities,
9152
+ barColorFor,
9153
+ scaleDenom,
9154
+ referenceVelocity,
9155
+ liveRepIndex,
9156
+ isNewPeak,
9157
+ targetReps,
9158
+ height,
9159
+ className,
9160
+ viewProps
9161
+ }) {
9162
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
9163
+ const liveScale = useLiveRepPop(true, liveRepIndex, liveVelocity, isNewPeak);
9164
+ const plotHeight = Math.max(0, height - HERO_LABEL_HEADROOM);
9165
+ const barHeight = (velocity) => scaleDenom > 0 ? Math.max(HERO_MIN_BAR_HEIGHT, Math.min(1, velocity / scaleDenom) * plotHeight) : HERO_MIN_BAR_HEIGHT;
9166
+ const pendingCount = Math.max(0, (targetReps ?? 0) - doneVelocities.length);
9167
+ const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
9168
+ const repCount = doneVelocities.length;
9169
+ const total = Math.max(repCount, targetReps ?? repCount);
9170
+ const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
9171
+ const { style: externalStyle, ...restProps } = viewProps;
9172
+ return /* @__PURE__ */ jsx(
9173
+ View,
9174
+ {
9175
+ className,
9176
+ style: [{ height }, externalStyle],
9177
+ accessibilityRole: "image",
9178
+ accessibilityLabel: label,
9179
+ testID: "velocity-strip-hero",
9180
+ ...restProps,
9181
+ children: /* @__PURE__ */ jsxs(
9182
+ View,
9183
+ {
9184
+ style: {
9185
+ flex: 1,
9186
+ flexDirection: "row",
9187
+ alignItems: "flex-end",
9188
+ gap: HERO_BAR_GAP,
9189
+ position: "relative",
9190
+ borderBottomWidth: 2,
9191
+ borderBottomColor: HERO_PENDING_COLOR
9192
+ },
9193
+ children: [
9194
+ referencePx > 0 && /* @__PURE__ */ jsx(
9195
+ View,
9196
+ {
9197
+ accessibilityElementsHidden: true,
9198
+ style: {
9199
+ position: "absolute",
9200
+ left: 0,
9201
+ right: 0,
9202
+ bottom: referencePx,
9203
+ borderTopWidth: 1,
9204
+ borderStyle: "dashed",
9205
+ borderColor: HERO_REFERENCE_COLOR,
9206
+ pointerEvents: "none"
9207
+ },
9208
+ testID: "velocity-hero-reference"
9209
+ }
9210
+ ),
9211
+ doneVelocities.map((velocity, i) => {
9212
+ const isLive = liveRepIndex === i;
9213
+ return /* @__PURE__ */ jsxs(
9214
+ View,
9215
+ {
9216
+ accessibilityElementsHidden: true,
9217
+ style: {
9218
+ flex: 1,
9219
+ maxWidth: HERO_BAR_MAX_WIDTH,
9220
+ height: "100%",
9221
+ alignItems: "center",
9222
+ justifyContent: "flex-end"
9223
+ },
9224
+ children: [
9225
+ /* @__PURE__ */ jsx(
9226
+ Text,
9227
+ {
9228
+ className: "text-text-primary",
9229
+ style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
9230
+ testID: `velocity-label-${i}`,
9231
+ children: formatVelocity(velocity)
9232
+ }
9233
+ ),
9234
+ /* @__PURE__ */ jsx(
9235
+ Animated.View,
9236
+ {
9237
+ style: [
9238
+ {
9239
+ width: "100%",
9240
+ height: barHeight(velocity),
9241
+ borderTopLeftRadius: HERO_BAR_RADIUS,
9242
+ borderTopRightRadius: HERO_BAR_RADIUS,
9243
+ backgroundColor: barColorFor(velocity)
9244
+ },
9245
+ isLive ? { transform: [{ scale: liveScale }] } : null
9246
+ ],
9247
+ testID: `velocity-bar-${i}`
9248
+ }
9249
+ )
9250
+ ]
9251
+ },
9252
+ i
9253
+ );
9254
+ }),
9255
+ Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
9256
+ View,
9257
+ {
9258
+ accessibilityElementsHidden: true,
9259
+ style: {
9260
+ flex: 1,
9261
+ maxWidth: HERO_BAR_MAX_WIDTH,
9262
+ height: "100%",
9263
+ alignItems: "center",
9264
+ justifyContent: "flex-end"
9265
+ },
9266
+ children: /* @__PURE__ */ jsx(
9267
+ View,
9268
+ {
9269
+ style: {
9270
+ width: "100%",
9271
+ height: HERO_MIN_BAR_HEIGHT * 3,
9272
+ borderWidth: 1,
9273
+ borderStyle: "dashed",
9274
+ borderColor: HERO_PENDING_COLOR,
9275
+ borderTopLeftRadius: HERO_BAR_RADIUS,
9276
+ borderTopRightRadius: HERO_BAR_RADIUS
9277
+ },
9278
+ testID: "velocity-slot-todo"
9279
+ }
9280
+ )
9281
+ },
9282
+ `pending-${i}`
9283
+ ))
9284
+ ]
9285
+ }
9286
+ )
9287
+ }
9288
+ );
9289
+ }
8961
9290
  function VelocityStrip({
8962
9291
  velocities,
8963
9292
  set,
@@ -8968,6 +9297,7 @@ function VelocityStrip({
8968
9297
  onRepPress,
8969
9298
  variant = "expanded",
8970
9299
  height = EXPANDED_HEIGHT,
9300
+ targetReps,
8971
9301
  scale = "peak",
8972
9302
  showNumbers = true,
8973
9303
  showInfo = true,
@@ -8993,45 +9323,17 @@ function VelocityStrip({
8993
9323
  return SET_STRIP_VARIABLE_COLOR;
8994
9324
  };
8995
9325
  const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
8996
- const prefersReducedMotion = usePrefersReducedMotion();
8997
9326
  const [heightAnim] = useState(() => new Animated.Value(expanded ? height : 3));
8998
9327
  const [labelOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
8999
9328
  const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
9000
- const [liveScale] = useState(() => new Animated.Value(1));
9001
9329
  const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
9002
9330
  const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
9003
- useEffect(() => {
9004
- if (variant !== "expanded" || !framed || liveRepIndex == null || liveVelocity == null) return;
9005
- if (prefersReducedMotion) {
9006
- liveScale.setValue(1);
9007
- return;
9008
- }
9009
- if (isNewPeak) {
9010
- liveScale.setValue(1);
9011
- Animated.sequence([
9012
- Animated.timing(liveScale, {
9013
- toValue: 1.25,
9014
- duration: 150,
9015
- easing: Easing.out(Easing.quad),
9016
- useNativeDriver: true
9017
- }),
9018
- Animated.spring(liveScale, {
9019
- toValue: 1,
9020
- friction: 3,
9021
- tension: 140,
9022
- useNativeDriver: true
9023
- })
9024
- ]).start();
9025
- } else {
9026
- liveScale.setValue(0.8);
9027
- Animated.timing(liveScale, {
9028
- toValue: 1,
9029
- duration: 300,
9030
- easing: POP_EASING,
9031
- useNativeDriver: true
9032
- }).start();
9033
- }
9034
- }, [variant, framed, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
9331
+ const liveScale = useLiveRepPop(
9332
+ variant === "expanded" && framed,
9333
+ liveRepIndex,
9334
+ liveVelocity,
9335
+ isNewPeak
9336
+ );
9035
9337
  useEffect(() => {
9036
9338
  if (variant !== "expanded" || !framed) return;
9037
9339
  Animated.parallel([
@@ -9058,6 +9360,24 @@ function VelocityStrip({
9058
9360
  if (set == null && velocities == null) return null;
9059
9361
  const repCount = doneVelocities.length;
9060
9362
  const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
9363
+ if (variant === "hero") {
9364
+ const heroHeight = height === EXPANDED_HEIGHT ? HERO_HEIGHT : height;
9365
+ return /* @__PURE__ */ jsx(
9366
+ HeroVelocityChart,
9367
+ {
9368
+ doneVelocities,
9369
+ barColorFor,
9370
+ scaleDenom,
9371
+ referenceVelocity: maxVelocity,
9372
+ liveRepIndex,
9373
+ isNewPeak,
9374
+ targetReps,
9375
+ height: heroHeight,
9376
+ className,
9377
+ viewProps: props
9378
+ }
9379
+ );
9380
+ }
9061
9381
  if (variant === "mini") {
9062
9382
  const { style: externalStyle, ...restProps } = props;
9063
9383
  return /* @__PURE__ */ jsx(
@@ -9822,6 +10142,99 @@ function InputBar({
9822
10142
  );
9823
10143
  }
9824
10144
  var BRAND_PRIMARY4 = getSemanticColors("dark")["brand-primary"];
10145
+ var RING_DEFAULT_SIZE = 180;
10146
+ function RestActions({ onAddTime, onSkip }) {
10147
+ return /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", gap: 8 }, children: [
10148
+ /* @__PURE__ */ jsx(
10149
+ Pressable,
10150
+ {
10151
+ onPress: onAddTime,
10152
+ style: {
10153
+ backgroundColor: "rgba(255,255,255,0.06)",
10154
+ paddingVertical: 8,
10155
+ paddingHorizontal: 20,
10156
+ borderRadius: 8
10157
+ },
10158
+ accessibilityRole: "button",
10159
+ accessibilityLabel: "Add 30 seconds",
10160
+ testID: "rest-timer-add-time",
10161
+ children: /* @__PURE__ */ jsx(
10162
+ Text,
10163
+ {
10164
+ className: "text-text-secondary",
10165
+ style: { fontSize: 11, fontFamily: "Inter, sans-serif", fontWeight: "600" },
10166
+ children: "+30s"
10167
+ }
10168
+ )
10169
+ }
10170
+ ),
10171
+ /* @__PURE__ */ jsx(
10172
+ Pressable,
10173
+ {
10174
+ onPress: onSkip,
10175
+ style: {
10176
+ backgroundColor: "rgba(255,121,0,0.12)",
10177
+ paddingVertical: 8,
10178
+ paddingHorizontal: 20,
10179
+ borderRadius: 8
10180
+ },
10181
+ accessibilityRole: "button",
10182
+ accessibilityLabel: "Skip rest",
10183
+ testID: "rest-timer-skip",
10184
+ children: /* @__PURE__ */ jsx(
10185
+ Text,
10186
+ {
10187
+ style: {
10188
+ fontSize: 11,
10189
+ fontFamily: "Inter, sans-serif",
10190
+ fontWeight: "600",
10191
+ color: BRAND_PRIMARY4
10192
+ },
10193
+ children: "Skip"
10194
+ }
10195
+ )
10196
+ }
10197
+ )
10198
+ ] });
10199
+ }
10200
+ function RestTimerRing({
10201
+ totalSeconds,
10202
+ elapsedMs,
10203
+ remainingSec,
10204
+ done,
10205
+ size,
10206
+ nextSetInfo,
10207
+ displayOnly,
10208
+ onSkip,
10209
+ onAddTime
10210
+ }) {
10211
+ const a11yLabel = done ? "Rest complete, next set ready" : `Rest timer, ${Math.max(0, remainingSec)} seconds remaining`;
10212
+ return /* @__PURE__ */ jsxs(View, { style: { alignItems: "center", gap: 16 }, testID: "rest-timer", children: [
10213
+ /* @__PURE__ */ jsx(
10214
+ CircularTimer,
10215
+ {
10216
+ durationMs: totalSeconds * 1e3,
10217
+ elapsedMs,
10218
+ mode: "down",
10219
+ size,
10220
+ doneLabel: "GO",
10221
+ doneColor: "success",
10222
+ accessibilityLabel: a11yLabel,
10223
+ controls: !displayOnly ? /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip }) : void 0,
10224
+ testID: "rest-timer-ring"
10225
+ }
10226
+ ),
10227
+ nextSetInfo != null && /* @__PURE__ */ jsx(
10228
+ Text,
10229
+ {
10230
+ className: "text-text-tertiary",
10231
+ style: { fontSize: 13, fontFamily: "Inter, sans-serif" },
10232
+ testID: "rest-timer-next-set",
10233
+ children: nextSetInfo
10234
+ }
10235
+ )
10236
+ ] });
10237
+ }
9825
10238
  function RestTimer({
9826
10239
  totalSeconds,
9827
10240
  elapsedMs,
@@ -9829,12 +10242,15 @@ function RestTimer({
9829
10242
  onAddTime,
9830
10243
  nextSetInfo,
9831
10244
  visible,
9832
- displayOnly = false
10245
+ displayOnly = false,
10246
+ variant = "bar",
10247
+ size = RING_DEFAULT_SIZE
9833
10248
  }) {
9834
10249
  const {
9835
10250
  remainingMs,
9836
10251
  progress,
9837
- label: timeDisplay
10252
+ label: timeDisplay,
10253
+ done
9838
10254
  } = useTimer({
9839
10255
  mode: "down",
9840
10256
  durationMs: totalSeconds * 1e3,
@@ -9843,6 +10259,22 @@ function RestTimer({
9843
10259
  const remainingSec = Math.ceil(remainingMs / 1e3);
9844
10260
  const progressPct = progress * 100;
9845
10261
  if (!visible) return null;
10262
+ if (variant === "ring") {
10263
+ return /* @__PURE__ */ jsx(
10264
+ RestTimerRing,
10265
+ {
10266
+ totalSeconds,
10267
+ elapsedMs,
10268
+ remainingSec,
10269
+ done,
10270
+ size,
10271
+ nextSetInfo,
10272
+ displayOnly,
10273
+ onSkip,
10274
+ onAddTime
10275
+ }
10276
+ );
10277
+ }
9846
10278
  return /* @__PURE__ */ jsxs(
9847
10279
  View,
9848
10280
  {
@@ -9940,62 +10372,7 @@ function RestTimer({
9940
10372
  )
9941
10373
  }
9942
10374
  ),
9943
- !displayOnly && /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", gap: 8 }, children: [
9944
- /* @__PURE__ */ jsx(
9945
- Pressable,
9946
- {
9947
- onPress: onAddTime,
9948
- style: {
9949
- backgroundColor: "rgba(255,255,255,0.06)",
9950
- paddingVertical: 8,
9951
- paddingHorizontal: 20,
9952
- borderRadius: 8
9953
- },
9954
- accessibilityRole: "button",
9955
- accessibilityLabel: "Add 30 seconds",
9956
- testID: "rest-timer-add-time",
9957
- children: /* @__PURE__ */ jsx(
9958
- Text,
9959
- {
9960
- className: "text-text-secondary",
9961
- style: {
9962
- fontSize: 11,
9963
- fontFamily: "Inter, sans-serif",
9964
- fontWeight: "600"
9965
- },
9966
- children: "+30s"
9967
- }
9968
- )
9969
- }
9970
- ),
9971
- /* @__PURE__ */ jsx(
9972
- Pressable,
9973
- {
9974
- onPress: onSkip,
9975
- style: {
9976
- backgroundColor: "rgba(255,121,0,0.12)",
9977
- paddingVertical: 8,
9978
- paddingHorizontal: 20,
9979
- borderRadius: 8
9980
- },
9981
- accessibilityRole: "button",
9982
- accessibilityLabel: "Skip rest",
9983
- testID: "rest-timer-skip",
9984
- children: /* @__PURE__ */ jsx(
9985
- Text,
9986
- {
9987
- style: {
9988
- fontSize: 11,
9989
- fontFamily: "Inter, sans-serif",
9990
- fontWeight: "600",
9991
- color: BRAND_PRIMARY4
9992
- },
9993
- children: "Skip"
9994
- }
9995
- )
9996
- }
9997
- )
9998
- ] })
10375
+ !displayOnly && /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip })
9999
10376
  ]
10000
10377
  }
10001
10378
  );
@@ -12985,9 +13362,36 @@ function glowShadow(color) {
12985
13362
  var DEFAULT_TRACK_COLOR = primitiveColors.charcoal[200];
12986
13363
  var DEFAULT_MARKER_COLOR2 = primitiveColors.white;
12987
13364
  var TICK_COLOR = t15["text-tertiary"];
12988
- var DEFAULT_TRACK_HEIGHT = 14;
12989
- var DEFAULT_NEEDLE_OVERHANG = 6;
12990
- var NEEDLE_WIDTH = 4;
13365
+ var ZONE_SIZES = {
13366
+ default: {
13367
+ trackHeight: 14,
13368
+ needleOverhang: 6,
13369
+ needleWidth: 4,
13370
+ tickLineNormal: 1.5,
13371
+ tickLineEmphasized: 2,
13372
+ tickLineHeightPad: 4,
13373
+ tickLineTopPad: 2,
13374
+ labelRowHeight: 16,
13375
+ labelMarginTop: 5,
13376
+ tickFont: 9,
13377
+ tickLetterSpacing: 0.5,
13378
+ tickCellWidth: 28
13379
+ },
13380
+ wall: {
13381
+ trackHeight: 24,
13382
+ needleOverhang: 9,
13383
+ needleWidth: 7,
13384
+ tickLineNormal: 2.5,
13385
+ tickLineEmphasized: 3.5,
13386
+ tickLineHeightPad: 6,
13387
+ tickLineTopPad: 3,
13388
+ labelRowHeight: 24,
13389
+ labelMarginTop: 8,
13390
+ tickFont: 14,
13391
+ tickLetterSpacing: 0.5,
13392
+ tickCellWidth: 48
13393
+ }
13394
+ };
12991
13395
  function fraction(value, min, max) {
12992
13396
  if (max <= min) return 0;
12993
13397
  return Math.max(0, Math.min(1, (value - min) / (max - min)));
@@ -13002,14 +13406,18 @@ function ZoneTrack({
13002
13406
  marker = null,
13003
13407
  ticks,
13004
13408
  band = null,
13005
- trackHeight = DEFAULT_TRACK_HEIGHT,
13006
- needleOverhang = DEFAULT_NEEDLE_OVERHANG,
13409
+ size = "default",
13410
+ trackHeight: trackHeightProp,
13411
+ needleOverhang: needleOverhangProp,
13007
13412
  trackColor = DEFAULT_TRACK_COLOR,
13008
13413
  className,
13009
13414
  style,
13010
13415
  accessibilityLabel: accessibilityLabel2,
13011
13416
  ...props
13012
13417
  }) {
13418
+ const s = ZONE_SIZES[size];
13419
+ const trackHeight = trackHeightProp ?? s.trackHeight;
13420
+ const needleOverhang = needleOverhangProp ?? s.needleOverhang;
13013
13421
  const isNeedle = marker?.type === "needle";
13014
13422
  const markerHeight = isNeedle ? trackHeight + needleOverhang * 2 : trackHeight;
13015
13423
  const markerFrac = marker != null ? fraction(marker.value, min, max) : 0;
@@ -13119,18 +13527,18 @@ function ZoneTrack({
13119
13527
  position: "absolute",
13120
13528
  top: 0,
13121
13529
  left: pct(markerFrac),
13122
- width: NEEDLE_WIDTH,
13530
+ width: s.needleWidth,
13123
13531
  height: markerHeight,
13124
- borderRadius: NEEDLE_WIDTH / 2,
13532
+ borderRadius: s.needleWidth / 2,
13125
13533
  backgroundColor: marker.color ?? DEFAULT_MARKER_COLOR2,
13126
- transform: [{ translateX: -NEEDLE_WIDTH / 2 }]
13534
+ transform: [{ translateX: -s.needleWidth / 2 }]
13127
13535
  }
13128
13536
  }
13129
13537
  ),
13130
13538
  ticks?.map((tick, i) => {
13131
13539
  const emphasized = tick.emphasized === true;
13132
13540
  const lineColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13133
- const lineWidth = emphasized ? 2 : 1.5;
13541
+ const lineWidth = emphasized ? s.tickLineEmphasized : s.tickLineNormal;
13134
13542
  return /* @__PURE__ */ jsx(
13135
13543
  View,
13136
13544
  {
@@ -13138,10 +13546,10 @@ function ZoneTrack({
13138
13546
  accessibilityElementsHidden: true,
13139
13547
  style: {
13140
13548
  position: "absolute",
13141
- top: (markerHeight - trackHeight) / 2 - 2,
13549
+ top: (markerHeight - trackHeight) / 2 - s.tickLineTopPad,
13142
13550
  left: pct(fraction(tick.value, min, max)),
13143
13551
  width: lineWidth,
13144
- height: trackHeight + 4,
13552
+ height: trackHeight + s.tickLineHeightPad,
13145
13553
  borderRadius: lineWidth / 2,
13146
13554
  backgroundColor: lineColor,
13147
13555
  transform: [{ translateX: -lineWidth / 2 }],
@@ -13152,40 +13560,46 @@ function ZoneTrack({
13152
13560
  );
13153
13561
  })
13154
13562
  ] }),
13155
- ticks != null && ticks.length > 0 && /* @__PURE__ */ jsx(View, { style: { position: "relative", height: 16, marginTop: 5 }, children: ticks.map((tick, i) => {
13156
- if (tick.label == null) return null;
13157
- const emphasized = tick.emphasized === true;
13158
- const labelColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13159
- const labelNode = /* @__PURE__ */ jsx(
13160
- Text,
13161
- {
13162
- testID: "zone-track-tick-label",
13163
- style: {
13164
- fontSize: 9,
13165
- letterSpacing: 0.5,
13166
- color: labelColor,
13167
- fontFamily: "monospace",
13168
- fontWeight: emphasized ? "700" : "400"
13169
- },
13170
- children: tick.label
13171
- }
13172
- );
13173
- return /* @__PURE__ */ jsx(
13174
- View,
13175
- {
13176
- testID: "zone-track-tick",
13177
- style: {
13178
- position: "absolute",
13179
- left: pct(fraction(tick.value, min, max)),
13180
- transform: [{ translateX: -14 }],
13181
- width: 28,
13182
- alignItems: "center"
13183
- },
13184
- children: tick.tooltip != null ? /* @__PURE__ */ jsx(Tooltip, { label: tick.tooltip, placement: "top", children: labelNode }) : labelNode
13185
- },
13186
- `label-${i}`
13187
- );
13188
- }) })
13563
+ ticks != null && ticks.length > 0 && /* @__PURE__ */ jsx(
13564
+ View,
13565
+ {
13566
+ style: { position: "relative", height: s.labelRowHeight, marginTop: s.labelMarginTop },
13567
+ children: ticks.map((tick, i) => {
13568
+ if (tick.label == null) return null;
13569
+ const emphasized = tick.emphasized === true;
13570
+ const labelColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13571
+ const labelNode = /* @__PURE__ */ jsx(
13572
+ Text,
13573
+ {
13574
+ testID: "zone-track-tick-label",
13575
+ style: {
13576
+ fontSize: s.tickFont,
13577
+ letterSpacing: s.tickLetterSpacing,
13578
+ color: labelColor,
13579
+ fontFamily: "monospace",
13580
+ fontWeight: emphasized ? "700" : "400"
13581
+ },
13582
+ children: tick.label
13583
+ }
13584
+ );
13585
+ return /* @__PURE__ */ jsx(
13586
+ View,
13587
+ {
13588
+ testID: "zone-track-tick",
13589
+ style: {
13590
+ position: "absolute",
13591
+ left: pct(fraction(tick.value, min, max)),
13592
+ transform: [{ translateX: -s.tickCellWidth / 2 }],
13593
+ width: s.tickCellWidth,
13594
+ alignItems: "center"
13595
+ },
13596
+ children: tick.tooltip != null ? /* @__PURE__ */ jsx(Tooltip, { label: tick.tooltip, placement: "top", children: labelNode }) : labelNode
13597
+ },
13598
+ `label-${i}`
13599
+ );
13600
+ })
13601
+ }
13602
+ )
13189
13603
  ]
13190
13604
  }
13191
13605
  );
@@ -13203,7 +13617,8 @@ function FatigueMeter({
13203
13617
  thresholds = DEFAULT_THRESHOLDS,
13204
13618
  zoneColors = DEFAULT_ZONE_COLORS,
13205
13619
  labels = DEFAULT_LABELS,
13206
- trackHeight = 14,
13620
+ size = "default",
13621
+ trackHeight,
13207
13622
  needleColor,
13208
13623
  className,
13209
13624
  ...props
@@ -13224,6 +13639,7 @@ function FatigueMeter({
13224
13639
  zones,
13225
13640
  min: 0,
13226
13641
  max,
13642
+ size,
13227
13643
  trackHeight,
13228
13644
  ticks,
13229
13645
  marker: { type: "needle", value, color: needleColor },
@@ -14250,6 +14666,6 @@ function composeValidators(...validators) {
14250
14666
  };
14251
14667
  }
14252
14668
 
14253
- export { Accordion, AccordionButton, AccordionItem, AccordionPanel, ActivityIcon, Alert, AlertDescription, AlertTitle, AlertTriangleIcon, Autocomplete, Avatar, AvatarBadge, AvatarGroup, AwardIcon, Badge, BadgeText, BaseBadge, BluetoothIcon, BrandLockup, BreadcrumbItem, Breadcrumbs, Button, ButtonIcon, ButtonSpinner, ButtonText, CATEGORICAL_CVD_SAFE_MAX, CapacityBandChart, Caption, Card, CardContent, CardDescription, CardFooter, CardHeader, CardSkeleton, CardTitle, Checkbox, CheckboxGroup, Chip, CircleSlashIcon, CircularProgress, Collapse, CollapseButton, CollapseContent, DashboardShell, DataRow, DateTime, DeviationBar, DeviceIndicator, DeviceMenu, DeviceRow, Divider, Drawer, DrawerBody, DrawerFooter, DrawerHeader, DumbbellIcon, EmptyState, ExerciseCard, ExerciseCardHeading, ExerciseHeading, ExerciseIndicator, FatigueMeter, FormActions, FormField, FormRow, FormSection, Gauge2 as Gauge, HStack, Heading, HelpTip, HistoryIcon, INDICATOR_PRECEDENCE, IconBox, Indicator, InfoIcon, Input, InputBar, InputGroup, IntensityBar, Label, LabelWithHelp, LayersIcon, Link, ListItem, ListItemContent, ListItemDivider, ListItemIcon, ListItemTrailing, LiveAuraFrame, METRIC_FONT, Menu, MenuDivider, MenuGroup, MenuItem, MenuList, MenuTrigger, MesoCard, MesoProgressBar, MesoStatusCard, Metric, MetricCell, MetricGroup, MetricTiles, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalTitle, MuscleGroupChip, NavItem, Overline, Paragraph, PasswordInput, PersonStandingIcon, Pill, PlaceholderStrip, Popover, PopoverCloseButton, PopoverContent, PopoverTrigger, PrBadge, PrHistoryModal, Progress, ProgressSteps, Radio, RadioGroup, ReadinessCheck, RestTimer, SET_STRIP_VARIABLE_COLOR, SET_STRIP_ZONES, ScaleIcon, Scatter, ScheduleTiles, Section, SectionContent, SectionHeader, SegmentedBar, SegmentedProgressBar, Select, SessionHeader, SessionRail, SessionStatePill, SetBar, SetRow, SetStrip, SetTableHeader, SetsRepsLoad, SideNav, Sidebar, SidebarContent, SidebarDivider, SidebarFooter, SidebarHeader, SidebarItem, SidebarSection, Skeleton, SkeletonCard, SkeletonCircle, SkeletonListItem, SkeletonText, Sparkline, Spinner, Stack, StarIcon, StatusDot, StatusPill2 as StatusPill, StepContent, StepIndicator, StepLabel, Stepper, Step as StepperStep, StrengthTrendChart, SupersetWrapper, Surface, SvgIcon, Switch, TEMPO_PACING, Tab, TabList, TabPanel, TabPanels, Table, TableBody, TableCell, TableHeader, TableHeaderCell, TablePagination, TableRow, Tabs, TempoBar, TempoDisplay, Tile, TimerReadout, Toast, ToastProvider, ToolbarButton, ToolbarButtonGroup, Tooltip, TopBar, Treemap, TrendingDownIcon, Typography, VStack, VelocityStrip, VoltrasMark, VolumeLandmarkBar, WeekRow, WeightBadge, WorkoutCard, WorkoutPill, ZoneTrack, alpha, applyThemePreset, audiobookPreset, bestTextColor, buttonSizes, calculateMeanVelocity, calculateVelocityLoss, categoricalPalette, cn, componentElevationRanges, composeValidators, createFieldId, createFlatShadow, createPressedHoverShadow, createPressedShadow, createRaisedHoverShadow, createRaisedShadow, darkThemeCSSVars, darken, defaultNavItems, defaultPreset, discreteRainbow, divergingScale, elevationSystem, formatDateTime, formatDuration, formatPrescription, formatSignedPct, formatVelocity, getBaseSurfaceColor, getCategoricalColor, getContrastText, getDiscreteRainbowColor, getElevationConfig, getElevationShadow, getElevationSurface, getFieldAriaProps, getFieldValidationProps, getGlowShadow, getHoverColors, getLuminance, getResultColor, getSemanticColors, getStatusColor, getTempoFillPct, getTempoPacingState, getThemeCSSVars, getValidatedElevation, getVelocityZoneColor, getVelocityZoneName, gluestackConfig, hasMaxLength, hasMinLength, hexToRgb, hsvToRgb, isCssPropertyManifest, isDark, isEmpty, isValidEmail, lightThemeCSSVars, lighten, linearGradient, liveAuraColor, neumorphicShadows, paceTone, paceToneColor, primitiveBorderRadius, primitiveBreakpoints, primitiveColors, primitiveRamps, primitiveShadows, primitiveSpacing, primitiveTypography, primitiveZIndex, resolveColor, resolveIndicator, rgbToHex, rgbToHsv, roundRpe, roundTempo, roundWeight, rpeColor, semanticColorsDark, semanticColorsLight, semanticTypography, sequentialEffort, shadowColors, statusPillColor, surfaceGradient, useTable, useTimer, useToast, useToolbarButton, validateCssPropertyManifest, validationRules, velocityZoneColor };
14669
+ export { Accordion, AccordionButton, AccordionItem, AccordionPanel, ActivityIcon, Alert, AlertDescription, AlertTitle, AlertTriangleIcon, Autocomplete, Avatar, AvatarBadge, AvatarGroup, AwardIcon, Badge, BadgeText, BaseBadge, BluetoothIcon, BrandLockup, BreadcrumbItem, Breadcrumbs, Button, ButtonIcon, ButtonSpinner, ButtonText, CATEGORICAL_CVD_SAFE_MAX, CapacityBandChart, Caption, Card, CardContent, CardDescription, CardFooter, CardHeader, CardSkeleton, CardTitle, Checkbox, CheckboxGroup, Chip, CircleSlashIcon, CircularProgress, CircularTimer, Collapse, CollapseButton, CollapseContent, DashboardShell, DataRow, DateTime, DeviationBar, DeviceIndicator, DeviceMenu, DeviceRow, Divider, Drawer, DrawerBody, DrawerFooter, DrawerHeader, DumbbellIcon, EmptyState, ExerciseCard, ExerciseCardHeading, ExerciseHeading, ExerciseIndicator, FatigueMeter, FormActions, FormField, FormRow, FormSection, Gauge2 as Gauge, HStack, Heading, HelpTip, HistoryIcon, INDICATOR_PRECEDENCE, IconBox, Indicator, InfoIcon, Input, InputBar, InputGroup, IntensityBar, Label, LabelWithHelp, LayersIcon, Link, ListItem, ListItemContent, ListItemDivider, ListItemIcon, ListItemTrailing, LiveAuraFrame, METRIC_FONT, Menu, MenuDivider, MenuGroup, MenuItem, MenuList, MenuTrigger, MesoCard, MesoProgressBar, MesoStatusCard, Metric, MetricCell, MetricGroup, MetricTiles, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalTitle, MuscleGroupChip, NavItem, Overline, Paragraph, PasswordInput, PersonStandingIcon, Pill, PlaceholderStrip, Popover, PopoverCloseButton, PopoverContent, PopoverTrigger, PrBadge, PrHistoryModal, Progress, ProgressSteps, Radio, RadioGroup, ReadinessCheck, RestTimer, SET_STRIP_VARIABLE_COLOR, SET_STRIP_ZONES, ScaleIcon, Scatter, ScheduleTiles, Section, SectionContent, SectionHeader, SegmentedBar, SegmentedProgressBar, Select, SessionHeader, SessionRail, SessionStatePill, SetBar, SetRow, SetStrip, SetTableHeader, SetsRepsLoad, SideNav, Sidebar, SidebarContent, SidebarDivider, SidebarFooter, SidebarHeader, SidebarItem, SidebarSection, Skeleton, SkeletonCard, SkeletonCircle, SkeletonListItem, SkeletonText, Sparkline, Spinner, Stack, StarIcon, StatusDot, StatusPill2 as StatusPill, StepContent, StepIndicator, StepLabel, Stepper, Step as StepperStep, StrengthTrendChart, SupersetWrapper, Surface, SvgIcon, Switch, TEMPO_PACING, Tab, TabList, TabPanel, TabPanels, Table, TableBody, TableCell, TableHeader, TableHeaderCell, TablePagination, TableRow, Tabs, TempoBar, TempoDisplay, Tile, TimerReadout, Toast, ToastProvider, ToolbarButton, ToolbarButtonGroup, Tooltip, TopBar, Treemap, TrendingDownIcon, Typography, VStack, VelocityStrip, VoltrasMark, VolumeLandmarkBar, WeekRow, WeightBadge, WorkoutCard, WorkoutPill, ZoneTrack, alpha, applyThemePreset, audiobookPreset, bestTextColor, buttonSizes, calculateMeanVelocity, calculateVelocityLoss, categoricalPalette, cn, componentElevationRanges, composeValidators, createFieldId, createFlatShadow, createPressedHoverShadow, createPressedShadow, createRaisedHoverShadow, createRaisedShadow, darkThemeCSSVars, darken, defaultNavItems, defaultPreset, discreteRainbow, divergingScale, elevationSystem, formatDateTime, formatDuration, formatPrescription, formatSignedPct, formatVelocity, getBaseSurfaceColor, getCategoricalColor, getContrastText, getDiscreteRainbowColor, getElevationConfig, getElevationShadow, getElevationSurface, getFieldAriaProps, getFieldValidationProps, getGlowShadow, getHoverColors, getLuminance, getResultColor, getSemanticColors, getStatusColor, getTempoFillPct, getTempoPacingState, getThemeCSSVars, getValidatedElevation, getVelocityZoneColor, getVelocityZoneName, gluestackConfig, hasMaxLength, hasMinLength, hexToRgb, hsvToRgb, isCssPropertyManifest, isDark, isEmpty, isValidEmail, lightThemeCSSVars, lighten, linearGradient, liveAuraColor, neumorphicShadows, paceTone, paceToneColor, primitiveBorderRadius, primitiveBreakpoints, primitiveColors, primitiveRamps, primitiveShadows, primitiveSpacing, primitiveTypography, primitiveZIndex, resolveColor, resolveIndicator, rgbToHex, rgbToHsv, roundRpe, roundTempo, roundWeight, rpeColor, semanticColorsDark, semanticColorsLight, semanticTypography, sequentialEffort, shadowColors, statusPillColor, surfaceGradient, useTable, useTimer, useToast, useToolbarButton, validateCssPropertyManifest, validationRules, velocityZoneColor };
14254
14670
  //# sourceMappingURL=index.mjs.map
14255
14671
  //# sourceMappingURL=index.mjs.map