@titan-design/react-ui 0.10.0 → 0.12.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 +173 -343
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +173 -343
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +416 -18
- package/dist/index.d.ts +416 -18
- package/dist/index.js +2512 -1307
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2480 -1299
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
- package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1644 -1575
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1645 -1576
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +409 -415
- package/dist/theme/index.d.ts +409 -415
- package/dist/theme/index.js +234 -408
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +227 -399
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +141 -140
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +141 -140
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +8 -3
- package/src/arch/Architecture.stories.tsx +9 -9
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
- package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
- package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
- package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
- package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
- package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
- package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
- package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
- package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
- package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
- package/src/components/custom/Fatigue/README.md +93 -0
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
- package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
- package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
- package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
- package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
- package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
- package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
- package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
- package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
- package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
- package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
- package/src/components/custom/Fatigue/index.ts +41 -0
- package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
- package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
- package/src/components/custom/Sidebar/Sidebar.tsx +4 -4
- package/src/components/custom/Table/Table.tsx +2 -2
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
- package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
- package/src/components/custom/Workout/BaseBadge.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.tsx +2 -2
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
- package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
- package/src/components/custom/Workout/DeviationBar.tsx +3 -2
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +7 -7
- package/src/components/custom/Workout/ExerciseCard.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +11 -5
- package/src/components/custom/Workout/InputBar.tsx +2 -2
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
- package/src/components/custom/Workout/MesoCard.tsx +3 -1
- package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
- package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
- package/src/components/custom/Workout/RestTimer.tsx +2 -2
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionHeader.tsx +1 -1
- package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +63 -18
- package/src/components/custom/Workout/SetBar.tsx +3 -3
- package/src/components/custom/Workout/SetRow.test.tsx +12 -9
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/StatusDot.tsx +5 -4
- package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
- package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -2
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
- package/src/components/custom/Workout/VelocityStrip.test.tsx +199 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
- package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
- package/src/components/custom/Workout/WeightBadge.tsx +2 -1
- package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
- package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
- package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
- package/src/components/custom/charts/SetBarChart.tsx +651 -0
- package/src/components/custom/charts/live-rep-growth.ts +91 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/custom/stepper/Stepper.tsx +3 -3
- package/src/components/shell/DeviceMenu.tsx +2 -2
- package/src/components/shell/SideNav.tsx +1 -1
- package/src/components/shell/TopBar.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -2
- package/src/components/ui/badge/Badge.tsx +2 -2
- package/src/components/ui/card/Card.tsx +3 -3
- package/src/components/ui/checkbox/Checkbox.tsx +1 -1
- package/src/components/ui/collapse/Collapse.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.tsx +3 -3
- package/src/components/ui/form-field/FormField.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.tsx +1 -1
- package/src/components/ui/menu/Menu.tsx +2 -2
- package/src/components/ui/pill/Pill.tsx +2 -2
- package/src/components/ui/popover/Popover.stories.tsx +2 -2
- package/src/components/ui/popover/Popover.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +3 -3
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.tsx +4 -4
- package/src/components/ui/select/Select.tsx +3 -3
- package/src/components/ui/surface/Surface.stories.tsx +3 -3
- package/src/components/ui/surface/Surface.test.tsx +32 -19
- package/src/components/ui/surface/Surface.tsx +3 -3
- package/src/components/ui/surface/SurfaceContext.ts +19 -29
- package/src/components/ui/surface/surface.contract.test.ts +57 -44
- package/src/components/ui/switch/Switch.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +2 -2
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
- package/src/stories/PresetShowcase.stories.tsx +21 -21
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +874 -0
- package/src/theme/ColorPrimitives.stories.tsx +348 -0
- package/src/theme/Depth.stories.tsx +312 -0
- package/src/theme/DepthCalibration.stories.tsx +655 -0
- package/src/theme/ThemeProvider.tsx +1 -1
- package/src/theme/barrel.ts +7 -14
- package/src/theme/color-stories.coverage.test.ts +125 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/theme/color-utils.ts +197 -0
- package/src/theme/config.ts +0 -8
- package/src/theme/depth-calibration.test.tsx +97 -0
- package/src/theme/elevation.test.ts +62 -1
- package/src/theme/elevation.ts +34 -8
- package/src/theme/global.css +47 -53
- package/src/theme/grey-ramp.test.ts +166 -0
- package/src/theme/materials.test.ts +106 -0
- package/src/theme/materials.ts +211 -0
- package/src/theme/presets/audiobook.ts +4 -4
- package/src/theme/presets/types.ts +3 -3
- package/src/theme/semantic-contrast.test.ts +83 -0
- package/src/theme/tokens/primitives.ts +62 -115
- package/src/theme/tokens/semantic.ts +130 -82
- package/src/utils/colors.ts +1 -1
- package/tailwind.config.js +2 -5
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
- package/src/theme/ColorSystem.stories.tsx +0 -508
- package/src/theme/Colors.stories.tsx +0 -323
- package/src/theme/shadows.stories.tsx +0 -523
- package/src/theme/shadows.ts +0 -439
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getVelocityZoneName,
|
|
8
8
|
calculateVelocityLoss,
|
|
9
9
|
calculateMeanVelocity,
|
|
10
|
+
getVelocityLossColor,
|
|
10
11
|
} from './VelocityStrip'
|
|
11
12
|
|
|
12
13
|
const sampleVelocities = [1.1, 0.95, 0.82, 0.68, 0.45]
|
|
@@ -29,7 +30,7 @@ describe('VelocityStrip expanded (framed chart)', () => {
|
|
|
29
30
|
).toBeInTheDocument()
|
|
30
31
|
})
|
|
31
32
|
|
|
32
|
-
it('
|
|
33
|
+
it('collapses to the flat state (tap-to-expand, no info row) when expanded is false', () => {
|
|
33
34
|
render(<VelocityStrip velocities={sampleVelocities} expanded={false} />)
|
|
34
35
|
expect(
|
|
35
36
|
screen.getByLabelText('Velocity chart for set, 5 reps, tap to expand')
|
|
@@ -78,17 +79,28 @@ describe('VelocityStrip expanded (framed chart)', () => {
|
|
|
78
79
|
})
|
|
79
80
|
|
|
80
81
|
describe('scale', () => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
// Folded onto SetBarChart: bars are px value-height (not %); the bare spotlight has static
|
|
83
|
+
// heights (no expand animation), so we assert the taller-rep-reads-taller scaling relationship.
|
|
84
|
+
const barPx = (id: string) => parseFloat(getComputedStyle(screen.getByTestId(id)).height)
|
|
85
|
+
const bareScale = {
|
|
86
|
+
variant: 'expanded' as const,
|
|
87
|
+
showNumbers: false,
|
|
88
|
+
showInfo: false,
|
|
89
|
+
height: 60,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
it('peak (default) scales the tallest rep taller than a smaller one', () => {
|
|
93
|
+
render(<VelocityStrip {...bareScale} velocities={[1.0, 0.5]} />)
|
|
94
|
+
expect(barPx('velocity-bar-0')).toBeGreaterThan(barPx('velocity-bar-1'))
|
|
95
|
+
// peak: 1.0 fills nearly the whole 60px plot.
|
|
96
|
+
expect(barPx('velocity-bar-0')).toBeGreaterThan(52)
|
|
85
97
|
})
|
|
86
98
|
|
|
87
|
-
it('fixed scales against
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
expect(
|
|
91
|
-
expect(
|
|
99
|
+
it('fixed scales against a ceiling above the set max (bars shorter than peak)', () => {
|
|
100
|
+
render(<VelocityStrip {...bareScale} scale="fixed" velocities={[1.0, 0.5]} />)
|
|
101
|
+
// fixed ceiling 1.15 > 1.0, so the tallest bar no longer fills the plot; 0.5 is ~half of 1.0.
|
|
102
|
+
expect(barPx('velocity-bar-0')).toBeLessThan(56)
|
|
103
|
+
expect(barPx('velocity-bar-1')).toBeLessThan(barPx('velocity-bar-0'))
|
|
92
104
|
})
|
|
93
105
|
})
|
|
94
106
|
|
|
@@ -116,31 +128,31 @@ describe('VelocityStrip expanded (framed chart)', () => {
|
|
|
116
128
|
})
|
|
117
129
|
})
|
|
118
130
|
|
|
119
|
-
describe('VelocityStrip
|
|
120
|
-
it('renders the flat
|
|
121
|
-
render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
122
|
-
expect(screen.getByTestId('velocity-strip-
|
|
131
|
+
describe('VelocityStrip compact variant (flat resting strip)', () => {
|
|
132
|
+
it('renders the flat compact strip', () => {
|
|
133
|
+
render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
|
|
134
|
+
expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
|
|
123
135
|
})
|
|
124
136
|
|
|
125
137
|
it('does not respond to press', () => {
|
|
126
|
-
render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
138
|
+
render(<VelocityStrip velocities={sampleVelocities} variant="compact" onToggle={() => {}} />)
|
|
127
139
|
expect(screen.queryByTestId('velocity-strip-pressable')).not.toBeInTheDocument()
|
|
128
140
|
})
|
|
129
141
|
|
|
130
142
|
it('renders one bar per rep', () => {
|
|
131
|
-
render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
143
|
+
render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
|
|
132
144
|
for (let i = 0; i < sampleVelocities.length; i++) {
|
|
133
145
|
expect(screen.getByTestId(`velocity-bar-${i}`)).toBeInTheDocument()
|
|
134
146
|
}
|
|
135
147
|
})
|
|
136
148
|
|
|
137
149
|
it('has no accessibility violations', async () => {
|
|
138
|
-
const { container } = render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
150
|
+
const { container } = render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
|
|
139
151
|
expect(await axe(container)).toHaveNoViolations()
|
|
140
152
|
})
|
|
141
153
|
|
|
142
|
-
it('does not apply live marking', () => {
|
|
143
|
-
render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} variant="
|
|
154
|
+
it('does not apply live marking (compact is the resting form)', () => {
|
|
155
|
+
render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} variant="compact" />)
|
|
144
156
|
expect(screen.queryByLabelText(/latest rep/)).not.toBeInTheDocument()
|
|
145
157
|
})
|
|
146
158
|
})
|
|
@@ -216,6 +228,94 @@ describe('calculateMeanVelocity', () => {
|
|
|
216
228
|
})
|
|
217
229
|
})
|
|
218
230
|
|
|
231
|
+
// Literal hex for the four VL-band tones (green-300 / amber-300 / orange-400 / red-600),
|
|
232
|
+
// the SAME stops FatigueMeter's VL10/VL20/VL30 default thresholds use.
|
|
233
|
+
const VL_GREEN = '#2ED573'
|
|
234
|
+
const VL_YELLOW = '#F9B415'
|
|
235
|
+
const VL_ORANGE = '#FF7900'
|
|
236
|
+
const VL_RED = '#D14343'
|
|
237
|
+
|
|
238
|
+
describe('getVelocityLossColor', () => {
|
|
239
|
+
it('reads green below the VL10 threshold', () => {
|
|
240
|
+
expect(getVelocityLossColor(0)).toBe(VL_GREEN)
|
|
241
|
+
expect(getVelocityLossColor(9.9)).toBe(VL_GREEN)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('reads gold/amber from VL10 up to (not including) VL20', () => {
|
|
245
|
+
expect(getVelocityLossColor(10)).toBe(VL_YELLOW)
|
|
246
|
+
expect(getVelocityLossColor(19.9)).toBe(VL_YELLOW)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('reads orange from VL20 up to (not including) VL30', () => {
|
|
250
|
+
expect(getVelocityLossColor(20)).toBe(VL_ORANGE)
|
|
251
|
+
expect(getVelocityLossColor(29.9)).toBe(VL_ORANGE)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('reads red at and past VL30', () => {
|
|
255
|
+
expect(getVelocityLossColor(30)).toBe(VL_RED)
|
|
256
|
+
expect(getVelocityLossColor(100)).toBe(VL_RED)
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
describe('VelocityStrip barColor prop', () => {
|
|
261
|
+
// Best rep is 1.0 m/s, so per-rep loss lands exactly on the VL10/20/30 boundaries:
|
|
262
|
+
// 0%, 10%, 20%, 30% loss — one rep in each band.
|
|
263
|
+
const decliningSet = [1.0, 0.9, 0.8, 0.7]
|
|
264
|
+
|
|
265
|
+
it('defaults to loss coloring (the landed default is barColor="loss")', () => {
|
|
266
|
+
render(<VelocityStrip velocities={decliningSet} variant="compact" />)
|
|
267
|
+
// Loss from the set's own best (1.0): 0% / 10% / 20% / 30% -> green / yellow / orange / red.
|
|
268
|
+
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
269
|
+
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: VL_YELLOW })
|
|
270
|
+
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ backgroundColor: VL_ORANGE })
|
|
271
|
+
expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ backgroundColor: VL_RED })
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('explicit barColor="zone" matches the default', () => {
|
|
275
|
+
render(<VelocityStrip velocities={decliningSet} variant="compact" barColor="zone" />)
|
|
276
|
+
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: VL_YELLOW })
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('barColor="loss" colors each bar green→red by loss from the set\'s own best', () => {
|
|
280
|
+
render(<VelocityStrip velocities={decliningSet} variant="compact" barColor="loss" />)
|
|
281
|
+
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
282
|
+
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: VL_YELLOW })
|
|
283
|
+
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ backgroundColor: VL_ORANGE })
|
|
284
|
+
expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ backgroundColor: VL_RED })
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('barColor="loss" reads a slow-but-still-best rep as green (loss-relative, not absolute)', () => {
|
|
288
|
+
// 0.3 m/s would be "vel-red" under the absolute zone scale, but as the set's
|
|
289
|
+
// (single) best rep its loss is 0 — loss-relative coloring reads it green.
|
|
290
|
+
render(<VelocityStrip velocities={[0.3]} variant="compact" barColor="loss" />)
|
|
291
|
+
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('barColor="loss" ignores a supplied zones prop (it is a separate scale)', () => {
|
|
295
|
+
render(
|
|
296
|
+
<VelocityStrip
|
|
297
|
+
velocities={decliningSet}
|
|
298
|
+
variant="compact"
|
|
299
|
+
barColor="loss"
|
|
300
|
+
zones={compoundBands}
|
|
301
|
+
/>
|
|
302
|
+
)
|
|
303
|
+
expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ backgroundColor: VL_RED })
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('barColor="loss" guards all-zero velocities to green (no NaN/best<=0)', () => {
|
|
307
|
+
render(<VelocityStrip velocities={[0, 0, 0]} variant="compact" barColor="loss" />)
|
|
308
|
+
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
309
|
+
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('renders no bars (and does not crash) for an empty velocities array', () => {
|
|
313
|
+
render(<VelocityStrip velocities={[]} variant="compact" barColor="loss" />)
|
|
314
|
+
expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
|
|
315
|
+
expect(screen.queryAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(0)
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
219
319
|
// WA-shaped 5-band zone set (compound movement-class defaults, mean m/s).
|
|
220
320
|
const compoundBands = [
|
|
221
321
|
{ id: 'grinding', label: 'Grinding', min: 0, max: 0.35 },
|
|
@@ -226,14 +326,29 @@ const compoundBands = [
|
|
|
226
326
|
] as const
|
|
227
327
|
|
|
228
328
|
describe('VelocityStrip zones prop', () => {
|
|
329
|
+
// The band mapping is the `zone` scale; the landed default is `loss`, so these pass `barColor="zone"`.
|
|
229
330
|
it('colors bars from the supplied bands (mini)', () => {
|
|
230
|
-
render(
|
|
331
|
+
render(
|
|
332
|
+
<VelocityStrip
|
|
333
|
+
velocities={[1.1, 0.45]}
|
|
334
|
+
zones={compoundBands}
|
|
335
|
+
variant="compact"
|
|
336
|
+
barColor="zone"
|
|
337
|
+
/>
|
|
338
|
+
)
|
|
231
339
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
|
|
232
340
|
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#D14343' })
|
|
233
341
|
})
|
|
234
342
|
|
|
235
343
|
it('gives grinding and maximalStrength DISTINCT reds (5-band, no collapse)', () => {
|
|
236
|
-
render(
|
|
344
|
+
render(
|
|
345
|
+
<VelocityStrip
|
|
346
|
+
velocities={[0.45, 0.2]}
|
|
347
|
+
zones={compoundBands}
|
|
348
|
+
variant="compact"
|
|
349
|
+
barColor="zone"
|
|
350
|
+
/>
|
|
351
|
+
)
|
|
237
352
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#D14343' })
|
|
238
353
|
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#A4221C' })
|
|
239
354
|
})
|
|
@@ -244,7 +359,7 @@ describe('VelocityStrip zones prop', () => {
|
|
|
244
359
|
})
|
|
245
360
|
|
|
246
361
|
it('falls back to the default scale when zones is absent', () => {
|
|
247
|
-
render(<VelocityStrip velocities={[1.1]} variant="
|
|
362
|
+
render(<VelocityStrip velocities={[1.1]} variant="compact" />)
|
|
248
363
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
|
|
249
364
|
})
|
|
250
365
|
})
|
|
@@ -257,16 +372,17 @@ describe('VelocityStrip live mode', () => {
|
|
|
257
372
|
else delete (window as { matchMedia?: unknown }).matchMedia
|
|
258
373
|
})
|
|
259
374
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
375
|
+
// The bars are now SetBarChart's (a11y-hidden; live-grow is the visual marking via `liveRepIndex`).
|
|
376
|
+
const spotlight = { variant: 'expanded' as const, showNumbers: false, showInfo: false }
|
|
377
|
+
|
|
378
|
+
it('renders the newest rep in the live spotlight', () => {
|
|
379
|
+
render(<VelocityStrip {...spotlight} velocities={sampleVelocities} liveRepIndex={3} />)
|
|
380
|
+
expect(screen.getByTestId('velocity-bar-3')).toBeInTheDocument()
|
|
263
381
|
})
|
|
264
382
|
|
|
265
|
-
it('
|
|
266
|
-
render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} />)
|
|
267
|
-
expect(
|
|
268
|
-
screen.getByLabelText(/Rep 1: 1\.10 meters per second, latest rep, new set peak$/)
|
|
269
|
-
).toBeInTheDocument()
|
|
383
|
+
it('renders a new-set-peak live rep without crashing', () => {
|
|
384
|
+
render(<VelocityStrip {...spotlight} velocities={sampleVelocities} liveRepIndex={0} />)
|
|
385
|
+
expect(screen.getByTestId('velocity-bar-0')).toBeInTheDocument()
|
|
270
386
|
})
|
|
271
387
|
|
|
272
388
|
it('renders without animation when prefers-reduced-motion is set', () => {
|
|
@@ -275,48 +391,59 @@ describe('VelocityStrip live mode', () => {
|
|
|
275
391
|
addEventListener: vi.fn(),
|
|
276
392
|
removeEventListener: vi.fn(),
|
|
277
393
|
}) as unknown as typeof window.matchMedia
|
|
278
|
-
render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} />)
|
|
394
|
+
render(<VelocityStrip {...spotlight} velocities={sampleVelocities} liveRepIndex={0} />)
|
|
279
395
|
expect(screen.getByTestId('velocity-bar-0')).toBeInTheDocument()
|
|
280
396
|
})
|
|
281
397
|
})
|
|
282
398
|
|
|
283
399
|
describe('VelocityStrip all-zero velocities (NaN guard)', () => {
|
|
284
|
-
it('emits a valid
|
|
285
|
-
render(
|
|
286
|
-
|
|
287
|
-
|
|
400
|
+
it('emits a valid (non-NaN) bar height when every velocity is zero', () => {
|
|
401
|
+
render(
|
|
402
|
+
<VelocityStrip
|
|
403
|
+
velocities={[0, 0, 0]}
|
|
404
|
+
variant="expanded"
|
|
405
|
+
showNumbers={false}
|
|
406
|
+
showInfo={false}
|
|
407
|
+
/>
|
|
408
|
+
)
|
|
409
|
+
const h = getComputedStyle(screen.getByTestId('velocity-bar-0')).height
|
|
410
|
+
expect(h).not.toContain('NaN')
|
|
411
|
+
expect(parseFloat(h)).toBeGreaterThanOrEqual(0)
|
|
288
412
|
})
|
|
289
413
|
})
|
|
290
414
|
|
|
291
415
|
// Literal-hex slot colors (must match the component's tokens exactly).
|
|
292
|
-
|
|
416
|
+
// TODO_SOLID = the LANDED surface-relative solid to-do tone (base/dark plane blended toward the
|
|
417
|
+
// on-surface neutral) — replaced the old fixed grey #2C2C2C. Solid fill, not a dashed outline.
|
|
418
|
+
// Pinned rather than recomputed on purpose: deriving it here would just mirror
|
|
419
|
+
// SetBarChart's mixHex and stop catching a change to the blend itself.
|
|
420
|
+
// = mixHex(grey-925, text-tertiary, 0.55); moved with the warm ramp (TD-07.14).
|
|
421
|
+
const TODO_SOLID = '#5B5957'
|
|
293
422
|
const VARIABLE_CYAN = '#0B3149'
|
|
294
423
|
const CONTINUE_OUTLINE = '#22465F'
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
describe('VelocityStrip set-type modes (mini)', () => {
|
|
300
|
-
it('straight: done reps colored + grey todo remainder to the planned count', () => {
|
|
424
|
+
describe('VelocityStrip set-type modes (compact)', () => {
|
|
425
|
+
// compact composes SetBarChart, so the per-slot geometry (WIDE chunk-notch px, container gap) is
|
|
426
|
+
// covered in SetBarChart.test; here we verify VelocityStrip maps each set type to the right slots.
|
|
427
|
+
it('straight: done reps colored + solid todo remainder to the planned count', () => {
|
|
301
428
|
render(
|
|
302
429
|
<VelocityStrip
|
|
303
430
|
set={{ type: 'straight', velocities: [1.1, 0.9, 0.8], planned: 5 }}
|
|
304
|
-
variant="
|
|
431
|
+
variant="compact"
|
|
305
432
|
/>
|
|
306
433
|
)
|
|
307
434
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(3)
|
|
308
435
|
expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(2)
|
|
309
436
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
|
|
310
437
|
expect(screen.getAllByTestId('velocity-slot-todo')[0]).toHaveStyle({
|
|
311
|
-
backgroundColor:
|
|
438
|
+
backgroundColor: TODO_SOLID,
|
|
312
439
|
})
|
|
313
440
|
})
|
|
314
441
|
|
|
315
|
-
it('range: committed
|
|
442
|
+
it('range: committed reps + solid floor todo + a cyan variable window floor..max', () => {
|
|
316
443
|
render(
|
|
317
444
|
<VelocityStrip
|
|
318
445
|
set={{ type: 'range', velocities: [1.1, 1.0, 0.9, 0.85, 0.8, 0.7], floor: 8, max: 12 }}
|
|
319
|
-
variant="
|
|
446
|
+
variant="compact"
|
|
320
447
|
/>
|
|
321
448
|
)
|
|
322
449
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(6)
|
|
@@ -327,15 +454,15 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
327
454
|
})
|
|
328
455
|
})
|
|
329
456
|
|
|
330
|
-
it('amrap: done reps + one trailing
|
|
331
|
-
render(<VelocityStrip set={{ type: 'amrap', velocities: [1.1, 1.0, 0.9] }} variant="
|
|
457
|
+
it('amrap: done reps + one trailing cyan-outline continue slot', () => {
|
|
458
|
+
render(<VelocityStrip set={{ type: 'amrap', velocities: [1.1, 1.0, 0.9] }} variant="compact" />)
|
|
332
459
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(3)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
460
|
+
expect(screen.getByTestId('velocity-slot-continue')).toHaveStyle({
|
|
461
|
+
borderTopColor: CONTINUE_OUTLINE,
|
|
462
|
+
})
|
|
336
463
|
})
|
|
337
464
|
|
|
338
|
-
it('drop:
|
|
465
|
+
it('drop: one bar per rep across the sub-loads', () => {
|
|
339
466
|
render(
|
|
340
467
|
<VelocityStrip
|
|
341
468
|
set={{
|
|
@@ -346,17 +473,13 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
346
473
|
[0.6, 0.5],
|
|
347
474
|
],
|
|
348
475
|
}}
|
|
349
|
-
variant="
|
|
476
|
+
variant="compact"
|
|
350
477
|
/>
|
|
351
478
|
)
|
|
352
479
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(6)
|
|
353
|
-
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ marginLeft: '0px' })
|
|
354
|
-
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ marginLeft: REP_SLOT_ML })
|
|
355
|
-
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
356
|
-
expect(screen.getByTestId('velocity-bar-4')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
357
480
|
})
|
|
358
481
|
|
|
359
|
-
it('myo: activation + clusters
|
|
482
|
+
it('myo: activation + clusters as reps, open adds a cyan continue', () => {
|
|
360
483
|
render(
|
|
361
484
|
<VelocityStrip
|
|
362
485
|
set={{
|
|
@@ -368,16 +491,14 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
368
491
|
],
|
|
369
492
|
open: true,
|
|
370
493
|
}}
|
|
371
|
-
variant="
|
|
494
|
+
variant="compact"
|
|
372
495
|
/>
|
|
373
496
|
)
|
|
374
497
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(7)
|
|
375
|
-
expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
376
|
-
expect(screen.getByTestId('velocity-bar-5')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
377
498
|
expect(screen.getByTestId('velocity-slot-continue')).toBeInTheDocument()
|
|
378
499
|
})
|
|
379
500
|
|
|
380
|
-
it('cluster: fixed count
|
|
501
|
+
it('cluster: fixed count + solid planned remainder', () => {
|
|
381
502
|
render(
|
|
382
503
|
<VelocityStrip
|
|
383
504
|
set={{
|
|
@@ -386,22 +507,19 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
386
507
|
groupSize: 2,
|
|
387
508
|
planned: 8,
|
|
388
509
|
}}
|
|
389
|
-
variant="
|
|
510
|
+
variant="compact"
|
|
390
511
|
/>
|
|
391
512
|
)
|
|
392
513
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(5)
|
|
393
514
|
expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(3)
|
|
394
|
-
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
395
|
-
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ marginLeft: REP_SLOT_ML })
|
|
396
515
|
})
|
|
397
516
|
|
|
398
|
-
it('back-compat: a velocities-only
|
|
399
|
-
render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
517
|
+
it('back-compat: a velocities-only compact strip renders rep bars, no todo', () => {
|
|
518
|
+
render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
|
|
400
519
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(sampleVelocities.length)
|
|
401
520
|
expect(screen.queryByTestId('velocity-slot-todo')).not.toBeInTheDocument()
|
|
402
|
-
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ marginLeft: '0px' })
|
|
403
521
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
|
|
404
|
-
expect(screen.getByTestId('velocity-strip-
|
|
522
|
+
expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
|
|
405
523
|
})
|
|
406
524
|
|
|
407
525
|
it('derives the summary from a set descriptor (mean / loss info row)', () => {
|
|
@@ -410,14 +528,14 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
410
528
|
})
|
|
411
529
|
|
|
412
530
|
it('labels the strip per set type', () => {
|
|
413
|
-
render(<VelocityStrip set={{ type: 'amrap', velocities: [1.0, 0.9] }} variant="
|
|
531
|
+
render(<VelocityStrip set={{ type: 'amrap', velocities: [1.0, 0.9] }} variant="compact" />)
|
|
414
532
|
expect(
|
|
415
533
|
screen.getByLabelText('Velocity strip, AMRAP set, 2 reps and counting')
|
|
416
534
|
).toBeInTheDocument()
|
|
417
535
|
})
|
|
418
536
|
|
|
419
537
|
it('renders nothing when neither velocities nor set is provided', () => {
|
|
420
|
-
const { container } = render(<VelocityStrip variant="
|
|
538
|
+
const { container } = render(<VelocityStrip variant="compact" />)
|
|
421
539
|
expect(container.firstChild).toBeNull()
|
|
422
540
|
})
|
|
423
541
|
})
|
|
@@ -428,7 +546,7 @@ describe('VelocityStrip set-type modes (expanded framed)', () => {
|
|
|
428
546
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(2)
|
|
429
547
|
expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(2)
|
|
430
548
|
expect(screen.getAllByTestId('velocity-slot-todo')[0]).toHaveStyle({
|
|
431
|
-
backgroundColor:
|
|
549
|
+
backgroundColor: TODO_SOLID,
|
|
432
550
|
})
|
|
433
551
|
})
|
|
434
552
|
|
|
@@ -459,8 +577,8 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
|
|
|
459
577
|
set={{ type: 'straight', velocities: [1.0, 0.8], planned: 2 }}
|
|
460
578
|
/>
|
|
461
579
|
)
|
|
462
|
-
expect(screen.getByTestId('velocity-strip-
|
|
463
|
-
expect(screen.queryByTestId('velocity-strip-
|
|
580
|
+
expect(screen.getByTestId('velocity-strip-spotlight')).toBeInTheDocument()
|
|
581
|
+
expect(screen.queryByTestId('velocity-strip-compact')).not.toBeInTheDocument()
|
|
464
582
|
expect(screen.queryByTestId('velocity-strip')).not.toBeInTheDocument()
|
|
465
583
|
})
|
|
466
584
|
|
|
@@ -500,7 +618,8 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
|
|
|
500
618
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(1)
|
|
501
619
|
const stubs = screen.getAllByTestId('velocity-slot-todo')
|
|
502
620
|
expect(stubs).toHaveLength(2)
|
|
503
|
-
|
|
621
|
+
// Folded onto SetBarChart: the solid to-do stub is the shared 9px height + surface-relative tone.
|
|
622
|
+
expect(stubs[0]).toHaveStyle({ height: '9px', backgroundColor: TODO_SOLID })
|
|
504
623
|
})
|
|
505
624
|
|
|
506
625
|
it('colors performed reps from the default effort scale', () => {
|
|
@@ -514,7 +633,7 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
|
|
|
514
633
|
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#D14343' })
|
|
515
634
|
})
|
|
516
635
|
|
|
517
|
-
it('carries the set-type
|
|
636
|
+
it('carries the set-type chunk encoding (drop sub-loads) as one bar per rep', () => {
|
|
518
637
|
render(
|
|
519
638
|
<VelocityStrip
|
|
520
639
|
{...bareProps}
|
|
@@ -527,8 +646,9 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
|
|
|
527
646
|
}}
|
|
528
647
|
/>
|
|
529
648
|
)
|
|
530
|
-
|
|
531
|
-
|
|
649
|
+
// Folded onto SetBarChart: 4 rep bars; the WIDE chunk-notch (now a proportional column margin)
|
|
650
|
+
// is covered in SetBarChart.test.
|
|
651
|
+
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(4)
|
|
532
652
|
})
|
|
533
653
|
|
|
534
654
|
it('has no chrome: no per-bar labels, no info row, no pressable', () => {
|
|
@@ -576,7 +696,7 @@ describe('VelocityStrip hero variant', () => {
|
|
|
576
696
|
expect(screen.getByTestId('velocity-label-0')).toHaveTextContent('0.90')
|
|
577
697
|
})
|
|
578
698
|
|
|
579
|
-
it('draws a
|
|
699
|
+
it('draws a to-do placeholder for each rep still to come (target − done)', () => {
|
|
580
700
|
render(<VelocityStrip velocities={heroSet} variant="hero" targetReps={8} />)
|
|
581
701
|
expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(4)
|
|
582
702
|
})
|