@titan-design/react-ui 0.4.0 → 0.6.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 (150) 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 +232 -185
  6. package/dist/bodymap.js.map +1 -1
  7. package/dist/bodymap.mjs +1963 -11
  8. package/dist/bodymap.mjs.map +1 -1
  9. package/dist/index.d.mts +193 -744
  10. package/dist/index.d.ts +193 -744
  11. package/dist/index.js +1551 -1856
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +10770 -8213
  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 +6694 -0
  20. package/dist/pages.js.map +1 -0
  21. package/dist/pages.mjs +6674 -0
  22. package/dist/pages.mjs.map +1 -0
  23. package/dist/theme/index.d.mts +473 -211
  24. package/dist/theme/index.d.ts +473 -211
  25. package/dist/theme/index.js +341 -127
  26. package/dist/theme/index.js.map +1 -1
  27. package/dist/theme/index.mjs +2085 -2
  28. package/dist/theme/index.mjs.map +1 -1
  29. package/dist/theme/tokens-css.js +148 -139
  30. package/dist/theme/tokens-css.js.map +1 -1
  31. package/dist/theme/tokens-css.mjs +582 -1
  32. package/dist/theme/tokens-css.mjs.map +1 -1
  33. package/package.json +11 -7
  34. package/src/components/custom/DateTime/DateTime.stories.tsx +82 -6
  35. package/src/components/custom/DateTime/DateTime.test.tsx +29 -3
  36. package/src/components/custom/DateTime/DateTime.tsx +55 -15
  37. package/src/components/custom/Gauge/Gauge.test.tsx +2 -2
  38. package/src/components/custom/Gauge/Gauge.tsx +6 -3
  39. package/src/components/custom/Scatter/Scatter.tsx +3 -5
  40. package/src/components/custom/Treemap/Treemap.tsx +3 -5
  41. package/src/components/custom/Typography/Typography.stories.tsx +45 -27
  42. package/src/components/custom/Typography/Typography.test.tsx +13 -3
  43. package/src/components/custom/Typography/Typography.tsx +5 -0
  44. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +2 -1
  45. package/src/components/custom/Workout/BaseBadge.stories.tsx +3 -3
  46. package/src/components/custom/Workout/BaseBadge.test.tsx +1 -1
  47. package/src/components/custom/Workout/BaseBadge.tsx +1 -1
  48. package/src/components/custom/Workout/BodyMap.tsx +6 -3
  49. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +6 -3
  50. package/src/components/custom/Workout/CapacityBandChart.test.tsx +2 -2
  51. package/src/components/custom/Workout/CapacityBandChart.tsx +9 -6
  52. package/src/components/custom/Workout/DeviationBar.test.tsx +1 -1
  53. package/src/components/custom/Workout/DeviationBar.tsx +7 -4
  54. package/src/components/custom/Workout/ExerciseCard.tsx +5 -1
  55. package/src/components/custom/Workout/ExerciseDetailPage.tsx +19 -5
  56. package/src/components/custom/Workout/InputBar.tsx +2 -1
  57. package/src/components/custom/Workout/IntensityBar.test.tsx +112 -60
  58. package/src/components/custom/Workout/IntensityBar.tsx +113 -58
  59. package/src/components/custom/Workout/MesoCard.tsx +8 -3
  60. package/src/components/custom/Workout/MesoProgressBar.test.tsx +1 -1
  61. package/src/components/custom/Workout/MesoProgressBar.tsx +3 -2
  62. package/src/components/custom/Workout/MesoStatusCard.tsx +20 -13
  63. package/src/components/custom/Workout/MuscleGroupChip.tsx +7 -4
  64. package/src/components/custom/Workout/PlaceholderStrip.tsx +5 -9
  65. package/src/components/custom/Workout/PrBadge.tsx +7 -4
  66. package/src/components/custom/Workout/PrHistoryModal.tsx +6 -5
  67. package/src/components/custom/Workout/ProgramPlanningPage.tsx +2 -1
  68. package/src/components/custom/Workout/README.md +50 -0
  69. package/src/components/custom/Workout/ReadinessCheck.tsx +7 -4
  70. package/src/components/custom/Workout/RestTimer.test.tsx +41 -10
  71. package/src/components/custom/Workout/RestTimer.tsx +50 -44
  72. package/src/components/custom/Workout/SetRow.tsx +12 -2
  73. package/src/components/custom/Workout/Sparkline.tsx +2 -1
  74. package/src/components/custom/Workout/StatusDot.test.tsx +2 -2
  75. package/src/components/custom/Workout/StatusDot.tsx +12 -9
  76. package/src/components/custom/Workout/StrengthTrendChart.tsx +9 -6
  77. package/src/components/custom/Workout/SupersetWrapper.tsx +2 -1
  78. package/src/components/custom/Workout/TempoDisplay.stories.tsx +69 -2
  79. package/src/components/custom/Workout/TempoDisplay.test.tsx +35 -0
  80. package/src/components/custom/Workout/TempoDisplay.tsx +109 -3
  81. package/src/components/custom/Workout/VelocityStrip.test.tsx +89 -2
  82. package/src/components/custom/Workout/VelocityStrip.tsx +203 -38
  83. package/src/components/custom/Workout/WeekRow.test.tsx +2 -2
  84. package/src/components/custom/Workout/WeekRow.tsx +3 -2
  85. package/src/components/custom/Workout/WeightBadge.test.tsx +2 -2
  86. package/src/components/custom/Workout/WeightBadge.tsx +8 -4
  87. package/src/components/custom/Workout/WorkoutCard.tsx +5 -2
  88. package/src/components/custom/Workout/WorkoutPill.tsx +12 -8
  89. package/src/components/custom/Workout/icons.tsx +7 -0
  90. package/src/components/custom/Workout/index.ts +38 -47
  91. package/src/components/icons/SvgIcon.tsx +50 -0
  92. package/src/components/icons/icons.stories.tsx +57 -0
  93. package/src/components/icons/icons.test.tsx +43 -0
  94. package/src/components/icons/icons.tsx +45 -0
  95. package/src/components/icons/index.ts +4 -0
  96. package/src/components/index.ts +6 -0
  97. package/src/components/shell/BrandLockup.stories.tsx +27 -0
  98. package/src/components/shell/BrandLockup.test.tsx +23 -0
  99. package/src/components/shell/BrandLockup.tsx +47 -0
  100. package/src/components/shell/DeviceIndicator.stories.tsx +28 -0
  101. package/src/components/shell/DeviceIndicator.test.tsx +26 -0
  102. package/src/components/shell/DeviceIndicator.tsx +52 -0
  103. package/src/components/shell/DeviceMenu.stories.tsx +47 -0
  104. package/src/components/shell/DeviceMenu.test.tsx +40 -0
  105. package/src/components/shell/DeviceMenu.tsx +65 -0
  106. package/src/components/shell/DeviceRow.stories.tsx +38 -0
  107. package/src/components/shell/DeviceRow.test.tsx +37 -0
  108. package/src/components/shell/DeviceRow.tsx +58 -0
  109. package/src/components/shell/README.md +86 -0
  110. package/src/components/shell/SessionStatePill.stories.tsx +27 -0
  111. package/src/components/shell/SessionStatePill.test.tsx +25 -0
  112. package/src/components/shell/SessionStatePill.tsx +46 -0
  113. package/src/components/shell/TopBar.stories.tsx +49 -0
  114. package/src/components/shell/TopBar.test.tsx +36 -0
  115. package/src/components/shell/TopBar.tsx +89 -0
  116. package/src/components/shell/index.ts +8 -0
  117. package/src/components/ui/indicator/Indicator.stories.tsx +37 -1
  118. package/src/components/ui/indicator/Indicator.test.tsx +19 -1
  119. package/src/components/ui/indicator/Indicator.tsx +45 -6
  120. package/src/components/ui/indicator/index.ts +1 -1
  121. package/src/components/ui/spinner/Spinner.tsx +3 -2
  122. package/src/components/ui/toolbar-button/ToolbarButton.tsx +2 -1
  123. package/src/index.ts +3 -2
  124. package/src/pages.ts +61 -0
  125. package/src/test/stories-smoke.test.tsx +50 -0
  126. package/src/theme/ColorSystem.stories.tsx +323 -0
  127. package/src/theme/Colors.stories.tsx +69 -1
  128. package/src/theme/barrel.ts +54 -0
  129. package/src/theme/config.ts +16 -3
  130. package/src/theme/extracted-colors-dataviz.ts +21 -0
  131. package/src/theme/extracted-colors-ui.ts +20 -0
  132. package/src/theme/global.css +64 -52
  133. package/src/theme/gradients.test.ts +25 -0
  134. package/src/theme/gradients.ts +36 -0
  135. package/src/theme/index.ts +8 -43
  136. package/src/theme/native-theming-guard.test.ts +117 -0
  137. package/src/theme/tokens/primitives.test.ts +232 -0
  138. package/src/theme/tokens/primitives.ts +247 -0
  139. package/src/theme/tokens/semantic.ts +93 -81
  140. package/src/theme/workout-tokens.ts +22 -21
  141. package/src/utils/avatar-color.ts +3 -3
  142. package/tailwind.config.js +10 -3
  143. package/dist/bodymap-BhG55xHM.d.mts +0 -318
  144. package/dist/bodymap-BhG55xHM.d.ts +0 -318
  145. package/dist/chunk-2SISKTWM.mjs +0 -1027
  146. package/dist/chunk-2SISKTWM.mjs.map +0 -1
  147. package/dist/chunk-7XPB4NAO.mjs +0 -1013
  148. package/dist/chunk-7XPB4NAO.mjs.map +0 -1
  149. package/dist/chunk-SNVKL5WZ.mjs +0 -883
  150. package/dist/chunk-SNVKL5WZ.mjs.map +0 -1
@@ -1,13 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { View } from 'react-native'
3
- import {
4
- Typography,
5
- Heading,
6
- Paragraph,
7
- Caption,
8
- Label,
9
- Overline,
10
- } from './Typography'
3
+ import { Typography, Heading, Paragraph, Caption, Label, Overline } from './Typography'
11
4
 
12
5
  const meta: Meta<typeof Typography> = {
13
6
  title: 'Custom/Typography',
@@ -17,16 +10,36 @@ const meta: Meta<typeof Typography> = {
17
10
  variant: {
18
11
  control: 'select',
19
12
  options: [
20
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
21
- 'body1', 'body2', 'subtitle1', 'subtitle2',
22
- 'caption', 'overline', 'button',
13
+ 'h1',
14
+ 'h2',
15
+ 'h3',
16
+ 'h4',
17
+ 'h5',
18
+ 'h6',
19
+ 'body1',
20
+ 'body2',
21
+ 'subtitle1',
22
+ 'subtitle2',
23
+ 'caption',
24
+ 'overline',
25
+ 'button',
26
+ 'mono',
27
+ 'monoLabel',
23
28
  ],
24
29
  },
25
30
  color: {
26
31
  control: 'select',
27
32
  options: [
28
- 'primary', 'secondary', 'tertiary', 'disabled',
29
- 'inverse', 'success', 'error', 'warning', 'info', 'inherit',
33
+ 'primary',
34
+ 'secondary',
35
+ 'tertiary',
36
+ 'disabled',
37
+ 'inverse',
38
+ 'success',
39
+ 'error',
40
+ 'warning',
41
+ 'info',
42
+ 'inherit',
30
43
  ],
31
44
  },
32
45
  align: {
@@ -65,19 +78,15 @@ export const AllBody: Story = {
65
78
  render: () => (
66
79
  <View style={{ gap: 16, maxWidth: 400 }}>
67
80
  <Typography variant="body1">
68
- Body 1 - This is the default body text style. It&apos;s used for main content
69
- and paragraphs throughout the application.
81
+ Body 1 - This is the default body text style. It&apos;s used for main content and paragraphs
82
+ throughout the application.
70
83
  </Typography>
71
84
  <Typography variant="body2">
72
- Body 2 - This is a smaller body text style. It&apos;s useful for secondary
73
- content or when you need more compact text.
74
- </Typography>
75
- <Typography variant="subtitle1">
76
- Subtitle 1 - Semi-bold text for emphasis
77
- </Typography>
78
- <Typography variant="subtitle2">
79
- Subtitle 2 - Smaller semi-bold text
85
+ Body 2 - This is a smaller body text style. It&apos;s useful for secondary content or when
86
+ you need more compact text.
80
87
  </Typography>
88
+ <Typography variant="subtitle1">Subtitle 1 - Semi-bold text for emphasis</Typography>
89
+ <Typography variant="subtitle2">Subtitle 2 - Smaller semi-bold text</Typography>
81
90
  </View>
82
91
  ),
83
92
  }
@@ -92,6 +101,16 @@ export const AllUtility: Story = {
92
101
  ),
93
102
  }
94
103
 
104
+ /** Monospace technical readouts (clocks, ids, metrics) and their all-caps label form. */
105
+ export const Mono: Story = {
106
+ render: () => (
107
+ <View style={{ gap: 16 }}>
108
+ <Typography variant="mono">mono — 16:12 · Voltra-A3F2 · 0.42 m/s</Typography>
109
+ <Typography variant="monoLabel">monoLabel — live status label</Typography>
110
+ </View>
111
+ ),
112
+ }
113
+
95
114
  export const AllColors: Story = {
96
115
  render: () => (
97
116
  <View style={{ gap: 8 }}>
@@ -121,12 +140,11 @@ export const ParagraphComponent: Story = {
121
140
  render: () => (
122
141
  <View style={{ gap: 16, maxWidth: 400 }}>
123
142
  <Paragraph>
124
- This is a regular paragraph using the default body1 variant.
125
- It&apos;s great for main content.
143
+ This is a regular paragraph using the default body1 variant. It&apos;s great for main
144
+ content.
126
145
  </Paragraph>
127
146
  <Paragraph small>
128
- This is a small paragraph using the body2 variant.
129
- It&apos;s useful for secondary content.
147
+ This is a small paragraph using the body2 variant. It&apos;s useful for secondary content.
130
148
  </Paragraph>
131
149
  </View>
132
150
  ),
@@ -77,6 +77,18 @@ describe('Typography', () => {
77
77
  })
78
78
  })
79
79
 
80
+ describe('mono variants', () => {
81
+ it('renders the mono variant', () => {
82
+ render(<Typography variant="mono">16:12</Typography>)
83
+ expect(screen.getByText('16:12')).toBeInTheDocument()
84
+ })
85
+
86
+ it('renders the monoLabel variant', () => {
87
+ render(<Typography variant="monoLabel">live</Typography>)
88
+ expect(screen.getByText('live')).toBeInTheDocument()
89
+ })
90
+ })
91
+
80
92
  describe('accessibility', () => {
81
93
  it('headings have no accessibility violations', async () => {
82
94
  const { container } = render(
@@ -91,9 +103,7 @@ describe('Typography', () => {
91
103
  })
92
104
 
93
105
  it('body text has no accessibility violations', async () => {
94
- const { container } = render(
95
- <Typography variant="body1">Body text content</Typography>
96
- )
106
+ const { container } = render(<Typography variant="body1">Body text content</Typography>)
97
107
 
98
108
  const results = await axe(container)
99
109
  expect(results).toHaveNoViolations()
@@ -16,6 +16,8 @@ export type TypographyVariant =
16
16
  | 'caption'
17
17
  | 'overline'
18
18
  | 'button'
19
+ | 'mono'
20
+ | 'monoLabel'
19
21
 
20
22
  export type TypographyColor =
21
23
  | 'primary'
@@ -70,6 +72,9 @@ const variantStyles: Record<TypographyVariant, string> = {
70
72
  caption: 'font-body text-xs font-normal leading-loose',
71
73
  overline: 'font-body text-xs font-semibold uppercase tracking-widest leading-relaxed',
72
74
  button: 'font-sans text-sm font-semibold leading-relaxed',
75
+ // Monospace technical readouts (clocks, ids, metrics) and their all-caps label form.
76
+ mono: 'font-mono text-xs font-normal leading-normal',
77
+ monoLabel: 'font-mono text-xs font-bold uppercase tracking-wide leading-normal',
73
78
  }
74
79
 
75
80
  const colorStyles: Record<TypographyColor, string> = {
@@ -7,8 +7,9 @@ import { RestTimer } from './RestTimer'
7
7
  import { SupersetWrapper } from './SupersetWrapper'
8
8
  import { type SetRowProps } from './SetRow'
9
9
  import { resolveColor } from '../../../theme/resolve-color'
10
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
10
11
 
11
- const BRAND_PRIMARY = '#FF7900'
12
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
12
13
 
13
14
  /** Where an exercise sits in the during-workout flow. */
14
15
  export type ActiveExerciseStatus = 'completed' | 'active' | 'upcoming'
@@ -1,6 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { View, Text } from 'react-native'
3
- import { Dumbbell } from 'lucide-react'
3
+ import { DumbbellIcon } from './icons'
4
4
  import { BaseBadge } from './BaseBadge'
5
5
 
6
6
  const meta: Meta<typeof BaseBadge> = {
@@ -47,7 +47,7 @@ export const Pr: Story = {
47
47
  export const WithIcon: Story = {
48
48
  args: {
49
49
  variant: 'plain',
50
- icon: <Dumbbell size={12} color="#9CA3AF" strokeWidth={2} />,
50
+ icon: <DumbbellIcon size={12} color="#9CA3AF" strokeWidth={2} />,
51
51
  children: <Label>225 lbs</Label>,
52
52
  },
53
53
  }
@@ -66,7 +66,7 @@ export const AllVariants: Story = {
66
66
  <BaseBadge variant="pr" size="lg"><Label color="#FF7900">PR</Label></BaseBadge>
67
67
  </View>
68
68
  <View style={{ flexDirection: 'row', gap: 12, alignItems: 'center' }}>
69
- <BaseBadge icon={<Dumbbell size={12} color="#9CA3AF" strokeWidth={2} />}>
69
+ <BaseBadge icon={<DumbbellIcon size={12} color="#9CA3AF" strokeWidth={2} />}>
70
70
  <Label>225 lbs</Label>
71
71
  </BaseBadge>
72
72
  </View>
@@ -22,7 +22,7 @@ describe('BaseBadge', () => {
22
22
  </BaseBadge>,
23
23
  )
24
24
  expect(screen.getByTestId('bb')).toHaveStyle({
25
- borderTopColor: 'rgba(255, 121, 0, 0.12)',
25
+ borderTopColor: 'rgba(255, 121, 0, 0.3)',
26
26
  })
27
27
  })
28
28
 
@@ -33,7 +33,7 @@ const variantColors: Record<
33
33
  plain: { backgroundColor: '#1C1C1C', borderColor: '#1F1F1F' },
34
34
  pr: {
35
35
  backgroundColor: 'rgba(255, 121, 0, 0.12)',
36
- borderColor: 'rgba(255, 121, 0, 0.12)',
36
+ borderColor: 'rgba(255, 121, 0, 0.3)',
37
37
  },
38
38
  }
39
39
 
@@ -4,6 +4,7 @@ import { View, Text, Pressable, Animated, Easing, type ViewProps } from 'react-n
4
4
  import BodyHighlighter, { type ExtendedBodyPart, type Slug } from 'react-native-body-highlighter'
5
5
  import { cn } from '../../../utils/cn'
6
6
  import { resolveColor } from '../../../theme/resolve-color'
7
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
7
8
  import {
8
9
  MuscleGroup,
9
10
  SimpleMuscleGroup,
@@ -25,6 +26,8 @@ import {
25
26
  const Body = ((BodyHighlighter as unknown as { default?: typeof BodyHighlighter }).default ??
26
27
  BodyHighlighter) as typeof BodyHighlighter
27
28
 
29
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
30
+
28
31
  const OUTLINE_FILL = 'rgba(255,255,255,0.08)'
29
32
  const OUTLINE_BORDER = 'rgba(255,255,255,0.12)'
30
33
  const BODY_SCALE = 0.8 // 200x400 intrinsic -> ~160x320 px
@@ -275,7 +278,7 @@ function ViewToggle({ view, onViewChange }: ViewToggleProps) {
275
278
  borderRadius: 9999,
276
279
  backgroundColor: active ? 'rgba(255,121,0,0.16)' : 'transparent',
277
280
  borderWidth: 1,
278
- borderColor: active ? '#FF7900' : resolveColor('border-strong'),
281
+ borderColor: active ? BRAND_PRIMARY : resolveColor('border-strong'),
279
282
  }}
280
283
  testID={`body-map-toggle-${side}`}
281
284
  >
@@ -284,7 +287,7 @@ function ViewToggle({ view, onViewChange }: ViewToggleProps) {
284
287
  fontSize: 12,
285
288
  fontFamily: 'Inter, sans-serif',
286
289
  fontWeight: active ? '700' : '500',
287
- color: active ? '#FF7900' : resolveColor('text-secondary'),
290
+ color: active ? BRAND_PRIMARY : resolveColor('text-secondary'),
288
291
  textTransform: 'capitalize',
289
292
  }}
290
293
  >
@@ -321,7 +324,7 @@ function MuscleButton({ entry, highlighted, onMusclePress }: MuscleButtonProps)
321
324
  paddingVertical: 3,
322
325
  borderRadius: 9999,
323
326
  borderWidth: 1,
324
- borderColor: highlighted ? '#FF7900' : resolveColor('border-default'),
327
+ borderColor: highlighted ? BRAND_PRIMARY : resolveColor('border-default'),
325
328
  }}
326
329
  testID={`body-map-muscle-${entry.key}`}
327
330
  >
@@ -18,11 +18,14 @@ import {
18
18
  type VolumeLandmarks,
19
19
  type VolumeStatus,
20
20
  } from './muscleTaxonomy'
21
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
21
22
 
22
- const BRAND_PRIMARY = '#FF7900'
23
+ const t = getSemanticColors('dark')
23
24
 
24
- /** Volume track gradient: status-info (blue) -> result-improve (teal) -> status-error (red). */
25
- const VOLUME_GRADIENT = 'linear-gradient(90deg, #2196F3 0%, #14B8A6 50%, #D14343 100%)'
25
+ const BRAND_PRIMARY = t['brand-primary']
26
+
27
+ /** Volume track gradient: status-info -> status-success -> status-error. */
28
+ const VOLUME_GRADIENT = `linear-gradient(90deg, ${t['status-info']} 0%, ${t['status-success']} 50%, ${t['status-error']} 100%)`
26
29
 
27
30
  /** Sheet slides up from this offset (px) and the backdrop fades to this opacity. */
28
31
  const SLIDE_OFFSET = 400
@@ -93,8 +93,8 @@ describe('CapacityBandChart', () => {
93
93
  it('colors dots by status relative to the band', () => {
94
94
  render(<CapacityBandChart {...baseProps} />)
95
95
  const dots = screen.getAllByTestId('capacity-band-chart-dot')
96
- expect(dots[0]).toHaveStyle({ backgroundColor: '#14B8A6' }) // within
97
- expect(dots[1]).toHaveStyle({ backgroundColor: '#FFB020' }) // above
96
+ expect(dots[0]).toHaveStyle({ backgroundColor: '#2ED573' }) // within
97
+ expect(dots[1]).toHaveStyle({ backgroundColor: '#F9B415' }) // above
98
98
  expect(dots[2]).toHaveStyle({ backgroundColor: '#2196F3' }) // below
99
99
  })
100
100
 
@@ -1,16 +1,19 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { useEffect, useState, useMemo } from 'react'
3
3
  import { View, Text, Pressable, Animated, Easing, type ViewProps } from 'react-native'
4
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
5
 
5
- const STATUS_SUCCESS = '#14B8A6'
6
- const STATUS_WARNING = '#FFB020'
7
- const STATUS_INFO = '#2196F3'
6
+ const t = getSemanticColors('dark')
7
+
8
+ const STATUS_SUCCESS = t['status-success']
9
+ const STATUS_WARNING = t['status-warning']
10
+ const STATUS_INFO = t['status-info']
8
11
  /** Dot outline: near-white ring so load dots read on the band fill and any
9
12
  * surface (matches the MesoStatusCard gauge-marker convention). */
10
13
  const DOT_BORDER = '#F3F4F6'
11
- const BAND_FILL = 'rgba(20,184,166,0.1)'
12
- const BAND_EDGE = 'rgba(20,184,166,0.45)'
13
- const PROJECTION_FILL = 'rgba(20,184,166,0.05)'
14
+ const BAND_FILL = 'rgba(46,213,115,0.1)'
15
+ const BAND_EDGE = 'rgba(46,213,115,0.45)'
16
+ const PROJECTION_FILL = 'rgba(46,213,115,0.05)'
14
17
 
15
18
  const PADDING_LEFT = 28
16
19
  const PADDING_RIGHT = 10
@@ -72,7 +72,7 @@ describe('DeviationBar', () => {
72
72
  const track = bar.firstElementChild as HTMLElement
73
73
  expect(track).toHaveStyle({
74
74
  backgroundImage:
75
- 'linear-gradient(90deg, rgba(20,184,166,0.25) 0%, rgba(107,114,128,0.15) 50%, rgba(255,176,32,0.25) 100%)',
75
+ 'linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(107,114,128,0.15) 50%, rgba(249,180,21,0.25) 100%)',
76
76
  })
77
77
  })
78
78
 
@@ -1,5 +1,8 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { View, type ViewProps, type ViewStyle } from 'react-native'
3
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
+
5
+ const t = getSemanticColors('dark')
3
6
 
4
7
  export interface DeviationBarProps extends ViewProps {
5
8
  deviation: number
@@ -9,10 +12,10 @@ export interface DeviationBarProps extends ViewProps {
9
12
 
10
13
  function getDotColor(deviation: number): string {
11
14
  const abs = Math.abs(deviation)
12
- if (deviation < -0.3) return '#14B8A6'
15
+ if (deviation < -0.3) return t['status-success']
13
16
  if (abs <= 0.3) return '#6B7280'
14
- if (abs <= 0.7) return '#FFB020'
15
- return '#D14343'
17
+ if (abs <= 0.7) return t['status-warning']
18
+ return t['status-error']
16
19
  }
17
20
 
18
21
  function getDeviationDescription(deviation: number): string {
@@ -55,7 +58,7 @@ export function DeviationBar({
55
58
  borderRadius: 3,
56
59
  // react-native-web renders backgroundImage at runtime; not in RN ViewStyle types
57
60
  backgroundImage:
58
- 'linear-gradient(90deg, rgba(20,184,166,0.25) 0%, rgba(107,114,128,0.15) 50%, rgba(255,176,32,0.25) 100%)',
61
+ 'linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(107,114,128,0.15) 50%, rgba(249,180,21,0.25) 100%)',
59
62
  } as ViewStyle}
60
63
  />
61
64
  <View
@@ -1,7 +1,7 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { useState } from 'react'
3
3
  import { View, Text, Pressable } from 'react-native'
4
- import { VelocityStrip } from './VelocityStrip'
4
+ import { VelocityStrip, type VelocityZoneBandProp } from './VelocityStrip'
5
5
  import { PlaceholderStrip } from './PlaceholderStrip'
6
6
  import { WeightBadge } from './WeightBadge'
7
7
  import { PrBadge } from './PrBadge'
@@ -26,6 +26,8 @@ export interface ExerciseCardProps {
26
26
  e1rm?: { value: number; unit: 'lbs' | 'kg' }
27
27
  isPR?: boolean
28
28
  setVelocities?: number[][]
29
+ /** Optional velocity-zone bands shared across this exercise's sets (WA bands). */
30
+ velocityZones?: readonly VelocityZoneBandProp[]
29
31
  totalPlannedSets?: number
30
32
  sets?: SetRowProps[]
31
33
  tempo?: [number, number, number, number]
@@ -106,6 +108,7 @@ function CollapsedCard({
106
108
  e1rm,
107
109
  isPR,
108
110
  setVelocities,
111
+ velocityZones,
109
112
  totalPlannedSets,
110
113
  supersetPosition,
111
114
  }: ExerciseCardProps) {
@@ -189,6 +192,7 @@ function CollapsedCard({
189
192
  <VelocityStrip
190
193
  key={i}
191
194
  velocities={velocities}
195
+ zones={velocityZones}
192
196
  variant="mini"
193
197
  testID={`exercise-card-velocity-strip-${i}`}
194
198
  />
@@ -15,11 +15,17 @@ import {
15
15
  } from './CapacityBandChart'
16
16
  import { ExerciseCard, type ExerciseCardProps } from './ExerciseCard'
17
17
  import { type SetRowProps } from './SetRow'
18
- import { VelocityStrip, calculateMeanVelocity, calculateVelocityLoss } from './VelocityStrip'
18
+ import {
19
+ VelocityStrip,
20
+ calculateMeanVelocity,
21
+ calculateVelocityLoss,
22
+ type VelocityZoneBandProp,
23
+ } from './VelocityStrip'
19
24
  import { resolveColor } from '../../../theme/resolve-color'
25
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
20
26
  import { cn } from '../../../utils/cn'
21
27
 
22
- const BRAND_PRIMARY = '#FF7900'
28
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
23
29
 
24
30
  /** Inner plot width: page width 390 − 16 page padding − 12 card padding on each side. */
25
31
  const CHART_WIDTH = 326
@@ -97,6 +103,8 @@ export interface ExerciseVbt {
97
103
  projection?: CapacityBandProjection
98
104
  /** Per-set velocity traces for the most recent session. */
99
105
  sets: ExerciseVbtSet[]
106
+ /** Optional velocity-zone bands for this exercise (WA `VelocityZones.bands`). */
107
+ zones?: readonly VelocityZoneBandProp[]
100
108
  }
101
109
 
102
110
  /** Page-level roll-up of the logged sessions. */
@@ -348,7 +356,13 @@ function EntryList({ entries, expandedId, onToggle, emptyLabel, testID }: EntryL
348
356
  )
349
357
  }
350
358
 
351
- function VbtBreakdown({ sets }: { sets: ExerciseVbtSet[] }) {
359
+ function VbtBreakdown({
360
+ sets,
361
+ zones,
362
+ }: {
363
+ sets: ExerciseVbtSet[]
364
+ zones?: readonly VelocityZoneBandProp[]
365
+ }) {
352
366
  return (
353
367
  <View style={{ gap: 10 }} testID="exercise-detail-page-vbt-breakdown">
354
368
  {sets.map((set) => {
@@ -373,7 +387,7 @@ function VbtBreakdown({ sets }: { sets: ExerciseVbtSet[] }) {
373
387
  {set.label}
374
388
  </Text>
375
389
  <View className="flex-1" style={{ height: 8, justifyContent: 'center' }}>
376
- <VelocityStrip velocities={set.velocities} variant="mini" />
390
+ <VelocityStrip velocities={set.velocities} zones={zones} variant="mini" />
377
391
  </View>
378
392
  <Text
379
393
  className="text-text-tertiary"
@@ -614,7 +628,7 @@ export function ExerciseDetailPage({
614
628
  </SectionCard>
615
629
  <VbtSummaryRow summary={vbtSummary} />
616
630
  <SectionCard title="Velocity Breakdown" testID="exercise-detail-page-velocity">
617
- <VbtBreakdown sets={vbt.sets} />
631
+ <VbtBreakdown sets={vbt.sets} zones={vbt.zones} />
618
632
  </SectionCard>
619
633
  </View>
620
634
  )}
@@ -2,6 +2,7 @@
2
2
  import React from 'react'
3
3
  import { View, Text, Pressable, TextInput } from 'react-native'
4
4
  import { resolveColor } from '../../../theme/resolve-color'
5
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
5
6
 
6
7
  export interface InputBarProps {
7
8
  exerciseName: string
@@ -17,7 +18,7 @@ export interface InputBarProps {
17
18
  visible: boolean
18
19
  }
19
20
 
20
- const BRAND_PRIMARY = '#FF7900'
21
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
21
22
  const INPUT_CLASSNAME = 'bg-surface-raised border-border-strong text-text-primary'
22
23
 
23
24
  const inputStyle = {