@titan-design/react-ui 0.4.0 → 0.5.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 (67) hide show
  1. package/dist/MesoStatusCard-CQk71hSi.d.mts +77 -0
  2. package/dist/MesoStatusCard-CQk71hSi.d.ts +77 -0
  3. package/dist/bodymap.d.mts +246 -3
  4. package/dist/bodymap.d.ts +246 -3
  5. package/dist/bodymap.mjs +1911 -6
  6. package/dist/bodymap.mjs.map +1 -1
  7. package/dist/index.d.mts +31 -738
  8. package/dist/index.d.ts +31 -738
  9. package/dist/index.js +894 -1692
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +10287 -8203
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/pages-D7Jlssvp.d.ts +791 -0
  14. package/dist/pages-MO0JCySL.d.mts +791 -0
  15. package/dist/pages.d.mts +4 -0
  16. package/dist/pages.d.ts +4 -0
  17. package/dist/pages.js +6646 -0
  18. package/dist/pages.js.map +1 -0
  19. package/dist/pages.mjs +6626 -0
  20. package/dist/pages.mjs.map +1 -0
  21. package/dist/theme/index.d.mts +12 -12
  22. package/dist/theme/index.d.ts +12 -12
  23. package/dist/theme/index.js +30 -30
  24. package/dist/theme/index.js.map +1 -1
  25. package/dist/theme/index.mjs +1880 -2
  26. package/dist/theme/index.mjs.map +1 -1
  27. package/dist/theme/tokens-css.mjs +573 -1
  28. package/dist/theme/tokens-css.mjs.map +1 -1
  29. package/package.json +9 -7
  30. package/src/components/custom/Workout/BaseBadge.stories.tsx +3 -3
  31. package/src/components/custom/Workout/BaseBadge.test.tsx +1 -1
  32. package/src/components/custom/Workout/BaseBadge.tsx +1 -1
  33. package/src/components/custom/Workout/ExerciseCard.tsx +5 -1
  34. package/src/components/custom/Workout/ExerciseDetailPage.tsx +17 -4
  35. package/src/components/custom/Workout/IntensityBar.test.tsx +112 -60
  36. package/src/components/custom/Workout/IntensityBar.tsx +109 -58
  37. package/src/components/custom/Workout/PlaceholderStrip.tsx +5 -9
  38. package/src/components/custom/Workout/PrBadge.tsx +3 -3
  39. package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
  40. package/src/components/custom/Workout/README.md +50 -0
  41. package/src/components/custom/Workout/RestTimer.test.tsx +41 -10
  42. package/src/components/custom/Workout/RestTimer.tsx +48 -43
  43. package/src/components/custom/Workout/SetRow.tsx +10 -1
  44. package/src/components/custom/Workout/TempoDisplay.stories.tsx +69 -2
  45. package/src/components/custom/Workout/TempoDisplay.test.tsx +35 -0
  46. package/src/components/custom/Workout/TempoDisplay.tsx +104 -1
  47. package/src/components/custom/Workout/VelocityStrip.test.tsx +89 -2
  48. package/src/components/custom/Workout/VelocityStrip.tsx +203 -38
  49. package/src/components/custom/Workout/WeekRow.test.tsx +2 -2
  50. package/src/components/custom/Workout/WeightBadge.test.tsx +2 -2
  51. package/src/components/custom/Workout/WeightBadge.tsx +2 -2
  52. package/src/components/custom/Workout/icons.tsx +68 -0
  53. package/src/components/custom/Workout/index.ts +38 -47
  54. package/src/index.ts +3 -2
  55. package/src/pages.ts +61 -0
  56. package/src/test/stories-smoke.test.tsx +50 -0
  57. package/src/theme/barrel.ts +53 -0
  58. package/src/theme/index.ts +8 -43
  59. package/src/theme/native-theming-guard.test.ts +117 -0
  60. package/dist/bodymap-BhG55xHM.d.mts +0 -318
  61. package/dist/bodymap-BhG55xHM.d.ts +0 -318
  62. package/dist/chunk-2SISKTWM.mjs +0 -1027
  63. package/dist/chunk-2SISKTWM.mjs.map +0 -1
  64. package/dist/chunk-7XPB4NAO.mjs +0 -1013
  65. package/dist/chunk-7XPB4NAO.mjs.map +0 -1
  66. package/dist/chunk-SNVKL5WZ.mjs +0 -883
  67. package/dist/chunk-SNVKL5WZ.mjs.map +0 -1
@@ -1,1027 +0,0 @@
1
- import { resolveColor, getValidatedElevation, getBaseSurfaceColor, getElevationSurface, getElevationShadow, jsxs, Fragment, jsx } from './chunk-SNVKL5WZ.mjs';
2
- import { clsx } from 'clsx';
3
- import { twMerge } from 'tailwind-merge';
4
- import { useState, useMemo, useSyncExternalStore, Fragment as Fragment$1 } from 'react';
5
- import { View, Pressable, Text } from 'react-native';
6
-
7
- function cn(...inputs) {
8
- return twMerge(clsx(inputs));
9
- }
10
- function getTheme() {
11
- if (typeof document === "undefined") return "dark";
12
- return document.documentElement.classList.contains("light") ? "light" : "dark";
13
- }
14
- var listeners = [];
15
- function subscribe(listener) {
16
- listeners = [...listeners, listener];
17
- if (typeof document !== "undefined" && listeners.length === 1) {
18
- const observer = new MutationObserver((mutations) => {
19
- for (const mutation of mutations) {
20
- if (mutation.type === "attributes" && mutation.attributeName === "class") {
21
- listeners.forEach((l) => l());
22
- }
23
- }
24
- });
25
- observer.observe(document.documentElement, {
26
- attributes: true,
27
- attributeFilter: ["class"]
28
- });
29
- subscribe._observer = observer;
30
- }
31
- return () => {
32
- listeners = listeners.filter((l) => l !== listener);
33
- if (listeners.length === 0 && subscribe._observer) {
34
- subscribe._observer.disconnect();
35
- subscribe._observer = null;
36
- }
37
- };
38
- }
39
- function getSnapshot() {
40
- return getTheme();
41
- }
42
- function getServerSnapshot() {
43
- return "dark";
44
- }
45
- function useTheme() {
46
- return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
47
- }
48
-
49
- // src/components/ui/card/Card.tsx
50
- var variantStyles = {
51
- elevated: "",
52
- // Will be set dynamically via elevation system
53
- outline: "border-2 border-border-strong",
54
- // Thicker border with stronger contrast
55
- filled: "",
56
- // Will be set dynamically via elevation system
57
- accent: "border border-border",
58
- subtle: "border border-border-subtle"
59
- };
60
- function Card({
61
- variant = "elevated",
62
- elevation = 2,
63
- isInteractive = false,
64
- isLoading = false,
65
- onPress,
66
- borderColor,
67
- bgColor,
68
- accentColor,
69
- accentWidth,
70
- className,
71
- children,
72
- style,
73
- skeletonHasHeader = true,
74
- skeletonHasFooter = false,
75
- skeletonContentLines = 3,
76
- ...props
77
- }) {
78
- const isClickable = isInteractive || !!onPress;
79
- const [isHovered, setIsHovered] = useState(false);
80
- const theme = useTheme();
81
- const elevationLevel = useMemo(() => {
82
- const validated = getValidatedElevation("card", elevation);
83
- if (variant === "outline" || variant === "accent" || variant === "subtle") {
84
- return 1;
85
- }
86
- return validated;
87
- }, [variant, elevation]);
88
- const baseColor = useMemo(() => getBaseSurfaceColor(theme), [theme]);
89
- const surfaceColor = useMemo(() => {
90
- if ((variant === "outline" || variant === "accent") && theme === "light") {
91
- return "#FAFAFA";
92
- }
93
- if (variant === "subtle") {
94
- return getElevationSurface(baseColor, 0, theme);
95
- }
96
- return getElevationSurface(baseColor, elevationLevel, theme);
97
- }, [baseColor, elevationLevel, theme, variant]);
98
- const shadowStyle = useMemo(() => {
99
- const shouldHover = isHovered && isClickable;
100
- return getElevationShadow(baseColor, elevationLevel, theme, shouldHover);
101
- }, [baseColor, elevationLevel, theme, isHovered, isClickable]);
102
- const cardContent = isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
103
- skeletonHasHeader && /* @__PURE__ */ jsxs(CardHeader, { children: [
104
- /* @__PURE__ */ jsx(View, { className: "h-5 w-1/3 bg-interactive-disabled rounded" }),
105
- /* @__PURE__ */ jsx(View, { className: "mt-2 h-4 w-2/3 bg-interactive-disabled rounded" })
106
- ] }),
107
- /* @__PURE__ */ jsx(CardContent, { children: Array.from({ length: skeletonContentLines }).map((_, i) => /* @__PURE__ */ jsx(
108
- View,
109
- {
110
- className: cn(
111
- "h-4 bg-interactive-disabled rounded",
112
- i < skeletonContentLines - 1 ? "mb-2 w-full" : "w-4/5"
113
- )
114
- },
115
- i
116
- )) }),
117
- skeletonHasFooter && /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx(View, { className: "h-9 w-24 bg-interactive-disabled rounded" }) })
118
- ] }) : children;
119
- const baseClassName = cn(
120
- "rounded-lg overflow-hidden relative",
121
- // Apply variant styles, but only use default border color if no custom borderColor
122
- variant === "outline" ? borderColor ? "border-2" : variantStyles[variant] : variantStyles[variant],
123
- // Remove Tailwind shadow classes - we're using elevation shadows via style prop
124
- isClickable && "web:cursor-pointer web:transition-all web:duration-150",
125
- isClickable && "web:hover:-translate-y-0.5 active:scale-[0.99]",
126
- isLoading && "pointer-events-none animate-pulse",
127
- className
128
- );
129
- const accentStyle = useMemo(() => variant === "accent" ? {
130
- borderLeftWidth: accentWidth ?? 3,
131
- borderLeftColor: accentColor ?? "var(--color-brand-primary)"
132
- } : {}, [variant, accentWidth, accentColor]);
133
- const mergedStyle = useMemo(() => {
134
- const elevationStyle = {
135
- backgroundColor: bgColor || surfaceColor,
136
- borderRadius: 8,
137
- overflow: "hidden",
138
- ...shadowStyle,
139
- ...accentStyle
140
- };
141
- if (borderColor) {
142
- elevationStyle.borderColor = borderColor;
143
- }
144
- if (!style) return elevationStyle;
145
- return [elevationStyle, style];
146
- }, [style, surfaceColor, shadowStyle, borderColor, bgColor, accentStyle]);
147
- if (isClickable) {
148
- return /* @__PURE__ */ jsx(
149
- Pressable,
150
- {
151
- onPress,
152
- onHoverIn: () => setIsHovered(true),
153
- onHoverOut: () => setIsHovered(false),
154
- accessibilityRole: "button",
155
- className: baseClassName,
156
- style: mergedStyle,
157
- ...props,
158
- children: cardContent
159
- }
160
- );
161
- }
162
- return /* @__PURE__ */ jsx(View, { className: baseClassName, style: mergedStyle, ...props, children: cardContent });
163
- }
164
- function CardHeader({ children, className }) {
165
- return /* @__PURE__ */ jsx(View, { className: cn("px-6 py-6", className), children });
166
- }
167
- function CardTitle({ children, className }) {
168
- return /* @__PURE__ */ jsx(
169
- Text,
170
- {
171
- accessibilityRole: "header",
172
- className: cn("text-lg font-semibold text-text-primary font-heading", className),
173
- children
174
- }
175
- );
176
- }
177
- function CardDescription({ children, className }) {
178
- return /* @__PURE__ */ jsx(Text, { className: cn("mt-1.5 text-sm text-text-secondary", className), children });
179
- }
180
- function CardContent({ children, className }) {
181
- return /* @__PURE__ */ jsx(View, { className: cn("px-6 py-4", className), children });
182
- }
183
- function CardFooter({ children, className }) {
184
- return /* @__PURE__ */ jsx(View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
185
- }
186
- function CardSkeleton({
187
- hasHeader = true,
188
- hasFooter = false,
189
- contentLines = 3,
190
- className
191
- }) {
192
- return /* @__PURE__ */ jsxs(Card, { variant: "elevated", elevation: 2, className: cn("animate-pulse", className), children: [
193
- hasHeader && /* @__PURE__ */ jsxs(CardHeader, { children: [
194
- /* @__PURE__ */ jsx(View, { className: "h-5 w-1/3 bg-interactive-disabled rounded" }),
195
- /* @__PURE__ */ jsx(View, { className: "mt-2 h-4 w-2/3 bg-interactive-disabled rounded" })
196
- ] }),
197
- /* @__PURE__ */ jsx(CardContent, { children: Array.from({ length: contentLines }).map((_, i) => /* @__PURE__ */ jsx(
198
- View,
199
- {
200
- className: cn(
201
- "h-4 bg-interactive-disabled rounded",
202
- i < contentLines - 1 ? "mb-2 w-full" : "w-4/5"
203
- )
204
- },
205
- i
206
- )) }),
207
- hasFooter && /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx(View, { className: "h-9 w-24 bg-interactive-disabled rounded" }) })
208
- ] });
209
- }
210
- var sizeStyles = {
211
- xs: "w-1 h-1",
212
- sm: "w-1.5 h-1.5",
213
- md: "w-2 h-2",
214
- lg: "w-2.5 h-2.5"
215
- };
216
- var colorStyles = {
217
- default: "bg-text-tertiary",
218
- primary: "bg-brand-primary",
219
- success: "bg-status-success",
220
- error: "bg-status-error",
221
- warning: "bg-status-warning",
222
- info: "bg-status-info"
223
- };
224
- function Indicator({
225
- size = "sm",
226
- color = "default",
227
- customColor,
228
- glow = false,
229
- ring = false,
230
- className,
231
- style,
232
- ...props
233
- }) {
234
- const dynamicStyle = {
235
- ...typeof style === "object" ? style : {},
236
- ...customColor ? { backgroundColor: customColor } : {},
237
- ...glow && !customColor ? {} : {},
238
- ...glow && customColor ? { boxShadow: `0 0 6px ${customColor}` } : {}
239
- };
240
- return /* @__PURE__ */ jsx(
241
- View,
242
- {
243
- className: cn(
244
- "rounded-full shrink-0",
245
- sizeStyles[size],
246
- !customColor && colorStyles[color],
247
- ring && "border-2 border-background-base",
248
- glow && !customColor && color === "success" && "shadow-[0_0_6px_rgba(20,184,166,0.6)]",
249
- glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
250
- glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
251
- glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
252
- className
253
- ),
254
- style: Object.keys(dynamicStyle).length > 0 ? dynamicStyle : style,
255
- ...props
256
- }
257
- );
258
- }
259
- var variantColorStyles = {
260
- solid: {
261
- default: "bg-border-strong text-text-inverse",
262
- primary: "bg-brand-primary text-text-inverse",
263
- secondary: "bg-brand-secondary text-text-inverse",
264
- success: "bg-status-success text-text-inverse",
265
- error: "bg-status-error text-text-inverse",
266
- warning: "bg-status-warning text-text-inverse",
267
- info: "bg-status-info text-text-inverse"
268
- },
269
- subtle: {
270
- default: "bg-surface-raised text-text-secondary",
271
- primary: "bg-brand-primary-subtle text-brand-primary",
272
- secondary: "bg-brand-secondary-subtle text-brand-secondary",
273
- success: "bg-status-success-subtle text-status-success",
274
- error: "bg-status-error-subtle text-status-error",
275
- warning: "bg-status-warning-subtle text-status-warning",
276
- info: "bg-status-info-subtle text-status-info"
277
- },
278
- outline: {
279
- default: "border border-border-strong text-text-secondary",
280
- primary: "border border-brand-primary text-brand-primary",
281
- secondary: "border border-brand-secondary text-brand-secondary",
282
- success: "border border-status-success text-status-success",
283
- error: "border border-status-error text-status-error",
284
- warning: "border border-status-warning text-status-warning",
285
- info: "border border-status-info text-status-info"
286
- }
287
- };
288
- var sizeStyles2 = {
289
- sm: "px-1.5 py-0.5 text-xs",
290
- md: "px-2 py-0.5 text-xs",
291
- lg: "px-2.5 py-1 text-sm"
292
- };
293
- function Badge({
294
- variant = "subtle",
295
- color = "default",
296
- size = "md",
297
- dot,
298
- dotColor,
299
- className,
300
- children,
301
- ...props
302
- }) {
303
- return /* @__PURE__ */ jsxs(
304
- View,
305
- {
306
- className: cn(
307
- "inline-flex items-center justify-center rounded-full font-medium",
308
- variantColorStyles[variant][color],
309
- sizeStyles2[size],
310
- className
311
- ),
312
- ...props,
313
- children: [
314
- dot && /* @__PURE__ */ jsx(
315
- Indicator,
316
- {
317
- size: "xs",
318
- color: dotColor ?? (color === "default" || color === "secondary" ? "default" : color),
319
- className: "mr-0.5"
320
- }
321
- ),
322
- typeof children === "string" ? /* @__PURE__ */ jsx(Text, { className: "text-inherit font-medium", children }) : children
323
- ]
324
- }
325
- );
326
- }
327
- function BadgeText({ children, className }) {
328
- return /* @__PURE__ */ jsx(Text, { className: cn("text-inherit font-medium", className), children });
329
- }
330
- var solidVariantColors = {
331
- success: "#14B8A6",
332
- warning: "#FFB020",
333
- error: "#D14343",
334
- neutral: "#6B7280"
335
- };
336
- var ringVariantStyles = {
337
- "on-track": {
338
- backgroundColor: "rgba(20,184,166,0.15)",
339
- borderWidth: 1,
340
- borderColor: "rgba(20,184,166,0.3)"
341
- },
342
- deviation: {
343
- backgroundColor: "rgba(245,158,11,0.15)",
344
- borderWidth: 1,
345
- borderColor: "rgba(245,158,11,0.25)"
346
- },
347
- future: {
348
- backgroundColor: "rgba(107,114,128,0.1)",
349
- borderWidth: 1,
350
- borderStyle: "dashed",
351
- borderColor: "rgba(107,114,128,0.2)"
352
- }
353
- };
354
- var glowStyles = {
355
- success: { boxShadow: "0 0 4px rgba(20,184,166,0.4)" },
356
- warning: { boxShadow: "0 0 4px rgba(245,158,11,0.4)" },
357
- error: { boxShadow: "0 0 4px rgba(239,68,68,0.4)" },
358
- neutral: { boxShadow: "0 0 4px rgba(107,114,128,0.4)" },
359
- "on-track": { boxShadow: "0 0 4px rgba(20,184,166,0.4)" },
360
- deviation: { boxShadow: "0 0 4px rgba(245,158,11,0.4)" },
361
- future: { boxShadow: "0 0 4px rgba(107,114,128,0.4)" }
362
- };
363
- var iconChars = {
364
- check: "\u2713",
365
- exclamation: "!",
366
- dash: "\u2014"
367
- };
368
- var iconColors = {
369
- success: "#0A5C52",
370
- warning: "#6B4000",
371
- error: "#5C1A1A",
372
- neutral: "#D1D5DB",
373
- "on-track": "#14B8A6",
374
- deviation: "#FFB020",
375
- future: "#6B7280"
376
- };
377
- var sizeDimensions = {
378
- sm: { size: 8, showIcon: false },
379
- md: { size: 18, showIcon: true }
380
- };
381
- function isSolidVariant(variant) {
382
- return variant in solidVariantColors;
383
- }
384
- function StatusDot({
385
- variant,
386
- icon,
387
- size = "sm",
388
- glow,
389
- label,
390
- className,
391
- ...props
392
- }) {
393
- const config = sizeDimensions[size];
394
- const solid = isSolidVariant(variant);
395
- const ring = ringVariantStyles[variant];
396
- const glowStyle = glow ? glowStyles[variant] : null;
397
- const dot = /* @__PURE__ */ jsx(
398
- View,
399
- {
400
- style: [
401
- {
402
- width: config.size,
403
- height: config.size,
404
- borderRadius: 9999,
405
- alignItems: "center",
406
- justifyContent: "center",
407
- flexShrink: 0
408
- },
409
- solid ? { backgroundColor: solidVariantColors[variant] } : void 0,
410
- ring ? ring : void 0,
411
- glowStyle ? glowStyle : void 0
412
- ],
413
- accessibilityRole: "image",
414
- accessibilityLabel: `${variant} status`,
415
- testID: "status-dot",
416
- children: config.showIcon && icon && /* @__PURE__ */ jsx(
417
- Text,
418
- {
419
- style: {
420
- fontSize: 11,
421
- lineHeight: 11,
422
- fontWeight: "900",
423
- color: iconColors[variant]
424
- },
425
- accessibilityElementsHidden: true,
426
- children: iconChars[icon]
427
- }
428
- )
429
- }
430
- );
431
- if (label) {
432
- return /* @__PURE__ */ jsxs(
433
- View,
434
- {
435
- className,
436
- style: { flexDirection: "row", alignItems: "center", gap: 6 },
437
- accessibilityLabel: `${variant} status: ${label}`,
438
- ...props,
439
- children: [
440
- /* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, children: dot }),
441
- /* @__PURE__ */ jsx(
442
- Text,
443
- {
444
- style: { fontSize: 12, lineHeight: 16, color: "#9CA3AF" },
445
- accessibilityElementsHidden: true,
446
- children: label
447
- }
448
- )
449
- ]
450
- }
451
- );
452
- }
453
- return /* @__PURE__ */ jsx(View, { className, ...props, children: dot });
454
- }
455
- var BRAND_PRIMARY = "#FF7900";
456
- function normalizeData(data, height) {
457
- if (data.length === 0) return [];
458
- const min = Math.min(...data);
459
- const max = Math.max(...data);
460
- const range = max - min || 1;
461
- return data.map((v) => height - (v - min) / range * height);
462
- }
463
- function normalizeValue(value, data, height) {
464
- const min = Math.min(...data);
465
- const max = Math.max(...data);
466
- const range = max - min || 1;
467
- return height - (value - min) / range * height;
468
- }
469
- function Sparkline({
470
- data,
471
- width = 80,
472
- height = 30,
473
- color,
474
- showDots = false,
475
- referenceLines,
476
- highlightLast = false,
477
- className,
478
- ...props
479
- }) {
480
- if (data.length === 0) {
481
- return /* @__PURE__ */ jsx(
482
- View,
483
- {
484
- style: { width, height },
485
- className,
486
- testID: "sparkline-empty",
487
- accessibilityLabel: "Sparkline chart, no data",
488
- ...props
489
- }
490
- );
491
- }
492
- const resolvedColor = color ?? BRAND_PRIMARY;
493
- const normalized = normalizeData(data, height);
494
- const stepX = data.length > 1 ? width / (data.length - 1) : 0;
495
- const dotSize = 3;
496
- const highlightSize = 6;
497
- return /* @__PURE__ */ jsxs(
498
- View,
499
- {
500
- style: { width, height, position: "relative" },
501
- className: cn(className),
502
- accessibilityRole: "image",
503
- accessibilityLabel: `Sparkline chart with ${data.length} data points`,
504
- testID: "sparkline",
505
- ...props,
506
- children: [
507
- referenceLines?.map((line, i) => {
508
- const y = normalizeValue(line.value, data, height);
509
- return /* @__PURE__ */ jsx(
510
- View,
511
- {
512
- style: [
513
- {
514
- position: "absolute",
515
- top: y,
516
- left: 0,
517
- right: 0,
518
- opacity: 0.6
519
- },
520
- line.dashed ? {
521
- height: 0,
522
- borderStyle: "dashed",
523
- borderTopWidth: 1,
524
- borderTopColor: line.color
525
- } : {
526
- height: 1,
527
- backgroundColor: line.color
528
- }
529
- ],
530
- accessibilityElementsHidden: true,
531
- testID: `sparkline-reference-${i}`,
532
- children: line.label && /* @__PURE__ */ jsx(
533
- Text,
534
- {
535
- style: {
536
- position: "absolute",
537
- right: 0,
538
- top: -10,
539
- fontSize: 7,
540
- color: line.color,
541
- opacity: 1
542
- },
543
- testID: `sparkline-reference-label-${i}`,
544
- children: line.label
545
- }
546
- )
547
- },
548
- `ref-${i}`
549
- );
550
- }),
551
- normalized.map((y, i) => {
552
- if (i === 0) return null;
553
- const prevY = normalized[i - 1];
554
- const x1 = (i - 1) * stepX;
555
- const x2 = i * stepX;
556
- const dx = x2 - x1;
557
- const dy = y - prevY;
558
- const length = Math.sqrt(dx * dx + dy * dy);
559
- const angle = Math.atan2(dy, dx) * (180 / Math.PI);
560
- return /* @__PURE__ */ jsx(
561
- View,
562
- {
563
- style: {
564
- position: "absolute",
565
- left: x1,
566
- top: prevY,
567
- width: length,
568
- height: 1.5,
569
- backgroundColor: resolvedColor,
570
- transformOrigin: "0 0",
571
- transform: [{ rotate: `${angle}deg` }]
572
- },
573
- accessibilityElementsHidden: true,
574
- testID: `sparkline-segment-${i}`
575
- },
576
- `line-${i}`
577
- );
578
- }),
579
- (showDots || highlightLast) && normalized.map((y, i) => {
580
- const isLast = i === data.length - 1;
581
- const shouldShow = showDots || highlightLast && isLast;
582
- if (!shouldShow) return null;
583
- const size = highlightLast && isLast ? highlightSize : dotSize;
584
- return /* @__PURE__ */ jsx(
585
- View,
586
- {
587
- style: {
588
- position: "absolute",
589
- left: i * stepX - size / 2,
590
- top: y - size / 2,
591
- width: size,
592
- height: size,
593
- borderRadius: size / 2,
594
- backgroundColor: resolvedColor
595
- },
596
- accessibilityElementsHidden: true,
597
- testID: `sparkline-dot-${i}`
598
- },
599
- `dot-${i}`
600
- );
601
- })
602
- ]
603
- }
604
- );
605
- }
606
- var BRAND_PRIMARY2 = "#FF7900";
607
- var BRAND_PRIMARY_DARK = "#C45E00";
608
- var BRAND_PRIMARY_LIGHT = "#FFB066";
609
- var SUCCESS = "#14B8A6";
610
- var WARNING = "#FFB020";
611
- var ERROR = "#D14343";
612
- var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY2, BRAND_PRIMARY_LIGHT];
613
- var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
614
- var GAUGE_GRADIENT = "linear-gradient(90deg, rgba(20,184,166,0.25) 0%, rgba(255,176,32,0.25) 50%, rgba(209,67,67,0.25) 100%)";
615
- var STATUS_VARIANTS = {
616
- success: { bg: "rgba(20,184,166,0.15)", border: "rgba(20,184,166,0.3)", text: SUCCESS },
617
- warning: { bg: "rgba(255,176,32,0.15)", border: "rgba(255,176,32,0.3)", text: WARNING },
618
- error: { bg: "rgba(209,67,67,0.15)", border: "rgba(209,67,67,0.25)", text: ERROR }
619
- };
620
- function clamp01(value) {
621
- if (!Number.isFinite(value)) return 0;
622
- if (value < 0) return 0;
623
- if (value > 1) return 1;
624
- return value;
625
- }
626
- function getGaugeZoneColor(level) {
627
- if (level >= 0.7) return ERROR;
628
- if (level >= 0.4) return WARNING;
629
- return SUCCESS;
630
- }
631
- function renderCoachingText(text, highlights) {
632
- if (!highlights || highlights.length === 0) return text;
633
- const escaped = highlights.filter(Boolean).map((h) => h.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
634
- if (escaped.length === 0) return text;
635
- const regex = new RegExp(`(${escaped.join("|")})`, "g");
636
- return text.split(regex).map(
637
- (part, index) => highlights.includes(part) ? /* @__PURE__ */ jsx(
638
- Text,
639
- {
640
- className: "text-text-primary",
641
- style: { fontWeight: "700" },
642
- children: part
643
- },
644
- `${part}-${index}`
645
- ) : /* @__PURE__ */ jsx(Fragment$1, { children: part }, `t-${index}`)
646
- );
647
- }
648
- function StatusPill({ badge }) {
649
- const colors = STATUS_VARIANTS[badge.variant];
650
- return /* @__PURE__ */ jsxs(
651
- View,
652
- {
653
- className: "flex-row items-center",
654
- style: {
655
- gap: 5,
656
- paddingVertical: 3,
657
- paddingHorizontal: 8,
658
- borderRadius: 4,
659
- borderWidth: 1,
660
- backgroundColor: colors.bg,
661
- borderColor: colors.border
662
- },
663
- testID: "meso-status-card-badge",
664
- children: [
665
- /* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(StatusDot, { variant: badge.variant, size: "sm" }) }),
666
- /* @__PURE__ */ jsx(
667
- Text,
668
- {
669
- style: {
670
- fontSize: 10,
671
- fontFamily: "Inter, sans-serif",
672
- fontWeight: "700",
673
- letterSpacing: 0.3,
674
- color: colors.text
675
- },
676
- children: badge.label
677
- }
678
- )
679
- ]
680
- }
681
- );
682
- }
683
- function MetricCell({ metric }) {
684
- return /* @__PURE__ */ jsxs(View, { style: { width: "48%" }, testID: "meso-status-card-metric", children: [
685
- /* @__PURE__ */ jsx(
686
- Text,
687
- {
688
- className: "text-text-tertiary",
689
- style: {
690
- fontSize: 10,
691
- fontFamily: "Inter, sans-serif",
692
- fontWeight: "600",
693
- letterSpacing: 0.5,
694
- textTransform: "uppercase"
695
- },
696
- children: metric.label
697
- }
698
- ),
699
- /* @__PURE__ */ jsx(
700
- Text,
701
- {
702
- className: "text-text-primary",
703
- style: {
704
- marginTop: 2,
705
- fontSize: 13,
706
- fontFamily: "Inter, sans-serif",
707
- fontWeight: "600"
708
- },
709
- children: metric.value
710
- }
711
- )
712
- ] });
713
- }
714
- function Gauge({ gauge }) {
715
- const level = clamp01(gauge.level);
716
- const percentage = Math.round(level * 100);
717
- const markerColor = getGaugeZoneColor(level);
718
- return /* @__PURE__ */ jsxs(
719
- View,
720
- {
721
- style: { gap: 6 },
722
- accessibilityRole: "progressbar",
723
- accessibilityValue: { min: 0, max: 100, now: percentage },
724
- accessibilityLabel: `${gauge.label}: ${percentage}%`,
725
- "aria-valuenow": percentage,
726
- "aria-valuemin": 0,
727
- "aria-valuemax": 100,
728
- testID: "meso-status-card-gauge",
729
- children: [
730
- /* @__PURE__ */ jsxs(
731
- View,
732
- {
733
- className: "flex-row items-center justify-between",
734
- accessibilityElementsHidden: true,
735
- children: [
736
- /* @__PURE__ */ jsx(
737
- Text,
738
- {
739
- className: "text-text-secondary",
740
- style: {
741
- fontSize: 10,
742
- fontFamily: "Inter, sans-serif",
743
- fontWeight: "500",
744
- letterSpacing: 0.5,
745
- textTransform: "uppercase"
746
- },
747
- children: gauge.label
748
- }
749
- ),
750
- gauge.sublabel != null && /* @__PURE__ */ jsx(
751
- Text,
752
- {
753
- className: "text-text-tertiary",
754
- style: {
755
- fontSize: 10,
756
- fontFamily: "Inter, sans-serif",
757
- fontWeight: "500"
758
- },
759
- children: gauge.sublabel
760
- }
761
- )
762
- ]
763
- }
764
- ),
765
- /* @__PURE__ */ jsxs(
766
- View,
767
- {
768
- style: {
769
- height: 8,
770
- borderRadius: 4,
771
- position: "relative",
772
- backgroundImage: GAUGE_GRADIENT
773
- },
774
- accessibilityElementsHidden: true,
775
- children: [
776
- /* @__PURE__ */ jsx(
777
- View,
778
- {
779
- style: {
780
- position: "absolute",
781
- left: "50%",
782
- top: 0,
783
- bottom: 0,
784
- width: 1,
785
- marginLeft: -0.5,
786
- backgroundColor: "rgba(255,255,255,0.3)"
787
- },
788
- testID: "meso-status-card-gauge-center"
789
- }
790
- ),
791
- /* @__PURE__ */ jsx(
792
- View,
793
- {
794
- className: "border-text-primary",
795
- style: {
796
- position: "absolute",
797
- left: `${percentage}%`,
798
- top: -3,
799
- marginLeft: -7,
800
- width: 14,
801
- height: 14,
802
- borderRadius: 9999,
803
- borderWidth: 2,
804
- backgroundColor: markerColor,
805
- boxShadow: "0 0 4px rgba(0,0,0,0.5)"
806
- },
807
- testID: "meso-status-card-gauge-marker"
808
- }
809
- )
810
- ]
811
- }
812
- )
813
- ]
814
- }
815
- );
816
- }
817
- function MesoStatusCard({
818
- mesoName,
819
- mesoSubtitle,
820
- statusBadge,
821
- metrics,
822
- gauges,
823
- coaching,
824
- nextTarget,
825
- className,
826
- ...props
827
- }) {
828
- return /* @__PURE__ */ jsxs(
829
- Card,
830
- {
831
- variant: "outline",
832
- elevation: 1,
833
- borderColor: BRAND_PRIMARY_DARK,
834
- className,
835
- style: {
836
- borderColor: BRAND_PRIMARY_DARK,
837
- borderWidth: 1,
838
- backgroundImage: CARD_GRADIENT
839
- },
840
- role: "article",
841
- "aria-label": `Mesocycle status for ${mesoName}`,
842
- testID: "meso-status-card",
843
- ...props,
844
- children: [
845
- /* @__PURE__ */ jsx(
846
- View,
847
- {
848
- className: "flex-row",
849
- style: { position: "absolute", top: 0, left: 0, right: 0, height: 3, zIndex: 1 },
850
- accessibilityElementsHidden: true,
851
- testID: "meso-status-card-accent",
852
- children: ACCENT_STOPS.map((color) => /* @__PURE__ */ jsx(View, { style: { flex: 1, backgroundColor: color } }, color))
853
- }
854
- ),
855
- /* @__PURE__ */ jsxs(View, { style: { padding: 14, gap: 14 }, testID: "meso-status-card-body", children: [
856
- /* @__PURE__ */ jsxs(View, { testID: "meso-status-card-header", children: [
857
- /* @__PURE__ */ jsxs(
858
- View,
859
- {
860
- className: "flex-row items-center justify-between",
861
- style: { gap: 8 },
862
- children: [
863
- /* @__PURE__ */ jsx(
864
- Text,
865
- {
866
- className: "text-text-primary",
867
- style: {
868
- flexShrink: 1,
869
- fontSize: 15,
870
- fontFamily: '"Space Grotesk", sans-serif',
871
- fontWeight: "700"
872
- },
873
- testID: "meso-status-card-name",
874
- children: mesoName
875
- }
876
- ),
877
- /* @__PURE__ */ jsx(StatusPill, { badge: statusBadge })
878
- ]
879
- }
880
- ),
881
- /* @__PURE__ */ jsx(
882
- Text,
883
- {
884
- className: "text-text-secondary",
885
- style: {
886
- marginTop: 4,
887
- fontSize: 12,
888
- fontFamily: "Inter, sans-serif"
889
- },
890
- testID: "meso-status-card-subtitle",
891
- children: mesoSubtitle
892
- }
893
- )
894
- ] }),
895
- metrics.length > 0 && /* @__PURE__ */ jsx(
896
- View,
897
- {
898
- className: "flex-row flex-wrap",
899
- style: { gap: 8 },
900
- testID: "meso-status-card-metrics",
901
- children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell, { metric }, `${metric.label}-${index}`))
902
- }
903
- ),
904
- gauges.length > 0 && /* @__PURE__ */ jsx(View, { style: { gap: 12 }, testID: "meso-status-card-gauges", children: gauges.map((gauge, index) => /* @__PURE__ */ jsx(Gauge, { gauge }, `${gauge.label}-${index}`)) }),
905
- coaching != null && /* @__PURE__ */ jsx(
906
- View,
907
- {
908
- style: {
909
- backgroundColor: "rgba(255,176,32,0.06)",
910
- borderWidth: 1,
911
- borderColor: "rgba(255,176,32,0.15)",
912
- borderRadius: 8,
913
- paddingVertical: 10,
914
- paddingHorizontal: 12
915
- },
916
- testID: "meso-status-card-coaching",
917
- children: /* @__PURE__ */ jsx(
918
- Text,
919
- {
920
- className: "text-text-secondary",
921
- style: {
922
- fontSize: 12,
923
- lineHeight: 17,
924
- fontFamily: "Inter, sans-serif"
925
- },
926
- children: renderCoachingText(coaching.text, coaching.highlights)
927
- }
928
- )
929
- }
930
- ),
931
- nextTarget != null && /* @__PURE__ */ jsxs(
932
- View,
933
- {
934
- className: "flex-row items-center",
935
- style: {
936
- gap: 8,
937
- backgroundColor: "rgba(20,184,166,0.06)",
938
- borderWidth: 1,
939
- borderColor: "rgba(20,184,166,0.2)",
940
- borderRadius: 8,
941
- paddingVertical: 10,
942
- paddingHorizontal: 12
943
- },
944
- testID: "meso-status-card-next-target",
945
- children: [
946
- /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: SUCCESS }, accessibilityElementsHidden: true, children: nextTarget.icon }),
947
- /* @__PURE__ */ jsx(
948
- Text,
949
- {
950
- style: {
951
- flexShrink: 1,
952
- fontSize: 12,
953
- fontFamily: "Inter, sans-serif",
954
- fontWeight: "600",
955
- color: SUCCESS
956
- },
957
- children: nextTarget.text
958
- }
959
- )
960
- ]
961
- }
962
- )
963
- ] })
964
- ]
965
- }
966
- );
967
- }
968
-
969
- // src/theme/workout-tokens.ts
970
- var WORKOUT_TOKENS = {
971
- // Canonical 4-band performance scale — the single source for BOTH the
972
- // VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
973
- // is intentionally inverted between the two consumers: for velocity, green =
974
- // fastest/best; for RPE, green = easiest. Each component maps its own
975
- // thresholds onto these four colors. Kept as vivid data-viz values (distinct
976
- // from the muted status tokens) and theme-independent, so — like the heatmap
977
- // scale below — they are consumed inline as a JS import rather than CSS vars.
978
- // A JS import also resolves on native RN, unlike var(--…) strings.
979
- scale: {
980
- green: "#2ed573",
981
- yellow: "#ffd43b",
982
- orange: "#ffa502",
983
- red: "#ff4757"
984
- },
985
- // BodyMap volume heatmap scale (drive dynamic SVG fills, so inline values).
986
- // Maps weekly-volume status against MEV/MAV/MRV landmarks to a fill color.
987
- heatmap: {
988
- none: "#E0E0E0",
989
- // gray — no training data
990
- under: "#4A90D9",
991
- // cool blue — below MEV
992
- maintenance: "#F5C842",
993
- // warm yellow — MEV to MAV
994
- productive: "#4CAF50",
995
- // green — MAV to MRV
996
- approaching: "#FFA502",
997
- // orange — near MRV
998
- over: "#FF6B35"
999
- // red-orange — over MRV
1000
- },
1001
- // Badge border-radius (rounded-sm is 4px, we need 2px)
1002
- badgeRadius: 2,
1003
- // Surface and border colors were removed here: those flip with the theme, so
1004
- // components use the CSS custom properties (var(--color-surface-*/border-*))
1005
- // directly. Only theme-independent data-viz values remain below.
1006
- // Intensity bar specific
1007
- intensity: {
1008
- track: "#333333",
1009
- over1: "#D14343",
1010
- over2: "#A62626",
1011
- over3: "#7A1C1C",
1012
- targetLine: "rgba(33, 150, 243, 0.5)",
1013
- atTargetGlow: "0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)"
1014
- },
1015
- // Placeholder strip
1016
- placeholder: {
1017
- fill: "#3A3A3A"
1018
- },
1019
- // Deviation bar
1020
- deviation: {
1021
- track: "#333333"
1022
- }
1023
- };
1024
-
1025
- export { Badge, BadgeText, Card, CardContent, CardDescription, CardFooter, CardHeader, CardSkeleton, CardTitle, Indicator, MesoStatusCard, Sparkline, StatusDot, WORKOUT_TOKENS, cn };
1026
- //# sourceMappingURL=chunk-2SISKTWM.mjs.map
1027
- //# sourceMappingURL=chunk-2SISKTWM.mjs.map