@titan-design/react-ui 0.2.7 → 0.4.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 (92) hide show
  1. package/dist/bodymap-BhG55xHM.d.mts +318 -0
  2. package/dist/bodymap-BhG55xHM.d.ts +318 -0
  3. package/dist/bodymap.d.mts +3 -0
  4. package/dist/bodymap.d.ts +3 -0
  5. package/dist/bodymap.js +3014 -0
  6. package/dist/bodymap.js.map +1 -0
  7. package/dist/bodymap.mjs +1088 -0
  8. package/dist/bodymap.mjs.map +1 -0
  9. package/dist/chunk-2SISKTWM.mjs +1027 -0
  10. package/dist/chunk-2SISKTWM.mjs.map +1 -0
  11. package/dist/{chunk-5SSKGS6E.mjs → chunk-7XPB4NAO.mjs} +124 -4
  12. package/dist/chunk-7XPB4NAO.mjs.map +1 -0
  13. package/dist/{chunk-3VLOBS6O.mjs → chunk-SNVKL5WZ.mjs} +42 -5
  14. package/dist/chunk-SNVKL5WZ.mjs.map +1 -0
  15. package/dist/index.d.mts +416 -250
  16. package/dist/index.d.ts +416 -250
  17. package/dist/index.js +1567 -1123
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +1491 -2201
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/theme/index.d.mts +288 -2
  22. package/dist/theme/index.d.ts +288 -2
  23. package/dist/theme/index.js +159 -2
  24. package/dist/theme/index.js.map +1 -1
  25. package/dist/theme/index.mjs +2 -2
  26. package/dist/theme/tokens-css.d.mts +5 -3
  27. package/dist/theme/tokens-css.d.ts +5 -3
  28. package/dist/theme/tokens-css.js +318 -9
  29. package/dist/theme/tokens-css.js.map +1 -1
  30. package/dist/theme/tokens-css.mjs +1 -1
  31. package/dist/theme/tokens-css.mjs.map +1 -1
  32. package/package.json +10 -1
  33. package/src/bodymap.ts +37 -0
  34. package/src/components/custom/Table/Table.test.tsx +27 -0
  35. package/src/components/custom/Table/Table.tsx +33 -17
  36. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +118 -0
  37. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +171 -0
  38. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +442 -0
  39. package/src/components/custom/Workout/BodyMap.tsx +7 -9
  40. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +25 -29
  41. package/src/components/custom/Workout/CapacityBandChart.tsx +2 -3
  42. package/src/components/custom/Workout/ExerciseCard.test.tsx +24 -0
  43. package/src/components/custom/Workout/ExerciseCard.tsx +24 -26
  44. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +225 -0
  45. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +128 -0
  46. package/src/components/custom/Workout/ExerciseDetailPage.tsx +624 -0
  47. package/src/components/custom/Workout/InputBar.tsx +10 -12
  48. package/src/components/custom/Workout/MesoCard.tsx +2 -4
  49. package/src/components/custom/Workout/MesoProgressBar.test.tsx +12 -0
  50. package/src/components/custom/Workout/MesoProgressBar.tsx +5 -1
  51. package/src/components/custom/Workout/MesoStatusCard.tsx +17 -15
  52. package/src/components/custom/Workout/MuscleGroupChip.tsx +2 -4
  53. package/src/components/custom/Workout/PlaceholderStrip.tsx +2 -4
  54. package/src/components/custom/Workout/PrHistoryModal.tsx +12 -14
  55. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +257 -0
  56. package/src/components/custom/Workout/ProgramPlanningPage.test.tsx +142 -0
  57. package/src/components/custom/Workout/ProgramPlanningPage.tsx +418 -0
  58. package/src/components/custom/Workout/ReadinessCheck.tsx +13 -12
  59. package/src/components/custom/Workout/RestTimer.test.tsx +10 -0
  60. package/src/components/custom/Workout/RestTimer.tsx +12 -9
  61. package/src/components/custom/Workout/SetRow.tsx +16 -19
  62. package/src/components/custom/Workout/StatusDot.tsx +1 -0
  63. package/src/components/custom/Workout/StrengthTrendChart.tsx +15 -17
  64. package/src/components/custom/Workout/TempoBar.stories.tsx +95 -0
  65. package/src/components/custom/Workout/TempoBar.test.tsx +103 -0
  66. package/src/components/custom/Workout/TempoBar.tsx +215 -0
  67. package/src/components/custom/Workout/TempoDisplay.tsx +3 -1
  68. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +212 -0
  69. package/src/components/custom/Workout/TrainingStatusPage.test.tsx +131 -0
  70. package/src/components/custom/Workout/TrainingStatusPage.tsx +312 -0
  71. package/src/components/custom/Workout/VelocityStrip.test.tsx +10 -0
  72. package/src/components/custom/Workout/VelocityStrip.tsx +19 -16
  73. package/src/components/custom/Workout/WeekRow.tsx +2 -1
  74. package/src/components/custom/Workout/WorkoutCard.tsx +3 -6
  75. package/src/components/custom/Workout/index.ts +80 -32
  76. package/src/test/nativewind-stub.ts +13 -0
  77. package/src/theme/ThemeProvider.test.tsx +24 -0
  78. package/src/theme/ThemeProvider.tsx +50 -0
  79. package/src/theme/config.completeness.test.ts +77 -0
  80. package/src/theme/config.ts +151 -0
  81. package/src/theme/index.ts +2 -0
  82. package/src/theme/manifest/design-freeze.test.ts +155 -0
  83. package/src/theme/manifest/extract-css-properties.fixture.html +29 -0
  84. package/src/theme/manifest/extract-css-properties.test.ts +112 -0
  85. package/src/theme/resolve-color.ts +19 -0
  86. package/src/theme/tokens-css.ts +5 -3
  87. package/src/theme/workout-tokens.ts +12 -14
  88. package/src/utils/index.ts +10 -0
  89. package/src/utils/workout-format.test.ts +81 -0
  90. package/src/utils/workout-format.ts +83 -0
  91. package/dist/chunk-3VLOBS6O.mjs.map +0 -1
  92. package/dist/chunk-5SSKGS6E.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import React from 'react'
3
3
  import { View, Text, Pressable, TextInput } from 'react-native'
4
- import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
4
+ import { resolveColor } from '../../../theme/resolve-color'
5
5
 
6
6
  export interface InputBarProps {
7
7
  exerciseName: string
@@ -18,19 +18,15 @@ export interface InputBarProps {
18
18
  }
19
19
 
20
20
  const BRAND_PRIMARY = '#FF7900'
21
- const TEXT_PRIMARY = '#F3F4F6'
22
- const TEXT_TERTIARY = '#6B7280'
21
+ const INPUT_CLASSNAME = 'bg-surface-raised border-border-strong text-text-primary'
23
22
 
24
23
  const inputStyle = {
25
24
  fontSize: 14,
26
25
  fontWeight: '600' as const,
27
26
  fontFamily: 'Inter, sans-serif',
28
- backgroundColor: WORKOUT_TOKENS.surface.raised,
29
27
  borderWidth: 1,
30
- borderColor: WORKOUT_TOKENS.border.strong,
31
28
  borderRadius: 6,
32
29
  textAlign: 'center' as const,
33
- color: TEXT_PRIMARY,
34
30
  paddingVertical: 5,
35
31
  paddingHorizontal: 2,
36
32
  }
@@ -54,11 +50,11 @@ export function InputBar({
54
50
 
55
51
  return (
56
52
  <View
53
+ className="bg-surface-elevated"
57
54
  style={{
58
55
  width: '100%',
59
- backgroundColor: WORKOUT_TOKENS.surface.elevated,
60
56
  borderTopWidth: 1,
61
- borderTopColor: WORKOUT_TOKENS.border.default,
57
+ borderTopColor: resolveColor('border-default'),
62
58
  paddingTop: 10,
63
59
  paddingHorizontal: 16,
64
60
  paddingBottom: 12,
@@ -71,11 +67,11 @@ export function InputBar({
71
67
  >
72
68
  <View style={{ minWidth: 80, flexDirection: 'column' }}>
73
69
  <Text
70
+ className="text-text-primary"
74
71
  style={{
75
72
  fontSize: 12,
76
73
  fontWeight: '700',
77
74
  fontFamily: '"Space Grotesk", sans-serif',
78
- color: TEXT_PRIMARY,
79
75
  }}
80
76
  testID="input-bar-exercise-name"
81
77
  numberOfLines={1}
@@ -83,10 +79,10 @@ export function InputBar({
83
79
  {exerciseName}
84
80
  </Text>
85
81
  <Text
82
+ className="text-text-tertiary"
86
83
  style={{
87
84
  fontSize: 10,
88
85
  fontFamily: 'Inter, sans-serif',
89
- color: TEXT_TERTIARY,
90
86
  }}
91
87
  testID="input-bar-set-info"
92
88
  >
@@ -98,16 +94,17 @@ export function InputBar({
98
94
  <TextInput
99
95
  value={reps}
100
96
  onChangeText={onRepsChange}
97
+ className={INPUT_CLASSNAME}
101
98
  style={{ ...inputStyle, width: 36 }}
102
99
  accessibilityLabel="Reps"
103
100
  testID="input-bar-reps"
104
101
  keyboardType="numeric"
105
102
  />
106
103
  <Text
104
+ className="text-text-tertiary"
107
105
  style={{
108
106
  fontSize: 12,
109
107
  fontFamily: 'Inter, sans-serif',
110
- color: TEXT_TERTIARY,
111
108
  }}
112
109
  >
113
110
  {'\u00D7'}
@@ -115,17 +112,18 @@ export function InputBar({
115
112
  <TextInput
116
113
  value={weight}
117
114
  onChangeText={onWeightChange}
115
+ className={INPUT_CLASSNAME}
118
116
  style={{ ...inputStyle, width: 52 }}
119
117
  accessibilityLabel={`Weight in ${unit}`}
120
118
  testID="input-bar-weight"
121
119
  keyboardType="numeric"
122
120
  />
123
121
  <Text
122
+ className="text-text-tertiary"
124
123
  style={{
125
124
  fontSize: 12,
126
125
  fontWeight: '500',
127
126
  fontFamily: 'Inter, sans-serif',
128
- color: TEXT_TERTIARY,
129
127
  }}
130
128
  testID="input-bar-unit"
131
129
  >
@@ -9,8 +9,6 @@ const BRAND_PRIMARY = '#FF7900'
9
9
  const BRAND_PRIMARY_DARK = '#C45E00'
10
10
  const BRAND_PRIMARY_LIGHT = '#FFB066'
11
11
  const BORDER_DEFAULT = '#1F1F1F'
12
- const TEXT_PRIMARY = '#F3F4F6'
13
- const TEXT_SECONDARY = '#9CA3AF'
14
12
 
15
13
  /** Gradient stops for the 3px top accent: dark -> primary -> light. */
16
14
  const ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY, BRAND_PRIMARY_LIGHT]
@@ -117,11 +115,11 @@ export function MesoCard({
117
115
  <View style={{ paddingHorizontal: 14, paddingTop: 12, paddingBottom: 10 }} testID="meso-card-body">
118
116
  <View className="flex-row items-center" style={{ gap: 8 }} testID="meso-card-header">
119
117
  <Text
118
+ className="text-text-primary"
120
119
  style={{
121
120
  fontSize: 15,
122
121
  fontFamily: '"Space Grotesk", sans-serif',
123
122
  fontWeight: '700',
124
- color: TEXT_PRIMARY,
125
123
  }}
126
124
  testID="meso-card-name"
127
125
  >
@@ -133,11 +131,11 @@ export function MesoCard({
133
131
  </View>
134
132
 
135
133
  <Text
134
+ className="text-text-secondary"
136
135
  style={{
137
136
  marginTop: 4,
138
137
  fontSize: 12,
139
138
  fontFamily: 'Inter, sans-serif',
140
- color: TEXT_SECONDARY,
141
139
  }}
142
140
  testID="meso-card-subheader"
143
141
  >
@@ -139,3 +139,15 @@ describe('MesoProgressBar', () => {
139
139
  })
140
140
  })
141
141
  })
142
+
143
+ describe('MesoProgressBar invalid weekCount (flex guard)', () => {
144
+ it('clamps zero and negative weekCount to a valid flex of 1', () => {
145
+ const edgeMesos: Meso[] = [
146
+ { id: 'z', name: 'Zero', weekCount: 0, status: 'upcoming' },
147
+ { id: 'n', name: 'Neg', weekCount: -3, status: 'upcoming' },
148
+ ]
149
+ render(<MesoProgressBar mesos={edgeMesos} activeMesoId={null} onMesoPress={vi.fn()} />)
150
+ expect(screen.getByTestId('meso-segment-z')).toHaveStyle({ flex: 1 })
151
+ expect(screen.getByTestId('meso-segment-n')).toHaveStyle({ flex: 1 })
152
+ })
153
+ })
@@ -66,9 +66,13 @@ function MesoSegment({ meso, isActive, onPress }: SegmentProps) {
66
66
  ? clampProgress(meso.currentWeek, meso.weekCount)
67
67
  : 0
68
68
 
69
+ // weekCount drives the segment's flex width; guard 0 / negative so flex stays
70
+ // a valid positive value and the segment never collapses out of the layout.
71
+ const segmentFlex = Math.max(1, meso.weekCount)
72
+
69
73
  return (
70
74
  <Pressable
71
- style={{ flex: meso.weekCount }}
75
+ style={{ flex: segmentFlex }}
72
76
  onPress={() => onPress(meso.id)}
73
77
  onPressIn={handlePressIn}
74
78
  onPressOut={handlePressOut}
@@ -3,13 +3,11 @@ import { Fragment, type ReactNode } from 'react'
3
3
  import { View, Text, type ViewProps, type ViewStyle } from 'react-native'
4
4
  import { Card } from '../../ui/card'
5
5
  import { StatusDot } from './StatusDot'
6
+ import { resolveColor } from '../../../theme/resolve-color'
6
7
 
7
8
  const BRAND_PRIMARY = '#FF7900'
8
9
  const BRAND_PRIMARY_DARK = '#C45E00'
9
10
  const BRAND_PRIMARY_LIGHT = '#FFB066'
10
- const TEXT_PRIMARY = '#F3F4F6'
11
- const TEXT_SECONDARY = '#9CA3AF'
12
- const TEXT_TERTIARY = '#6B7280'
13
11
 
14
12
  const SUCCESS = '#14B8A6'
15
13
  const WARNING = '#FFB020'
@@ -18,9 +16,12 @@ const ERROR = '#D14343'
18
16
  /** Gradient stops for the 3px top accent: dark -> primary -> light (matches MesoCard). */
19
17
  const ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY, BRAND_PRIMARY_LIGHT]
20
18
 
21
- /** Card surface gradient: surface-elevated -> near-black at 135deg. */
22
- const CARD_GRADIENT =
23
- 'linear-gradient(135deg, #191919 0%, rgba(25,25,25,0.95) 100%)'
19
+ /**
20
+ * Card surface gradient: elevated -> raised at 135deg. Uses resolved dark-theme
21
+ * hexes (a gradient string can't carry a className token, and var() renders
22
+ * black on native).
23
+ */
24
+ const CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor('surface-elevated')} 0%, ${resolveColor('surface-raised')} 100%)`
24
25
 
25
26
  /** Gauge track gradient (teal -> amber -> red) at 0.25 alpha. */
26
27
  const GAUGE_GRADIENT =
@@ -118,7 +119,8 @@ function renderCoachingText(text: string, highlights?: string[]): ReactNode {
118
119
  highlights.includes(part) ? (
119
120
  <Text
120
121
  key={`${part}-${index}`}
121
- style={{ fontWeight: '700', color: TEXT_PRIMARY }}
122
+ className="text-text-primary"
123
+ style={{ fontWeight: '700' }}
122
124
  >
123
125
  {part}
124
126
  </Text>
@@ -166,24 +168,24 @@ function MetricCell({ metric }: { metric: MesoStatusMetric }) {
166
168
  return (
167
169
  <View style={{ width: '48%' }} testID="meso-status-card-metric">
168
170
  <Text
171
+ className="text-text-tertiary"
169
172
  style={{
170
173
  fontSize: 10,
171
174
  fontFamily: 'Inter, sans-serif',
172
175
  fontWeight: '600',
173
176
  letterSpacing: 0.5,
174
177
  textTransform: 'uppercase',
175
- color: TEXT_TERTIARY,
176
178
  }}
177
179
  >
178
180
  {metric.label}
179
181
  </Text>
180
182
  <Text
183
+ className="text-text-primary"
181
184
  style={{
182
185
  marginTop: 2,
183
186
  fontSize: 13,
184
187
  fontFamily: 'Inter, sans-serif',
185
188
  fontWeight: '600',
186
- color: TEXT_PRIMARY,
187
189
  }}
188
190
  >
189
191
  {metric.value}
@@ -212,24 +214,24 @@ function Gauge({ gauge }: { gauge: MesoStatusGauge }) {
212
214
  accessibilityElementsHidden
213
215
  >
214
216
  <Text
217
+ className="text-text-secondary"
215
218
  style={{
216
219
  fontSize: 10,
217
220
  fontFamily: 'Inter, sans-serif',
218
221
  fontWeight: '500',
219
222
  letterSpacing: 0.5,
220
223
  textTransform: 'uppercase',
221
- color: TEXT_SECONDARY,
222
224
  }}
223
225
  >
224
226
  {gauge.label}
225
227
  </Text>
226
228
  {gauge.sublabel != null && (
227
229
  <Text
230
+ className="text-text-tertiary"
228
231
  style={{
229
232
  fontSize: 10,
230
233
  fontFamily: 'Inter, sans-serif',
231
234
  fontWeight: '500',
232
- color: TEXT_TERTIARY,
233
235
  }}
234
236
  >
235
237
  {gauge.sublabel}
@@ -261,6 +263,7 @@ function Gauge({ gauge }: { gauge: MesoStatusGauge }) {
261
263
  testID="meso-status-card-gauge-center"
262
264
  />
263
265
  <View
266
+ className="border-text-primary"
264
267
  style={{
265
268
  position: 'absolute',
266
269
  left: `${percentage}%`,
@@ -270,7 +273,6 @@ function Gauge({ gauge }: { gauge: MesoStatusGauge }) {
270
273
  height: 14,
271
274
  borderRadius: 9999,
272
275
  borderWidth: 2,
273
- borderColor: TEXT_PRIMARY,
274
276
  backgroundColor: markerColor,
275
277
  boxShadow: '0 0 4px rgba(0,0,0,0.5)',
276
278
  }}
@@ -349,12 +351,12 @@ export function MesoStatusCard({
349
351
  style={{ gap: 8 }}
350
352
  >
351
353
  <Text
354
+ className="text-text-primary"
352
355
  style={{
353
356
  flexShrink: 1,
354
357
  fontSize: 15,
355
358
  fontFamily: '"Space Grotesk", sans-serif',
356
359
  fontWeight: '700',
357
- color: TEXT_PRIMARY,
358
360
  }}
359
361
  testID="meso-status-card-name"
360
362
  >
@@ -363,11 +365,11 @@ export function MesoStatusCard({
363
365
  <StatusPill badge={statusBadge} />
364
366
  </View>
365
367
  <Text
368
+ className="text-text-secondary"
366
369
  style={{
367
370
  marginTop: 4,
368
371
  fontSize: 12,
369
372
  fontFamily: 'Inter, sans-serif',
370
- color: TEXT_SECONDARY,
371
373
  }}
372
374
  testID="meso-status-card-subtitle"
373
375
  >
@@ -408,11 +410,11 @@ export function MesoStatusCard({
408
410
  testID="meso-status-card-coaching"
409
411
  >
410
412
  <Text
413
+ className="text-text-secondary"
411
414
  style={{
412
415
  fontSize: 12,
413
416
  lineHeight: 17,
414
417
  fontFamily: 'Inter, sans-serif',
415
- color: TEXT_SECONDARY,
416
418
  }}
417
419
  >
418
420
  {renderCoachingText(coaching.text, coaching.highlights)}
@@ -34,16 +34,14 @@ export function MuscleGroupChip({
34
34
 
35
35
  const content = (
36
36
  <View
37
- className={className}
37
+ className={['bg-surface-raised border-border', className].filter(Boolean).join(' ')}
38
38
  style={{
39
39
  flexDirection: 'row',
40
40
  alignItems: 'center',
41
41
  borderRadius: 9999,
42
42
  paddingHorizontal: 9,
43
43
  paddingVertical: 3,
44
- backgroundColor: 'var(--color-surface-raised)',
45
44
  borderWidth: 1,
46
- borderColor: 'var(--color-border-default)',
47
45
  }}
48
46
  accessibilityLabel={onPress ? undefined : `${name}, volume status: ${statusLabel}`}
49
47
  testID="muscle-group-chip"
@@ -62,11 +60,11 @@ export function MuscleGroupChip({
62
60
  testID="muscle-group-chip-dot"
63
61
  />
64
62
  <Text
63
+ className="text-text-secondary"
65
64
  style={{
66
65
  fontFamily: 'Inter, sans-serif',
67
66
  fontWeight: '500',
68
67
  fontSize: 11,
69
- color: 'var(--color-text-secondary)',
70
68
  }}
71
69
  accessibilityElementsHidden
72
70
  >
@@ -1,7 +1,6 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { View, type ViewProps } from 'react-native'
3
3
  import { cn } from '../../../utils/cn'
4
- import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
5
4
 
6
5
  export interface PlaceholderStripProps extends ViewProps {
7
6
  width?: number | string
@@ -19,14 +18,13 @@ function SingleStrip({
19
18
  }: Omit<PlaceholderStripProps, 'mode' | 'segments'>) {
20
19
  return (
21
20
  <View
22
- className={cn(className)}
21
+ className={cn('border-border-strong', className)}
23
22
  style={[
24
23
  {
25
24
  height: 3,
26
25
  backgroundColor: 'transparent',
27
26
  borderWidth: 1,
28
27
  borderStyle: 'dashed',
29
- borderColor: WORKOUT_TOKENS.border.strong,
30
28
  borderRadius: 1,
31
29
  opacity: 0.5,
32
30
  },
@@ -61,13 +59,13 @@ function SegmentedStrip({
61
59
  {Array.from({ length: segments }, (_, i) => (
62
60
  <View
63
61
  key={i}
62
+ className="border-border-strong"
64
63
  style={{
65
64
  flex: 1,
66
65
  height: 3,
67
66
  backgroundColor: 'transparent',
68
67
  borderWidth: 1,
69
68
  borderStyle: 'dashed',
70
- borderColor: WORKOUT_TOKENS.border.strong,
71
69
  borderRadius: 1,
72
70
  minWidth: 4,
73
71
  }}
@@ -2,15 +2,14 @@
2
2
  import { View, Text, Pressable, type ViewProps } from 'react-native'
3
3
  import { Star } from 'lucide-react'
4
4
  import { Drawer, DrawerBody } from '../../ui/drawer'
5
+ import { resolveColor } from '../../../theme/resolve-color'
5
6
 
6
7
  const BRAND_PRIMARY = '#FF7900'
7
8
  const RECENT_BORDER = 'rgba(255,176,32,0.3)'
8
9
  const RECENT_GLOW = '0 0 12px rgba(255,176,32,0.06)'
9
- const SURFACE_RAISED = '#1C1C1C'
10
- const BORDER_DEFAULT = '#1F1F1F'
11
- const TEXT_PRIMARY = '#F3F4F6'
12
- const TEXT_SECONDARY = '#9CA3AF'
13
- const TEXT_TERTIARY = '#6B7280'
10
+ // Native-safe fallback for the conditional row border (recent uses a computed
11
+ // rgba, so className can't express both branches).
12
+ const BORDER_DEFAULT = resolveColor('border-default')
14
13
 
15
14
  export type PrRecordType = 'e1rm' | 'weight' | 'reps' | 'volume' | 'velocity'
16
15
 
@@ -72,14 +71,13 @@ function PrRecordRow({ record, index }: { record: PrRecord; index: number }) {
72
71
  <View
73
72
  accessibilityRole="text"
74
73
  accessibilityLabel={a11yLabel}
75
- className="flex-row items-center"
74
+ className="flex-row items-center bg-surface-raised"
76
75
  style={{
77
76
  gap: 10,
78
77
  paddingVertical: 10,
79
78
  paddingHorizontal: 12,
80
79
  marginBottom: 8,
81
80
  borderRadius: 8,
82
- backgroundColor: SURFACE_RAISED,
83
81
  borderWidth: 1,
84
82
  borderColor: record.isRecent ? RECENT_BORDER : BORDER_DEFAULT,
85
83
  ...(record.isRecent ? { boxShadow: RECENT_GLOW } : {}),
@@ -88,35 +86,35 @@ function PrRecordRow({ record, index }: { record: PrRecord; index: number }) {
88
86
  >
89
87
  <Star size={16} color={BRAND_PRIMARY} fill={BRAND_PRIMARY} strokeWidth={2} />
90
88
  <Text
89
+ className="text-text-secondary"
91
90
  style={{
92
91
  flex: 1,
93
92
  fontSize: 13,
94
93
  fontFamily: 'Inter, sans-serif',
95
94
  fontWeight: '600',
96
- color: TEXT_SECONDARY,
97
95
  }}
98
96
  testID={`pr-history-modal-record-${index}-type`}
99
97
  >
100
98
  {label}
101
99
  </Text>
102
100
  <Text
101
+ className="text-text-primary"
103
102
  style={{
104
103
  fontSize: 14,
105
104
  fontFamily: '"Space Grotesk", sans-serif',
106
105
  fontWeight: '700',
107
- color: TEXT_PRIMARY,
108
106
  }}
109
107
  testID={`pr-history-modal-record-${index}-value`}
110
108
  >
111
109
  {valueText}
112
110
  </Text>
113
111
  <Text
112
+ className="text-text-tertiary"
114
113
  style={{
115
114
  minWidth: 64,
116
115
  textAlign: 'right',
117
116
  fontSize: 11,
118
117
  fontFamily: 'Inter, sans-serif',
119
- color: TEXT_TERTIARY,
120
118
  }}
121
119
  testID={`pr-history-modal-record-${index}-date`}
122
120
  >
@@ -173,13 +171,13 @@ export function PrHistoryModal({
173
171
  >
174
172
  <View
175
173
  accessibilityElementsHidden
174
+ className="bg-border"
176
175
  style={{
177
176
  alignSelf: 'center',
178
177
  width: 40,
179
178
  height: 4,
180
179
  borderRadius: 2,
181
180
  marginTop: 8,
182
- backgroundColor: BORDER_DEFAULT,
183
181
  }}
184
182
  testID="pr-history-modal-handle"
185
183
  />
@@ -191,11 +189,11 @@ export function PrHistoryModal({
191
189
  >
192
190
  <Text
193
191
  accessibilityRole="header"
192
+ className="text-text-primary"
194
193
  style={{
195
194
  fontSize: 16,
196
195
  fontFamily: '"Space Grotesk", sans-serif',
197
196
  fontWeight: '700',
198
- color: TEXT_PRIMARY,
199
197
  }}
200
198
  testID="pr-history-modal-title"
201
199
  >
@@ -209,7 +207,7 @@ export function PrHistoryModal({
209
207
  style={{ padding: 4, margin: -4 }}
210
208
  testID="pr-history-modal-close"
211
209
  >
212
- <Text style={{ fontSize: 22, lineHeight: 22, color: TEXT_SECONDARY }}>
210
+ <Text className="text-text-secondary" style={{ fontSize: 22, lineHeight: 22 }}>
213
211
  {'×'}
214
212
  </Text>
215
213
  </Pressable>
@@ -219,11 +217,11 @@ export function PrHistoryModal({
219
217
  {records.length === 0 ? (
220
218
  <View style={{ paddingVertical: 24 }} testID="pr-history-modal-empty">
221
219
  <Text
220
+ className="text-text-tertiary"
222
221
  style={{
223
222
  textAlign: 'center',
224
223
  fontSize: 13,
225
224
  fontFamily: 'Inter, sans-serif',
226
- color: TEXT_TERTIARY,
227
225
  }}
228
226
  >
229
227
  No personal records yet