@titan-design/react-ui 0.12.0 → 0.13.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 (509) hide show
  1. package/README.md +46 -47
  2. package/dist/bodymap.js +768 -701
  3. package/dist/bodymap.js.map +1 -1
  4. package/dist/bodymap.mjs +770 -703
  5. package/dist/bodymap.mjs.map +1 -1
  6. package/dist/index.d.mts +1320 -215
  7. package/dist/index.d.ts +1320 -215
  8. package/dist/index.js +5996 -4498
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +5773 -4357
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{pages-CvsFqNtx.d.mts → pages-CpW3OB3g.d.mts} +21 -3
  13. package/dist/{pages-B1hqnGIC.d.ts → pages-_3Ajpiqu.d.ts} +21 -3
  14. package/dist/pages.d.mts +1 -1
  15. package/dist/pages.d.ts +1 -1
  16. package/dist/pages.js +1435 -1428
  17. package/dist/pages.js.map +1 -1
  18. package/dist/pages.mjs +1443 -1436
  19. package/dist/pages.mjs.map +1 -1
  20. package/dist/theme/index.d.mts +202 -1042
  21. package/dist/theme/index.d.ts +202 -1042
  22. package/dist/theme/index.js +215 -300
  23. package/dist/theme/index.js.map +1 -1
  24. package/dist/theme/index.mjs +202 -296
  25. package/dist/theme/index.mjs.map +1 -1
  26. package/dist/theme/tokens-css.js +62 -5
  27. package/dist/theme/tokens-css.js.map +1 -1
  28. package/dist/theme/tokens-css.mjs +62 -5
  29. package/dist/theme/tokens-css.mjs.map +1 -1
  30. package/dist/theme/tokens.d.mts +972 -0
  31. package/dist/theme/tokens.d.ts +972 -0
  32. package/dist/theme/tokens.js +904 -0
  33. package/dist/theme/tokens.js.map +1 -0
  34. package/dist/theme/tokens.mjs +880 -0
  35. package/dist/theme/tokens.mjs.map +1 -0
  36. package/docs/audits/2026-09-08-critique-addenda.md +51 -0
  37. package/docs/audits/2026-09-08-library-critique.md +212 -0
  38. package/docs/library-roadmap.md +99 -0
  39. package/package.json +7 -1
  40. package/src/arch/arch-graph.freshness.test.ts +52 -0
  41. package/src/arch/arch-graph.json +5021 -1617
  42. package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
  43. package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
  44. package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
  45. package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
  46. package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
  47. package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
  48. package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
  49. package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
  50. package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
  51. package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
  52. package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
  53. package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
  54. package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
  55. package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
  56. package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
  57. package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
  58. package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
  59. package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
  60. package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
  61. package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
  62. package/src/components/custom/ActiveWork/InitiativeBrief.tsx +181 -0
  63. package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
  64. package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
  65. package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
  66. package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
  67. package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
  68. package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
  69. package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
  70. package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
  71. package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
  72. package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
  73. package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
  74. package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
  75. package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
  76. package/src/components/custom/ActiveWork/README.md +305 -0
  77. package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
  78. package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
  79. package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
  80. package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
  81. package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
  82. package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
  83. package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
  84. package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
  85. package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
  86. package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
  87. package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
  88. package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
  89. package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
  90. package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
  91. package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
  92. package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
  93. package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
  94. package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
  95. package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
  96. package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
  97. package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
  98. package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
  99. package/src/components/custom/ActiveWork/format-time.ts +32 -0
  100. package/src/components/custom/ActiveWork/index.ts +19 -0
  101. package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
  102. package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
  103. package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
  104. package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
  105. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +4 -3
  106. package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
  107. package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
  108. package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
  109. package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
  110. package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
  111. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
  112. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  113. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
  114. package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
  115. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
  116. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
  117. package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
  118. package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
  119. package/src/components/custom/Fatigue/README.md +40 -3
  120. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  121. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
  122. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
  123. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  124. package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
  125. package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
  126. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
  127. package/src/components/custom/Fatigue/index.ts +17 -5
  128. package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
  129. package/src/components/custom/Fatigue/panel-layout.ts +185 -0
  130. package/src/components/custom/Gauge/Gauge.stories.tsx +2 -2
  131. package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
  132. package/src/components/custom/Gauge/Gauge.tsx +4 -1
  133. package/src/components/custom/Metric/Metric.stories.tsx +10 -1
  134. package/src/components/custom/Metric/Metric.test.tsx +1 -3
  135. package/src/components/custom/Metric/Metric.tsx +5 -20
  136. package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
  137. package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
  138. package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
  139. package/src/components/custom/Prose/README.md +58 -0
  140. package/src/components/custom/Prose/index.ts +1 -0
  141. package/src/components/custom/Scatter/Scatter.stories.tsx +46 -8
  142. package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
  143. package/src/components/custom/Scatter/Scatter.tsx +116 -14
  144. package/src/components/custom/Sidebar/Sidebar.stories.tsx +40 -7
  145. package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
  146. package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
  147. package/src/components/custom/Table/Table.stories.tsx +98 -15
  148. package/src/components/custom/Table/Table.test.tsx +273 -86
  149. package/src/components/custom/Table/Table.tsx +159 -67
  150. package/src/components/custom/Table/column-fit.test.ts +75 -0
  151. package/src/components/custom/Table/column-fit.ts +116 -0
  152. package/src/components/custom/Table/index.ts +9 -0
  153. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
  154. package/src/components/custom/Treemap/Treemap.stories.tsx +2 -2
  155. package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
  156. package/src/components/custom/Treemap/Treemap.tsx +7 -2
  157. package/src/components/custom/Typography/Typography.stories.tsx +28 -1
  158. package/src/components/custom/Typography/Typography.test.tsx +34 -0
  159. package/src/components/custom/Typography/Typography.tsx +6 -0
  160. package/src/components/custom/Typography/index.ts +1 -8
  161. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +9 -1
  162. package/src/components/custom/Workout/BaseBadge.stories.tsx +19 -7
  163. package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
  164. package/src/components/custom/Workout/BaseBadge.tsx +3 -0
  165. package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
  166. package/src/components/custom/Workout/BodyMap.tsx +3 -1
  167. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +16 -4
  168. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
  169. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +45 -9
  170. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +10 -8
  171. package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
  172. package/src/components/custom/Workout/CapacityBandChart.tsx +139 -108
  173. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  174. package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
  175. package/src/components/custom/Workout/DeviationBar.tsx +21 -16
  176. package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +182 -0
  177. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +3 -2
  178. package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -7
  179. package/src/components/custom/Workout/ExerciseCard.test.tsx +13 -3
  180. package/src/components/custom/Workout/ExerciseCard.tsx +42 -51
  181. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +7 -7
  182. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
  183. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
  184. package/src/components/custom/Workout/ExerciseHeading.tsx +8 -5
  185. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -4
  186. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
  187. package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
  188. package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
  189. package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
  190. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  191. package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
  192. package/src/components/custom/Workout/IntensityBar.tsx +4 -2
  193. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
  194. package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
  195. package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
  196. package/src/components/custom/Workout/MesoCard.tsx +12 -9
  197. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  198. package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
  199. package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
  200. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
  201. package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
  202. package/src/components/custom/Workout/MesoStatusCard.tsx +9 -23
  203. package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
  204. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
  205. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
  206. package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
  207. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  208. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
  209. package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
  210. package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
  211. package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
  212. package/src/components/custom/Workout/PrBadge.tsx +13 -17
  213. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +4 -1
  214. package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
  215. package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
  216. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
  217. package/src/components/custom/Workout/README.md +12 -7
  218. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
  219. package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
  220. package/src/components/custom/Workout/ReadinessCheck.tsx +19 -3
  221. package/src/components/custom/Workout/RestTimer.stories.tsx +3 -2
  222. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
  223. package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
  224. package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
  225. package/src/components/custom/Workout/SegmentedBar.stories.tsx +5 -5
  226. package/src/components/custom/Workout/SegmentedBar.tsx +6 -2
  227. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
  228. package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
  229. package/src/components/custom/Workout/SessionHeader.tsx +12 -12
  230. package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
  231. package/src/components/custom/Workout/SessionRail.tsx +7 -9
  232. package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
  233. package/src/components/custom/Workout/SetBar.test.tsx +49 -4
  234. package/src/components/custom/Workout/SetBar.tsx +38 -12
  235. package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
  236. package/src/components/custom/Workout/SetRow.test.tsx +16 -2
  237. package/src/components/custom/Workout/SetRow.tsx +61 -23
  238. package/src/components/custom/Workout/SetStrip.stories.tsx +60 -5
  239. package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
  240. package/src/components/custom/Workout/SetStrip.tsx +24 -4
  241. package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
  242. package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
  243. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
  244. package/src/components/custom/Workout/SetsRepsLoad.tsx +3 -5
  245. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  246. package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
  247. package/src/components/custom/Workout/Sparkline.tsx +14 -18
  248. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  249. package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
  250. package/src/components/custom/Workout/StatusDot.tsx +93 -63
  251. package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
  252. package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
  253. package/src/components/custom/Workout/StatusPill.tsx +27 -38
  254. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +9 -3
  255. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
  256. package/src/components/custom/Workout/StrengthTrendChart.tsx +31 -30
  257. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
  258. package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
  259. package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
  260. package/src/components/custom/Workout/TempoDisplay.stories.tsx +22 -7
  261. package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
  262. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
  263. package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
  264. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +7 -4
  265. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +4 -1
  266. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +4 -1
  267. package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
  268. package/src/components/custom/Workout/VelocityStrip.tsx +3 -0
  269. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
  270. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
  271. package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
  272. package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
  273. package/src/components/custom/Workout/WeekRow.tsx +23 -16
  274. package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
  275. package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
  276. package/src/components/custom/Workout/WeightBadge.tsx +28 -27
  277. package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
  278. package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
  279. package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
  280. package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
  281. package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
  282. package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
  283. package/src/components/custom/Workout/ZoneTrack.stories.tsx +7 -6
  284. package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
  285. package/src/components/custom/Workout/index.ts +5 -0
  286. package/src/components/custom/Workout/metricText.stories.tsx +6 -5
  287. package/src/components/custom/Workout/metricText.tsx +4 -0
  288. package/src/components/custom/Workout/setHeadingKit.tsx +207 -26
  289. package/src/components/custom/charts/README.md +61 -0
  290. package/src/components/custom/charts/SetBarChart.test.tsx +30 -1
  291. package/src/components/custom/charts/SetBarChart.tsx +42 -4
  292. package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
  293. package/src/components/custom/charts/SparkBars.test.tsx +69 -0
  294. package/src/components/custom/charts/SparkBars.tsx +85 -0
  295. package/src/components/custom/charts/index.ts +3 -0
  296. package/src/components/custom/index.ts +3 -0
  297. package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
  298. package/src/components/custom/stepper/Stepper.test.tsx +1 -7
  299. package/src/components/custom/stepper/Stepper.tsx +13 -28
  300. package/src/components/icons/icons.stories.tsx +1 -1
  301. package/src/components/icons/icons.tsx +51 -0
  302. package/src/components/shell/AppShell.stories.tsx +150 -0
  303. package/src/components/shell/AppShell.test.tsx +70 -0
  304. package/src/components/shell/AppShell.tsx +90 -0
  305. package/src/components/shell/BrandLockup.stories.tsx +66 -5
  306. package/src/components/shell/BrandLockup.test.tsx +34 -1
  307. package/src/components/shell/BrandLockup.tsx +27 -11
  308. package/src/components/shell/NavItem.stories.tsx +4 -4
  309. package/src/components/shell/NavItem.test.tsx +17 -0
  310. package/src/components/shell/NavItem.tsx +17 -5
  311. package/src/components/shell/README.md +90 -27
  312. package/src/components/shell/SideNav.stories.tsx +38 -7
  313. package/src/components/shell/SideNav.test.tsx +16 -8
  314. package/src/components/shell/SideNav.tsx +17 -17
  315. package/src/components/shell/TopBar.stories.tsx +44 -19
  316. package/src/components/shell/TopBar.test.tsx +34 -12
  317. package/src/components/shell/TopBar.tsx +61 -40
  318. package/src/components/shell/brands.tsx +66 -0
  319. package/src/components/shell/index.ts +9 -8
  320. package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
  321. package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
  322. package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
  323. package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
  324. package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
  325. package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
  326. package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
  327. package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
  328. package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
  329. package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
  330. package/src/components/shell/workout/WorkoutShell.tsx +77 -0
  331. package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
  332. package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
  333. package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
  334. package/src/components/shell/workout/index.ts +9 -0
  335. package/src/components/shell/workout/workoutNavItems.tsx +16 -0
  336. package/src/components/ui/README.md +110 -0
  337. package/src/components/ui/alert/Alert.stories.tsx +1 -1
  338. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
  339. package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
  340. package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
  341. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  342. package/src/components/ui/avatar/Avatar.tsx +2 -10
  343. package/src/components/ui/badge/Badge.stories.tsx +83 -28
  344. package/src/components/ui/badge/Badge.test.tsx +19 -3
  345. package/src/components/ui/badge/Badge.tsx +48 -55
  346. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  347. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
  348. package/src/components/ui/button/Button.stories.tsx +46 -12
  349. package/src/components/ui/button/Button.tsx +32 -25
  350. package/src/components/ui/card/Card.stories.tsx +90 -94
  351. package/src/components/ui/card/Card.test.tsx +80 -6
  352. package/src/components/ui/card/Card.tsx +150 -203
  353. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  354. package/src/components/ui/checkbox/Checkbox.tsx +6 -26
  355. package/src/components/ui/chip/Chip.stories.tsx +37 -13
  356. package/src/components/ui/chip/Chip.test.tsx +10 -6
  357. package/src/components/ui/chip/Chip.tsx +76 -91
  358. package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
  359. package/src/components/ui/collapse/Collapse.tsx +6 -11
  360. package/src/components/ui/data-row/DataRow.stories.tsx +9 -2
  361. package/src/components/ui/data-row/DataRow.test.tsx +5 -15
  362. package/src/components/ui/data-row/DataRow.tsx +18 -7
  363. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  364. package/src/components/ui/divider/Divider.tsx +1 -5
  365. package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
  366. package/src/components/ui/drawer/Drawer.tsx +13 -30
  367. package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
  368. package/src/components/ui/form-field/FormField.stories.tsx +6 -20
  369. package/src/components/ui/form-field/FormField.test.tsx +1 -5
  370. package/src/components/ui/form-field/FormField.tsx +10 -46
  371. package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
  372. package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
  373. package/src/components/ui/help-tip/HelpTip.tsx +21 -22
  374. package/src/components/ui/help-tip/index.ts +7 -1
  375. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  376. package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
  377. package/src/components/ui/icon-box/IconBox.tsx +2 -12
  378. package/src/components/ui/indicator/Indicator.stories.tsx +1 -1
  379. package/src/components/ui/indicator/Indicator.tsx +20 -14
  380. package/src/components/ui/input/Input.stories.tsx +3 -11
  381. package/src/components/ui/input/Input.test.tsx +2 -10
  382. package/src/components/ui/input/Input.tsx +10 -25
  383. package/src/components/ui/input/index.ts +7 -1
  384. package/src/components/ui/link/Link.stories.tsx +19 -7
  385. package/src/components/ui/list-item/ListItem.stories.tsx +11 -2
  386. package/src/components/ui/list-item/ListItem.tsx +2 -9
  387. package/src/components/ui/menu/Menu.stories.tsx +21 -15
  388. package/src/components/ui/menu/Menu.test.tsx +2 -11
  389. package/src/components/ui/menu/Menu.tsx +12 -14
  390. package/src/components/ui/menu/index.ts +8 -1
  391. package/src/components/ui/modal/Modal.stories.tsx +2 -2
  392. package/src/components/ui/modal/Modal.tsx +29 -19
  393. package/src/components/ui/pill/Pill.stories.tsx +58 -23
  394. package/src/components/ui/pill/Pill.test.tsx +101 -3
  395. package/src/components/ui/pill/Pill.tsx +164 -49
  396. package/src/components/ui/pill/index.ts +1 -1
  397. package/src/components/ui/popover/Popover.stories.tsx +5 -7
  398. package/src/components/ui/popover/Popover.test.tsx +1 -6
  399. package/src/components/ui/popover/Popover.tsx +20 -15
  400. package/src/components/ui/popover/index.ts +7 -1
  401. package/src/components/ui/progress/Progress.stories.tsx +3 -12
  402. package/src/components/ui/progress/Progress.test.tsx +1 -5
  403. package/src/components/ui/radio/Radio.stories.tsx +42 -21
  404. package/src/components/ui/radio/Radio.test.tsx +6 -2
  405. package/src/components/ui/radio/Radio.tsx +23 -22
  406. package/src/components/ui/section/Section.stories.tsx +5 -14
  407. package/src/components/ui/section/Section.test.tsx +1 -6
  408. package/src/components/ui/section/Section.tsx +1 -3
  409. package/src/components/ui/section/index.ts +1 -5
  410. package/src/components/ui/select/Select.stories.tsx +3 -13
  411. package/src/components/ui/select/Select.test.tsx +2 -11
  412. package/src/components/ui/select/Select.tsx +14 -14
  413. package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
  414. package/src/components/ui/skeleton/Skeleton.tsx +3 -14
  415. package/src/components/ui/skeleton/index.ts +1 -7
  416. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  417. package/src/components/ui/spinner/Spinner.tsx +1 -4
  418. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  419. package/src/components/ui/stack/Stack.tsx +1 -1
  420. package/src/components/ui/surface/Surface.stories.tsx +17 -5
  421. package/src/components/ui/surface/Surface.test.tsx +88 -28
  422. package/src/components/ui/surface/Surface.tsx +37 -87
  423. package/src/components/ui/surface/SurfaceContext.ts +22 -62
  424. package/src/components/ui/surface/index.ts +8 -0
  425. package/src/components/ui/surface/resolveSurface.ts +98 -0
  426. package/src/components/ui/surface/surface.contract.test.ts +6 -1
  427. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  428. package/src/components/ui/switch/Switch.tsx +7 -6
  429. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  430. package/src/components/ui/tabs/Tabs.tsx +7 -22
  431. package/src/components/ui/tabs/index.ts +1 -7
  432. package/src/components/ui/tile/Tile.stories.tsx +1 -1
  433. package/src/components/ui/tile/Tile.tsx +3 -0
  434. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  435. package/src/components/ui/toast/Toast.test.tsx +2 -8
  436. package/src/components/ui/toast/Toast.tsx +43 -53
  437. package/src/components/ui/toast/index.ts +0 -1
  438. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
  439. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
  440. package/src/components/ui/toolbar-button/ToolbarButton.tsx +25 -43
  441. package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
  442. package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
  443. package/src/components/ui/tooltip/Tooltip.tsx +55 -36
  444. package/src/components/ui/tooltip/index.ts +1 -1
  445. package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
  446. package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
  447. package/src/components/ui/trigger/index.ts +2 -0
  448. package/src/stories/PresetShowcase.stories.tsx +100 -47
  449. package/src/test/no-device-internals.test.ts +3 -3
  450. package/src/theme/ChoosingTokens.stories.tsx +48 -0
  451. package/src/theme/ColorPalettes.stories.tsx +62 -45
  452. package/src/theme/ColorPrimitives.stories.tsx +35 -35
  453. package/src/theme/Depth.stories.tsx +132 -65
  454. package/src/theme/DepthCalibration.stories.tsx +24 -22
  455. package/src/theme/ThemeProvider.test.tsx +4 -4
  456. package/src/theme/barrel.ts +7 -6
  457. package/src/theme/color-stories.coverage.test.ts +1 -1
  458. package/src/theme/color-utils.ts +41 -29
  459. package/src/theme/config.completeness.test.ts +1 -1
  460. package/src/theme/config.ts +28 -0
  461. package/src/theme/elevation.test.ts +82 -56
  462. package/src/theme/elevation.ts +77 -561
  463. package/src/theme/global.css +30 -2
  464. package/src/theme/grey-ramp.test.ts +24 -8
  465. package/src/theme/lift.test.ts +39 -0
  466. package/src/theme/lift.ts +102 -0
  467. package/src/theme/materials.test.ts +5 -2
  468. package/src/theme/materials.ts +38 -4
  469. package/src/theme/native-theming-guard.test.ts +5 -5
  470. package/src/theme/presets/apply.ts +4 -0
  471. package/src/theme/presets/audiobook.ts +5 -2
  472. package/src/theme/presets/default.ts +4 -0
  473. package/src/theme/presets/index.ts +14 -1
  474. package/src/theme/presets/types.ts +1 -1
  475. package/src/theme/semantic-contrast.test.ts +13 -4
  476. package/src/theme/status-distinctness.test.ts +66 -0
  477. package/src/theme/surface-planes.test.ts +37 -0
  478. package/src/theme/surface-planes.ts +68 -0
  479. package/src/theme/token-export-guard.test.ts +102 -0
  480. package/src/theme/tokens/primitives.test.ts +15 -7
  481. package/src/theme/tokens/primitives.ts +67 -35
  482. package/src/theme/tokens/semantic.ts +71 -37
  483. package/src/theme/wash-ladder.test.ts +58 -0
  484. package/src/theme/workout-tokens.ts +1 -7
  485. package/src/types/jest-axe.d.ts +2 -6
  486. package/src/types/raw-md.d.ts +5 -0
  487. package/src/utils/colors.ts +4 -4
  488. package/src/utils/form.ts +1 -3
  489. package/src/utils/index.ts +2 -7
  490. package/src/utils/number-format.ts +17 -0
  491. package/src/utils/workout-format.ts +15 -6
  492. package/src/web-jsx/jsx-runtime.ts +1 -5
  493. package/tailwind.config.js +5 -0
  494. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
  495. package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
  496. package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
  497. package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
  498. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
  499. package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
  500. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
  501. package/src/components/shell/DashboardShell.stories.tsx +0 -65
  502. package/src/components/shell/DashboardShell.tsx +0 -75
  503. package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
  504. package/src/theme/elevation.stories.tsx +0 -346
  505. package/src/utils/useTheme.ts +0 -72
  506. /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
  507. /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
  508. /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
  509. /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
@@ -1,7 +1,6 @@
1
1
  import type { ReactNode } from 'react'
2
2
  import { View } from 'react-native'
3
3
  import { cn } from '../../utils/cn'
4
- import { ActivityIcon, HistoryIcon, LayersIcon, PersonStandingIcon } from '../icons'
5
4
  import { NavItem } from './NavItem'
6
5
 
7
6
  export interface SideNavItem {
@@ -14,8 +13,8 @@ export interface SideNavItem {
14
13
  }
15
14
 
16
15
  export interface SideNavProps {
17
- /** Category items, top → bottom. Defaults to the four dashboard categories. */
18
- items?: SideNavItem[]
16
+ /** Category items, top → bottom. The app owns its own categories. */
17
+ items: SideNavItem[]
19
18
  /** Key of the active category. */
20
19
  activeKey: string
21
20
  /** Called with the tapped item's key. */
@@ -25,30 +24,29 @@ export interface SideNavProps {
25
24
  * running off its view) → a quiet green cue on that item's label.
26
25
  */
27
26
  liveKey?: string | null
27
+ /** Semantic `text-*` token for the active item. Defaults to the Voltras brand. */
28
+ accentClassName?: string
29
+ /** Semantic `bg-*` token for the active bar. Pair it with `accentClassName`. */
30
+ accentBarClassName?: string
28
31
  className?: string
29
32
  }
30
33
 
31
- /** The four dashboard categories with their locked S2 glyphs (activity · history · layers · figure). */
32
- export const defaultNavItems: SideNavItem[] = [
33
- { key: 'live', label: 'Live', icon: <ActivityIcon size={20} color="currentColor" /> },
34
- { key: 'review', label: 'Review', icon: <HistoryIcon size={20} color="currentColor" /> },
35
- { key: 'program', label: 'Plan', icon: <LayersIcon size={20} color="currentColor" /> },
36
- { key: 'body', label: 'Body', icon: <PersonStandingIcon size={20} color="currentColor" /> },
37
- ]
38
-
39
34
  /**
40
35
  * Shell S2 · SideNav — the persistent 60px left rail that switches the main
41
- * viewport between categories (Live · Review · Program · Body). Presentational:
42
- * it renders `items` and reports taps via `onNavigate`; the app owns routing and
43
- * which key is `live`. Active item shows a left accent bar; `liveKey` (when not
44
- * the active view) tints that item's label a muted green. Fixed 60px at every
45
- * width — labels sit under the glyph, so they never change the rail width.
36
+ * viewport between an app's categories. Presentational: it renders `items` and
37
+ * reports taps via `onNavigate`; the app owns its categories, routing, and which
38
+ * key is `live`. Active item shows a left accent bar in the app's brand accent;
39
+ * `liveKey` (when not the active view) tints that item's label a muted green.
40
+ * Fixed 60px at every width — labels sit under the glyph, so they never change
41
+ * the rail width.
46
42
  */
47
43
  export function SideNav({
48
- items = defaultNavItems,
44
+ items,
49
45
  activeKey,
50
46
  onNavigate,
51
47
  liveKey = null,
48
+ accentClassName,
49
+ accentBarClassName,
52
50
  className,
53
51
  }: SideNavProps) {
54
52
  return (
@@ -66,6 +64,8 @@ export function SideNav({
66
64
  label={item.label}
67
65
  active={item.key === activeKey}
68
66
  live={item.key === liveKey && item.key !== activeKey}
67
+ accentClassName={accentClassName}
68
+ accentBarClassName={accentBarClassName}
69
69
  onPress={() => onNavigate?.(item.key)}
70
70
  />
71
71
  ))}
@@ -1,44 +1,41 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { Pill } from '../ui/pill'
2
3
  import { TopBar } from './TopBar'
3
- import { type Device } from './DeviceRow'
4
-
5
- const DEVICES: Device[] = [
6
- { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
7
- { id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
8
- { id: 'Voltra-77E0', nickname: 'Spare', slot: null, state: 'available' },
9
- ]
4
+ import { brandKeys } from './brands'
10
5
 
11
6
  const meta: Meta<typeof TopBar> = {
12
7
  title: 'Shell/TopBar',
13
8
  component: TopBar,
14
- tags: ['autodocs'],
15
- args: { state: 'live', devices: DEVICES },
9
+ tags: ['autodocs', 'status:candidate', '!status:review'],
10
+ args: { brand: 'voltras' },
16
11
  argTypes: {
17
- state: { control: 'select', options: ['live', 'rest', 'idle'] },
18
- devices: { control: 'object' },
12
+ brand: { control: 'select', options: brandKeys },
19
13
  subtitle: { control: 'text' },
20
14
  showSubtitle: { control: 'boolean' },
21
15
  showClock: { control: 'boolean' },
22
16
  time: { control: false },
23
- onSelectDevice: { control: false },
17
+ leading: { control: false },
18
+ trailing: { control: false },
24
19
  },
25
20
  parameters: {
26
21
  layout: 'fullscreen',
27
22
  docs: {
28
23
  description: {
29
24
  component:
30
- '**Organism** — the persistent shell chrome band, and the root of the top-bar family ' +
25
+ '**Organism** — the persistent shell chrome band, generic over the app ' +
31
26
  '(see `shell/README.md` for the full dependency map). Composes ' +
32
27
  '[BrandLockup](?path=/docs/shell-brandlockup--docs) + ' +
33
- '[SessionStatePill](?path=/docs/shell-sessionstatepill--docs) + ' +
34
28
  '[Divider](?path=/docs/components-atoms-divider--docs) (`bg-border-prominent`) + ' +
35
- '[DeviceMenu](?path=/docs/shell-devicemenu--docs) + ' +
36
29
  '[DateTime](?path=/docs/components-molecules-datetime--docs) (`variant="mono"` live clock). ' +
37
30
  'Background = the shared `surfaceGradient.chrome` primitive.\n\n' +
38
- '**Try it:** use the **Controls** to change `state`, edit `devices` (set one to `lost` to see the ' +
39
- 'fault), or toggle `showSubtitle` / `showClock`. **Resize the canvas** to watch the ' +
40
- 'container-responsive collapse (SIZE-D01) subtitle drops < 1024px, clock < 720px. ' +
41
- 'Click the device glyph to open the menu.',
31
+ '**Composition (AW-132).** The bar owns the band, the brand region, the divider ' +
32
+ 'rhythm and the edge-pinned clock. An app supplies its own chrome through ' +
33
+ '`trailing` pass an array and the bar puts its dividers between the items. ' +
34
+ "`leading` replaces the brand region outright. The workout app's cluster lives in " +
35
+ '[WorkoutTopBar](?path=/docs/shell-workout-workouttopbar--docs).\n\n' +
36
+ '**Try it:** switch `brand` in the **Controls**, or toggle `showSubtitle` / ' +
37
+ '`showClock`. **Resize the canvas** to watch the container-responsive collapse ' +
38
+ '(SIZE-D01) — subtitle drops < 1024px, clock < 720px.',
42
39
  },
43
40
  },
44
41
  },
@@ -46,4 +43,32 @@ const meta: Meta<typeof TopBar> = {
46
43
  export default meta
47
44
  type Story = StoryObj<typeof TopBar>
48
45
 
46
+ /** Brand + clock only. No app has supplied any chrome. */
49
47
  export const Default: Story = {}
48
+
49
+ /** Two app-supplied items in `trailing` — the bar adds the dividers and the clock. */
50
+ export const WithAppChrome: Story = {
51
+ args: {
52
+ brand: 'brain',
53
+ trailing: [
54
+ <Pill key="index" tone="success" leading="dot" size="xs">
55
+ indexed
56
+ </Pill>,
57
+ <Pill key="scope" tone="neutral" size="xs">
58
+ 4 vaults
59
+ </Pill>,
60
+ ],
61
+ },
62
+ }
63
+
64
+ /** One item, no array — a single node is a valid `trailing` too. */
65
+ export const SingleTrailingItem: Story = {
66
+ args: {
67
+ brand: 'audiobook',
68
+ trailing: (
69
+ <Pill tone="brand-secondary" size="xs">
70
+ 3 downloading
71
+ </Pill>
72
+ ),
73
+ },
74
+ }
@@ -1,36 +1,58 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { render, screen } from '@testing-library/react'
3
+ import { Text } from 'react-native'
3
4
  import { axe } from 'jest-axe'
4
5
  import { TopBar } from './TopBar'
5
- import { type Device } from './DeviceRow'
6
-
7
- const devices: Device[] = [
8
- { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
9
- { id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
10
- ]
11
6
 
12
7
  const AT_1612 = new Date(2024, 0, 1, 16, 12) // local 16:12
13
8
 
14
9
  describe('TopBar', () => {
15
- it('renders brand, state and a 24h clock', () => {
16
- render(<TopBar state="live" devices={devices} time={AT_1612} showClock />)
10
+ it('renders the brand and a 24h clock', () => {
11
+ render(<TopBar time={AT_1612} showClock />)
17
12
  expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
18
- expect(screen.getByText('LIVE')).toBeInTheDocument()
19
13
  expect(screen.getByText('16:12')).toBeInTheDocument()
20
14
  })
21
15
 
16
+ it('renders another app brand without knowing anything about it', () => {
17
+ render(<TopBar brand="brain" showClock={false} />)
18
+ expect(screen.getByText('BRAIN')).toBeInTheDocument()
19
+ expect(screen.getByText('/ knowledge')).toBeInTheDocument()
20
+ })
21
+
22
+ it('mounts app chrome from the trailing slot', () => {
23
+ render(
24
+ <TopBar
25
+ showClock={false}
26
+ trailing={[<Text key="a">alpha</Text>, <Text key="b">beta</Text>]}
27
+ />
28
+ )
29
+ expect(screen.getByText('alpha')).toBeInTheDocument()
30
+ expect(screen.getByText('beta')).toBeInTheDocument()
31
+ })
32
+
33
+ it('accepts a single trailing node as well as an array', () => {
34
+ render(<TopBar showClock={false} trailing={<Text>solo</Text>} />)
35
+ expect(screen.getByText('solo')).toBeInTheDocument()
36
+ })
37
+
38
+ it('replaces the brand region when a leading node is given', () => {
39
+ render(<TopBar leading={<Text>my own lockup</Text>} showClock={false} />)
40
+ expect(screen.getByText('my own lockup')).toBeInTheDocument()
41
+ expect(screen.queryByText('VOLTRAS')).not.toBeInTheDocument()
42
+ })
43
+
22
44
  it('hides the clock when showClock is false', () => {
23
- render(<TopBar state="live" devices={devices} time={AT_1612} showClock={false} />)
45
+ render(<TopBar time={AT_1612} showClock={false} />)
24
46
  expect(screen.queryByText('16:12')).not.toBeInTheDocument()
25
47
  })
26
48
 
27
49
  it('hides the subtitle when showSubtitle is false', () => {
28
- render(<TopBar state="rest" devices={devices} showSubtitle={false} />)
50
+ render(<TopBar showSubtitle={false} />)
29
51
  expect(screen.queryByText('/ wall dashboard')).not.toBeInTheDocument()
30
52
  })
31
53
 
32
54
  it('has no accessibility violations', async () => {
33
- const { container } = render(<TopBar state="live" devices={devices} time={AT_1612} showClock />)
55
+ const { container } = render(<TopBar time={AT_1612} showClock trailing={<Text>chrome</Text>} />)
34
56
  expect(await axe(container)).toHaveNoViolations()
35
57
  })
36
58
  })
@@ -1,28 +1,34 @@
1
- import { useState } from 'react'
1
+ import { Fragment, useState, type ReactNode } from 'react'
2
2
  import { View, type LayoutChangeEvent } from 'react-native'
3
3
  import { cn } from '../../utils/cn'
4
4
  import { surfaceGradient } from '../../theme/gradients'
5
5
  import { Divider } from '../ui/divider'
6
6
  import { DateTime } from '../custom/DateTime'
7
7
  import { BrandLockup } from './BrandLockup'
8
- import { SessionStatePill, type SessionState } from './SessionStatePill'
9
- import { DeviceMenu } from './DeviceMenu'
10
- import { type Device } from './DeviceRow'
8
+ import { type BrandKey } from './brands'
11
9
 
12
10
  export interface TopBarProps {
13
- /** Global session state the status pill. */
14
- state: SessionState
15
- /** Devices for the connection glyph + dropdown. */
16
- devices: Device[]
17
- /** The moment to display in the clock (Date/timestamp). Omit for a live ticking clock. */
18
- time?: number | Date
19
- /** Brand subtitle. Default "wall dashboard". */
11
+ /** Which app identity the default {@link BrandLockup} renders. */
12
+ brand?: BrandKey
13
+ /** Brand subtitle. Defaults to the brand preset's own. */
20
14
  subtitle?: string
21
15
  /** Force the subtitle on/off; defaults to container-responsive (hidden below ~1024px). */
22
16
  showSubtitle?: boolean
17
+ /**
18
+ * Replace the whole brand region. Wins over `brand` / `subtitle`, and opts out
19
+ * of the responsive subtitle collapse — the node owns its own behaviour.
20
+ */
21
+ leading?: ReactNode
22
+ /**
23
+ * App chrome for the right cluster, left → right. An array is rendered with the
24
+ * bar's own vertical dividers between the items, so an app supplies its
25
+ * controls and the shell keeps the divider rhythm.
26
+ */
27
+ trailing?: ReactNode | ReactNode[]
28
+ /** The moment to display in the clock (Date/timestamp). Omit for a live ticking clock. */
29
+ time?: number | Date
23
30
  /** Force the clock on/off; defaults to container-responsive (hidden below ~720px). */
24
31
  showClock?: boolean
25
- onSelectDevice?: (device: Device) => void
26
32
  className?: string
27
33
  }
28
34
 
@@ -30,20 +36,39 @@ export interface TopBarProps {
30
36
  const SUBTITLE_MIN = 1024
31
37
  const CLOCK_MIN = 720
32
38
 
39
+ function ClusterDivider() {
40
+ return <Divider orientation="vertical" className="h-4 bg-border-prominent" />
41
+ }
42
+
43
+ /** Interleave the bar's divider between slot items, skipping empty slots. */
44
+ function dividedCluster(items: ReactNode[]) {
45
+ return items
46
+ .filter((item) => item !== null && item !== undefined && item !== false)
47
+ .map((item, i) => (
48
+ <Fragment key={i}>
49
+ {i > 0 ? <ClusterDivider /> : null}
50
+ {item}
51
+ </Fragment>
52
+ ))
53
+ }
54
+
33
55
  /**
34
- * S1 · TopBar — the persistent shell chrome band. Brand (left) + a right cluster
35
- * of session state · device glyph · clock (clock edge-pinned so spacing stays
36
- * stable as the state text changes). Collapses subtitle then clock as its
37
- * container narrows.
56
+ * S1 · TopBar — the persistent shell chrome band, generic over the app. Brand
57
+ * (left) + a `trailing` cluster of app-supplied chrome and the wall clock (clock
58
+ * edge-pinned so spacing stays stable as the items' text changes). Collapses
59
+ * subtitle then clock as its container narrows.
60
+ *
61
+ * @example
62
+ * <TopBar brand="brain" trailing={[<IndexStatus />, <SearchScope />]} />
38
63
  */
39
64
  export function TopBar({
40
- state,
41
- devices,
42
- time,
43
- subtitle = 'wall dashboard',
65
+ brand = 'voltras',
66
+ subtitle,
44
67
  showSubtitle,
68
+ leading,
69
+ trailing,
70
+ time,
45
71
  showClock,
46
- onSelectDevice,
47
72
  className,
48
73
  }: TopBarProps) {
49
74
  const [width, setWidth] = useState(SUBTITLE_MIN)
@@ -52,6 +77,18 @@ export function TopBar({
52
77
  const subtitleVisible = showSubtitle ?? width >= SUBTITLE_MIN
53
78
  const clockVisible = showClock ?? width >= CLOCK_MIN
54
79
 
80
+ const clock = clockVisible ? (
81
+ <DateTime
82
+ value={time}
83
+ live={time == null}
84
+ format="time"
85
+ hour12={false}
86
+ variant="mono"
87
+ color="secondary"
88
+ className="text-[11px] min-w-[38px] text-right"
89
+ />
90
+ ) : null
91
+
55
92
  return (
56
93
  <View
57
94
  onLayout={onLayout}
@@ -62,27 +99,11 @@ export function TopBar({
62
99
  className
63
100
  )}
64
101
  >
65
- <BrandLockup subtitle={subtitle} showSubtitle={subtitleVisible} />
102
+ {leading ?? <BrandLockup brand={brand} subtitle={subtitle} showSubtitle={subtitleVisible} />}
66
103
 
67
- {/* right cluster — order: state · device · time (time pinned to the edge) */}
104
+ {/* right cluster — app chrome then the clock, pinned to the edge */}
68
105
  <View className="ml-auto flex-row items-center gap-[12px]">
69
- <SessionStatePill state={state} />
70
- <Divider orientation="vertical" className="h-4 bg-border-prominent" />
71
- <DeviceMenu devices={devices} onSelectDevice={onSelectDevice} />
72
- {clockVisible ? (
73
- <>
74
- <Divider orientation="vertical" className="h-4 bg-border-prominent" />
75
- <DateTime
76
- value={time}
77
- live={time == null}
78
- format="time"
79
- hour12={false}
80
- variant="mono"
81
- color="secondary"
82
- className="text-[11px] min-w-[38px] text-right"
83
- />
84
- </>
85
- ) : null}
106
+ {dividedCluster([...(Array.isArray(trailing) ? trailing : [trailing]), clock])}
86
107
  </View>
87
108
  </View>
88
109
  )
@@ -0,0 +1,66 @@
1
+ import type { ReactNode } from 'react'
2
+ import { BotIcon, BrainIcon, HeadphonesIcon, KanbanIcon, VoltrasMark } from '../icons'
3
+
4
+ /** The apps that are expected to mount this shell. */
5
+ export type BrandKey = 'voltras' | 'audiobook' | 'active-work' | 'agents' | 'brain'
6
+
7
+ export interface BrandPreset {
8
+ /** The mark glyph, rendered at 14px through the accent token via `currentColor`. */
9
+ mark: ReactNode
10
+ /** Uppercase product wordmark. */
11
+ wordmark: string
12
+ /** Semantic text token that colours the mark. */
13
+ accentClassName: string
14
+ /**
15
+ * The same accent as a background token, for the nav's active bar. Declared as
16
+ * a literal alongside its `text-*` twin because Tailwind only emits classes it
17
+ * can see in the source — a name built at runtime is never generated.
18
+ */
19
+ accentBarClassName: string
20
+ /** Default "/ subtitle" for that app's shell. */
21
+ subtitle: string
22
+ }
23
+
24
+ // Accents come from `data-*` rather than `status-*`: `data-*` is the library's
25
+ // palette of N distinct, CVD-checked hues with no semantic load, which is exactly
26
+ // what a per-app identity accent needs. Voltras keeps the real brand token.
27
+ export const brandPresets: Record<BrandKey, BrandPreset> = {
28
+ voltras: {
29
+ mark: <VoltrasMark size={14} color="currentColor" />,
30
+ wordmark: 'VOLTRAS',
31
+ accentClassName: 'text-brand-primary',
32
+ accentBarClassName: 'bg-brand-primary',
33
+ subtitle: 'wall dashboard',
34
+ },
35
+ audiobook: {
36
+ mark: <HeadphonesIcon size={14} color="currentColor" />,
37
+ wordmark: 'AUDIOBOOK',
38
+ accentClassName: 'text-data-5',
39
+ accentBarClassName: 'bg-data-5',
40
+ subtitle: 'library',
41
+ },
42
+ 'active-work': {
43
+ mark: <KanbanIcon size={14} color="currentColor" />,
44
+ wordmark: 'ACTIVE WORK',
45
+ accentClassName: 'text-data-1',
46
+ accentBarClassName: 'bg-data-1',
47
+ subtitle: 'initiatives',
48
+ },
49
+ agents: {
50
+ mark: <BotIcon size={14} color="currentColor" />,
51
+ wordmark: 'AGENTS',
52
+ accentClassName: 'text-data-2',
53
+ accentBarClassName: 'bg-data-2',
54
+ subtitle: 'fleet',
55
+ },
56
+ brain: {
57
+ mark: <BrainIcon size={14} color="currentColor" />,
58
+ wordmark: 'BRAIN',
59
+ accentClassName: 'text-data-3',
60
+ accentBarClassName: 'bg-data-3',
61
+ subtitle: 'knowledge',
62
+ },
63
+ }
64
+
65
+ /** Every brand key, in the order the stories present them. */
66
+ export const brandKeys = Object.keys(brandPresets) as BrandKey[]
@@ -1,13 +1,14 @@
1
- // Shell chrome — persistent dashboard frame (nav / top bar / rail / drawers / strip).
2
- // S1 · Top bar family. (Icons live in the shared `components/icons` primitive.)
1
+ // Shell chrome — the GENERIC persistent app frame (nav / top bar / brand).
2
+ // App-specific chrome composes this family; it never leaks back into it.
3
+ // (Icons live in the shared `components/icons` primitive.)
4
+ export * from './brands'
3
5
  export * from './BrandLockup'
4
- export * from './SessionStatePill'
5
- export * from './DeviceIndicator'
6
- export * from './DeviceRow'
7
- export * from './DeviceMenu'
6
+ // S1 · Top bar family.
8
7
  export * from './TopBar'
9
8
  // S2 · Side nav family.
10
9
  export * from './NavItem'
11
10
  export * from './SideNav'
12
- // S3 · Dashboard shell — composes the nav + top-bar chrome over a content slot.
13
- export * from './DashboardShell'
11
+ // S3 · App shell — composes the nav + top-bar chrome over a content slot.
12
+ export * from './AppShell'
13
+ // AW-132 · Workout-app shell, composed over the generic family above.
14
+ export * from './workout'
@@ -2,9 +2,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { DeviceIndicator } from './DeviceIndicator'
3
3
 
4
4
  const meta: Meta<typeof DeviceIndicator> = {
5
- title: 'Shell/DeviceIndicator',
5
+ title: 'Shell/Workout/DeviceIndicator',
6
6
  component: DeviceIndicator,
7
- tags: ['autodocs'],
7
+ tags: ['autodocs', 'status:candidate', '!status:review'],
8
8
  args: { status: 'connected' },
9
9
  argTypes: {
10
10
  status: { control: 'select', options: ['connected', 'degraded', 'lost'] },
@@ -1,6 +1,6 @@
1
1
  import { View, Pressable, type ViewProps } from 'react-native'
2
- import { cn } from '../../utils/cn'
3
- import { BluetoothIcon } from '../icons'
2
+ import { cn } from '../../../utils/cn'
3
+ import { BluetoothIcon } from '../../icons'
4
4
 
5
5
  export type DeviceConnState = 'connected' | 'degraded' | 'lost'
6
6
 
@@ -10,9 +10,9 @@ const DEVICES: Device[] = [
10
10
  ]
11
11
 
12
12
  const meta: Meta<typeof DeviceMenu> = {
13
- title: 'Shell/DeviceMenu',
13
+ title: 'Shell/Workout/DeviceMenu',
14
14
  component: DeviceMenu,
15
- tags: ['autodocs'],
15
+ tags: ['autodocs', 'status:candidate', '!status:review'],
16
16
  args: { devices: DEVICES, isOpen: false },
17
17
  argTypes: {
18
18
  devices: { control: 'object' },
@@ -1,7 +1,7 @@
1
1
  import { View } from 'react-native'
2
- import { cn } from '../../utils/cn'
3
- import { Typography } from '../custom/Typography'
4
- import { Popover, PopoverTrigger, PopoverContent } from '../ui/popover'
2
+ import { cn } from '../../../utils/cn'
3
+ import { Typography } from '../../custom/Typography'
4
+ import { Popover, PopoverTrigger, PopoverContent } from '../../ui/popover'
5
5
  import { DeviceIndicator, type DeviceConnState } from './DeviceIndicator'
6
6
  import { DeviceRow, type Device } from './DeviceRow'
7
7
 
@@ -3,9 +3,9 @@ import { View } from 'react-native'
3
3
  import { DeviceRow } from './DeviceRow'
4
4
 
5
5
  const meta: Meta<typeof DeviceRow> = {
6
- title: 'Shell/DeviceRow',
6
+ title: 'Shell/Workout/DeviceRow',
7
7
  component: DeviceRow,
8
- tags: ['autodocs'],
8
+ tags: ['autodocs', 'status:candidate', '!status:review'],
9
9
  args: {
10
10
  device: { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
11
11
  },
@@ -1,7 +1,7 @@
1
1
  import { View, Pressable, type ViewProps } from 'react-native'
2
- import { cn } from '../../utils/cn'
3
- import { Indicator, type IndicatorColor } from '../ui/indicator'
4
- import { Typography } from '../custom/Typography'
2
+ import { cn } from '../../../utils/cn'
3
+ import { Indicator, type IndicatorColor } from '../../ui/indicator'
4
+ import { Typography } from '../../custom/Typography'
5
5
 
6
6
  export type DeviceRowState = 'connected' | 'available' | 'degraded' | 'lost'
7
7
 
@@ -2,9 +2,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { SessionStatePill } from './SessionStatePill'
3
3
 
4
4
  const meta: Meta<typeof SessionStatePill> = {
5
- title: 'Shell/SessionStatePill',
5
+ title: 'Shell/Workout/SessionStatePill',
6
6
  component: SessionStatePill,
7
- tags: ['autodocs'],
7
+ tags: ['autodocs', 'status:candidate', '!status:review'],
8
8
  args: { state: 'live' },
9
9
  argTypes: {
10
10
  state: { control: 'select', options: ['live', 'rest', 'idle'] },
@@ -1,7 +1,7 @@
1
1
  import { View, type ViewProps } from 'react-native'
2
- import { cn } from '../../utils/cn'
3
- import { Indicator, type IndicatorColor, type IndicatorPulse } from '../ui/indicator'
4
- import { Typography, type TypographyColor } from '../custom/Typography'
2
+ import { cn } from '../../../utils/cn'
3
+ import { Indicator, type IndicatorColor, type IndicatorPulse } from '../../ui/indicator'
4
+ import { Typography, type TypographyColor } from '../../custom/Typography'
5
5
 
6
6
  export type SessionState = 'live' | 'rest' | 'idle'
7
7
 
@@ -32,6 +32,8 @@ const stateConfig: Record<
32
32
  * The global session-state readout (dot + label). Shared: the top bar's status
33
33
  * and the Live-view header both use it (this is the ledger's "StatusPill",
34
34
  * Family C — built once). S1 · SessionStatePill.
35
+ *
36
+ * @deprecated Use `<Pill tone="…" leading="dot">` — removed after AW-127 consumer migration.
35
37
  */
36
38
  export function SessionStatePill({ state, label, className, ...props }: SessionStatePillProps) {
37
39
  const cfg = stateConfig[state]
@@ -0,0 +1,71 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { WorkoutShell } from './WorkoutShell'
4
+ import { Surface } from '../../ui/surface'
5
+ import { Typography } from '../../custom/Typography'
6
+
7
+ /**
8
+ * `Pages/WorkoutShell` — the workout wall-dashboard chrome as one registered surface.
9
+ * Composes the generic AppShell and fills its slots with the workout's own chrome, so
10
+ * the dashboard app mounts a single component.
11
+ */
12
+ const meta: Meta<typeof WorkoutShell> = {
13
+ title: 'Pages/WorkoutShell',
14
+ component: WorkoutShell,
15
+ tags: ['autodocs', 'status:candidate', '!status:review'],
16
+ parameters: {
17
+ layout: 'fullscreen',
18
+ docs: {
19
+ description: {
20
+ component:
21
+ '**Page shell (workout app).** Composes ' +
22
+ '[AppShell](?path=/docs/pages-appshell--docs) and fills its top-bar slot with ' +
23
+ '[WorkoutTopBar](?path=/docs/shell-workout-workouttopbar--docs) — which pulls in ' +
24
+ 'DeviceMenu, DeviceRow, DeviceIndicator and SessionStatePill — and its nav slot with ' +
25
+ 'the four workout categories. Mount this instead of re-assembling the chrome per ' +
26
+ 'view.\n\nThis is the AW-132 shape: the generic shell knows nothing about devices or ' +
27
+ 'session state; this component is the whole workout-specific surface.',
28
+ },
29
+ },
30
+ },
31
+ argTypes: {
32
+ state: { control: 'select', options: ['live', 'rest', 'idle'] },
33
+ activeKey: { control: 'select', options: ['live', 'review', 'program', 'body'] },
34
+ liveKey: { control: 'select', options: [null, 'live', 'review', 'program', 'body'] },
35
+ onNavigate: { control: false },
36
+ onSelectDevice: { control: false },
37
+ },
38
+ decorators: [
39
+ (Story) => (
40
+ <Surface level="base" style={{ height: '100vh' as unknown as number }}>
41
+ <Story />
42
+ </Surface>
43
+ ),
44
+ ],
45
+ }
46
+
47
+ export default meta
48
+ type Story = StoryObj<typeof WorkoutShell>
49
+
50
+ export const Default: Story = {
51
+ args: { activeKey: 'live', state: 'live' },
52
+ }
53
+
54
+ export const WithContent: Story = {
55
+ args: { activeKey: 'review', state: 'rest', liveKey: 'live' },
56
+ render: (args) => (
57
+ <WorkoutShell {...args}>
58
+ <View className="flex-1 gap-1.5 p-7">
59
+ <Typography variant="h4" color="primary">
60
+ Review
61
+ </Typography>
62
+ <Typography variant="body2" color="tertiary">
63
+ A page mounts its content here; the shell keeps the nav + top-bar chrome persistent.
64
+ </Typography>
65
+ </View>
66
+ </WorkoutShell>
67
+ ),
68
+ parameters: {
69
+ docs: { description: { story: 'The shell with a page mounted in its content slot.' } },
70
+ },
71
+ }