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