@titan-design/react-ui 0.11.0 → 0.12.1

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 (152) hide show
  1. package/dist/bodymap.js +173 -343
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +173 -343
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +27 -22
  6. package/dist/index.d.ts +27 -22
  7. package/dist/index.js +366 -561
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +360 -553
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/pages.js +242 -407
  12. package/dist/pages.js.map +1 -1
  13. package/dist/pages.mjs +242 -407
  14. package/dist/pages.mjs.map +1 -1
  15. package/dist/theme/index.d.mts +250 -1157
  16. package/dist/theme/index.d.ts +250 -1157
  17. package/dist/theme/index.js +234 -408
  18. package/dist/theme/index.js.map +1 -1
  19. package/dist/theme/index.mjs +227 -399
  20. package/dist/theme/index.mjs.map +1 -1
  21. package/dist/theme/tokens-css.js +141 -140
  22. package/dist/theme/tokens-css.js.map +1 -1
  23. package/dist/theme/tokens-css.mjs +141 -140
  24. package/dist/theme/tokens-css.mjs.map +1 -1
  25. package/dist/theme/tokens.d.mts +904 -0
  26. package/dist/theme/tokens.d.ts +904 -0
  27. package/dist/theme/tokens.js +856 -0
  28. package/dist/theme/tokens.js.map +1 -0
  29. package/dist/theme/tokens.mjs +833 -0
  30. package/dist/theme/tokens.mjs.map +1 -0
  31. package/package.json +7 -1
  32. package/src/arch/Architecture.stories.tsx +9 -9
  33. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  34. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +3 -3
  35. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  36. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +3 -3
  37. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +1 -1
  38. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +2 -2
  39. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  40. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +4 -4
  41. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +3 -3
  42. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  43. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -12
  44. package/src/components/custom/Sidebar/Sidebar.tsx +4 -4
  45. package/src/components/custom/Table/Table.tsx +2 -2
  46. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
  47. package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
  48. package/src/components/custom/Workout/BaseBadge.tsx +1 -1
  49. package/src/components/custom/Workout/BodyMap.tsx +2 -2
  50. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
  51. package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
  52. package/src/components/custom/Workout/DeviationBar.tsx +3 -2
  53. package/src/components/custom/Workout/ExerciseCard.test.tsx +3 -3
  54. package/src/components/custom/Workout/ExerciseCard.tsx +2 -2
  55. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -4
  56. package/src/components/custom/Workout/InputBar.tsx +2 -2
  57. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
  58. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
  59. package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
  60. package/src/components/custom/Workout/MesoCard.tsx +3 -1
  61. package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
  62. package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
  63. package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
  64. package/src/components/custom/Workout/README.md +1 -1
  65. package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
  66. package/src/components/custom/Workout/RestTimer.tsx +2 -2
  67. package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
  68. package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
  69. package/src/components/custom/Workout/SessionHeader.tsx +1 -1
  70. package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
  71. package/src/components/custom/Workout/SessionRail.tsx +13 -5
  72. package/src/components/custom/Workout/SetBar.tsx +3 -3
  73. package/src/components/custom/Workout/SetRow.test.tsx +6 -3
  74. package/src/components/custom/Workout/StatusDot.tsx +5 -4
  75. package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
  76. package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -2
  77. package/src/components/custom/Workout/VelocityStrip.test.tsx +5 -2
  78. package/src/components/custom/Workout/VelocityStrip.tsx +4 -4
  79. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
  80. package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
  81. package/src/components/custom/Workout/WeightBadge.tsx +2 -1
  82. package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
  83. package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
  84. package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
  85. package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
  86. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  87. package/src/components/custom/charts/SetBarChart.tsx +3 -3
  88. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  89. package/src/components/custom/stepper/Stepper.tsx +3 -3
  90. package/src/components/shell/DeviceMenu.tsx +2 -2
  91. package/src/components/shell/README.md +1 -1
  92. package/src/components/shell/SessionRailSpecimen.stories.tsx +1 -1
  93. package/src/components/shell/SideNav.tsx +1 -1
  94. package/src/components/shell/TopBar.tsx +1 -1
  95. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  96. package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
  97. package/src/components/ui/avatar/Avatar.tsx +2 -2
  98. package/src/components/ui/badge/Badge.tsx +2 -2
  99. package/src/components/ui/card/Card.tsx +3 -3
  100. package/src/components/ui/checkbox/Checkbox.tsx +1 -1
  101. package/src/components/ui/collapse/Collapse.tsx +1 -1
  102. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  103. package/src/components/ui/drawer/Drawer.tsx +3 -3
  104. package/src/components/ui/form-field/FormField.tsx +1 -1
  105. package/src/components/ui/help-tip/HelpTip.tsx +1 -1
  106. package/src/components/ui/menu/Menu.tsx +2 -2
  107. package/src/components/ui/pill/Pill.tsx +2 -2
  108. package/src/components/ui/popover/Popover.stories.tsx +2 -2
  109. package/src/components/ui/popover/Popover.tsx +1 -1
  110. package/src/components/ui/progress/Progress.tsx +3 -3
  111. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  112. package/src/components/ui/radio/Radio.tsx +4 -4
  113. package/src/components/ui/select/Select.tsx +3 -3
  114. package/src/components/ui/surface/Surface.stories.tsx +3 -3
  115. package/src/components/ui/surface/Surface.test.tsx +32 -19
  116. package/src/components/ui/surface/Surface.tsx +3 -3
  117. package/src/components/ui/surface/SurfaceContext.ts +19 -29
  118. package/src/components/ui/surface/surface.contract.test.ts +58 -45
  119. package/src/components/ui/switch/Switch.tsx +1 -1
  120. package/src/components/ui/tabs/Tabs.tsx +2 -2
  121. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
  122. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
  123. package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
  124. package/src/stories/PresetShowcase.stories.tsx +21 -21
  125. package/src/theme/ColorPalettes.stories.tsx +41 -38
  126. package/src/theme/ColorPrimitives.stories.tsx +89 -41
  127. package/src/theme/Depth.stories.tsx +312 -0
  128. package/src/theme/DepthCalibration.stories.tsx +655 -0
  129. package/src/theme/ThemeProvider.tsx +1 -1
  130. package/src/theme/barrel.ts +7 -14
  131. package/src/theme/color-stories.coverage.test.ts +15 -20
  132. package/src/theme/color-story-kit.tsx +1 -1
  133. package/src/theme/color-utils.ts +197 -0
  134. package/src/theme/config.ts +0 -8
  135. package/src/theme/depth-calibration.test.tsx +97 -0
  136. package/src/theme/elevation.test.ts +62 -1
  137. package/src/theme/elevation.ts +34 -8
  138. package/src/theme/global.css +47 -53
  139. package/src/theme/grey-ramp.test.ts +166 -0
  140. package/src/theme/materials.test.ts +106 -0
  141. package/src/theme/materials.ts +211 -0
  142. package/src/theme/presets/audiobook.ts +4 -4
  143. package/src/theme/presets/types.ts +3 -3
  144. package/src/theme/semantic-contrast.test.ts +83 -0
  145. package/src/theme/token-export-guard.test.ts +104 -0
  146. package/src/theme/tokens/primitives.ts +63 -116
  147. package/src/theme/tokens/semantic.ts +130 -82
  148. package/src/utils/colors.ts +1 -1
  149. package/tailwind.config.js +2 -5
  150. package/src/theme/bar-paper.ts +0 -59
  151. package/src/theme/shadows.stories.tsx +0 -523
  152. package/src/theme/shadows.ts +0 -439
@@ -24,7 +24,7 @@ function PresetSwitcher({
24
24
  onSwitch: (name: string) => void
25
25
  }) {
26
26
  return (
27
- <View className="flex-row items-center gap-3 p-4 bg-surface-elevated rounded-lg border border-border">
27
+ <View className="flex-row items-center gap-3 p-4 bg-surface-elevated rounded-lg border border-hairline">
28
28
  <Text className="text-text-secondary text-sm font-medium">Active Preset:</Text>
29
29
  {Object.keys(presets).map((name) => (
30
30
  <Pressable
@@ -33,7 +33,7 @@ function PresetSwitcher({
33
33
  className={`px-4 py-2 rounded-md border ${
34
34
  activePreset === name
35
35
  ? 'bg-brand-primary border-brand-primary'
36
- : 'bg-surface-base border-border web:hover:bg-interactive-hover'
36
+ : 'bg-surface-base border-hairline web:hover:bg-interactive-hover'
37
37
  }`}
38
38
  >
39
39
  <Text
@@ -272,7 +272,7 @@ function TypographyShowcase() {
272
272
  </Section>
273
273
 
274
274
  <Section title="Monospace Font (font-mono)">
275
- <View className="bg-surface-elevated rounded-lg p-4 border border-border">
275
+ <View className="bg-surface-elevated rounded-lg p-4 border border-hairline">
276
276
  <Text className="text-sm text-text-primary font-mono">
277
277
  {'const theme = applyThemePreset(audiobookPreset)'}
278
278
  </Text>
@@ -368,13 +368,13 @@ function ColorPaletteShowcase() {
368
368
 
369
369
  <Section title="Text Colors">
370
370
  <View className="flex-row gap-3 flex-wrap">
371
- <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-elevated border border-border">
371
+ <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-elevated border border-hairline">
372
372
  <Text className="text-xs font-mono font-medium text-text-primary">text-primary</Text>
373
373
  </View>
374
- <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-elevated border border-border">
374
+ <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-elevated border border-hairline">
375
375
  <Text className="text-xs font-mono font-medium text-text-secondary">text-secondary</Text>
376
376
  </View>
377
- <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-elevated border border-border">
377
+ <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-elevated border border-hairline">
378
378
  <Text className="text-xs font-mono font-medium text-text-tertiary">text-tertiary</Text>
379
379
  </View>
380
380
  </View>
@@ -382,32 +382,32 @@ function ColorPaletteShowcase() {
382
382
 
383
383
  <Section title="Surface Colors">
384
384
  <View className="flex-row gap-3 flex-wrap">
385
- <ColorSwatch label="surface-base" className="bg-surface-base border border-border" textClass="text-text-primary" />
386
- <ColorSwatch label="surface-elevated" className="bg-surface-elevated border border-border" textClass="text-text-primary" />
387
- <ColorSwatch label="surface-raised" className="bg-surface-raised border border-border" textClass="text-text-primary" />
388
- <ColorSwatch label="surface-overlay" className="bg-surface-overlay border border-border" textClass="text-text-primary" />
389
- <ColorSwatch label="surface-input" className="bg-surface-input border border-border" textClass="text-text-primary" />
385
+ <ColorSwatch label="surface-base" className="bg-surface-base border border-hairline" textClass="text-text-primary" />
386
+ <ColorSwatch label="surface-elevated" className="bg-surface-elevated border border-hairline" textClass="text-text-primary" />
387
+ <ColorSwatch label="surface-raised" className="bg-surface-raised border border-hairline" textClass="text-text-primary" />
388
+ <ColorSwatch label="surface-overlay" className="bg-surface-overlay border border-hairline" textClass="text-text-primary" />
389
+ <ColorSwatch label="surface-input" className="bg-surface-input border border-hairline" textClass="text-text-primary" />
390
390
  </View>
391
391
  </Section>
392
392
 
393
393
  <Section title="Background Colors">
394
394
  <View className="flex-row gap-3 flex-wrap">
395
- <ColorSwatch label="background-base" className="bg-background-base border border-border" textClass="text-text-primary" />
396
- <ColorSwatch label="background-default" className="bg-background-default border border-border" textClass="text-text-primary" />
397
- <ColorSwatch label="background-subtle" className="bg-background-subtle border border-border" textClass="text-text-primary" />
395
+ <ColorSwatch label="background-base" className="bg-background-base border border-hairline" textClass="text-text-primary" />
396
+ <ColorSwatch label="background-default" className="bg-background-default border border-hairline" textClass="text-text-primary" />
397
+ <ColorSwatch label="background-subtle" className="bg-background-subtle border border-hairline" textClass="text-text-primary" />
398
398
  </View>
399
399
  </Section>
400
400
 
401
401
  <Section title="Border Colors">
402
402
  <View className="flex-row gap-3 flex-wrap">
403
- <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-border">
404
- <Text className="text-xs font-mono font-medium text-text-primary">border-default</Text>
403
+ <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-hairline-default">
404
+ <Text className="text-xs font-mono font-medium text-text-primary">hairline-default</Text>
405
405
  </View>
406
- <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-border-subtle">
407
- <Text className="text-xs font-mono font-medium text-text-primary">border-subtle</Text>
406
+ <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-hairline-subtle">
407
+ <Text className="text-xs font-mono font-medium text-text-primary">hairline-subtle</Text>
408
408
  </View>
409
- <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-border-strong">
410
- <Text className="text-xs font-mono font-medium text-text-primary">border-strong</Text>
409
+ <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-hairline-strong">
410
+ <Text className="text-xs font-mono font-medium text-text-primary">hairline-strong</Text>
411
411
  </View>
412
412
  <View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-border-focus">
413
413
  <Text className="text-xs font-mono font-medium text-text-primary">border-focus</Text>
@@ -425,7 +425,7 @@ function ColorPaletteShowcase() {
425
425
  function Section({ title, children }: { title: string; children: React.ReactNode }) {
426
426
  return (
427
427
  <View className="gap-3">
428
- <Text className="text-lg font-semibold text-text-primary font-heading border-b border-border-subtle pb-2">
428
+ <Text className="text-lg font-semibold text-text-primary font-heading border-b border-hairline-subtle pb-2">
429
429
  {title}
430
430
  </Text>
431
431
  {children}
@@ -1,17 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { View, Text } from 'react-native'
3
- import {
4
- primitiveColors,
5
- surfaceRampDark,
6
- backgroundFrameDark,
7
- categoricalPalette,
8
- getCategoricalColor,
9
- CATEGORICAL_CVD_SAFE_MAX,
10
- divergingScale,
11
- sequentialEffort,
12
- discreteRainbow,
13
- bestTextColor,
14
- } from './tokens/primitives'
3
+ import { categoricalPalette, getCategoricalColor, CATEGORICAL_CVD_SAFE_MAX, divergingScale, sequentialEffort, discreteRainbow, bestTextColor, greyRamp } from './tokens/primitives'
15
4
  import { semanticColorsLight, semanticColorsDark } from './tokens/semantic'
16
5
  import { WORKOUT_TOKENS } from './workout-tokens'
17
6
  import { WORKOUT_PILL_DELOAD } from './extracted-colors-dataviz'
@@ -51,22 +40,24 @@ export default meta
51
40
  // ============================================================================
52
41
 
53
42
  /**
54
- * The dark surface ramp, darkest plane first.
43
+ * The dark surface planes, darkest first — the depth ladder.
44
+ *
45
+ * Each plane is a STEP on `greyRamp` (TD-07.14); the `step` column is the whole
46
+ * point, because it is what lets a plane and a border be compared on one scale
47
+ * instead of two. Written as an explicit ordered list because object key order
48
+ * is not a contract and depth order is what this story is about.
49
+ * `color-stories.coverage.test.ts` asserts every plane appears here.
55
50
  *
56
- * Order and `lStar` mirror `surfaceRampDark` in tokens/primitives.ts, plus the
57
- * out-of-ramp `backgroundFrameDark` bezel that sits below it. Written as an
58
- * explicit ordered list because object key order is not a contract and depth
59
- * order is the whole point of this story. `color-stories.coverage.test.ts`
60
- * asserts every ramp step appears here.
51
+ * The old `inset` plane is absent: it was retired, not renamed. See the ramp in
52
+ * `Foundations/Color/Primitives`.
61
53
  */
62
54
  const SURFACE_PLANES = [
63
- { hex: backgroundFrameDark, lStar: 3.79, role: 'Frame / bezel — chrome outside the ramp' },
64
- { hex: surfaceRampDark.inset, lStar: 4.5, role: 'Sub-shell well / pressed' },
65
- { hex: surfaceRampDark.background, lStar: 9, role: 'Shell' },
66
- { hex: surfaceRampDark.base, lStar: 13.5, role: 'Main content plane' },
67
- { hex: surfaceRampDark.elevated, lStar: 17, role: 'Nav / rail' },
68
- { hex: surfaceRampDark.raised, lStar: 20, role: 'Cards' },
69
- { hex: surfaceRampDark.overlay, lStar: 22.5, role: 'Hero / popover' },
55
+ { step: 975, hex: greyRamp[975], lStar: 3.8, role: 'Frame / bezel — chrome the ramp sits inside' },
56
+ { step: 950, hex: greyRamp[950], lStar: 9, role: 'Shell' },
57
+ { step: 925, hex: greyRamp[925], lStar: 13.9, role: 'Main content plane' },
58
+ { step: 900, hex: greyRamp[900], lStar: 17.2, role: 'Nav / rail' },
59
+ { step: 875, hex: greyRamp[875], lStar: 19.9, role: 'Cards' },
60
+ { step: 850, hex: greyRamp[850], lStar: 22.7, role: 'Hero / popover' },
70
61
  ] as const
71
62
 
72
63
  /** Semantic tokens resolving to `hex`, derived at render so the mapping can't drift. */
@@ -77,7 +68,17 @@ function tokensResolvingTo(hex: string): string[] {
77
68
  .sort()
78
69
  }
79
70
 
80
- function SurfacePlaneRow({ hex, lStar, role }: { hex: string; lStar: number; role: string }) {
71
+ function SurfacePlaneRow({
72
+ step,
73
+ hex,
74
+ lStar,
75
+ role,
76
+ }: {
77
+ step: number
78
+ hex: string
79
+ lStar: number
80
+ role: string
81
+ }) {
81
82
  const tokens = tokensResolvingTo(hex)
82
83
  return (
83
84
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 16 }}>
@@ -93,7 +94,8 @@ function SurfacePlaneRow({ hex, lStar, role }: { hex: string; lStar: number; rol
93
94
  />
94
95
  <View style={{ flex: 1 }}>
95
96
  <Text className="font-semibold text-text-primary text-sm">
96
- {hex.toUpperCase()}{' '}
97
+ grey-{step}{' '}
98
+ <Text className="text-text-secondary text-xs font-normal">{hex.toUpperCase()}</Text>{' '}
97
99
  <Text className="text-text-tertiary text-xs font-normal">L*{lStar}</Text>
98
100
  </Text>
99
101
  <Text className="text-text-secondary text-xs">{role}</Text>
@@ -440,7 +442,7 @@ export const FatigueRomPalette: StoryObj = {
440
442
  DRIFT_GREY is `neutral[600]` #4B5563, and the unified warm grey ramp assigns it no
441
443
  destination — the 15-row fold maps the other greys but skips this one. It needs an
442
444
  assignment before that migration lands. Note that the recovered spec records DRIFT_GREY as
443
- `charcoal[500]` #1C1C1C; that is stale, `fatigue-tokens.ts` on main reads `neutral[600]`,
445
+ `grey[500]` #1C1C1C; that is stale, `fatigue-tokens.ts` on main reads `neutral[600]`,
444
446
  which puts it in the expensive half of the fold rather than the cheap one.
445
447
  </Text>
446
448
  </View>
@@ -511,11 +513,12 @@ export const StatusColors: StoryObj = {
511
513
  <Text className="text-2xl font-bold text-text-primary mb-2">Status Colors</Text>
512
514
  <SectionIntro>
513
515
  Status roles and their modifiers. <Text className="font-semibold">status-error-vivid</Text>{' '}
514
- is a separate, higher-chroma role (not a modifier of status-error) reserved for
515
- destructive emphasis. It is the one status family still sourced from the redVivid support
516
- scale rather than the OKLCH ramps, and it is on its way out: under TD-07.14 redVivid
517
- retires, status-error-vivid collapses onto status-error&apos;s red[600], and the
518
- Indicator&apos;s emphasis moves to a glow derived from red[500].
516
+ is a separate role (not a modifier of status-error) reserved for destructive
517
+ emphasis. It used to be the one status family sourced from the `redVivid` support scale
518
+ rather than the OKLCH ramps; TD-07.14 deleted that scale, so the family now resolves to
519
+ the same red[600]/[500]/[700] steps as status-error. The two are therefore identical in
520
+ value today — the role is kept distinct because emphasis is meant to come from a glow
521
+ derived from red[500], not from a second, higher-chroma red.
519
522
  </SectionIntro>
520
523
  <VariantMatrix
521
524
  bases={[
@@ -571,7 +574,7 @@ export const TextAndBorderColors: StoryObj = {
571
574
  <View style={{ padding: 24 }}>
572
575
  <Text className="text-2xl font-bold text-text-primary mb-2">Text & Border Colors</Text>
573
576
  <SectionIntro>
574
- The dark-mode text and border roles. Today the border family is the charcoal grey ramp
577
+ The dark-mode text and border roles. Today the border family is the grey grey ramp
575
578
  assigned to line-work, and text-tertiary and the link colors come from the neutral and
576
579
  blue support scales — all three of which are pending migration (TD-07.14): the greys fold
577
580
  into one warm grey ramp and the links move to the OKLCH blue. These are the roles that
@@ -594,9 +597,9 @@ export const TextAndBorderColors: StoryObj = {
594
597
 
595
598
  <SectionTitle>Border</SectionTitle>
596
599
  <View style={{ gap: 16 }}>
597
- <ColorSwatch name="border-default" value={semanticColorsDark['border-default']} />
598
- <ColorSwatch name="border-subtle" value={semanticColorsDark['border-subtle']} />
599
- <ColorSwatch name="border-strong" value={semanticColorsDark['border-strong']} />
600
+ <ColorSwatch name="hairline-default" value={semanticColorsDark['hairline-default']} />
601
+ <ColorSwatch name="hairline-subtle" value={semanticColorsDark['hairline-subtle']} />
602
+ <ColorSwatch name="hairline-strong" value={semanticColorsDark['hairline-strong']} />
600
603
  <ColorSwatch name="border-prominent" value={semanticColorsDark['border-prominent']} />
601
604
  <ColorSwatch name="border-focus" value={semanticColorsDark['border-focus']} />
602
605
  <ColorSwatch name="border-input" value={semanticColorsDark['border-input']} />
@@ -626,7 +629,7 @@ const SEM = {
626
629
  brandDark: semanticColorsDark['brand-primary-dark'],
627
630
  brandSecondary: semanticColorsDark['brand-secondary'],
628
631
  spinner: SPINNER_PRIMARY,
629
- neutral: primitiveColors.neutral[500],
632
+ neutral: greyRamp[600],
630
633
  } as const
631
634
 
632
635
  export const RolesInUse: StoryObj = {
@@ -3,8 +3,8 @@ import { View, Text } from 'react-native'
3
3
  import {
4
4
  primitiveColors,
5
5
  primitiveRamps,
6
- surfaceRampDark,
7
- backgroundFrameDark,
6
+ greyRamp,
7
+ SURFACE_PLANE_STEPS,
8
8
  categoricalPalette,
9
9
  CATEGORICAL_CVD_SAFE_MAX,
10
10
  } from './tokens/primitives'
@@ -25,18 +25,12 @@ import {
25
25
  * organisations OF these scales — surfaces, categorical, status, brand,
26
26
  * fatigue and the rest — live in the sibling `Foundations/Color/Palettes`.
27
27
  *
28
- * This page is a MENU: everything on it is something you may legitimately
29
- * reach for today and that survives the migration ahead. The older
30
- * `primitiveColors` scales `charcoal`, `neutral`, `redVivid`, and the support
31
- * `blue`/`red` are deliberately NOT rendered here even though they still have
32
- * call sites in `semantic.ts`, because TD-07.14 deletes them. A swatch on a
33
- * menu is an invitation, and no annotation is loud enough to cancel that; the
34
- * intro carries a one-line pointer instead, so a reader who comes looking for
35
- * charcoal learns where it went rather than assuming an oversight.
36
- *
37
- * The semantic tokens those scales currently back are still documented, with
38
- * swatches, in `Foundations/Color/Palettes` — which is what the coverage guard
39
- * checks, and why removing the scale rows here costs no coverage.
28
+ * This page is a MENU: everything on it is something you may legitimately reach
29
+ * for. It is now also the complete list — TD-07.14 deleted the five older
30
+ * `primitiveColors` scales (`charcoal`, `neutral`, `redVivid`, and the support
31
+ * `blue`/`red`) rather than leaving them shelved, so there is no longer a
32
+ * category of "real but don't pick these". The greys folded into `greyRamp`
33
+ * below; the chromatics into the OKLCH `primitiveRamps`.
40
34
  */
41
35
  const meta: Meta = {
42
36
  title: 'Foundations/Color/Primitives',
@@ -64,22 +58,55 @@ const KEYWORDS = [
64
58
  { name: 'black', value: primitiveColors.black },
65
59
  ] as const
66
60
 
61
+ /** Which surface plane each ramp step backs, for the annotation row. */
62
+ const PLANE_BY_STEP: Record<number, string> = Object.fromEntries(
63
+ Object.entries(SURFACE_PLANE_STEPS).map(([plane, step]) => [step, plane])
64
+ )
65
+
66
+ /** Steps whose value is byte-identical to what shipped in v0.10.0. */
67
+ const ANCHOR_STEPS = new Set<number>(Object.values(SURFACE_PLANE_STEPS))
68
+
69
+ const GREY_STEPS = Object.keys(greyRamp).map(Number).sort((a, b) => a - b)
70
+
67
71
  /**
68
- * The dark surface planes, darkest first the greys you may actually pick.
72
+ * The grey ramp, rendered as a numbered scale with its plane aliases.
69
73
  *
70
- * Shown here as a ramp (values only) because a menu of raw scales with no greys
71
- * on it would be a lie. The depth semantics, the L* spacing rationale and the
72
- * per-plane semantic aliases live in the full `Foundations/Color/Palettes`
73
- * surface story, and are deliberately not duplicated.
74
+ * This story used to show six unnumbered plane NAMES here, which was the visible
75
+ * symptom of the greys not actually being a ramp. The numbers are the content:
76
+ * they are what let a border, a plane and a text colour be compared on one scale.
74
77
  */
75
- const SURFACE_STEPS: Record<string, string> = {
76
- frame: backgroundFrameDark,
77
- inset: surfaceRampDark.inset,
78
- background: surfaceRampDark.background,
79
- base: surfaceRampDark.base,
80
- elevated: surfaceRampDark.elevated,
81
- raised: surfaceRampDark.raised,
82
- overlay: surfaceRampDark.overlay,
78
+ function GreyRampRow() {
79
+ return (
80
+ <View style={{ flexDirection: 'row', gap: 4, marginBottom: 4 }}>
81
+ {GREY_STEPS.map((step) => {
82
+ const hex = greyRamp[step as keyof typeof greyRamp]
83
+ const plane = PLANE_BY_STEP[step]
84
+ return (
85
+ <View key={step} style={{ flex: 1, alignItems: 'stretch' }}>
86
+ <View
87
+ style={{
88
+ height: 76,
89
+ borderRadius: 6,
90
+ backgroundColor: hex,
91
+ borderWidth: ANCHOR_STEPS.has(step) ? 1.5 : 1,
92
+ borderColor: ANCHOR_STEPS.has(step) ? '#FF7900' : SWATCH_BORDER,
93
+ }}
94
+ />
95
+ <Text className="text-text-primary" style={{ fontSize: 10, fontWeight: '600', marginTop: 3 }}>
96
+ {step}
97
+ {ANCHOR_STEPS.has(step) ? ' ★' : ''}
98
+ </Text>
99
+ <Text className="text-text-tertiary" style={{ fontSize: 8 }}>
100
+ {hex.toUpperCase()}
101
+ </Text>
102
+ {plane ? (
103
+ <Text style={{ fontSize: 8, color: '#FF9628', marginTop: 1 }}>{plane}</Text>
104
+ ) : null}
105
+ </View>
106
+ )
107
+ })}
108
+ </View>
109
+ )
83
110
  }
84
111
 
85
112
  export const Ramps: StoryObj = {
@@ -97,13 +124,14 @@ export const Ramps: StoryObj = {
97
124
 
98
125
  <Text className="text-text-tertiary text-xs mb-6">
99
126
  Looking for <Text className="font-semibold">charcoal</Text>,{' '}
100
- <Text className="font-semibold">neutral</Text>, <Text className="font-semibold">redVivid</Text>{' '}
101
- or the support <Text className="font-semibold">blue</Text>/
102
- <Text className="font-semibold">red</Text> scales? They are intentionally not shown — all
103
- five retire into a unified warm grey ramp plus the OKLCH ramps under TD-07.14. They still
104
- have call sites in `semantic.ts`, so the semantic tokens they back are real and documented
105
- in Palettes; the raw scales are not on the menu because you should not be picking from
106
- them. Spec: coordination/design-explorations/foundations/warm-grey-ramp/.
127
+ <Text className="font-semibold">neutral</Text>,{' '}
128
+ <Text className="font-semibold">redVivid</Text> or the support{' '}
129
+ <Text className="font-semibold">blue</Text>/<Text className="font-semibold">red</Text>{' '}
130
+ scales? All five were DELETED in TD-07.14, not shelved. The two grey scales folded into
131
+ the warm ramp below they were cold (neutral ran R−B down to −26) while the surfaces had
132
+ been warm since v0.10.0, and nothing ever reconciled the two. The chromatics moved to the
133
+ OKLCH ramps above. Spec and generator:
134
+ sources/design/foundations/warm-grey-ramp/.
107
135
  </Text>
108
136
 
109
137
  <SectionTitle>Chromatic — OKLCH tonal ramps</SectionTitle>
@@ -119,15 +147,35 @@ export const Ramps: StoryObj = {
119
147
  <ScaleRow key={key} name={name} scale={primitiveRamps[key]} />
120
148
  ))}
121
149
 
122
- <SectionTitle>Achromatic — the dark surface ramp</SectionTitle>
150
+ <SectionTitle>Achromatic — the warm grey ramp</SectionTitle>
151
+ <Text className="text-text-secondary text-xs mb-3">
152
+ The ONE grey scale. Fifteen steps, warm at every one (R−B never below +2), spaced by
153
+ perceptual lightness so a step is the same visual distance anywhere in the stack. It
154
+ replaced three separate scales — a cold <Text className="font-semibold">charcoal</Text>, a
155
+ cold <Text className="font-semibold">neutral</Text>, and a standalone surface ramp — which
156
+ is why the surface planes appear here as ordinary numbered steps rather than as a parallel
157
+ set of names.
158
+ </Text>
123
159
  <Text className="text-text-secondary text-xs mb-4">
124
- The greys. A derived depth ladder rather than a generated hue ramp — spaced by perceptual
125
- lightness so one step is the same visual distance anywhere in the stack. Shown here as
126
- values; the depth semantics and the semantic tokens aliasing each plane are in the
127
- Palettes surface story, and are not repeated. Apply them with{' '}
128
- <Text className="font-semibold">&lt;Surface level&gt;</Text>, not by reading a step.
160
+ Two things about the numbering, both deliberate.{' '}
161
+ <Text className="font-semibold">975</Text> sits past the end of the shared grid: the
162
+ canonical L* ladder — the median of the seven chromatic ramps stops at L*10.3, which is
163
+ step 950, and the bezel is L*3.8. There was no number left for it.{' '}
164
+ <Text className="font-semibold">850</Text> and <Text className="font-semibold">875</Text>{' '}
165
+ are quarter-steps because overlay, raised and elevated sit only ~2.7 L* apart and all three
166
+ collide on 900 at normal resolution. There is deliberately no{' '}
167
+ <Text className="font-semibold">inset</Text>: at ΔE 1.10 from the frame it was an
168
+ imperceptible duplicate, which is why <Text className="font-semibold">&lt;Surface
169
+ pressed&gt;</Text> kept collapsing into it.
170
+ </Text>
171
+ <GreyRampRow />
172
+ <Text className="text-text-tertiary text-xs mb-4">
173
+ <Text style={{ color: '#FF9628' }}>★ / orange outline</Text> = a step that also names a
174
+ surface plane, byte-identical to the value that shipped in v0.10.0 — the surfaces did not
175
+ move, only their names. Reach for planes with{' '}
176
+ <Text className="font-semibold">&lt;Surface level&gt;</Text> rather than by reading a step;
177
+ the depth semantics and per-plane token aliases are in the Palettes surface story.
129
178
  </Text>
130
- <ScaleRow name="Surface (dark)" scale={SURFACE_STEPS} />
131
179
 
132
180
  <SectionTitle>Keywords</SectionTitle>
133
181
  <Text className="text-text-secondary text-xs mb-4">