@titan-design/react-ui 0.5.0 → 0.7.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 +241 -189
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +241 -189
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +1017 -130
- package/dist/index.d.ts +1017 -130
- package/dist/index.js +3058 -1090
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2992 -1091
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-8u_4WbL-.d.mts} +280 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-DaWiBOGa.d.ts} +280 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1797 -1292
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1797 -1292
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +464 -268
- package/dist/theme/index.d.ts +464 -268
- package/dist/theme/index.js +303 -191
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +295 -192
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +160 -143
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +160 -143
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +3 -1
- package/src/arch/Architecture.stories.tsx +870 -0
- package/src/arch/annotations.json +48 -0
- package/src/arch/arch-graph.json +4301 -0
- package/src/components/custom/DateTime/DateTime.stories.tsx +83 -7
- package/src/components/custom/DateTime/DateTime.test.tsx +29 -3
- package/src/components/custom/DateTime/DateTime.tsx +55 -15
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.test.tsx +2 -2
- package/src/components/custom/Gauge/Gauge.tsx +6 -3
- package/src/components/custom/Metric/Metric.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.tsx +3 -5
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
- package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
- package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
- package/src/components/custom/TimerReadout/index.ts +1 -0
- package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.tsx +3 -5
- package/src/components/custom/Typography/Typography.stories.tsx +46 -28
- 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.stories.tsx +21 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +11 -16
- package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.tsx +6 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +6 -3
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
- 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.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.test.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.tsx +7 -4
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
- package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
- package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +6 -6
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
- package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
- package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
- package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
- package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
- package/src/components/custom/Workout/InputBar.tsx +2 -1
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -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/LiveAuraFrame.stories.tsx +153 -0
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
- package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoCard.tsx +8 -3
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- 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.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoStatusCard.tsx +20 -13
- package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
- package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
- package/src/components/custom/Workout/MetricTiles.tsx +48 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
- package/src/components/custom/Workout/MuscleGroupChip.tsx +7 -4
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.tsx +5 -2
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
- package/src/components/custom/Workout/PrHistoryModal.tsx +4 -3
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +10 -7
- package/src/components/custom/Workout/README.md +146 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/ReadinessCheck.tsx +7 -4
- package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.tsx +17 -11
- package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
- package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
- package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
- package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
- package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
- package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
- package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
- package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
- package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
- package/src/components/custom/Workout/SessionHeader.tsx +152 -0
- package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
- package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
- package/src/components/custom/Workout/SessionRail.tsx +126 -0
- package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
- package/src/components/custom/Workout/SetBar.test.tsx +123 -0
- package/src/components/custom/Workout/SetBar.tsx +171 -0
- package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
- package/src/components/custom/Workout/SetRow.test.tsx +94 -166
- package/src/components/custom/Workout/SetRow.tsx +170 -232
- package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
- package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
- package/src/components/custom/Workout/SetStrip.tsx +71 -0
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
- package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
- package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
- package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
- package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/Sparkline.tsx +2 -1
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -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/StatusPill.stories.tsx +59 -0
- package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
- package/src/components/custom/Workout/StatusPill.tsx +88 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
- package/src/components/custom/Workout/StrengthTrendChart.tsx +9 -6
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
- package/src/components/custom/Workout/SupersetWrapper.tsx +2 -1
- package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +29 -36
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
- package/src/components/custom/Workout/VelocityStrip.test.tsx +379 -144
- package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
- package/src/components/custom/Workout/WeekRow.tsx +3 -2
- package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/WeightBadge.tsx +6 -2
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
- package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
- package/src/components/custom/Workout/WorkoutCard.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +12 -8
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
- package/src/components/custom/Workout/icons.tsx +6 -67
- package/src/components/custom/Workout/index.ts +55 -2
- package/src/components/custom/Workout/metricText.stories.tsx +69 -0
- package/src/components/custom/Workout/metricText.tsx +34 -0
- package/src/components/custom/Workout/paceTone.test.ts +29 -0
- package/src/components/custom/Workout/paceTone.ts +27 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/icons/SvgIcon.tsx +50 -0
- package/src/components/icons/icons.stories.tsx +122 -0
- package/src/components/icons/icons.test.tsx +73 -0
- package/src/components/icons/icons.tsx +153 -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/DashboardShell.stories.tsx +65 -0
- package/src/components/shell/DashboardShell.test.tsx +42 -0
- package/src/components/shell/DashboardShell.tsx +74 -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/NavItem.stories.tsx +52 -0
- package/src/components/shell/NavItem.test.tsx +35 -0
- package/src/components/shell/NavItem.tsx +75 -0
- package/src/components/shell/README.md +116 -0
- package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -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/SideNav.stories.tsx +81 -0
- package/src/components/shell/SideNav.test.tsx +51 -0
- package/src/components/shell/SideNav.tsx +74 -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 +13 -0
- package/src/components/ui/alert/Alert.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/badge/Badge.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/button/Button.stories.tsx +1 -1
- package/src/components/ui/card/Card.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/chip/Chip.stories.tsx +1 -1
- package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
- package/src/components/ui/form-field/FormField.stories.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +38 -2
- package/src/components/ui/indicator/Indicator.test.tsx +20 -1
- package/src/components/ui/indicator/Indicator.tsx +50 -6
- package/src/components/ui/indicator/index.ts +1 -1
- package/src/components/ui/input/Input.stories.tsx +1 -1
- package/src/components/ui/link/Link.stories.tsx +1 -1
- package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
- package/src/components/ui/menu/Menu.stories.tsx +1 -1
- package/src/components/ui/modal/Modal.stories.tsx +1 -1
- package/src/components/ui/pill/Pill.stories.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +1 -1
- package/src/components/ui/select/Select.stories.tsx +1 -1
- package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.tsx +3 -2
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.stories.tsx +79 -0
- package/src/components/ui/tile/Tile.test.tsx +48 -0
- package/src/components/ui/tile/Tile.tsx +64 -0
- package/src/components/ui/tile/index.ts +2 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +2 -1
- package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTimer.test.ts +150 -0
- package/src/hooks/useTimer.ts +77 -0
- package/src/index.ts +3 -0
- package/src/pages.ts +1 -1
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/stories/ThemePresets.stories.tsx +1 -1
- package/src/theme/ColorSystem.stories.tsx +323 -0
- package/src/theme/Colors.stories.tsx +74 -9
- package/src/theme/barrel.ts +1 -0
- package/src/theme/config.ts +20 -3
- package/src/theme/elevation.stories.tsx +1 -1
- package/src/theme/extracted-colors-dataviz.ts +21 -0
- package/src/theme/extracted-colors-ui.ts +20 -0
- package/src/theme/global.css +69 -53
- package/src/theme/gradients.test.ts +25 -0
- package/src/theme/gradients.ts +36 -0
- package/src/theme/shadows.stories.tsx +1 -1
- package/src/theme/tokens/primitives.test.ts +232 -0
- package/src/theme/tokens/primitives.ts +238 -87
- package/src/theme/tokens/semantic.ts +101 -81
- package/src/theme/workout-tokens.ts +26 -21
- package/src/utils/avatar-color.ts +3 -3
- package/tailwind.config.js +15 -3
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { SetStrip, velocityZoneColor, SET_STRIP_ZONES, type SetStripSet } from './SetStrip'
|
|
5
|
+
|
|
6
|
+
describe('SetStrip', () => {
|
|
7
|
+
describe('velocityZoneColor', () => {
|
|
8
|
+
it('maps velocity ratios to their zone pins', () => {
|
|
9
|
+
expect(velocityZoneColor(0.4)).toBe(SET_STRIP_ZONES.slow)
|
|
10
|
+
expect(velocityZoneColor(0.6)).toBe(SET_STRIP_ZONES.moderate)
|
|
11
|
+
expect(velocityZoneColor(0.85)).toBe(SET_STRIP_ZONES.fast)
|
|
12
|
+
expect(velocityZoneColor(1.2)).toBe(SET_STRIP_ZONES.fastest)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('uses the real titan ramp pins (not ad-hoc hexes)', () => {
|
|
16
|
+
expect(SET_STRIP_ZONES.slow).toBe('#D14343') // red 600
|
|
17
|
+
expect(SET_STRIP_ZONES.moderate).toBe('#FF7900') // orange 400
|
|
18
|
+
expect(SET_STRIP_ZONES.fast).toBe('#F9B415') // amber 300
|
|
19
|
+
expect(SET_STRIP_ZONES.fastest).toBe('#2ED573') // green 300
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('renders one bar per set', () => {
|
|
24
|
+
const sets: SetStripSet[] = [
|
|
25
|
+
{ status: 'done', velocities: [1, 0.9] },
|
|
26
|
+
{ status: 'done', velocities: [0.8] },
|
|
27
|
+
]
|
|
28
|
+
render(<SetStrip sets={sets} />)
|
|
29
|
+
expect(screen.getAllByTestId('set-strip-set')).toHaveLength(2)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('set statuses', () => {
|
|
33
|
+
it('done: one colored fill segment per rep, no empties, no pulse', () => {
|
|
34
|
+
render(<SetStrip sets={[{ status: 'done', velocities: [1, 0.8, 0.6, 0.4] }]} />)
|
|
35
|
+
expect(screen.getAllByTestId('set-strip-fill')).toHaveLength(4)
|
|
36
|
+
expect(screen.queryByTestId('set-strip-empty')).not.toBeInTheDocument()
|
|
37
|
+
expect(screen.queryByTestId('set-strip-pulse')).not.toBeInTheDocument()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('active: performed reps pulse, the remainder is greyed', () => {
|
|
41
|
+
render(<SetStrip sets={[{ status: 'active', velocities: [0.9, 0.7, 0.5], planned: 8 }]} />)
|
|
42
|
+
expect(screen.getAllByTestId('set-strip-pulse')).toHaveLength(3)
|
|
43
|
+
expect(screen.getAllByTestId('set-strip-empty')).toHaveLength(5)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('todo: a single grey bar, no fills or pulses', () => {
|
|
47
|
+
render(<SetStrip sets={[{ status: 'todo', planned: 10 }]} />)
|
|
48
|
+
expect(screen.getAllByTestId('set-strip-empty')).toHaveLength(1)
|
|
49
|
+
expect(screen.queryByTestId('set-strip-fill')).not.toBeInTheDocument()
|
|
50
|
+
expect(screen.queryByTestId('set-strip-pulse')).not.toBeInTheDocument()
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('defaults to an 8px height and honors the height prop', () => {
|
|
55
|
+
const sets: SetStripSet[] = [{ status: 'todo', planned: 3 }]
|
|
56
|
+
const { rerender } = render(<SetStrip sets={sets} />)
|
|
57
|
+
expect(screen.getByTestId('set-strip')).toHaveStyle({ height: '8px' })
|
|
58
|
+
rerender(<SetStrip sets={sets} height={4} />)
|
|
59
|
+
expect(screen.getByTestId('set-strip')).toHaveStyle({ height: '4px' })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('exposes a set-progress summary label', () => {
|
|
63
|
+
const sets: SetStripSet[] = [
|
|
64
|
+
{ status: 'done', velocities: [1] },
|
|
65
|
+
{ status: 'active', velocities: [0.9], planned: 3 },
|
|
66
|
+
{ status: 'todo', planned: 3 },
|
|
67
|
+
]
|
|
68
|
+
render(<SetStrip sets={sets} />)
|
|
69
|
+
expect(
|
|
70
|
+
screen.getByLabelText('Set progress: 1 done, 1 in progress, 1 upcoming')
|
|
71
|
+
).toBeInTheDocument()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('categorises set-type variants in the progress summary', () => {
|
|
75
|
+
// drop + myo read as done · a started range reads in progress · myo-upcoming reads upcoming
|
|
76
|
+
const sets: SetStripSet[] = [
|
|
77
|
+
{ status: 'drop', subloads: [[0.8], [0.7]] },
|
|
78
|
+
{ status: 'myo', activation: [0.75], clusters: [[0.6]] },
|
|
79
|
+
{ status: 'range', floor: 15, max: 20, doneVels: [0.9, 0.85] },
|
|
80
|
+
{ status: 'myo-upcoming', activationLen: 12 },
|
|
81
|
+
]
|
|
82
|
+
render(<SetStrip sets={sets} />)
|
|
83
|
+
expect(
|
|
84
|
+
screen.getByLabelText('Set progress: 2 done, 1 in progress, 1 upcoming')
|
|
85
|
+
).toBeInTheDocument()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('renders one bar per set across mixed set-type variants', () => {
|
|
89
|
+
const sets: SetStripSet[] = [
|
|
90
|
+
{ status: 'drop', subloads: [[0.8, 0.7], [0.6]] },
|
|
91
|
+
{ status: 'range', floor: 3, max: 5, doneVels: [] },
|
|
92
|
+
]
|
|
93
|
+
render(<SetStrip sets={sets} />)
|
|
94
|
+
expect(screen.getAllByTestId('set-strip-set')).toHaveLength(2)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('accessibility', () => {
|
|
98
|
+
it('has no accessibility violations', async () => {
|
|
99
|
+
const sets: SetStripSet[] = [
|
|
100
|
+
{ status: 'done', velocities: [1, 0.8] },
|
|
101
|
+
{ status: 'active', velocities: [0.9], planned: 3 },
|
|
102
|
+
{ status: 'todo', planned: 3 },
|
|
103
|
+
]
|
|
104
|
+
const { container } = render(<SetStrip sets={sets} />)
|
|
105
|
+
const results = await axe(container)
|
|
106
|
+
expect(results).toHaveNoViolations()
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import { View, type ViewProps } from 'react-native'
|
|
3
|
+
import { SetBar, type SetStripSet } from './SetBar'
|
|
4
|
+
|
|
5
|
+
// Re-exported so the per-set colour vocabulary stays importable from either the
|
|
6
|
+
// molecule (SetStrip) or the atom (SetBar) it now lives in.
|
|
7
|
+
export {
|
|
8
|
+
SET_STRIP_ZONES,
|
|
9
|
+
SET_STRIP_VARIABLE_COLOR,
|
|
10
|
+
velocityZoneColor,
|
|
11
|
+
SetBar,
|
|
12
|
+
type SetBarProps,
|
|
13
|
+
} from './SetBar'
|
|
14
|
+
export type { SetStripSet } from './SetBar'
|
|
15
|
+
|
|
16
|
+
/** Gap between set bars — ~2× the (zero) rep gap, so sets read as discrete units. */
|
|
17
|
+
const SET_STRIP_GAP = 5
|
|
18
|
+
|
|
19
|
+
/** Collapse a set (flat or set-type) to its glance category for the progress summary. */
|
|
20
|
+
function setCategory(set: SetStripSet): 'done' | 'active' | 'upcoming' {
|
|
21
|
+
switch (set.status) {
|
|
22
|
+
case 'done':
|
|
23
|
+
case 'drop':
|
|
24
|
+
case 'myo':
|
|
25
|
+
return 'done'
|
|
26
|
+
case 'active':
|
|
27
|
+
return 'active'
|
|
28
|
+
case 'range':
|
|
29
|
+
return set.doneVels.length > 0 ? 'active' : 'upcoming'
|
|
30
|
+
case 'todo':
|
|
31
|
+
case 'myo-upcoming':
|
|
32
|
+
return 'upcoming'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function describeSets(sets: SetStripSet[]): string {
|
|
37
|
+
const done = sets.filter((s) => setCategory(s) === 'done').length
|
|
38
|
+
const active = sets.filter((s) => setCategory(s) === 'active').length
|
|
39
|
+
const upcoming = sets.filter((s) => setCategory(s) === 'upcoming').length
|
|
40
|
+
return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SetStripProps extends ViewProps {
|
|
44
|
+
/** Per-set performance data, in set order. */
|
|
45
|
+
sets: SetStripSet[]
|
|
46
|
+
/** Bar height in px. Default 8. */
|
|
47
|
+
height?: number
|
|
48
|
+
className?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The per-set segmented performance strip: one continuous {@link SetBar} per set
|
|
53
|
+
* (rep intensities as butted color segments, no rep gaps), sets separated by a
|
|
54
|
+
* fixed gap. Fills its container width. Colors are the real titan ramp pins.
|
|
55
|
+
*/
|
|
56
|
+
export function SetStrip({ sets, height = 8, className, ...props }: SetStripProps) {
|
|
57
|
+
return (
|
|
58
|
+
<View
|
|
59
|
+
className={className}
|
|
60
|
+
style={{ flexDirection: 'row', width: '100%', height, gap: SET_STRIP_GAP }}
|
|
61
|
+
accessibilityRole="image"
|
|
62
|
+
accessibilityLabel={describeSets(sets)}
|
|
63
|
+
testID="set-strip"
|
|
64
|
+
{...props}
|
|
65
|
+
>
|
|
66
|
+
{sets.map((set, i) => (
|
|
67
|
+
<SetBar key={i} set={set} height={height} />
|
|
68
|
+
))}
|
|
69
|
+
</View>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { SetTableHeader } from './SetTableHeader'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 🚧 WIP / placeholder — `SetTableHeader` (exported as `SetTableHeader`; the generic
|
|
7
|
+
* `TableHeader` name is taken by the DataViz `Table`). The expanded-set-table column
|
|
8
|
+
* header row (SET · PREV · REPS · LOAD · RPE) whose widths mirror `SetRow`. Extracted
|
|
9
|
+
* from `ExerciseCard`; the column set / widths are provisional pending the responsive
|
|
10
|
+
* unification (TD-03.56).
|
|
11
|
+
*/
|
|
12
|
+
const meta: Meta<typeof SetTableHeader> = {
|
|
13
|
+
title: 'Workout/SetTableHeader',
|
|
14
|
+
component: SetTableHeader,
|
|
15
|
+
tags: ['autodocs'],
|
|
16
|
+
parameters: {
|
|
17
|
+
docs: {
|
|
18
|
+
description: {
|
|
19
|
+
component:
|
|
20
|
+
'**Atom.** The expanded-set-table column header row; per-column widths mirror ' +
|
|
21
|
+
'[SetRow](?path=/docs/workout-setrow--docs) so headers align over the rows. ' +
|
|
22
|
+
'Used-by ↑ [ExerciseCard](?path=/docs/workout-exercisecard--docs) (expanded), which ' +
|
|
23
|
+
'renders it with `showPrevious={false}`. ' +
|
|
24
|
+
'Column set / widths provisional pending TD-03.56 responsive unification.',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
argTypes: {
|
|
29
|
+
unit: { control: 'select', options: ['lbs', 'kg'] },
|
|
30
|
+
showPrevious: { control: 'boolean' },
|
|
31
|
+
},
|
|
32
|
+
decorators: [
|
|
33
|
+
(Story) => (
|
|
34
|
+
<View style={{ width: 320, backgroundColor: '#131313' }}>
|
|
35
|
+
<Story />
|
|
36
|
+
</View>
|
|
37
|
+
),
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default meta
|
|
42
|
+
type Story = StoryObj<typeof SetTableHeader>
|
|
43
|
+
|
|
44
|
+
export const Lbs: Story = {
|
|
45
|
+
args: { unit: 'lbs' },
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const Kg: Story = {
|
|
49
|
+
args: { unit: 'kg' },
|
|
50
|
+
parameters: {
|
|
51
|
+
docs: { description: { story: 'The weight column reflects the `kg` unit.' } },
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const NoPrevious: Story = {
|
|
56
|
+
args: { unit: 'lbs', showPrevious: false },
|
|
57
|
+
parameters: {
|
|
58
|
+
docs: {
|
|
59
|
+
description: {
|
|
60
|
+
story:
|
|
61
|
+
'PREV dropped for rail density (`showPrevious={false}`); the column blanks to a flex ' +
|
|
62
|
+
'spacer so REPS/LOAD/RPE hold their positions. Pair with `SetRow`’s `showPrevious`.',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { SetTableHeader } from './SetTableHeader'
|
|
5
|
+
|
|
6
|
+
describe('SetTableHeader', () => {
|
|
7
|
+
it('renders the five column labels with the lbs weight column by default', () => {
|
|
8
|
+
render(<SetTableHeader />)
|
|
9
|
+
const header = screen.getByTestId('table-header')
|
|
10
|
+
expect(header).toHaveTextContent('SET')
|
|
11
|
+
expect(header).toHaveTextContent('PREV')
|
|
12
|
+
expect(header).toHaveTextContent('REPS')
|
|
13
|
+
expect(header).toHaveTextContent('LBS')
|
|
14
|
+
expect(header).toHaveTextContent('RPE')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('drops the PREV column when showPrevious is false', () => {
|
|
18
|
+
render(<SetTableHeader showPrevious={false} />)
|
|
19
|
+
const header = screen.getByTestId('table-header')
|
|
20
|
+
expect(header).not.toHaveTextContent('PREV')
|
|
21
|
+
// the other columns remain
|
|
22
|
+
expect(header).toHaveTextContent('SET')
|
|
23
|
+
expect(header).toHaveTextContent('REPS')
|
|
24
|
+
expect(header).toHaveTextContent('RPE')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('shows the KG weight column when unit is kg', () => {
|
|
28
|
+
render(<SetTableHeader unit="kg" />)
|
|
29
|
+
const header = screen.getByTestId('table-header')
|
|
30
|
+
expect(header).toHaveTextContent('KG')
|
|
31
|
+
expect(header).not.toHaveTextContent('LBS')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('honors a custom testID so a host card can keep its own', () => {
|
|
35
|
+
render(<SetTableHeader testID="exercise-card-column-headers" />)
|
|
36
|
+
expect(screen.getByTestId('exercise-card-column-headers')).toBeInTheDocument()
|
|
37
|
+
expect(screen.queryByTestId('table-header')).not.toBeInTheDocument()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('accessibility', () => {
|
|
41
|
+
it('has no accessibility violations', async () => {
|
|
42
|
+
const { container } = render(<SetTableHeader />)
|
|
43
|
+
const results = await axe(container)
|
|
44
|
+
expect(results).toHaveNoViolations()
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
|
|
4
|
+
// One header column: its label and its fixed width (PREV is the lone flex column,
|
|
5
|
+
// `undefined`). Order + widths mirror SetRow's cells so headers align over rows.
|
|
6
|
+
type Column = { label: string; width: number | undefined }
|
|
7
|
+
|
|
8
|
+
export interface SetTableHeaderProps {
|
|
9
|
+
/** Weight-column label: LBS / KG. Default lbs. */
|
|
10
|
+
unit?: 'lbs' | 'kg'
|
|
11
|
+
/** Show the PREV (previous-best) column. Default true; false drops it for rail density. */
|
|
12
|
+
showPrevious?: boolean
|
|
13
|
+
/** Overridable so a host card can keep its own testID. Default "table-header". */
|
|
14
|
+
testID?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// The previous-best column is the lone flex column (`undefined` width). When hidden
|
|
18
|
+
// it is dropped entirely (not blanked to a spacer) and the four fixed columns
|
|
19
|
+
// distribute across the full strip width via `justifyContent: space-between`.
|
|
20
|
+
const PREV_COLUMN: Column = { label: 'PREV', width: undefined }
|
|
21
|
+
|
|
22
|
+
/** Columns in order, with the weight column reflecting `unit`; PREV dropped when hidden. */
|
|
23
|
+
function buildColumns(unit: 'lbs' | 'kg', showPrevious: boolean): Column[] {
|
|
24
|
+
return [
|
|
25
|
+
{ label: 'SET', width: 36 },
|
|
26
|
+
...(showPrevious ? [PREV_COLUMN] : []),
|
|
27
|
+
{ label: 'REPS', width: 44 },
|
|
28
|
+
{ label: unit.toUpperCase(), width: 56 },
|
|
29
|
+
{ label: 'RPE', width: 36 },
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The expanded-set-table column-header row: SET · PREV · REPS · LOAD · RPE, with
|
|
35
|
+
* the weight column reflecting `unit`. `showPrevious={false}` drops PREV (rail
|
|
36
|
+
* density). Its per-column widths mirror {@link SetRow} so headers align over the
|
|
37
|
+
* rows. Extracted from {@link ExerciseCard}'s expanded state for reuse.
|
|
38
|
+
*/
|
|
39
|
+
export function SetTableHeader({
|
|
40
|
+
unit = 'lbs',
|
|
41
|
+
showPrevious = true,
|
|
42
|
+
testID = 'table-header',
|
|
43
|
+
}: SetTableHeaderProps) {
|
|
44
|
+
const columns = buildColumns(unit, showPrevious)
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<View
|
|
48
|
+
className="flex-row"
|
|
49
|
+
style={{
|
|
50
|
+
paddingVertical: 8,
|
|
51
|
+
paddingHorizontal: 8,
|
|
52
|
+
paddingBottom: 4,
|
|
53
|
+
...(showPrevious ? {} : { justifyContent: 'space-between' }),
|
|
54
|
+
}}
|
|
55
|
+
testID={testID}
|
|
56
|
+
>
|
|
57
|
+
{columns.map(({ label, width }) => {
|
|
58
|
+
const isFlex = width === undefined
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<View
|
|
62
|
+
key={label}
|
|
63
|
+
style={{
|
|
64
|
+
...(isFlex ? { minWidth: 44 } : { width, flexShrink: 1 }),
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
justifyContent: 'center',
|
|
67
|
+
gap: 8,
|
|
68
|
+
}}
|
|
69
|
+
{...(isFlex ? { className: 'flex-1' } : {})}
|
|
70
|
+
>
|
|
71
|
+
<Text
|
|
72
|
+
className="text-text-tertiary"
|
|
73
|
+
style={{
|
|
74
|
+
fontSize: 10,
|
|
75
|
+
fontWeight: '600',
|
|
76
|
+
fontFamily: 'Inter, sans-serif',
|
|
77
|
+
textTransform: 'uppercase',
|
|
78
|
+
letterSpacing: 0.8,
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
{label}
|
|
82
|
+
</Text>
|
|
83
|
+
</View>
|
|
84
|
+
)
|
|
85
|
+
})}
|
|
86
|
+
</View>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { SetsRepsLoad } from './SetsRepsLoad'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `SetsRepsLoad` — the `sets × reps @ load` prescription line in the TempoDisplay
|
|
7
|
+
* visual language (Inter · 600 · letter-spacing 1 · value cells with muted
|
|
8
|
+
* separators).
|
|
9
|
+
*/
|
|
10
|
+
const meta: Meta<typeof SetsRepsLoad> = {
|
|
11
|
+
title: 'Workout/SetsRepsLoad',
|
|
12
|
+
component: SetsRepsLoad,
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
parameters: {
|
|
15
|
+
docs: {
|
|
16
|
+
description: {
|
|
17
|
+
component:
|
|
18
|
+
'**Atom.** The `sets × reps @ load` prescription line in the TempoDisplay visual ' +
|
|
19
|
+
'language. Composes [MetricCell](?path=/docs/workout-metriccell--docs) ' +
|
|
20
|
+
'(shared with [TempoDisplay](?path=/docs/workout-tempodisplay--docs)). ' +
|
|
21
|
+
'Used-by ↑ [ExerciseHeading](?path=/docs/workout-exerciseheading--docs).',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
decorators: [
|
|
26
|
+
(Story) => (
|
|
27
|
+
<View style={{ padding: 16, backgroundColor: '#131313' }}>
|
|
28
|
+
<Story />
|
|
29
|
+
</View>
|
|
30
|
+
),
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default meta
|
|
35
|
+
type Story = StoryObj<typeof SetsRepsLoad>
|
|
36
|
+
|
|
37
|
+
export const Default: Story = {
|
|
38
|
+
args: { sets: 5, reps: 8, load: 145, unit: 'lb' },
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const RepRange: Story = {
|
|
42
|
+
args: { sets: 3, reps: '15-20', load: 40, unit: 'lb' },
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Kilograms: Story = {
|
|
46
|
+
args: { sets: 4, reps: 6, load: 84, unit: 'kg' },
|
|
47
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { SetsRepsLoad } from './SetsRepsLoad'
|
|
5
|
+
|
|
6
|
+
describe('SetsRepsLoad', () => {
|
|
7
|
+
it('renders the sets, reps and load values', () => {
|
|
8
|
+
render(<SetsRepsLoad sets={5} reps={8} load={145} />)
|
|
9
|
+
const line = screen.getByTestId('sets-reps-load')
|
|
10
|
+
expect(line).toHaveTextContent('5')
|
|
11
|
+
expect(line).toHaveTextContent('8')
|
|
12
|
+
expect(line).toHaveTextContent('145')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('renders the × and @ separators', () => {
|
|
16
|
+
render(<SetsRepsLoad sets={3} reps={10} load={90} />)
|
|
17
|
+
const line = screen.getByTestId('sets-reps-load')
|
|
18
|
+
expect(line).toHaveTextContent('×')
|
|
19
|
+
expect(line).toHaveTextContent('@')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('defaults the unit label to lb', () => {
|
|
23
|
+
render(<SetsRepsLoad sets={3} reps={10} load={90} />)
|
|
24
|
+
expect(screen.getByTestId('sets-reps-load')).toHaveTextContent('lb')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('accepts a custom unit label', () => {
|
|
28
|
+
render(<SetsRepsLoad sets={3} reps={10} load={40} unit="kg" />)
|
|
29
|
+
expect(screen.getByTestId('sets-reps-load')).toHaveTextContent('kg')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('supports rep ranges', () => {
|
|
33
|
+
render(<SetsRepsLoad sets={3} reps="15-20" load={40} />)
|
|
34
|
+
expect(screen.getByTestId('sets-reps-load')).toHaveTextContent('15-20')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('accessibility', () => {
|
|
38
|
+
it('has no accessibility violations', async () => {
|
|
39
|
+
const { container } = render(<SetsRepsLoad sets={5} reps={8} load={145} />)
|
|
40
|
+
const results = await axe(container)
|
|
41
|
+
expect(results).toHaveNoViolations()
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import { View, type ViewProps } from 'react-native'
|
|
3
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
4
|
+
import { MetricCell } from './metricText'
|
|
5
|
+
|
|
6
|
+
const t = getSemanticColors('dark')
|
|
7
|
+
|
|
8
|
+
const TIMES = '×' // × multiplication sign, padded by muted separators
|
|
9
|
+
const AT = '@'
|
|
10
|
+
|
|
11
|
+
export interface SetsRepsLoadProps extends ViewProps {
|
|
12
|
+
sets: number
|
|
13
|
+
reps: number | string
|
|
14
|
+
load: number
|
|
15
|
+
/** Displayed load unit label (e.g. "lb", "kg"). */
|
|
16
|
+
unit?: string
|
|
17
|
+
className?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The `sets × reps @ load` prescription line, in the TempoDisplay visual language
|
|
22
|
+
* (Inter · 600 · letter-spacing 1 · value cells with muted `×` / `@` separators).
|
|
23
|
+
* Shares the {@link MetricCell} primitive with TempoDisplay so the two stay in step.
|
|
24
|
+
*/
|
|
25
|
+
export function SetsRepsLoad({
|
|
26
|
+
sets,
|
|
27
|
+
reps,
|
|
28
|
+
load,
|
|
29
|
+
unit = 'lb',
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}: SetsRepsLoadProps) {
|
|
33
|
+
const value = t['text-primary']
|
|
34
|
+
const sep = t['text-tertiary']
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<View
|
|
38
|
+
className={className}
|
|
39
|
+
style={{ flexDirection: 'row', alignItems: 'baseline' }}
|
|
40
|
+
accessibilityLabel={`${sets} sets of ${reps} reps at ${load} ${unit}`}
|
|
41
|
+
testID="sets-reps-load"
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<MetricCell color={value}>{sets}</MetricCell>
|
|
45
|
+
<MetricCell color={sep}>{` ${TIMES} `}</MetricCell>
|
|
46
|
+
<MetricCell color={value}>{reps}</MetricCell>
|
|
47
|
+
<MetricCell color={sep}>{` ${AT} `}</MetricCell>
|
|
48
|
+
<MetricCell color={value}>{load}</MetricCell>
|
|
49
|
+
<MetricCell color={sep}>{` ${unit}`}</MetricCell>
|
|
50
|
+
</View>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -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
|
|