@titan-design/react-ui 0.5.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 (116) hide show
  1. package/dist/bodymap.js +232 -185
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +232 -185
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +164 -8
  6. package/dist/index.d.ts +164 -8
  7. package/dist/index.js +835 -342
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +816 -343
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/pages.js +570 -522
  12. package/dist/pages.js.map +1 -1
  13. package/dist/pages.mjs +570 -522
  14. package/dist/pages.mjs.map +1 -1
  15. package/dist/theme/index.d.mts +461 -199
  16. package/dist/theme/index.d.ts +461 -199
  17. package/dist/theme/index.js +311 -97
  18. package/dist/theme/index.js.map +1 -1
  19. package/dist/theme/index.mjs +303 -98
  20. package/dist/theme/index.mjs.map +1 -1
  21. package/dist/theme/tokens-css.js +148 -139
  22. package/dist/theme/tokens-css.js.map +1 -1
  23. package/dist/theme/tokens-css.mjs +148 -139
  24. package/dist/theme/tokens-css.mjs.map +1 -1
  25. package/package.json +3 -1
  26. package/src/components/custom/DateTime/DateTime.stories.tsx +82 -6
  27. package/src/components/custom/DateTime/DateTime.test.tsx +29 -3
  28. package/src/components/custom/DateTime/DateTime.tsx +55 -15
  29. package/src/components/custom/Gauge/Gauge.test.tsx +2 -2
  30. package/src/components/custom/Gauge/Gauge.tsx +6 -3
  31. package/src/components/custom/Scatter/Scatter.tsx +3 -5
  32. package/src/components/custom/Treemap/Treemap.tsx +3 -5
  33. package/src/components/custom/Typography/Typography.stories.tsx +45 -27
  34. package/src/components/custom/Typography/Typography.test.tsx +13 -3
  35. package/src/components/custom/Typography/Typography.tsx +5 -0
  36. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +2 -1
  37. package/src/components/custom/Workout/BodyMap.tsx +6 -3
  38. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +6 -3
  39. package/src/components/custom/Workout/CapacityBandChart.test.tsx +2 -2
  40. package/src/components/custom/Workout/CapacityBandChart.tsx +9 -6
  41. package/src/components/custom/Workout/DeviationBar.test.tsx +1 -1
  42. package/src/components/custom/Workout/DeviationBar.tsx +7 -4
  43. package/src/components/custom/Workout/ExerciseDetailPage.tsx +2 -1
  44. package/src/components/custom/Workout/InputBar.tsx +2 -1
  45. package/src/components/custom/Workout/IntensityBar.test.tsx +8 -8
  46. package/src/components/custom/Workout/IntensityBar.tsx +10 -6
  47. package/src/components/custom/Workout/MesoCard.tsx +8 -3
  48. package/src/components/custom/Workout/MesoProgressBar.test.tsx +1 -1
  49. package/src/components/custom/Workout/MesoProgressBar.tsx +3 -2
  50. package/src/components/custom/Workout/MesoStatusCard.tsx +20 -13
  51. package/src/components/custom/Workout/MuscleGroupChip.tsx +7 -4
  52. package/src/components/custom/Workout/PrBadge.tsx +5 -2
  53. package/src/components/custom/Workout/PrHistoryModal.tsx +4 -3
  54. package/src/components/custom/Workout/ProgramPlanningPage.tsx +2 -1
  55. package/src/components/custom/Workout/ReadinessCheck.tsx +7 -4
  56. package/src/components/custom/Workout/RestTimer.tsx +2 -1
  57. package/src/components/custom/Workout/SetRow.tsx +2 -1
  58. package/src/components/custom/Workout/Sparkline.tsx +2 -1
  59. package/src/components/custom/Workout/StatusDot.test.tsx +2 -2
  60. package/src/components/custom/Workout/StatusDot.tsx +12 -9
  61. package/src/components/custom/Workout/StrengthTrendChart.tsx +9 -6
  62. package/src/components/custom/Workout/SupersetWrapper.tsx +2 -1
  63. package/src/components/custom/Workout/TempoDisplay.tsx +6 -3
  64. package/src/components/custom/Workout/VelocityStrip.test.tsx +3 -3
  65. package/src/components/custom/Workout/WeekRow.tsx +3 -2
  66. package/src/components/custom/Workout/WeightBadge.tsx +6 -2
  67. package/src/components/custom/Workout/WorkoutCard.tsx +5 -2
  68. package/src/components/custom/Workout/WorkoutPill.tsx +12 -8
  69. package/src/components/custom/Workout/icons.tsx +6 -67
  70. package/src/components/icons/SvgIcon.tsx +50 -0
  71. package/src/components/icons/icons.stories.tsx +57 -0
  72. package/src/components/icons/icons.test.tsx +43 -0
  73. package/src/components/icons/icons.tsx +45 -0
  74. package/src/components/icons/index.ts +4 -0
  75. package/src/components/index.ts +6 -0
  76. package/src/components/shell/BrandLockup.stories.tsx +27 -0
  77. package/src/components/shell/BrandLockup.test.tsx +23 -0
  78. package/src/components/shell/BrandLockup.tsx +47 -0
  79. package/src/components/shell/DeviceIndicator.stories.tsx +28 -0
  80. package/src/components/shell/DeviceIndicator.test.tsx +26 -0
  81. package/src/components/shell/DeviceIndicator.tsx +52 -0
  82. package/src/components/shell/DeviceMenu.stories.tsx +47 -0
  83. package/src/components/shell/DeviceMenu.test.tsx +40 -0
  84. package/src/components/shell/DeviceMenu.tsx +65 -0
  85. package/src/components/shell/DeviceRow.stories.tsx +38 -0
  86. package/src/components/shell/DeviceRow.test.tsx +37 -0
  87. package/src/components/shell/DeviceRow.tsx +58 -0
  88. package/src/components/shell/README.md +86 -0
  89. package/src/components/shell/SessionStatePill.stories.tsx +27 -0
  90. package/src/components/shell/SessionStatePill.test.tsx +25 -0
  91. package/src/components/shell/SessionStatePill.tsx +46 -0
  92. package/src/components/shell/TopBar.stories.tsx +49 -0
  93. package/src/components/shell/TopBar.test.tsx +36 -0
  94. package/src/components/shell/TopBar.tsx +89 -0
  95. package/src/components/shell/index.ts +8 -0
  96. package/src/components/ui/indicator/Indicator.stories.tsx +37 -1
  97. package/src/components/ui/indicator/Indicator.test.tsx +19 -1
  98. package/src/components/ui/indicator/Indicator.tsx +45 -6
  99. package/src/components/ui/indicator/index.ts +1 -1
  100. package/src/components/ui/spinner/Spinner.tsx +3 -2
  101. package/src/components/ui/toolbar-button/ToolbarButton.tsx +2 -1
  102. package/src/theme/ColorSystem.stories.tsx +323 -0
  103. package/src/theme/Colors.stories.tsx +69 -1
  104. package/src/theme/barrel.ts +1 -0
  105. package/src/theme/config.ts +16 -3
  106. package/src/theme/extracted-colors-dataviz.ts +21 -0
  107. package/src/theme/extracted-colors-ui.ts +20 -0
  108. package/src/theme/global.css +64 -52
  109. package/src/theme/gradients.test.ts +25 -0
  110. package/src/theme/gradients.ts +36 -0
  111. package/src/theme/tokens/primitives.test.ts +232 -0
  112. package/src/theme/tokens/primitives.ts +247 -0
  113. package/src/theme/tokens/semantic.ts +93 -81
  114. package/src/theme/workout-tokens.ts +22 -21
  115. package/src/utils/avatar-color.ts +3 -3
  116. package/tailwind.config.js +10 -3
@@ -4,10 +4,15 @@ import { View, Text, Pressable, Animated, Easing, type ViewProps } from 'react-n
4
4
  import { Card } from '../../ui/card'
5
5
  import { Badge } from '../../ui/badge'
6
6
  import { WeekRow, type WeekRowProps } from './WeekRow'
7
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
8
+ import {
9
+ MESO_ACCENT_GRADIENT_DARK,
10
+ MESO_ACCENT_GRADIENT_LIGHT,
11
+ } from '../../../theme/extracted-colors-dataviz'
7
12
 
8
- const BRAND_PRIMARY = '#FF7900'
9
- const BRAND_PRIMARY_DARK = '#C45E00'
10
- const BRAND_PRIMARY_LIGHT = '#FFB066'
13
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
14
+ const BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK
15
+ const BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT
11
16
  const BORDER_DEFAULT = '#1F1F1F'
12
17
 
13
18
  /** Gradient stops for the 3px top accent: dark -> primary -> light. */
@@ -40,7 +40,7 @@ describe('MesoProgressBar', () => {
40
40
  it('applies completed color at 0.3 alpha', () => {
41
41
  render(<MesoProgressBar mesos={mesos} activeMesoId={null} onMesoPress={vi.fn()} />)
42
42
  expect(screen.getByTestId('meso-segment-inner-m1')).toHaveStyle({
43
- backgroundColor: 'rgba(20,184,166,0.3)',
43
+ backgroundColor: 'rgba(46,213,115,0.3)',
44
44
  })
45
45
  })
46
46
 
@@ -1,8 +1,9 @@
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, Pressable, Animated, type ViewProps } from 'react-native'
4
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
5
 
5
- const BRAND_PRIMARY = '#FF7900'
6
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
6
7
 
7
8
  export type MesoStatus = 'completed' | 'current' | 'upcoming'
8
9
 
@@ -26,7 +27,7 @@ export interface MesoProgressBarProps extends ViewProps {
26
27
  }
27
28
 
28
29
  const segmentBgColors: Record<MesoStatus, string> = {
29
- completed: 'rgba(20,184,166,0.3)',
30
+ completed: 'rgba(46,213,115,0.3)',
30
31
  current: 'rgba(255,121,0,0.5)',
31
32
  upcoming: 'rgba(255,255,255,0.06)',
32
33
  }
@@ -4,14 +4,21 @@ import { View, Text, type ViewProps, type ViewStyle } from 'react-native'
4
4
  import { Card } from '../../ui/card'
5
5
  import { StatusDot } from './StatusDot'
6
6
  import { resolveColor } from '../../../theme/resolve-color'
7
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
8
+ import {
9
+ MESO_ACCENT_GRADIENT_DARK,
10
+ MESO_ACCENT_GRADIENT_LIGHT,
11
+ } from '../../../theme/extracted-colors-dataviz'
7
12
 
8
- const BRAND_PRIMARY = '#FF7900'
9
- const BRAND_PRIMARY_DARK = '#C45E00'
10
- const BRAND_PRIMARY_LIGHT = '#FFB066'
13
+ const t = getSemanticColors('dark')
11
14
 
12
- const SUCCESS = '#14B8A6'
13
- const WARNING = '#FFB020'
14
- const ERROR = '#D14343'
15
+ const BRAND_PRIMARY = t['brand-primary']
16
+ const BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK
17
+ const BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT
18
+
19
+ const SUCCESS = t['status-success']
20
+ const WARNING = t['status-warning']
21
+ const ERROR = t['status-error']
15
22
 
16
23
  /** Gradient stops for the 3px top accent: dark -> primary -> light (matches MesoCard). */
17
24
  const ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY, BRAND_PRIMARY_LIGHT]
@@ -25,7 +32,7 @@ const CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor('surface-elevated'
25
32
 
26
33
  /** Gauge track gradient (teal -> amber -> red) at 0.25 alpha. */
27
34
  const GAUGE_GRADIENT =
28
- 'linear-gradient(90deg, rgba(20,184,166,0.25) 0%, rgba(255,176,32,0.25) 50%, rgba(209,67,67,0.25) 100%)'
35
+ 'linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(249,180,21,0.25) 50%, rgba(209,67,67,0.25) 100%)'
29
36
 
30
37
  export type MesoStatusBadgeVariant = 'success' | 'warning' | 'error'
31
38
 
@@ -33,8 +40,8 @@ const STATUS_VARIANTS: Record<
33
40
  MesoStatusBadgeVariant,
34
41
  { bg: string; border: string; text: string }
35
42
  > = {
36
- success: { bg: 'rgba(20,184,166,0.15)', border: 'rgba(20,184,166,0.3)', text: SUCCESS },
37
- warning: { bg: 'rgba(255,176,32,0.15)', border: 'rgba(255,176,32,0.3)', text: WARNING },
43
+ success: { bg: 'rgba(46,213,115,0.15)', border: 'rgba(46,213,115,0.3)', text: SUCCESS },
44
+ warning: { bg: 'rgba(249,180,21,0.15)', border: 'rgba(249,180,21,0.3)', text: WARNING },
38
45
  error: { bg: 'rgba(209,67,67,0.15)', border: 'rgba(209,67,67,0.25)', text: ERROR },
39
46
  }
40
47
 
@@ -400,9 +407,9 @@ export function MesoStatusCard({
400
407
  {coaching != null && (
401
408
  <View
402
409
  style={{
403
- backgroundColor: 'rgba(255,176,32,0.06)',
410
+ backgroundColor: 'rgba(249,180,21,0.06)',
404
411
  borderWidth: 1,
405
- borderColor: 'rgba(255,176,32,0.15)',
412
+ borderColor: 'rgba(249,180,21,0.15)',
406
413
  borderRadius: 8,
407
414
  paddingVertical: 10,
408
415
  paddingHorizontal: 12,
@@ -427,9 +434,9 @@ export function MesoStatusCard({
427
434
  className="flex-row items-center"
428
435
  style={{
429
436
  gap: 8,
430
- backgroundColor: 'rgba(20,184,166,0.06)',
437
+ backgroundColor: 'rgba(46,213,115,0.06)',
431
438
  borderWidth: 1,
432
- borderColor: 'rgba(20,184,166,0.2)',
439
+ borderColor: 'rgba(46,213,115,0.2)',
433
440
  borderRadius: 8,
434
441
  paddingVertical: 10,
435
442
  paddingHorizontal: 12,
@@ -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, Text, Pressable, type ViewProps } from 'react-native'
3
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
+
5
+ const t = getSemanticColors('dark')
3
6
 
4
7
  // Aliases for spec compatibility: under=behind, maintenance=ontrack, productive=target
5
8
  export type VolumeStatus = 'untrained' | 'behind' | 'ontrack' | 'target' | 'over'
@@ -13,10 +16,10 @@ export interface MuscleGroupChipProps extends ViewProps {
13
16
 
14
17
  const dotColorMap: Record<VolumeStatus, string> = {
15
18
  untrained: '#2C2C2C',
16
- behind: '#406D87',
17
- ontrack: '#14B8A6',
18
- target: '#FF7900',
19
- over: '#D14343',
19
+ behind: t['brand-secondary'],
20
+ ontrack: t['status-success'],
21
+ target: t['brand-primary'],
22
+ over: t['status-error'],
20
23
  }
21
24
 
22
25
  export function MuscleGroupChip({
@@ -3,6 +3,9 @@ import { useEffect, useState } from 'react'
3
3
  import { View, Text, Animated, Easing, type ViewProps } from 'react-native'
4
4
  import { StarIcon } from './icons'
5
5
  import { BaseBadge } from './BaseBadge'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
7
+
8
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
6
9
 
7
10
  export type PRType = 'e1rm' | 'weight' | 'reps' | 'volume' | 'velocity'
8
11
 
@@ -66,7 +69,7 @@ export function PrBadge({
66
69
  testID="pr-badge-star"
67
70
  {...props}
68
71
  >
69
- <StarIcon size={14} color="#FF7900" fill="#FF7900" strokeWidth={2} />
72
+ <StarIcon size={14} color={BRAND_PRIMARY} fill={BRAND_PRIMARY} strokeWidth={2} />
70
73
  </View>
71
74
  ) : (
72
75
  <BaseBadge
@@ -78,7 +81,7 @@ export function PrBadge({
78
81
  <Text
79
82
  style={{
80
83
  fontSize: 12,
81
- color: '#FF7900',
84
+ color: BRAND_PRIMARY,
82
85
  fontWeight: '700',
83
86
  fontFamily: 'Inter, sans-serif',
84
87
  }}
@@ -3,10 +3,11 @@ import { View, Text, Pressable, type ViewProps } from 'react-native'
3
3
  import { StarIcon } from './icons'
4
4
  import { Drawer, DrawerBody } from '../../ui/drawer'
5
5
  import { resolveColor } from '../../../theme/resolve-color'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
6
7
 
7
- const BRAND_PRIMARY = '#FF7900'
8
- const RECENT_BORDER = 'rgba(255,176,32,0.3)'
9
- const RECENT_GLOW = '0 0 12px rgba(255,176,32,0.06)'
8
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
9
+ const RECENT_BORDER = 'rgba(249,180,21,0.3)'
10
+ const RECENT_GLOW = '0 0 12px rgba(249,180,21,0.06)'
10
11
  // Native-safe fallback for the conditional row border (recent uses a computed
11
12
  // rgba, so className can't express both branches).
12
13
  const BORDER_DEFAULT = resolveColor('border-default')
@@ -8,8 +8,9 @@ import { WorkoutCard, type WorkoutStatus, type WorkoutMuscleGroup } from './Work
8
8
  import { type WorkoutPillStatus } from './WorkoutPill'
9
9
  import { type ExerciseCardProps } from './ExerciseCard'
10
10
  import { cn } from '../../../utils/cn'
11
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
11
12
 
12
- const BRAND_PRIMARY = '#FF7900'
13
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
13
14
 
14
15
  /** A single workout within a planned week, plus its exercise breakdown. */
15
16
  export interface PlanWorkout {
@@ -2,12 +2,15 @@
2
2
  import { View, Text, Pressable } from 'react-native'
3
3
  import { Card } from '../../ui/card'
4
4
  import { Badge, type BadgeColor } from '../../ui/badge'
5
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
5
6
 
6
- const BRAND_PRIMARY = '#FF7900'
7
+ const t = getSemanticColors('dark')
8
+
9
+ const BRAND_PRIMARY = t['brand-primary']
7
10
  const BRAND_PRIMARY_SUBTLE = 'rgba(255,121,0,0.12)'
8
- const STATUS_SUCCESS = '#14B8A6'
9
- const STATUS_WARNING = '#FFB020'
10
- const STATUS_ERROR = '#D14343'
11
+ const STATUS_SUCCESS = t['status-success']
12
+ const STATUS_WARNING = t['status-warning']
13
+ const STATUS_ERROR = t['status-error']
11
14
 
12
15
  /** Emoji option sets per known factor, keyed by lowercase id/label. */
13
16
  const EMOJI_SETS: Record<string, readonly string[]> = {
@@ -1,8 +1,9 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { View, Text, Pressable } from 'react-native'
3
3
  import { resolveColor } from '../../../theme/resolve-color'
4
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
5
 
5
- const BRAND_PRIMARY = '#FF7900'
6
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
6
7
 
7
8
  export interface RestTimerProps {
8
9
  totalSeconds: number
@@ -3,6 +3,7 @@ import { View, Text } from 'react-native'
3
3
  import { VelocityStrip, type VelocityZoneBandProp } from './VelocityStrip'
4
4
  import { PrBadge, type PRType } from './PrBadge'
5
5
  import { roundRpe, rpeColor, roundWeight } from '../../../utils/workout-format'
6
+ import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
6
7
 
7
8
  export type SetRowMode = 'active' | 'completed' | 'history'
8
9
 
@@ -100,7 +101,7 @@ export function SetRow({
100
101
  fontSize: 10,
101
102
  fontWeight: '700',
102
103
  fontFamily: 'Inter, sans-serif',
103
- color: '#ffa502',
104
+ color: WORKOUT_TOKENS.scale.orange,
104
105
  backgroundColor: 'rgba(255,165,2,0.12)',
105
106
  paddingVertical: 1,
106
107
  paddingHorizontal: 5,
@@ -2,8 +2,9 @@
2
2
  import React from 'react'
3
3
  import { View, Text, type ViewProps } from 'react-native'
4
4
  import { cn } from '../../../utils/cn'
5
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
5
6
 
6
- const BRAND_PRIMARY = '#FF7900'
7
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
7
8
 
8
9
  export interface SparklineProps extends ViewProps {
9
10
  data: number[]
@@ -103,13 +103,13 @@ describe('StatusDot', () => {
103
103
  it('applies a box-shadow when glow is set on a solid variant', () => {
104
104
  render(<StatusDot variant="success" glow />)
105
105
  const dot = screen.getByTestId('status-dot')
106
- expect(dot.style.boxShadow).toContain('rgba(20,184,166,0.4)')
106
+ expect(dot.style.boxShadow).toContain('rgba(46,213,115,0.4)')
107
107
  })
108
108
 
109
109
  it('applies a box-shadow when glow is set on a ring variant', () => {
110
110
  render(<StatusDot variant="on-track" glow />)
111
111
  const dot = screen.getByTestId('status-dot')
112
- expect(dot.style.boxShadow).toContain('rgba(20,184,166,0.4)')
112
+ expect(dot.style.boxShadow).toContain('rgba(46,213,115,0.4)')
113
113
  })
114
114
 
115
115
  it('applies a gray box-shadow when glow is set on the future variant', () => {
@@ -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, Text, type ViewProps } from 'react-native'
3
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
+
5
+ const t = getSemanticColors('dark')
3
6
 
4
7
  export type StatusDotVariant =
5
8
  | 'success'
@@ -22,17 +25,17 @@ export interface StatusDotProps extends ViewProps {
22
25
  }
23
26
 
24
27
  const solidVariantColors: Record<string, string> = {
25
- success: '#14B8A6',
26
- warning: '#FFB020',
27
- error: '#D14343',
28
+ success: t['status-success'],
29
+ warning: t['status-warning'],
30
+ error: t['status-error'],
28
31
  neutral: '#6B7280',
29
32
  }
30
33
 
31
34
  const ringVariantStyles: Record<string, Record<string, unknown>> = {
32
35
  'on-track': {
33
- backgroundColor: 'rgba(20,184,166,0.15)',
36
+ backgroundColor: 'rgba(46,213,115,0.15)',
34
37
  borderWidth: 1,
35
- borderColor: 'rgba(20,184,166,0.3)',
38
+ borderColor: 'rgba(46,213,115,0.3)',
36
39
  },
37
40
  deviation: {
38
41
  backgroundColor: 'rgba(245,158,11,0.15)',
@@ -48,11 +51,11 @@ const ringVariantStyles: Record<string, Record<string, unknown>> = {
48
51
  }
49
52
 
50
53
  const glowStyles: Record<StatusDotVariant, Record<string, unknown>> = {
51
- success: { boxShadow: '0 0 4px rgba(20,184,166,0.4)' },
54
+ success: { boxShadow: '0 0 4px rgba(46,213,115,0.4)' },
52
55
  warning: { boxShadow: '0 0 4px rgba(245,158,11,0.4)' },
53
56
  error: { boxShadow: '0 0 4px rgba(239,68,68,0.4)' },
54
57
  neutral: { boxShadow: '0 0 4px rgba(107,114,128,0.4)' },
55
- 'on-track': { boxShadow: '0 0 4px rgba(20,184,166,0.4)' },
58
+ 'on-track': { boxShadow: '0 0 4px rgba(46,213,115,0.4)' },
56
59
  deviation: { boxShadow: '0 0 4px rgba(245,158,11,0.4)' },
57
60
  future: { boxShadow: '0 0 4px rgba(107,114,128,0.4)' },
58
61
  }
@@ -69,8 +72,8 @@ const iconColors: Record<StatusDotVariant, string> = {
69
72
  warning: '#6B4000',
70
73
  error: '#5C1A1A',
71
74
  neutral: '#D1D5DB',
72
- 'on-track': '#14B8A6',
73
- deviation: '#FFB020',
75
+ 'on-track': t['status-success'],
76
+ deviation: t['status-warning'],
74
77
  future: '#6B7280',
75
78
  }
76
79
 
@@ -3,17 +3,20 @@ import { useEffect, useMemo, useState } from 'react'
3
3
  import { View, Text, Pressable, Animated, Easing, type ViewProps } from 'react-native'
4
4
  import { cn } from '../../../utils/cn'
5
5
  import { resolveColor } from '../../../theme/resolve-color'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
6
7
 
7
- const BRAND_PRIMARY = '#FF7900'
8
+ const sem = getSemanticColors('dark')
9
+
10
+ const BRAND_PRIMARY = sem['brand-primary']
8
11
  // Line/border color for the dashed projection, used where a className cannot
9
12
  // apply (passed as a prop into an inline style, and a per-edge borderTopColor).
10
13
  const PROJECTION_LINE_COLOR = resolveColor('text-tertiary')
11
- const STATUS_SUCCESS = '#14B8A6'
12
- const STATUS_ERROR = '#D14343'
13
- const STATUS_WARNING = '#FFB020'
14
+ const STATUS_SUCCESS = sem['status-success']
15
+ const STATUS_ERROR = sem['status-error']
16
+ const STATUS_WARNING = sem['status-warning']
14
17
  const GRID_LINE = 'rgba(255,255,255,0.06)'
15
- const SUCCESS_PILL_BG = 'rgba(20,184,166,0.10)'
16
- const SUCCESS_PILL_BORDER = 'rgba(20,184,166,0.20)'
18
+ const SUCCESS_PILL_BG = 'rgba(46,213,115,0.10)'
19
+ const SUCCESS_PILL_BORDER = 'rgba(46,213,115,0.20)'
17
20
  const ERROR_PILL_BG = 'rgba(209,67,67,0.10)'
18
21
  const ERROR_PILL_BORDER = 'rgba(209,67,67,0.20)'
19
22
 
@@ -1,6 +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 } from 'react-native'
4
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
5
 
5
6
  export interface SupersetWrapperProps {
6
7
  label?: string
@@ -10,7 +11,7 @@ export interface SupersetWrapperProps {
10
11
 
11
12
  const DEFAULTS = {
12
13
  label: 'SS',
13
- color: '#FF7900',
14
+ color: getSemanticColors('dark')['brand-primary'],
14
15
  bgBase: '#101010',
15
16
  } as const
16
17
 
@@ -3,8 +3,11 @@ import { useState, useCallback } from 'react'
3
3
  import { View, Text, Pressable, type ViewProps } from 'react-native'
4
4
  import { roundTempo } from '../../../utils/workout-format'
5
5
  import { alpha } from '../../../utils/colors'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
6
7
  import { getTempoFillPct, getTempoPacingState } from './TempoBar'
7
8
 
9
+ const t = getSemanticColors('dark')
10
+
8
11
  /** The four tempo phases, in the order the display renders them. */
9
12
  export type TempoLivePhase = 'eccentric' | 'pauseBottom' | 'concentric' | 'pauseTop'
10
13
 
@@ -40,13 +43,13 @@ export interface TempoDisplayProps extends ViewProps {
40
43
 
41
44
  const INTER = 'Inter, sans-serif'
42
45
  const TEXT_TERTIARY = '#6B7280'
43
- const STATUS_ERROR = '#D14343' // status-error (red), shown when a phase runs behind pace
46
+ const STATUS_ERROR = t['status-error'] // shown when a phase runs behind pace
44
47
 
45
48
  // Phase colors: [eccentric, pauseBottom, concentric, pauseTop]
46
49
  const phaseColors = {
47
- eccentric: '#FFB020', // status-warning (amber)
50
+ eccentric: t['status-warning'],
48
51
  pauseBottom: TEXT_TERTIARY,
49
- concentric: '#14B8A6', // status-success (teal)
52
+ concentric: t['status-success'],
50
53
  pauseTop: TEXT_TERTIARY,
51
54
  dash: TEXT_TERTIARY,
52
55
  }
@@ -266,8 +266,8 @@ describe('VelocityStrip zones prop', () => {
266
266
  it('colors bars from the supplied bands (mini)', () => {
267
267
  render(<VelocityStrip velocities={[1.1, 0.45]} zones={compoundBands} variant="mini" />)
268
268
  // 1.1 -> speed -> green; 0.45 -> maximalStrength -> red (shared with grinding).
269
- expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ed573' })
270
- expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#ff4757' })
269
+ expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
270
+ expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#D14343' })
271
271
  })
272
272
 
273
273
  it('labels the summary row with the band containing the mean velocity', () => {
@@ -278,7 +278,7 @@ describe('VelocityStrip zones prop', () => {
278
278
 
279
279
  it('falls back to the default scale when zones is absent', () => {
280
280
  render(<VelocityStrip velocities={[1.1]} variant="mini" />)
281
- expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ed573' })
281
+ expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
282
282
  })
283
283
  })
284
284
 
@@ -2,8 +2,9 @@
2
2
  import { View, Text, type ViewProps } from 'react-native'
3
3
  import { WorkoutPill, type WorkoutPillStatus } from './WorkoutPill'
4
4
  import { IntensityBar } from './IntensityBar'
5
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
5
6
 
6
- const BRAND_PRIMARY = '#FF7900'
7
+ const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
7
8
 
8
9
  export interface WeekRowWorkout {
9
10
  name: string
@@ -66,7 +67,7 @@ export function WeekRow({
66
67
  }
67
68
 
68
69
  if (isDeload) {
69
- rowStyle.backgroundColor = 'rgba(147,51,234,0.06)'
70
+ rowStyle.backgroundColor = 'rgba(186,41,150,0.06)'
70
71
  }
71
72
 
72
73
  if (isCurrent) {
@@ -7,6 +7,7 @@ import {
7
7
  type BaseBadgeSize,
8
8
  type BaseBadgeProps,
9
9
  } from './BaseBadge'
10
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
10
11
 
11
12
  export type WeightBadgeSize = BaseBadgeSize
12
13
 
@@ -42,7 +43,7 @@ export function WeightBadge({
42
43
  ...props
43
44
  }: WeightBadgeProps) {
44
45
  const config = baseBadgeSizeConfig[size]
45
- const textColor = isPr ? '#FF7900' : '#9CA3AF'
46
+ const textColor = isPr ? getSemanticColors('dark')['brand-primary'] : '#9CA3AF'
46
47
  // Dumbbell reads clearly as a weight/strength metric and is unit-agnostic
47
48
  // across estimated-1RM and N-rep-max contexts (vs. TrendingUp which implies
48
49
  // change/trend rather than a load value).
@@ -100,7 +101,10 @@ export function WeightBadge({
100
101
  fontFamily: '"Space Grotesk", sans-serif',
101
102
  fontSize: config.fontSize,
102
103
  marginLeft: 4,
103
- color: delta >= 0 ? '#4caf50' : '#ef5350',
104
+ color:
105
+ delta >= 0
106
+ ? getSemanticColors('dark')['result-improve']
107
+ : getSemanticColors('dark')['result-degrade'],
104
108
  }}
105
109
  testID="weight-badge-delta"
106
110
  >
@@ -6,6 +6,9 @@ import {
6
6
  MuscleGroupChip,
7
7
  type VolumeStatus,
8
8
  } from './MuscleGroupChip'
9
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
10
+
11
+ const t = getSemanticColors('dark')
9
12
 
10
13
  export type WorkoutStatus = 'completed' | 'today' | 'upcoming'
11
14
 
@@ -43,8 +46,8 @@ export interface WorkoutCardProps extends ViewProps {
43
46
  }
44
47
 
45
48
  const COLORS = {
46
- statusSuccess: '#14B8A6',
47
- brandPrimary: '#FF7900',
49
+ statusSuccess: t['status-success'],
50
+ brandPrimary: t['brand-primary'],
48
51
  borderDefault: '#1F1F1F',
49
52
  brandPrimarySubtle: 'rgba(255,121,0,0.06)',
50
53
  }
@@ -1,6 +1,10 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { useEffect, useState } from 'react'
3
3
  import { View, Text, Pressable, Animated, Easing, type ViewProps } from 'react-native'
4
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
5
+ import { WORKOUT_PILL_DELOAD } from '../../../theme/extracted-colors-dataviz'
6
+
7
+ const t = getSemanticColors('dark')
4
8
 
5
9
  /**
6
10
  * Spec statuses: completed | current | upcoming | deload.
@@ -19,30 +23,30 @@ export interface WorkoutPillProps extends ViewProps {
19
23
  }
20
24
 
21
25
  const statusBgColors: Record<WorkoutPillStatus, string> = {
22
- completed: 'rgba(20,184,166,0.15)',
26
+ completed: 'rgba(46,213,115,0.15)',
23
27
  current: 'rgba(255,121,0,0.15)',
24
28
  next: 'transparent',
25
29
  upcoming: '#1C1C1C',
26
30
  missed: 'rgba(209,67,67,0.1)',
27
- deload: 'rgba(147,51,234,0.15)',
31
+ deload: 'rgba(186,41,150,0.15)',
28
32
  }
29
33
 
30
34
  const statusBorderStyles: Record<WorkoutPillStatus, Record<string, unknown>> = {
31
- completed: { borderWidth: 1, borderColor: 'rgba(20,184,166,0.3)' },
35
+ completed: { borderWidth: 1, borderColor: 'rgba(46,213,115,0.3)' },
32
36
  current: { borderWidth: 1, borderColor: 'rgba(255,121,0,0.3)' },
33
37
  next: { borderWidth: 1, borderColor: 'rgba(255,121,0,0.4)' },
34
38
  upcoming: { borderWidth: 1, borderColor: '#1F1F1F' },
35
39
  missed: { borderWidth: 1, borderColor: 'rgba(209,67,67,0.25)' },
36
- deload: { borderWidth: 1, borderColor: 'rgba(147,51,234,0.3)' },
40
+ deload: { borderWidth: 1, borderColor: 'rgba(186,41,150,0.3)' },
37
41
  }
38
42
 
39
43
  const textColors: Record<WorkoutPillStatus, string> = {
40
- completed: '#14B8A6',
41
- current: '#FF7900',
42
- next: '#FF7900',
44
+ completed: t['status-success'],
45
+ current: t['brand-primary'],
46
+ next: t['brand-primary'],
43
47
  upcoming: '#6B7280',
44
48
  missed: 'rgba(209,67,67,0.7)',
45
- deload: '#9333ea',
49
+ deload: WORKOUT_PILL_DELOAD,
46
50
  }
47
51
 
48
52
  function usePulse(enabled: boolean) {
@@ -1,68 +1,7 @@
1
- // Inline SVG icons for the Workout badges replaces the `lucide-react`
2
- // dependency (dep hygiene, titan 0.5.0). The paths mirror lucide-react's
3
- // `Dumbbell` and `Star` glyphs verbatim so rendering is pixel-identical to the
4
- // previous `<Dumbbell>` / `<Star>` output (same viewBox, stroke, fill, and
5
- // round line caps). Rendered as raw `<svg>` DOM elements — the same web-SVG
6
- // mechanism `Progress.tsx` uses, and the same surface `lucide-react` produced
7
- // (both are web-only SVG; native icon rendering was never wired here).
1
+ // Workout icons were promoted to the shared icon primitive
2
+ // (`components/icons`). This shim re-exports them so existing Workout consumers
3
+ // keep their local `./icons` import unchanged.
4
+ export { DumbbellIcon, StarIcon } from '../../icons'
8
5
 
9
- export interface WorkoutIconProps {
10
- /** Rendered width/height in px (viewBox is fixed 24×24). */
11
- size?: number
12
- /** Stroke color. */
13
- color?: string
14
- /** Fill color; defaults to `none` (outline only). */
15
- fill?: string
16
- /** Stroke width. */
17
- strokeWidth?: number
18
- }
19
-
20
- /** Dumbbell glyph (mirrors lucide-react `Dumbbell`). Outline-only by default. */
21
- export function DumbbellIcon({
22
- size = 24,
23
- color = 'currentColor',
24
- fill = 'none',
25
- strokeWidth = 2,
26
- }: WorkoutIconProps) {
27
- return (
28
- <svg
29
- width={size}
30
- height={size}
31
- viewBox="0 0 24 24"
32
- fill={fill}
33
- stroke={color}
34
- strokeWidth={strokeWidth}
35
- strokeLinecap="round"
36
- strokeLinejoin="round"
37
- >
38
- <path d="M14.4 14.4 9.6 9.6" />
39
- <path d="M18.657 21.485a2 2 0 1 1-2.829-2.828l-1.767 1.768a2 2 0 1 1-2.829-2.829l6.364-6.364a2 2 0 1 1 2.829 2.829l-1.768 1.767a2 2 0 1 1 2.828 2.829z" />
40
- <path d="m21.5 21.5-1.4-1.4" />
41
- <path d="M3.9 3.9 2.5 2.5" />
42
- <path d="M6.404 12.768a2 2 0 1 1-2.829-2.829l1.768-1.767a2 2 0 1 1-2.828-2.829l2.828-2.828a2 2 0 1 1 2.829 2.828l1.767-1.768a2 2 0 1 1 2.829 2.829z" />
43
- </svg>
44
- )
45
- }
46
-
47
- /** Star glyph (mirrors lucide-react `Star`). Pass `fill` for a solid star. */
48
- export function StarIcon({
49
- size = 24,
50
- color = 'currentColor',
51
- fill = 'none',
52
- strokeWidth = 2,
53
- }: WorkoutIconProps) {
54
- return (
55
- <svg
56
- width={size}
57
- height={size}
58
- viewBox="0 0 24 24"
59
- fill={fill}
60
- stroke={color}
61
- strokeWidth={strokeWidth}
62
- strokeLinecap="round"
63
- strokeLinejoin="round"
64
- >
65
- <path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" />
66
- </svg>
67
- )
68
- }
6
+ /** @deprecated Use `IconProps` from `components/icons`. */
7
+ export type { IconProps as WorkoutIconProps } from '../../icons'