@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
@@ -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}
@@ -19,7 +19,7 @@ SideNav ...................... organism — the 60px left rail (Shell/SideNav)
19
19
  └─ (accent bar) .............. bg-brand-primary edge bar on the active item
20
20
  ```
21
21
 
22
- Decisions locked 2026-07-08 (specimen `coordination/design-explorations/shell/S2-sidenav/`): lucide glyphs
22
+ Decisions locked 2026-07-08 (specimen `sources/design/shell/S2-sidenav/`): lucide glyphs
23
23
  (activity · history · layers · figure) · **Plan** label (narrower than "Program") · active = **left accent
24
24
  bar** (short, centered on the icon+label) · **60px** icon+micro-label · live cue = **muted-green label**
25
25
  (`status-live-muted`) on the Live item while a set runs off-Live · four items, no footer, no top-nav. Fixed
@@ -2,7 +2,7 @@
2
2
  * S3 · Session Rail — SPECIMEN (rapid-prototype, not yet a hardened component).
3
3
  *
4
4
  * A faithful React port of the R2/C3 converged rail
5
- * (coordination/design-explorations/drilldown-pass/r2-synthesis/gallery.html), brought into
5
+ * (sources/design/drilldown-pass/r2-synthesis/gallery.html), brought into
6
6
  * titan Storybook so we can iterate with the token system + real titan components + side-by-side.
7
7
  * Raw hex lives here on purpose — it mirrors the gallery 1:1; tokenization happens at harden.
8
8
  *
@@ -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}
@@ -253,7 +253,7 @@ export function FormActions({
253
253
  <View
254
254
  className={cn(
255
255
  'flex-row items-center gap-3 pt-4',
256
- 'border-t border-border mt-4',
256
+ 'border-t border-hairline mt-4',
257
257
  alignStyles[align],
258
258
  className
259
259
  )}
@@ -155,7 +155,7 @@ export function HelpTip({
155
155
  className={cn(
156
156
  'absolute z-50',
157
157
  'bg-surface-elevated rounded-lg shadow-lg',
158
- 'border border-border',
158
+ 'border border-hairline',
159
159
  'px-3 py-2',
160
160
  placementStyles[placement],
161
161
  tooltipClassName
@@ -116,7 +116,7 @@ export function MenuList({ children, className }: MenuListProps) {
116
116
  <View
117
117
  className={cn(
118
118
  'absolute z-50 top-full left-0 mt-1 min-w-[160px]',
119
- 'bg-surface-elevated rounded-lg shadow-lg border border-border',
119
+ 'bg-surface-elevated rounded-lg shadow-lg border border-hairline',
120
120
  'py-1 overflow-hidden',
121
121
  className
122
122
  )}
@@ -195,7 +195,7 @@ export interface MenuDividerProps {
195
195
  * Divider between menu items.
196
196
  */
197
197
  export function MenuDivider({ className }: MenuDividerProps) {
198
- return <View className={cn('h-px bg-border my-1', className)} />
198
+ return <View className={cn('h-px bg-hairline my-1', className)} />
199
199
  }
200
200
 
201
201
  export interface MenuGroupProps {
@@ -27,7 +27,7 @@ export interface PillProps extends ViewProps {
27
27
 
28
28
  const variantColorStyles: Record<PillVariant, Record<PillColor, string>> = {
29
29
  subtle: {
30
- default: 'bg-surface-raised border-border-subtle text-text-secondary',
30
+ default: 'bg-surface-raised border-hairline-subtle text-text-secondary',
31
31
  primary: 'bg-brand-primary/10 border-brand-primary/25 text-brand-primary',
32
32
  secondary: 'bg-brand-secondary/10 border-brand-secondary/25 text-brand-secondary',
33
33
  success: 'bg-status-success/10 border-status-success/25 text-status-success',
@@ -36,7 +36,7 @@ const variantColorStyles: Record<PillVariant, Record<PillColor, string>> = {
36
36
  info: 'bg-status-info/10 border-status-info/25 text-status-info',
37
37
  },
38
38
  outline: {
39
- default: 'border-border text-text-secondary',
39
+ default: 'border-hairline text-text-secondary',
40
40
  primary: 'border-brand-primary text-brand-primary',
41
41
  secondary: 'border-brand-secondary text-brand-secondary',
42
42
  success: 'border-status-success text-status-success',
@@ -152,12 +152,12 @@ export const WithFormContent: Story = {
152
152
  <Text className="text-text-secondary text-xs">
153
153
  Enter your new display name below.
154
154
  </Text>
155
- <View className="bg-surface-input border border-border rounded-md px-3 py-2">
155
+ <View className="bg-surface-input border border-hairline rounded-md px-3 py-2">
156
156
  <Text className="text-text-secondary text-sm">John Doe</Text>
157
157
  </View>
158
158
  <View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
159
159
  <PopoverCloseButton>
160
- <View className="px-3 py-1.5 rounded-md bg-surface-elevated border border-border">
160
+ <View className="px-3 py-1.5 rounded-md bg-surface-elevated border border-hairline">
161
161
  <Text className="text-text-primary text-sm">Cancel</Text>
162
162
  </View>
163
163
  </PopoverCloseButton>
@@ -188,7 +188,7 @@ export function PopoverContent({ children, className }: PopoverContentProps) {
188
188
  <View
189
189
  className={cn(
190
190
  'absolute z-50 min-w-[200px]',
191
- 'bg-surface-elevated rounded-lg shadow-lg border border-border',
191
+ 'bg-surface-elevated rounded-lg shadow-lg border border-hairline',
192
192
  'p-4',
193
193
  placementStyles[placement],
194
194
  className
@@ -230,7 +230,7 @@ export function CircularProgress({
230
230
  cy={center}
231
231
  r={radius}
232
232
  fill="none"
233
- stroke="var(--color-border-default)"
233
+ stroke="var(--color-hairline-default)"
234
234
  strokeWidth={strokeWidth}
235
235
  />
236
236
  {/* Progress arc */}
@@ -325,7 +325,7 @@ export function ProgressSteps({
325
325
  className={cn(
326
326
  'rounded-full',
327
327
  dot,
328
- index <= currentStep ? colorStyles[color] : 'bg-border-strong'
328
+ index <= currentStep ? colorStyles[color] : 'bg-hairline-strong'
329
329
  )}
330
330
  />
331
331
 
@@ -335,7 +335,7 @@ export function ProgressSteps({
335
335
  className={cn(
336
336
  'flex-1 mx-1',
337
337
  connector,
338
- index < currentStep ? colorStyles[color] : 'bg-border-strong'
338
+ index < currentStep ? colorStyles[color] : 'bg-hairline-strong'
339
339
  )}
340
340
  />
341
341
  )}
@@ -188,7 +188,7 @@ export const FormExample: Story = {
188
188
  <Radio value="yearly">Yearly (save 20%)</Radio>
189
189
  </RadioGroup>
190
190
 
191
- <View className="pt-4 border-t border-border">
191
+ <View className="pt-4 border-t border-hairline">
192
192
  <Text className="text-sm text-text-secondary">
193
193
  Selected: {plan} plan, billed {billing}
194
194
  </Text>
@@ -117,10 +117,10 @@ const sizeConfig: Record<RadioSize, { outer: string; inner: string; text: string
117
117
  }
118
118
 
119
119
  const colorStyles: Record<RadioColor, { checked: string; unchecked: string }> = {
120
- primary: { checked: 'border-brand-primary bg-brand-primary', unchecked: 'border-border-strong' },
121
- secondary: { checked: 'border-brand-secondary bg-brand-secondary', unchecked: 'border-border-strong' },
122
- success: { checked: 'border-status-success bg-status-success', unchecked: 'border-border-strong' },
123
- error: { checked: 'border-status-error bg-status-error', unchecked: 'border-border-strong' },
120
+ primary: { checked: 'border-brand-primary bg-brand-primary', unchecked: 'border-hairline-strong' },
121
+ secondary: { checked: 'border-brand-secondary bg-brand-secondary', unchecked: 'border-hairline-strong' },
122
+ success: { checked: 'border-status-success bg-status-success', unchecked: 'border-hairline-strong' },
123
+ error: { checked: 'border-status-error bg-status-error', unchecked: 'border-hairline-strong' },
124
124
  }
125
125
 
126
126
  /**
@@ -145,7 +145,7 @@ export function Select<T extends string = string>({
145
145
  isInvalid
146
146
  ? 'border-border-input-error'
147
147
  : variant === 'filled'
148
- ? 'border-border-subtle'
148
+ ? 'border-hairline-subtle'
149
149
  : 'border-border-input',
150
150
  !isDisabled && !isInvalid && 'web:hover:border-border-input-hover',
151
151
  isOpen && 'border-border-input-focus',
@@ -187,7 +187,7 @@ export function Select<T extends string = string>({
187
187
  <View
188
188
  className={cn(
189
189
  'absolute z-50 top-full left-0 right-0 mt-1',
190
- 'bg-surface-elevated rounded-md shadow-lg border border-border',
190
+ 'bg-surface-elevated rounded-md shadow-lg border border-hairline',
191
191
  'max-h-60 overflow-hidden'
192
192
  )}
193
193
  >
@@ -229,7 +229,7 @@ function SelectOption<T>({ option }: SelectOptionComponentProps<T>) {
229
229
  <View
230
230
  className={cn(
231
231
  'w-4 h-4 mr-3 rounded border',
232
- selected ? 'bg-brand-primary border-brand-primary' : 'border-border-strong'
232
+ selected ? 'bg-brand-primary border-brand-primary' : 'border-hairline-strong'
233
233
  )}
234
234
  >
235
235
  {selected && <Text className="text-white text-xs text-center">✓</Text>}
@@ -15,8 +15,8 @@ const meta: Meta<typeof Surface> = {
15
15
  },
16
16
  level: {
17
17
  control: 'select',
18
- options: ['inset', 'background', 'base', 'elevated', 'raised', 'overlay'],
19
- description: 'Named charcoal plane (flat background from a semantic token)',
18
+ options: ['frame', 'background', 'base', 'elevated', 'raised', 'overlay'],
19
+ description: 'Named grey plane (flat background from a semantic token)',
20
20
  },
21
21
  pressed: {
22
22
  control: 'boolean',
@@ -88,7 +88,7 @@ export const WithGlow: Story = {
88
88
  ),
89
89
  }
90
90
 
91
- // The named-plane model: flat, full-bleed charcoal planes straight from the
91
+ // The named-plane model: flat, full-bleed grey planes straight from the
92
92
  // semantic surface tokens — reaching the darker nav/page shades the numeric
93
93
  // lighten model can't. These back the shell / rail / stage.
94
94
  export const NamedPlanes: Story = {