@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,69 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { MetricCell } from './metricText'
4
+
5
+ /**
6
+ * `MetricCell` — the shared "segmented metric" typography primitive (Inter · 600 ·
7
+ * letter-spacing 1). Value cells take a primary color, separators a muted one. The
8
+ * single source of the value/separator look shared by SetsRepsLoad and TempoDisplay.
9
+ */
10
+ const meta: Meta<typeof MetricCell> = {
11
+ title: 'Workout/MetricCell',
12
+ component: MetricCell,
13
+ tags: ['autodocs'],
14
+ parameters: {
15
+ docs: {
16
+ description: {
17
+ component:
18
+ '**Primitive.** The shared Inter·600·letter-spacing-1 value/separator cell. ' +
19
+ 'Extracted so the two prescription readouts read as one visual language instead of ' +
20
+ 'drifting copies. Used-by ↑ ' +
21
+ '[SetsRepsLoad](?path=/docs/workout-setsrepsload--docs) + ' +
22
+ '[TempoDisplay](?path=/docs/workout-tempodisplay--docs).',
23
+ },
24
+ },
25
+ },
26
+ argTypes: {
27
+ color: { control: 'color' },
28
+ fontSize: { control: { type: 'range', min: 9, max: 24, step: 1 } },
29
+ },
30
+ decorators: [
31
+ (Story) => (
32
+ <View style={{ padding: 20, backgroundColor: '#131313' }}>
33
+ <Story />
34
+ </View>
35
+ ),
36
+ ],
37
+ }
38
+
39
+ export default meta
40
+ type Story = StoryObj<typeof MetricCell>
41
+
42
+ export const ValueCell: Story = {
43
+ args: { children: '145', color: '#F3F4F6', fontSize: 11 },
44
+ }
45
+
46
+ export const Separator: Story = {
47
+ args: { children: '×', color: '#6B7280', fontSize: 11 },
48
+ parameters: { docs: { description: { story: 'A muted separator cell (× / @ / -).' } } },
49
+ }
50
+
51
+ export const InContext: Story = {
52
+ render: () => (
53
+ <View style={{ flexDirection: 'row', alignItems: 'baseline' }}>
54
+ <MetricCell color="#F3F4F6">5</MetricCell>
55
+ <MetricCell color="#6B7280">&nbsp;×&nbsp;</MetricCell>
56
+ <MetricCell color="#F3F4F6">8</MetricCell>
57
+ <MetricCell color="#6B7280">&nbsp;@&nbsp;</MetricCell>
58
+ <MetricCell color="#F3F4F6">145</MetricCell>
59
+ <MetricCell color="#6B7280">&nbsp;lb</MetricCell>
60
+ </View>
61
+ ),
62
+ parameters: {
63
+ docs: {
64
+ description: {
65
+ story: 'Value + separator cells composed into the sets × reps @ load language.',
66
+ },
67
+ },
68
+ },
69
+ }
@@ -0,0 +1,34 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import type { ReactNode } from 'react'
3
+ import { Text } from 'react-native'
4
+
5
+ /**
6
+ * Shared "segmented metric" typography primitive — the Inter · 600 · letter-spacing 1
7
+ * value/separator cell used by both {@link TempoDisplay} (tempo digits + dashes) and
8
+ * {@link SetsRepsLoad} (sets × reps @ load). Extracted so the two read as one visual
9
+ * language instead of drifting copies.
10
+ */
11
+ export const METRIC_FONT = 'Inter, sans-serif'
12
+
13
+ export interface MetricCellProps {
14
+ children: ReactNode
15
+ /** Cell text color — value cells take a primary color, separators a muted one. */
16
+ color: string
17
+ fontSize?: number
18
+ }
19
+
20
+ export function MetricCell({ children, color, fontSize = 11 }: MetricCellProps) {
21
+ return (
22
+ <Text
23
+ style={{
24
+ fontFamily: METRIC_FONT,
25
+ fontSize,
26
+ color,
27
+ fontWeight: '600',
28
+ letterSpacing: 1,
29
+ }}
30
+ >
31
+ {children}
32
+ </Text>
33
+ )
34
+ }
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { paceTone, paceToneColor } from './paceTone'
3
+
4
+ describe('paceTone', () => {
5
+ it('reads as neutral when there is no target', () => {
6
+ expect(paceTone(0.6)).toBe('neutral')
7
+ expect(paceTone(0)).toBe('neutral')
8
+ })
9
+
10
+ it('reads as ahead when progress equals the target (inclusive)', () => {
11
+ expect(paceTone(0.5, 0.5)).toBe('ahead')
12
+ })
13
+
14
+ it('reads as ahead when progress is past the target', () => {
15
+ expect(paceTone(0.8, 0.5)).toBe('ahead')
16
+ })
17
+
18
+ it('reads as behind when progress trails the target', () => {
19
+ expect(paceTone(0.3, 0.5)).toBe('behind')
20
+ })
21
+
22
+ describe('paceToneColor', () => {
23
+ it('maps each tone to its real titan literal hex', () => {
24
+ expect(paceToneColor('ahead')).toBe('#2ED573') // status-success (green 300)
25
+ expect(paceToneColor('behind')).toBe('#F9B415') // status-warning (amber 300)
26
+ expect(paceToneColor('neutral')).toBe('#01B5D1') // cyan 400
27
+ })
28
+ })
29
+ })
@@ -0,0 +1,27 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import { primitiveRamps } from '../../../theme/tokens/primitives'
3
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
4
+
5
+ /** How progress compares to its target pace: `ahead`, `behind`, or `neutral` (no target). */
6
+ export type PaceTone = 'ahead' | 'behind' | 'neutral'
7
+
8
+ /**
9
+ * Classify `progress` (0..1) against an optional `target` (0..1). No target reads
10
+ * as `neutral`; at or past target is `ahead`, otherwise `behind`.
11
+ */
12
+ export function paceTone(progress: number, target?: number): PaceTone {
13
+ if (target === undefined) return 'neutral'
14
+ return progress >= target ? 'ahead' : 'behind'
15
+ }
16
+
17
+ /** Literal-hex fill for each tone (RNW-safe): success · warning · cyan pin. */
18
+ const PACE_TONE_COLORS: Record<PaceTone, string> = {
19
+ ahead: getSemanticColors('dark')['status-success'],
20
+ behind: getSemanticColors('dark')['status-warning'],
21
+ neutral: primitiveRamps.cyan[400],
22
+ }
23
+
24
+ /** The literal-hex colour for a tone. */
25
+ export function paceToneColor(tone: PaceTone): string {
26
+ return PACE_TONE_COLORS[tone]
27
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Shared kit for the S3 Session-Rail explorations (NOT a stories file — imported by the sheets under
3
+ * `Custom/Workout/Explorations/*`). Throwaway CSS R&D at the real 232px rail width, on real titan tokens.
4
+ * When these decisions harden they move into the real `ExerciseCard` + a titan `SessionRail` organism.
5
+ */
6
+ import type { ReactNode } from 'react'
7
+ import { TempoDisplay } from './TempoDisplay'
8
+
9
+ // ---- geometry
10
+ export const RAIL_W = 232
11
+ export const SET_GAP = 5
12
+
13
+ // ---- surface + text tokens (dark theme; charcoal + neutral ramps)
14
+ export const GREY = '#2C2C2C' // charcoal 300 — upcoming/placeholder
15
+ export const BORDER_SUBTLE = '#1C1C1C' // charcoal 500 — row dividers
16
+ export const T_PRIMARY = '#F3F4F6' // neutral 100
17
+ export const T_SECONDARY = '#9CA3AF' // neutral 400
18
+ export const T_TERTIARY = '#6B7280' // neutral 500
19
+ export const RAISED = '#1F1F1F' // charcoal 400 — nav + heading raised plane
20
+ export const INSET = '#131313' // charcoal 800 — sunk exercise-list column
21
+ // depth lives on the sunk list: inner shadow from the heading (top) + nav (left). Raised plane is flat.
22
+ export const INSET_SHADOW =
23
+ 'inset 0 9px 12px -6px rgba(0,0,0,0.85), inset 7px 0 10px -6px rgba(0,0,0,0.7), inset 0 -1px 0 rgba(255,255,255,0.02)'
24
+
25
+ // ---- zone colors = real titan ramp pins (primitiveRamps)
26
+ export const RED = '#D14343' // red 600
27
+ export const ORANGE = '#FF7900' // orange 400
28
+ export const AMBER = '#F9B415' // amber 300
29
+ export const GREEN = '#2ED573' // green 300
30
+ export const velColor = (v: number): string => (v < 0.5 ? RED : v < 0.75 ? ORANGE : v < 1.0 ? AMBER : GREEN)
31
+
32
+ // active-set pulse: each segment eases along its own ramp (adjacent lighter/darker steps) through the pin.
33
+ export const PULSE_CSS = `
34
+ @keyframes s3pRed{0%,100%{background:#E05254}50%{background:#A4221C}}
35
+ @keyframes s3pOrange{0%,100%{background:#FFA063}50%{background:#DA5F00}}
36
+ @keyframes s3pAmber{0%,100%{background:#FFD352}50%{background:#E08C00}}
37
+ @keyframes s3pGreen{0%,100%{background:#58F69E}50%{background:#21C05D}}`
38
+ export const pulseAnim = (c: string): string | undefined => {
39
+ const name = c === RED ? 's3pRed' : c === ORANGE ? 's3pOrange' : c === AMBER ? 's3pAmber' : c === GREEN ? 's3pGreen' : null
40
+ return name ? `${name} 1.9s ease-in-out infinite` : undefined
41
+ }
42
+
43
+ // ---- sets/reps/load in the TempoDisplay visual language (Inter · 600 · letter-spacing 1 · gray separators)
44
+ const INTER = 'Inter, sans-serif'
45
+ const SRL_SEP = '#6B7280'
46
+ function SRLCell({ children, color = T_PRIMARY }: { children: ReactNode; color?: string }) {
47
+ return <span style={{ fontFamily: INTER, fontSize: 11, fontWeight: 600, letterSpacing: 1, color }}>{children}</span>
48
+ }
49
+ export function SetsRepsLoad({ sets, reps, load, unit = 'lb' }: { sets: number; reps: number | string; load: number; unit?: string }) {
50
+ return (
51
+ <span style={{ display: 'inline-flex', alignItems: 'baseline', whiteSpace: 'nowrap' }}>
52
+ <SRLCell>{sets}</SRLCell>
53
+ <SRLCell color={SRL_SEP}>&nbsp;×&nbsp;</SRLCell>
54
+ <SRLCell>{reps}</SRLCell>
55
+ <SRLCell color={SRL_SEP}>&nbsp;@&nbsp;</SRLCell>
56
+ <SRLCell>{load}</SRLCell>
57
+ <SRLCell color={SRL_SEP}>&nbsp;{unit}</SRLCell>
58
+ </span>
59
+ )
60
+ }
61
+
62
+ // ---- per-set bar: one continuous bar, rep intensities as butted segments (no gaps); active segments pulse
63
+ export type SetState = { status: 'done'; reps: number[] } | { status: 'active'; reps: number[]; planned: number } | { status: 'todo'; planned: number }
64
+ export function setColors(s: SetState): string[] {
65
+ if (s.status === 'todo') return [GREY]
66
+ if (s.status === 'done') return s.reps.map(velColor)
67
+ return [...s.reps.map(velColor), ...Array(Math.max(0, s.planned - s.reps.length)).fill(GREY)]
68
+ }
69
+ function SetBar({ colors, h, pulse }: { colors: string[]; h: number; pulse?: boolean }) {
70
+ return (
71
+ <div style={{ display: 'flex', flex: 1, minWidth: 0, height: h, borderRadius: 2, overflow: 'hidden' }}>
72
+ {colors.map((c, i) => (
73
+ <div key={i} style={{ flex: 1, height: '100%', background: c, animation: pulse && c !== GREY ? pulseAnim(c) : undefined }} />
74
+ ))}
75
+ </div>
76
+ )
77
+ }
78
+ export function SetStrip({ sets, h }: { sets: SetState[]; h: number }) {
79
+ return (
80
+ <div style={{ display: 'flex', width: '100%', height: h, gap: SET_GAP }}>
81
+ {sets.map((s, i) => (
82
+ <SetBar key={i} colors={setColors(s)} h={h} pulse={s.status === 'active'} />
83
+ ))}
84
+ </div>
85
+ )
86
+ }
87
+
88
+ export function Indicator({ kind }: { kind: 'pr' | 'issue' | 'info' }) {
89
+ const map = { pr: ['★', ORANGE], issue: ['!', RED], info: ['i', '#2196F3'] } as const
90
+ const [glyph, color] = map[kind]
91
+ return (
92
+ <span style={{ width: 16, height: 16, borderRadius: 8, border: `1px solid ${color}`, color, fontSize: 10, fontWeight: 800, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>{glyph}</span>
93
+ )
94
+ }
95
+
96
+ const labelStyle = { fontSize: 14, fontWeight: 800, color: T_PRIMARY } as const
97
+
98
+ // ---- the finalized heading unit
99
+ export type Exercise = { name: string; sets: number; reps: number | string; load: number; tempo: [number, number, number, number]; indicator?: 'pr' | 'issue' | 'info'; setStates: SetState[]; upcoming?: boolean }
100
+ export function Heading({ ex, stripH }: { ex: Exercise; stripH: number }) {
101
+ return (
102
+ <div style={{ padding: '9px 12px', display: 'flex', flexDirection: 'column', opacity: ex.upcoming ? 0.55 : 1 }}>
103
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
104
+ <span style={labelStyle}>{ex.name}</span>
105
+ <div style={{ flex: 1 }} />
106
+ {ex.indicator && <Indicator kind={ex.indicator} />}
107
+ </div>
108
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 1 }}>
109
+ <SetsRepsLoad sets={ex.sets} reps={ex.reps} load={ex.load} />
110
+ <div style={{ flex: 1 }} />
111
+ <TempoDisplay tempo={ex.tempo} size="sm" showLabel={false} showInfo={false} />
112
+ </div>
113
+ <div style={{ marginTop: 7 }}>
114
+ <SetStrip sets={ex.setStates} h={stripH} />
115
+ </div>
116
+ </div>
117
+ )
118
+ }
119
+
120
+ // ---- sample session (per-rep mean velocities; sets fatigue across the workout, reps decay in-set)
121
+ export const reps = (n: number, start: number, span = 0.4): number[] => Array.from({ length: n }, (_, r) => +(start - (span * r) / Math.max(1, n - 1)).toFixed(3))
122
+ export const SESSION: Exercise[] = [
123
+ { name: 'Seated Cable Row', sets: 5, reps: 8, load: 145, tempo: [3, 1, 2, 0], indicator: 'pr', setStates: [1.0, 0.9, 0.8, 0.7, 0.6].map((s) => ({ status: 'done', reps: reps(8, s) })) },
124
+ { name: 'Incline DB Press', sets: 3, reps: 8, load: 70, tempo: [3, 0, 3, 0], setStates: [0.72, 0.62, 0.54].map((s) => ({ status: 'done', reps: reps(8, s) })) },
125
+ { name: 'Cable Chest Press', sets: 3, reps: 10, load: 90, tempo: [2, 1, 2, 0], indicator: 'info', setStates: [{ status: 'done', reps: reps(10, 0.72) }, { status: 'active', reps: reps(5, 0.62), planned: 10 }, { status: 'todo', planned: 10 }] },
126
+ { name: 'Standing Calf Raise', sets: 5, reps: 20, load: 25, tempo: [2, 1, 2, 0], upcoming: true, setStates: [1, 2, 3, 4, 5].map(() => ({ status: 'todo', planned: 20 })) },
127
+ { name: 'Face Pull', sets: 3, reps: '15-20', load: 40, tempo: [2, 1, 2, 0], upcoming: true, setStates: [1, 2, 3].map(() => ({ status: 'todo', planned: 18 })) },
128
+ ]
129
+
130
+ // ---- shell chrome (neumorphic): flat raised nav + heading plane; sunk inset exercise list
131
+ export function NavStub() {
132
+ return (
133
+ <div style={{ width: 60, alignSelf: 'stretch', position: 'relative', zIndex: 2, background: RAISED, display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: 12, gap: 8 }}>
134
+ {['Live', 'Plan', 'Body'].map((t) => (
135
+ <span key={t} style={{ fontSize: 8, color: T_TERTIARY, fontWeight: 700 }}>{t}</span>
136
+ ))}
137
+ </div>
138
+ )
139
+ }
140
+ export function Rail({ stripH, footer }: { stripH: number; footer?: ReactNode }) {
141
+ return (
142
+ <div style={{ width: 246, background: INSET, display: 'flex', flexDirection: 'column' }}>
143
+ <div style={{ position: 'relative', zIndex: 2, padding: '11px 12px 10px', background: RAISED }}>
144
+ <div style={{ fontSize: 9, fontWeight: 800, letterSpacing: 1, textTransform: 'uppercase', color: T_TERTIARY, display: 'flex', alignItems: 'center', gap: 5 }}>
145
+ <span style={{ width: 6, height: 6, borderRadius: 3, background: GREEN }} />
146
+ Live session
147
+ </div>
148
+ <div style={{ fontSize: 14, fontWeight: 800, marginTop: 3, color: T_PRIMARY }}>Pull A · Intensification</div>
149
+ <div style={{ fontSize: 10, color: T_SECONDARY, marginTop: 2, fontFamily: 'monospace' }}>ex 3/5 · set 2 live</div>
150
+ </div>
151
+ <div style={{ flex: 1, background: INSET, boxShadow: INSET_SHADOW }}>
152
+ {SESSION.map((ex, i) => (
153
+ <div key={i} style={{ borderBottom: i < SESSION.length - 1 ? `1px solid ${BORDER_SUBTLE}` : undefined }}>
154
+ <Heading ex={ex} stripH={stripH} />
155
+ </div>
156
+ ))}
157
+ </div>
158
+ {footer}
159
+ </div>
160
+ )
161
+ }
162
+
163
+ // ---- page scaffold shared by the sheets
164
+ export function Page({ title, note, children }: { title: string; note: string; children: ReactNode }) {
165
+ return (
166
+ <div style={{ minHeight: '100vh', background: '#0A0A0A', color: T_PRIMARY, padding: 28, display: 'flex', flexDirection: 'column', gap: 22, fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }}>
167
+ <style>{PULSE_CSS}</style>
168
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
169
+ <span style={{ fontSize: 20, fontWeight: 800 }}>{title}</span>
170
+ <span style={{ fontSize: 12, color: T_SECONDARY, maxWidth: 820 }}>{note}</span>
171
+ </div>
172
+ {children}
173
+ </div>
174
+ )
175
+ }
176
+ export const sectionTitle = { fontSize: 11, fontWeight: 800, letterSpacing: 1.2, textTransform: 'uppercase', color: ORANGE } as const
177
+ export const monoTag = { fontFamily: 'monospace', fontSize: 11, color: T_SECONDARY } as const
@@ -5,6 +5,8 @@ export * from './Table'
5
5
  export * from './EmptyState'
6
6
  export * from './stepper'
7
7
  export * from './DateTime'
8
+ export * from './TimerReadout'
9
+ export * from './CircularTimer'
8
10
  export * from './Metric'
9
11
  export * from './Workout'
10
12
  export * from './Treemap'
@@ -3,7 +3,7 @@ import { View, Text } from 'react-native'
3
3
  import { Stepper, Step, StepIndicator, StepLabel, StepContent } from './Stepper'
4
4
 
5
5
  const meta: Meta<typeof Stepper> = {
6
- title: 'Custom/Stepper',
6
+ title: 'Components/Molecules/Stepper',
7
7
  component: Stepper,
8
8
  tags: ['autodocs'],
9
9
  argTypes: {
@@ -1,10 +1,25 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import type { ReactNode } from 'react'
3
3
  import { View, Text } from 'react-native'
4
- import { VoltrasMark, BluetoothIcon, DumbbellIcon, StarIcon } from './icons'
4
+ import {
5
+ VoltrasMark,
6
+ BluetoothIcon,
7
+ DumbbellIcon,
8
+ StarIcon,
9
+ ActivityIcon,
10
+ HistoryIcon,
11
+ LayersIcon,
12
+ PersonStandingIcon,
13
+ ScaleIcon,
14
+ AlertTriangleIcon,
15
+ TrendingDownIcon,
16
+ CircleSlashIcon,
17
+ AwardIcon,
18
+ InfoIcon,
19
+ } from './icons'
5
20
 
6
21
  const meta: Meta = {
7
- title: 'Icons',
22
+ title: 'Foundations/Icons',
8
23
  tags: ['autodocs'],
9
24
  parameters: {
10
25
  docs: {
@@ -52,6 +67,56 @@ export const All: Story = {
52
67
  <StarIcon size={28} fill="currentColor" />
53
68
  </View>
54
69
  </Swatch>
70
+ <Swatch label="ActivityIcon">
71
+ <View className="text-text-primary">
72
+ <ActivityIcon size={28} />
73
+ </View>
74
+ </Swatch>
75
+ <Swatch label="HistoryIcon">
76
+ <View className="text-text-primary">
77
+ <HistoryIcon size={28} />
78
+ </View>
79
+ </Swatch>
80
+ <Swatch label="LayersIcon">
81
+ <View className="text-text-primary">
82
+ <LayersIcon size={28} />
83
+ </View>
84
+ </Swatch>
85
+ <Swatch label="PersonStandingIcon">
86
+ <View className="text-text-primary">
87
+ <PersonStandingIcon size={28} />
88
+ </View>
89
+ </Swatch>
90
+ <Swatch label="ScaleIcon">
91
+ <View className="text-status-error">
92
+ <ScaleIcon size={28} />
93
+ </View>
94
+ </Swatch>
95
+ <Swatch label="AlertTriangleIcon">
96
+ <View className="text-status-error">
97
+ <AlertTriangleIcon size={28} />
98
+ </View>
99
+ </Swatch>
100
+ <Swatch label="TrendingDownIcon">
101
+ <View className="text-status-warning">
102
+ <TrendingDownIcon size={28} />
103
+ </View>
104
+ </Swatch>
105
+ <Swatch label="CircleSlashIcon">
106
+ <View className="text-status-warning">
107
+ <CircleSlashIcon size={28} />
108
+ </View>
109
+ </Swatch>
110
+ <Swatch label="AwardIcon">
111
+ <View className="text-status-success">
112
+ <AwardIcon size={28} />
113
+ </View>
114
+ </Swatch>
115
+ <Swatch label="InfoIcon">
116
+ <View className="text-status-info">
117
+ <InfoIcon size={28} />
118
+ </View>
119
+ </Swatch>
55
120
  </View>
56
121
  ),
57
122
  }
@@ -1,12 +1,42 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { render, screen } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
- import { VoltrasMark, BluetoothIcon, DumbbellIcon, StarIcon } from './icons'
4
+ import {
5
+ VoltrasMark,
6
+ BluetoothIcon,
7
+ DumbbellIcon,
8
+ StarIcon,
9
+ ActivityIcon,
10
+ HistoryIcon,
11
+ LayersIcon,
12
+ PersonStandingIcon,
13
+ ScaleIcon,
14
+ AlertTriangleIcon,
15
+ TrendingDownIcon,
16
+ CircleSlashIcon,
17
+ AwardIcon,
18
+ InfoIcon,
19
+ } from './icons'
5
20
  import { SvgIcon } from './SvgIcon'
6
21
 
7
22
  describe('icon primitives', () => {
8
23
  it('every icon renders an svg', () => {
9
- ;[VoltrasMark, BluetoothIcon, DumbbellIcon, StarIcon].forEach((Icon) => {
24
+ ;[
25
+ VoltrasMark,
26
+ BluetoothIcon,
27
+ DumbbellIcon,
28
+ StarIcon,
29
+ ActivityIcon,
30
+ HistoryIcon,
31
+ LayersIcon,
32
+ PersonStandingIcon,
33
+ ScaleIcon,
34
+ AlertTriangleIcon,
35
+ TrendingDownIcon,
36
+ CircleSlashIcon,
37
+ AwardIcon,
38
+ InfoIcon,
39
+ ].forEach((Icon) => {
10
40
  const { container, unmount } = render(<Icon />)
11
41
  expect(container.querySelector('svg')).toBeInTheDocument()
12
42
  unmount()
@@ -43,3 +43,111 @@ export function StarIcon(props: IconProps) {
43
43
  </SvgIcon>
44
44
  )
45
45
  }
46
+
47
+ /** Activity / pulse-line glyph (mirrors lucide-react `Activity`). Shell S2 nav → Live. */
48
+ export function ActivityIcon(props: IconProps) {
49
+ return (
50
+ <SvgIcon {...props}>
51
+ <path d="M22 12h-4l-3 9L9 3l-3 9H2" />
52
+ </SvgIcon>
53
+ )
54
+ }
55
+
56
+ /** History / clock-arrow glyph (mirrors lucide-react `History`). Shell S2 nav → Review. */
57
+ export function HistoryIcon(props: IconProps) {
58
+ return (
59
+ <SvgIcon {...props}>
60
+ <path d="M3 12a9 9 0 1 0 3-6.7L3 8" />
61
+ <path d="M3 3v5h5" />
62
+ <path d="M12 7v5l4 2" />
63
+ </SvgIcon>
64
+ )
65
+ }
66
+
67
+ /** Layers / stack glyph (mirrors lucide-react `Layers`). Shell S2 nav → Program (mesocycle stack). */
68
+ export function LayersIcon(props: IconProps) {
69
+ return (
70
+ <SvgIcon {...props}>
71
+ <path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z" />
72
+ <path d="m6.08 9.5-3.49 1.59a1 1 0 0 0 0 1.81l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9a1 1 0 0 0 0-1.83l-3.5-1.59" />
73
+ <path d="m6.08 14.5-3.49 1.59a1 1 0 0 0 0 1.81l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9a1 1 0 0 0 0-1.83l-3.5-1.59" />
74
+ </SvgIcon>
75
+ )
76
+ }
77
+
78
+ /** Standing-figure glyph (mirrors lucide-react `PersonStanding`). Shell S2 nav → Body (muscle map). */
79
+ export function PersonStandingIcon(props: IconProps) {
80
+ return (
81
+ <SvgIcon {...props}>
82
+ <circle cx="12" cy="5" r="1" />
83
+ <path d="m9 20 3-6 3 6" />
84
+ <path d="m6 8 6 2 6-2" />
85
+ <path d="M12 10v4" />
86
+ </SvgIcon>
87
+ )
88
+ }
89
+
90
+ /** Balance-scale glyph (mirrors lucide-react `Scale`). ExerciseIndicator → `imbalance`. */
91
+ export function ScaleIcon(props: IconProps) {
92
+ return (
93
+ <SvgIcon {...props}>
94
+ <path d="m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" />
95
+ <path d="m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" />
96
+ <path d="M7 21h10" />
97
+ <path d="M12 3v18" />
98
+ <path d="M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" />
99
+ </SvgIcon>
100
+ )
101
+ }
102
+
103
+ /** Warning-triangle glyph (mirrors lucide-react `AlertTriangle`). ExerciseIndicator → `overshoot`. */
104
+ export function AlertTriangleIcon(props: IconProps) {
105
+ return (
106
+ <SvgIcon {...props}>
107
+ <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
108
+ <path d="M12 9v4" />
109
+ <path d="M12 17h.01" />
110
+ </SvgIcon>
111
+ )
112
+ }
113
+
114
+ /** Down-trend glyph (mirrors lucide-react `TrendingDown`). ExerciseIndicator → `velocity-loss`. */
115
+ export function TrendingDownIcon(props: IconProps) {
116
+ return (
117
+ <SvgIcon {...props}>
118
+ <path d="M16 17h6v-6" />
119
+ <path d="m22 17-8.5-8.5-5 5L2 7" />
120
+ </SvgIcon>
121
+ )
122
+ }
123
+
124
+ /** Slashed-circle glyph (mirrors lucide-react `CircleSlash`). ExerciseIndicator → `missed-reps`. */
125
+ export function CircleSlashIcon(props: IconProps) {
126
+ return (
127
+ <SvgIcon {...props}>
128
+ <circle cx="12" cy="12" r="10" />
129
+ <path d="m9 15 6-6" />
130
+ </SvgIcon>
131
+ )
132
+ }
133
+
134
+ /** Medal glyph (mirrors lucide-react `Award`). ExerciseIndicator → `pr`. */
135
+ export function AwardIcon(props: IconProps) {
136
+ return (
137
+ <SvgIcon {...props}>
138
+ <path d="m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526" />
139
+ <circle cx="12" cy="8" r="6" />
140
+ </SvgIcon>
141
+ )
142
+ }
143
+
144
+ /** Info-circle glyph (mirrors lucide-react `Info`). ExerciseIndicator → `info`. */
145
+ export function InfoIcon(props: IconProps) {
146
+ return (
147
+ <SvgIcon {...props}>
148
+ <circle cx="12" cy="12" r="10" />
149
+ <path d="M12 16v-4" />
150
+ <path d="M12 8h.01" />
151
+ </SvgIcon>
152
+ )
153
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { BrandLockup } from './BrandLockup'
3
3
 
4
4
  const meta: Meta<typeof BrandLockup> = {
5
- title: 'Shell/TopBar/BrandLockup',
5
+ title: 'Shell/BrandLockup',
6
6
  component: BrandLockup,
7
7
  tags: ['autodocs'],
8
8
  args: { subtitle: 'wall dashboard', showSubtitle: true },
@@ -14,8 +14,8 @@ const meta: Meta<typeof BrandLockup> = {
14
14
  docs: {
15
15
  description: {
16
16
  component:
17
- '**Molecule.** Composes [VoltrasMark](?path=/docs/icons--docs) (icon) + ' +
18
- '[Typography](?path=/docs/custom-typography--docs) (wordmark + subtitle). ' +
17
+ '**Molecule.** Composes [VoltrasMark](?path=/docs/foundations-icons--docs) (icon) + ' +
18
+ '[Typography](?path=/docs/foundations-typography--docs) (wordmark + subtitle). ' +
19
19
  'Toggle `showSubtitle` to see the responsive collapse.',
20
20
  },
21
21
  },