@titan-design/react-ui 0.11.0 → 0.12.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 (142) 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 +24 -21
  6. package/dist/index.d.ts +24 -21
  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 +409 -415
  16. package/dist/theme/index.d.ts +409 -415
  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/package.json +1 -1
  26. package/src/arch/Architecture.stories.tsx +9 -9
  27. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  28. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +3 -3
  29. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  30. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +3 -3
  31. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +1 -1
  32. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +2 -2
  33. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  34. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +4 -4
  35. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +3 -3
  36. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  37. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -12
  38. package/src/components/custom/Sidebar/Sidebar.tsx +4 -4
  39. package/src/components/custom/Table/Table.tsx +2 -2
  40. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
  41. package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
  42. package/src/components/custom/Workout/BaseBadge.tsx +1 -1
  43. package/src/components/custom/Workout/BodyMap.tsx +2 -2
  44. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
  45. package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
  46. package/src/components/custom/Workout/DeviationBar.tsx +3 -2
  47. package/src/components/custom/Workout/ExerciseCard.test.tsx +3 -3
  48. package/src/components/custom/Workout/ExerciseCard.tsx +2 -2
  49. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -4
  50. package/src/components/custom/Workout/InputBar.tsx +2 -2
  51. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
  52. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
  53. package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
  54. package/src/components/custom/Workout/MesoCard.tsx +3 -1
  55. package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
  56. package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
  57. package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
  58. package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
  59. package/src/components/custom/Workout/RestTimer.tsx +2 -2
  60. package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
  61. package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
  62. package/src/components/custom/Workout/SessionHeader.tsx +1 -1
  63. package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
  64. package/src/components/custom/Workout/SessionRail.tsx +13 -5
  65. package/src/components/custom/Workout/SetBar.tsx +3 -3
  66. package/src/components/custom/Workout/SetRow.test.tsx +6 -3
  67. package/src/components/custom/Workout/StatusDot.tsx +5 -4
  68. package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
  69. package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -2
  70. package/src/components/custom/Workout/VelocityStrip.test.tsx +5 -2
  71. package/src/components/custom/Workout/VelocityStrip.tsx +4 -4
  72. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
  73. package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
  74. package/src/components/custom/Workout/WeightBadge.tsx +2 -1
  75. package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
  76. package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
  77. package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
  78. package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
  79. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  80. package/src/components/custom/charts/SetBarChart.tsx +3 -3
  81. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  82. package/src/components/custom/stepper/Stepper.tsx +3 -3
  83. package/src/components/shell/DeviceMenu.tsx +2 -2
  84. package/src/components/shell/SideNav.tsx +1 -1
  85. package/src/components/shell/TopBar.tsx +1 -1
  86. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  87. package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
  88. package/src/components/ui/avatar/Avatar.tsx +2 -2
  89. package/src/components/ui/badge/Badge.tsx +2 -2
  90. package/src/components/ui/card/Card.tsx +3 -3
  91. package/src/components/ui/checkbox/Checkbox.tsx +1 -1
  92. package/src/components/ui/collapse/Collapse.tsx +1 -1
  93. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  94. package/src/components/ui/drawer/Drawer.tsx +3 -3
  95. package/src/components/ui/form-field/FormField.tsx +1 -1
  96. package/src/components/ui/help-tip/HelpTip.tsx +1 -1
  97. package/src/components/ui/menu/Menu.tsx +2 -2
  98. package/src/components/ui/pill/Pill.tsx +2 -2
  99. package/src/components/ui/popover/Popover.stories.tsx +2 -2
  100. package/src/components/ui/popover/Popover.tsx +1 -1
  101. package/src/components/ui/progress/Progress.tsx +3 -3
  102. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  103. package/src/components/ui/radio/Radio.tsx +4 -4
  104. package/src/components/ui/select/Select.tsx +3 -3
  105. package/src/components/ui/surface/Surface.stories.tsx +3 -3
  106. package/src/components/ui/surface/Surface.test.tsx +32 -19
  107. package/src/components/ui/surface/Surface.tsx +3 -3
  108. package/src/components/ui/surface/SurfaceContext.ts +19 -29
  109. package/src/components/ui/surface/surface.contract.test.ts +57 -44
  110. package/src/components/ui/switch/Switch.tsx +1 -1
  111. package/src/components/ui/tabs/Tabs.tsx +2 -2
  112. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
  113. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
  114. package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
  115. package/src/stories/PresetShowcase.stories.tsx +21 -21
  116. package/src/theme/ColorPalettes.stories.tsx +41 -38
  117. package/src/theme/ColorPrimitives.stories.tsx +89 -41
  118. package/src/theme/Depth.stories.tsx +312 -0
  119. package/src/theme/DepthCalibration.stories.tsx +655 -0
  120. package/src/theme/ThemeProvider.tsx +1 -1
  121. package/src/theme/barrel.ts +7 -14
  122. package/src/theme/color-stories.coverage.test.ts +15 -20
  123. package/src/theme/color-story-kit.tsx +1 -1
  124. package/src/theme/color-utils.ts +197 -0
  125. package/src/theme/config.ts +0 -8
  126. package/src/theme/depth-calibration.test.tsx +97 -0
  127. package/src/theme/elevation.test.ts +62 -1
  128. package/src/theme/elevation.ts +34 -8
  129. package/src/theme/global.css +47 -53
  130. package/src/theme/grey-ramp.test.ts +166 -0
  131. package/src/theme/materials.test.ts +106 -0
  132. package/src/theme/materials.ts +211 -0
  133. package/src/theme/presets/audiobook.ts +4 -4
  134. package/src/theme/presets/types.ts +3 -3
  135. package/src/theme/semantic-contrast.test.ts +83 -0
  136. package/src/theme/tokens/primitives.ts +62 -115
  137. package/src/theme/tokens/semantic.ts +130 -82
  138. package/src/utils/colors.ts +1 -1
  139. package/tailwind.config.js +2 -5
  140. package/src/theme/bar-paper.ts +0 -59
  141. package/src/theme/shadows.stories.tsx +0 -523
  142. package/src/theme/shadows.ts +0 -439
@@ -10,7 +10,7 @@ const t = getSemanticColors('dark')
10
10
  * `SessionRail` (shell organism) — the live-workout exercise list: a flat raised
11
11
  * `SessionHeader` glance (title, stat tiles, chunked pace bar) over a sunk, inset list
12
12
  * of ExerciseCard rail headings. The header plan (chunk widths ∝ sets) is derived from
13
- * `exercises`. Surfaces bind to the charcoal ramp; the list depth is a subtle neumorphic
13
+ * `exercises`. Surfaces bind to the grey ramp; the list depth is a subtle neumorphic
14
14
  * inset. Driven entirely by props.
15
15
  */
16
16
  const meta: Meta<typeof SessionRail> = {
@@ -37,7 +37,7 @@ const meta: Meta<typeof SessionRail> = {
37
37
  description: {
38
38
  component:
39
39
  '**Organism** (shell S3). The live-workout exercise list: raised header glance over ' +
40
- 'a sunk inset list, subtle neumorphic depth (charcoal ramp + `neumorphicShadows`). ' +
40
+ 'a sunk inset list, subtle neumorphic depth (grey ramp + `neumorphicShadows`). ' +
41
41
  'Composes ' +
42
42
  '[SessionHeader](?path=/docs/shell-sessionrail-sessionheader--docs) + ' +
43
43
  '[ExerciseCardHeading](?path=/docs/workout-exercisecardheading--docs) × N. ' +
@@ -1,7 +1,8 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { View, type ViewProps } from 'react-native'
3
- import { primitiveColors } from '../../../theme/tokens/primitives'
4
- import { neumorphicShadows } from '../../../theme/shadows'
3
+ import { greyRamp } from '../../../theme/tokens/primitives'
4
+ import { insetWell } from '../../../theme/materials'
5
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
5
6
  import { Surface } from '../../ui/surface'
6
7
  import { ExerciseCardHeading } from './ExerciseCardHeading'
7
8
  import { ExerciseCard } from './ExerciseCard'
@@ -17,7 +18,9 @@ import type { ExerciseIndicatorKind } from './ExerciseIndicator'
17
18
  // recessed via its inner shadow yet paler than the header, so the transparent
18
19
  // exercise headings on it never blend into the header plane. Surface owns both backgrounds,
19
20
  // so the rail no longer hand-sets them.
20
- const DIVIDER = primitiveColors.charcoal[300] // #2C2C2C — row divider (raised to read on surface-elevated #2A2827)
21
+ const SEMANTIC = getSemanticColors('dark')
22
+ /** Row divider — one step up from the well it sits in so the line reads. */
23
+ const DIVIDER = greyRamp[875]
21
24
 
22
25
  const DEFAULT_WIDTH = 246
23
26
 
@@ -124,9 +127,14 @@ export function SessionRail({
124
127
  next={next}
125
128
  />
126
129
 
130
+ {/* The list is a WELL cut into the rail: one plane down from the rail's
131
+ `elevated`, recessed by the shared inset material rather than by a
132
+ neumorphic pressed shadow (which had no room to read at this
133
+ lightness). `level` follows the well's tone so on-surface text
134
+ resolves against what is actually painted. */}
127
135
  <Surface
128
- level="elevated"
129
- style={[{ flex: 1 }, neumorphicShadows.charcoal.pressed.subtle]}
136
+ level="base"
137
+ style={[{ flex: 1 }, insetWell(SEMANTIC['surface-base'])]}
130
138
  testID="session-rail-list"
131
139
  >
132
140
  {exercises.map((ex, i) => (
@@ -1,5 +1,5 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
- import { primitiveColors, primitiveRamps } from '../../../theme/tokens/primitives'
2
+ import { primitiveRamps, greyRamp } from '../../../theme/tokens/primitives'
3
3
  import { SegmentedBar, type SegmentedBarSegment } from './SegmentedBar'
4
4
 
5
5
  /**
@@ -13,8 +13,8 @@ export const SET_STRIP_ZONES = {
13
13
  fastest: primitiveRamps.green[300],
14
14
  } as const
15
15
 
16
- /** Grey fill for planned-but-unperformed reps / upcoming sets (charcoal placeholder). */
17
- const TODO_COLOR = primitiveColors.charcoal[300]
16
+ /** Grey fill for planned-but-unperformed reps / upcoming sets (grey placeholder). */
17
+ const TODO_COLOR = greyRamp[900]
18
18
 
19
19
  /**
20
20
  * The learned "variable / unknown / opportunity" semantic (cyan-900, a real ramp
@@ -3,10 +3,13 @@ import { render, screen } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
4
  import { View } from 'react-native'
5
5
  import { SetRow, type SetRowProps } from './SetRow'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
6
7
 
7
- // Literal-hex text treatments (dark theme neutral 100 / 400).
8
- const T_ACTIVE = '#F3F4F6'
9
- const T_MUTED = '#9CA3AF'
8
+ // Text treatments, read from the tokens rather than pinned as hexes. These
9
+ // used to be hand-copied literals and silently desynced when the greys moved
10
+ // to the warm ramp (TD-07.14). The assertion still bites — it checks the
11
+ // component reaches for the right ROLE — it just no longer restates the value.
12
+ const { 'text-primary': T_ACTIVE, 'text-secondary': T_MUTED } = getSemanticColors('dark')
10
13
 
11
14
  const doneRow: SetRowProps = {
12
15
  state: 'done',
@@ -1,6 +1,7 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { View, Text, type ViewProps } from 'react-native'
3
3
  import { getSemanticColors } from '../../../theme/tokens/semantic'
4
+ import { greyRamp } from '../../../theme/tokens/primitives'
4
5
 
5
6
  const t = getSemanticColors('dark')
6
7
 
@@ -28,7 +29,7 @@ const solidVariantColors: Record<string, string> = {
28
29
  success: t['status-success'],
29
30
  warning: t['status-warning'],
30
31
  error: t['status-error'],
31
- neutral: '#6B7280',
32
+ neutral: greyRamp[500],
32
33
  }
33
34
 
34
35
  const ringVariantStyles: Record<string, Record<string, unknown>> = {
@@ -71,10 +72,10 @@ const iconColors: Record<StatusDotVariant, string> = {
71
72
  success: '#0A5C52',
72
73
  warning: '#6B4000',
73
74
  error: '#5C1A1A',
74
- neutral: '#D1D5DB',
75
+ neutral: greyRamp[200],
75
76
  'on-track': t['status-success'],
76
77
  deviation: t['status-warning'],
77
- future: '#6B7280',
78
+ future: greyRamp[500],
78
79
  }
79
80
 
80
81
  const sizeDimensions = {
@@ -146,7 +147,7 @@ export function StatusDot({
146
147
  >
147
148
  <View accessibilityElementsHidden>{dot}</View>
148
149
  <Text
149
- style={{ fontSize: 12, lineHeight: 16, color: '#9CA3AF' }}
150
+ style={{ fontSize: 12, lineHeight: 16, color: greyRamp[400] }}
150
151
  accessibilityElementsHidden
151
152
  >
152
153
  {label}
@@ -477,7 +477,7 @@ export function StrengthTrendChart({
477
477
  <View
478
478
  testID="strength-trend-chart-tooltip"
479
479
  accessibilityElementsHidden
480
- className="bg-surface-elevated border-border-strong"
480
+ className="bg-surface-elevated border-hairline-strong"
481
481
  style={{
482
482
  position: 'absolute',
483
483
  left: Math.max(
@@ -114,7 +114,7 @@ function SummaryCards({ summary }: { summary: TrainingStatusSummary }) {
114
114
  return (
115
115
  <View
116
116
  key={key}
117
- className="bg-surface-elevated border-border"
117
+ className="bg-surface-elevated border-hairline"
118
118
  style={{
119
119
  flexGrow: 1,
120
120
  flexBasis: '46%',
@@ -265,7 +265,7 @@ export function TrainingStatusPage({
265
265
  <SummaryCards summary={summary} />
266
266
 
267
267
  <View
268
- className="bg-surface-elevated border-border"
268
+ className="bg-surface-elevated border-hairline"
269
269
  style={{
270
270
  borderWidth: 1,
271
271
  borderRadius: 12,
@@ -414,8 +414,11 @@ describe('VelocityStrip all-zero velocities (NaN guard)', () => {
414
414
 
415
415
  // Literal-hex slot colors (must match the component's tokens exactly).
416
416
  // TODO_SOLID = the LANDED surface-relative solid to-do tone (base/dark plane blended toward the
417
- // on-surface neutral) — replaced the old fixed charcoal #2C2C2C. Solid fill, not a dashed outline.
418
- const TODO_SOLID = '#4C4E55'
417
+ // on-surface neutral) — replaced the old fixed grey #2C2C2C. Solid fill, not a dashed outline.
418
+ // Pinned rather than recomputed on purpose: deriving it here would just mirror
419
+ // SetBarChart's mixHex and stop catching a change to the blend itself.
420
+ // = mixHex(grey-925, text-tertiary, 0.55); moved with the warm ramp (TD-07.14).
421
+ const TODO_SOLID = '#5B5957'
419
422
  const VARIABLE_CYAN = '#0B3149'
420
423
  const CONTINUE_OUTLINE = '#22465F'
421
424
  describe('VelocityStrip set-type modes (compact)', () => {
@@ -2,7 +2,7 @@
2
2
  import { useEffect, useState } from 'react'
3
3
  import { View, Text, Pressable, Animated, type ViewProps, type ViewStyle } from 'react-native'
4
4
  import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
5
- import { primitiveColors, sequentialEffort } from '../../../theme/tokens/primitives'
5
+ import { sequentialEffort, greyRamp } from '../../../theme/tokens/primitives'
6
6
  import { getSemanticColors } from '../../../theme/tokens/semantic'
7
7
  import { alpha } from '../../../utils/colors'
8
8
  import { formatVelocity } from '../../../utils/workout-format'
@@ -456,14 +456,14 @@ const EXPANDED_HEIGHT = 60
456
456
  /** Default `compact` (flat resting strip) height (px) — a THIN radius-2 pill row, the resting glance. */
457
457
  const COMPACT_HEIGHT = 8
458
458
 
459
- /** The dashed running-best reference line color (the lightest charcoal step). */
460
- const HERO_REFERENCE_COLOR = primitiveColors.charcoal[0]
459
+ /** The dashed running-best reference line color (the lightest grey step). */
460
+ const HERO_REFERENCE_COLOR = greyRamp[600]
461
461
 
462
462
  /**
463
463
  * A dashed running-best reference line spanning the plot width, absolutely positioned a
464
464
  * pixel `offset` from the given `anchor` edge. Shared by the single `hero` chart (anchored
465
465
  * to the baseline, `bottom`) and BOTH wings of the diverging dual chart (anchored to the
466
- * centre axis, `top`) so the three sites render one line treatment (charcoal-0, 1px dashed).
466
+ * centre axis, `top`) so the three sites render one line treatment (grey-0, 1px dashed).
467
467
  * Presentation-only + non-interactive — the numeric best lives in each chart's container
468
468
  * accessibility label, so the line itself is hidden from the a11y tree.
469
469
  *
@@ -1,7 +1,7 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { View, Text, type ViewProps } from 'react-native'
3
3
  import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
4
- import { primitiveColors } from '../../../theme/tokens/primitives'
4
+ import { greyRamp } from '../../../theme/tokens/primitives'
5
5
  import { ZoneTrack } from './ZoneTrack'
6
6
  import { DataRow } from '../../ui/data-row/DataRow'
7
7
  import type { VolumeLandmarks } from './muscleTaxonomy'
@@ -11,9 +11,9 @@ import type { VolumeLandmarks } from './muscleTaxonomy'
11
11
  // BodyMap / MesoProgressBar so a muscle reads identically across the app.
12
12
  const HEAT = WORKOUT_TOKENS.heatmap
13
13
 
14
- // The muted, un-reached track colour — the same charcoal step ZoneTrack defaults
14
+ // The muted, un-reached track colour — the same grey step ZoneTrack defaults
15
15
  // to, so the bar sits on the shared gauge-track surface.
16
- const NEUTRAL_TRACK = primitiveColors.charcoal[200]
16
+ const NEUTRAL_TRACK = greyRamp[800]
17
17
  const MONO = 'monospace'
18
18
 
19
19
  export type VolumeZone = 'under' | 'maintenance' | 'productive' | 'approaching' | 'over'
@@ -2,6 +2,11 @@ import { describe, it, expect, vi } from 'vitest'
2
2
  import { render, screen, fireEvent } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
4
  import { WeightBadge } from './WeightBadge'
5
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
6
+
7
+ // Read from the token, not pinned: these used to be hand-copied hexes and
8
+ // silently desynced when borders became alpha hairlines (TD-07.16).
9
+ const T = getSemanticColors('dark')
5
10
 
6
11
  describe('WeightBadge', () => {
7
12
  it('renders value and default unit', () => {
@@ -71,7 +76,7 @@ describe('WeightBadge', () => {
71
76
  it('uses default border color when not a PR', () => {
72
77
  render(<WeightBadge value={225} />)
73
78
  expect(screen.getByTestId('weight-badge')).toHaveStyle({
74
- borderTopColor: '#1F1F1F',
79
+ borderTopColor: T['hairline-default'],
75
80
  })
76
81
  })
77
82
 
@@ -8,6 +8,7 @@ import {
8
8
  type BaseBadgeProps,
9
9
  } from './BaseBadge'
10
10
  import { getSemanticColors } from '../../../theme/tokens/semantic'
11
+ import { greyRamp } from '../../../theme/tokens/primitives'
11
12
 
12
13
  export type WeightBadgeSize = BaseBadgeSize
13
14
 
@@ -43,7 +44,7 @@ export function WeightBadge({
43
44
  ...props
44
45
  }: WeightBadgeProps) {
45
46
  const config = baseBadgeSizeConfig[size]
46
- const textColor = isPr ? getSemanticColors('dark')['brand-primary'] : '#9CA3AF'
47
+ const textColor = isPr ? getSemanticColors('dark')['brand-primary'] : greyRamp[400]
47
48
  // Dumbbell reads clearly as a weight/strength metric and is unit-agnostic
48
49
  // across estimated-1RM and N-rep-max contexts (vs. TrendingUp which implies
49
50
  // change/trend rather than a load value).
@@ -8,6 +8,8 @@ import {
8
8
  } from './MuscleGroupChip'
9
9
  import { getSemanticColors } from '../../../theme/tokens/semantic'
10
10
 
11
+ const HAIRLINE_DEFAULT = getSemanticColors('dark')['hairline-default']
12
+
11
13
  const t = getSemanticColors('dark')
12
14
 
13
15
  export type WorkoutStatus = 'completed' | 'today' | 'upcoming'
@@ -48,7 +50,7 @@ export interface WorkoutCardProps extends ViewProps {
48
50
  const COLORS = {
49
51
  statusSuccess: t['status-success'],
50
52
  brandPrimary: t['brand-primary'],
51
- borderDefault: '#1F1F1F',
53
+ borderDefault: HAIRLINE_DEFAULT,
52
54
  brandPrimarySubtle: 'rgba(255,121,0,0.06)',
53
55
  }
54
56
 
@@ -3,6 +3,9 @@ import { useEffect, useState } from 'react'
3
3
  import { View, Text, Pressable, Animated, Easing, type ViewProps } from 'react-native'
4
4
  import { getSemanticColors } from '../../../theme/tokens/semantic'
5
5
  import { WORKOUT_PILL_DELOAD } from '../../../theme/extracted-colors-dataviz'
6
+ import { greyRamp } from '../../../theme/tokens/primitives'
7
+
8
+ const HAIRLINE_DEFAULT = getSemanticColors('dark')['hairline-default']
6
9
 
7
10
  const t = getSemanticColors('dark')
8
11
 
@@ -35,7 +38,7 @@ const statusBorderStyles: Record<WorkoutPillStatus, Record<string, unknown>> = {
35
38
  completed: { borderWidth: 1, borderColor: 'rgba(46,213,115,0.3)' },
36
39
  current: { borderWidth: 1, borderColor: 'rgba(255,121,0,0.3)' },
37
40
  next: { borderWidth: 1, borderColor: 'rgba(255,121,0,0.4)' },
38
- upcoming: { borderWidth: 1, borderColor: '#1F1F1F' },
41
+ upcoming: { borderWidth: 1, borderColor: HAIRLINE_DEFAULT },
39
42
  missed: { borderWidth: 1, borderColor: 'rgba(209,67,67,0.25)' },
40
43
  deload: { borderWidth: 1, borderColor: 'rgba(186,41,150,0.3)' },
41
44
  }
@@ -44,7 +47,7 @@ const textColors: Record<WorkoutPillStatus, string> = {
44
47
  completed: t['status-success'],
45
48
  current: t['brand-primary'],
46
49
  next: t['brand-primary'],
47
- upcoming: '#6B7280',
50
+ upcoming: greyRamp[500],
48
51
  missed: 'rgba(209,67,67,0.7)',
49
52
  deload: WORKOUT_PILL_DELOAD,
50
53
  }
@@ -2,7 +2,7 @@
2
2
  import { useState } from 'react'
3
3
  import { View, Text, type ViewProps, type DimensionValue, type LayoutChangeEvent } from 'react-native'
4
4
  import { getSemanticColors } from '../../../theme/tokens/semantic'
5
- import { primitiveColors } from '../../../theme/tokens/primitives'
5
+ import { primitiveColors, greyRamp } from '../../../theme/tokens/primitives'
6
6
  import { alpha } from '../../../utils/colors'
7
7
  import { Tooltip } from '../../ui/tooltip/Tooltip'
8
8
 
@@ -13,8 +13,8 @@ function glowShadow(color: string): string {
13
13
  return `0 0 5px 1px ${alpha(color, 0.3)}, 0 0 10px 3px ${alpha(color, 0.12)}`
14
14
  }
15
15
 
16
- /** Muted, un-reached track colour — a charcoal step, matches the IntensityBar track family. */
17
- const DEFAULT_TRACK_COLOR = primitiveColors.charcoal[200]
16
+ /** Muted, un-reached track colour — a grey step, matches the IntensityBar track family. */
17
+ const DEFAULT_TRACK_COLOR = greyRamp[800]
18
18
  /** Default needle / fill-marker colour. */
19
19
  const DEFAULT_MARKER_COLOR = primitiveColors.white
20
20
  /** Tick mark + tick label colour. */
@@ -139,7 +139,7 @@ export interface ZoneTrackProps extends ViewProps {
139
139
  trackHeight?: number
140
140
  /** How far a needle marker overhangs the track top + bottom, in px. Overrides the `size` default (6 default / 9 wall). */
141
141
  needleOverhang?: number
142
- /** Muted colour of the track behind / beyond the fill. Default a charcoal step. */
142
+ /** Muted colour of the track behind / beyond the fill. Default a grey step. */
143
143
  trackColor?: string
144
144
  className?: string
145
145
  }
@@ -5,19 +5,25 @@
5
5
  */
6
6
  import type { ReactNode } from 'react'
7
7
  import { TempoDisplay } from './TempoDisplay'
8
+ import { greyRamp } from '../../../theme/tokens/primitives'
9
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
8
10
 
9
11
  // ---- geometry
10
12
  export const RAIL_W = 232
11
13
  export const SET_GAP = 5
12
14
 
13
- // ---- surface + text tokens (dark theme; charcoal + neutral ramps)
14
- export const GREY = '#2C2C2C' // charcoal 300 upcoming/placeholder
15
- export const BORDER_SUBTLE = '#1C1C1C' // charcoal 500 row dividers
16
- export const T_PRIMARY = '#F3F4F6' // neutral 100
17
- export const T_SECONDARY = '#9CA3AF' // neutral 400
18
- export const T_TERTIARY = '#6B7280' // neutral 500
19
- export const RAISED = '#1F1F1F' // charcoal 400 — nav + heading raised plane
20
- export const INSET = '#131313' // charcoal 800 — sunk exercise-list column
15
+ // ---- surface + text tokens (dark theme)
16
+ // These were hand-copied hexes labelled with their grey/neutral steps, which
17
+ // is exactly how they silently stopped matching the ramps they named. They read
18
+ // from the tokens now, so a retune reaches them.
19
+ const T = getSemanticColors('dark')
20
+ export const GREY = greyRamp[900] // upcoming/placeholder
21
+ export const BORDER_SUBTLE = greyRamp[950] // row dividers
22
+ export const T_PRIMARY = T['text-primary']
23
+ export const T_SECONDARY = T['text-secondary']
24
+ export const T_TERTIARY = T['text-tertiary']
25
+ export const RAISED = greyRamp[925] // nav + heading raised plane
26
+ export const INSET = greyRamp[975] // sunk exercise-list column
21
27
  // depth lives on the sunk list: inner shadow from the heading (top) + nav (left). Raised plane is flat.
22
28
  export const INSET_SHADOW =
23
29
  'inset 0 9px 12px -6px rgba(0,0,0,0.85), inset 7px 0 10px -6px rgba(0,0,0,0.7), inset 0 -1px 0 rgba(255,255,255,0.02)'
@@ -20,13 +20,13 @@ import type { ReactNode } from 'react'
20
20
  import type { Decorator } from '@storybook/react-vite'
21
21
  import { View, Text } from 'react-native'
22
22
  import { VelocityStrip, DualVelocityStrip, type VelocitySet } from './VelocityStrip'
23
- import { surfaceRampDark } from '../../../theme/tokens/primitives'
23
+ import { greyRamp } from '../../../theme/tokens/primitives'
24
24
  import { getSemanticColors } from '../../../theme/tokens/semantic'
25
25
 
26
26
  const t = getSemanticColors('dark')
27
27
 
28
28
  /** The real dark surface-background plane — stories sit on the token, not an ad-hoc grey. */
29
- export const SURFACE_BG = surfaceRampDark.background
29
+ export const SURFACE_BG = greyRamp[950]
30
30
 
31
31
  export const VIEW_LABEL = t['text-secondary']
32
32
  export const VIEW_NOTE = t['text-tertiary']
@@ -20,7 +20,7 @@ import {
20
20
  type LayoutChangeEvent,
21
21
  } from 'react-native'
22
22
  import { primitiveRamps } from '../../../theme/tokens/primitives'
23
- import { barPaper } from '../../../theme/bar-paper'
23
+ import { barPaper } from '../../../theme/materials'
24
24
  import { useOnSurfaceColor, useSurface, surfaceBackground } from '../../ui/surface/SurfaceContext'
25
25
  import { useLiveRepGrowth } from './live-rep-growth'
26
26
 
@@ -313,7 +313,7 @@ export function SetBarChart({
313
313
  const flipStyle = flip ? ({ transform: [{ scaleY: -1 as number }] } as const) : null
314
314
 
315
315
  // Planned/to-do reps + the baseline draw in a SURFACE-relative neutral (on-surface tertiary)
316
- // so they stay legible on every plane instead of a fixed charcoal.
316
+ // so they stay legible on every plane instead of a fixed grey.
317
317
  const placeholderColor = useOnSurfaceColor('tertiary')
318
318
  // The expanded strip's solid to-do tone: the surface plane blended toward the neutral (the same
319
319
  // relative model), so a `todoVariant="solid"` section holds ~constant contrast on every plane.
@@ -590,7 +590,7 @@ export interface SideRailSection {
590
590
  /**
591
591
  * The ONE shared gutter/side-rail lockup used by BOTH the single hero and the diverging dual, so a
592
592
  * single chart is pixel-identical to the dual's upper half — going dual only adds a lower section.
593
- * A fixed-width gutter (34px hero / 18px rail) with the `border-border` right hairline (the vertical
593
+ * A fixed-width gutter (34px hero / 18px rail) with the `border-hairline` right hairline (the vertical
594
594
  * axis) and one rotated, height-responsive, initials-collapsing label per section (each `sectionExtent`
595
595
  * tall). The single passes ONE section; the dual passes two (up + down wings). Rendered null when no
596
596
  * section carries a label. The rotate lives on a wrapper sized to `sectionExtent` so `numberOfLines`
@@ -137,7 +137,7 @@ export const VerticalWithContent: Story = {
137
137
  <Text className="text-text-secondary text-sm">
138
138
  Fill in your profile details to continue.
139
139
  </Text>
140
- <View className="bg-surface-input border border-border rounded-md px-3 py-2">
140
+ <View className="bg-surface-input border border-hairline rounded-md px-3 py-2">
141
141
  <Text className="text-text-secondary text-sm">Display Name</Text>
142
142
  </View>
143
143
  </View>
@@ -91,7 +91,7 @@ function StepConnector({ index }: StepConnectorProps) {
91
91
  <View
92
92
  className={cn(
93
93
  'flex-1 h-0.5 mx-2',
94
- isCompleted ? 'bg-brand-primary' : 'bg-border'
94
+ isCompleted ? 'bg-brand-primary' : 'bg-hairline'
95
95
  )}
96
96
  />
97
97
  )
@@ -101,7 +101,7 @@ function StepConnector({ index }: StepConnectorProps) {
101
101
  <View
102
102
  className={cn(
103
103
  'w-0.5 h-8 ml-4 my-1',
104
- isCompleted ? 'bg-brand-primary' : 'bg-border'
104
+ isCompleted ? 'bg-brand-primary' : 'bg-hairline'
105
105
  )}
106
106
  />
107
107
  )
@@ -171,7 +171,7 @@ export function StepIndicator({
171
171
  const statusStyles: Record<StepStatus, string> = {
172
172
  completed: 'bg-brand-primary border-brand-primary',
173
173
  active: 'bg-brand-primary border-brand-primary',
174
- upcoming: 'bg-surface-base border-border-strong',
174
+ upcoming: 'bg-surface-base border-hairline-strong',
175
175
  error: 'bg-status-error border-status-error',
176
176
  }
177
177
 
@@ -43,7 +43,7 @@ export function DeviceMenu({
43
43
  <PopoverTrigger>
44
44
  <DeviceIndicator status={status} />
45
45
  </PopoverTrigger>
46
- <PopoverContent className="right-0 left-auto mt-[10px] w-[326px] bg-surface-elevated border border-border-strong rounded-[10px] p-[7px]">
46
+ <PopoverContent className="right-0 left-auto mt-[10px] w-[326px] bg-surface-elevated border border-hairline-strong rounded-[10px] p-[7px]">
47
47
  <Typography
48
48
  variant="monoLabel"
49
49
  color="tertiary"
@@ -52,7 +52,7 @@ export function DeviceMenu({
52
52
  Devices — {boundCount} bound · {availCount} available
53
53
  </Typography>
54
54
  {devices.map((device, i) => (
55
- <View key={device.id} className={cn(i > 0 && 'border-t border-border')}>
55
+ <View key={device.id} className={cn(i > 0 && 'border-t border-hairline')}>
56
56
  <DeviceRow
57
57
  device={device}
58
58
  onPress={onSelectDevice ? () => onSelectDevice(device) : undefined}
@@ -55,7 +55,7 @@ export function SideNav({
55
55
  <View
56
56
  accessibilityRole="tablist"
57
57
  className={cn(
58
- 'w-[60px] items-center gap-[6px] border-r border-border bg-background-base py-3',
58
+ 'w-[60px] items-center gap-[6px] border-r border-hairline bg-background-base py-3',
59
59
  className
60
60
  )}
61
61
  >
@@ -58,7 +58,7 @@ export function TopBar({
58
58
  // shared chrome gradient (web); solid bg-surface-elevated is the native fallback
59
59
  style={surfaceGradient.chrome() as object}
60
60
  className={cn(
61
- 'h-[46px] flex-row items-center gap-[14px] px-4 bg-surface-elevated border-b border-border',
61
+ 'h-[46px] flex-row items-center gap-[14px] px-4 bg-surface-elevated border-b border-hairline',
62
62
  className
63
63
  )}
64
64
  >
@@ -287,7 +287,7 @@ export const FormExample: Story = {
287
287
  isRequired
288
288
  />
289
289
 
290
- <View className="pt-2 border-t border-border">
290
+ <View className="pt-2 border-t border-hairline">
291
291
  <Text className="text-sm text-text-secondary">
292
292
  Selected: {country ? `${countryOptions.find(c => c.value === country)?.label}` : '-'}
293
293
  {city ? `, ${cityOptions[country!]?.find(c => c.value === city)?.label}` : ''}
@@ -238,7 +238,7 @@ export function Autocomplete<T extends string = string>({
238
238
  <View
239
239
  className={cn(
240
240
  'absolute z-50 top-full left-0 right-0 mt-1',
241
- 'bg-surface-elevated rounded-md shadow-lg border border-border',
241
+ 'bg-surface-elevated rounded-md shadow-lg border border-hairline',
242
242
  'max-h-60 overflow-hidden'
243
243
  )}
244
244
  >
@@ -67,7 +67,7 @@ export function Avatar({
67
67
  accessibilityLabel={alt || resolvedFallback || 'Avatar'}
68
68
  className={cn(
69
69
  'items-center justify-center rounded-full overflow-hidden',
70
- !nameColor && 'bg-border-strong',
70
+ !nameColor && 'bg-hairline-strong',
71
71
  styles.container,
72
72
  className
73
73
  )}
@@ -102,7 +102,7 @@ const badgeColors: Record<string, string> = {
102
102
  success: 'bg-status-success',
103
103
  error: 'bg-status-error',
104
104
  warning: 'bg-status-warning',
105
- default: 'bg-border-strong',
105
+ default: 'bg-hairline-strong',
106
106
  }
107
107
 
108
108
  /**
@@ -25,7 +25,7 @@ export interface BadgeProps extends ViewProps {
25
25
 
26
26
  const variantColorStyles: Record<BadgeVariant, Record<BadgeColor, string>> = {
27
27
  solid: {
28
- default: 'bg-border-strong text-text-inverse',
28
+ default: 'bg-hairline-strong text-text-inverse',
29
29
  primary: 'bg-brand-primary text-text-inverse',
30
30
  secondary: 'bg-brand-secondary text-text-inverse',
31
31
  success: 'bg-status-success text-text-inverse',
@@ -43,7 +43,7 @@ const variantColorStyles: Record<BadgeVariant, Record<BadgeColor, string>> = {
43
43
  info: 'bg-status-info-subtle text-status-info',
44
44
  },
45
45
  outline: {
46
- default: 'border border-border-strong text-text-secondary',
46
+ default: 'border border-hairline-strong text-text-secondary',
47
47
  primary: 'border border-brand-primary text-brand-primary',
48
48
  secondary: 'border border-brand-secondary text-brand-secondary',
49
49
  success: 'border border-status-success text-status-success',
@@ -45,10 +45,10 @@ export interface CardProps extends ViewProps {
45
45
 
46
46
  const variantStyles: Record<CardVariant, string> = {
47
47
  elevated: '', // Will be set dynamically via elevation system
48
- outline: 'border-2 border-border-strong', // Thicker border with stronger contrast
48
+ outline: 'border-2 border-hairline-strong', // Thicker border with stronger contrast
49
49
  filled: '', // Will be set dynamically via elevation system
50
- accent: 'border border-border',
51
- subtle: 'border border-border-subtle',
50
+ accent: 'border border-hairline',
51
+ subtle: 'border border-hairline-subtle',
52
52
  }
53
53
 
54
54
  /**
@@ -91,7 +91,7 @@ export const Checkbox = forwardRef<View, CheckboxProps>(function Checkbox(
91
91
  styles.box,
92
92
  isChecked || isIndeterminate
93
93
  ? 'bg-brand-primary border-brand-primary'
94
- : 'bg-transparent border-border',
94
+ : 'bg-transparent border-hairline',
95
95
  isInvalid && 'border-status-error',
96
96
  !isDisabled && 'web:hover:border-brand-primary'
97
97
  )}
@@ -180,7 +180,7 @@ export function Accordion({
180
180
 
181
181
  return (
182
182
  <AccordionContext.Provider value={{ openIndices, toggleIndex, allowMultiple }}>
183
- <View className={cn('w-full divide-y divide-border', className)} {...props}>
183
+ <View className={cn('w-full divide-y divide-hairline', className)} {...props}>
184
184
  {React.Children.map(children, (child, index) => {
185
185
  if (React.isValidElement(child)) {
186
186
  return React.cloneElement(child as React.ReactElement<any>, { index })
@@ -73,6 +73,6 @@ export const CustomClassName: Story = {
73
73
  args: {
74
74
  label: 'Bordered Row',
75
75
  value: 'With custom styles',
76
- className: 'border-b border-border px-4',
76
+ className: 'border-b border-hairline px-4',
77
77
  },
78
78
  }
@@ -125,7 +125,7 @@ export function Drawer({
125
125
  >
126
126
  {/* Header */}
127
127
  {(title || showCloseButton) && (
128
- <View className="flex-row items-center justify-between px-4 py-3 border-b border-border">
128
+ <View className="flex-row items-center justify-between px-4 py-3 border-b border-hairline">
129
129
  {title && (
130
130
  <Text className="text-lg font-semibold text-text-primary">
131
131
  {title}
@@ -165,7 +165,7 @@ export interface DrawerHeaderProps extends ViewProps {
165
165
  export function DrawerHeader({ className, children, ...props }: DrawerHeaderProps) {
166
166
  return (
167
167
  <View
168
- className={cn('px-4 py-3 border-b border-border', className)}
168
+ className={cn('px-4 py-3 border-b border-hairline', className)}
169
169
  {...props}
170
170
  >
171
171
  {children}
@@ -217,7 +217,7 @@ export function DrawerFooter({ className, children, ...props }: DrawerFooterProp
217
217
  <View
218
218
  className={cn(
219
219
  'flex-row items-center justify-end gap-3',
220
- 'px-4 py-3 border-t border-border',
220
+ 'px-4 py-3 border-t border-hairline',
221
221
  className
222
222
  )}
223
223
  {...props}