@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.
- package/dist/bodymap.js +232 -185
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +232 -185
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +164 -8
- package/dist/index.d.ts +164 -8
- package/dist/index.js +835 -342
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +816 -343
- package/dist/index.mjs.map +1 -1
- package/dist/pages.js +570 -522
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +570 -522
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +461 -199
- package/dist/theme/index.d.ts +461 -199
- package/dist/theme/index.js +311 -97
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +303 -98
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +148 -139
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +148 -139
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +3 -1
- package/src/components/custom/DateTime/DateTime.stories.tsx +82 -6
- package/src/components/custom/DateTime/DateTime.test.tsx +29 -3
- package/src/components/custom/DateTime/DateTime.tsx +55 -15
- package/src/components/custom/Gauge/Gauge.test.tsx +2 -2
- package/src/components/custom/Gauge/Gauge.tsx +6 -3
- package/src/components/custom/Scatter/Scatter.tsx +3 -5
- package/src/components/custom/Treemap/Treemap.tsx +3 -5
- package/src/components/custom/Typography/Typography.stories.tsx +45 -27
- package/src/components/custom/Typography/Typography.test.tsx +13 -3
- package/src/components/custom/Typography/Typography.tsx +5 -0
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +2 -1
- package/src/components/custom/Workout/BodyMap.tsx +6 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +6 -3
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +2 -2
- package/src/components/custom/Workout/CapacityBandChart.tsx +9 -6
- package/src/components/custom/Workout/DeviationBar.test.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.tsx +7 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +2 -1
- package/src/components/custom/Workout/InputBar.tsx +2 -1
- package/src/components/custom/Workout/IntensityBar.test.tsx +8 -8
- package/src/components/custom/Workout/IntensityBar.tsx +10 -6
- package/src/components/custom/Workout/MesoCard.tsx +8 -3
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.tsx +3 -2
- package/src/components/custom/Workout/MesoStatusCard.tsx +20 -13
- package/src/components/custom/Workout/MuscleGroupChip.tsx +7 -4
- package/src/components/custom/Workout/PrBadge.tsx +5 -2
- package/src/components/custom/Workout/PrHistoryModal.tsx +4 -3
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +2 -1
- package/src/components/custom/Workout/ReadinessCheck.tsx +7 -4
- package/src/components/custom/Workout/RestTimer.tsx +2 -1
- package/src/components/custom/Workout/SetRow.tsx +2 -1
- package/src/components/custom/Workout/Sparkline.tsx +2 -1
- package/src/components/custom/Workout/StatusDot.test.tsx +2 -2
- package/src/components/custom/Workout/StatusDot.tsx +12 -9
- package/src/components/custom/Workout/StrengthTrendChart.tsx +9 -6
- package/src/components/custom/Workout/SupersetWrapper.tsx +2 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +6 -3
- package/src/components/custom/Workout/VelocityStrip.test.tsx +3 -3
- package/src/components/custom/Workout/WeekRow.tsx +3 -2
- package/src/components/custom/Workout/WeightBadge.tsx +6 -2
- package/src/components/custom/Workout/WorkoutCard.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.tsx +12 -8
- package/src/components/custom/Workout/icons.tsx +6 -67
- package/src/components/icons/SvgIcon.tsx +50 -0
- package/src/components/icons/icons.stories.tsx +57 -0
- package/src/components/icons/icons.test.tsx +43 -0
- package/src/components/icons/icons.tsx +45 -0
- package/src/components/icons/index.ts +4 -0
- package/src/components/index.ts +6 -0
- package/src/components/shell/BrandLockup.stories.tsx +27 -0
- package/src/components/shell/BrandLockup.test.tsx +23 -0
- package/src/components/shell/BrandLockup.tsx +47 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +28 -0
- package/src/components/shell/DeviceIndicator.test.tsx +26 -0
- package/src/components/shell/DeviceIndicator.tsx +52 -0
- package/src/components/shell/DeviceMenu.stories.tsx +47 -0
- package/src/components/shell/DeviceMenu.test.tsx +40 -0
- package/src/components/shell/DeviceMenu.tsx +65 -0
- package/src/components/shell/DeviceRow.stories.tsx +38 -0
- package/src/components/shell/DeviceRow.test.tsx +37 -0
- package/src/components/shell/DeviceRow.tsx +58 -0
- package/src/components/shell/README.md +86 -0
- package/src/components/shell/SessionStatePill.stories.tsx +27 -0
- package/src/components/shell/SessionStatePill.test.tsx +25 -0
- package/src/components/shell/SessionStatePill.tsx +46 -0
- package/src/components/shell/TopBar.stories.tsx +49 -0
- package/src/components/shell/TopBar.test.tsx +36 -0
- package/src/components/shell/TopBar.tsx +89 -0
- package/src/components/shell/index.ts +8 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +37 -1
- package/src/components/ui/indicator/Indicator.test.tsx +19 -1
- package/src/components/ui/indicator/Indicator.tsx +45 -6
- package/src/components/ui/indicator/index.ts +1 -1
- package/src/components/ui/spinner/Spinner.tsx +3 -2
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +2 -1
- package/src/theme/ColorSystem.stories.tsx +323 -0
- package/src/theme/Colors.stories.tsx +69 -1
- package/src/theme/barrel.ts +1 -0
- package/src/theme/config.ts +16 -3
- package/src/theme/extracted-colors-dataviz.ts +21 -0
- package/src/theme/extracted-colors-ui.ts +20 -0
- package/src/theme/global.css +64 -52
- package/src/theme/gradients.test.ts +25 -0
- package/src/theme/gradients.ts +36 -0
- package/src/theme/tokens/primitives.test.ts +232 -0
- package/src/theme/tokens/primitives.ts +247 -0
- package/src/theme/tokens/semantic.ts +93 -81
- package/src/theme/workout-tokens.ts +22 -21
- package/src/utils/avatar-color.ts +3 -3
- 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 = '
|
|
9
|
-
const BRAND_PRIMARY_DARK =
|
|
10
|
-
const BRAND_PRIMARY_LIGHT =
|
|
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(
|
|
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 = '
|
|
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(
|
|
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
|
|
9
|
-
const BRAND_PRIMARY_DARK = '#C45E00'
|
|
10
|
-
const BRAND_PRIMARY_LIGHT = '#FFB066'
|
|
13
|
+
const t = getSemanticColors('dark')
|
|
11
14
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
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(
|
|
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(
|
|
37
|
-
warning: { bg: 'rgba(
|
|
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(
|
|
410
|
+
backgroundColor: 'rgba(249,180,21,0.06)',
|
|
404
411
|
borderWidth: 1,
|
|
405
|
-
borderColor: 'rgba(
|
|
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(
|
|
437
|
+
backgroundColor: 'rgba(46,213,115,0.06)',
|
|
431
438
|
borderWidth: 1,
|
|
432
|
-
borderColor: 'rgba(
|
|
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: '
|
|
17
|
-
ontrack: '
|
|
18
|
-
target: '
|
|
19
|
-
over: '
|
|
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=
|
|
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:
|
|
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 = '
|
|
8
|
-
const RECENT_BORDER = 'rgba(
|
|
9
|
-
const RECENT_GLOW = '0 0 12px rgba(
|
|
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 = '
|
|
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
|
|
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 = '
|
|
9
|
-
const STATUS_WARNING = '
|
|
10
|
-
const STATUS_ERROR = '
|
|
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 = '
|
|
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:
|
|
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 = '
|
|
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(
|
|
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(
|
|
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: '
|
|
26
|
-
warning: '
|
|
27
|
-
error: '
|
|
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(
|
|
36
|
+
backgroundColor: 'rgba(46,213,115,0.15)',
|
|
34
37
|
borderWidth: 1,
|
|
35
|
-
borderColor: 'rgba(
|
|
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(
|
|
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(
|
|
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': '
|
|
73
|
-
deviation: '
|
|
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
|
|
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 = '
|
|
12
|
-
const STATUS_ERROR = '
|
|
13
|
-
const STATUS_WARNING = '
|
|
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(
|
|
16
|
-
const SUCCESS_PILL_BORDER = 'rgba(
|
|
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: '
|
|
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 = '
|
|
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: '
|
|
50
|
+
eccentric: t['status-warning'],
|
|
48
51
|
pauseBottom: TEXT_TERTIARY,
|
|
49
|
-
concentric: '
|
|
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: '#
|
|
270
|
-
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#
|
|
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: '#
|
|
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 = '
|
|
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(
|
|
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 ? '
|
|
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:
|
|
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: '
|
|
47
|
-
brandPrimary: '
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
40
|
+
deload: { borderWidth: 1, borderColor: 'rgba(186,41,150,0.3)' },
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
const textColors: Record<WorkoutPillStatus, string> = {
|
|
40
|
-
completed: '
|
|
41
|
-
current: '
|
|
42
|
-
next: '
|
|
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:
|
|
49
|
+
deload: WORKOUT_PILL_DELOAD,
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
function usePulse(enabled: boolean) {
|
|
@@ -1,68 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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'
|