@titan-design/react-ui 0.13.0 → 0.14.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/README.md +14 -0
- package/dist/bodymap.js +129 -76
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +129 -76
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +117 -26
- package/dist/index.d.ts +117 -26
- package/dist/index.js +677 -450
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +673 -451
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-_3Ajpiqu.d.ts → pages-CsjsUn0G.d.ts} +30 -13
- package/dist/{pages-CpW3OB3g.d.mts → pages-ZbT_pD_U.d.mts} +30 -13
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +676 -701
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +676 -701
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +31 -7
- package/dist/theme/index.d.ts +31 -7
- package/dist/theme/index.js +84 -20
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +83 -21
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +84 -20
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +84 -20
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/dist/theme/tokens.d.mts +56 -7
- package/dist/theme/tokens.d.ts +56 -7
- package/dist/theme/tokens.js +72 -20
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/tokens.mjs +71 -21
- package/dist/theme/tokens.mjs.map +1 -1
- package/docs/render-testing-pattern.md +130 -0
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +3 -1
- package/src/arch/arch-graph.json +74 -72
- package/src/components/custom/ActiveWork/InitiativeBrief.tsx +2 -1
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +8 -3
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +4 -1
- package/src/components/custom/Gauge/Gauge.tsx +3 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +10 -7
- package/src/components/custom/Scatter/Scatter.tsx +6 -4
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/Table/Table.tsx +2 -2
- package/src/components/custom/Treemap/Treemap.stories.tsx +13 -10
- package/src/components/custom/Treemap/Treemap.tsx +4 -1
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +2 -1
- package/src/components/custom/Workout/BaseBadge.stories.tsx +11 -7
- package/src/components/custom/Workout/BaseBadge.tsx +3 -2
- package/src/components/custom/Workout/BodyMap.tsx +5 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +13 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +5 -3
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +2 -1
- package/src/components/custom/Workout/CapacityBandChart.tsx +5 -3
- package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
- package/src/components/custom/Workout/DeviationBar.tsx +5 -2
- package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +7 -6
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +29 -16
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -8
- package/src/components/custom/Workout/ExerciseCard.test.tsx +39 -19
- package/src/components/custom/Workout/ExerciseCard.tsx +92 -167
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +89 -6
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
- package/src/components/custom/Workout/ExerciseHeading.tsx +192 -47
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -5
- package/src/components/custom/Workout/InputBar.tsx +4 -2
- package/src/components/custom/Workout/IntensityBar.tsx +4 -1
- package/src/components/custom/Workout/MesoCard.tsx +2 -1
- package/src/components/custom/Workout/MesoStatusCard.tsx +11 -10
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +8 -3
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +12 -2
- package/src/components/custom/Workout/README.md +58 -12
- package/src/components/custom/Workout/ReadinessCheck.tsx +4 -2
- package/src/components/custom/Workout/RestTimer.stories.tsx +13 -2
- package/src/components/custom/Workout/RestTimer.tsx +4 -2
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +16 -12
- package/src/components/custom/Workout/SegmentedBar.tsx +4 -3
- package/src/components/custom/Workout/SetStrip.stories.tsx +3 -2
- package/src/components/custom/Workout/SetsRepsLoad.tsx +23 -9
- package/src/components/custom/Workout/Sparkline.stories.tsx +13 -10
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +2 -1
- package/src/components/custom/Workout/StrengthTrendChart.tsx +8 -6
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +4 -3
- package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +3 -2
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +40 -0
- package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
- package/src/components/custom/Workout/VelocityStrip.tsx +23 -14
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +3 -1
- package/src/components/custom/Workout/exerciseRowState.ts +29 -0
- package/src/components/custom/Workout/index.ts +12 -2
- package/src/components/custom/Workout/metricText.stories.tsx +9 -8
- package/src/components/custom/Workout/metricText.tsx +4 -2
- package/src/components/custom/Workout/setHeadingKit.tsx +12 -10
- package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
- package/src/components/custom/charts/README.md +5 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +16 -0
- package/src/components/custom/charts/SetBarChart.tsx +14 -5
- package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
- package/src/components/custom/charts/flatBarGeometry.ts +32 -0
- package/src/components/custom/stepper/Stepper.tsx +8 -6
- package/src/components/ui/alert/Alert.stories.tsx +14 -4
- package/src/components/ui/alert/Alert.tsx +24 -3
- package/src/components/ui/button/Button.tsx +12 -12
- package/src/components/ui/checkbox/Checkbox.tsx +2 -2
- package/src/components/ui/data-row/DataRow.stories.tsx +6 -2
- package/src/components/ui/drawer/Drawer.tsx +4 -1
- package/src/components/ui/icon-box/IconBox.tsx +8 -6
- package/src/components/ui/indicator/Indicator.stories.tsx +4 -3
- package/src/components/ui/list-item/ListItem.stories.tsx +7 -4
- package/src/components/ui/modal/Modal.tsx +2 -0
- package/src/components/ui/pill/Pill.stories.tsx +53 -0
- package/src/components/ui/pill/Pill.tsx +10 -6
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +9 -6
- package/src/components/ui/radio/Radio.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +10 -10
- package/src/components/ui/select/Select.tsx +2 -1
- package/src/components/ui/spinner/Spinner.tsx +3 -1
- package/src/components/ui/stack/Stack.stories.tsx +7 -7
- package/src/components/ui/surface/Surface.stories.tsx +75 -28
- package/src/components/ui/switch/Switch.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +4 -2
- package/src/components/ui/toast/Toast.tsx +6 -4
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +13 -6
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/test/no-raw-color.test.ts +39 -0
- package/src/theme/Alpha.stories.tsx +103 -0
- package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
- package/src/theme/config.ts +12 -0
- package/src/theme/global.css +15 -3
- package/src/theme/subtle-tone-weight.test.ts +163 -0
- package/src/theme/tailwind-var-opacity.test.ts +163 -0
- package/src/theme/tokens/primitives.ts +33 -0
- package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
- package/src/theme/tokens/semantic.ts +56 -20
- package/src/utils/colors.alpha-adoption.test.ts +408 -0
- package/tailwind.config.js +50 -9
|
@@ -1,77 +1,164 @@
|
|
|
1
1
|
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
2
3
|
import { View, Pressable } from 'react-native'
|
|
3
4
|
import { Typography } from '../Typography'
|
|
5
|
+
import { cn } from '../../../utils/cn'
|
|
4
6
|
import { roundWeight } from '../../../utils/workout-format'
|
|
7
|
+
import { useSurfaceMode } from '../../ui/surface/SurfaceContext'
|
|
5
8
|
import { SetsRepsLoad } from './SetsRepsLoad'
|
|
6
9
|
import { TempoDisplay } from './TempoDisplay'
|
|
7
10
|
import { ExerciseIndicator, type ExerciseIndicatorKind } from './ExerciseIndicator'
|
|
11
|
+
import { exerciseLiveColor } from './exerciseRowState'
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
/** Dim depth for a not-yet-reached exercise, when the block dims itself. */
|
|
14
|
+
const DIMMED_OPACITY = 0.55
|
|
15
|
+
|
|
16
|
+
/** `stacked` puts the prescription on its own line under the name; `inline` keeps one row. */
|
|
17
|
+
export type ExerciseHeadingLayout = 'stacked' | 'inline'
|
|
18
|
+
|
|
19
|
+
interface ExerciseHeadingBaseProps {
|
|
10
20
|
name: string
|
|
11
|
-
/** Prescription line values (sets × reps @ load). */
|
|
12
|
-
sets: number
|
|
13
|
-
reps: number | string
|
|
14
|
-
/** Load value; a string (e.g. "—") passes through verbatim for an unset/discovery load. */
|
|
15
|
-
load: number | string
|
|
16
21
|
unit?: 'lbs' | 'kg'
|
|
17
|
-
/** Tempo tuple [eccentric, pauseBottom, concentric, pauseTop]
|
|
22
|
+
/** Tempo tuple [eccentric, pauseBottom, concentric, pauseTop]. `stacked` layout only. */
|
|
18
23
|
tempo?: [number, number, number, number]
|
|
19
24
|
/** Small PR / issue / info chip in the title row. */
|
|
20
25
|
indicator?: ExerciseIndicatorKind
|
|
21
26
|
/** Dim the block as a not-yet-reached exercise (standalone use). */
|
|
22
27
|
dimmed?: boolean
|
|
28
|
+
/** Row shape. Default `stacked`. */
|
|
29
|
+
layout?: ExerciseHeadingLayout
|
|
30
|
+
/**
|
|
31
|
+
* Render the structured `sets × reps @ load` prescription in one dimmer, regular
|
|
32
|
+
* tone instead of the rail's bright/bold value cells. `stacked` (rail) never sets
|
|
33
|
+
* this; `inline` (compact, upcoming) always does.
|
|
34
|
+
*/
|
|
35
|
+
mutedPrescription?: boolean
|
|
36
|
+
/** Trailing right-edge caption (a previous best). `inline` layout only. */
|
|
37
|
+
previousBest?: string
|
|
38
|
+
/** This exercise is being performed right now — the name takes the live tone, never animated. */
|
|
39
|
+
isLive?: boolean
|
|
40
|
+
/**
|
|
41
|
+
* Who owns the press. `self` (default) makes the name row the button. `ancestor`
|
|
42
|
+
* renders it as a plain row, for a wrapper that presses as a whole — legal only in
|
|
43
|
+
* `inline` layout, since `stacked`'s TempoDisplay sibling is itself a Pressable.
|
|
44
|
+
*/
|
|
45
|
+
pressTarget?: 'self' | 'ancestor'
|
|
23
46
|
/** Press target for the name row (expand / select). */
|
|
24
47
|
onPress?: () => void
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
reps: number | string,
|
|
31
|
-
load: number | string,
|
|
32
|
-
unit: string
|
|
33
|
-
) {
|
|
34
|
-
const loadLabel = typeof load === 'number' ? roundWeight(load) : load
|
|
35
|
-
return `${name}, ${sets}×${reps} @ ${loadLabel} ${unit}`
|
|
48
|
+
onPressIn?: () => void
|
|
49
|
+
onPressOut?: () => void
|
|
50
|
+
/** Web-only; on a touch surface these never fire and the row has no hover state. */
|
|
51
|
+
onHoverIn?: () => void
|
|
52
|
+
onHoverOut?: () => void
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
/**
|
|
39
|
-
* The
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* target — TempoDisplay is a sibling, never a descendant, since it is itself a
|
|
43
|
-
* Pressable (nesting would be an a11y nested-interactive violation).
|
|
56
|
+
* The prescription is EITHER the structured `sets × reps @ load` triple, OR a
|
|
57
|
+
* free-text line for an exercise whose numbers aren't loaded yet, OR absent.
|
|
58
|
+
* Mixing the two is a type error rather than a silently half-rendered line.
|
|
44
59
|
*/
|
|
45
|
-
|
|
60
|
+
type ExerciseHeadingPrescription =
|
|
61
|
+
| { sets: number; reps: number | string; load: number | string; prescription?: never }
|
|
62
|
+
| { prescription: string; sets?: never; reps?: never; load?: never }
|
|
63
|
+
| { sets?: never; reps?: never; load?: never; prescription?: never }
|
|
64
|
+
|
|
65
|
+
export type ExerciseHeadingProps = ExerciseHeadingBaseProps & ExerciseHeadingPrescription
|
|
66
|
+
|
|
67
|
+
function prescriptionText(p: ExerciseHeadingProps, unit: string): string | undefined {
|
|
68
|
+
if (p.prescription !== undefined) return p.prescription
|
|
69
|
+
if (p.sets === undefined) return undefined
|
|
70
|
+
const loadLabel = typeof p.load === 'number' ? roundWeight(p.load) : p.load
|
|
71
|
+
return `${p.sets}×${p.reps} @ ${loadLabel} ${unit}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The row's accessible name. Exported so a wrapper that owns the press can reuse it. */
|
|
75
|
+
export function exerciseHeadingLabel(p: ExerciseHeadingProps, unit: string = p.unit ?? 'lbs') {
|
|
76
|
+
const prescription = prescriptionText(p, unit)
|
|
77
|
+
return prescription ? `${p.name}, ${prescription}` : p.name
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function pressProps(p: ExerciseHeadingProps, unit: string) {
|
|
81
|
+
return {
|
|
82
|
+
onPress: p.onPress,
|
|
83
|
+
onPressIn: p.onPressIn,
|
|
84
|
+
onPressOut: p.onPressOut,
|
|
85
|
+
onHoverIn: p.onHoverIn,
|
|
86
|
+
onHoverOut: p.onHoverOut,
|
|
87
|
+
accessibilityRole: 'button' as const,
|
|
88
|
+
accessibilityLabel: exerciseHeadingLabel(p, unit),
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface NameStyle {
|
|
93
|
+
color: string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function HeadingName({
|
|
46
97
|
name,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
98
|
+
nameStyle,
|
|
99
|
+
className,
|
|
100
|
+
}: {
|
|
101
|
+
name: string
|
|
102
|
+
nameStyle?: NameStyle
|
|
103
|
+
className?: string
|
|
104
|
+
}) {
|
|
105
|
+
return (
|
|
106
|
+
// The row is a button label, not a heading, so `boldLabel` carries the weight and
|
|
107
|
+
// no header role has to be undone. Size and the Space Grotesk face are pinned.
|
|
108
|
+
<Typography
|
|
109
|
+
variant="boldLabel"
|
|
110
|
+
color={nameStyle ? 'inherit' : 'primary'}
|
|
111
|
+
style={nameStyle}
|
|
112
|
+
className={cn('font-heading text-sm leading-[normal]', className)}
|
|
113
|
+
testID="exercise-card-name"
|
|
114
|
+
>
|
|
115
|
+
{name}
|
|
116
|
+
</Typography>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function prescriptionNode(p: ExerciseHeadingProps, unit: string): ReactNode {
|
|
121
|
+
if (p.prescription !== undefined) {
|
|
122
|
+
return (
|
|
123
|
+
<Typography
|
|
124
|
+
variant="caption"
|
|
125
|
+
color="secondary"
|
|
126
|
+
maxLines={1}
|
|
127
|
+
className="font-sans shrink leading-[normal]"
|
|
128
|
+
testID="exercise-card-prescription"
|
|
129
|
+
>
|
|
130
|
+
{p.prescription}
|
|
131
|
+
</Typography>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
if (p.sets === undefined) return null
|
|
135
|
+
return (
|
|
136
|
+
<SetsRepsLoad
|
|
137
|
+
sets={p.sets}
|
|
138
|
+
reps={p.reps}
|
|
139
|
+
load={p.load}
|
|
140
|
+
unit={unit}
|
|
141
|
+
muted={p.mutedPrescription}
|
|
142
|
+
/>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface LayoutProps {
|
|
147
|
+
heading: ExerciseHeadingProps
|
|
148
|
+
nameStyle?: NameStyle
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function StackedHeading({ heading, nameStyle }: LayoutProps) {
|
|
152
|
+
const { unit = 'lbs', tempo, indicator } = heading
|
|
56
153
|
return (
|
|
57
|
-
|
|
154
|
+
<>
|
|
58
155
|
<Pressable
|
|
59
|
-
|
|
60
|
-
accessibilityRole="button"
|
|
61
|
-
accessibilityLabel={headingLabel(name, sets, reps, load, unit)}
|
|
156
|
+
{...pressProps(heading, unit)}
|
|
62
157
|
className="flex-row items-center"
|
|
63
158
|
style={{ gap: 8 }}
|
|
64
159
|
testID="exercise-card-header"
|
|
65
160
|
>
|
|
66
|
-
{
|
|
67
|
-
no header role has to be undone. Size and the Space Grotesk face are pinned. */}
|
|
68
|
-
<Typography
|
|
69
|
-
variant="boldLabel"
|
|
70
|
-
className="font-heading text-sm leading-[normal]"
|
|
71
|
-
testID="exercise-card-name"
|
|
72
|
-
>
|
|
73
|
-
{name}
|
|
74
|
-
</Typography>
|
|
161
|
+
<HeadingName name={heading.name} nameStyle={nameStyle} />
|
|
75
162
|
<View className="flex-1" />
|
|
76
163
|
{indicator && <ExerciseIndicator kind={indicator} />}
|
|
77
164
|
</Pressable>
|
|
@@ -81,10 +168,68 @@ export function ExerciseHeading({
|
|
|
81
168
|
style={{ gap: 8, marginTop: 1 }}
|
|
82
169
|
testID="exercise-card-summary"
|
|
83
170
|
>
|
|
84
|
-
|
|
171
|
+
{prescriptionNode(heading, unit)}
|
|
85
172
|
<View className="flex-1" />
|
|
86
173
|
{tempo && <TempoDisplay tempo={tempo} size="sm" showLabel={false} showInfo={false} />}
|
|
87
174
|
</View>
|
|
175
|
+
</>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function InlineHeading({ heading, nameStyle }: LayoutProps) {
|
|
180
|
+
const { unit = 'lbs', indicator, previousBest } = heading
|
|
181
|
+
const Row = heading.pressTarget === 'ancestor' ? View : Pressable
|
|
182
|
+
return (
|
|
183
|
+
<Row
|
|
184
|
+
{...(heading.pressTarget === 'ancestor' ? {} : pressProps(heading, unit))}
|
|
185
|
+
className="flex-row items-center"
|
|
186
|
+
style={{ gap: 8 }}
|
|
187
|
+
testID="exercise-card-header"
|
|
188
|
+
>
|
|
189
|
+
{/* Name never truncates (no numberOfLines); prescription + previousBest ellipsize first. */}
|
|
190
|
+
<HeadingName name={heading.name} nameStyle={nameStyle} className="shrink-0" />
|
|
191
|
+
<View className="shrink flex-row items-center" testID="exercise-card-summary">
|
|
192
|
+
{prescriptionNode(heading, unit)}
|
|
193
|
+
</View>
|
|
194
|
+
<View className="flex-1" style={{ minWidth: 8 }} />
|
|
195
|
+
{indicator && <ExerciseIndicator kind={indicator} />}
|
|
196
|
+
{previousBest && (
|
|
197
|
+
<Typography
|
|
198
|
+
variant="caption"
|
|
199
|
+
color="tertiary"
|
|
200
|
+
maxLines={1}
|
|
201
|
+
className="font-sans shrink text-2xs leading-[normal]"
|
|
202
|
+
testID="exercise-card-previous-best"
|
|
203
|
+
>
|
|
204
|
+
{previousBest}
|
|
205
|
+
</Typography>
|
|
206
|
+
)}
|
|
207
|
+
</Row>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The exercise-heading info block (no strip): the name + {@link ExerciseIndicator}
|
|
213
|
+
* title row, its prescription line, and — `inline` only — a trailing previous-best
|
|
214
|
+
* caption. The name row is the sole press target: TempoDisplay and the previous-best
|
|
215
|
+
* caption are siblings of it, never descendants, since TempoDisplay is itself a
|
|
216
|
+
* Pressable (nesting would be an a11y nested-interactive violation).
|
|
217
|
+
*
|
|
218
|
+
* `layout` is the one axis that separates the session-rail row (`stacked`) from the
|
|
219
|
+
* collapsed and upcoming card rows (`inline`) — same parts, same vocabulary, one row
|
|
220
|
+
* instead of two. `inline` drops the tempo: it has no room for a second metric lockup.
|
|
221
|
+
*/
|
|
222
|
+
export function ExerciseHeading(props: ExerciseHeadingProps) {
|
|
223
|
+
const mode = useSurfaceMode()
|
|
224
|
+
const nameStyle = props.isLive ? { color: exerciseLiveColor(mode) } : undefined
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<View style={{ opacity: props.dimmed ? DIMMED_OPACITY : 1 }} testID="exercise-heading">
|
|
228
|
+
{props.layout === 'inline' ? (
|
|
229
|
+
<InlineHeading heading={props} nameStyle={nameStyle} />
|
|
230
|
+
) : (
|
|
231
|
+
<StackedHeading heading={props} nameStyle={nameStyle} />
|
|
232
|
+
)}
|
|
88
233
|
</View>
|
|
89
234
|
)
|
|
90
235
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type ExerciseIndicatorKind,
|
|
8
8
|
} from './ExerciseIndicator'
|
|
9
9
|
import { Surface } from '../../ui/surface'
|
|
10
|
+
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* `ExerciseIndicator` — a small circular OUTLINED chip in an exercise heading's
|
|
@@ -70,9 +71,9 @@ export const AllKinds: Story = {
|
|
|
70
71
|
<View style={{ gap: 12 }}>
|
|
71
72
|
{INDICATOR_PRECEDENCE.map((kind, i) => (
|
|
72
73
|
<View key={kind} style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
|
|
73
|
-
<Text style={{ color:
|
|
74
|
+
<Text style={{ color: greyRamp[500], fontSize: 11, width: 14 }}>{i + 1}</Text>
|
|
74
75
|
<ExerciseIndicator kind={kind} />
|
|
75
|
-
<Text style={{ color:
|
|
76
|
+
<Text style={{ color: greyRamp[200], fontSize: 12 }}>{KIND_LABELS[kind]}</Text>
|
|
76
77
|
</View>
|
|
77
78
|
))}
|
|
78
79
|
</View>
|
|
@@ -97,12 +98,12 @@ export const ResolvePrecedence: Story = {
|
|
|
97
98
|
const winner = resolveIndicator(candidates)
|
|
98
99
|
return (
|
|
99
100
|
<View key={i} style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
|
|
100
|
-
<Text style={{ color:
|
|
101
|
+
<Text style={{ color: greyRamp[500], fontSize: 12, width: 200 }}>
|
|
101
102
|
{candidates.join(', ')}
|
|
102
103
|
</Text>
|
|
103
|
-
<Text style={{ color:
|
|
104
|
+
<Text style={{ color: greyRamp[500], fontSize: 12 }}>→</Text>
|
|
104
105
|
{winner ? <ExerciseIndicator kind={winner} /> : null}
|
|
105
|
-
<Text style={{ color:
|
|
106
|
+
<Text style={{ color: greyRamp[200], fontSize: 12 }}>{winner}</Text>
|
|
106
107
|
</View>
|
|
107
108
|
)
|
|
108
109
|
})}
|
|
@@ -18,7 +18,9 @@ export interface InputBarProps {
|
|
|
18
18
|
visible: boolean
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const t = getSemanticColors('dark')
|
|
22
|
+
const BRAND_PRIMARY = t['brand-primary']
|
|
23
|
+
const ON_BRAND_PRIMARY = t['on-brand-primary']
|
|
22
24
|
const INPUT_CLASSNAME = 'bg-surface-raised border-hairline-strong text-text-primary'
|
|
23
25
|
|
|
24
26
|
const inputStyle = {
|
|
@@ -151,7 +153,7 @@ export function InputBar({
|
|
|
151
153
|
fontSize: 13,
|
|
152
154
|
fontWeight: '700',
|
|
153
155
|
fontFamily: 'Inter, sans-serif',
|
|
154
|
-
color:
|
|
156
|
+
color: ON_BRAND_PRIMARY,
|
|
155
157
|
}}
|
|
156
158
|
>
|
|
157
159
|
Record
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
12
12
|
import { getGlowShadow } from '../../../theme/elevation'
|
|
13
13
|
import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
|
|
14
|
+
import { alpha } from '../../../utils/colors'
|
|
14
15
|
|
|
15
16
|
const t = getSemanticColors('dark')
|
|
16
17
|
|
|
@@ -32,9 +33,11 @@ export interface IntensityBarProps extends ViewProps {
|
|
|
32
33
|
className?: string
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
// VW-82: neither value is on a ramp (nearest surface-raised #31302F / greyRamp[600]
|
|
37
|
+
// #72716F). Left raw pending the proposed roles — snapping them is a visual change.
|
|
35
38
|
const TRACK_BG = '#333333'
|
|
36
39
|
const LABEL_COLOR = '#6B7280'
|
|
37
|
-
const TARGET_LINE_COLOR = '
|
|
40
|
+
const TARGET_LINE_COLOR = alpha(t['status-info'], 0.5)
|
|
38
41
|
/** At-target is EMPHASIS, not depth: an info-toned glow through the shared builder. */
|
|
39
42
|
const AT_TARGET_GLOW = getGlowShadow(t['status-info'], 'subtle')
|
|
40
43
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
MESO_ACCENT_GRADIENT_LIGHT,
|
|
11
11
|
} from '../../../theme/extracted-colors-dataviz'
|
|
12
12
|
import { getGlowShadow } from '../../../theme/elevation'
|
|
13
|
+
import { alpha } from '../../../utils/colors'
|
|
13
14
|
|
|
14
15
|
const HAIRLINE_DEFAULT = getSemanticColors('dark')['hairline-default']
|
|
15
16
|
|
|
@@ -61,7 +62,7 @@ export interface MesoCardProps extends ViewProps {
|
|
|
61
62
|
function heatmapColor(percentage: number): string {
|
|
62
63
|
const clamped = Math.max(0, Math.min(100, percentage))
|
|
63
64
|
const opacity = 0.12 + (clamped / 100) * 0.78
|
|
64
|
-
return
|
|
65
|
+
return alpha(BRAND_PRIMARY, opacity)
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/**
|
|
@@ -5,11 +5,13 @@ import { Card } from '../../ui/card'
|
|
|
5
5
|
import { StatusDot } from './StatusDot'
|
|
6
6
|
import { resolveColor } from '../../../theme/resolve-color'
|
|
7
7
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
8
|
+
import { primitiveColors } from '../../../theme/tokens/primitives'
|
|
8
9
|
import {
|
|
9
10
|
MESO_ACCENT_GRADIENT_DARK,
|
|
10
11
|
MESO_ACCENT_GRADIENT_LIGHT,
|
|
11
12
|
} from '../../../theme/extracted-colors-dataviz'
|
|
12
13
|
import { liftStyle } from '../../../theme/lift'
|
|
14
|
+
import { alpha } from '../../../utils/colors'
|
|
13
15
|
|
|
14
16
|
const t = getSemanticColors('dark')
|
|
15
17
|
|
|
@@ -32,8 +34,7 @@ const ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY, BRAND_PRIMARY_LIGHT]
|
|
|
32
34
|
const CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor('surface-elevated')} 0%, ${resolveColor('surface-raised')} 100%)`
|
|
33
35
|
|
|
34
36
|
/** Gauge track gradient (teal -> amber -> red) at 0.25 alpha. */
|
|
35
|
-
const GAUGE_GRADIENT =
|
|
36
|
-
'linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(249,180,21,0.25) 50%, rgba(209,67,67,0.25) 100%)'
|
|
37
|
+
const GAUGE_GRADIENT = `linear-gradient(90deg, ${alpha(SUCCESS, 0.25)} 0%, ${alpha(WARNING, 0.25)} 50%, ${alpha(ERROR, 0.25)} 100%)`
|
|
37
38
|
|
|
38
39
|
export type MesoStatusBadgeVariant = 'success' | 'warning' | 'error'
|
|
39
40
|
|
|
@@ -41,9 +42,9 @@ const STATUS_VARIANTS: Record<
|
|
|
41
42
|
MesoStatusBadgeVariant,
|
|
42
43
|
{ bg: string; border: string; text: string }
|
|
43
44
|
> = {
|
|
44
|
-
success: { bg:
|
|
45
|
-
warning: { bg:
|
|
46
|
-
error: { bg:
|
|
45
|
+
success: { bg: alpha(SUCCESS, 0.15), border: alpha(SUCCESS, 0.3), text: SUCCESS },
|
|
46
|
+
warning: { bg: alpha(WARNING, 0.15), border: alpha(WARNING, 0.3), text: WARNING },
|
|
47
|
+
error: { bg: alpha(ERROR, 0.15), border: alpha(ERROR, 0.25), text: ERROR },
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export interface MesoStatusBadge {
|
|
@@ -257,7 +258,7 @@ function Gauge({ gauge }: { gauge: MesoStatusGauge }) {
|
|
|
257
258
|
bottom: 0,
|
|
258
259
|
width: 1,
|
|
259
260
|
marginLeft: -0.5,
|
|
260
|
-
backgroundColor:
|
|
261
|
+
backgroundColor: alpha(primitiveColors.white, 0.3),
|
|
261
262
|
}}
|
|
262
263
|
testID="meso-status-card-gauge-center"
|
|
263
264
|
/>
|
|
@@ -393,9 +394,9 @@ export function MesoStatusCard({
|
|
|
393
394
|
{coaching != null && (
|
|
394
395
|
<View
|
|
395
396
|
style={{
|
|
396
|
-
backgroundColor:
|
|
397
|
+
backgroundColor: alpha(WARNING, 0.06),
|
|
397
398
|
borderWidth: 1,
|
|
398
|
-
borderColor:
|
|
399
|
+
borderColor: alpha(WARNING, 0.15),
|
|
399
400
|
borderRadius: 8,
|
|
400
401
|
paddingVertical: 10,
|
|
401
402
|
paddingHorizontal: 12,
|
|
@@ -420,9 +421,9 @@ export function MesoStatusCard({
|
|
|
420
421
|
className="flex-row items-center"
|
|
421
422
|
style={{
|
|
422
423
|
gap: 8,
|
|
423
|
-
backgroundColor:
|
|
424
|
+
backgroundColor: alpha(SUCCESS, 0.06),
|
|
424
425
|
borderWidth: 1,
|
|
425
|
-
borderColor:
|
|
426
|
+
borderColor: alpha(SUCCESS, 0.2),
|
|
426
427
|
borderRadius: 8,
|
|
427
428
|
paddingVertical: 10,
|
|
428
429
|
paddingHorizontal: 12,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import { View } from 'react-native'
|
|
3
3
|
import { PlaceholderStrip } from './PlaceholderStrip'
|
|
4
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
5
|
+
|
|
6
|
+
const t = getSemanticColors('dark')
|
|
4
7
|
|
|
5
8
|
const meta: Meta<typeof PlaceholderStrip> = {
|
|
6
9
|
title: 'Custom/Workout/PlaceholderStrip',
|
|
@@ -74,9 +77,11 @@ export const SegmentedThree: Story = {
|
|
|
74
77
|
export const MultipleStrips: Story = {
|
|
75
78
|
render: () => (
|
|
76
79
|
<View style={{ flexDirection: 'row', gap: 4, width: 300 }}>
|
|
77
|
-
<View style={{ flex: 1, height: 3, backgroundColor: '
|
|
78
|
-
<View
|
|
79
|
-
|
|
80
|
+
<View style={{ flex: 1, height: 3, backgroundColor: t['status-success'], borderRadius: 1 }} />
|
|
81
|
+
<View
|
|
82
|
+
style={{ flex: 1, height: 3, backgroundColor: t['status-warning-light'], borderRadius: 1 }}
|
|
83
|
+
/>
|
|
84
|
+
<View style={{ flex: 1, height: 3, backgroundColor: t['status-warning'], borderRadius: 1 }} />
|
|
80
85
|
<PlaceholderStrip />
|
|
81
86
|
<PlaceholderStrip />
|
|
82
87
|
</View>
|
|
@@ -2,6 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
import { useState } from 'react'
|
|
3
3
|
import { View, Text, Pressable } from 'react-native'
|
|
4
4
|
import { PrHistoryModal, type PrRecord } from './PrHistoryModal'
|
|
5
|
+
import { primitiveColors } from '../../../theme/tokens/primitives'
|
|
6
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
7
|
+
|
|
8
|
+
const t = getSemanticColors('dark')
|
|
5
9
|
|
|
6
10
|
const records: PrRecord[] = [
|
|
7
11
|
{ type: 'e1rm', value: 245, unit: 'lbs', date: 'Mar 14', isRecent: true },
|
|
@@ -93,13 +97,19 @@ function InteractivePrHistoryModal() {
|
|
|
93
97
|
accessibilityRole="button"
|
|
94
98
|
style={{
|
|
95
99
|
alignSelf: 'flex-start',
|
|
96
|
-
backgroundColor: '
|
|
100
|
+
backgroundColor: t['brand-primary'],
|
|
97
101
|
paddingVertical: 10,
|
|
98
102
|
paddingHorizontal: 20,
|
|
99
103
|
borderRadius: 8,
|
|
100
104
|
}}
|
|
101
105
|
>
|
|
102
|
-
<Text
|
|
106
|
+
<Text
|
|
107
|
+
style={{
|
|
108
|
+
color: primitiveColors.white,
|
|
109
|
+
fontWeight: '700',
|
|
110
|
+
fontFamily: 'Inter, sans-serif',
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
103
113
|
Open PR history
|
|
104
114
|
</Text>
|
|
105
115
|
</Pressable>
|
|
@@ -86,11 +86,50 @@ type props without pulling the dependency.
|
|
|
86
86
|
└─ SetStrip
|
|
87
87
|
└─ SetBar × N (one set's per-rep colour bar)
|
|
88
88
|
|
|
89
|
-
ExerciseCard
|
|
89
|
+
ExerciseCard (all three representations) ──delegates to──▶ ExerciseCardHeading
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
`ExerciseCardHeading`
|
|
92
|
+
**TD-03.55 / TD-03.56 — one row, three densities, and interaction states.**
|
|
93
|
+
`ExerciseCardHeading` IS the exercise row. Its `density` prop carries the only
|
|
94
|
+
axis that ever separated the three call sites:
|
|
95
|
+
|
|
96
|
+
| `density` | shape | was |
|
|
97
|
+
| ---------- | ------------------------------------------------------- | ---------------------------- |
|
|
98
|
+
| `rail` | two lines: name, then prescription beside the tempo | `ExerciseCardHeading` |
|
|
99
|
+
| `compact` | one line: name + prescription, strip below | `ExerciseCard`'s CollapsedCard |
|
|
100
|
+
| `upcoming` | `compact`, dimmed, previous best pinned right | `ExerciseCard`'s UpcomingCard |
|
|
101
|
+
|
|
102
|
+
`CollapsedCard` and `UpcomingCard` are deleted; `ExerciseCard` maps its props onto
|
|
103
|
+
the row, so **its own prop shape is unchanged** and no consumer migrates. Three
|
|
104
|
+
things converged in the process, each a consistency gain rather than a new idea:
|
|
105
|
+
the collapsed row's freeform `3×6 @ 175 lbs` caption became the shared
|
|
106
|
+
`SetsRepsLoad` line; its hand-rolled `VelocityStrip` + `PlaceholderStrip` row
|
|
107
|
+
became one `SetStrip` (so `velocityZones` no longer tints the collapsed glance —
|
|
108
|
+
the expanded header already ignored it); and `isPR` surfaces through the
|
|
109
|
+
`ExerciseIndicator` chip, which is what the expanded header already did.
|
|
110
|
+
|
|
111
|
+
The prescription is a **discriminated union**: either the structured
|
|
112
|
+
`sets`/`reps`/`load` triple, or a free-text `prescription` string for an exercise
|
|
113
|
+
whose numbers aren't loaded, or neither. Mixing them is a type error rather than a
|
|
114
|
+
half-rendered line.
|
|
115
|
+
|
|
116
|
+
Interaction states are `interactive-*` token washes on the row —
|
|
117
|
+
**press > selection > hover** — plus an `isLive` name tone (`status-live`). Every
|
|
118
|
+
one is static: this row renders on the wall during a set, where titan's "the grain
|
|
119
|
+
never animates" rule applies. Hover is web-only; RN Pressable's `onHoverIn` never
|
|
120
|
+
fires on a touch surface, so a touch consumer simply gets no hover state and
|
|
121
|
+
`isSelected` carries the same "this one" meaning. Selection has **no ARIA**:
|
|
122
|
+
`aria-selected` is not allowed on `role="button"`, so the wash is the whole signal
|
|
123
|
+
and list semantics stay the rail's job.
|
|
124
|
+
|
|
125
|
+
Two follow-ups are deliberately NOT taken here. The dim depths still differ by
|
|
126
|
+
density (rail 0.55, card 0.60) because they came from two specimens and converging
|
|
127
|
+
them needs an **opacity token the set does not have** — a foundations decision.
|
|
128
|
+
And `exerciseRowState.ts` (the `interactive-*` → literal-hex map, written in the
|
|
129
|
+
shape of `onSurfaceColors`) should be **promoted into the surface module** the
|
|
130
|
+
moment a second family needs row washes.
|
|
131
|
+
|
|
132
|
+
`SetBar`
|
|
94
133
|
owns the per-set colour/pulse logic; `SetStrip` lays several side by side.
|
|
95
134
|
Beyond the flat `done`/`active`/`todo` sets, `SetStripSet` models set-type
|
|
96
135
|
prescriptions: `range` (variable rep-range — range-max segments, committed-todo grey
|
|
@@ -130,7 +169,7 @@ type props without pulling the dependency.
|
|
|
130
169
|
array (unchanged, still the source of truth for `SetRow` / `ExerciseCard`), the
|
|
131
170
|
strip accepts an optional structured `VelocitySet` descriptor and renders the
|
|
132
171
|
strength set-type vocabulary as a typed slot list. Slots are
|
|
133
|
-
`rep` (velocity-coloured) · `todo` (
|
|
172
|
+
`rep` (velocity-coloured) · `todo` (`border-prominent` grey) · `variable` /
|
|
134
173
|
`continue` (`SET_STRIP_VARIABLE_COLOR` cyan-900; `continue` adds a cyan-800
|
|
135
174
|
outline to read as "keep going"). Types: `straight` (done + grey todo to
|
|
136
175
|
`planned`), `range` (committed grey + a cyan variable window `floor..max`),
|
|
@@ -182,14 +221,22 @@ Modalities`** (each card carries a `Collapse` accordion; promoted from the
|
|
|
182
221
|
when neither side carries one the gutter is omitted entirely. The label keeps the prior
|
|
183
222
|
vertical (rotated) orientation so it never overlaps the bars. Two scales: `hero` (tall
|
|
184
223
|
wings, per-rep m/s velocity labels, a dashed running-best reference line per side) and
|
|
185
|
-
`
|
|
224
|
+
`dual-expanded` (compact — no velocity labels / reference lines, slot names in a narrow
|
|
225
|
+
gutter).
|
|
186
226
|
**Rep-index alignment is the invariant:** column _i_ is rep _i_ on
|
|
187
227
|
both sides, so the set-type slot _kinds_ carry through (rep / todo / variable / continue,
|
|
188
228
|
coloured as in the single strip) but the wide-notch chunk **gaps** (drop / myo / cluster
|
|
189
229
|
boundaries) are intentionally NOT rendered — per-side horizontal gaps would break the
|
|
190
230
|
mirrored L↔R column alignment. Single-voltra sets keep using `VelocityStrip`
|
|
191
|
-
`variant="hero"`. Documented by the `Playground` / `Hero*` / `
|
|
192
|
-
background (`Custom/Workout/DataViz/DualVelocityStrip`).
|
|
231
|
+
`variant="hero"`. Documented by the `Playground` / `Hero*` / `DualExpanded` stories on the
|
|
232
|
+
wall background (`Custom/Workout/DataViz/DualVelocityStrip`).
|
|
233
|
+
|
|
234
|
+
**`variant="rail"` → `variant="dual-expanded"` (VW-97).** `rail` was a verified misnomer —
|
|
235
|
+
this variant is the value-height strip a session-rail row _expands into_, not the rail
|
|
236
|
+
itself (the rail stays 246px and never changes width; expansion lives in a separate detail
|
|
237
|
+
pane, per the VW-97 rail-width decision). `rail` is kept as a **deprecated alias** for one
|
|
238
|
+
release (same renderer, same output) so existing call sites keep working; migrate to
|
|
239
|
+
`dual-expanded` and expect `rail` to be removed in a future release.
|
|
193
240
|
|
|
194
241
|
**Reuse audit — `DashedReferenceLine` (in-file today, top-level follow-up).** The dashed
|
|
195
242
|
running-best line was hand-rolled three times inside `VelocityStrip.tsx` (the single
|
|
@@ -303,8 +350,7 @@ responsive level views land (removal is out of scope for this scaffold ticket):
|
|
|
303
350
|
(Workout Expansion), `S3SetTypes` (Set Types), `S3SessionPace` (Session Pace).
|
|
304
351
|
Each is superseded by a real Shell/SessionRail component + its Storybook stories;
|
|
305
352
|
`S3SessionPace` in particular is superseded by the `SessionHeader` pace glance.
|
|
306
|
-
- **`ExerciseCard`'s `collapsed` / `upcoming` state representations** —
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
`ExerciseCard` are the superseded representations to retire.
|
|
353
|
+
- **`ExerciseCard`'s `collapsed` / `upcoming` state representations** — DONE
|
|
354
|
+
(TD-03.56). Both hand-rolled sub-cards are deleted; `ExerciseCard` now delegates
|
|
355
|
+
all three representations to `ExerciseCardHeading` and its `density` prop. What
|
|
356
|
+
remains to retire is the expanded view, which is moving to `ExpandedDrawer`.
|
|
@@ -3,11 +3,13 @@ import { View, Text, Pressable } from 'react-native'
|
|
|
3
3
|
import { Card } from '../../ui/card'
|
|
4
4
|
import { Badge, type BadgeColor } from '../../ui/badge'
|
|
5
5
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
6
|
+
import { alpha } from '../../../utils/colors'
|
|
6
7
|
|
|
7
8
|
const t = getSemanticColors('dark')
|
|
8
9
|
|
|
9
10
|
const BRAND_PRIMARY = t['brand-primary']
|
|
10
|
-
const
|
|
11
|
+
const ON_BRAND_PRIMARY = t['on-brand-primary']
|
|
12
|
+
const BRAND_PRIMARY_SUBTLE = alpha(BRAND_PRIMARY, 0.12)
|
|
11
13
|
const STATUS_SUCCESS = t['status-success']
|
|
12
14
|
const STATUS_WARNING = t['status-warning']
|
|
13
15
|
const STATUS_ERROR = t['status-error']
|
|
@@ -274,7 +276,7 @@ export function ReadinessCheck({
|
|
|
274
276
|
fontSize: 13,
|
|
275
277
|
fontWeight: '700',
|
|
276
278
|
fontFamily: 'Inter, sans-serif',
|
|
277
|
-
color:
|
|
279
|
+
color: ON_BRAND_PRIMARY,
|
|
278
280
|
}}
|
|
279
281
|
>
|
|
280
282
|
Start Workout
|