@titan-design/react-ui 0.6.0 → 0.8.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 +12 -7
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +12 -7
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +956 -128
- package/dist/index.d.ts +956 -128
- package/dist/index.js +2873 -981
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2826 -982
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-DCFBqp79.d.mts} +289 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-_fI3-x7Z.d.ts} +289 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1659 -799
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1660 -800
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +16 -82
- package/dist/theme/index.d.ts +16 -82
- package/dist/theme/index.js +13 -115
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +13 -115
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +14 -6
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +14 -6
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +1 -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/CircularTimer/CircularTimer.stories.tsx +90 -0
- package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
- package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
- package/src/components/custom/CircularTimer/index.ts +1 -0
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
- package/src/components/custom/Metric/Metric.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
- 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/Typography/Typography.stories.tsx +1 -1
- 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 +9 -15
- 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/BodyMapDetailPanel.stories.tsx +1 -1
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- 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 +4 -5
- 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 +85 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +86 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- 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/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
- 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/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
- package/src/components/custom/Workout/README.md +193 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.stories.tsx +133 -3
- package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
- package/src/components/custom/Workout/RestTimer.tsx +155 -59
- 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 +169 -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/StatusDot.stories.tsx +1 -1
- 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/SupersetWrapper.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoBar.stories.tsx +70 -1
- package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
- package/src/components/custom/Workout/TempoBar.tsx +107 -22
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
- 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 +262 -56
- package/src/components/custom/Workout/VelocityStrip.test.tsx +440 -141
- package/src/components/custom/Workout/VelocityStrip.tsx +776 -141
- 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/WeightBadge.stories.tsx +1 -1
- 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/WorkoutPill.stories.tsx +1 -1
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +139 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +234 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +373 -0
- 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 +2 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/icons/icons.stories.tsx +67 -2
- package/src/components/icons/icons.test.tsx +32 -2
- package/src/components/icons/icons.tsx +108 -0
- package/src/components/shell/BrandLockup.stories.tsx +3 -3
- 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 +2 -2
- package/src/components/shell/DeviceMenu.stories.tsx +5 -5
- package/src/components/shell/DeviceRow.stories.tsx +3 -3
- 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 +30 -0
- package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
- package/src/components/shell/SessionStatePill.stories.tsx +3 -3
- package/src/components/shell/SessionStatePill.tsx +1 -1
- 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 +5 -5
- package/src/components/shell/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +86 -3
- package/src/components/ui/alert/Alert.test.tsx +52 -0
- package/src/components/ui/alert/Alert.tsx +54 -20
- 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 +2 -2
- package/src/components/ui/indicator/Indicator.test.tsx +1 -0
- package/src/components/ui/indicator/Indicator.tsx +6 -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/progress/Progress.tsx +47 -22
- 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/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/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 +1 -1
- package/src/theme/Colors.stories.tsx +5 -8
- package/src/theme/config.ts +4 -0
- package/src/theme/elevation.stories.tsx +1 -1
- package/src/theme/global.css +5 -1
- package/src/theme/shadows.stories.tsx +1 -1
- package/src/theme/tokens/primitives.ts +11 -107
- package/src/theme/tokens/semantic.ts +8 -0
- package/src/theme/workout-tokens.ts +11 -7
- package/tailwind.config.js +5 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { ZoneTrack, type ZoneTrackZone } from './ZoneTrack'
|
|
5
|
+
import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
|
|
6
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
7
|
+
|
|
8
|
+
const { green, yellow, orange, red } = WORKOUT_TOKENS.scale
|
|
9
|
+
const t = getSemanticColors('dark')
|
|
10
|
+
|
|
11
|
+
const ZONES: ZoneTrackZone[] = [
|
|
12
|
+
{ upTo: 10, color: green },
|
|
13
|
+
{ upTo: 20, color: yellow },
|
|
14
|
+
{ upTo: 30, color: orange },
|
|
15
|
+
{ upTo: 40, color: red },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
describe('ZoneTrack', () => {
|
|
19
|
+
it('renders one band per zone', () => {
|
|
20
|
+
render(<ZoneTrack zones={ZONES} max={40} />)
|
|
21
|
+
expect(screen.getAllByTestId('zone-track-band')).toHaveLength(4)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('colours each band from the supplied zone token (literal hex, not a var ref)', () => {
|
|
25
|
+
render(<ZoneTrack zones={ZONES} max={40} />)
|
|
26
|
+
const bands = screen.getAllByTestId('zone-track-band')
|
|
27
|
+
expect(bands[0]).toHaveStyle({ backgroundColor: green })
|
|
28
|
+
expect(bands[3]).toHaveStyle({ backgroundColor: red })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('weights each band by its domain span', () => {
|
|
32
|
+
render(
|
|
33
|
+
<ZoneTrack
|
|
34
|
+
zones={[
|
|
35
|
+
{ upTo: 30, color: green },
|
|
36
|
+
{ upTo: 40, color: red },
|
|
37
|
+
]}
|
|
38
|
+
max={40}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
const bands = screen.getAllByTestId('zone-track-band')
|
|
42
|
+
expect(bands[0]).toHaveStyle({ flexGrow: 30 })
|
|
43
|
+
expect(bands[1]).toHaveStyle({ flexGrow: 10 })
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('renders no marker when none is supplied', () => {
|
|
47
|
+
render(<ZoneTrack zones={ZONES} max={40} />)
|
|
48
|
+
expect(screen.queryByTestId('zone-track-needle')).not.toBeInTheDocument()
|
|
49
|
+
expect(screen.queryByTestId('zone-track-fill')).not.toBeInTheDocument()
|
|
50
|
+
expect(screen.queryByTestId('zone-track-unfilled')).not.toBeInTheDocument()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('positions a needle marker at the value fraction of the domain', () => {
|
|
54
|
+
render(<ZoneTrack zones={ZONES} max={40} marker={{ type: 'needle', value: 10 }} />)
|
|
55
|
+
expect(screen.getByTestId('zone-track-needle')).toHaveStyle({ left: '25%' })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('clamps a needle past the max to the track end', () => {
|
|
59
|
+
render(<ZoneTrack zones={ZONES} max={40} marker={{ type: 'needle', value: 100 }} />)
|
|
60
|
+
expect(screen.getByTestId('zone-track-needle')).toHaveStyle({ left: '100%' })
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('clip-reveals the gradient for a fill marker without a colour', () => {
|
|
64
|
+
render(<ZoneTrack zones={ZONES} max={40} marker={{ type: 'fill', value: 20 }} />)
|
|
65
|
+
expect(screen.getByTestId('zone-track-unfilled')).toHaveStyle({ left: '50%' })
|
|
66
|
+
expect(screen.queryByTestId('zone-track-fill')).not.toBeInTheDocument()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('draws a solid trend fill when the fill marker has a colour', () => {
|
|
70
|
+
render(<ZoneTrack zones={ZONES} max={40} marker={{ type: 'fill', value: 30, color: red }} />)
|
|
71
|
+
const fill = screen.getByTestId('zone-track-fill')
|
|
72
|
+
expect(fill).toHaveStyle({ width: '75%', backgroundColor: red })
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('renders a tick with its label per entry', () => {
|
|
76
|
+
render(
|
|
77
|
+
<ZoneTrack
|
|
78
|
+
zones={ZONES}
|
|
79
|
+
max={40}
|
|
80
|
+
ticks={[
|
|
81
|
+
{ value: 0, label: 'fresh' },
|
|
82
|
+
{ value: 40, label: 'stop' },
|
|
83
|
+
]}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
expect(screen.getAllByTestId('zone-track-tick')).toHaveLength(2)
|
|
87
|
+
expect(screen.getByText('fresh')).toBeInTheDocument()
|
|
88
|
+
expect(screen.getByText('stop')).toBeInTheDocument()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('draws a range highlight only when a band is supplied', () => {
|
|
92
|
+
const { rerender } = render(<ZoneTrack zones={ZONES} max={40} />)
|
|
93
|
+
expect(screen.queryByTestId('zone-track-band-highlight')).not.toBeInTheDocument()
|
|
94
|
+
rerender(
|
|
95
|
+
<ZoneTrack
|
|
96
|
+
zones={ZONES}
|
|
97
|
+
max={40}
|
|
98
|
+
band={{ from: 10, to: 20, color: 'rgba(255,255,255,0.2)' }}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
expect(screen.getByTestId('zone-track-band-highlight')).toHaveStyle({
|
|
102
|
+
left: '25%',
|
|
103
|
+
width: '25%',
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// RNW does not map accessibilityValue → aria-valuenow under jsdom (gotcha #11), so the
|
|
108
|
+
// value is verified via the marker position + the accessible name, not aria-valuenow.
|
|
109
|
+
it('exposes the progressbar role with an accessible name', () => {
|
|
110
|
+
render(
|
|
111
|
+
<ZoneTrack
|
|
112
|
+
zones={ZONES}
|
|
113
|
+
max={40}
|
|
114
|
+
marker={{ type: 'needle', value: 12 }}
|
|
115
|
+
accessibilityLabel="Fatigue meter"
|
|
116
|
+
/>
|
|
117
|
+
)
|
|
118
|
+
expect(screen.getByRole('progressbar', { name: 'Fatigue meter' })).toBeInTheDocument()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('accessibility', () => {
|
|
122
|
+
it('has no accessibility violations', async () => {
|
|
123
|
+
const { container } = render(
|
|
124
|
+
<ZoneTrack
|
|
125
|
+
zones={ZONES}
|
|
126
|
+
max={40}
|
|
127
|
+
marker={{ type: 'needle', value: 21 }}
|
|
128
|
+
ticks={[
|
|
129
|
+
{ value: 0, label: 'fresh' },
|
|
130
|
+
{ value: 40, label: 'stop' },
|
|
131
|
+
]}
|
|
132
|
+
accessibilityLabel="Fatigue meter"
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
const results = await axe(container)
|
|
136
|
+
expect(results).toHaveNoViolations()
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('glow', () => {
|
|
141
|
+
it('haloes the track when the marker has glow', () => {
|
|
142
|
+
render(
|
|
143
|
+
<ZoneTrack
|
|
144
|
+
zones={ZONES}
|
|
145
|
+
max={40}
|
|
146
|
+
marker={{ type: 'fill', value: 24, color: orange, glow: true }}
|
|
147
|
+
/>
|
|
148
|
+
)
|
|
149
|
+
expect((screen.getByTestId('zone-track-track') as HTMLElement).style.boxShadow).not.toBe('')
|
|
150
|
+
})
|
|
151
|
+
it('has no glow by default', () => {
|
|
152
|
+
render(
|
|
153
|
+
<ZoneTrack zones={ZONES} max={40} marker={{ type: 'fill', value: 24, color: orange }} />
|
|
154
|
+
)
|
|
155
|
+
expect((screen.getByTestId('zone-track-track') as HTMLElement).style.boxShadow).toBe('')
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
describe('ticks (colored lines + labels + tooltip)', () => {
|
|
160
|
+
it('renders a line and label per tick', () => {
|
|
161
|
+
render(
|
|
162
|
+
<ZoneTrack
|
|
163
|
+
zones={ZONES}
|
|
164
|
+
max={40}
|
|
165
|
+
ticks={[
|
|
166
|
+
{ value: 10, label: 'A' },
|
|
167
|
+
{ value: 30, label: 'B' },
|
|
168
|
+
]}
|
|
169
|
+
/>
|
|
170
|
+
)
|
|
171
|
+
expect(screen.getAllByTestId('zone-track-tick-line')).toHaveLength(2)
|
|
172
|
+
expect(screen.getAllByTestId('zone-track-tick-label').map((e) => e.textContent)).toEqual([
|
|
173
|
+
'A',
|
|
174
|
+
'B',
|
|
175
|
+
])
|
|
176
|
+
})
|
|
177
|
+
it('colours an emphasized tick line with the brand accent', () => {
|
|
178
|
+
render(
|
|
179
|
+
<ZoneTrack zones={ZONES} max={40} ticks={[{ value: 20, label: 'T', emphasized: true }]} />
|
|
180
|
+
)
|
|
181
|
+
expect(screen.getByTestId('zone-track-tick-line')).toHaveStyle({
|
|
182
|
+
backgroundColor: t['brand-primary'],
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
it('applies a per-tick color override to the line', () => {
|
|
186
|
+
render(<ZoneTrack zones={ZONES} max={40} ticks={[{ value: 20, label: 'X', color: red }]} />)
|
|
187
|
+
expect(screen.getByTestId('zone-track-tick-line')).toHaveStyle({ backgroundColor: red })
|
|
188
|
+
})
|
|
189
|
+
it('renders a tooltip label trigger', () => {
|
|
190
|
+
render(
|
|
191
|
+
<ZoneTrack
|
|
192
|
+
zones={ZONES}
|
|
193
|
+
max={40}
|
|
194
|
+
ticks={[{ value: 20, label: 'MAV', tooltip: 'Maximum Adaptive Volume' }]}
|
|
195
|
+
/>
|
|
196
|
+
)
|
|
197
|
+
expect(screen.getByText('MAV')).toBeInTheDocument()
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
describe('ZoneTrack size="wall"', () => {
|
|
203
|
+
const TICKS = [
|
|
204
|
+
{ value: 0, label: 'fresh' },
|
|
205
|
+
{ value: 40, label: 'stop' },
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
it('scales the needle and tick labels up together', () => {
|
|
209
|
+
render(
|
|
210
|
+
<ZoneTrack
|
|
211
|
+
zones={ZONES}
|
|
212
|
+
max={40}
|
|
213
|
+
size="wall"
|
|
214
|
+
marker={{ type: 'needle', value: 20 }}
|
|
215
|
+
ticks={TICKS}
|
|
216
|
+
/>
|
|
217
|
+
)
|
|
218
|
+
expect(screen.getByTestId('zone-track-needle')).toHaveStyle({ width: '7px' })
|
|
219
|
+
expect(screen.getAllByTestId('zone-track-tick-label')[0]).toHaveStyle({ fontSize: 14 })
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('defaults to the compact scale (needle 4 / label 9) when size is omitted', () => {
|
|
223
|
+
render(
|
|
224
|
+
<ZoneTrack zones={ZONES} max={40} marker={{ type: 'needle', value: 20 }} ticks={TICKS} />
|
|
225
|
+
)
|
|
226
|
+
expect(screen.getByTestId('zone-track-needle')).toHaveStyle({ width: '4px' })
|
|
227
|
+
expect(screen.getAllByTestId('zone-track-tick-label')[0]).toHaveStyle({ fontSize: 9 })
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('still lets an explicit trackHeight override the size default', () => {
|
|
231
|
+
render(<ZoneTrack zones={ZONES} max={40} size="wall" trackHeight={30} />)
|
|
232
|
+
expect(screen.getByTestId('zone-track-track')).toHaveStyle({ height: '30px' })
|
|
233
|
+
})
|
|
234
|
+
})
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import { View, Text, type ViewProps, type DimensionValue } from 'react-native'
|
|
3
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
4
|
+
import { primitiveColors } from '../../../theme/tokens/primitives'
|
|
5
|
+
import { alpha } from '../../../utils/colors'
|
|
6
|
+
import { Tooltip } from '../../ui/tooltip/Tooltip'
|
|
7
|
+
|
|
8
|
+
const t = getSemanticColors('dark')
|
|
9
|
+
|
|
10
|
+
/** Soft two-stop glow in `color`, for a marker's optional `glow`. */
|
|
11
|
+
function glowShadow(color: string): string {
|
|
12
|
+
return `0 0 5px 1px ${alpha(color, 0.3)}, 0 0 10px 3px ${alpha(color, 0.12)}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Muted, un-reached track colour — a charcoal step, matches the IntensityBar track family. */
|
|
16
|
+
const DEFAULT_TRACK_COLOR = primitiveColors.charcoal[200]
|
|
17
|
+
/** Default needle / fill-marker colour. */
|
|
18
|
+
const DEFAULT_MARKER_COLOR = primitiveColors.white
|
|
19
|
+
/** Tick mark + tick label colour. */
|
|
20
|
+
const TICK_COLOR = t['text-tertiary']
|
|
21
|
+
|
|
22
|
+
/** Density. `default` — the compact card/rail gauge. `wall` — the across-the-room dashboard scale. */
|
|
23
|
+
export type ZoneTrackSize = 'default' | 'wall'
|
|
24
|
+
|
|
25
|
+
interface ZoneTrackSizing {
|
|
26
|
+
trackHeight: number
|
|
27
|
+
needleOverhang: number
|
|
28
|
+
needleWidth: number
|
|
29
|
+
tickLineNormal: number
|
|
30
|
+
tickLineEmphasized: number
|
|
31
|
+
tickLineHeightPad: number
|
|
32
|
+
tickLineTopPad: number
|
|
33
|
+
labelRowHeight: number
|
|
34
|
+
labelMarginTop: number
|
|
35
|
+
tickFont: number
|
|
36
|
+
tickLetterSpacing: number
|
|
37
|
+
tickCellWidth: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Per-density magnitudes. `default` reproduces the original compact values exactly;
|
|
42
|
+
* `wall` scales the track, needle, tick lines and tick labels up together so the gauge
|
|
43
|
+
* reads across a room. `trackHeight` / `needleOverhang` remain overridable per call.
|
|
44
|
+
*/
|
|
45
|
+
const ZONE_SIZES: Record<ZoneTrackSize, ZoneTrackSizing> = {
|
|
46
|
+
default: {
|
|
47
|
+
trackHeight: 14,
|
|
48
|
+
needleOverhang: 6,
|
|
49
|
+
needleWidth: 4,
|
|
50
|
+
tickLineNormal: 1.5,
|
|
51
|
+
tickLineEmphasized: 2,
|
|
52
|
+
tickLineHeightPad: 4,
|
|
53
|
+
tickLineTopPad: 2,
|
|
54
|
+
labelRowHeight: 16,
|
|
55
|
+
labelMarginTop: 5,
|
|
56
|
+
tickFont: 9,
|
|
57
|
+
tickLetterSpacing: 0.5,
|
|
58
|
+
tickCellWidth: 28,
|
|
59
|
+
},
|
|
60
|
+
wall: {
|
|
61
|
+
trackHeight: 24,
|
|
62
|
+
needleOverhang: 9,
|
|
63
|
+
needleWidth: 7,
|
|
64
|
+
tickLineNormal: 2.5,
|
|
65
|
+
tickLineEmphasized: 3.5,
|
|
66
|
+
tickLineHeightPad: 6,
|
|
67
|
+
tickLineTopPad: 3,
|
|
68
|
+
labelRowHeight: 24,
|
|
69
|
+
labelMarginTop: 8,
|
|
70
|
+
tickFont: 14,
|
|
71
|
+
tickLetterSpacing: 0.5,
|
|
72
|
+
tickCellWidth: 48,
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A single colour band of the zone gradient, spanning `[prev.upTo, upTo]` of the domain. */
|
|
77
|
+
export interface ZoneTrackZone {
|
|
78
|
+
/** Upper bound of this band in domain units. The last band's `upTo` should reach `max`. */
|
|
79
|
+
upTo: number
|
|
80
|
+
/** Band fill colour — a literal hex (RNW-safe), sourced from a ramp/effort-scale token. */
|
|
81
|
+
color: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A tick at `value`: a colored line over the track + an optional compact label
|
|
86
|
+
* below, with an optional tooltip (to expand an acronym / show the raw value so
|
|
87
|
+
* the label footer can stay light). Multiple ticks are supported.
|
|
88
|
+
*/
|
|
89
|
+
export interface ZoneTrackTick {
|
|
90
|
+
/** Position of the tick in domain units. */
|
|
91
|
+
value: number
|
|
92
|
+
/** Optional compact label rendered under the tick. */
|
|
93
|
+
label?: string
|
|
94
|
+
/** Line + label colour. Defaults to a muted tick colour (or brand when `emphasized`). */
|
|
95
|
+
color?: string
|
|
96
|
+
/** Emphasize this tick (thicker line, brand colour by default) — e.g. a target landmark. */
|
|
97
|
+
emphasized?: boolean
|
|
98
|
+
/** Tooltip content shown on hover (web) / long-press (native) — expand the acronym, show the raw value. */
|
|
99
|
+
tooltip?: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The value marker sitting on the track.
|
|
104
|
+
* - `needle`: a vertical line at `value`, overhanging the track (FatigueMeter / TrainingLoadGauge).
|
|
105
|
+
* - `fill`: a left-anchored fill from `min` to `value`. Omit `color` to clip-reveal the zone
|
|
106
|
+
* gradient up to `value`; supply `color` for a solid trend-coloured fill (FatigueGauge-style).
|
|
107
|
+
*/
|
|
108
|
+
export type ZoneTrackMarker =
|
|
109
|
+
| { type: 'needle'; value: number; color?: string; glow?: boolean }
|
|
110
|
+
| { type: 'fill'; value: number; color?: string; glow?: boolean }
|
|
111
|
+
|
|
112
|
+
/** An optional translucent range highlight (e.g. RpeCalibration's predicted CI band). */
|
|
113
|
+
export interface ZoneTrackBand {
|
|
114
|
+
/** Range start in domain units. */
|
|
115
|
+
from: number
|
|
116
|
+
/** Range end in domain units. */
|
|
117
|
+
to: number
|
|
118
|
+
/** Highlight colour (typically a translucent token). */
|
|
119
|
+
color: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ZoneTrackProps extends ViewProps {
|
|
123
|
+
/** Ordered zone bands, laid left→right; each covers `[previous.upTo, upTo]` of the domain. */
|
|
124
|
+
zones: ZoneTrackZone[]
|
|
125
|
+
/** Domain maximum (right edge). */
|
|
126
|
+
max: number
|
|
127
|
+
/** Domain minimum (left edge). Default 0. */
|
|
128
|
+
min?: number
|
|
129
|
+
/** The value marker — a needle line or a left-anchored fill. Omit for a bare gradient track. */
|
|
130
|
+
marker?: ZoneTrackMarker | null
|
|
131
|
+
/** Optional tick marks with labels below the track. */
|
|
132
|
+
ticks?: ZoneTrackTick[]
|
|
133
|
+
/** Optional translucent range highlight drawn over the gradient. */
|
|
134
|
+
band?: ZoneTrackBand | null
|
|
135
|
+
/** Density: `default` (compact) or `wall` (the across-the-room dashboard scale). Scales track, needle, ticks and labels together. */
|
|
136
|
+
size?: ZoneTrackSize
|
|
137
|
+
/** Track height in px. Overrides the `size` default (14 default / 24 wall). */
|
|
138
|
+
trackHeight?: number
|
|
139
|
+
/** How far a needle marker overhangs the track top + bottom, in px. Overrides the `size` default (6 default / 9 wall). */
|
|
140
|
+
needleOverhang?: number
|
|
141
|
+
/** Muted colour of the track behind / beyond the fill. Default a charcoal step. */
|
|
142
|
+
trackColor?: string
|
|
143
|
+
className?: string
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Map a domain value to a clamped 0..1 fraction of the track width. */
|
|
147
|
+
function fraction(value: number, min: number, max: number): number {
|
|
148
|
+
if (max <= min) return 0
|
|
149
|
+
return Math.max(0, Math.min(1, (value - min) / (max - min)))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function pct(frac: number): DimensionValue {
|
|
153
|
+
return `${frac * 100}%`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Low-level gauge primitive: a horizontal pill track carrying an N-band zone gradient,
|
|
158
|
+
* optional tick marks with labels, and a single value marker (a needle line or a
|
|
159
|
+
* left-anchored fill). The zone bands are weighted by their domain span, so thresholds
|
|
160
|
+
* need not be evenly spaced. This is the shared visual base for the linear gauge family
|
|
161
|
+
* (FatigueMeter needle, TrainingLoadGauge zone bar, RpeCalibration band + marker) — pass
|
|
162
|
+
* the domain (`min`/`max`), the `zones`, and a `marker`; colours are literal hex from the
|
|
163
|
+
* ramp tokens (never `var()` refs, so they survive the RNW/vitest alias).
|
|
164
|
+
*/
|
|
165
|
+
export function ZoneTrack({
|
|
166
|
+
zones,
|
|
167
|
+
max,
|
|
168
|
+
min = 0,
|
|
169
|
+
marker = null,
|
|
170
|
+
ticks,
|
|
171
|
+
band = null,
|
|
172
|
+
size = 'default',
|
|
173
|
+
trackHeight: trackHeightProp,
|
|
174
|
+
needleOverhang: needleOverhangProp,
|
|
175
|
+
trackColor = DEFAULT_TRACK_COLOR,
|
|
176
|
+
className,
|
|
177
|
+
style,
|
|
178
|
+
accessibilityLabel,
|
|
179
|
+
...props
|
|
180
|
+
}: ZoneTrackProps) {
|
|
181
|
+
const s = ZONE_SIZES[size]
|
|
182
|
+
const trackHeight = trackHeightProp ?? s.trackHeight
|
|
183
|
+
const needleOverhang = needleOverhangProp ?? s.needleOverhang
|
|
184
|
+
const isNeedle = marker?.type === 'needle'
|
|
185
|
+
const markerHeight = isNeedle ? trackHeight + needleOverhang * 2 : trackHeight
|
|
186
|
+
const markerFrac = marker != null ? fraction(marker.value, min, max) : 0
|
|
187
|
+
|
|
188
|
+
const bands = zones.map((zone, i) => {
|
|
189
|
+
const prev = i === 0 ? min : zones[i - 1].upTo
|
|
190
|
+
const start = Math.max(min, Math.min(max, prev))
|
|
191
|
+
const end = Math.max(min, Math.min(max, zone.upTo))
|
|
192
|
+
return { color: zone.color, weight: Math.max(0, end - start) }
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<View
|
|
197
|
+
className={className}
|
|
198
|
+
style={[{ width: '100%' }, style]}
|
|
199
|
+
accessibilityRole="progressbar"
|
|
200
|
+
accessibilityValue={marker != null ? { min, max, now: marker.value } : { min, max, now: min }}
|
|
201
|
+
accessibilityLabel={accessibilityLabel ?? `Zone track: ${marker?.value ?? min}`}
|
|
202
|
+
testID="zone-track"
|
|
203
|
+
{...props}
|
|
204
|
+
>
|
|
205
|
+
<View style={{ position: 'relative', height: markerHeight }}>
|
|
206
|
+
<View
|
|
207
|
+
testID="zone-track-track"
|
|
208
|
+
accessibilityElementsHidden
|
|
209
|
+
style={{
|
|
210
|
+
position: 'absolute',
|
|
211
|
+
top: (markerHeight - trackHeight) / 2,
|
|
212
|
+
left: 0,
|
|
213
|
+
right: 0,
|
|
214
|
+
height: trackHeight,
|
|
215
|
+
borderRadius: trackHeight / 2,
|
|
216
|
+
backgroundColor: trackColor,
|
|
217
|
+
overflow: 'hidden',
|
|
218
|
+
flexDirection: 'row',
|
|
219
|
+
// The pill's own box-shadow renders outside its overflow:hidden, so
|
|
220
|
+
// `glow` haloes the whole track in the marker colour.
|
|
221
|
+
...(marker?.glow
|
|
222
|
+
? { boxShadow: glowShadow(marker.color ?? DEFAULT_MARKER_COLOR) }
|
|
223
|
+
: null),
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
{bands.map((b, i) => (
|
|
227
|
+
<View
|
|
228
|
+
key={i}
|
|
229
|
+
testID="zone-track-band"
|
|
230
|
+
style={{
|
|
231
|
+
flexGrow: b.weight,
|
|
232
|
+
flexShrink: 1,
|
|
233
|
+
flexBasis: 0,
|
|
234
|
+
minWidth: 0,
|
|
235
|
+
height: '100%',
|
|
236
|
+
backgroundColor: b.color,
|
|
237
|
+
}}
|
|
238
|
+
/>
|
|
239
|
+
))}
|
|
240
|
+
|
|
241
|
+
{band != null && (
|
|
242
|
+
<View
|
|
243
|
+
testID="zone-track-band-highlight"
|
|
244
|
+
style={{
|
|
245
|
+
position: 'absolute',
|
|
246
|
+
top: 0,
|
|
247
|
+
bottom: 0,
|
|
248
|
+
left: pct(fraction(band.from, min, max)),
|
|
249
|
+
width: pct(fraction(band.to, min, max) - fraction(band.from, min, max)),
|
|
250
|
+
backgroundColor: band.color,
|
|
251
|
+
}}
|
|
252
|
+
/>
|
|
253
|
+
)}
|
|
254
|
+
|
|
255
|
+
{marker?.type === 'fill' && (
|
|
256
|
+
<View
|
|
257
|
+
testID="zone-track-unfilled"
|
|
258
|
+
style={{
|
|
259
|
+
position: 'absolute',
|
|
260
|
+
top: 0,
|
|
261
|
+
bottom: 0,
|
|
262
|
+
left: pct(markerFrac),
|
|
263
|
+
right: 0,
|
|
264
|
+
backgroundColor: trackColor,
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
)}
|
|
268
|
+
{marker?.type === 'fill' && marker.color != null && (
|
|
269
|
+
<View
|
|
270
|
+
testID="zone-track-fill"
|
|
271
|
+
style={{
|
|
272
|
+
position: 'absolute',
|
|
273
|
+
top: 0,
|
|
274
|
+
bottom: 0,
|
|
275
|
+
left: 0,
|
|
276
|
+
width: pct(markerFrac),
|
|
277
|
+
backgroundColor: marker.color,
|
|
278
|
+
}}
|
|
279
|
+
/>
|
|
280
|
+
)}
|
|
281
|
+
</View>
|
|
282
|
+
|
|
283
|
+
{isNeedle && (
|
|
284
|
+
<View
|
|
285
|
+
testID="zone-track-needle"
|
|
286
|
+
style={{
|
|
287
|
+
position: 'absolute',
|
|
288
|
+
top: 0,
|
|
289
|
+
left: pct(markerFrac),
|
|
290
|
+
width: s.needleWidth,
|
|
291
|
+
height: markerHeight,
|
|
292
|
+
borderRadius: s.needleWidth / 2,
|
|
293
|
+
backgroundColor: marker.color ?? DEFAULT_MARKER_COLOR,
|
|
294
|
+
transform: [{ translateX: -s.needleWidth / 2 }],
|
|
295
|
+
}}
|
|
296
|
+
/>
|
|
297
|
+
)}
|
|
298
|
+
|
|
299
|
+
{/* Colored tick lines over the track (decorative; the labels below carry the a11y). */}
|
|
300
|
+
{ticks?.map((tick, i) => {
|
|
301
|
+
const emphasized = tick.emphasized === true
|
|
302
|
+
const lineColor = tick.color ?? (emphasized ? t['brand-primary'] : TICK_COLOR)
|
|
303
|
+
const lineWidth = emphasized ? s.tickLineEmphasized : s.tickLineNormal
|
|
304
|
+
return (
|
|
305
|
+
<View
|
|
306
|
+
key={`line-${i}`}
|
|
307
|
+
testID="zone-track-tick-line"
|
|
308
|
+
accessibilityElementsHidden
|
|
309
|
+
style={{
|
|
310
|
+
position: 'absolute',
|
|
311
|
+
top: (markerHeight - trackHeight) / 2 - s.tickLineTopPad,
|
|
312
|
+
left: pct(fraction(tick.value, min, max)),
|
|
313
|
+
width: lineWidth,
|
|
314
|
+
height: trackHeight + s.tickLineHeightPad,
|
|
315
|
+
borderRadius: lineWidth / 2,
|
|
316
|
+
backgroundColor: lineColor,
|
|
317
|
+
transform: [{ translateX: -lineWidth / 2 }],
|
|
318
|
+
zIndex: 3,
|
|
319
|
+
}}
|
|
320
|
+
/>
|
|
321
|
+
)
|
|
322
|
+
})}
|
|
323
|
+
</View>
|
|
324
|
+
|
|
325
|
+
{ticks != null && ticks.length > 0 && (
|
|
326
|
+
<View
|
|
327
|
+
style={{ position: 'relative', height: s.labelRowHeight, marginTop: s.labelMarginTop }}
|
|
328
|
+
>
|
|
329
|
+
{ticks.map((tick, i) => {
|
|
330
|
+
if (tick.label == null) return null
|
|
331
|
+
const emphasized = tick.emphasized === true
|
|
332
|
+
const labelColor = tick.color ?? (emphasized ? t['brand-primary'] : TICK_COLOR)
|
|
333
|
+
const labelNode = (
|
|
334
|
+
<Text
|
|
335
|
+
testID="zone-track-tick-label"
|
|
336
|
+
style={{
|
|
337
|
+
fontSize: s.tickFont,
|
|
338
|
+
letterSpacing: s.tickLetterSpacing,
|
|
339
|
+
color: labelColor,
|
|
340
|
+
fontFamily: 'monospace',
|
|
341
|
+
fontWeight: emphasized ? '700' : '400',
|
|
342
|
+
}}
|
|
343
|
+
>
|
|
344
|
+
{tick.label}
|
|
345
|
+
</Text>
|
|
346
|
+
)
|
|
347
|
+
return (
|
|
348
|
+
<View
|
|
349
|
+
key={`label-${i}`}
|
|
350
|
+
testID="zone-track-tick"
|
|
351
|
+
style={{
|
|
352
|
+
position: 'absolute',
|
|
353
|
+
left: pct(fraction(tick.value, min, max)),
|
|
354
|
+
transform: [{ translateX: -s.tickCellWidth / 2 }],
|
|
355
|
+
width: s.tickCellWidth,
|
|
356
|
+
alignItems: 'center',
|
|
357
|
+
}}
|
|
358
|
+
>
|
|
359
|
+
{tick.tooltip != null ? (
|
|
360
|
+
<Tooltip label={tick.tooltip} placement="top">
|
|
361
|
+
{labelNode}
|
|
362
|
+
</Tooltip>
|
|
363
|
+
) : (
|
|
364
|
+
labelNode
|
|
365
|
+
)}
|
|
366
|
+
</View>
|
|
367
|
+
)
|
|
368
|
+
})}
|
|
369
|
+
</View>
|
|
370
|
+
)}
|
|
371
|
+
</View>
|
|
372
|
+
)
|
|
373
|
+
}
|
|
@@ -29,6 +29,7 @@ export { WorkoutPill, type WorkoutPillProps, type WorkoutPillStatus } from './Wo
|
|
|
29
29
|
export {
|
|
30
30
|
VelocityStrip,
|
|
31
31
|
type VelocityStripProps,
|
|
32
|
+
type VelocitySet,
|
|
32
33
|
type VelocityZoneBandProp,
|
|
33
34
|
getVelocityZoneColor,
|
|
34
35
|
getVelocityZoneName,
|
|
@@ -37,10 +38,46 @@ export {
|
|
|
37
38
|
} from './VelocityStrip'
|
|
38
39
|
export { MuscleGroupChip, type MuscleGroupChipProps, type VolumeStatus } from './MuscleGroupChip'
|
|
39
40
|
export { Sparkline, type SparklineProps } from './Sparkline'
|
|
40
|
-
export { SetRow, type SetRowProps, type
|
|
41
|
+
export { SetRow, type SetRowProps, type SetRowState, type SetRowUnit } from './SetRow'
|
|
41
42
|
export { InputBar, type InputBarProps } from './InputBar'
|
|
42
43
|
export { RestTimer, type RestTimerProps } from './RestTimer'
|
|
43
|
-
export {
|
|
44
|
+
export { MetricCell, type MetricCellProps, METRIC_FONT } from './metricText'
|
|
45
|
+
export { SetsRepsLoad, type SetsRepsLoadProps } from './SetsRepsLoad'
|
|
46
|
+
export { SegmentedBar, type SegmentedBarProps, type SegmentedBarSegment } from './SegmentedBar'
|
|
47
|
+
export { paceTone, paceToneColor, type PaceTone } from './paceTone'
|
|
48
|
+
export {
|
|
49
|
+
SegmentedProgressBar,
|
|
50
|
+
type SegmentedProgressBarProps,
|
|
51
|
+
type SegmentedProgressBarSegment,
|
|
52
|
+
} from './SegmentedProgressBar'
|
|
53
|
+
export { MetricTiles, type MetricTilesProps, type MetricTileData } from './MetricTiles'
|
|
54
|
+
export { ScheduleTiles, type ScheduleTilesProps } from './ScheduleTiles'
|
|
55
|
+
export { SetBar, type SetBarProps } from './SetBar'
|
|
56
|
+
export {
|
|
57
|
+
SetStrip,
|
|
58
|
+
type SetStripProps,
|
|
59
|
+
type SetStripSet,
|
|
60
|
+
SET_STRIP_ZONES,
|
|
61
|
+
SET_STRIP_VARIABLE_COLOR,
|
|
62
|
+
velocityZoneColor,
|
|
63
|
+
} from './SetStrip'
|
|
64
|
+
export {
|
|
65
|
+
ExerciseIndicator,
|
|
66
|
+
resolveIndicator,
|
|
67
|
+
INDICATOR_PRECEDENCE,
|
|
68
|
+
type ExerciseIndicatorProps,
|
|
69
|
+
type ExerciseIndicatorKind,
|
|
70
|
+
} from './ExerciseIndicator'
|
|
71
|
+
export { ExerciseHeading, type ExerciseHeadingProps } from './ExerciseHeading'
|
|
72
|
+
export { ExerciseCardHeading, type ExerciseCardHeadingProps } from './ExerciseCardHeading'
|
|
73
|
+
export { ExerciseCard, type ExerciseCardProps } from './ExerciseCard'
|
|
74
|
+
export { SetTableHeader, type SetTableHeaderProps } from './SetTableHeader'
|
|
75
|
+
export {
|
|
76
|
+
SessionHeader,
|
|
77
|
+
type SessionHeaderProps,
|
|
78
|
+
type SessionHeaderPlanEntry,
|
|
79
|
+
} from './SessionHeader'
|
|
80
|
+
export { SessionRail, type SessionRailProps, type SessionRailExercise } from './SessionRail'
|
|
44
81
|
export { SupersetWrapper, type SupersetWrapperProps } from './SupersetWrapper'
|
|
45
82
|
export {
|
|
46
83
|
MesoProgressBar,
|
|
@@ -152,3 +189,19 @@ export type {
|
|
|
152
189
|
WorkoutProgress,
|
|
153
190
|
WorkoutGroup,
|
|
154
191
|
} from './ActiveWorkoutPage'
|
|
192
|
+
export { ZoneTrack } from './ZoneTrack'
|
|
193
|
+
export type {
|
|
194
|
+
ZoneTrackProps,
|
|
195
|
+
ZoneTrackZone,
|
|
196
|
+
ZoneTrackTick,
|
|
197
|
+
ZoneTrackMarker,
|
|
198
|
+
ZoneTrackBand,
|
|
199
|
+
} from './ZoneTrack'
|
|
200
|
+
export { FatigueMeter } from './FatigueMeter'
|
|
201
|
+
export type { FatigueMeterProps } from './FatigueMeter'
|
|
202
|
+
export { VolumeLandmarkBar } from './VolumeLandmarkBar'
|
|
203
|
+
export type { VolumeLandmarkBarProps, VolumeZone } from './VolumeLandmarkBar'
|
|
204
|
+
export { StatusPill, statusPillColor } from './StatusPill'
|
|
205
|
+
export type { StatusPillProps, StatusPillStatus } from './StatusPill'
|
|
206
|
+
export { LiveAuraFrame, liveAuraColor } from './LiveAuraFrame'
|
|
207
|
+
export type { LiveAuraFrameProps, LiveAuraCategory } from './LiveAuraFrame'
|