@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
@@ -0,0 +1,85 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import { View, type ViewProps } from 'react-native'
3
+ import { cn } from '../../../utils/cn'
4
+ import { resolveColor } from '../../../theme/resolve-color'
5
+
6
+ /** Shortest bar drawn, so an all-but-empty series still reads as a series. */
7
+ const MIN_BAR_HEIGHT = 2
8
+
9
+ export interface SparkBarsProps extends Omit<ViewProps, 'accessibilityLabel'> {
10
+ /** The series, oldest first. Negative values draw in `negativeColor`. */
11
+ values: number[]
12
+ /** Overall bar-field height in px. */
13
+ height?: number
14
+ /** Width of a single bar in px. */
15
+ barWidth?: number
16
+ /** Gap between bars in px. */
17
+ gap?: number
18
+ /** Keep only the last N values — a sparkline is a recent-history glance, not an archive. */
19
+ maxBars?: number
20
+ /** Fill for non-negative bars. Defaults to `result-improve`. */
21
+ color?: string
22
+ /** Fill for negative bars. Defaults to `result-degrade`. */
23
+ negativeColor?: string
24
+ /** Screen-reader description. Defaults to a bar count. */
25
+ label?: string
26
+ className?: string
27
+ }
28
+
29
+ /**
30
+ * SparkBars — a tiny bar-mark sparkline for a signed series.
31
+ *
32
+ * The bar-mark counterpart to {@link Sparkline}: where a line reads a
33
+ * *trajectory*, bars read *discrete per-period magnitude*, which is what
34
+ * per-session activity and per-session net growth actually are. Bars are scaled
35
+ * by absolute value so a negative period is as tall as an equally large
36
+ * positive one, and tinted with `negativeColor` to carry the sign.
37
+ *
38
+ * @example
39
+ * <SparkBars values={[3, 8, -2, 14]} height={22} />
40
+ */
41
+ export function SparkBars({
42
+ values,
43
+ height = 22,
44
+ barWidth = 3,
45
+ gap = 2,
46
+ maxBars = 24,
47
+ color,
48
+ negativeColor,
49
+ label,
50
+ className,
51
+ ...props
52
+ }: SparkBarsProps) {
53
+ const shown = values.slice(-maxBars)
54
+ // A per-period delta going up or down is `result-*` (a change in a measurement),
55
+ // not `status-*` (a thing being broken). See TOKENS.md §1.
56
+ const positiveFill = color ?? resolveColor('result-improve')
57
+ const negativeFill = negativeColor ?? resolveColor('result-degrade')
58
+ // Scaling on magnitude keeps a -100 as tall as a +100; sign is carried by fill.
59
+ const peak = Math.max(1, ...shown.map((v) => Math.abs(v)))
60
+
61
+ return (
62
+ <View
63
+ style={{ height, gap }}
64
+ className={cn('flex-row items-end', className)}
65
+ accessibilityRole="image"
66
+ accessibilityLabel={label ?? `Bar sparkline with ${shown.length} values`}
67
+ testID="spark-bars"
68
+ {...props}
69
+ >
70
+ {shown.map((v, i) => (
71
+ <View
72
+ key={i}
73
+ style={{
74
+ height: Math.max(MIN_BAR_HEIGHT, Math.round((Math.abs(v) / peak) * height)),
75
+ width: barWidth,
76
+ backgroundColor: v < 0 ? negativeFill : positiveFill,
77
+ }}
78
+ className="rounded-t-[1px]"
79
+ accessibilityElementsHidden
80
+ testID={`spark-bars-bar-${i}`}
81
+ />
82
+ ))}
83
+ </View>
84
+ )
85
+ }
@@ -0,0 +1,3 @@
1
+ // Public chart primitives. `SetBarChart` and `live-rep-growth` are deliberately
2
+ // not re-exported here — they are workout-internal and imported by path.
3
+ export * from './SparkBars'
@@ -13,3 +13,6 @@ export * from './Fatigue'
13
13
  export * from './Treemap'
14
14
  export * from './Scatter'
15
15
  export * from './Gauge'
16
+ export * from './charts'
17
+ export * from './ActiveWork'
18
+ export * from './Prose'
@@ -5,7 +5,16 @@ import { Stepper, Step, StepIndicator, StepLabel, StepContent } from './Stepper'
5
5
  const meta: Meta<typeof Stepper> = {
6
6
  title: 'Components/Molecules/Stepper',
7
7
  component: Stepper,
8
- tags: ['autodocs'],
8
+ tags: ['autodocs', 'status:candidate', '!status:review'],
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component:
13
+ 'Composes RN primitives only. The five parts share step index and orientation through ' +
14
+ 'a context, the same shape as **Tabs**.',
15
+ },
16
+ },
17
+ },
9
18
  argTypes: {
10
19
  activeStep: {
11
20
  control: { type: 'number', min: 0, max: 4 },
@@ -124,9 +133,7 @@ export const VerticalWithContent: Story = {
124
133
  <StepIndicator />
125
134
  <StepLabel>Create Account</StepLabel>
126
135
  <StepContent>
127
- <Text className="text-text-secondary text-sm">
128
- Account creation is complete.
129
- </Text>
136
+ <Text className="text-text-secondary text-sm">Account creation is complete.</Text>
130
137
  </StepContent>
131
138
  </Step>
132
139
  <Step>
@@ -147,9 +154,7 @@ export const VerticalWithContent: Story = {
147
154
  <StepIndicator />
148
155
  <StepLabel>Configure Settings</StepLabel>
149
156
  <StepContent>
150
- <Text className="text-text-secondary text-sm">
151
- Adjust your preferences.
152
- </Text>
157
+ <Text className="text-text-secondary text-sm">Adjust your preferences.</Text>
153
158
  </StepContent>
154
159
  </Step>
155
160
  </Stepper>
@@ -221,9 +226,7 @@ export const ActiveStepProgression: Story = {
221
226
  <View style={{ gap: 24 }}>
222
227
  {[0, 1, 2, 3].map((activeStep) => (
223
228
  <View key={activeStep}>
224
- <Text className="text-text-secondary text-xs mb-2">
225
- Active step: {activeStep}
226
- </Text>
229
+ <Text className="text-text-secondary text-xs mb-2">Active step: {activeStep}</Text>
227
230
  <Stepper activeStep={activeStep} orientation="horizontal">
228
231
  <Step>
229
232
  <StepIndicator />
@@ -1,13 +1,7 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { render, screen } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
- import {
5
- Stepper,
6
- Step,
7
- StepIndicator,
8
- StepLabel,
9
- StepContent,
10
- } from './Stepper'
4
+ import { Stepper, Step, StepIndicator, StepLabel, StepContent } from './Stepper'
11
5
 
12
6
  function renderStepper(activeStep = 1) {
13
7
  return render(
@@ -65,9 +65,7 @@ export function Stepper({
65
65
  return (
66
66
  <>
67
67
  {React.cloneElement(child as React.ReactElement<any>, { index })}
68
- {index < React.Children.count(children) - 1 && (
69
- <StepConnector index={index} />
70
- )}
68
+ {index < React.Children.count(children) - 1 && <StepConnector index={index} />}
71
69
  </>
72
70
  )
73
71
  }
@@ -88,22 +86,12 @@ function StepConnector({ index }: StepConnectorProps) {
88
86
 
89
87
  if (orientation === 'horizontal') {
90
88
  return (
91
- <View
92
- className={cn(
93
- 'flex-1 h-0.5 mx-2',
94
- isCompleted ? 'bg-brand-primary' : 'bg-hairline'
95
- )}
96
- />
89
+ <View className={cn('flex-1 h-0.5 mx-2', isCompleted ? 'bg-brand-primary' : 'bg-hairline')} />
97
90
  )
98
91
  }
99
92
 
100
93
  return (
101
- <View
102
- className={cn(
103
- 'w-0.5 h-8 ml-4 my-1',
104
- isCompleted ? 'bg-brand-primary' : 'bg-hairline'
105
- )}
106
- />
94
+ <View className={cn('w-0.5 h-8 ml-4 my-1', isCompleted ? 'bg-brand-primary' : 'bg-hairline')} />
107
95
  )
108
96
  }
109
97
 
@@ -123,10 +111,8 @@ export interface StepProps {
123
111
  export function Step({ index = 0, status, className, children }: StepProps) {
124
112
  const { activeStep, orientation } = useContext(StepperContext)
125
113
 
126
- const derivedStatus: StepStatus = status || (
127
- index < activeStep ? 'completed' :
128
- index === activeStep ? 'active' : 'upcoming'
129
- )
114
+ const derivedStatus: StepStatus =
115
+ status || (index < activeStep ? 'completed' : index === activeStep ? 'active' : 'upcoming')
130
116
 
131
117
  return (
132
118
  <View
@@ -216,10 +202,13 @@ export function StepLabel({ status = 'upcoming', className, children }: StepLabe
216
202
  className={cn(
217
203
  'text-sm font-medium',
218
204
  orientation === 'horizontal' ? 'mt-2' : 'ml-3',
219
- status === 'active' ? 'text-text-primary' :
220
- status === 'completed' ? 'text-brand-primary' :
221
- status === 'error' ? 'text-status-error' :
222
- 'text-text-secondary',
205
+ status === 'active'
206
+ ? 'text-text-primary'
207
+ : status === 'completed'
208
+ ? 'text-brand-primary'
209
+ : status === 'error'
210
+ ? 'text-status-error'
211
+ : 'text-text-secondary',
223
212
  className
224
213
  )}
225
214
  >
@@ -242,9 +231,5 @@ export interface StepContentProps {
242
231
  export function StepContent({ status = 'upcoming', className, children }: StepContentProps) {
243
232
  if (status !== 'active') return null
244
233
 
245
- return (
246
- <View className={cn('ml-11 mt-2 pb-4', className)}>
247
- {children}
248
- </View>
249
- )
234
+ return <View className={cn('ml-11 mt-2 pb-4', className)}>{children}</View>
250
235
  }
@@ -20,7 +20,7 @@ import {
20
20
 
21
21
  const meta: Meta = {
22
22
  title: 'Foundations/Icons',
23
- tags: ['autodocs'],
23
+ tags: ['autodocs', 'status:candidate', '!status:review'],
24
24
  parameters: {
25
25
  docs: {
26
26
  description: {
@@ -151,3 +151,54 @@ export function InfoIcon(props: IconProps) {
151
151
  </SvgIcon>
152
152
  )
153
153
  }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // AW-132 · App brand marks — the mark half of a `BrandLockup` for each app that
157
+ // is likely to mount the shell. Sized 14 by default, like `VoltrasMark`.
158
+ // ---------------------------------------------------------------------------
159
+
160
+ /** Headphones glyph (mirrors lucide-react `Headphones`). Audiobook brand mark. */
161
+ export function HeadphonesIcon({ size = 14, ...props }: IconProps) {
162
+ return (
163
+ <SvgIcon size={size} {...props}>
164
+ <path d="M3 14h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-5a9 9 0 0 1 18 0v5a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3" />
165
+ </SvgIcon>
166
+ )
167
+ }
168
+
169
+ /** Kanban board glyph (mirrors lucide-react `SquareKanban`). Active-work brand mark. */
170
+ export function KanbanIcon({ size = 14, ...props }: IconProps) {
171
+ return (
172
+ <SvgIcon size={size} {...props}>
173
+ <rect width="18" height="18" x="3" y="3" rx="2" />
174
+ <path d="M8 7v7" />
175
+ <path d="M12 7v4" />
176
+ <path d="M16 7v9" />
177
+ </SvgIcon>
178
+ )
179
+ }
180
+
181
+ /** Robot glyph (mirrors lucide-react `Bot`). Agent-dashboard brand mark. */
182
+ export function BotIcon({ size = 14, ...props }: IconProps) {
183
+ return (
184
+ <SvgIcon size={size} {...props}>
185
+ <path d="M12 8V4H8" />
186
+ <rect width="16" height="12" x="4" y="8" rx="2" />
187
+ <path d="M2 14h2" />
188
+ <path d="M20 14h2" />
189
+ <path d="M15 13v2" />
190
+ <path d="M9 13v2" />
191
+ </SvgIcon>
192
+ )
193
+ }
194
+
195
+ /** Brain glyph (mirrors lucide-react `Brain`). Brain-app brand mark. */
196
+ export function BrainIcon({ size = 14, ...props }: IconProps) {
197
+ return (
198
+ <SvgIcon size={size} {...props}>
199
+ <path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" />
200
+ <path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z" />
201
+ <path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4" />
202
+ </SvgIcon>
203
+ )
204
+ }
@@ -0,0 +1,150 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View, Text } from 'react-native'
3
+ import { Surface } from '../ui/surface'
4
+ import { Pill } from '../ui/pill'
5
+ import { Typography } from '../custom/Typography'
6
+ import { BotIcon, BrainIcon, HeadphonesIcon, LayersIcon, StarIcon } from '../icons'
7
+ import { AppShell } from './AppShell'
8
+ import { brandKeys } from './brands'
9
+ import { type SideNavItem } from './SideNav'
10
+
11
+ const brainNav: SideNavItem[] = [
12
+ { key: 'notes', label: 'Notes', icon: <LayersIcon size={20} color="currentColor" /> },
13
+ { key: 'graph', label: 'Graph', icon: <BrainIcon size={20} color="currentColor" /> },
14
+ { key: 'agents', label: 'Agents', icon: <BotIcon size={20} color="currentColor" /> },
15
+ ]
16
+
17
+ const audiobookNav: SideNavItem[] = [
18
+ { key: 'library', label: 'Library', icon: <HeadphonesIcon size={20} color="currentColor" /> },
19
+ { key: 'saved', label: 'Saved', icon: <StarIcon size={20} color="currentColor" /> },
20
+ ]
21
+
22
+ function Page({ title }: { title: string }) {
23
+ return (
24
+ <View className="flex-1 gap-1.5 p-7">
25
+ <Typography variant="h4" color="primary">
26
+ {title}
27
+ </Typography>
28
+ <Typography variant="body2" color="tertiary">
29
+ A page mounts its content here; the shell keeps the nav + top-bar chrome persistent.
30
+ </Typography>
31
+ </View>
32
+ )
33
+ }
34
+
35
+ /**
36
+ * `Pages/AppShell` — the generic dashboard chrome as one registered surface.
37
+ * The app supplies its brand, its nav categories and its own top-bar chrome.
38
+ */
39
+ const meta: Meta<typeof AppShell> = {
40
+ title: 'Pages/AppShell',
41
+ component: AppShell,
42
+ tags: ['autodocs', 'status:candidate', '!status:review'],
43
+ parameters: {
44
+ layout: 'fullscreen',
45
+ docs: {
46
+ description: {
47
+ component:
48
+ '**Page shell (generic).** Composes ' +
49
+ '[SideNav](?path=/docs/shell-sidenav--docs) + ' +
50
+ '[TopBar](?path=/docs/shell-topbar--docs) over a `children` content slot.\n\n' +
51
+ '**Composition (AW-132).** It knows nothing about any one app. Chrome arrives ' +
52
+ 'through `topBarTrailing`; categories through `navItems`; `topBar` and `nav` ' +
53
+ 'replace those regions outright. An app builds its own shell by composing this one ' +
54
+ '— see [WorkoutShell](?path=/docs/pages-workoutshell--docs).',
55
+ },
56
+ },
57
+ },
58
+ argTypes: {
59
+ brand: { control: 'select', options: brandKeys },
60
+ onNavigate: { control: false },
61
+ topBar: { control: false },
62
+ nav: { control: false },
63
+ topBarTrailing: { control: false },
64
+ navItems: { control: false },
65
+ },
66
+ decorators: [
67
+ (Story) => (
68
+ <Surface level="base" className="min-h-screen">
69
+ <Story />
70
+ </Surface>
71
+ ),
72
+ ],
73
+ }
74
+
75
+ export default meta
76
+ type Story = StoryObj<typeof AppShell>
77
+
78
+ /** No app chrome at all — the frame on its own. */
79
+ export const Default: Story = {
80
+ args: { brand: 'voltras', navItems: brainNav, activeKey: 'notes' },
81
+ }
82
+
83
+ /** The brain app: its own brand, categories, top-bar chrome and page. */
84
+ export const BrainApp: Story = {
85
+ args: {
86
+ brand: 'brain',
87
+ navItems: brainNav,
88
+ activeKey: 'graph',
89
+ topBarTrailing: [
90
+ <Pill key="index" tone="success" leading="dot" size="xs">
91
+ indexed
92
+ </Pill>,
93
+ <Pill key="vaults" tone="neutral" size="xs">
94
+ 4 vaults
95
+ </Pill>,
96
+ ],
97
+ },
98
+ render: (args) => (
99
+ <AppShell {...args}>
100
+ <Page title="Graph" />
101
+ </AppShell>
102
+ ),
103
+ }
104
+
105
+ /** The audiobook app in the same frame — different brand, categories and chrome. */
106
+ export const AudiobookApp: Story = {
107
+ args: {
108
+ brand: 'audiobook',
109
+ navItems: audiobookNav,
110
+ activeKey: 'library',
111
+ liveKey: 'saved',
112
+ topBarTrailing: (
113
+ <Pill tone="brand-secondary" leading="dot" size="xs">
114
+ 3 downloading
115
+ </Pill>
116
+ ),
117
+ },
118
+ render: (args) => (
119
+ <AppShell {...args}>
120
+ <Page title="Library" />
121
+ </AppShell>
122
+ ),
123
+ }
124
+
125
+ /** Both slots replaced: the app brings its own bar and its own rail. */
126
+ export const ReplacedRegions: Story = {
127
+ args: { navItems: brainNav, activeKey: 'notes' },
128
+ render: (args) => (
129
+ <AppShell
130
+ {...args}
131
+ topBar={
132
+ <Surface level="elevated" className="h-[46px] justify-center border-b border-hairline px-4">
133
+ <Text className="font-heading text-sm text-text-secondary">an app-owned bar</Text>
134
+ </Surface>
135
+ }
136
+ nav={
137
+ <Surface
138
+ level="base"
139
+ className="w-[60px] items-center justify-center border-r border-hairline"
140
+ >
141
+ <Typography variant="microLabel" color="tertiary">
142
+ rail
143
+ </Typography>
144
+ </Surface>
145
+ }
146
+ >
147
+ <Page title="Anything" />
148
+ </AppShell>
149
+ ),
150
+ }
@@ -0,0 +1,70 @@
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 { AppShell } from './AppShell'
6
+ import { type SideNavItem } from './SideNav'
7
+
8
+ const navItems: SideNavItem[] = [
9
+ { key: 'notes', label: 'Notes', icon: null },
10
+ { key: 'graph', label: 'Graph', icon: null },
11
+ ]
12
+
13
+ describe('AppShell', () => {
14
+ it('composes the SideNav rail from the supplied categories', () => {
15
+ render(<AppShell navItems={navItems} activeKey="notes" />)
16
+ expect(screen.getByRole('tablist')).toBeInTheDocument()
17
+ ;['Notes', 'Graph'].forEach((name) => {
18
+ expect(screen.getByRole('tab', { name })).toBeInTheDocument()
19
+ })
20
+ })
21
+
22
+ it('renders the content-slot placeholder when no children are given', () => {
23
+ render(<AppShell navItems={navItems} activeKey="notes" />)
24
+ expect(screen.getByText('main content region')).toBeInTheDocument()
25
+ })
26
+
27
+ it('mounts children in the content slot and drops the placeholder', () => {
28
+ render(
29
+ <AppShell navItems={navItems} activeKey="graph">
30
+ <Text>my page body</Text>
31
+ </AppShell>
32
+ )
33
+ expect(screen.getByText('my page body')).toBeInTheDocument()
34
+ expect(screen.queryByText('main content region')).not.toBeInTheDocument()
35
+ })
36
+
37
+ it('forwards nav taps through onNavigate', () => {
38
+ const onNavigate = vi.fn()
39
+ render(<AppShell navItems={navItems} activeKey="notes" onNavigate={onNavigate} />)
40
+ fireEvent.click(screen.getByRole('tab', { name: 'Graph' }))
41
+ expect(onNavigate).toHaveBeenCalledWith('graph')
42
+ })
43
+
44
+ it('renders app chrome in the top bar without knowing what it is', () => {
45
+ render(<AppShell navItems={navItems} activeKey="notes" topBarTrailing={<Text>3 jobs</Text>} />)
46
+ expect(screen.getByText('3 jobs')).toBeInTheDocument()
47
+ })
48
+
49
+ it('takes a whole replacement top bar and nav', () => {
50
+ render(
51
+ <AppShell topBar={<Text>my own bar</Text>} nav={<Text>my own rail</Text>}>
52
+ <Text>body</Text>
53
+ </AppShell>
54
+ )
55
+ expect(screen.getByText('my own bar')).toBeInTheDocument()
56
+ expect(screen.getByText('my own rail')).toBeInTheDocument()
57
+ expect(screen.queryByText('VOLTRAS')).not.toBeInTheDocument()
58
+ expect(screen.queryByRole('tablist')).not.toBeInTheDocument()
59
+ })
60
+
61
+ it('carries the brand through to the top bar', () => {
62
+ render(<AppShell brand="agents" navItems={navItems} activeKey="notes" />)
63
+ expect(screen.getByText('AGENTS')).toBeInTheDocument()
64
+ })
65
+
66
+ it('has no a11y violations', async () => {
67
+ const { container } = render(<AppShell navItems={navItems} activeKey="notes" />)
68
+ expect(await axe(container)).toHaveNoViolations()
69
+ })
70
+ })
@@ -0,0 +1,90 @@
1
+ import type { ReactNode } from 'react'
2
+ import { View, Text } from 'react-native'
3
+ import { cn } from '../../utils/cn'
4
+ import { Surface } from '../ui/surface'
5
+ import { SideNav, type SideNavItem } from './SideNav'
6
+ import { TopBar } from './TopBar'
7
+ import { brandPresets, type BrandKey } from './brands'
8
+
9
+ export interface AppShellProps {
10
+ /** Which app identity the default {@link TopBar} and nav accent render. */
11
+ brand?: BrandKey
12
+ /** Brand subtitle on the default top bar. */
13
+ subtitle?: string
14
+ /** App chrome for the default top bar's right cluster (divider-separated). */
15
+ topBarTrailing?: ReactNode | ReactNode[]
16
+ /** Replace the whole top bar. Wins over `brand` / `subtitle` / `topBarTrailing`. */
17
+ topBar?: ReactNode
18
+ /** Nav categories, top → bottom. */
19
+ navItems?: SideNavItem[]
20
+ /** Active nav category key. */
21
+ activeKey?: string
22
+ /** A category with off-view live activity → a quiet cue on that item. */
23
+ liveKey?: string | null
24
+ onNavigate?: (key: string) => void
25
+ /** Replace the whole left rail. Wins over `navItems` / `activeKey` / `liveKey`. */
26
+ nav?: ReactNode
27
+ /** Main content region. A placeholder renders when omitted. */
28
+ children?: ReactNode
29
+ className?: string
30
+ }
31
+
32
+ function ContentPlaceholder() {
33
+ return (
34
+ <View className="flex-1 items-center justify-center">
35
+ <Text className="font-heading text-sm text-text-tertiary">main content region</Text>
36
+ </View>
37
+ )
38
+ }
39
+
40
+ /**
41
+ * `AppShell` — the generic dashboard chrome: a {@link TopBar} band over a
42
+ * {@link SideNav} rail and a `children` content region. It knows nothing about
43
+ * any one app: chrome arrives through the `topBarTrailing` slot (or the whole
44
+ * `topBar` / `nav` slots), and categories through `navItems`. An app builds its
45
+ * own shell by composing this one — see `shell/workout/WorkoutShell`.
46
+ *
47
+ * @example
48
+ * <AppShell brand="brain" navItems={brainNavItems} activeKey={key} onNavigate={go}>
49
+ * <BrainPage />
50
+ * </AppShell>
51
+ */
52
+ export function AppShell({
53
+ brand = 'voltras',
54
+ subtitle,
55
+ topBarTrailing,
56
+ topBar,
57
+ navItems = [],
58
+ activeKey = '',
59
+ liveKey = null,
60
+ onNavigate,
61
+ nav,
62
+ children,
63
+ className,
64
+ }: AppShellProps) {
65
+ const { accentClassName, accentBarClassName } = brandPresets[brand]
66
+
67
+ return (
68
+ // Column: the TopBar spans the FULL width across the top, and the SideNav sits BELOW it
69
+ // in the content row (not a full-height left rail). This keeps the brand/status band
70
+ // unbroken edge-to-edge and lets the nav align under it. The shell is the outermost
71
+ // Surface — it owns the base plane and seeds the on-surface colour context (mode) for
72
+ // the whole dashboard tree.
73
+ <Surface level="base" className={cn('flex-1', className)}>
74
+ {topBar ?? <TopBar brand={brand} subtitle={subtitle} trailing={topBarTrailing} />}
75
+ <View className="flex-1 flex-row">
76
+ {nav ?? (
77
+ <SideNav
78
+ items={navItems}
79
+ activeKey={activeKey}
80
+ liveKey={liveKey}
81
+ accentClassName={accentClassName}
82
+ accentBarClassName={accentBarClassName}
83
+ onNavigate={onNavigate}
84
+ />
85
+ )}
86
+ <View className="flex-1">{children ?? <ContentPlaceholder />}</View>
87
+ </View>
88
+ </Surface>
89
+ )
90
+ }