@titan-design/react-ui 0.12.1 → 0.14.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 (531) hide show
  1. package/README.md +60 -47
  2. package/dist/bodymap.js +856 -736
  3. package/dist/bodymap.js.map +1 -1
  4. package/dist/bodymap.mjs +858 -738
  5. package/dist/bodymap.mjs.map +1 -1
  6. package/dist/index.d.mts +1431 -237
  7. package/dist/index.d.ts +1431 -237
  8. package/dist/index.js +6506 -4781
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +6280 -4642
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{pages-B1hqnGIC.d.ts → pages-CsjsUn0G.d.ts} +51 -16
  13. package/dist/{pages-CvsFqNtx.d.mts → pages-ZbT_pD_U.d.mts} +51 -16
  14. package/dist/pages.d.mts +1 -1
  15. package/dist/pages.d.ts +1 -1
  16. package/dist/pages.js +1919 -1937
  17. package/dist/pages.js.map +1 -1
  18. package/dist/pages.mjs +1927 -1945
  19. package/dist/pages.mjs.map +1 -1
  20. package/dist/theme/index.d.mts +225 -140
  21. package/dist/theme/index.d.ts +225 -140
  22. package/dist/theme/index.js +298 -319
  23. package/dist/theme/index.js.map +1 -1
  24. package/dist/theme/index.mjs +283 -315
  25. package/dist/theme/index.mjs.map +1 -1
  26. package/dist/theme/tokens-css.js +145 -24
  27. package/dist/theme/tokens-css.js.map +1 -1
  28. package/dist/theme/tokens-css.mjs +145 -24
  29. package/dist/theme/tokens-css.mjs.map +1 -1
  30. package/dist/theme/tokens.d.mts +128 -11
  31. package/dist/theme/tokens.d.ts +128 -11
  32. package/dist/theme/tokens.js +126 -26
  33. package/dist/theme/tokens.js.map +1 -1
  34. package/dist/theme/tokens.mjs +124 -27
  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/docs/render-testing-pattern.md +130 -0
  40. package/package.json +1 -1
  41. package/src/arch/Architecture.stories.tsx +3 -1
  42. package/src/arch/arch-graph.freshness.test.ts +52 -0
  43. package/src/arch/arch-graph.json +4989 -1583
  44. package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
  45. package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
  46. package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
  47. package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
  48. package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
  49. package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
  50. package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
  51. package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
  52. package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
  53. package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
  54. package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
  55. package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
  56. package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
  57. package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
  58. package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
  59. package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
  60. package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
  61. package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
  62. package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
  63. package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
  64. package/src/components/custom/ActiveWork/InitiativeBrief.tsx +182 -0
  65. package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
  66. package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
  67. package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
  68. package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
  69. package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
  70. package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
  71. package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
  72. package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
  73. package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
  74. package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
  75. package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
  76. package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
  77. package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
  78. package/src/components/custom/ActiveWork/README.md +305 -0
  79. package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
  80. package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
  81. package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
  82. package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
  83. package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
  84. package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
  85. package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
  86. package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
  87. package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
  88. package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
  89. package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
  90. package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
  91. package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
  92. package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
  93. package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
  94. package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
  95. package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
  96. package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
  97. package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
  98. package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
  99. package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
  100. package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
  101. package/src/components/custom/ActiveWork/format-time.ts +32 -0
  102. package/src/components/custom/ActiveWork/index.ts +19 -0
  103. package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
  104. package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
  105. package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
  106. package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
  107. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +12 -6
  108. package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
  109. package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
  110. package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
  111. package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
  112. package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
  113. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
  114. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  115. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
  116. package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
  117. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
  118. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
  119. package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
  120. package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
  121. package/src/components/custom/Fatigue/README.md +40 -3
  122. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  123. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
  124. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
  125. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  126. package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
  127. package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
  128. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
  129. package/src/components/custom/Fatigue/index.ts +17 -5
  130. package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
  131. package/src/components/custom/Fatigue/panel-layout.ts +185 -0
  132. package/src/components/custom/Gauge/Gauge.stories.tsx +6 -3
  133. package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
  134. package/src/components/custom/Gauge/Gauge.tsx +7 -2
  135. package/src/components/custom/Metric/Metric.stories.tsx +10 -1
  136. package/src/components/custom/Metric/Metric.test.tsx +1 -3
  137. package/src/components/custom/Metric/Metric.tsx +5 -20
  138. package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
  139. package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
  140. package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
  141. package/src/components/custom/Prose/README.md +58 -0
  142. package/src/components/custom/Prose/index.ts +1 -0
  143. package/src/components/custom/Scatter/Scatter.stories.tsx +55 -14
  144. package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
  145. package/src/components/custom/Scatter/Scatter.tsx +122 -18
  146. package/src/components/custom/Sidebar/Sidebar.stories.tsx +41 -8
  147. package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
  148. package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
  149. package/src/components/custom/Table/Table.stories.tsx +99 -16
  150. package/src/components/custom/Table/Table.test.tsx +273 -86
  151. package/src/components/custom/Table/Table.tsx +159 -67
  152. package/src/components/custom/Table/column-fit.test.ts +75 -0
  153. package/src/components/custom/Table/column-fit.ts +116 -0
  154. package/src/components/custom/Table/index.ts +9 -0
  155. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
  156. package/src/components/custom/Treemap/Treemap.stories.tsx +15 -12
  157. package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
  158. package/src/components/custom/Treemap/Treemap.tsx +11 -3
  159. package/src/components/custom/Typography/Typography.stories.tsx +28 -1
  160. package/src/components/custom/Typography/Typography.test.tsx +34 -0
  161. package/src/components/custom/Typography/Typography.tsx +6 -0
  162. package/src/components/custom/Typography/index.ts +1 -8
  163. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +11 -2
  164. package/src/components/custom/Workout/BaseBadge.stories.tsx +27 -11
  165. package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
  166. package/src/components/custom/Workout/BaseBadge.tsx +6 -2
  167. package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
  168. package/src/components/custom/Workout/BodyMap.tsx +8 -4
  169. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +28 -6
  170. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
  171. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +50 -12
  172. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +12 -9
  173. package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
  174. package/src/components/custom/Workout/CapacityBandChart.tsx +144 -111
  175. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  176. package/src/components/custom/Workout/DeviationBar.test.tsx +10 -4
  177. package/src/components/custom/Workout/DeviationBar.tsx +24 -16
  178. package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +183 -0
  179. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +32 -18
  180. package/src/components/custom/Workout/ExerciseCard.stories.tsx +12 -11
  181. package/src/components/custom/Workout/ExerciseCard.test.tsx +52 -22
  182. package/src/components/custom/Workout/ExerciseCard.tsx +99 -183
  183. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +95 -12
  184. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
  185. package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
  186. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
  187. package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
  188. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
  189. package/src/components/custom/Workout/ExerciseHeading.tsx +194 -46
  190. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +12 -9
  191. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
  192. package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
  193. package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
  194. package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
  195. package/src/components/custom/Workout/InputBar.tsx +4 -2
  196. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  197. package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
  198. package/src/components/custom/Workout/IntensityBar.tsx +8 -3
  199. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
  200. package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
  201. package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
  202. package/src/components/custom/Workout/MesoCard.tsx +14 -10
  203. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  204. package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
  205. package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
  206. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
  207. package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
  208. package/src/components/custom/Workout/MesoStatusCard.tsx +20 -33
  209. package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
  210. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
  211. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
  212. package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
  213. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +9 -4
  214. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
  215. package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
  216. package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
  217. package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
  218. package/src/components/custom/Workout/PrBadge.tsx +13 -17
  219. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +16 -3
  220. package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
  221. package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
  222. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
  223. package/src/components/custom/Workout/README.md +68 -17
  224. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
  225. package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
  226. package/src/components/custom/Workout/ReadinessCheck.tsx +22 -4
  227. package/src/components/custom/Workout/RestTimer.stories.tsx +16 -4
  228. package/src/components/custom/Workout/RestTimer.tsx +4 -2
  229. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
  230. package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
  231. package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
  232. package/src/components/custom/Workout/SegmentedBar.stories.tsx +21 -17
  233. package/src/components/custom/Workout/SegmentedBar.tsx +10 -5
  234. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
  235. package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
  236. package/src/components/custom/Workout/SessionHeader.tsx +12 -12
  237. package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
  238. package/src/components/custom/Workout/SessionRail.tsx +7 -9
  239. package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
  240. package/src/components/custom/Workout/SetBar.test.tsx +49 -4
  241. package/src/components/custom/Workout/SetBar.tsx +38 -12
  242. package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
  243. package/src/components/custom/Workout/SetRow.test.tsx +16 -2
  244. package/src/components/custom/Workout/SetRow.tsx +61 -23
  245. package/src/components/custom/Workout/SetStrip.stories.tsx +63 -7
  246. package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
  247. package/src/components/custom/Workout/SetStrip.tsx +24 -4
  248. package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
  249. package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
  250. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
  251. package/src/components/custom/Workout/SetsRepsLoad.tsx +24 -12
  252. package/src/components/custom/Workout/Sparkline.stories.tsx +14 -11
  253. package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
  254. package/src/components/custom/Workout/Sparkline.tsx +14 -18
  255. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  256. package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
  257. package/src/components/custom/Workout/StatusDot.tsx +93 -63
  258. package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
  259. package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
  260. package/src/components/custom/Workout/StatusPill.tsx +27 -38
  261. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +11 -4
  262. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
  263. package/src/components/custom/Workout/StrengthTrendChart.tsx +39 -36
  264. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
  265. package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
  266. package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
  267. package/src/components/custom/Workout/TempoDisplay.stories.tsx +26 -10
  268. package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
  269. package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
  270. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
  271. package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
  272. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +10 -6
  273. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +5 -2
  274. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +44 -1
  275. package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
  276. package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
  277. package/src/components/custom/Workout/VelocityStrip.tsx +26 -14
  278. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
  279. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
  280. package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
  281. package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
  282. package/src/components/custom/Workout/WeekRow.tsx +23 -16
  283. package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
  284. package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
  285. package/src/components/custom/Workout/WeightBadge.tsx +28 -27
  286. package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
  287. package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
  288. package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
  289. package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
  290. package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
  291. package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
  292. package/src/components/custom/Workout/ZoneTrack.stories.tsx +10 -7
  293. package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
  294. package/src/components/custom/Workout/exerciseRowState.ts +29 -0
  295. package/src/components/custom/Workout/index.ts +17 -2
  296. package/src/components/custom/Workout/metricText.stories.tsx +15 -13
  297. package/src/components/custom/Workout/metricText.tsx +8 -2
  298. package/src/components/custom/Workout/setHeadingKit.tsx +219 -36
  299. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  300. package/src/components/custom/charts/README.md +66 -0
  301. package/src/components/custom/charts/SetBarChart.test.tsx +46 -1
  302. package/src/components/custom/charts/SetBarChart.tsx +56 -9
  303. package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
  304. package/src/components/custom/charts/SparkBars.test.tsx +69 -0
  305. package/src/components/custom/charts/SparkBars.tsx +85 -0
  306. package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
  307. package/src/components/custom/charts/flatBarGeometry.ts +32 -0
  308. package/src/components/custom/charts/index.ts +3 -0
  309. package/src/components/custom/index.ts +3 -0
  310. package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
  311. package/src/components/custom/stepper/Stepper.test.tsx +1 -7
  312. package/src/components/custom/stepper/Stepper.tsx +21 -34
  313. package/src/components/icons/icons.stories.tsx +1 -1
  314. package/src/components/icons/icons.tsx +51 -0
  315. package/src/components/shell/AppShell.stories.tsx +150 -0
  316. package/src/components/shell/AppShell.test.tsx +70 -0
  317. package/src/components/shell/AppShell.tsx +90 -0
  318. package/src/components/shell/BrandLockup.stories.tsx +66 -5
  319. package/src/components/shell/BrandLockup.test.tsx +34 -1
  320. package/src/components/shell/BrandLockup.tsx +27 -11
  321. package/src/components/shell/NavItem.stories.tsx +4 -4
  322. package/src/components/shell/NavItem.test.tsx +17 -0
  323. package/src/components/shell/NavItem.tsx +17 -5
  324. package/src/components/shell/README.md +89 -26
  325. package/src/components/shell/SideNav.stories.tsx +38 -7
  326. package/src/components/shell/SideNav.test.tsx +16 -8
  327. package/src/components/shell/SideNav.tsx +17 -17
  328. package/src/components/shell/TopBar.stories.tsx +44 -19
  329. package/src/components/shell/TopBar.test.tsx +34 -12
  330. package/src/components/shell/TopBar.tsx +61 -40
  331. package/src/components/shell/brands.tsx +66 -0
  332. package/src/components/shell/index.ts +9 -8
  333. package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
  334. package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
  335. package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
  336. package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
  337. package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
  338. package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
  339. package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
  340. package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
  341. package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
  342. package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
  343. package/src/components/shell/workout/WorkoutShell.tsx +77 -0
  344. package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
  345. package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
  346. package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
  347. package/src/components/shell/workout/index.ts +9 -0
  348. package/src/components/shell/workout/workoutNavItems.tsx +16 -0
  349. package/src/components/ui/README.md +110 -0
  350. package/src/components/ui/alert/Alert.stories.tsx +15 -5
  351. package/src/components/ui/alert/Alert.tsx +24 -3
  352. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
  353. package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
  354. package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
  355. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  356. package/src/components/ui/avatar/Avatar.tsx +2 -10
  357. package/src/components/ui/badge/Badge.stories.tsx +83 -28
  358. package/src/components/ui/badge/Badge.test.tsx +19 -3
  359. package/src/components/ui/badge/Badge.tsx +48 -55
  360. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  361. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
  362. package/src/components/ui/button/Button.stories.tsx +46 -12
  363. package/src/components/ui/button/Button.tsx +44 -37
  364. package/src/components/ui/card/Card.stories.tsx +90 -94
  365. package/src/components/ui/card/Card.test.tsx +80 -6
  366. package/src/components/ui/card/Card.tsx +150 -203
  367. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  368. package/src/components/ui/checkbox/Checkbox.tsx +7 -27
  369. package/src/components/ui/chip/Chip.stories.tsx +37 -13
  370. package/src/components/ui/chip/Chip.test.tsx +10 -6
  371. package/src/components/ui/chip/Chip.tsx +76 -91
  372. package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
  373. package/src/components/ui/collapse/Collapse.tsx +6 -11
  374. package/src/components/ui/data-row/DataRow.stories.tsx +14 -3
  375. package/src/components/ui/data-row/DataRow.test.tsx +5 -15
  376. package/src/components/ui/data-row/DataRow.tsx +18 -7
  377. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  378. package/src/components/ui/divider/Divider.tsx +1 -5
  379. package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
  380. package/src/components/ui/drawer/Drawer.tsx +13 -27
  381. package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
  382. package/src/components/ui/form-field/FormField.stories.tsx +6 -20
  383. package/src/components/ui/form-field/FormField.test.tsx +1 -5
  384. package/src/components/ui/form-field/FormField.tsx +10 -46
  385. package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
  386. package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
  387. package/src/components/ui/help-tip/HelpTip.tsx +21 -22
  388. package/src/components/ui/help-tip/index.ts +7 -1
  389. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  390. package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
  391. package/src/components/ui/icon-box/IconBox.tsx +10 -18
  392. package/src/components/ui/indicator/Indicator.stories.tsx +5 -4
  393. package/src/components/ui/indicator/Indicator.tsx +20 -14
  394. package/src/components/ui/input/Input.stories.tsx +3 -11
  395. package/src/components/ui/input/Input.test.tsx +2 -10
  396. package/src/components/ui/input/Input.tsx +10 -25
  397. package/src/components/ui/input/index.ts +7 -1
  398. package/src/components/ui/link/Link.stories.tsx +19 -7
  399. package/src/components/ui/list-item/ListItem.stories.tsx +17 -5
  400. package/src/components/ui/list-item/ListItem.tsx +2 -9
  401. package/src/components/ui/menu/Menu.stories.tsx +21 -15
  402. package/src/components/ui/menu/Menu.test.tsx +2 -11
  403. package/src/components/ui/menu/Menu.tsx +12 -14
  404. package/src/components/ui/menu/index.ts +8 -1
  405. package/src/components/ui/modal/Modal.stories.tsx +2 -2
  406. package/src/components/ui/modal/Modal.tsx +31 -19
  407. package/src/components/ui/pill/Pill.stories.tsx +111 -23
  408. package/src/components/ui/pill/Pill.test.tsx +101 -3
  409. package/src/components/ui/pill/Pill.tsx +168 -49
  410. package/src/components/ui/pill/index.ts +1 -1
  411. package/src/components/ui/popover/Popover.stories.tsx +6 -8
  412. package/src/components/ui/popover/Popover.test.tsx +1 -6
  413. package/src/components/ui/popover/Popover.tsx +20 -15
  414. package/src/components/ui/popover/index.ts +7 -1
  415. package/src/components/ui/progress/Progress.stories.tsx +3 -12
  416. package/src/components/ui/progress/Progress.test.tsx +1 -5
  417. package/src/components/ui/progress/Progress.tsx +9 -6
  418. package/src/components/ui/radio/Radio.stories.tsx +42 -21
  419. package/src/components/ui/radio/Radio.test.tsx +6 -2
  420. package/src/components/ui/radio/Radio.tsx +23 -22
  421. package/src/components/ui/section/Section.stories.tsx +12 -21
  422. package/src/components/ui/section/Section.test.tsx +1 -6
  423. package/src/components/ui/section/Section.tsx +1 -3
  424. package/src/components/ui/section/index.ts +1 -5
  425. package/src/components/ui/select/Select.stories.tsx +3 -13
  426. package/src/components/ui/select/Select.test.tsx +2 -11
  427. package/src/components/ui/select/Select.tsx +16 -15
  428. package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
  429. package/src/components/ui/skeleton/Skeleton.tsx +3 -14
  430. package/src/components/ui/skeleton/index.ts +1 -7
  431. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  432. package/src/components/ui/spinner/Spinner.tsx +4 -5
  433. package/src/components/ui/stack/Stack.stories.tsx +8 -8
  434. package/src/components/ui/stack/Stack.tsx +1 -1
  435. package/src/components/ui/surface/Surface.stories.tsx +92 -33
  436. package/src/components/ui/surface/Surface.test.tsx +88 -28
  437. package/src/components/ui/surface/Surface.tsx +37 -87
  438. package/src/components/ui/surface/SurfaceContext.ts +22 -62
  439. package/src/components/ui/surface/index.ts +8 -0
  440. package/src/components/ui/surface/resolveSurface.ts +98 -0
  441. package/src/components/ui/surface/surface.contract.test.ts +6 -1
  442. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  443. package/src/components/ui/switch/Switch.tsx +7 -6
  444. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  445. package/src/components/ui/tabs/Tabs.tsx +8 -21
  446. package/src/components/ui/tabs/index.ts +1 -7
  447. package/src/components/ui/tile/Tile.stories.tsx +1 -1
  448. package/src/components/ui/tile/Tile.tsx +3 -0
  449. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  450. package/src/components/ui/toast/Toast.test.tsx +2 -8
  451. package/src/components/ui/toast/Toast.tsx +49 -57
  452. package/src/components/ui/toast/index.ts +0 -1
  453. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
  454. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
  455. package/src/components/ui/toolbar-button/ToolbarButton.tsx +38 -49
  456. package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
  457. package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
  458. package/src/components/ui/tooltip/Tooltip.tsx +55 -36
  459. package/src/components/ui/tooltip/index.ts +1 -1
  460. package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
  461. package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
  462. package/src/components/ui/trigger/index.ts +2 -0
  463. package/src/stories/PresetShowcase.stories.tsx +101 -48
  464. package/src/test/no-device-internals.test.ts +3 -3
  465. package/src/test/no-raw-color.test.ts +39 -0
  466. package/src/theme/Alpha.stories.tsx +103 -0
  467. package/src/theme/ChoosingTokens.stories.tsx +48 -0
  468. package/src/theme/ColorPalettes.stories.tsx +62 -45
  469. package/src/theme/ColorPrimitives.stories.tsx +35 -35
  470. package/src/theme/Depth.stories.tsx +132 -65
  471. package/src/theme/DepthCalibration.stories.tsx +23 -21
  472. package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
  473. package/src/theme/ThemeProvider.test.tsx +4 -4
  474. package/src/theme/barrel.ts +7 -6
  475. package/src/theme/color-stories.coverage.test.ts +1 -1
  476. package/src/theme/color-utils.ts +41 -29
  477. package/src/theme/config.completeness.test.ts +1 -1
  478. package/src/theme/config.ts +40 -0
  479. package/src/theme/elevation.test.ts +82 -56
  480. package/src/theme/elevation.ts +77 -561
  481. package/src/theme/global.css +45 -5
  482. package/src/theme/grey-ramp.test.ts +24 -8
  483. package/src/theme/lift.test.ts +39 -0
  484. package/src/theme/lift.ts +102 -0
  485. package/src/theme/materials.test.ts +5 -2
  486. package/src/theme/materials.ts +38 -4
  487. package/src/theme/native-theming-guard.test.ts +5 -5
  488. package/src/theme/presets/apply.ts +4 -0
  489. package/src/theme/presets/audiobook.ts +5 -2
  490. package/src/theme/presets/default.ts +4 -0
  491. package/src/theme/presets/index.ts +14 -1
  492. package/src/theme/presets/types.ts +1 -1
  493. package/src/theme/semantic-contrast.test.ts +13 -4
  494. package/src/theme/status-distinctness.test.ts +66 -0
  495. package/src/theme/subtle-tone-weight.test.ts +163 -0
  496. package/src/theme/surface-planes.test.ts +37 -0
  497. package/src/theme/surface-planes.ts +68 -0
  498. package/src/theme/tailwind-var-opacity.test.ts +163 -0
  499. package/src/theme/token-export-guard.test.ts +1 -3
  500. package/src/theme/tokens/primitives.test.ts +15 -7
  501. package/src/theme/tokens/primitives.ts +98 -33
  502. package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
  503. package/src/theme/tokens/semantic.ts +124 -54
  504. package/src/theme/wash-ladder.test.ts +58 -0
  505. package/src/theme/workout-tokens.ts +1 -7
  506. package/src/types/jest-axe.d.ts +2 -6
  507. package/src/types/raw-md.d.ts +5 -0
  508. package/src/utils/colors.alpha-adoption.test.ts +408 -0
  509. package/src/utils/colors.ts +4 -4
  510. package/src/utils/form.ts +1 -3
  511. package/src/utils/index.ts +2 -7
  512. package/src/utils/number-format.ts +17 -0
  513. package/src/utils/workout-format.ts +15 -6
  514. package/src/web-jsx/jsx-runtime.ts +1 -5
  515. package/tailwind.config.js +55 -9
  516. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
  517. package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
  518. package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
  519. package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
  520. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
  521. package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
  522. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
  523. package/src/components/shell/DashboardShell.stories.tsx +0 -65
  524. package/src/components/shell/DashboardShell.tsx +0 -75
  525. package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
  526. package/src/theme/elevation.stories.tsx +0 -346
  527. package/src/utils/useTheme.ts +0 -72
  528. /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
  529. /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
  530. /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
  531. /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
@@ -1,600 +1,116 @@
1
1
  /**
2
- * Elevation System
3
- *
4
- * A unified depth system where each elevation level corresponds to:
5
- * - Calculated surface color (derived from base using lighten)
6
- * - Shadow intensity and style (raised/pressed)
7
- * - Dynamically calculated shadow colors (derived from surface color)
8
- * - Semantic name for reference
9
- *
10
- * Elevation range: -2 (deep inset) to +5 (floating overlay)
11
- *
12
- * Negative elevations: Inset/pressed elements (sunken into surface)
13
- * Zero elevation: Base surface (background/page level)
14
- * Positive elevations: Raised elements (floating above surface)
15
- *
16
- * All colors are calculated dynamically from a base surface color,
17
- * allowing for custom surfaces and consistent color relationships.
18
- *
19
- * Lighting Model: Light source from upper-right corner
20
- * - Raised elements are always lighter than base (consistent across themes)
21
- * - Base colors must allow sufficient lightening in both themes
2
+ * Elevation — numeric depth that resolves to the grey ramp.
3
+ *
4
+ * A level is a plane on the surface ramp plus the treatment that plane wears:
5
+ *
6
+ * -2 frame recessed (inset well, two planes down)
7
+ * -1 background recessed (inset well, one plane down)
8
+ * 0 base flat, no treatment
9
+ * 1 elevated lifted (tone + rim-light + ambient shadow)
10
+ * 2 raised lifted
11
+ * 3 overlay lifted
12
+ * 4 overlay floating (same recipe, larger shadow)
13
+ * 5 overlay floating
14
+ *
15
+ * These are ABSOLUTE readings from the page plane. `<Surface>` and `<Card>`
16
+ * apply the same numbers RELATIVE to the plane they are nested in; see
17
+ * `resolveSurfaceDepth`.
18
+ *
19
+ * No level can produce a colour off the ramp. The previous ladder lightened a
20
+ * base colour in HSV by 2.5% per level and put five of its six stops between
21
+ * ramp steps, drifting warmer as it rose while the ramp is designed to cool.
22
+ * Floating levels share the overlay plane on purpose: past the top of the ramp,
23
+ * separation is the shadow's job, not a lighter grey.
24
+ *
25
+ * Depth is TONE + LIFT by default. The earlier rule that levels 1–3 separate by
26
+ * tone alone (TD-07.16) was reversed on 2026-09-08: the tight upper steps of the
27
+ * ramp (ΔL* 2.5–3) need the second channel, and a soft ambient shadow on a
28
+ * near-black plane is not inert once the rim-light gives it an edge to fall
29
+ * from. See Foundations/Depth.
22
30
  */
23
-
24
31
  import { Platform, type ViewStyle } from 'react-native'
25
- import { lighten, darken, hexToRgb, rgbToHsv, hsvToRgb, rgbToHex } from './color-utils'
26
- import { semanticColorsLight, semanticColorsDark } from './tokens/semantic'
32
+ import { hexToRgb } from './color-utils'
33
+ import { insetWell } from './materials'
34
+ import { FLOATING_LIFT_MIN, liftStyle, type LiftOptions, type LiftStep } from './lift'
35
+ import { surfaceBackground, type SurfaceLevel } from './surface-planes'
36
+ import type { ThemeMode } from './tokens/semantic'
27
37
 
28
38
  export type ElevationLevel = -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5
29
39
 
30
- export interface ElevationConfig {
31
- /** Semantic name for this elevation level */
32
- name: string
33
- /** Amount to lighten base color (0-1) */
34
- colorAdjustment: number
35
- /** Shadow intensity */
36
- shadowIntensity: 'subtle' | 'medium' | 'strong'
37
- /** Shadow style: raised (floating) or pressed (inset) */
38
- shadowStyle: 'raised' | 'pressed'
39
- /** Description of use case */
40
- description: string
41
- }
42
-
43
- export const elevationSystem: Record<ElevationLevel, ElevationConfig> = {
44
- // Deep inset (recessed input fields, deeply pressed buttons)
45
- [-2]: {
46
- name: 'deep-inset',
47
- colorAdjustment: 0,
48
- shadowIntensity: 'strong',
49
- shadowStyle: 'pressed',
50
- description: 'Deeply recessed elements (e.g., input fields, deeply pressed buttons)',
51
- },
52
-
53
- // Shallow inset (pressed buttons, sunken panels)
54
- [-1]: {
55
- name: 'inset',
56
- colorAdjustment: 0,
57
- shadowIntensity: 'medium',
58
- shadowStyle: 'pressed',
59
- description: 'Shallow inset elements (e.g., pressed buttons, sunken panels within cards)',
60
- },
61
-
62
- // Base level (page background, default surface)
63
- [0]: {
64
- name: 'base',
65
- colorAdjustment: 0,
66
- shadowIntensity: 'subtle',
67
- shadowStyle: 'raised',
68
- description: 'Base surface level (page background, default containers)',
69
- },
70
-
71
- // Level 1: Subtle elevation (outline cards, subtle containers)
72
- [1]: {
73
- name: 'subtle',
74
- // The whole ladder was re-spaced in TD-07.16. It used to run
75
- // 0.01/0.02/0.04/0.06/0.08, where the first step was ~2 RGB values — under
76
- // the perceptual floor, so level 1 existed in the type system and nowhere on
77
- // screen. With drop-shadow now gated off below FLOATING_ELEVATION_MIN, TONE
78
- // is the only thing separating 0-3, so every step has to actually land.
79
- // Even 0.025 spacing, verified in elevation.test.ts as ΔL* > 1 per step.
80
- colorAdjustment: 0.025,
81
- shadowIntensity: 'subtle',
82
- shadowStyle: 'raised',
83
- description: 'Subtle elevation (outline cards, subtle containers)',
84
- },
85
-
86
- // Level 2: Standard elevation (default cards, buttons)
87
- [2]: {
88
- name: 'standard',
89
- colorAdjustment: 0.05,
90
- shadowIntensity: 'medium',
91
- shadowStyle: 'raised',
92
- description: 'Standard elevation (default cards, raised buttons)',
93
- },
94
-
95
- // Level 3: Prominent elevation (important cards, modals)
96
- [3]: {
97
- name: 'prominent',
98
- colorAdjustment: 0.075,
99
- shadowIntensity: 'strong',
100
- shadowStyle: 'raised',
101
- description: 'Prominent elevation (important cards, modals, dropdowns)',
102
- },
103
-
104
- // Level 4: High elevation (overlays, popovers)
105
- [4]: {
106
- name: 'high',
107
- colorAdjustment: 0.10,
108
- shadowIntensity: 'strong',
109
- shadowStyle: 'raised',
110
- description: 'High elevation (overlays, popovers, tooltips)',
111
- },
112
-
113
- // Level 5: Floating (toasts, floating action buttons)
114
- [5]: {
115
- name: 'floating',
116
- colorAdjustment: 0.13,
117
- shadowIntensity: 'strong',
118
- shadowStyle: 'raised',
119
- description: 'Floating elevation (toasts, floating action buttons, highest z-index)',
120
- },
121
- }
122
-
123
- /**
124
- * Calculate rim light color from the BASE color.
125
- * Creates a consistent bright highlight with opacity that scales with elevation.
126
- *
127
- * The rim light represents light hitting the edge of raised elements from a
128
- * consistent light source. Uses ABSOLUTE HSV value (not relative lightening)
129
- * for consistent intensity across all surface colors.
130
- *
131
- * For colored surfaces: preserves hue/saturation, sets value to fixed bright level
132
- * For gray surfaces: uses white with appropriate opacity
133
- *
134
- * @param baseColor - The base surface color (hex)
135
- * @param level - Elevation level (used to scale opacity)
136
- */
137
- function calculateRimLightColor(baseColor: string, level: ElevationLevel): string {
138
- const rgb = hexToRgb(baseColor)
139
- if (!rgb) {
140
- return 'rgba(255, 255, 255, 0.5)'
141
- }
142
-
143
- const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b)
144
- const isLight = hsv.v > 0.6
145
- const isSaturated = hsv.s > 0.3 // Colored (not gray)
146
-
147
- // Opacity scales with elevation to compensate for lighter surfaces at higher levels
148
- // Range: +0% at level 1 to +20% at level 5
149
- const absLevel = Math.abs(level)
150
- const opacityBoost = Math.max(0, (absLevel - 1) * 0.05) // +5% per level above 1 (0% to 20%)
151
-
152
- if (isSaturated) {
153
- // For colored surfaces, preserve hue and saturation but set value to fixed bright level
154
- // This ensures consistent rim light intensity across all colored surfaces
155
- const rimLightValue = 0.95 // Fixed bright value for rim light
156
- const rimLightRgb = hsvToRgb(hsv.h, hsv.s * 0.3, rimLightValue) // Reduce saturation for brighter appearance
157
-
158
- // Base opacity + elevation boost, capped at 1.0
159
- const baseOpacity = isLight ? 0.9 : 0.5
160
- const finalOpacity = Math.min(1, baseOpacity + opacityBoost)
161
- return `rgba(${rimLightRgb.r}, ${rimLightRgb.g}, ${rimLightRgb.b}, ${finalOpacity.toFixed(2)})`
162
- } else {
163
- // For gray/neutral surfaces, use white with appropriate opacity
164
- const baseOpacity = isLight ? 0.9 : 0.12
165
- const finalOpacity = Math.min(1, baseOpacity + opacityBoost)
166
- return `rgba(255, 255, 255, ${finalOpacity.toFixed(2)})`
167
- }
168
- }
169
-
170
- /**
171
- * Calculate dark shadow colors from the surface color.
172
- * The dark shadow is cast BY the element, so it should match the surface color.
173
- *
174
- * Uses ABSOLUTE HSV value (not relative darkening) for consistent shadow intensity
175
- * across all surface colors - simulating a consistent light source.
176
- *
177
- * For colored surfaces: preserves hue/saturation, sets value to fixed dark level
178
- * For gray surfaces: uses black with appropriate opacity
179
- */
180
- function calculateDarkShadowColors(surfaceColor: string): {
181
- dark: string
182
- darker: string
183
- } {
184
- const rgb = hexToRgb(surfaceColor)
185
- if (!rgb) {
186
- return {
187
- dark: 'rgba(0, 0, 0, 0.3)',
188
- darker: 'rgba(0, 0, 0, 0.4)',
189
- }
190
- }
191
-
192
- const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b)
193
- const isLight = hsv.v > 0.6
194
- const isSaturated = hsv.s > 0.3
195
-
196
- if (isSaturated) {
197
- // For colored surfaces, preserve hue and saturation but set value to fixed dark level
198
- // This ensures consistent shadow intensity across all colored surfaces
199
- const darkShadowValue = 0.15 // Fixed dark value for shadows
200
- const darkShadowRgb = hsvToRgb(hsv.h, hsv.s, darkShadowValue)
201
-
202
- if (isLight) {
203
- return {
204
- dark: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.3)`,
205
- darker: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.4)`,
206
- }
207
- } else {
208
- return {
209
- dark: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.5)`,
210
- darker: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.6)`,
211
- }
212
- }
213
- } else {
214
- // For gray surfaces, use standard black shadows
215
- if (isLight) {
216
- return {
217
- dark: 'rgba(0, 0, 0, 0.2)',
218
- darker: 'rgba(0, 0, 0, 0.3)',
219
- }
220
- } else {
221
- return {
222
- dark: 'rgba(0, 0, 0, 0.5)',
223
- darker: 'rgba(0, 0, 0, 0.6)',
224
- }
225
- }
226
- }
40
+ export const ELEVATION_PLANE: Record<ElevationLevel, SurfaceLevel> = {
41
+ [-2]: 'frame',
42
+ [-1]: 'background',
43
+ [0]: 'base',
44
+ [1]: 'elevated',
45
+ [2]: 'raised',
46
+ [3]: 'overlay',
47
+ [4]: 'overlay',
48
+ [5]: 'overlay',
227
49
  }
228
50
 
229
- /**
230
- * Get rim light size config.
231
- * Fixed 1px offset and blur for a consistent, crisp rim highlight.
232
- * Opacity scaling (in calculateRimLightColor) handles elevation differences.
233
- */
234
- function getRimLightConfig(): { offset: number; blur: number } {
235
- return {
236
- offset: 1, // Fixed 1px for crisp rim
237
- blur: 1, // Tight blur for crisp edge
238
- }
239
- }
240
-
241
- /**
242
- * Get elevation configuration for a given level
243
- */
244
- export function getElevationConfig(level: ElevationLevel): ElevationConfig {
245
- return elevationSystem[level]
246
- }
51
+ /** The lowest elevation that is floating (menu, popover, toast) rather than content. */
52
+ export const FLOATING_ELEVATION_MIN: ElevationLevel = FLOATING_LIFT_MIN
247
53
 
248
- // Simple cache for elevation surface colors
249
- const elevationSurfaceCache = new Map<string, string>()
250
- const MAX_CACHE_SIZE = 100
54
+ /** Elevation backing a `<Surface pressed>` recess: one plane down. */
55
+ export const PRESSED_ELEVATION_LEVEL: ElevationLevel = -1
251
56
 
252
- function getCacheKey(baseColor: string, level: ElevationLevel, theme: 'light' | 'dark'): string {
253
- return `${baseColor}-${level}-${theme}`
57
+ /** The ramp plane an absolute elevation sits on. */
58
+ export function elevationPlane(level: ElevationLevel): SurfaceLevel {
59
+ return ELEVATION_PLANE[level]
254
60
  }
255
61
 
256
- function calculateElevationSurface(
257
- baseColor: string,
258
- level: ElevationLevel,
259
- theme: 'light' | 'dark'
260
- ): string {
261
- const config = elevationSystem[level]
262
-
263
- if (config.colorAdjustment === 0) {
264
- return baseColor
265
- }
266
-
267
- // For raised elevations (positive), always lighten (consistent lighting model)
268
- // Light source is from upper-right, so raised elements are always lighter
269
- // For inset elevations (negative), we keep base color (shadow creates depth)
270
- if (level < 0) {
271
- return baseColor // Inset elements use base color
272
- }
273
-
274
- // Always lighten for raised elements (consistent across themes)
275
- // Base colors must be chosen to allow sufficient lightening in dark mode
276
- return lighten(baseColor, config.colorAdjustment)
62
+ /** The background hex for an absolute elevation: always a ramp step. */
63
+ export function getElevationSurface(level: ElevationLevel, mode: ThemeMode = 'dark'): string {
64
+ return surfaceBackground(ELEVATION_PLANE[level], mode)
277
65
  }
278
66
 
279
67
  /**
280
- * Calculate surface color for an elevation level from a base color.
281
- * Results are cached to avoid redundant calculations.
282
- *
283
- * @param baseColor - Base surface color (hex)
284
- * @param level - Elevation level
285
- * @param theme - Current theme ('light' or 'dark')
286
- * @returns Calculated surface color (hex)
68
+ * Inner-shadow recess for a pressed (sunken) surface: the `insetWell` material's
69
+ * cut, which the wall calibrated in VW-99. Web only; on native the recess reads
70
+ * from the darker fill alone, which is why the fill is never optional.
287
71
  */
288
- export function getElevationSurface(
289
- baseColor: string,
290
- level: ElevationLevel,
291
- theme: 'light' | 'dark'
292
- ): string {
293
- const cacheKey = getCacheKey(baseColor, level, theme)
294
-
295
- // Check cache
296
- if (elevationSurfaceCache.has(cacheKey)) {
297
- return elevationSurfaceCache.get(cacheKey)!
298
- }
299
-
300
- // Calculate
301
- const result = calculateElevationSurface(baseColor, level, theme)
302
-
303
- // Cache result (with size limit to prevent memory leaks)
304
- if (elevationSurfaceCache.size >= MAX_CACHE_SIZE) {
305
- // Remove oldest entry (simple FIFO)
306
- const firstKey = elevationSurfaceCache.keys().next().value!
307
- elevationSurfaceCache.delete(firstKey)
308
- }
309
- elevationSurfaceCache.set(cacheKey, result)
310
-
311
- return result
312
- }
313
-
314
- // Cache for shadow styles (less frequently used, smaller cache)
315
- const elevationShadowCache = new Map<string, ViewStyle>()
316
- const MAX_SHADOW_CACHE_SIZE = 50
317
-
318
- function getShadowCacheKey(
319
- baseColor: string,
320
- level: ElevationLevel,
321
- theme: 'light' | 'dark',
322
- isHovered: boolean
323
- ): string {
324
- return `${baseColor}-${level}-${theme}-${isHovered}`
325
- }
326
-
327
- function calculateElevationShadow(
328
- baseColor: string,
329
- level: ElevationLevel,
330
- theme: 'light' | 'dark',
331
- isHovered: boolean = false
332
- ): ViewStyle {
333
- const config = elevationSystem[level]
334
-
335
- // Base level has no shadow
336
- if (level === 0) {
337
- return {}
338
- }
339
-
340
- // Calculate the actual elevated surface color
341
- const elevatedSurfaceColor = calculateElevationSurface(baseColor, level, theme)
342
-
343
- // Rim light: calculated from BASE color (consistent across all elevations)
344
- // This represents a consistent light source - only size changes with elevation
345
- const rimLightColor = calculateRimLightColor(baseColor, level)
346
-
347
- // Dark shadow: calculated from ELEVATED surface color
348
- // The shadow is cast by the element, so it should match the surface
349
- const darkShadowColors = calculateDarkShadowColors(elevatedSurfaceColor)
350
-
351
- // Get shadow intensity config (for dark shadow)
352
- const intensityConfig = {
353
- subtle: { offset: 1, blur: 2 },
354
- medium: { offset: 2, blur: 4 },
355
- strong: { offset: 2, blur: 5 },
356
- }
357
-
358
- const { offset, blur } = intensityConfig[config.shadowIntensity]
359
-
360
- // Get rim light config (scales with elevation level)
361
- const rimConfig = getRimLightConfig()
362
-
363
- if (config.shadowStyle === 'pressed') {
364
- // Inset shadow (pressed elements)
365
- if (isHovered) {
366
- // Reduce shadow depth for hover
367
- const hoverOffset = Math.max(1, Math.round(offset / 2))
368
- const hoverBlur = Math.max(1, Math.round(blur / 2))
369
- return Platform.select({
370
- web: {
371
- boxShadow: `inset ${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${darkShadowColors.darker}, inset -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`,
372
- } as any,
373
- default: {
374
- shadowOpacity: 0,
375
- elevation: 0,
376
- },
377
- }) as ViewStyle
378
- }
379
-
380
- return Platform.select({
381
- web: {
382
- boxShadow: `inset ${offset}px ${offset}px ${blur}px ${darkShadowColors.darker}, inset -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`,
383
- } as any,
384
- default: {
385
- shadowOpacity: 0,
386
- elevation: 0,
387
- },
388
- }) as ViewStyle
389
- } else {
390
- // Raised shadow (floating elements)
391
- if (isHovered) {
392
- // Reduce shadow depth for hover
393
- const hoverOffset = Math.max(1, Math.round(offset / 2))
394
- const hoverBlur = Math.max(1, Math.round(blur / 2))
395
- return Platform.select({
396
- web: {
397
- boxShadow: `${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${darkShadowColors.dark}, -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`,
398
- } as any,
399
- default: {
400
- shadowColor: '#000',
401
- shadowOffset: { width: hoverOffset, height: hoverOffset },
402
- shadowOpacity: 0.2,
403
- shadowRadius: hoverBlur / 2,
404
- elevation: hoverOffset,
405
- },
406
- }) as ViewStyle
407
- }
408
-
409
- return Platform.select({
410
- web: {
411
- boxShadow: `${offset}px ${offset}px ${blur}px ${darkShadowColors.dark}, -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`,
412
- } as any,
413
- default: {
414
- shadowColor: '#000',
415
- shadowOffset: { width: offset, height: offset },
416
- shadowOpacity: 0.3,
417
- shadowRadius: blur / 2,
418
- elevation: offset,
419
- },
420
- }) as ViewStyle
421
- }
72
+ export function getPressedRecessShadow(fillColor: string, _mode: ThemeMode = 'dark'): ViewStyle {
73
+ const { boxShadow } = insetWell(fillColor) as unknown as { boxShadow: string }
74
+ return Platform.select({
75
+ web: { boxShadow } as unknown as ViewStyle,
76
+ default: {},
77
+ }) as ViewStyle
422
78
  }
423
79
 
424
80
  /**
425
- * Get shadow style for an elevation level, calculated dynamically from surface color.
426
- * Results are cached to avoid redundant calculations.
427
- *
428
- * @param baseColor - Base surface color (hex)
429
- * @param level - Elevation level
430
- * @param theme - Current theme ('light' or 'dark')
431
- * @param isHovered - Whether element is hovered
432
- * @returns ViewStyle with shadow properties
433
- */
434
- /**
435
- * The lowest elevation that still casts an outward shadow.
436
- *
437
- * On a near-black surface a drop-shadow is inert: at levels 1-3 the shadow is
438
- * dark-on-dark and contributes nothing a viewer can see, while still costing a
439
- * composite layer. Material dropped shadow-as-hierarchy for the same reason.
440
- * Inline hierarchy is the hairline's job (`hairline-*`), which composites by a
441
- * constant amount over any plane.
442
- *
443
- * At 4+ the element is genuinely FLOATING over a backdrop — menu, popover,
444
- * toast — where a large soft shadow describes separation from the page rather
445
- * than a rank in a stack. Those should also carry a hairline ring.
81
+ * The treatment an absolute elevation wears: a recess below 0, nothing at 0,
82
+ * a lift above it. Compose after `backgroundColor`.
446
83
  */
447
- export const FLOATING_ELEVATION_MIN: ElevationLevel = 4
448
-
449
84
  export function getElevationShadow(
450
- baseColor: string,
451
85
  level: ElevationLevel,
452
- theme: 'light' | 'dark',
453
- isHovered: boolean = false
86
+ mode: ThemeMode = 'dark',
87
+ opts: LiftOptions = {}
454
88
  ): ViewStyle {
455
- const cacheKey = getShadowCacheKey(baseColor, level, theme, isHovered)
456
-
457
- // Check cache
458
- if (elevationShadowCache.has(cacheKey)) {
459
- return elevationShadowCache.get(cacheKey)!
460
- }
461
-
462
- // Calculate shadow. Outward drop-shadow is gated to FLOATING levels only —
463
- // see FLOATING_ELEVATION_MIN. Inset levels keep theirs; a recess reads from
464
- // the inner shadow, which is a different mechanism from a cast shadow.
465
- const result =
466
- level > 0 && level < FLOATING_ELEVATION_MIN
467
- ? {}
468
- : calculateElevationShadow(baseColor, level, theme, isHovered)
469
-
470
- // Cache result
471
- if (elevationShadowCache.size >= MAX_SHADOW_CACHE_SIZE) {
472
- const firstKey = elevationShadowCache.keys().next().value!
473
- elevationShadowCache.delete(firstKey)
474
- }
475
- elevationShadowCache.set(cacheKey, result)
476
-
477
- return result
478
- }
479
-
480
- /**
481
- * Elevation level backing a `<Surface pressed>` recess: the shallow `inset`
482
- * (-1) step. Pressed exposes ONE level of depth — pressed-2 is intentionally
483
- * not surfaced (the deeper `-2` deep-inset stays available to `elevation`).
484
- */
485
- export const PRESSED_ELEVATION_LEVEL: ElevationLevel = -1
486
-
487
- /**
488
- * Inner-shadow recess for a pressed (sunken) surface, tuned to the well's own
489
- * fill colour so the rim/shadow tints track the darker fill. Composes WITH the
490
- * darker pressed fill: on web it adds an inset boxShadow; on the native path
491
- * (no inset-shadow support) it's a no-op and the recess reads via the fill
492
- * alone — so a pressed surface stays legibly sunken with or without the shadow.
493
- *
494
- * @param fillColor - The pressed surface's own fill (one ramp step down).
495
- * @param theme - Current theme ('light' or 'dark').
496
- */
497
- export function getPressedRecessShadow(fillColor: string, theme: 'light' | 'dark'): ViewStyle {
498
- return getElevationShadow(fillColor, PRESSED_ELEVATION_LEVEL, theme)
499
- }
500
-
501
- /**
502
- * Get base surface color from theme.
503
- * This is the foundation color that all elevations are calculated from.
504
- *
505
- * IMPORTANT: Base colors must be chosen to allow sufficient lightening.
506
- *
507
- * **Light Mode**: Cannot use `#FFFFFF` (white) - cannot lighten white!
508
- * - Use `#F3F4F6` (neutral[100]) - can lighten to `#FAFAFA` → `#FFFFFF`
509
- * - This allows raised elevations to be lighter (closer to white)
510
- *
511
- * **Dark Mode**: Need lighter base to allow sufficient lightening
512
- * - Use `surface-elevated` (#2A2827, TD-surface-tokens ramp) - allows further lightening
513
- *
514
- * Can be overridden to use custom base colors for special cases.
515
- */
516
- export function getBaseSurfaceColor(theme: 'light' | 'dark'): string {
517
- // Use semantic token values so elevation colors stay in sync with the token pipeline.
518
- // Light mode: surface-raised (#F3F4F6 / neutral[100]) - NOT white, so we can lighten it
519
- // Dark mode: surface-elevated (#2A2827, TD-surface-tokens ramp) - allows sufficient lightening
520
-
521
- return theme === 'light'
522
- ? semanticColorsLight['surface-raised']
523
- : semanticColorsDark['surface-elevated']
524
- }
525
-
526
- /**
527
- * Component-specific elevation ranges
528
- * Defines which elevation levels each component type can use
529
- */
530
- export const componentElevationRanges = {
531
- button: {
532
- allowed: [2] as const,
533
- default: 2 as const,
534
- hover: 3 as const,
535
- },
536
- card: {
537
- allowed: [1, 2, 3] as const,
538
- default: 2 as const,
539
- hover: 3 as const,
540
- },
541
- input: {
542
- allowed: [-2, -1, 0] as const,
543
- default: -1 as const,
544
- },
545
- overlay: {
546
- allowed: [4, 5] as const,
547
- default: 4 as const,
548
- },
549
- } as const
550
-
551
- export type ComponentType = keyof typeof componentElevationRanges
552
-
553
- /**
554
- * Validate and clamp elevation to component's allowed range
555
- */
556
- export function getValidatedElevation(
557
- component: ComponentType,
558
- requested: ElevationLevel
559
- ): ElevationLevel {
560
- const range = componentElevationRanges[component]
561
- if ((range.allowed as readonly ElevationLevel[]).includes(requested)) {
562
- return requested
563
- }
564
- // Clamp to nearest allowed elevation
565
- const sorted = [...range.allowed].sort((a, b) => a - b)
566
- if (requested < sorted[0]) return sorted[0]
567
- if (requested > sorted[sorted.length - 1]) return sorted[sorted.length - 1]
568
- // Find closest
569
- return sorted.reduce((prev, curr) =>
570
- Math.abs(curr - requested) < Math.abs(prev - requested) ? curr : prev
571
- ) as ElevationLevel
89
+ if (level < 0) return getPressedRecessShadow(getElevationSurface(level, mode), mode)
90
+ if (level === 0) return {}
91
+ return liftStyle(level as LiftStep, mode, opts)
572
92
  }
573
93
 
574
94
  // =============================================================================
575
95
  // Glow Shadow Support
576
96
  // =============================================================================
577
97
 
578
- export type GlowIntensity = 'subtle' | 'medium' | 'strong'
98
+ export type GlowIntensity = 'tight' | 'subtle' | 'medium' | 'strong'
579
99
 
100
+ // Four fixed intensities, chosen by EMPHASIS rather than by the element's size:
101
+ // a caller says how loud the glow should be, not how big the thing is. `tight`
102
+ // is the close halo a small indicator wore before glows were centralised; it is
103
+ // kept as a rung on the scale so that look stays reachable.
580
104
  const glowConfig: Record<GlowIntensity, { blur: number; spread: number; opacity: number }> = {
105
+ tight: { blur: 4, spread: 0, opacity: 0.4 },
581
106
  subtle: { blur: 12, spread: 0, opacity: 0.25 },
582
107
  medium: { blur: 20, spread: 2, opacity: 0.4 },
583
108
  strong: { blur: 30, spread: 4, opacity: 0.55 },
584
109
  }
585
110
 
586
111
  /**
587
- * Get a glow shadow style for emphasis/active states.
588
- *
589
- * Creates a colored radial glow around an element, useful for:
590
- * - Active recording indicators (orange glow)
591
- * - Success states (green glow)
592
- * - Error/danger indicators (red glow)
593
- * - Focus rings and attention-drawing elements
594
- *
595
- * @param color - Glow color (hex string, e.g. '#FF7900')
596
- * @param intensity - Glow intensity level
597
- * @returns ViewStyle with platform-specific shadow properties
112
+ * A coloured radial glow around an element, for emphasis and active states:
113
+ * a live recording indicator, a success or error pulse, a focus halo.
598
114
  */
599
115
  export function getGlowShadow(color: string, intensity: GlowIntensity = 'medium'): ViewStyle {
600
116
  const config = glowConfig[intensity]
@@ -604,7 +120,7 @@ export function getGlowShadow(color: string, intensity: GlowIntensity = 'medium'
604
120
  return Platform.select({
605
121
  web: {
606
122
  boxShadow: `0 0 ${config.blur}px ${config.spread}px rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${config.opacity})`,
607
- } as any,
123
+ } as unknown as ViewStyle,
608
124
  default: {
609
125
  shadowColor: color,
610
126
  shadowOffset: { width: 0, height: 0 },