@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,22 +1,37 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { Surface } from '../ui/surface'
4
+ import { Typography } from '../custom/Typography'
5
+ import { StarIcon } from '../icons'
2
6
  import { BrandLockup } from './BrandLockup'
7
+ import { brandKeys } from './brands'
3
8
 
4
9
  const meta: Meta<typeof BrandLockup> = {
5
10
  title: 'Shell/BrandLockup',
6
11
  component: BrandLockup,
7
- tags: ['autodocs'],
8
- args: { subtitle: 'wall dashboard', showSubtitle: true },
12
+ tags: ['autodocs', 'status:candidate', '!status:review'],
13
+ args: { brand: 'voltras', showSubtitle: true },
9
14
  argTypes: {
15
+ brand: { control: 'select', options: brandKeys },
10
16
  subtitle: { control: 'text' },
17
+ wordmark: { control: 'text' },
18
+ accentClassName: { control: 'text' },
11
19
  showSubtitle: { control: 'boolean' },
20
+ mark: { control: false },
12
21
  },
13
22
  parameters: {
14
23
  docs: {
15
24
  description: {
16
25
  component:
17
- '**Molecule.** Composes [VoltrasMark](?path=/docs/foundations-icons--docs) (icon) + ' +
18
- '[Typography](?path=/docs/foundations-typography--docs) (wordmark + subtitle). ' +
19
- 'Toggle `showSubtitle` to see the responsive collapse.',
26
+ '**Molecule.** Composes an [icon](?path=/docs/foundations-icons--docs) mark + ' +
27
+ '[Typography](?path=/docs/foundations-typography--docs) (wordmark + subtitle).\n\n' +
28
+ '**Generic over the app (AW-132).** Pick a `brand` preset, or override `mark` / ' +
29
+ '`wordmark` / `accentClassName` / `subtitle` for an app that has no preset yet. ' +
30
+ "Accents come from `data-*` rather than `status-*`: `data-*` is the library's set of " +
31
+ 'distinct, CVD-checked hues with no semantic load, which is what a per-app identity ' +
32
+ 'accent needs. Voltras keeps the real `brand-primary` token.\n\n' +
33
+ '**Try it:** switch `brand` in the **Controls**, or toggle `showSubtitle` to see the ' +
34
+ 'responsive collapse.',
20
35
  },
21
36
  },
22
37
  },
@@ -25,3 +40,49 @@ export default meta
25
40
  type Story = StoryObj<typeof BrandLockup>
26
41
 
27
42
  export const Default: Story = {}
43
+
44
+ /** Every app likely to mount this shell, in the chrome band's own surface. */
45
+ export const BrandVariants: Story = {
46
+ parameters: {
47
+ docs: {
48
+ description: {
49
+ story:
50
+ 'The five branding presets. Each is a mark, a wordmark, a semantic accent token and a ' +
51
+ 'default subtitle — nothing else in the shell changes between them.',
52
+ },
53
+ },
54
+ },
55
+ render: () => (
56
+ <Surface level="elevated" className="gap-4 p-5">
57
+ {brandKeys.map((brand) => (
58
+ <View key={brand} className="gap-1">
59
+ <Typography variant="microLabel" color="tertiary">
60
+ {brand}
61
+ </Typography>
62
+ <BrandLockup brand={brand} />
63
+ </View>
64
+ ))}
65
+ </Surface>
66
+ ),
67
+ }
68
+
69
+ /** Collapsed subtitle — what every brand looks like below ~1024px. */
70
+ export const BrandVariantsCollapsed: Story = {
71
+ render: () => (
72
+ <Surface level="elevated" className="gap-4 p-5">
73
+ {brandKeys.map((brand) => (
74
+ <BrandLockup key={brand} brand={brand} showSubtitle={false} />
75
+ ))}
76
+ </Surface>
77
+ ),
78
+ }
79
+
80
+ /** An app with no preset: supply mark, wordmark, accent and subtitle directly. */
81
+ export const CustomBrand: Story = {
82
+ args: {
83
+ mark: <StarIcon size={14} color="currentColor" />,
84
+ wordmark: 'HYPERFRAMES',
85
+ subtitle: 'renders',
86
+ accentClassName: 'text-data-6',
87
+ },
88
+ }
@@ -2,9 +2,10 @@ import { describe, it, expect } from 'vitest'
2
2
  import { render, screen } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
4
  import { BrandLockup } from './BrandLockup'
5
+ import { brandKeys, brandPresets } from './brands'
5
6
 
6
7
  describe('BrandLockup', () => {
7
- it('renders the wordmark', () => {
8
+ it('renders the voltras wordmark by default', () => {
8
9
  render(<BrandLockup />)
9
10
  expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
10
11
  })
@@ -16,6 +17,38 @@ describe('BrandLockup', () => {
16
17
  expect(screen.queryByText('/ wall dashboard')).not.toBeInTheDocument()
17
18
  })
18
19
 
20
+ it.each(brandKeys)('renders the %s preset wordmark and subtitle', (brand) => {
21
+ render(<BrandLockup brand={brand} />)
22
+ expect(screen.getByText(brandPresets[brand].wordmark)).toBeInTheDocument()
23
+ expect(screen.getByText(`/ ${brandPresets[brand].subtitle}`)).toBeInTheDocument()
24
+ })
25
+
26
+ it('lets an app override the preset parts', () => {
27
+ render(<BrandLockup brand="brain" wordmark="HYPERFRAMES" subtitle="renders" />)
28
+ expect(screen.getByText('HYPERFRAMES')).toBeInTheDocument()
29
+ expect(screen.getByText('/ renders')).toBeInTheDocument()
30
+ expect(screen.queryByText('BRAIN')).not.toBeInTheDocument()
31
+ })
32
+
33
+ // nativewind compiles className to style, so the rendered accent colour is not
34
+ // assertable in jsdom. Assert the token CHOICE instead: every preset picks a
35
+ // semantic `text-*` token, and no two apps share one.
36
+ it('gives every brand a distinct semantic accent token', () => {
37
+ const accents = brandKeys.map((brand) => brandPresets[brand].accentClassName)
38
+ accents.forEach((accent) => expect(accent).toMatch(/^text-(brand|data)-/))
39
+ expect(new Set(accents).size).toBe(accents.length)
40
+ })
41
+
42
+ // The nav's active bar needs the accent as a background, and a mismatched pair
43
+ // would render a lockup and an active nav item in two different hues.
44
+ it('pairs every accent with the same token as a background', () => {
45
+ brandKeys.forEach((brand) => {
46
+ const { accentClassName, accentBarClassName } = brandPresets[brand]
47
+ expect(accentBarClassName).toMatch(/^bg-(brand|data)-/)
48
+ expect(accentBarClassName).toBe(accentClassName.replace(/^text-/, 'bg-'))
49
+ })
50
+ })
51
+
19
52
  it('has no accessibility violations', async () => {
20
53
  const { container } = render(<BrandLockup />)
21
54
  expect(await axe(container)).toHaveNoViolations()
@@ -1,10 +1,19 @@
1
+ import type { ReactNode } from 'react'
1
2
  import { View, type ViewProps } from 'react-native'
2
3
  import { cn } from '../../utils/cn'
3
4
  import { Typography } from '../custom/Typography'
4
- import { VoltrasMark } from '../icons'
5
+ import { brandPresets, type BrandKey } from './brands'
5
6
 
6
7
  export interface BrandLockupProps extends ViewProps {
7
- /** The "/ subtitle" that follows the wordmark. Default "wall dashboard". */
8
+ /** Which app's identity to render. Defaults to `voltras`. */
9
+ brand?: BrandKey
10
+ /** Replace the preset's mark glyph. */
11
+ mark?: ReactNode
12
+ /** Replace the preset's wordmark. */
13
+ wordmark?: string
14
+ /** Replace the preset's accent — a semantic `text-*` token class for the mark. */
15
+ accentClassName?: string
16
+ /** The "/ subtitle" that follows the wordmark. Defaults to the brand's own. */
8
17
  subtitle?: string
9
18
  /** Show the subtitle. Hidden at tablet width and below (S1-Q4 responsive). */
10
19
  showSubtitle?: boolean
@@ -12,34 +21,41 @@ export interface BrandLockupProps extends ViewProps {
12
21
  }
13
22
 
14
23
  /**
15
- * Product identity lockup for the top bar: mark + VOLTRAS wordmark + optional
16
- * subtitle. Composes the VoltrasMark icon + Typography. S1 · BrandLockup.
24
+ * Product identity lockup for the top bar: mark + wordmark + optional subtitle.
25
+ * Generic over the app: pick a `brand` preset, or override `mark` / `wordmark` /
26
+ * `accentClassName` / `subtitle` piecemeal for an app that has no preset yet.
27
+ * Composes an icon primitive + Typography. S1 · BrandLockup.
17
28
  */
18
29
  export function BrandLockup({
19
- subtitle = 'wall dashboard',
30
+ brand = 'voltras',
31
+ mark,
32
+ wordmark,
33
+ accentClassName,
34
+ subtitle,
20
35
  showSubtitle = true,
21
36
  className,
22
37
  ...props
23
38
  }: BrandLockupProps) {
39
+ const preset = brandPresets[brand]
40
+ const resolvedSubtitle = subtitle ?? preset.subtitle
41
+
24
42
  return (
25
43
  <View className={cn('flex-row items-center gap-[7px]', className)} {...props}>
26
- <View className="text-brand-primary">
27
- <VoltrasMark size={14} color="currentColor" />
28
- </View>
44
+ <View className={accentClassName ?? preset.accentClassName}>{mark ?? preset.mark}</View>
29
45
  <Typography
30
46
  variant="button"
31
47
  color="primary"
32
48
  className="text-[12px] font-extrabold tracking-[1.5px]"
33
49
  >
34
- VOLTRAS
50
+ {wordmark ?? preset.wordmark}
35
51
  </Typography>
36
- {showSubtitle && subtitle ? (
52
+ {showSubtitle && resolvedSubtitle ? (
37
53
  <Typography
38
54
  variant="button"
39
55
  color="tertiary"
40
56
  className="text-[10px] font-semibold tracking-[0.4px]"
41
57
  >
42
- / {subtitle}
58
+ / {resolvedSubtitle}
43
59
  </Typography>
44
60
  ) : null}
45
61
  </View>
@@ -1,12 +1,12 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
- import { View } from 'react-native'
3
2
  import { NavItem } from './NavItem'
4
3
  import { ActivityIcon } from '../icons'
4
+ import { Surface } from '../ui/surface'
5
5
 
6
6
  const meta: Meta<typeof NavItem> = {
7
7
  title: 'Shell/NavItem',
8
8
  component: NavItem,
9
- tags: ['autodocs'],
9
+ tags: ['autodocs', 'status:candidate', '!status:review'],
10
10
  args: { label: 'Live', active: false, live: false },
11
11
  argTypes: {
12
12
  label: { control: 'text' },
@@ -17,9 +17,9 @@ const meta: Meta<typeof NavItem> = {
17
17
  },
18
18
  decorators: [
19
19
  (Story) => (
20
- <View className="w-[60px] items-center bg-background-base py-2">
20
+ <Surface level="background" className="w-[60px] items-center py-2">
21
21
  <Story />
22
- </View>
22
+ </Surface>
23
23
  ),
24
24
  ],
25
25
  parameters: {
@@ -32,4 +32,21 @@ describe('NavItem', () => {
32
32
  const { container } = render(<NavItem icon={icon} label="Live" live />)
33
33
  expect(container.firstChild).toBeInTheDocument()
34
34
  })
35
+
36
+ // nativewind compiles className to style, so jsdom cannot see the accent colour.
37
+ // Assert an app-supplied accent leaves the rest of the active state intact; the
38
+ // colour itself is guarded by the paired-token test in BrandLockup.test.
39
+ it('keeps the active state when an app supplies its own accent', () => {
40
+ render(
41
+ <NavItem
42
+ icon={icon}
43
+ label="Graph"
44
+ active
45
+ accentClassName="text-data-3"
46
+ accentBarClassName="bg-data-3"
47
+ />
48
+ )
49
+ expect(screen.getByTestId('nav-item-accent')).toBeInTheDocument()
50
+ expect(screen.getByRole('tab', { name: 'Graph' })).toBeInTheDocument()
51
+ })
35
52
  })
@@ -8,13 +8,17 @@ export interface NavItemProps {
8
8
  icon: ReactNode
9
9
  /** Short label shown under the glyph (uppercased for display; used as the accessible name). */
10
10
  label: string
11
- /** Active category → left accent bar + brand-colored glyph & label. */
11
+ /** Active category → left accent bar + accent-colored glyph & label. */
12
12
  active?: boolean
13
13
  /**
14
14
  * A set is running for this category while it is NOT the active view
15
15
  * (live-elsewhere) → a quiet muted-green label tint. Ignored when `active`.
16
16
  */
17
17
  live?: boolean
18
+ /** Semantic `text-*` token for the active glyph and label. Defaults to the Voltras brand. */
19
+ accentClassName?: string
20
+ /** Semantic `bg-*` token for the active bar. Pair it with `accentClassName`. */
21
+ accentBarClassName?: string
18
22
  onPress?: () => void
19
23
  className?: string
20
24
  }
@@ -23,20 +27,25 @@ export interface NavItemProps {
23
27
  * Shell S2 · NavItem — one category button in the {@link SideNav}: a 20px glyph
24
28
  * over an uppercase micro-label in a 46×46 target. The button spans the full 60px
25
29
  * rail so the active **left accent bar** sits flush to the rail's edge. States:
26
- * active = accent bar + `brand-primary`; `live` (while not active) tints only the
30
+ * active = accent bar + the accent token; `live` (while not active) tints only the
27
31
  * label `status-success-dark` (the glyph stays dim); otherwise dim `text-tertiary`.
32
+ *
33
+ * The accent follows the mounting app's brand, so a Brain shell reads yellow
34
+ * throughout rather than showing a Voltras-orange active item under its own lockup.
28
35
  */
29
36
  export function NavItem({
30
37
  icon,
31
38
  label,
32
39
  active = false,
33
40
  live = false,
41
+ accentClassName = 'text-brand-primary',
42
+ accentBarClassName = 'bg-brand-primary',
34
43
  onPress,
35
44
  className,
36
45
  }: NavItemProps) {
37
- const glyphColor = active ? 'text-brand-primary' : 'text-text-tertiary'
46
+ const glyphColor = active ? accentClassName : 'text-text-tertiary'
38
47
  const labelColor = active
39
- ? 'text-brand-primary'
48
+ ? accentClassName
40
49
  : live
41
50
  ? 'text-status-live-muted'
42
51
  : 'text-text-tertiary'
@@ -52,7 +61,10 @@ export function NavItem({
52
61
  {active ? (
53
62
  <View
54
63
  testID="nav-item-accent"
55
- className="absolute left-0 top-[13px] bottom-[13px] w-[3px] rounded-r-[3px] bg-brand-primary"
64
+ className={cn(
65
+ 'absolute left-0 top-[13px] bottom-[13px] w-[3px] rounded-r-[3px]',
66
+ accentBarClassName
67
+ )}
56
68
  />
57
69
  ) : null}
58
70
  <View
@@ -1,64 +1,113 @@
1
1
  # Shell chrome — component family
2
2
 
3
- The persistent dashboard frame (top bar, and — as the decomposition proceeds — side nav, session
4
- rail, drawers, pinned strip). Components live flat on disk; the tiering below is a documentation
5
- contract, not a directory layout, so promoting/composing is pure import churn.
3
+ The persistent dashboard frame (top bar, side nav, and — as the decomposition proceeds — session
4
+ rail, drawers, pinned strip). The tiering below is a documentation contract, not a directory
5
+ layout, so promoting/composing is pure import churn.
6
6
 
7
7
  This README is the **index**: it maps each component's dependencies (**composes ↓**) and its consumers
8
8
  (**used-by ↑**) so you can navigate the tree in both directions and spot where a hand-rolled element
9
9
  should be reaching for an existing primitive instead. Storybook mirrors this tree under `Shell/TopBar/…`,
10
10
  and every component's autodocs page repeats its "Composes" links.
11
11
 
12
+ ## The generic/app split (AW-132)
13
+
14
+ `shell/` is **generic**. It knows about a band, a rail, a brand lockup and a content region, and
15
+ nothing about any one product. `shell/workout/` is the **workout app's** chrome, and it composes
16
+ the generic family. The dependency arrow points one way only: nothing in `shell/` may import from
17
+ `shell/workout/`, and a second app (audiobook, active-work, agent dashboards, brain) gets its own
18
+ sibling directory rather than a new prop on `AppShell`.
19
+
20
+ **The composition mechanism is the library's existing slot idiom** — named `ReactNode` props with a
21
+ built-in default, the same shape as `Pill`'s `leading`/`trailing` and `SectionHeader`'s `trailing`.
22
+ No second mechanism was introduced.
23
+
24
+ | Slot | On | Default | An app uses it to… |
25
+ | ------------------------ | ----------- | -------------------------------- | ---------------------------------------- |
26
+ | `TopBar.trailing` | `TopBar` | just the clock | put its own chrome in the right cluster |
27
+ | `TopBar.leading` | `TopBar` | `<BrandLockup brand=…>` | replace the brand region outright |
28
+ | `AppShell.topBarTrailing` | `AppShell` | — | pass chrome through to the default bar |
29
+ | `AppShell.topBar` | `AppShell` | `<TopBar brand=…>` | swap the whole band |
30
+ | `AppShell.nav` | `AppShell` | `<SideNav items=…>` | swap the whole rail |
31
+ | `AppShell.children` | `AppShell` | a placeholder | mount its page |
32
+
33
+ `TopBar.trailing` takes an array as well as a node. **The bar interleaves its own vertical dividers
34
+ between the items**, so an app supplies the controls and the shell keeps the divider rhythm from the
35
+ S1 lock — the app never hand-places a `Divider`, and can't drift from it.
36
+
37
+ Two things stopped being generic defaults and became the workout app's data:
38
+
39
+ - `SideNav.items` is **required**. The four Live · Review · Plan · Body categories are
40
+ `workoutNavItems` in `shell/workout/`.
41
+ - `BrandLockup` is generic over `brand` (five presets in `brands.tsx`), rather than hard-coding
42
+ the Voltras mark and wordmark.
43
+
12
44
  ## Specimen: `SideNav` (the second shell unit, S2)
13
45
 
14
46
  ```
15
47
  SideNav ...................... organism — the 60px left rail (Shell/SideNav)
16
- ├─ NavItem ................... molecule × 4 (Live · Review · Plan · Body)
17
- │ ├─ icon ................... icons (shared primitive) — Activity/History/Layers/PersonStanding
48
+ ├─ NavItem ................... molecule × `items` (the app's own categories)
49
+ │ ├─ icon ................... icons (shared primitive) — supplied per item
18
50
  │ └─ Typography ............. titan · button variant (uppercase micro-label)
19
- └─ (accent bar) .............. bg-brand-primary edge bar on the active item
51
+ └─ (accent bar) .............. edge bar on the active item, in the app's brand accent
20
52
  ```
21
53
 
22
- Decisions locked 2026-07-08 (specimen `coordination/design-explorations/shell/S2-sidenav/`): lucide glyphs
54
+ Decisions locked 2026-07-08 (specimen `sources/design/shell/S2-sidenav/`): lucide glyphs
23
55
  (activity · history · layers · figure) · **Plan** label (narrower than "Program") · active = **left accent
24
56
  bar** (short, centered on the icon+label) · **60px** icon+micro-label · live cue = **muted-green label**
25
57
  (`status-live-muted`) on the Live item while a set runs off-Live · four items, no footer, no top-nav. Fixed
26
58
  60px at every width (labels sit under the glyph). `SideNav` is presentational — `activeKey` / `onNavigate` /
27
- `liveKey`; the app owns routing + which key is live.
59
+ `liveKey`; the app owns routing + which key is live. Those four categories are now
60
+ `workoutNavItems` in `shell/workout/` (AW-132); the rail itself has no built-in set.
28
61
 
29
62
  ## Specimen: `TopBar` (the first shell unit, S1)
30
63
 
31
64
  ```
32
65
  TopBar ....................... organism — the chrome band (Shell/TopBar)
33
- ├─ BrandLockup ............... molecule
34
- │ ├─ VoltrasMark ............ icons (shared primitive)
66
+ ├─ BrandLockup ............... molecule — default `leading`
67
+ │ ├─ brand mark ............. icons (shared primitive) — per `brands.tsx` preset
35
68
  │ └─ Typography ............. titan · button variant
36
- ├─ SessionStatePill .......... molecule (= the reusable StatusPill)
37
- ├─ Indicator .............. titan · ping pulse + vivid color
38
- │ └─ Typography ............. titan · monoLabel
39
- ├─ Divider ................... titan · bg-border-prominent
40
- ├─ DeviceMenu ................ organism
41
- │ ├─ Popover ................ titan
42
- │ ├─ DeviceIndicator ........ molecule → BluetoothIcon (icons primitive)
43
- │ ├─ DeviceRow .............. molecule → Indicator + Typography (body2 / mono)
44
- │ └─ Typography ............. titan · monoLabel (header)
45
- ├─ DateTime .................. titan · mono variant, live 24h clock
69
+ ├─ trailing[] ................ SLOT — app chrome, divider-interleaved by the bar
70
+ ├─ Divider ................... titan · bg-border-prominent (one per slot gap)
71
+ ├─ DateTime .................. titan · mono variant, live 24h clock (edge-pinned)
46
72
  └─ surfaceGradient.chrome .... theme · gradient primitive
47
73
  ```
48
74
 
49
- ## Dependency map
75
+ ## Specimen: `WorkoutTopBar` (the same band, filled by the workout app)
76
+
77
+ ```
78
+ WorkoutTopBar ................ organism — Shell/Workout/WorkoutTopBar
79
+ └─ TopBar .................... generic band, brand="voltras"
80
+ └─ trailing[] ............. supplied by the workout app:
81
+ ├─ SessionStatePill .... molecule → Indicator + Typography (monoLabel)
82
+ └─ DeviceMenu .......... organism
83
+ ├─ Popover .......... titan
84
+ ├─ DeviceIndicator .. molecule → BluetoothIcon (icons primitive)
85
+ ├─ DeviceRow ........ molecule → Indicator + Typography (body2 / mono)
86
+ └─ Typography ....... titan · monoLabel (header)
87
+ ```
88
+
89
+ ## Dependency map — generic (`shell/`)
50
90
 
51
91
  | Component | Tier | Composes ↓ | Used-by ↑ |
52
92
  |---|---|---|---|
53
- | `SideNav` | organism | NavItem ×4, `components/icons` (Activity/History/Layers/PersonStanding) | dashboard app root *(planned)* |
93
+ | `AppShell` | page shell | TopBar, SideNav, Surface | WorkoutShell, any app root |
94
+ | `SideNav` | organism | NavItem × `items` | AppShell |
54
95
  | `NavItem` | molecule | icon, Typography | SideNav |
55
- | `TopBar` | organism | BrandLockup, SessionStatePill, Divider, DeviceMenu, DateTime, `surfaceGradient.chrome` | dashboard app root *(planned)* |
56
- | `BrandLockup` | molecule | VoltrasMark, Typography | TopBar |
57
- | `SessionStatePill` | molecule | Indicator, Typography | TopBar, **Live-view header** *(planned reuse)* |
58
- | `DeviceMenu` | organism | Popover, DeviceIndicator, DeviceRow, Typography | TopBar |
96
+ | `TopBar` | organism | BrandLockup, Divider, DateTime, `surfaceGradient.chrome` | AppShell, WorkoutTopBar |
97
+ | `BrandLockup` | molecule | a brand mark icon, Typography | TopBar |
98
+ | `brands.tsx` | data | VoltrasMark / Headphones / Kanban / Bot / Brain icons | BrandLockup, TopBar, AppShell |
99
+
100
+ ## Dependency map — workout app (`shell/workout/`)
101
+
102
+ | Component | Tier | Composes ↓ | Used-by ↑ |
103
+ |---|---|---|---|
104
+ | `WorkoutShell` | page shell | AppShell, WorkoutTopBar, workoutNavItems | dashboard app root |
105
+ | `WorkoutTopBar` | organism | TopBar, SessionStatePill, DeviceMenu | WorkoutShell |
106
+ | `SessionStatePill` | molecule | Indicator, Typography | WorkoutTopBar, **Live-view header** *(planned reuse)* |
107
+ | `DeviceMenu` | organism | Popover, DeviceIndicator, DeviceRow, Typography | WorkoutTopBar |
59
108
  | `DeviceIndicator` | molecule | BluetoothIcon | DeviceMenu |
60
109
  | `DeviceRow` | molecule | Indicator, Typography | DeviceMenu |
61
- | `VoltrasMark` / `BluetoothIcon` | **`components/icons`** primitive | `SvgIcon` base | BrandLockup / DeviceIndicator (+ any consumer) |
110
+ | `workoutNavItems` | data | Activity/History/Layers/PersonStanding icons | WorkoutShell |
62
111
 
63
112
  ## Shared substrates introduced here (reusable beyond the shell)
64
113
 
@@ -98,6 +147,20 @@ Every leaf now composes a primitive rather than hand-rolling it:
98
147
  - **Lint guardrails** — components may not inline `linear-gradient` (use `surfaceGradient`); shell + icons may
99
148
  not use raw hex (use tokens).
100
149
 
150
+ **AW-132 shared substrate:** four brand marks added to `components/icons` (`HeadphonesIcon`,
151
+ `KanbanIcon`, `BotIcon`, `BrainIcon`), so `brands.tsx` can carry a preset for every app expected to
152
+ mount this shell. Brand accents come from `data-*` rather than `status-*`: `data-*` is the library's
153
+ set of distinct, CVD-checked hues with no semantic load, which is what a per-app identity accent
154
+ needs. Voltras keeps the real `brand-primary` token.
155
+
156
+ **The nav accent is brand-level, not shell-level** (operator, Gate 2 2026-09-11). `SideNav`'s active
157
+ item and its edge bar take the mounting app's accent, so a Brain shell reads yellow throughout
158
+ rather than showing a Voltras-orange active item under a yellow lockup. `AppShell` threads it down
159
+ from `brand`; a bare `SideNav` takes `accentClassName` / `accentBarClassName`. Each preset declares
160
+ both as literals (`text-data-3` + `bg-data-3`) because Tailwind only emits classes it can see in the
161
+ source — a name built at runtime is never generated — and a test asserts the pair always matches.
162
+ Workout is unaffected: its accent *is* `brand-primary`.
163
+
101
164
  **S2 shared substrate:** four nav glyphs added to `components/icons` (`ActivityIcon`, `HistoryIcon`,
102
165
  `LayersIcon`, `PersonStandingIcon` — lucide-mirrored, like Dumbbell/Star), available system-wide.
103
166
 
@@ -3,15 +3,20 @@ import { useArgs } from 'storybook/preview-api'
3
3
  import { View } from 'react-native'
4
4
  import { SideNav } from './SideNav'
5
5
  import { Typography } from '../custom/Typography'
6
+ import { Surface } from '../ui/surface'
7
+ import { BotIcon, BrainIcon, LayersIcon } from '../icons'
8
+ import { workoutNavItems } from './workout'
9
+ import { brandPresets } from './brands'
6
10
 
7
11
  const meta: Meta<typeof SideNav> = {
8
12
  title: 'Shell/SideNav',
9
13
  component: SideNav,
10
- tags: ['autodocs'],
11
- args: { activeKey: 'live', liveKey: null },
14
+ tags: ['autodocs', 'status:candidate', '!status:review'],
15
+ args: { items: workoutNavItems, activeKey: 'live', liveKey: null },
12
16
  argTypes: {
13
17
  activeKey: { control: 'select', options: ['live', 'review', 'program', 'body'] },
14
18
  liveKey: { control: 'select', options: [null, 'live', 'review', 'program', 'body'] },
19
+ items: { control: false },
15
20
  onNavigate: { action: 'navigate' },
16
21
  },
17
22
  // Interactive AND Controls-synced: clicking a category writes `activeKey` back to the
@@ -35,14 +40,14 @@ const meta: Meta<typeof SideNav> = {
35
40
  // stretches with the viewport (resize the canvas height to see it hold).
36
41
  decorators: [
37
42
  (Story) => (
38
- <View className="min-h-screen flex-row bg-background-default">
43
+ <Surface level="base" className="min-h-screen flex-row">
39
44
  <Story />
40
45
  <View className="flex-1 items-center justify-center">
41
46
  <Typography variant="body2" color="tertiary">
42
47
  main viewport
43
48
  </Typography>
44
49
  </View>
45
- </View>
50
+ </Surface>
46
51
  ),
47
52
  ],
48
53
  parameters: {
@@ -50,9 +55,11 @@ const meta: Meta<typeof SideNav> = {
50
55
  docs: {
51
56
  description: {
52
57
  component:
53
- '**Organism** (shell region). The persistent 60px left rail switching Live · Review · Plan · ' +
54
- 'Body. Composes [NavItem](?path=/docs/shell-navitem--docs) × the four categories + the ' +
55
- 'shared [icon set](?path=/docs/foundations-icons--docs). Presentational drive it with `activeKey` / ' +
58
+ '**Organism** (shell region). The persistent 60px left rail that switches the main viewport ' +
59
+ "between an app's categories. Composes [NavItem](?path=/docs/shell-navitem--docs) × `items` + the " +
60
+ 'shared [icon set](?path=/docs/foundations-icons--docs). It has no built-in categories: `items` is ' +
61
+ 'required, and each app supplies its own (the workout set is `workoutNavItems`). ' +
62
+ 'Presentational — drive it with `activeKey` / ' +
56
63
  '`onNavigate` / `liveKey`. Active = left accent bar; `liveKey` (when not active) tints that ' +
57
64
  'label a muted green.\n\n' +
58
65
  '**Try it:** stories render in a full-height shell frame — the rail pins to the left edge and ' +
@@ -79,3 +86,27 @@ export const LiveElsewhere: Story = {
79
86
  },
80
87
  },
81
88
  }
89
+
90
+ /** A different app's categories and accent in the same rail (AW-132). */
91
+ export const AnotherApp: Story = {
92
+ args: {
93
+ items: [
94
+ { key: 'notes', label: 'Notes', icon: <LayersIcon size={20} color="currentColor" /> },
95
+ { key: 'graph', label: 'Graph', icon: <BrainIcon size={20} color="currentColor" /> },
96
+ { key: 'agents', label: 'Agents', icon: <BotIcon size={20} color="currentColor" /> },
97
+ ],
98
+ activeKey: 'graph',
99
+ accentClassName: brandPresets.brain.accentClassName,
100
+ accentBarClassName: brandPresets.brain.accentBarClassName,
101
+ },
102
+ parameters: {
103
+ docs: {
104
+ description: {
105
+ story:
106
+ "The brain app's three categories in its own accent. Same rail, same states, different " +
107
+ '`items` and accent. `AppShell` passes the accent down from its `brand`; drive the bare ' +
108
+ 'rail with `accentClassName` / `accentBarClassName`.',
109
+ },
110
+ },
111
+ },
112
+ }
@@ -1,11 +1,19 @@
1
1
  import { describe, it, expect, vi } from 'vitest'
2
2
  import { render, screen, fireEvent, within } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
- import { SideNav } from './SideNav'
4
+ import { SideNav, type SideNavItem } from './SideNav'
5
+
6
+ // A generic four-category set — the rail has no built-in categories any more.
7
+ const items: SideNavItem[] = [
8
+ { key: 'live', label: 'Live', icon: null },
9
+ { key: 'review', label: 'Review', icon: null },
10
+ { key: 'program', label: 'Plan', icon: null },
11
+ { key: 'body', label: 'Body', icon: null },
12
+ ]
5
13
 
6
14
  describe('SideNav', () => {
7
- it('renders the four default categories as tabs in a tablist', () => {
8
- render(<SideNav activeKey="live" />)
15
+ it('renders the supplied categories as tabs in a tablist', () => {
16
+ render(<SideNav items={items} activeKey="live" />)
9
17
  expect(screen.getByRole('tablist')).toBeInTheDocument()
10
18
  ;['Live', 'Review', 'Plan', 'Body'].forEach((name) => {
11
19
  expect(screen.getByRole('tab', { name })).toBeInTheDocument()
@@ -13,7 +21,7 @@ describe('SideNav', () => {
13
21
  })
14
22
 
15
23
  it('shows the accent bar only on the active category', () => {
16
- render(<SideNav activeKey="program" />)
24
+ render(<SideNav items={items} activeKey="program" />)
17
25
  expect(screen.getAllByTestId('nav-item-accent')).toHaveLength(1)
18
26
  expect(
19
27
  within(screen.getByRole('tab', { name: 'Plan' })).getByTestId('nav-item-accent')
@@ -25,13 +33,13 @@ describe('SideNav', () => {
25
33
 
26
34
  it('reports the tapped key via onNavigate', () => {
27
35
  const onNavigate = vi.fn()
28
- render(<SideNav activeKey="live" onNavigate={onNavigate} />)
36
+ render(<SideNav items={items} activeKey="live" onNavigate={onNavigate} />)
29
37
  fireEvent.click(screen.getByRole('tab', { name: 'Body' }))
30
38
  expect(onNavigate).toHaveBeenCalledWith('body')
31
39
  })
32
40
 
33
41
  it('keeps the active item as active even when it is also the liveKey (no double-state)', () => {
34
- render(<SideNav activeKey="live" liveKey="live" />)
42
+ render(<SideNav items={items} activeKey="live" liveKey="live" />)
35
43
  // active wins: exactly one accent bar, on Live (the live cue never applies to the active view)
36
44
  expect(screen.getAllByTestId('nav-item-accent')).toHaveLength(1)
37
45
  expect(
@@ -39,13 +47,13 @@ describe('SideNav', () => {
39
47
  ).toBeInTheDocument()
40
48
  })
41
49
 
42
- it('accepts custom items', () => {
50
+ it('accepts an app-specific item set', () => {
43
51
  render(<SideNav activeKey="a" items={[{ key: 'a', label: 'Alpha', icon: null }]} />)
44
52
  expect(screen.getByRole('tab', { name: 'Alpha' })).toBeInTheDocument()
45
53
  })
46
54
 
47
55
  it('has no accessibility violations', async () => {
48
- const { container } = render(<SideNav activeKey="live" />)
56
+ const { container } = render(<SideNav items={items} activeKey="live" />)
49
57
  expect(await axe(container)).toHaveNoViolations()
50
58
  })
51
59
  })