@titan-design/react-ui 0.12.1 → 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 +1319 -216
  7. package/dist/index.d.ts +1319 -216
  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 +195 -134
  21. package/dist/theme/index.d.ts +195 -134
  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 +73 -5
  31. package/dist/theme/tokens.d.ts +73 -5
  32. package/dist/theme/tokens.js +55 -7
  33. package/dist/theme/tokens.js.map +1 -1
  34. package/dist/theme/tokens.mjs +55 -8
  35. package/dist/theme/tokens.mjs.map +1 -1
  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 +1 -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 +11 -6
  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 +89 -26
  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 +23 -21
  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 +1 -3
  480. package/src/theme/tokens/primitives.test.ts +15 -7
  481. package/src/theme/tokens/primitives.ts +65 -33
  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
@@ -12,8 +12,21 @@ import {
12
12
  useSurfaceMode,
13
13
  } from './SurfaceContext'
14
14
  import { getSemanticColors } from '../../../theme/tokens/semantic'
15
+ import { greyRamp, primitiveColors } from '../../../theme/tokens/primitives'
15
16
  import { getPressedRecessShadow } from '../../../theme/elevation'
16
17
 
18
+ // Planes by RAMP STEP, never by literal bytes: a re-space of the grey ramp
19
+ // should move these expectations with it, not break them. Which step backs
20
+ // which plane is still asserted — that mapping is the behaviour.
21
+ const FRAME = greyRamp[975]
22
+ const BACKGROUND = greyRamp[950]
23
+ const BASE = greyRamp[925]
24
+ const ELEVATED = greyRamp[900]
25
+ const RAISED = greyRamp[875]
26
+ const OVERLAY = greyRamp[850]
27
+ const LIGHT_BASE = primitiveColors.white
28
+ const LIGHT_TEXT_PRIMARY = getSemanticColors('light')['text-primary']
29
+
17
30
  // A descendant probe that renders the on-surface colour + mode it resolves from
18
31
  // context, so tests can assert what a nested consumer would actually paint.
19
32
  function Probe({ role = 'primary' as const, label = 'probe' }) {
@@ -72,11 +85,56 @@ describe('Surface (card model)', () => {
72
85
  expect(screen.getByTestId('my-surface')).toBeInTheDocument()
73
86
  })
74
87
 
75
- it('paints the default flat dark card surface', () => {
88
+ it('sits flat on the inherited plane by default (dark base, no treatment)', () => {
76
89
  render(<Surface testID="s" />)
77
- // elevation 0 → base surface (surface-elevated) with no lightening.
78
- // TD-surface-tokens S-3 re-space: was #2A2827.
79
- expect(screen.getByTestId('s')).toHaveStyle({ backgroundColor: '#2C2A28' })
90
+ const el = screen.getByTestId('s')
91
+ expect(el).toHaveStyle({ backgroundColor: BASE })
92
+ expect(el.style.boxShadow).toBe('')
93
+ })
94
+
95
+ it('lifts onto ramp planes with rim + shadow: elevation 2 from the page is the card plane', () => {
96
+ render(<Surface elevation={2} testID="s" />)
97
+ const el = screen.getByTestId('s')
98
+ expect(el).toHaveStyle({ backgroundColor: RAISED })
99
+ expect(el.style.boxShadow).toContain('inset 0 1px 0')
100
+ expect(el.style.boxShadow).toMatch(/, 0 \d+px \d+px rgba\(0,0,0/)
101
+ })
102
+
103
+ it('raise is relative: one step up from an elevated host lands on raised', () => {
104
+ render(
105
+ <Surface level="elevated">
106
+ <Surface raise={1} testID="s" />
107
+ </Surface>
108
+ )
109
+ expect(screen.getByTestId('s')).toHaveStyle({ backgroundColor: RAISED })
110
+ })
111
+
112
+ it('clamps at overlay so raised-on-raised cannot leave the ramp', () => {
113
+ render(
114
+ <Surface level="overlay">
115
+ <Surface raise={2} testID="s" />
116
+ </Surface>
117
+ )
118
+ expect(screen.getByTestId('s')).toHaveStyle({ backgroundColor: OVERLAY })
119
+ })
120
+
121
+ it('floats on the overlay plane with a larger shadow and no ring', () => {
122
+ render(<Surface elevation={4} testID="s" />)
123
+ const el = screen.getByTestId('s')
124
+ expect(el).toHaveStyle({ backgroundColor: OVERLAY })
125
+ expect(el.style.boxShadow.split(', ').length).toBe(4)
126
+ expect(el.style.borderWidth).toBe('')
127
+ })
128
+
129
+ it('lets a caller switch the lift off on a raised plane, or on for a named one', () => {
130
+ render(
131
+ <>
132
+ <Surface raise={1} lift={false} testID="flat" />
133
+ <Surface level="raised" lift testID="lifted" />
134
+ </>
135
+ )
136
+ expect(screen.getByTestId('flat').style.boxShadow).toBe('')
137
+ expect(screen.getByTestId('lifted').style.boxShadow).toContain('inset 0 1px 0')
80
138
  })
81
139
 
82
140
  describe('accessibility', () => {
@@ -94,10 +152,10 @@ describe('Surface (card model)', () => {
94
152
 
95
153
  describe('Surface (named plane)', () => {
96
154
  it.each([
97
- ['background', '#1C1916'],
98
- ['base', '#252321'],
99
- ['elevated', '#2C2A28'],
100
- ['raised', '#31302F'],
155
+ ['background', BACKGROUND],
156
+ ['base', BASE],
157
+ ['elevated', ELEVATED],
158
+ ['raised', RAISED],
101
159
  ] as const)('maps level %s to the surface-ramp token %s', (level, hex) => {
102
160
  render(<Surface level={level} testID="s" />)
103
161
  expect(screen.getByTestId('s')).toHaveStyle({ backgroundColor: hex })
@@ -105,7 +163,7 @@ describe('Surface (named plane)', () => {
105
163
 
106
164
  it('resolves the light-mode background when theme is overridden', () => {
107
165
  render(<Surface level="base" theme="light" testID="s" />)
108
- expect(screen.getByTestId('s')).toHaveStyle({ backgroundColor: '#FFFFFF' })
166
+ expect(screen.getByTestId('s')).toHaveStyle({ backgroundColor: LIGHT_BASE })
109
167
  })
110
168
 
111
169
  it('lets a caller style override the owned background', () => {
@@ -134,12 +192,12 @@ function lstar(hex: string): number {
134
192
 
135
193
  describe('Surface (pressed well)', () => {
136
194
  it.each([
137
- // TD-surface-tokens S-3 re-space: top steps widened (elevated #2C2A28,
138
- // raised #31302F, overlay #373635); base/background unchanged.
139
- ['overlay', '#373635', '#31302F'], // → raised
140
- ['raised', '#31302F', '#2C2A28'], // → elevated
141
- ['elevated', '#2C2A28', '#252321'], // → base
142
- ['base', '#252321', '#1C1916'], // → background
195
+ // TD-surface-tokens S-3 re-space widened the top steps; base and
196
+ // background were unchanged. The pairs below are ramp-adjacent by index.
197
+ ['overlay', OVERLAY, RAISED], // → raised
198
+ ['raised', RAISED, ELEVATED], // → elevated
199
+ ['elevated', ELEVATED, BASE], // → base
200
+ ['base', BASE, BACKGROUND], // → background
143
201
  ] as const)(
144
202
  'in a %s parent renders one ramp step down (%s → %s), darker than its parent',
145
203
  (parent, parentHex, pressedHex) => {
@@ -155,7 +213,7 @@ describe('Surface (pressed well)', () => {
155
213
 
156
214
  it('with no enclosing Surface (default base) presses to the background plane', () => {
157
215
  render(<Surface pressed testID="well" />)
158
- expect(screen.getByTestId('well')).toHaveStyle({ backgroundColor: '#1C1916' })
216
+ expect(screen.getByTestId('well')).toHaveStyle({ backgroundColor: BACKGROUND })
159
217
  })
160
218
 
161
219
  it('clamps at the frame floor: pressed directly in background does not underflow', () => {
@@ -164,8 +222,8 @@ describe('Surface (pressed well)', () => {
164
222
  <Surface pressed testID="well" />
165
223
  </Surface>
166
224
  )
167
- // background (#1C1916) steps down to the frame floor (#100D0A), the bezel.
168
- expect(screen.getByTestId('well')).toHaveStyle({ backgroundColor: '#100D0A' })
225
+ // background steps down to the frame floor, the bezel.
226
+ expect(screen.getByTestId('well')).toHaveStyle({ backgroundColor: FRAME })
169
227
  })
170
228
 
171
229
  it('does not step below the floor: pressed within a floor-pressed well stays at frame', () => {
@@ -176,7 +234,7 @@ describe('Surface (pressed well)', () => {
176
234
  </Surface>
177
235
  </Surface>
178
236
  )
179
- expect(screen.getByTestId('deeper')).toHaveStyle({ backgroundColor: '#100D0A' })
237
+ expect(screen.getByTestId('deeper')).toHaveStyle({ backgroundColor: FRAME })
180
238
  })
181
239
 
182
240
  it('publishes the stepped-down level to descendants so a nested press steps again', () => {
@@ -209,8 +267,8 @@ describe('Surface (pressed well)', () => {
209
267
  <Surface pressed testID="well" style={{ boxShadow: undefined }} />
210
268
  </Surface>
211
269
  )
212
- expect(screen.getByTestId('well')).toHaveStyle({ backgroundColor: '#1C1916' })
213
- expect(lstar('#1C1916')).toBeLessThan(lstar('#252321'))
270
+ expect(screen.getByTestId('well')).toHaveStyle({ backgroundColor: BACKGROUND })
271
+ expect(lstar(BACKGROUND)).toBeLessThan(lstar(BASE))
214
272
  })
215
273
 
216
274
  it('rounds the well by default and honours an explicit rounded override', () => {
@@ -240,7 +298,7 @@ describe('pressedLevel helper', () => {
240
298
 
241
299
  describe('getPressedRecessShadow', () => {
242
300
  it('returns an inset recess tuned to the fill colour on the web path', () => {
243
- const style = getPressedRecessShadow('#1C1916', 'dark') as { boxShadow?: string }
301
+ const style = getPressedRecessShadow(BACKGROUND, 'dark') as { boxShadow?: string }
244
302
  expect(style.boxShadow).toContain('inset')
245
303
  })
246
304
  })
@@ -262,7 +320,9 @@ describe('Surface on-surface colour context', () => {
262
320
 
263
321
  it('resolves dark on-surface colours even with no enclosing Surface', () => {
264
322
  render(<Probe role="primary" label="p" />)
265
- expect(screen.getByTestId('p')).toHaveStyle({ color: getSemanticColors('dark')['text-primary'] })
323
+ expect(screen.getByTestId('p')).toHaveStyle({
324
+ color: getSemanticColors('dark')['text-primary'],
325
+ })
266
326
  })
267
327
 
268
328
  it('flows an overridden theme to descendant text', () => {
@@ -271,7 +331,7 @@ describe('Surface on-surface colour context', () => {
271
331
  <Probe role="primary" label="p" />
272
332
  </Surface>
273
333
  )
274
- expect(screen.getByTestId('p')).toHaveStyle({ color: '#121828' })
334
+ expect(screen.getByTestId('p')).toHaveStyle({ color: LIGHT_TEXT_PRIMARY })
275
335
  expect(screen.getByTestId('p')).toHaveTextContent('light')
276
336
  })
277
337
 
@@ -283,15 +343,15 @@ describe('Surface on-surface colour context', () => {
283
343
  </Surface>
284
344
  </Surface>
285
345
  )
286
- expect(screen.getByTestId('p')).toHaveStyle({ color: '#121828' })
346
+ expect(screen.getByTestId('p')).toHaveStyle({ color: LIGHT_TEXT_PRIMARY })
287
347
  })
288
348
  })
289
349
 
290
350
  describe('surface colour helpers', () => {
291
351
  it('surfaceBackground returns literal hex per level + mode', () => {
292
- expect(surfaceBackground('elevated', 'dark')).toBe('#2C2A28')
293
- expect(surfaceBackground('background', 'dark')).toBe('#1C1916')
294
- expect(surfaceBackground('base', 'light')).toBe('#FFFFFF')
352
+ expect(surfaceBackground('elevated', 'dark')).toBe(ELEVATED)
353
+ expect(surfaceBackground('background', 'dark')).toBe(BACKGROUND)
354
+ expect(surfaceBackground('base', 'light')).toBe(LIGHT_BASE)
295
355
  })
296
356
 
297
357
  it('resolves the frame floor from its own semantic token', () => {
@@ -1,57 +1,14 @@
1
1
  import React, { useMemo } from 'react'
2
- import { View, type ViewProps, type ViewStyle } from 'react-native'
2
+ import { View, type ViewProps } from 'react-native'
3
3
  import { cn } from '../../../utils/cn'
4
- import { type ThemeMode } from '../../../theme/tokens/semantic'
5
- import {
6
- type ElevationLevel,
7
- getElevationSurface,
8
- getElevationShadow,
9
- getPressedRecessShadow,
10
- getBaseSurfaceColor,
11
- type GlowIntensity,
12
- getGlowShadow,
13
- } from '../../../theme/elevation'
14
- import {
15
- SurfaceContext,
16
- surfaceBackground,
17
- pressedLevel,
18
- useSurface,
19
- type SurfaceContextValue,
20
- type SurfaceLevel,
21
- } from './SurfaceContext'
4
+ import { type GlowIntensity, getGlowShadow } from '../../../theme/elevation'
5
+ import { SurfaceContext, type SurfaceContextValue } from './SurfaceContext'
6
+ import { useResolvedSurface, type SurfaceDepthProps } from './resolveSurface'
22
7
 
23
- export interface SurfaceProps extends ViewProps {
24
- /**
25
- * Numeric depth → a computed lighten + shadow (the raised-card model). The
26
- * background is derived from the base surface by lightening. Ignored for the
27
- * background when `level` is set. Default 0 (flat card).
28
- */
29
- elevation?: ElevationLevel
30
- /**
31
- * Named grey plane (shell / rail / stage). Sets the background directly
32
- * from a semantic surface token — reaching the darker nav/page planes the
33
- * lighten model can't — and drops the default rounding + auto shadow so the
34
- * plane reads full-bleed. Composable with `elevation`'s glow.
35
- */
36
- level?: SurfaceLevel
37
- /**
38
- * Sunken "well": render ONE ramp step DOWN from the parent surface's level —
39
- * the symmetric twin of a raised surface stepping up — and add an inner-shadow
40
- * recess so the surface reads pressed (darker fill + inset shadow). Relative to
41
- * the enclosing Surface's level (not this surface's own `level`), and clamped
42
- * at the ramp's `inset` floor so it never underflows. One level of depth only.
43
- */
44
- pressed?: boolean
8
+ export interface SurfaceProps extends ViewProps, SurfaceDepthProps {
45
9
  glowColor?: string
46
10
  glowIntensity?: GlowIntensity
47
- /**
48
- * Theme mode. Also seeds the on-surface colour CONTEXT so descendant text /
49
- * icons resolve their colour from this surface instead of a `text-*` className
50
- * that silently fails to black on raw RN. Inherits the nearest Surface /
51
- * ThemeProvider when omitted (default dark — the wall).
52
- */
53
- theme?: ThemeMode
54
- /** Round the corners. Defaults on for the card model, off for a `level` plane. */
11
+ /** Round the corners. Defaults on for a lifted or pressed surface, off for an absolute plane. */
55
12
  rounded?: boolean
56
13
  className?: string
57
14
  children?: React.ReactNode
@@ -59,17 +16,23 @@ export interface SurfaceProps extends ViewProps {
59
16
 
60
17
  /**
61
18
  * A container that OWNS its background and establishes an on-surface colour
62
- * context. Two grounded ways to pick the background:
63
- * - `elevation` (numeric): the raised-card model lighten-from-base + shadow.
64
- * - `level` (named): a flat grey plane straight from a semantic token,
65
- * for the shell / rail / stage backgrounds.
66
- * Either way, descendants read the surface via {@link useOnSurfaceColor} /
67
- * {@link useSurfaceMode} rather than hard-coding `getSemanticColors('dark')`.
19
+ * context. Depth is relative to the enclosing Surface:
20
+ * - `raise` / positive `elevation`: step up the ramp and wear the lift
21
+ * treatment (rim-light + ambient shadow). Clamps at `overlay`.
22
+ * - `pressed` / negative `elevation`: step down with an inner-shadow recess.
23
+ * - `level` (absolute): a flat grey plane straight from a token, for shell
24
+ * roots only (shell = background, page = base). No lift.
25
+ * - nothing: sit flat on the inherited plane.
26
+ * Descendants read the surface via {@link useOnSurfaceColor} / {@link useSurfaceMode}
27
+ * rather than hard-coding `getSemanticColors('dark')`.
68
28
  */
69
29
  export function Surface({
70
- elevation = 0,
30
+ elevation,
71
31
  level,
32
+ raise,
72
33
  pressed = false,
34
+ lift,
35
+ liftOptions,
73
36
  glowColor,
74
37
  glowIntensity,
75
38
  theme,
@@ -79,39 +42,21 @@ export function Surface({
79
42
  children,
80
43
  ...props
81
44
  }: SurfaceProps) {
82
- const inherited = useSurface()
83
- const mode = theme ?? inherited.mode
84
- const isPlane = level != null
85
-
86
- // Three grounded ways to own a background, in precedence order:
87
- // - pressed: a well ONE ramp step DOWN from the parent level (relative),
88
- // with an inner-shadow recess — clamps at the `inset` floor.
89
- // - level (named plane): a flat grey plane straight from a token.
90
- // - elevation (numeric): the raised-card model — lighten-from-base + shadow.
91
- const baseColor = getBaseSurfaceColor(mode)
92
- const resolvedLevel = pressed ? pressedLevel(inherited.level) : (level ?? inherited.level)
93
-
94
- let backgroundColor: string
95
- let shadowStyle: ViewStyle
96
- if (pressed) {
97
- backgroundColor = surfaceBackground(resolvedLevel, mode)
98
- shadowStyle = getPressedRecessShadow(backgroundColor, mode)
99
- } else if (isPlane) {
100
- backgroundColor = surfaceBackground(level, mode)
101
- // Planes own a flat full-bleed background — no depth shadow.
102
- shadowStyle = {}
103
- } else {
104
- backgroundColor = getElevationSurface(baseColor, elevation, mode)
105
- shadowStyle = getElevationShadow(baseColor, elevation, mode)
106
- }
107
-
45
+ const resolved = useResolvedSurface({
46
+ elevation,
47
+ level,
48
+ raise,
49
+ pressed,
50
+ lift,
51
+ liftOptions,
52
+ theme,
53
+ })
108
54
  const glowStyle = glowColor ? getGlowShadow(glowColor, glowIntensity) : {}
109
- // A pressed well and the card model round by default; a flat plane doesn't.
110
- const applyRounded = rounded ?? (pressed || !isPlane)
55
+ const applyRounded = rounded ?? (pressed || level == null)
111
56
 
112
57
  const value = useMemo<SurfaceContextValue>(
113
- () => ({ mode, level: resolvedLevel }),
114
- [mode, resolvedLevel]
58
+ () => ({ mode: resolved.mode, level: resolved.plane }),
59
+ [resolved.mode, resolved.plane]
115
60
  )
116
61
 
117
62
  return (
@@ -119,7 +64,12 @@ export function Surface({
119
64
  <View
120
65
  className={cn(applyRounded && 'rounded-2xl', className)}
121
66
  // backgroundColor first so a caller `style` can still override it.
122
- style={[{ backgroundColor }, shadowStyle, glowStyle, style]}
67
+ style={[
68
+ { backgroundColor: resolved.backgroundColor },
69
+ resolved.depthStyle,
70
+ glowStyle,
71
+ style,
72
+ ]}
123
73
  {...props}
124
74
  >
125
75
  {children}
@@ -1,52 +1,31 @@
1
1
  // On-surface colour context (TD-05.12).
2
2
  //
3
- // A <Surface> owns a background from a small elevation scale AND publishes the
4
- // current theme mode so descendant text/icons resolve their colour from "what
5
- // surface am I on" instead of a `text-*` className. Those classNames silently
6
- // fail to black when the tree renders as raw RN in the standalone wall SPA
7
- // (no global.css, no nativewind) the bug class this primitive retires.
3
+ // A <Surface> owns a background from the surface ramp AND publishes the current
4
+ // theme mode so descendant text/icons resolve their colour from "what surface am
5
+ // I on" instead of a `text-*` className. Those classNames silently fail to black
6
+ // when the tree renders as raw RN in the standalone wall SPA (no global.css, no
7
+ // nativewind), which is the bug class this primitive retires.
8
8
  import { createContext, useContext } from 'react'
9
9
  import { getSemanticColors, type ThemeMode } from '../../../theme/tokens/semantic'
10
+ import type { SurfaceLevel } from '../../../theme/surface-planes'
10
11
 
11
- type ColorToken = keyof ReturnType<typeof getSemanticColors>
12
+ // The plane vocabulary lives in the theme so `elevation.ts` can resolve levels
13
+ // to planes without importing a component. Re-exported here for callers that
14
+ // reached it through the Surface module.
15
+ export {
16
+ SURFACE_LEVEL_TOKEN,
17
+ PLANE_ORDER,
18
+ surfaceBackground,
19
+ pressedLevel,
20
+ raisedLevel,
21
+ type SurfaceLevel,
22
+ } from '../../../theme/surface-planes'
12
23
 
13
- /**
14
- * A Surface's depth on the dark surface ramp. Each addressable level maps to an
15
- * EXISTING semantic surface/background token (already grounded in the derived
16
- * surface ramp — TD-surface-tokens S-1 — and theme-aware) — Surface introduces
17
- * no new hexes. Darkest → lightest:
18
- * inset (#13100D) < background (#1C1916) < base (#252321) < elevated (#2A2827) < raised (#2D2C2B) < overlay (#302F2E)
19
- *
20
- * `frame` is the bezel the ramp sits inside — the top bar / side nav chrome —
21
- * and the floor a `<Surface pressed>` clamps at (see {@link pressedLevel}).
22
- *
23
- * It replaced a separate `inset` level in TD-07.14. That level was RETIRED, not
24
- * renamed: at ΔE 1.10 from the frame it was an imperceptible duplicate, which is
25
- * exactly why pressed (surface − 1) kept collapsing into it.
26
- */
27
- export type SurfaceLevel = 'frame' | 'background' | 'base' | 'elevated' | 'raised' | 'overlay'
24
+ type ColorToken = keyof ReturnType<typeof getSemanticColors>
28
25
 
29
26
  /** On-surface neutral text roles, resolved for the current surface + theme. */
30
27
  export type OnSurfaceRole = 'primary' | 'secondary' | 'tertiary'
31
28
 
32
- // Which semantic token backs each level. Values live in `semantic.ts` and stay
33
- // in sync with the grey ramp — this map never carries literal hexes. Every level
34
- // is present now: swapping `inset` (which had no token) for `frame` (which does)
35
- // is what made this a total map and removed `surfaceBackground`'s special case.
36
- export const SURFACE_LEVEL_TOKEN = {
37
- frame: 'background-frame',
38
- background: 'background-base',
39
- base: 'surface-base',
40
- elevated: 'surface-elevated',
41
- raised: 'surface-raised',
42
- overlay: 'surface-overlay',
43
- } as const satisfies Record<SurfaceLevel, ColorToken>
44
-
45
- // Darkest → lightest. A `<Surface pressed>` renders one index DOWN from its
46
- // parent's level and clamps at `frame` (index 0). Kept in lockstep with the grey
47
- // ramp and the surface.contract.test PLANE_ORDER.
48
- const PRESSED_RAMP = ['frame', 'background', 'base', 'elevated', 'raised', 'overlay'] as const
49
-
50
29
  const ON_SURFACE_TOKEN = {
51
30
  primary: 'text-primary',
52
31
  secondary: 'text-secondary',
@@ -56,7 +35,7 @@ const ON_SURFACE_TOKEN = {
56
35
  export interface SurfaceContextValue {
57
36
  /** Active theme mode. Defaults to dark (the wall). */
58
37
  mode: ThemeMode
59
- /** Depth of the nearest enclosing Surface. */
38
+ /** Plane of the nearest enclosing Surface. */
60
39
  level: SurfaceLevel
61
40
  }
62
41
 
@@ -78,9 +57,9 @@ export function useSurfaceMode(): ThemeMode {
78
57
 
79
58
  /**
80
59
  * Literal-hex on-surface neutral text colours for a theme mode. Uses
81
- * `getSemanticColors` (literal hex — the TempoBar pattern), never `resolveColor`
82
- * (which returns `var()` under the RNW vitest alias), so values stay real in
83
- * tested component code and on raw-RN surfaces alike.
60
+ * `getSemanticColors` (literal hex), never `resolveColor` (which returns `var()`
61
+ * under the RNW vitest alias), so values stay real in tested component code and
62
+ * on raw-RN surfaces alike.
84
63
  */
85
64
  export function onSurfaceColors(mode: ThemeMode): Record<OnSurfaceRole, string> {
86
65
  const c = getSemanticColors(mode)
@@ -95,22 +74,3 @@ export function onSurfaceColors(mode: ThemeMode): Record<OnSurfaceRole, string>
95
74
  export function useOnSurfaceColor(role: OnSurfaceRole = 'primary'): string {
96
75
  return getSemanticColors(useSurfaceMode())[ON_SURFACE_TOKEN[role]]
97
76
  }
98
-
99
- /** The background hex for a surface level under a theme mode (literal hex). */
100
- export function surfaceBackground(level: SurfaceLevel, mode: ThemeMode): string {
101
- return getSemanticColors(mode)[SURFACE_LEVEL_TOKEN[level]]
102
- }
103
-
104
- /**
105
- * The level a `<Surface pressed>` resolves to: its parent's level stepped one
106
- * index DOWN the ramp — the symmetric twin of a raised surface stepping up —
107
- * clamped at the `frame` floor so a pressed surface never underflows the bezel.
108
- * overlay→raised, raised→elevated, elevated→base, base→background,
109
- * background→frame, frame→frame (clamped).
110
- * Pressed exposes ONE level of depth only (no pressed-2).
111
- */
112
- export function pressedLevel(parent: SurfaceLevel): SurfaceLevel {
113
- const index = PRESSED_RAMP.indexOf(parent)
114
- const down = Math.max(0, index - 1)
115
- return PRESSED_RAMP[down]
116
- }
@@ -1,4 +1,10 @@
1
1
  export { Surface, type SurfaceProps } from './Surface'
2
+ export {
3
+ resolveSurfaceDepth,
4
+ useResolvedSurface,
5
+ type SurfaceDepthProps,
6
+ type ResolvedSurface,
7
+ } from './resolveSurface'
2
8
  export {
3
9
  SurfaceContext,
4
10
  useSurface,
@@ -7,7 +13,9 @@ export {
7
13
  onSurfaceColors,
8
14
  surfaceBackground,
9
15
  pressedLevel,
16
+ raisedLevel,
10
17
  SURFACE_LEVEL_TOKEN,
18
+ PLANE_ORDER,
11
19
  type SurfaceLevel,
12
20
  type OnSurfaceRole,
13
21
  type SurfaceContextValue,
@@ -0,0 +1,98 @@
1
+ // How a container decides which plane it sits on and what it wears there.
2
+ //
3
+ // One resolution path shared by <Surface> and <Card>, so the two can never
4
+ // disagree about depth. Everything is RELATIVE to the enclosing Surface unless
5
+ // an absolute `level` names a shell root.
6
+ import { useMemo } from 'react'
7
+ import type { ViewStyle } from 'react-native'
8
+ import {
9
+ FLOATING_ELEVATION_MIN,
10
+ getPressedRecessShadow,
11
+ type ElevationLevel,
12
+ } from '../../../theme/elevation'
13
+ import { liftStyle, type LiftOptions, type LiftStep } from '../../../theme/lift'
14
+ import {
15
+ pressedLevel,
16
+ raisedLevel,
17
+ surfaceBackground,
18
+ type SurfaceLevel,
19
+ } from '../../../theme/surface-planes'
20
+ import type { ThemeMode } from '../../../theme/tokens/semantic'
21
+ import { useSurface, type SurfaceContextValue } from './SurfaceContext'
22
+
23
+ export interface SurfaceDepthProps {
24
+ /** Absolute plane, for shell roots (shell = background, page = base). No lift. */
25
+ level?: SurfaceLevel
26
+ /** Planes to step UP from the enclosing Surface, with the lift treatment. */
27
+ raise?: 1 | 2 | 3
28
+ /**
29
+ * Numeric depth relative to the enclosing Surface: negative recesses, 0 sits
30
+ * flat on the same plane, 1–3 lift, 4–5 float on the overlay plane.
31
+ */
32
+ elevation?: ElevationLevel
33
+ /** One plane DOWN with an inner-shadow recess. */
34
+ pressed?: boolean
35
+ /** Force the lift treatment on or off regardless of the step taken. */
36
+ lift?: boolean
37
+ /** Tune the lift (rim alpha). Specimens and hero surfaces only. */
38
+ liftOptions?: LiftOptions
39
+ /** Override the inherited theme mode. */
40
+ theme?: ThemeMode
41
+ }
42
+
43
+ export interface ResolvedSurface {
44
+ mode: ThemeMode
45
+ plane: SurfaceLevel
46
+ backgroundColor: string
47
+ /** Recess or lift, to compose after `backgroundColor`. */
48
+ depthStyle: ViewStyle
49
+ /** Planes stepped up (0 when flat, pressed, or an absolute plane). */
50
+ step: number
51
+ lifted: boolean
52
+ }
53
+
54
+ function recessDepth(inherited: SurfaceContextValue, planesDown: number, mode: ThemeMode) {
55
+ let plane = inherited.level
56
+ for (let i = 0; i < planesDown; i++) plane = pressedLevel(plane)
57
+ const backgroundColor = surfaceBackground(plane, mode)
58
+ return { plane, backgroundColor, depthStyle: getPressedRecessShadow(backgroundColor, mode) }
59
+ }
60
+
61
+ /** Pure resolution: the plane, its hex, and the treatment. */
62
+ export function resolveSurfaceDepth(
63
+ inherited: SurfaceContextValue,
64
+ props: SurfaceDepthProps
65
+ ): ResolvedSurface {
66
+ const mode = props.theme ?? inherited.mode
67
+ const elevation = props.elevation ?? 0
68
+ if (props.pressed || elevation < 0) {
69
+ const down = props.pressed ? 1 : -elevation
70
+ return { mode, step: 0, lifted: false, ...recessDepth(inherited, down, mode) }
71
+ }
72
+ const step = props.level != null ? 0 : (props.raise ?? elevation)
73
+ const floating = step >= FLOATING_ELEVATION_MIN
74
+ const plane = props.level ?? (floating ? 'overlay' : raisedLevel(inherited.level, step))
75
+ const lifted = props.lift ?? step > 0
76
+ const liftStep = Math.max(1, Math.min(5, step)) as LiftStep
77
+ const depthStyle = lifted ? liftStyle(liftStep, mode, props.liftOptions) : {}
78
+ return { mode, plane, backgroundColor: surfaceBackground(plane, mode), depthStyle, step, lifted }
79
+ }
80
+
81
+ /** The resolved depth of a container at this point in the tree. */
82
+ export function useResolvedSurface(props: SurfaceDepthProps): ResolvedSurface {
83
+ const inherited = useSurface()
84
+ const { level, raise, elevation, pressed, lift, liftOptions, theme } = props
85
+ return useMemo(
86
+ () =>
87
+ resolveSurfaceDepth(inherited, {
88
+ level,
89
+ raise,
90
+ elevation,
91
+ pressed,
92
+ lift,
93
+ liftOptions,
94
+ theme,
95
+ }),
96
+ [inherited, level, raise, elevation, pressed, lift, liftOptions, theme]
97
+ )
98
+ }
@@ -8,7 +8,8 @@ import { getSemanticColors } from '../../../theme/tokens/semantic'
8
8
  * palette locks its eligibility mask with a value-level test.
9
9
  *
10
10
  * Scope: DARK mode only. The north-star diagnosis
11
- * (sources/design/surface-system-north-star.md) measured and
11
+ * (`sources/design/surface-system-north-star.md`, in the `voltras-workspace`
12
+ * active-work initiative rather than this repo) measured and
12
13
  * fixed the dark ramp specifically; light mode has its own latent collisions
13
14
  * (e.g. `surface-overlay` === `surface-base`, both `#FFFFFF`) that were never
14
15
  * part of this investigation — flagged as a follow-up, not silently patched
@@ -91,6 +92,10 @@ describe('surface ramp contract (dark) — token-value guardrails', () => {
91
92
  // Locked values from surface-system-north-star.md (re-spaced S-3) — see
92
93
  // surfaceRampDark in primitives.ts for the derivation this must match
93
94
  // byte-for-byte. background/base are unchanged by the re-space.
95
+ //
96
+ // The literal hexes are the POINT of this one test: it pins the bytes, so a
97
+ // re-space has to come here and say so. Every other test in the suite
98
+ // references the ramp instead.
94
99
  expect(dark['background-frame']).toBe('#100D0A')
95
100
  expect(dark['background-base']).toBe('#1C1916')
96
101
  expect(dark['surface-base']).toBe('#252321')
@@ -5,7 +5,7 @@ import { Switch } from './Switch'
5
5
  const meta: Meta<typeof Switch> = {
6
6
  title: 'Components/Molecules/Switch',
7
7
  component: Switch,
8
- tags: ['autodocs'],
8
+ tags: ['autodocs', 'status:stable', '!status:review'],
9
9
  argTypes: {
10
10
  isChecked: {
11
11
  control: 'boolean',