@titan-design/react-ui 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/dist/bodymap.js +241 -189
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +241 -189
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +1017 -130
  6. package/dist/index.d.ts +1017 -130
  7. package/dist/index.js +3058 -1090
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2992 -1091
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{pages-MO0JCySL.d.mts → pages-8u_4WbL-.d.mts} +280 -57
  12. package/dist/{pages-D7Jlssvp.d.ts → pages-DaWiBOGa.d.ts} +280 -57
  13. package/dist/pages.d.mts +1 -1
  14. package/dist/pages.d.ts +1 -1
  15. package/dist/pages.js +1797 -1292
  16. package/dist/pages.js.map +1 -1
  17. package/dist/pages.mjs +1797 -1292
  18. package/dist/pages.mjs.map +1 -1
  19. package/dist/theme/index.d.mts +464 -268
  20. package/dist/theme/index.d.ts +464 -268
  21. package/dist/theme/index.js +303 -191
  22. package/dist/theme/index.js.map +1 -1
  23. package/dist/theme/index.mjs +295 -192
  24. package/dist/theme/index.mjs.map +1 -1
  25. package/dist/theme/tokens-css.js +160 -143
  26. package/dist/theme/tokens-css.js.map +1 -1
  27. package/dist/theme/tokens-css.mjs +160 -143
  28. package/dist/theme/tokens-css.mjs.map +1 -1
  29. package/package.json +3 -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/DateTime/DateTime.stories.tsx +83 -7
  34. package/src/components/custom/DateTime/DateTime.test.tsx +29 -3
  35. package/src/components/custom/DateTime/DateTime.tsx +55 -15
  36. package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
  37. package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
  38. package/src/components/custom/Gauge/Gauge.test.tsx +2 -2
  39. package/src/components/custom/Gauge/Gauge.tsx +6 -3
  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/Scatter/Scatter.tsx +3 -5
  43. package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
  44. package/src/components/custom/Table/Table.stories.tsx +1 -1
  45. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
  46. package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
  47. package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
  48. package/src/components/custom/TimerReadout/index.ts +1 -0
  49. package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
  50. package/src/components/custom/Treemap/Treemap.tsx +3 -5
  51. package/src/components/custom/Typography/Typography.stories.tsx +46 -28
  52. package/src/components/custom/Typography/Typography.test.tsx +13 -3
  53. package/src/components/custom/Typography/Typography.tsx +5 -0
  54. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
  55. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
  56. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +11 -16
  57. package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
  58. package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
  59. package/src/components/custom/Workout/BodyMap.tsx +6 -3
  60. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
  61. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +6 -3
  62. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
  63. package/src/components/custom/Workout/CapacityBandChart.test.tsx +2 -2
  64. package/src/components/custom/Workout/CapacityBandChart.tsx +9 -6
  65. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  66. package/src/components/custom/Workout/DeviationBar.test.tsx +1 -1
  67. package/src/components/custom/Workout/DeviationBar.tsx +7 -4
  68. package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
  69. package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
  70. package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
  71. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
  72. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
  73. package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
  74. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
  75. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
  76. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
  77. package/src/components/custom/Workout/ExerciseDetailPage.tsx +6 -6
  78. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
  79. package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
  80. package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
  81. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
  82. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
  83. package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
  84. package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
  85. package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
  86. package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
  87. package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
  88. package/src/components/custom/Workout/InputBar.tsx +2 -1
  89. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  90. package/src/components/custom/Workout/IntensityBar.test.tsx +8 -8
  91. package/src/components/custom/Workout/IntensityBar.tsx +10 -6
  92. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
  93. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
  94. package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
  95. package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
  96. package/src/components/custom/Workout/MesoCard.tsx +8 -3
  97. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  98. package/src/components/custom/Workout/MesoProgressBar.test.tsx +1 -1
  99. package/src/components/custom/Workout/MesoProgressBar.tsx +3 -2
  100. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
  101. package/src/components/custom/Workout/MesoStatusCard.tsx +20 -13
  102. package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
  103. package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
  104. package/src/components/custom/Workout/MetricTiles.tsx +48 -0
  105. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
  106. package/src/components/custom/Workout/MuscleGroupChip.tsx +7 -4
  107. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  108. package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
  109. package/src/components/custom/Workout/PrBadge.tsx +5 -2
  110. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
  111. package/src/components/custom/Workout/PrHistoryModal.tsx +4 -3
  112. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
  113. package/src/components/custom/Workout/ProgramPlanningPage.tsx +10 -7
  114. package/src/components/custom/Workout/README.md +146 -4
  115. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
  116. package/src/components/custom/Workout/ReadinessCheck.tsx +7 -4
  117. package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
  118. package/src/components/custom/Workout/RestTimer.tsx +17 -11
  119. package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
  120. package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
  121. package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
  122. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
  123. package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
  124. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
  125. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
  126. package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
  127. package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
  128. package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
  129. package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
  130. package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
  131. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
  132. package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
  133. package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
  134. package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
  135. package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
  136. package/src/components/custom/Workout/SessionHeader.tsx +152 -0
  137. package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
  138. package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
  139. package/src/components/custom/Workout/SessionRail.tsx +126 -0
  140. package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
  141. package/src/components/custom/Workout/SetBar.test.tsx +123 -0
  142. package/src/components/custom/Workout/SetBar.tsx +171 -0
  143. package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
  144. package/src/components/custom/Workout/SetRow.test.tsx +94 -166
  145. package/src/components/custom/Workout/SetRow.tsx +170 -232
  146. package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
  147. package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
  148. package/src/components/custom/Workout/SetStrip.tsx +71 -0
  149. package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
  150. package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
  151. package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
  152. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
  153. package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
  154. package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
  155. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  156. package/src/components/custom/Workout/Sparkline.tsx +2 -1
  157. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  158. package/src/components/custom/Workout/StatusDot.test.tsx +2 -2
  159. package/src/components/custom/Workout/StatusDot.tsx +12 -9
  160. package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
  161. package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
  162. package/src/components/custom/Workout/StatusPill.tsx +88 -0
  163. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
  164. package/src/components/custom/Workout/StrengthTrendChart.tsx +9 -6
  165. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
  166. package/src/components/custom/Workout/SupersetWrapper.tsx +2 -1
  167. package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
  168. package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
  169. package/src/components/custom/Workout/TempoDisplay.tsx +29 -36
  170. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
  171. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
  172. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
  173. package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
  174. package/src/components/custom/Workout/VelocityStrip.test.tsx +379 -144
  175. package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
  176. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
  177. package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
  178. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
  179. package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
  180. package/src/components/custom/Workout/WeekRow.tsx +3 -2
  181. package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
  182. package/src/components/custom/Workout/WeightBadge.tsx +6 -2
  183. package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
  184. package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
  185. package/src/components/custom/Workout/WorkoutCard.tsx +5 -2
  186. package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
  187. package/src/components/custom/Workout/WorkoutPill.tsx +12 -8
  188. package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
  189. package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
  190. package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
  191. package/src/components/custom/Workout/icons.tsx +6 -67
  192. package/src/components/custom/Workout/index.ts +55 -2
  193. package/src/components/custom/Workout/metricText.stories.tsx +69 -0
  194. package/src/components/custom/Workout/metricText.tsx +34 -0
  195. package/src/components/custom/Workout/paceTone.test.ts +29 -0
  196. package/src/components/custom/Workout/paceTone.ts +27 -0
  197. package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
  198. package/src/components/custom/index.ts +1 -0
  199. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  200. package/src/components/icons/SvgIcon.tsx +50 -0
  201. package/src/components/icons/icons.stories.tsx +122 -0
  202. package/src/components/icons/icons.test.tsx +73 -0
  203. package/src/components/icons/icons.tsx +153 -0
  204. package/src/components/icons/index.ts +4 -0
  205. package/src/components/index.ts +6 -0
  206. package/src/components/shell/BrandLockup.stories.tsx +27 -0
  207. package/src/components/shell/BrandLockup.test.tsx +23 -0
  208. package/src/components/shell/BrandLockup.tsx +47 -0
  209. package/src/components/shell/DashboardShell.stories.tsx +65 -0
  210. package/src/components/shell/DashboardShell.test.tsx +42 -0
  211. package/src/components/shell/DashboardShell.tsx +74 -0
  212. package/src/components/shell/DeviceIndicator.stories.tsx +28 -0
  213. package/src/components/shell/DeviceIndicator.test.tsx +26 -0
  214. package/src/components/shell/DeviceIndicator.tsx +52 -0
  215. package/src/components/shell/DeviceMenu.stories.tsx +47 -0
  216. package/src/components/shell/DeviceMenu.test.tsx +40 -0
  217. package/src/components/shell/DeviceMenu.tsx +65 -0
  218. package/src/components/shell/DeviceRow.stories.tsx +38 -0
  219. package/src/components/shell/DeviceRow.test.tsx +37 -0
  220. package/src/components/shell/DeviceRow.tsx +58 -0
  221. package/src/components/shell/NavItem.stories.tsx +52 -0
  222. package/src/components/shell/NavItem.test.tsx +35 -0
  223. package/src/components/shell/NavItem.tsx +75 -0
  224. package/src/components/shell/README.md +116 -0
  225. package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
  226. package/src/components/shell/SessionStatePill.stories.tsx +27 -0
  227. package/src/components/shell/SessionStatePill.test.tsx +25 -0
  228. package/src/components/shell/SessionStatePill.tsx +46 -0
  229. package/src/components/shell/SideNav.stories.tsx +81 -0
  230. package/src/components/shell/SideNav.test.tsx +51 -0
  231. package/src/components/shell/SideNav.tsx +74 -0
  232. package/src/components/shell/TopBar.stories.tsx +49 -0
  233. package/src/components/shell/TopBar.test.tsx +36 -0
  234. package/src/components/shell/TopBar.tsx +89 -0
  235. package/src/components/shell/index.ts +13 -0
  236. package/src/components/ui/alert/Alert.stories.tsx +1 -1
  237. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  238. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  239. package/src/components/ui/badge/Badge.stories.tsx +1 -1
  240. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  241. package/src/components/ui/button/Button.stories.tsx +1 -1
  242. package/src/components/ui/card/Card.stories.tsx +1 -1
  243. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  244. package/src/components/ui/chip/Chip.stories.tsx +1 -1
  245. package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
  246. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  247. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  248. package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
  249. package/src/components/ui/form-field/FormField.stories.tsx +1 -1
  250. package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
  251. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  252. package/src/components/ui/index.ts +1 -0
  253. package/src/components/ui/indicator/Indicator.stories.tsx +38 -2
  254. package/src/components/ui/indicator/Indicator.test.tsx +20 -1
  255. package/src/components/ui/indicator/Indicator.tsx +50 -6
  256. package/src/components/ui/indicator/index.ts +1 -1
  257. package/src/components/ui/input/Input.stories.tsx +1 -1
  258. package/src/components/ui/link/Link.stories.tsx +1 -1
  259. package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
  260. package/src/components/ui/menu/Menu.stories.tsx +1 -1
  261. package/src/components/ui/modal/Modal.stories.tsx +1 -1
  262. package/src/components/ui/pill/Pill.stories.tsx +1 -1
  263. package/src/components/ui/popover/Popover.stories.tsx +1 -1
  264. package/src/components/ui/progress/Progress.stories.tsx +1 -1
  265. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  266. package/src/components/ui/section/Section.stories.tsx +1 -1
  267. package/src/components/ui/select/Select.stories.tsx +1 -1
  268. package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
  269. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  270. package/src/components/ui/spinner/Spinner.tsx +3 -2
  271. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  272. package/src/components/ui/surface/Surface.stories.tsx +1 -1
  273. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  274. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  275. package/src/components/ui/tile/Tile.stories.tsx +79 -0
  276. package/src/components/ui/tile/Tile.test.tsx +48 -0
  277. package/src/components/ui/tile/Tile.tsx +64 -0
  278. package/src/components/ui/tile/index.ts +2 -0
  279. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  280. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  281. package/src/components/ui/toolbar-button/ToolbarButton.tsx +2 -1
  282. package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
  283. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
  284. package/src/hooks/index.ts +2 -0
  285. package/src/hooks/useTimer.test.ts +150 -0
  286. package/src/hooks/useTimer.ts +77 -0
  287. package/src/index.ts +3 -0
  288. package/src/pages.ts +1 -1
  289. package/src/stories/PresetShowcase.stories.tsx +1 -1
  290. package/src/stories/ThemePresets.stories.tsx +1 -1
  291. package/src/theme/ColorSystem.stories.tsx +323 -0
  292. package/src/theme/Colors.stories.tsx +74 -9
  293. package/src/theme/barrel.ts +1 -0
  294. package/src/theme/config.ts +20 -3
  295. package/src/theme/elevation.stories.tsx +1 -1
  296. package/src/theme/extracted-colors-dataviz.ts +21 -0
  297. package/src/theme/extracted-colors-ui.ts +20 -0
  298. package/src/theme/global.css +69 -53
  299. package/src/theme/gradients.test.ts +25 -0
  300. package/src/theme/gradients.ts +36 -0
  301. package/src/theme/shadows.stories.tsx +1 -1
  302. package/src/theme/tokens/primitives.test.ts +232 -0
  303. package/src/theme/tokens/primitives.ts +238 -87
  304. package/src/theme/tokens/semantic.ts +101 -81
  305. package/src/theme/workout-tokens.ts +26 -21
  306. package/src/utils/avatar-color.ts +3 -3
  307. package/tailwind.config.js +15 -3
@@ -0,0 +1,122 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import type { ReactNode } from 'react'
3
+ import { View, Text } from 'react-native'
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'
20
+
21
+ const meta: Meta = {
22
+ title: 'Foundations/Icons',
23
+ tags: ['autodocs'],
24
+ parameters: {
25
+ docs: {
26
+ description: {
27
+ component:
28
+ 'The shared icon primitive — 24×24 web-`<svg>` glyphs on a common `SvgIcon` base ' +
29
+ '(a11y contract: titled → `role="img"`, untitled → `aria-hidden`; `currentColor` inheritance). ' +
30
+ 'Reusable design-system-wide (the shell, Workout badges, etc. compose these).',
31
+ },
32
+ },
33
+ },
34
+ }
35
+ export default meta
36
+ type Story = StoryObj
37
+
38
+ function Swatch({ label, children }: { label: string; children: ReactNode }) {
39
+ return (
40
+ <View className="items-center gap-2">
41
+ {children}
42
+ <Text className="font-mono text-[10px] text-text-tertiary">{label}</Text>
43
+ </View>
44
+ )
45
+ }
46
+
47
+ export const All: Story = {
48
+ render: () => (
49
+ <View className="flex-row gap-8 p-4 items-start">
50
+ <Swatch label="VoltrasMark">
51
+ <View className="text-brand-primary">
52
+ <VoltrasMark size={28} />
53
+ </View>
54
+ </Swatch>
55
+ <Swatch label="BluetoothIcon">
56
+ <View className="text-status-success">
57
+ <BluetoothIcon size={28} />
58
+ </View>
59
+ </Swatch>
60
+ <Swatch label="DumbbellIcon">
61
+ <View className="text-text-primary">
62
+ <DumbbellIcon size={28} />
63
+ </View>
64
+ </Swatch>
65
+ <Swatch label="StarIcon (fill)">
66
+ <View className="text-brand-primary">
67
+ <StarIcon size={28} fill="currentColor" />
68
+ </View>
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>
120
+ </View>
121
+ ),
122
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
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'
20
+ import { SvgIcon } from './SvgIcon'
21
+
22
+ describe('icon primitives', () => {
23
+ it('every icon renders an svg', () => {
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) => {
40
+ const { container, unmount } = render(<Icon />)
41
+ expect(container.querySelector('svg')).toBeInTheDocument()
42
+ unmount()
43
+ })
44
+ })
45
+
46
+ it('a titled icon is exposed as an accessible image', () => {
47
+ render(<BluetoothIcon title="Devices" />)
48
+ expect(screen.getByRole('img', { name: 'Devices' })).toBeInTheDocument()
49
+ })
50
+
51
+ it('an untitled icon is decorative (aria-hidden, no role)', () => {
52
+ const { container } = render(<VoltrasMark />)
53
+ const svg = container.querySelector('svg')!
54
+ expect(svg.getAttribute('aria-hidden')).toBe('true')
55
+ expect(svg.getAttribute('role')).toBeNull()
56
+ })
57
+
58
+ it('honors size + strokeWidth via the SvgIcon base', () => {
59
+ const { container } = render(
60
+ <SvgIcon size={32} strokeWidth={3}>
61
+ <path d="M0 0" />
62
+ </SvgIcon>
63
+ )
64
+ const svg = container.querySelector('svg')!
65
+ expect(svg.getAttribute('width')).toBe('32')
66
+ expect(svg.getAttribute('stroke-width')).toBe('3')
67
+ })
68
+
69
+ it('a decorative icon has no accessibility violations', async () => {
70
+ const { container } = render(<StarIcon />)
71
+ expect(await axe(container)).toHaveNoViolations()
72
+ })
73
+ })
@@ -0,0 +1,153 @@
1
+ import { SvgIcon, type IconProps } from './SvgIcon'
2
+
3
+ /**
4
+ * Voltras brand mark — the ◇ diamond (outline). Pass `fill` for a solid mark.
5
+ */
6
+ export function VoltrasMark({ size = 14, ...props }: IconProps) {
7
+ return (
8
+ <SvgIcon size={size} {...props}>
9
+ <path d="M12 2 22 12 12 22 2 12Z" />
10
+ </SvgIcon>
11
+ )
12
+ }
13
+
14
+ /**
15
+ * Bluetooth glyph (stroked) — e.g. device-connection state (color via `currentColor`).
16
+ */
17
+ export function BluetoothIcon({ size = 18, ...props }: IconProps) {
18
+ return (
19
+ <SvgIcon size={size} {...props}>
20
+ <path d="M7 7 17 17 12 22 12 2 17 7 7 17" />
21
+ </SvgIcon>
22
+ )
23
+ }
24
+
25
+ /** Dumbbell glyph (mirrors lucide-react `Dumbbell`). Outline-only by default. */
26
+ export function DumbbellIcon(props: IconProps) {
27
+ return (
28
+ <SvgIcon {...props}>
29
+ <path d="M14.4 14.4 9.6 9.6" />
30
+ <path d="M18.657 21.485a2 2 0 1 1-2.829-2.828l-1.767 1.768a2 2 0 1 1-2.829-2.829l6.364-6.364a2 2 0 1 1 2.829 2.829l-1.768 1.767a2 2 0 1 1 2.828 2.829z" />
31
+ <path d="m21.5 21.5-1.4-1.4" />
32
+ <path d="M3.9 3.9 2.5 2.5" />
33
+ <path d="M6.404 12.768a2 2 0 1 1-2.829-2.829l1.768-1.767a2 2 0 1 1-2.828-2.829l2.828-2.828a2 2 0 1 1 2.829 2.828l1.767-1.768a2 2 0 1 1 2.829 2.829z" />
34
+ </SvgIcon>
35
+ )
36
+ }
37
+
38
+ /** Star glyph (mirrors lucide-react `Star`). Pass `fill` for a solid star. */
39
+ export function StarIcon(props: IconProps) {
40
+ return (
41
+ <SvgIcon {...props}>
42
+ <path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" />
43
+ </SvgIcon>
44
+ )
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
+ }
@@ -0,0 +1,4 @@
1
+ // Icon primitives — the shared, design-system-wide icon set. A base `SvgIcon`
2
+ // wrapper (24×24 web SVG + a11y contract + currentColor) and the icon glyphs.
3
+ export { SvgIcon, type IconProps, type SvgIconProps } from './SvgIcon'
4
+ export * from './icons'
@@ -1,5 +1,11 @@
1
+ // Icon primitives (shared, design-system-wide)
2
+ export * from './icons'
3
+
1
4
  // Re-export all UI components
2
5
  export * from './ui'
3
6
 
4
7
  // Re-export custom components
5
8
  export * from './custom'
9
+
10
+ // Re-export shell chrome components
11
+ export * from './shell'
@@ -0,0 +1,27 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { BrandLockup } from './BrandLockup'
3
+
4
+ const meta: Meta<typeof BrandLockup> = {
5
+ title: 'Shell/BrandLockup',
6
+ component: BrandLockup,
7
+ tags: ['autodocs'],
8
+ args: { subtitle: 'wall dashboard', showSubtitle: true },
9
+ argTypes: {
10
+ subtitle: { control: 'text' },
11
+ showSubtitle: { control: 'boolean' },
12
+ },
13
+ parameters: {
14
+ docs: {
15
+ description: {
16
+ component:
17
+ '**Molecule.** Composes [VoltrasMark](?path=/docs/foundations-icons--docs) (icon) + ' +
18
+ '[Typography](?path=/docs/foundations-typography--docs) (wordmark + subtitle). ' +
19
+ 'Toggle `showSubtitle` to see the responsive collapse.',
20
+ },
21
+ },
22
+ },
23
+ }
24
+ export default meta
25
+ type Story = StoryObj<typeof BrandLockup>
26
+
27
+ export const Default: Story = {}
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { BrandLockup } from './BrandLockup'
5
+
6
+ describe('BrandLockup', () => {
7
+ it('renders the wordmark', () => {
8
+ render(<BrandLockup />)
9
+ expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
10
+ })
11
+
12
+ it('shows the subtitle by default and hides it when showSubtitle is false', () => {
13
+ const { rerender } = render(<BrandLockup subtitle="wall dashboard" />)
14
+ expect(screen.getByText('/ wall dashboard')).toBeInTheDocument()
15
+ rerender(<BrandLockup subtitle="wall dashboard" showSubtitle={false} />)
16
+ expect(screen.queryByText('/ wall dashboard')).not.toBeInTheDocument()
17
+ })
18
+
19
+ it('has no accessibility violations', async () => {
20
+ const { container } = render(<BrandLockup />)
21
+ expect(await axe(container)).toHaveNoViolations()
22
+ })
23
+ })
@@ -0,0 +1,47 @@
1
+ import { View, type ViewProps } from 'react-native'
2
+ import { cn } from '../../utils/cn'
3
+ import { Typography } from '../custom/Typography'
4
+ import { VoltrasMark } from '../icons'
5
+
6
+ export interface BrandLockupProps extends ViewProps {
7
+ /** The "/ subtitle" that follows the wordmark. Default "wall dashboard". */
8
+ subtitle?: string
9
+ /** Show the subtitle. Hidden at tablet width and below (S1-Q4 responsive). */
10
+ showSubtitle?: boolean
11
+ className?: string
12
+ }
13
+
14
+ /**
15
+ * Product identity lockup for the top bar: ◇ mark + VOLTRAS wordmark + optional
16
+ * subtitle. Composes the VoltrasMark icon + Typography. S1 · BrandLockup.
17
+ */
18
+ export function BrandLockup({
19
+ subtitle = 'wall dashboard',
20
+ showSubtitle = true,
21
+ className,
22
+ ...props
23
+ }: BrandLockupProps) {
24
+ return (
25
+ <View className={cn('flex-row items-center gap-[7px]', className)} {...props}>
26
+ <View className="text-brand-primary">
27
+ <VoltrasMark size={14} color="currentColor" />
28
+ </View>
29
+ <Typography
30
+ variant="button"
31
+ color="primary"
32
+ className="text-[12px] font-extrabold tracking-[1.5px]"
33
+ >
34
+ VOLTRAS
35
+ </Typography>
36
+ {showSubtitle && subtitle ? (
37
+ <Typography
38
+ variant="button"
39
+ color="tertiary"
40
+ className="text-[10px] font-semibold tracking-[0.4px]"
41
+ >
42
+ / {subtitle}
43
+ </Typography>
44
+ ) : null}
45
+ </View>
46
+ )
47
+ }
@@ -0,0 +1,65 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View, Text } from 'react-native'
3
+ import { DashboardShell } from './DashboardShell'
4
+
5
+ /**
6
+ * `Pages/DashboardShell` — the wall-dashboard chrome as one registered surface.
7
+ * Composes the shell-nav island (SideNav + TopBar and everything they pull in) over a
8
+ * `children` content slot, so the dashboard app mounts a single component.
9
+ */
10
+ const meta: Meta<typeof DashboardShell> = {
11
+ title: 'Pages/DashboardShell',
12
+ component: DashboardShell,
13
+ tags: ['autodocs'],
14
+ parameters: {
15
+ layout: 'fullscreen',
16
+ docs: {
17
+ description: {
18
+ component:
19
+ '**Page shell.** The wall-dashboard chrome. Composes ' +
20
+ '[SideNav](?path=/docs/shell-sidenav--docs) + ' +
21
+ '[TopBar](?path=/docs/shell-topbar--docs) — which pulls in DeviceMenu, DeviceRow, ' +
22
+ 'DeviceIndicator, SessionStatePill, BrandLockup, DateTime and Divider — over a ' +
23
+ '`children` content slot. Mount this instead of re-assembling the chrome per view.',
24
+ },
25
+ },
26
+ },
27
+ argTypes: {
28
+ state: { control: 'select', options: ['live', 'rest', 'idle'] },
29
+ activeKey: { control: 'select', options: ['live', 'review', 'program', 'body'] },
30
+ liveKey: { control: 'select', options: [null, 'live', 'review', 'program', 'body'] },
31
+ onNavigate: { control: false },
32
+ onSelectDevice: { control: false },
33
+ },
34
+ decorators: [
35
+ (Story) => (
36
+ <View style={{ height: '100vh' as unknown as number, backgroundColor: '#0E0E0E' }}>
37
+ <Story />
38
+ </View>
39
+ ),
40
+ ],
41
+ }
42
+
43
+ export default meta
44
+ type Story = StoryObj<typeof DashboardShell>
45
+
46
+ export const Default: Story = {
47
+ args: { activeKey: 'live', state: 'live' },
48
+ }
49
+
50
+ export const WithContent: Story = {
51
+ args: { activeKey: 'review', state: 'rest', liveKey: 'live' },
52
+ render: (args) => (
53
+ <DashboardShell {...args}>
54
+ <View style={{ flex: 1, padding: 28 }}>
55
+ <Text style={{ color: '#E5E5E5', fontSize: 22, fontWeight: '700' }}>Review</Text>
56
+ <Text style={{ color: '#8A8A8A', fontSize: 13, marginTop: 6 }}>
57
+ A page mounts its content here; the shell keeps the nav + top-bar chrome persistent.
58
+ </Text>
59
+ </View>
60
+ </DashboardShell>
61
+ ),
62
+ parameters: {
63
+ docs: { description: { story: 'The shell with a page mounted in its content slot.' } },
64
+ },
65
+ }
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { render, screen, fireEvent } from '@testing-library/react'
3
+ import { Text } from 'react-native'
4
+ import { axe } from 'jest-axe'
5
+ import { DashboardShell } from './DashboardShell'
6
+
7
+ describe('DashboardShell', () => {
8
+ it('composes the SideNav rail and its categories', () => {
9
+ render(<DashboardShell activeKey="live" />)
10
+ expect(screen.getByRole('tablist')).toBeInTheDocument()
11
+ ;['Live', 'Review', 'Plan', 'Body'].forEach((name) => {
12
+ expect(screen.getByRole('tab', { name })).toBeInTheDocument()
13
+ })
14
+ })
15
+
16
+ it('renders the content-slot placeholder when no children are given', () => {
17
+ render(<DashboardShell activeKey="live" />)
18
+ expect(screen.getByText('main content region')).toBeInTheDocument()
19
+ })
20
+
21
+ it('mounts children in the content slot and drops the placeholder', () => {
22
+ render(
23
+ <DashboardShell activeKey="review">
24
+ <Text>my page body</Text>
25
+ </DashboardShell>
26
+ )
27
+ expect(screen.getByText('my page body')).toBeInTheDocument()
28
+ expect(screen.queryByText('main content region')).not.toBeInTheDocument()
29
+ })
30
+
31
+ it('forwards nav taps through onNavigate', () => {
32
+ const onNavigate = vi.fn()
33
+ render(<DashboardShell activeKey="live" onNavigate={onNavigate} />)
34
+ fireEvent.click(screen.getByRole('tab', { name: 'Body' }))
35
+ expect(onNavigate).toHaveBeenCalledWith('body')
36
+ })
37
+
38
+ it('has no a11y violations', async () => {
39
+ const { container } = render(<DashboardShell activeKey="live" />)
40
+ expect(await axe(container)).toHaveNoViolations()
41
+ })
42
+ })
@@ -0,0 +1,74 @@
1
+ import type { ReactNode } from 'react'
2
+ import { View, Text } from 'react-native'
3
+ import { cn } from '../../utils/cn'
4
+ import { SideNav, type SideNavItem } from './SideNav'
5
+ import { TopBar } from './TopBar'
6
+ import { type Device } from './DeviceRow'
7
+ import { type SessionState } from './SessionStatePill'
8
+
9
+ /**
10
+ * `DashboardShell` — the wall-dashboard chrome: a persistent {@link SideNav} rail +
11
+ * {@link TopBar} over a main content region. A thin frame that wires the shell-nav
12
+ * organisms together and exposes a `children` content slot, so the dashboard app
13
+ * mounts one component instead of re-assembling the chrome. Driven entirely by props.
14
+ */
15
+ export interface DashboardShellProps {
16
+ /** Active nav category key. */
17
+ activeKey?: string
18
+ /** Nav categories, top → bottom. Defaults to SideNav's four dashboard categories. */
19
+ navItems?: SideNavItem[]
20
+ /** A category with off-view live activity → a quiet cue on that item. */
21
+ liveKey?: string | null
22
+ /** Global session state → the TopBar status pill. */
23
+ state?: SessionState
24
+ /** Connected devices → the TopBar connection glyph + dropdown. */
25
+ devices?: Device[]
26
+ /** TopBar brand subtitle. */
27
+ subtitle?: string
28
+ onNavigate?: (key: string) => void
29
+ onSelectDevice?: (device: Device) => void
30
+ /** Main content region. A placeholder renders when omitted. */
31
+ children?: ReactNode
32
+ className?: string
33
+ }
34
+
35
+ const DEMO_DEVICES: Device[] = [
36
+ { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
37
+ { id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
38
+ ]
39
+
40
+ function ContentPlaceholder() {
41
+ return (
42
+ <View className="flex-1 items-center justify-center">
43
+ <Text className="font-heading text-sm text-text-tertiary">main content region</Text>
44
+ </View>
45
+ )
46
+ }
47
+
48
+ export function DashboardShell({
49
+ activeKey = 'live',
50
+ navItems,
51
+ liveKey = null,
52
+ state = 'live',
53
+ devices = DEMO_DEVICES,
54
+ subtitle = 'wall dashboard',
55
+ onNavigate,
56
+ onSelectDevice,
57
+ children,
58
+ className,
59
+ }: DashboardShellProps) {
60
+ return (
61
+ <View className={cn('flex-1 flex-row bg-surface-base', className)}>
62
+ <SideNav activeKey={activeKey} items={navItems} liveKey={liveKey} onNavigate={onNavigate} />
63
+ <View className="flex-1">
64
+ <TopBar
65
+ state={state}
66
+ devices={devices}
67
+ subtitle={subtitle}
68
+ onSelectDevice={onSelectDevice}
69
+ />
70
+ <View className="flex-1">{children ?? <ContentPlaceholder />}</View>
71
+ </View>
72
+ </View>
73
+ )
74
+ }
@@ -0,0 +1,28 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { DeviceIndicator } from './DeviceIndicator'
3
+
4
+ const meta: Meta<typeof DeviceIndicator> = {
5
+ title: 'Shell/DeviceIndicator',
6
+ component: DeviceIndicator,
7
+ tags: ['autodocs'],
8
+ args: { status: 'connected' },
9
+ argTypes: {
10
+ status: { control: 'select', options: ['connected', 'degraded', 'lost'] },
11
+ label: { control: 'text' },
12
+ onPress: { control: false },
13
+ },
14
+ parameters: {
15
+ docs: {
16
+ description: {
17
+ component:
18
+ '**Molecule.** Composes [BluetoothIcon](?path=/docs/foundations-icons--docs), color-coded by ' +
19
+ 'connection state via a `text-status-*-vivid` token — the glyph color alone carries the fault ' +
20
+ '(no separate badge). Switch the `status` control to see connected / degraded / lost.',
21
+ },
22
+ },
23
+ },
24
+ }
25
+ export default meta
26
+ type Story = StoryObj<typeof DeviceIndicator>
27
+
28
+ export const Default: Story = {}