@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
@@ -2,41 +2,107 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { View } from 'react-native'
3
3
  import { Pill } from './Pill'
4
4
  import { Indicator } from '../indicator'
5
+ import { Surface } from '../surface'
6
+ import { Typography } from '../../custom/Typography'
5
7
 
6
8
  const meta: Meta<typeof Pill> = {
7
9
  title: 'Components/Atoms/Pill',
8
10
  component: Pill,
9
- tags: ['autodocs'],
11
+ tags: ['autodocs', 'status:stable', '!status:review'],
10
12
  argTypes: {
11
- variant: { control: 'select', options: ['subtle', 'outline'] },
12
- color: { control: 'select', options: ['default', 'primary', 'secondary', 'success', 'error', 'warning', 'info'] },
13
- size: { control: 'select', options: ['xs', 'sm', 'md'] },
13
+ variant: { control: 'select', options: ['solid', 'subtle', 'outline'] },
14
+ tone: {
15
+ control: 'select',
16
+ options: ['neutral', 'brand', 'brand-secondary', 'success', 'warning', 'error', 'info'],
17
+ },
18
+ size: { control: 'select', options: ['xs', 'sm', 'md', 'lg', 'xl'] },
19
+ leading: { control: 'select', options: [undefined, 'dot'] },
14
20
  rounded: { control: 'boolean' },
21
+ isDisabled: { control: 'boolean' },
15
22
  },
16
23
  }
17
24
  export default meta
18
25
  type Story = StoryObj<typeof Pill>
19
26
 
20
- export const Default: Story = { args: { children: 'Label', color: 'primary' } }
27
+ export const Default: Story = { args: { children: 'Label', tone: 'brand' } }
21
28
 
22
29
  export const AllVariants: Story = {
23
30
  render: () => (
24
31
  <View className="flex-row gap-2">
25
- <Pill variant="subtle" color="primary">Subtle</Pill>
26
- <Pill variant="outline" color="primary">Outline</Pill>
32
+ <Pill variant="solid" tone="brand">
33
+ Solid
34
+ </Pill>
35
+ <Pill variant="subtle" tone="brand">
36
+ Subtle
37
+ </Pill>
38
+ <Pill variant="outline" tone="brand">
39
+ Outline
40
+ </Pill>
27
41
  </View>
28
42
  ),
29
43
  }
30
44
 
31
- export const AllColors: Story = {
45
+ export const AllTones: Story = {
32
46
  render: () => (
33
47
  <View className="flex-row gap-2 flex-wrap">
34
- <Pill color="default">Default</Pill>
35
- <Pill color="primary">Primary</Pill>
36
- <Pill color="success">Success</Pill>
37
- <Pill color="error">Error</Pill>
38
- <Pill color="warning">Warning</Pill>
39
- <Pill color="info">Info</Pill>
48
+ <Pill tone="neutral">Neutral</Pill>
49
+ <Pill tone="brand">Brand</Pill>
50
+ <Pill tone="brand-secondary">Accent</Pill>
51
+ <Pill tone="success">Success</Pill>
52
+ <Pill tone="warning">Warning</Pill>
53
+ <Pill tone="error">Error</Pill>
54
+ <Pill tone="info">Info</Pill>
55
+ </View>
56
+ ),
57
+ }
58
+
59
+ /**
60
+ * The same row on both planes a subtle pill actually lands on. A `-subtle` fill is
61
+ * alpha, so it composites against whatever is behind it: the raised card lifts every
62
+ * capsule and costs each label contrast. It is the harder case and the one AW-133 is
63
+ * measured against, so judge the tone weight here, not only on the page plane.
64
+ */
65
+ export const OnBothPlanes: Story = {
66
+ parameters: {
67
+ docs: {
68
+ description: {
69
+ story:
70
+ 'Page plane (`grey-925`) above, raised card (`grey-875`) below. Every tone loses ' +
71
+ 'roughly 0.8-1.0 of contrast ratio on the card. `brand` is the deliberate exception ' +
72
+ 'from AW-133 — its label stays the exact `orange[400]` brand hue rather than ' +
73
+ 'levelling with the other five, which leaves it the one tone under AA here.',
74
+ },
75
+ },
76
+ },
77
+ render: () => (
78
+ <View className="gap-4">
79
+ <View className="gap-2">
80
+ <Typography variant="caption" color="tertiary">
81
+ page plane · grey-925
82
+ </Typography>
83
+ <View className="flex-row gap-2 flex-wrap">
84
+ <Pill tone="brand">Brand</Pill>
85
+ <Pill tone="brand-secondary">Accent</Pill>
86
+ <Pill tone="success">Success</Pill>
87
+ <Pill tone="warning">Warning</Pill>
88
+ <Pill tone="error">Error</Pill>
89
+ <Pill tone="info">Info</Pill>
90
+ </View>
91
+ </View>
92
+
93
+ <Surface level="raised" className="gap-2 rounded-xl border-hairline p-4">
94
+ <Typography variant="caption" color="tertiary">
95
+ raised card · grey-875
96
+ </Typography>
97
+ <View className="flex-row gap-2 flex-wrap">
98
+ <Pill tone="brand">Brand</Pill>
99
+ <Pill tone="brand-secondary">Accent</Pill>
100
+ <Pill tone="success">Success</Pill>
101
+ <Pill tone="warning">Warning</Pill>
102
+ <Pill tone="error">Error</Pill>
103
+ <Pill tone="info">Info</Pill>
104
+ </View>
105
+ </Surface>
40
106
  </View>
41
107
  ),
42
108
  }
@@ -44,19 +110,37 @@ export const AllColors: Story = {
44
110
  export const AllSizes: Story = {
45
111
  render: () => (
46
112
  <View className="flex-row gap-2 items-center">
47
- <Pill size="xs" color="primary">XS</Pill>
48
- <Pill size="sm" color="primary">SM</Pill>
49
- <Pill size="md" color="primary">MD</Pill>
113
+ <Pill size="xs" tone="brand">
114
+ XS
115
+ </Pill>
116
+ <Pill size="sm" tone="brand">
117
+ SM
118
+ </Pill>
119
+ <Pill size="md" tone="brand">
120
+ MD
121
+ </Pill>
122
+ <Pill size="lg" tone="brand">
123
+ LG
124
+ </Pill>
125
+ <Pill size="xl" tone="brand">
126
+ XL
127
+ </Pill>
50
128
  </View>
51
129
  ),
52
130
  }
53
131
 
54
- export const WithDot: Story = {
132
+ export const LeadingSlot: Story = {
55
133
  render: () => (
56
134
  <View className="flex-row gap-2">
57
- <Pill color="success" leftElement={<Indicator size="xs" color="success" />}>Active</Pill>
58
- <Pill color="error" leftElement={<Indicator size="xs" color="error" />}>Failed</Pill>
59
- <Pill color="warning" leftElement={<Indicator size="xs" color="warning" />}>Pending</Pill>
135
+ <Pill tone="success" leading="dot">
136
+ Active
137
+ </Pill>
138
+ <Pill tone="error" leading="dot">
139
+ Failed
140
+ </Pill>
141
+ <Pill tone="warning" leading={<Indicator size="xs" color="warning" />}>
142
+ Pending
143
+ </Pill>
60
144
  </View>
61
145
  ),
62
146
  }
@@ -64,8 +148,12 @@ export const WithDot: Story = {
64
148
  export const SquareCorners: Story = {
65
149
  render: () => (
66
150
  <View className="flex-row gap-2">
67
- <Pill rounded={false} color="primary">Tag</Pill>
68
- <Pill rounded={false} color="success">Done</Pill>
151
+ <Pill rounded={false} tone="brand">
152
+ Tag
153
+ </Pill>
154
+ <Pill rounded={false} tone="success">
155
+ Done
156
+ </Pill>
69
157
  </View>
70
158
  ),
71
159
  }
@@ -10,7 +10,7 @@ describe('Pill', () => {
10
10
  })
11
11
 
12
12
  it('renders with all variants', () => {
13
- const variants = ['subtle', 'outline'] as const
13
+ const variants = ['solid', 'subtle', 'outline'] as const
14
14
  variants.forEach((variant) => {
15
15
  const { unmount } = render(<Pill variant={variant}>Test</Pill>)
16
16
  expect(screen.getByText('Test')).toBeInTheDocument()
@@ -19,7 +19,15 @@ describe('Pill', () => {
19
19
  })
20
20
 
21
21
  it('renders with all colors', () => {
22
- const colors = ['default', 'primary', 'secondary', 'success', 'error', 'warning', 'info'] as const
22
+ const colors = [
23
+ 'default',
24
+ 'primary',
25
+ 'secondary',
26
+ 'success',
27
+ 'error',
28
+ 'warning',
29
+ 'info',
30
+ ] as const
23
31
  colors.forEach((color) => {
24
32
  const { unmount } = render(<Pill color={color}>Test</Pill>)
25
33
  expect(screen.getByText('Test')).toBeInTheDocument()
@@ -27,8 +35,25 @@ describe('Pill', () => {
27
35
  })
28
36
  })
29
37
 
38
+ it('renders with all tones', () => {
39
+ const tones = [
40
+ 'neutral',
41
+ 'brand',
42
+ 'brand-secondary',
43
+ 'success',
44
+ 'warning',
45
+ 'error',
46
+ 'info',
47
+ ] as const
48
+ tones.forEach((tone) => {
49
+ const { unmount } = render(<Pill tone={tone}>Test</Pill>)
50
+ expect(screen.getByText('Test')).toBeInTheDocument()
51
+ unmount()
52
+ })
53
+ })
54
+
30
55
  it('renders with all sizes', () => {
31
- const sizes = ['xs', 'sm', 'md'] as const
56
+ const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const
32
57
  sizes.forEach((size) => {
33
58
  const { unmount } = render(<Pill size={size}>Test</Pill>)
34
59
  expect(screen.getByText('Test')).toBeInTheDocument()
@@ -36,6 +61,60 @@ describe('Pill', () => {
36
61
  })
37
62
  })
38
63
 
64
+ it('renders a tone-matched dot for leading="dot"', () => {
65
+ render(
66
+ <Pill tone="success" leading="dot">
67
+ Active
68
+ </Pill>
69
+ )
70
+ expect(screen.getByTestId('pill-dot')).toBeInTheDocument()
71
+ })
72
+
73
+ it('renders each tone with a leading dot', () => {
74
+ const tones = ['neutral', 'brand', 'brand-secondary', 'error'] as const
75
+ tones.forEach((tone) => {
76
+ const { unmount } = render(
77
+ <Pill tone={tone} leading="dot">
78
+ Test
79
+ </Pill>
80
+ )
81
+ expect(screen.getByTestId('pill-dot')).toBeInTheDocument()
82
+ unmount()
83
+ })
84
+ })
85
+
86
+ it('renders a leading icon node', () => {
87
+ render(<Pill leading={<span data-testid="icon" />}>Label</Pill>)
88
+ expect(screen.getByTestId('icon')).toBeInTheDocument()
89
+ expect(screen.queryByTestId('pill-dot')).not.toBeInTheDocument()
90
+ })
91
+
92
+ it('renders a trailing slot', () => {
93
+ render(<Pill trailing={<span data-testid="dismiss" />}>Label</Pill>)
94
+ expect(screen.getByTestId('dismiss')).toBeInTheDocument()
95
+ })
96
+
97
+ it('does not fire onPress when disabled', () => {
98
+ const handler = vi.fn()
99
+ render(
100
+ <Pill onPress={handler} isDisabled>
101
+ Click
102
+ </Pill>
103
+ )
104
+ fireEvent.click(screen.getByRole('button'))
105
+ expect(handler).not.toHaveBeenCalled()
106
+ })
107
+
108
+ it('maps the legacy color prop onto a tone', () => {
109
+ render(
110
+ <Pill color="primary" leading="dot">
111
+ Legacy
112
+ </Pill>
113
+ )
114
+ expect(screen.getByText('Legacy')).toBeInTheDocument()
115
+ expect(screen.getByTestId('pill-dot')).toBeInTheDocument()
116
+ })
117
+
39
118
  it('applies rounded-full by default', () => {
40
119
  const { container } = render(<Pill>Test</Pill>)
41
120
  expect(container.firstChild).toBeInTheDocument()
@@ -65,5 +144,24 @@ describe('Pill', () => {
65
144
  const results = await axe(container)
66
145
  expect(results).toHaveNoViolations()
67
146
  })
147
+
148
+ it('has no accessibility violations with a leading dot', async () => {
149
+ const { container } = render(
150
+ <Pill tone="success" leading="dot">
151
+ Active
152
+ </Pill>
153
+ )
154
+ expect(await axe(container)).toHaveNoViolations()
155
+ })
156
+
157
+ it('has no accessibility violations when interactive', async () => {
158
+ const { container } = render(<Pill onPress={() => {}}>Filter</Pill>)
159
+ expect(await axe(container)).toHaveNoViolations()
160
+ })
161
+
162
+ it('exposes a button role when interactive', () => {
163
+ render(<Pill onPress={() => {}}>Filter</Pill>)
164
+ expect(screen.getByRole('button')).toBeInTheDocument()
165
+ })
68
166
  })
69
167
  })
@@ -2,102 +2,221 @@ import React from 'react'
2
2
  import { View, Text, Pressable, type ViewProps } from 'react-native'
3
3
  import { cn } from '../../../utils/cn'
4
4
 
5
- export type PillVariant = 'subtle' | 'outline'
6
- export type PillColor = 'default' | 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info'
7
- export type PillSize = 'xs' | 'sm' | 'md'
5
+ export type PillVariant = 'solid' | 'subtle' | 'outline'
6
+ /** Semantic tone. `brand-secondary` is the accent, not a second brand. */
7
+ export type PillTone =
8
+ | 'neutral'
9
+ | 'brand'
10
+ | 'brand-secondary'
11
+ | 'success'
12
+ | 'warning'
13
+ | 'error'
14
+ | 'info'
15
+ /** Legacy palette names, kept so existing `color=` call sites keep working. */
16
+ export type PillColor =
17
+ | 'default'
18
+ | 'primary'
19
+ | 'secondary'
20
+ | 'success'
21
+ | 'error'
22
+ | 'warning'
23
+ | 'info'
24
+ export type PillSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
8
25
 
9
26
  export interface PillProps extends ViewProps {
10
27
  /** Pill content */
11
- children: React.ReactNode
28
+ children?: React.ReactNode
12
29
  /** Visual variant */
13
30
  variant?: PillVariant
14
- /** Color scheme */
31
+ /** Semantic tone */
32
+ tone?: PillTone
33
+ /** @deprecated Use `tone` — `color` maps onto it and is kept for call-site compatibility. */
15
34
  color?: PillColor
16
35
  /** Size */
17
36
  size?: PillSize
18
37
  /** Fully rounded (default true) or slight radius */
19
38
  rounded?: boolean
20
- /** Element before the label (e.g., Indicator dot) */
39
+ /** Leading slot: `'dot'` renders a tone-matched dot, any node renders as-is. */
40
+ leading?: React.ReactNode | 'dot'
41
+ /**
42
+ * Colour the leading dot independently of the capsule. For the pattern where
43
+ * the label is neutral and only the dot carries status, so a row of chips
44
+ * stays quiet and the dots are the signal.
45
+ */
46
+ dotTone?: PillTone
47
+ /** @deprecated Use `leading`. */
21
48
  leftElement?: React.ReactNode
49
+ /** Trailing slot (dismiss affordance, counter) */
50
+ trailing?: React.ReactNode
22
51
  /** Press handler (makes pill interactive) */
23
52
  onPress?: () => void
24
- /** Additional className */
53
+ /** Non-interactive and dimmed */
54
+ isDisabled?: boolean
55
+ /** Additional className on the container */
25
56
  className?: string
57
+ /** Additional className on the label, for presets that own their typography */
58
+ textClassName?: string
26
59
  }
27
60
 
28
- const variantColorStyles: Record<PillVariant, Record<PillColor, string>> = {
61
+ const colorToTone: Record<PillColor, PillTone> = {
62
+ default: 'neutral',
63
+ primary: 'brand',
64
+ secondary: 'brand-secondary',
65
+ success: 'success',
66
+ error: 'error',
67
+ warning: 'warning',
68
+ info: 'info',
69
+ }
70
+
71
+ const toneStyles: Record<PillVariant, Record<PillTone, string>> = {
72
+ solid: {
73
+ neutral: 'bg-hairline-strong border-transparent text-text-inverse',
74
+ brand: 'bg-brand-primary border-transparent text-text-inverse',
75
+ 'brand-secondary': 'bg-brand-secondary border-transparent text-text-inverse',
76
+ success: 'bg-status-success border-transparent text-text-inverse',
77
+ warning: 'bg-status-warning border-transparent text-text-inverse',
78
+ error: 'bg-status-error border-transparent text-text-inverse',
79
+ info: 'bg-status-info border-transparent text-text-inverse',
80
+ },
29
81
  subtle: {
30
- default: 'bg-surface-raised border-hairline-subtle text-text-secondary',
31
- primary: 'bg-brand-primary/10 border-brand-primary/25 text-brand-primary',
32
- secondary: 'bg-brand-secondary/10 border-brand-secondary/25 text-brand-secondary',
33
- success: 'bg-status-success/10 border-status-success/25 text-status-success',
34
- error: 'bg-status-error/10 border-status-error/25 text-status-error',
35
- warning: 'bg-status-warning/10 border-status-warning/25 text-status-warning',
36
- info: 'bg-status-info/10 border-status-info/25 text-status-info',
82
+ // Alpha-white rather than a ramp step: a pill sits on whatever plane its
83
+ // host is, and an alpha fill composites by the same amount on all of them.
84
+ // A `surface-*` fill would vanish on the plane it names. Borderless like the
85
+ // six coloured tones — the fill carries the capsule, not a ring.
86
+ // Label comes from `on-*-subtle`, not the base tone token: the base is tuned to
87
+ // carry a white label as a solid fill, which left error and accent unreadable
88
+ // here (AW-133). The on-subtle tokens level the family at one weight.
89
+ neutral: 'bg-hairline-subtle border-transparent text-text-secondary',
90
+ brand: 'bg-brand-primary-subtle border-transparent text-on-brand-primary-subtle',
91
+ 'brand-secondary':
92
+ 'bg-brand-secondary-subtle border-transparent text-on-brand-secondary-subtle',
93
+ success: 'bg-status-success-subtle border-transparent text-on-status-success-subtle',
94
+ warning: 'bg-status-warning-subtle border-transparent text-on-status-warning-subtle',
95
+ error: 'bg-status-error-subtle border-transparent text-on-status-error-subtle',
96
+ info: 'bg-status-info-subtle border-transparent text-on-status-info-subtle',
37
97
  },
38
98
  outline: {
39
- default: 'border-hairline text-text-secondary',
40
- primary: 'border-brand-primary text-brand-primary',
41
- secondary: 'border-brand-secondary text-brand-secondary',
99
+ neutral: 'border-hairline text-text-secondary',
100
+ brand: 'border-brand-primary text-brand-primary',
101
+ 'brand-secondary': 'border-brand-secondary text-brand-secondary',
42
102
  success: 'border-status-success text-status-success',
43
- error: 'border-status-error text-status-error',
44
103
  warning: 'border-status-warning text-status-warning',
104
+ error: 'border-status-error text-status-error',
45
105
  info: 'border-status-info text-status-info',
46
106
  },
47
107
  }
48
108
 
109
+ const dotToneStyles: Record<PillTone, string> = {
110
+ neutral: 'bg-text-tertiary',
111
+ brand: 'bg-brand-primary',
112
+ 'brand-secondary': 'bg-brand-secondary',
113
+ success: 'bg-status-success',
114
+ warning: 'bg-status-warning',
115
+ error: 'bg-status-error',
116
+ info: 'bg-status-info',
117
+ }
118
+
119
+ // One text step per rung. `md` used to repeat `sm`'s 10px, which left no 12px
120
+ // capsule at all and pushed anything between the two down to 10px — that is how
121
+ // MuscleGroupChip lost 7px of height. `md` is the 12px rung now.
49
122
  const sizeStyles: Record<PillSize, { container: string; text: string }> = {
50
- xs: { container: 'px-1 py-px', text: 'text-[9px]' },
51
- sm: { container: 'px-2 py-0.5', text: 'text-[10px]' },
52
- md: { container: 'px-2 py-1', text: 'text-[10px]' },
123
+ xs: { container: 'px-1 py-px', text: 'text-3xs' },
124
+ sm: { container: 'px-2 py-0.5', text: 'text-2xs' },
125
+ md: { container: 'px-2.5 py-1', text: 'text-xs' },
126
+ lg: { container: 'px-3 py-1.5', text: 'text-sm' },
127
+ xl: { container: 'px-4 py-2', text: 'text-base' },
128
+ }
129
+
130
+ // The dot derives its testID from the pill's, so a preset's dot stays
131
+ // addressable under the preset's own name (`muscle-group-chip-dot`) instead of
132
+ // collapsing to a generic one — the visual parity layer targets it by name.
133
+ function PillDot({ tone, testID }: { tone: PillTone; testID?: string }) {
134
+ return (
135
+ <View
136
+ className={cn('w-1.5 h-1.5 rounded-full shrink-0', dotToneStyles[tone])}
137
+ accessibilityElementsHidden
138
+ testID={testID ? `${testID}-dot` : 'pill-dot'}
139
+ />
140
+ )
53
141
  }
54
142
 
55
- export function Pill({
56
- children,
57
- variant = 'subtle',
58
- color = 'default',
59
- size = 'sm',
60
- rounded = true,
61
- leftElement,
62
- onPress,
63
- className,
64
- ...props
65
- }: PillProps) {
66
- const containerClasses = cn(
143
+ function containerClasses(p: PillProps, tone: PillTone) {
144
+ return cn(
67
145
  'flex-row items-center gap-1 border self-start shrink-0',
68
- rounded ? 'rounded-full' : 'rounded',
69
- variantColorStyles[variant][color],
70
- sizeStyles[size].container,
71
- className
146
+ p.rounded === false ? 'rounded' : 'rounded-full',
147
+ toneStyles[p.variant ?? 'subtle'][tone],
148
+ sizeStyles[p.size ?? 'sm'].container,
149
+ p.isDisabled && 'opacity-50',
150
+ p.className
72
151
  )
152
+ }
73
153
 
154
+ function PillContent({ tone, ...p }: PillProps & { tone: PillTone }) {
74
155
  const textClasses = cn(
75
- 'font-heading font-semibold',
76
- sizeStyles[size].text,
77
- 'text-inherit'
156
+ 'font-heading font-semibold text-inherit',
157
+ sizeStyles[p.size ?? 'sm'].text,
158
+ p.textClassName
78
159
  )
79
-
80
- const content = (
160
+ const slot = p.leading ?? p.leftElement
161
+ return (
81
162
  <>
82
- {leftElement}
83
- {typeof children === 'string' ? (
84
- <Text className={textClasses}>{children}</Text>
163
+ {slot === 'dot' ? <PillDot tone={p.dotTone ?? tone} testID={p.testID} /> : slot}
164
+ {typeof p.children === 'string' ? (
165
+ <Text className={textClasses}>{p.children}</Text>
85
166
  ) : (
86
- children
167
+ p.children
87
168
  )}
169
+ {p.trailing}
88
170
  </>
89
171
  )
172
+ }
173
+
174
+ /**
175
+ * The single pill primitive: a capsule of tone-coloured label with optional
176
+ * leading and trailing slots. `Badge`, `Chip`, `StatusPill` and
177
+ * `MuscleGroupChip` are presets over it.
178
+ *
179
+ * @example
180
+ * <Pill tone="success" leading="dot">Active</Pill>
181
+ */
182
+ export function Pill(props: PillProps) {
183
+ const {
184
+ variant,
185
+ tone,
186
+ color,
187
+ size,
188
+ rounded,
189
+ leading,
190
+ dotTone,
191
+ leftElement,
192
+ trailing,
193
+ onPress,
194
+ isDisabled,
195
+ className,
196
+ textClassName,
197
+ children,
198
+ ...viewProps
199
+ } = props
200
+ const resolvedTone = tone ?? (color ? colorToTone[color] : 'neutral')
201
+ const classes = containerClasses(props, resolvedTone)
202
+ const content = <PillContent {...props} tone={resolvedTone} />
90
203
 
91
204
  if (onPress) {
92
205
  return (
93
- <Pressable onPress={onPress} className={containerClasses} {...props}>
206
+ <Pressable
207
+ onPress={onPress}
208
+ disabled={isDisabled}
209
+ accessibilityRole="button"
210
+ className={classes}
211
+ {...viewProps}
212
+ >
94
213
  {content}
95
214
  </Pressable>
96
215
  )
97
216
  }
98
217
 
99
218
  return (
100
- <View className={containerClasses} {...props}>
219
+ <View className={classes} {...viewProps}>
101
220
  {content}
102
221
  </View>
103
222
  )
@@ -1,2 +1,2 @@
1
1
  export { Pill } from './Pill'
2
- export type { PillProps, PillVariant, PillColor, PillSize } from './Pill'
2
+ export type { PillProps, PillVariant, PillTone, PillColor, PillSize } from './Pill'
@@ -124,15 +124,15 @@ export const WithCloseButton: Story = {
124
124
  </Button>
125
125
  </PopoverTrigger>
126
126
  <PopoverContent>
127
- <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
127
+ <View
128
+ style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}
129
+ >
128
130
  <Text className="text-text-primary text-sm font-semibold">Settings</Text>
129
131
  <PopoverCloseButton>
130
132
  <Text className="text-text-secondary text-lg">&#10005;</Text>
131
133
  </PopoverCloseButton>
132
134
  </View>
133
- <Text className="text-text-secondary text-sm mt-2">
134
- Configure your preferences here.
135
- </Text>
135
+ <Text className="text-text-secondary text-sm mt-2">Configure your preferences here.</Text>
136
136
  </PopoverContent>
137
137
  </Popover>
138
138
  ),
@@ -149,9 +149,7 @@ export const WithFormContent: Story = {
149
149
  <PopoverContent>
150
150
  <View style={{ gap: 12 }}>
151
151
  <Text className="text-text-primary text-sm font-semibold">Edit Display Name</Text>
152
- <Text className="text-text-secondary text-xs">
153
- Enter your new display name below.
154
- </Text>
152
+ <Text className="text-text-secondary text-xs">Enter your new display name below.</Text>
155
153
  <View className="bg-surface-input border border-hairline rounded-md px-3 py-2">
156
154
  <Text className="text-text-secondary text-sm">John Doe</Text>
157
155
  </View>
@@ -162,7 +160,7 @@ export const WithFormContent: Story = {
162
160
  </View>
163
161
  </PopoverCloseButton>
164
162
  <View className="px-3 py-1.5 rounded-md bg-brand-primary">
165
- <Text className="text-white text-sm font-medium">Save</Text>
163
+ <Text className="text-on-brand-primary text-sm font-medium">Save</Text>
166
164
  </View>
167
165
  </View>
168
166
  </View>
@@ -1,12 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
2
  import { render, screen, fireEvent, act } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
- import {
5
- Popover,
6
- PopoverTrigger,
7
- PopoverContent,
8
- PopoverCloseButton,
9
- } from './Popover'
4
+ import { Popover, PopoverTrigger, PopoverContent, PopoverCloseButton } from './Popover'
10
5
 
11
6
  describe('Popover', () => {
12
7
  it('renders trigger element', () => {