@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.
Files changed (258) hide show
  1. package/dist/bodymap.js +12 -7
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +12 -7
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +956 -128
  6. package/dist/index.d.ts +956 -128
  7. package/dist/index.js +2873 -981
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2826 -982
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{pages-MO0JCySL.d.mts → pages-DCFBqp79.d.mts} +289 -57
  12. package/dist/{pages-D7Jlssvp.d.ts → pages-_fI3-x7Z.d.ts} +289 -57
  13. package/dist/pages.d.mts +1 -1
  14. package/dist/pages.d.ts +1 -1
  15. package/dist/pages.js +1659 -799
  16. package/dist/pages.js.map +1 -1
  17. package/dist/pages.mjs +1660 -800
  18. package/dist/pages.mjs.map +1 -1
  19. package/dist/theme/index.d.mts +16 -82
  20. package/dist/theme/index.d.ts +16 -82
  21. package/dist/theme/index.js +13 -115
  22. package/dist/theme/index.js.map +1 -1
  23. package/dist/theme/index.mjs +13 -115
  24. package/dist/theme/index.mjs.map +1 -1
  25. package/dist/theme/tokens-css.js +14 -6
  26. package/dist/theme/tokens-css.js.map +1 -1
  27. package/dist/theme/tokens-css.mjs +14 -6
  28. package/dist/theme/tokens-css.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/src/arch/Architecture.stories.tsx +870 -0
  31. package/src/arch/annotations.json +48 -0
  32. package/src/arch/arch-graph.json +4301 -0
  33. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +90 -0
  34. package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
  35. package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
  36. package/src/components/custom/CircularTimer/index.ts +1 -0
  37. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  38. package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
  39. package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
  40. package/src/components/custom/Metric/Metric.stories.tsx +1 -1
  41. package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
  42. package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
  43. package/src/components/custom/Table/Table.stories.tsx +1 -1
  44. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
  45. package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
  46. package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
  47. package/src/components/custom/TimerReadout/index.ts +1 -0
  48. package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
  49. package/src/components/custom/Typography/Typography.stories.tsx +1 -1
  50. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
  51. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
  52. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
  53. package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
  54. package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
  55. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
  56. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
  57. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  58. package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
  59. package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
  60. package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
  61. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
  62. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
  63. package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
  64. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
  65. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
  66. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
  67. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
  68. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
  69. package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
  70. package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
  71. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
  72. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
  73. package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
  74. package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
  75. package/src/components/custom/Workout/FatigueMeter.test.tsx +85 -0
  76. package/src/components/custom/Workout/FatigueMeter.tsx +86 -0
  77. package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
  78. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  79. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
  80. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
  81. package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
  82. package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
  83. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  84. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
  85. package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
  86. package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
  87. package/src/components/custom/Workout/MetricTiles.tsx +48 -0
  88. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
  89. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  90. package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
  91. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
  92. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
  93. package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
  94. package/src/components/custom/Workout/README.md +193 -4
  95. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
  96. package/src/components/custom/Workout/RestTimer.stories.tsx +133 -3
  97. package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
  98. package/src/components/custom/Workout/RestTimer.tsx +155 -59
  99. package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
  100. package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
  101. package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
  102. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
  103. package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
  104. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
  105. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
  106. package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
  107. package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
  108. package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
  109. package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
  110. package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
  111. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
  112. package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
  113. package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
  114. package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
  115. package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
  116. package/src/components/custom/Workout/SessionHeader.tsx +152 -0
  117. package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
  118. package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
  119. package/src/components/custom/Workout/SessionRail.tsx +126 -0
  120. package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
  121. package/src/components/custom/Workout/SetBar.test.tsx +123 -0
  122. package/src/components/custom/Workout/SetBar.tsx +171 -0
  123. package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
  124. package/src/components/custom/Workout/SetRow.test.tsx +94 -166
  125. package/src/components/custom/Workout/SetRow.tsx +169 -232
  126. package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
  127. package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
  128. package/src/components/custom/Workout/SetStrip.tsx +71 -0
  129. package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
  130. package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
  131. package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
  132. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
  133. package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
  134. package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
  135. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  136. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  137. package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
  138. package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
  139. package/src/components/custom/Workout/StatusPill.tsx +88 -0
  140. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
  141. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
  142. package/src/components/custom/Workout/TempoBar.stories.tsx +70 -1
  143. package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
  144. package/src/components/custom/Workout/TempoBar.tsx +107 -22
  145. package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
  146. package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
  147. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
  148. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
  149. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
  150. package/src/components/custom/Workout/VelocityStrip.stories.tsx +262 -56
  151. package/src/components/custom/Workout/VelocityStrip.test.tsx +440 -141
  152. package/src/components/custom/Workout/VelocityStrip.tsx +776 -141
  153. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
  154. package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
  155. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
  156. package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
  157. package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
  158. package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
  159. package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
  160. package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
  161. package/src/components/custom/Workout/ZoneTrack.stories.tsx +139 -0
  162. package/src/components/custom/Workout/ZoneTrack.test.tsx +234 -0
  163. package/src/components/custom/Workout/ZoneTrack.tsx +373 -0
  164. package/src/components/custom/Workout/index.ts +55 -2
  165. package/src/components/custom/Workout/metricText.stories.tsx +69 -0
  166. package/src/components/custom/Workout/metricText.tsx +34 -0
  167. package/src/components/custom/Workout/paceTone.test.ts +29 -0
  168. package/src/components/custom/Workout/paceTone.ts +27 -0
  169. package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
  170. package/src/components/custom/index.ts +2 -0
  171. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  172. package/src/components/icons/icons.stories.tsx +67 -2
  173. package/src/components/icons/icons.test.tsx +32 -2
  174. package/src/components/icons/icons.tsx +108 -0
  175. package/src/components/shell/BrandLockup.stories.tsx +3 -3
  176. package/src/components/shell/DashboardShell.stories.tsx +65 -0
  177. package/src/components/shell/DashboardShell.test.tsx +42 -0
  178. package/src/components/shell/DashboardShell.tsx +74 -0
  179. package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
  180. package/src/components/shell/DeviceMenu.stories.tsx +5 -5
  181. package/src/components/shell/DeviceRow.stories.tsx +3 -3
  182. package/src/components/shell/NavItem.stories.tsx +52 -0
  183. package/src/components/shell/NavItem.test.tsx +35 -0
  184. package/src/components/shell/NavItem.tsx +75 -0
  185. package/src/components/shell/README.md +30 -0
  186. package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
  187. package/src/components/shell/SessionStatePill.stories.tsx +3 -3
  188. package/src/components/shell/SessionStatePill.tsx +1 -1
  189. package/src/components/shell/SideNav.stories.tsx +81 -0
  190. package/src/components/shell/SideNav.test.tsx +51 -0
  191. package/src/components/shell/SideNav.tsx +74 -0
  192. package/src/components/shell/TopBar.stories.tsx +5 -5
  193. package/src/components/shell/index.ts +5 -0
  194. package/src/components/ui/alert/Alert.stories.tsx +86 -3
  195. package/src/components/ui/alert/Alert.test.tsx +52 -0
  196. package/src/components/ui/alert/Alert.tsx +54 -20
  197. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  198. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  199. package/src/components/ui/badge/Badge.stories.tsx +1 -1
  200. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  201. package/src/components/ui/button/Button.stories.tsx +1 -1
  202. package/src/components/ui/card/Card.stories.tsx +1 -1
  203. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  204. package/src/components/ui/chip/Chip.stories.tsx +1 -1
  205. package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
  206. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  207. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  208. package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
  209. package/src/components/ui/form-field/FormField.stories.tsx +1 -1
  210. package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
  211. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  212. package/src/components/ui/index.ts +1 -0
  213. package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
  214. package/src/components/ui/indicator/Indicator.test.tsx +1 -0
  215. package/src/components/ui/indicator/Indicator.tsx +6 -1
  216. package/src/components/ui/input/Input.stories.tsx +1 -1
  217. package/src/components/ui/link/Link.stories.tsx +1 -1
  218. package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
  219. package/src/components/ui/menu/Menu.stories.tsx +1 -1
  220. package/src/components/ui/modal/Modal.stories.tsx +1 -1
  221. package/src/components/ui/pill/Pill.stories.tsx +1 -1
  222. package/src/components/ui/popover/Popover.stories.tsx +1 -1
  223. package/src/components/ui/progress/Progress.stories.tsx +1 -1
  224. package/src/components/ui/progress/Progress.tsx +47 -22
  225. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  226. package/src/components/ui/section/Section.stories.tsx +1 -1
  227. package/src/components/ui/select/Select.stories.tsx +1 -1
  228. package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
  229. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  230. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  231. package/src/components/ui/surface/Surface.stories.tsx +1 -1
  232. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  233. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  234. package/src/components/ui/tile/Tile.stories.tsx +79 -0
  235. package/src/components/ui/tile/Tile.test.tsx +48 -0
  236. package/src/components/ui/tile/Tile.tsx +64 -0
  237. package/src/components/ui/tile/index.ts +2 -0
  238. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  239. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  240. package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
  241. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
  242. package/src/hooks/index.ts +2 -0
  243. package/src/hooks/useTimer.test.ts +150 -0
  244. package/src/hooks/useTimer.ts +77 -0
  245. package/src/index.ts +3 -0
  246. package/src/pages.ts +1 -1
  247. package/src/stories/PresetShowcase.stories.tsx +1 -1
  248. package/src/stories/ThemePresets.stories.tsx +1 -1
  249. package/src/theme/ColorSystem.stories.tsx +1 -1
  250. package/src/theme/Colors.stories.tsx +5 -8
  251. package/src/theme/config.ts +4 -0
  252. package/src/theme/elevation.stories.tsx +1 -1
  253. package/src/theme/global.css +5 -1
  254. package/src/theme/shadows.stories.tsx +1 -1
  255. package/src/theme/tokens/primitives.ts +11 -107
  256. package/src/theme/tokens/semantic.ts +8 -0
  257. package/src/theme/workout-tokens.ts +11 -7
  258. 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 SetRowMode } from './SetRow'
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 { ExerciseCard, type ExerciseCardProps, type ExerciseCardState } from './ExerciseCard'
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'