@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
@@ -0,0 +1,312 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View, Text } from 'react-native'
3
+ import { greyRamp } from './tokens/primitives'
4
+ import { getSemanticColors } from './tokens/semantic'
5
+ import {
6
+ getElevationSurface,
7
+ getElevationShadow,
8
+ getBaseSurfaceColor,
9
+ FLOATING_ELEVATION_MIN,
10
+ type ElevationLevel,
11
+ } from './elevation'
12
+ import { paperSheet, insetWell, barPaper } from './materials'
13
+ import { SectionIntro, SectionTitle } from './color-story-kit'
14
+
15
+ const t = getSemanticColors('dark')
16
+
17
+ /**
18
+ * Foundations/Depth — how a surface says how far away it is.
19
+ *
20
+ * There are exactly three mechanisms, and which one applies is not a taste call:
21
+ *
22
+ * TONE how deep a plane sits — the grey ramp
23
+ * HAIRLINE where one plane ends — alpha, self-normalising
24
+ * MATERIAL what a plane is made of — paper, well
25
+ *
26
+ * Drop-shadow is deliberately not on that list for anything inline. This page
27
+ * exists because the system previously had a fourth mechanism — a neumorphic
28
+ * dual-opposing shadow — that could not work here and was removed in TD-07.16.
29
+ */
30
+ const meta: Meta = {
31
+ title: 'Foundations/Depth',
32
+ tags: ['autodocs'],
33
+ }
34
+ export default meta
35
+
36
+ const CONTENT_LEVELS: ElevationLevel[] = [0, 1, 2, 3]
37
+ const FLOATING_LEVELS: ElevationLevel[] = [4, 5]
38
+ const base = getBaseSurfaceColor('dark')
39
+
40
+ function Swatch({
41
+ label,
42
+ note,
43
+ style,
44
+ }: {
45
+ label: string
46
+ note: string
47
+ style: Record<string, unknown>
48
+ }) {
49
+ return (
50
+ <View style={{ width: 190 }}>
51
+ <View
52
+ style={{
53
+ height: 92,
54
+ borderRadius: 10,
55
+ padding: 10,
56
+ justifyContent: 'flex-end',
57
+ ...style,
58
+ }}
59
+ >
60
+ <Text className="text-text-primary" style={{ fontSize: 12, fontWeight: '600' }}>
61
+ {label}
62
+ </Text>
63
+ </View>
64
+ <Text className="text-text-tertiary" style={{ fontSize: 9, marginTop: 4, lineHeight: 13 }}>
65
+ {note}
66
+ </Text>
67
+ </View>
68
+ )
69
+ }
70
+
71
+ export const Mechanisms: StoryObj = {
72
+ name: '1. The three mechanisms',
73
+ render: () => (
74
+ <View style={{ padding: 24, backgroundColor: greyRamp[975] }}>
75
+ <Text className="text-2xl font-bold text-text-primary mb-2">Depth</Text>
76
+ <SectionIntro>
77
+ Three mechanisms, in the order you should reach for them. A surface that needs to read as
78
+ deeper takes a TONE; one that needs an edge takes a HAIRLINE; one that needs to feel
79
+ physical takes a MATERIAL. Drop-shadow is not on the list for anything that is not
80
+ floating — see section 3 for why.
81
+ </SectionIntro>
82
+
83
+ <SectionTitle>Tone — the grey ramp</SectionTitle>
84
+ <Text className="text-text-secondary text-xs mb-3">
85
+ The primary cue and the only one that works identically on web and native. Every plane is
86
+ a ramp step.
87
+ </Text>
88
+ <View style={{ flexDirection: 'row', gap: 10, marginBottom: 22 }}>
89
+ {[975, 950, 925, 900, 875, 850].map((step) => (
90
+ <Swatch
91
+ key={step}
92
+ label={`grey-${step}`}
93
+ note=""
94
+ style={{ backgroundColor: greyRamp[step as keyof typeof greyRamp] }}
95
+ />
96
+ ))}
97
+ </View>
98
+
99
+ <SectionTitle>Hairline — where a plane ends</SectionTitle>
100
+ <Text className="text-text-secondary text-xs mb-3">
101
+ Alpha-white, so it composites toward white by the same amount on any background and holds
102
+ a near-constant ΔL* wherever it lands. A solid border swings wildly across the ramp; this
103
+ does not. It is the default separator, and it is what replaced the solid dark border
104
+ tokens.
105
+ </Text>
106
+ <View style={{ flexDirection: 'row', gap: 10, marginBottom: 22 }}>
107
+ {(['hairline-subtle', 'hairline-default', 'hairline-strong'] as const).map((token) => (
108
+ <Swatch
109
+ key={token}
110
+ label={token.replace('hairline-', '')}
111
+ note={t[token]}
112
+ style={{
113
+ backgroundColor: greyRamp[925],
114
+ borderWidth: 1,
115
+ borderColor: t[token],
116
+ }}
117
+ />
118
+ ))}
119
+ </View>
120
+
121
+ <SectionTitle>Material — what a plane is made of</SectionTitle>
122
+ <Text className="text-text-secondary text-xs mb-3">
123
+ Used sparingly. Paper is for HERO surfaces only and takes muted tones only — vivid colour
124
+ is ink ON paper, never the paper itself. There are no levels of paper; one treatment, or
125
+ the texture stops meaning anything.
126
+ </Text>
127
+ <View style={{ flexDirection: 'row', gap: 10, marginBottom: 8 }}>
128
+ <Swatch
129
+ label="paperSheet"
130
+ note="tonal fill (ΔL* ≤ 3) + grain + dither + rim + contact shadow. Hero surfaces."
131
+ style={paperSheet(greyRamp[875]) as Record<string, unknown>}
132
+ />
133
+ <Swatch
134
+ label="insetWell"
135
+ note="inner top cut + a light line on the floor. The floor rim is the half that says 'below'."
136
+ style={insetWell(greyRamp[950]) as Record<string, unknown>}
137
+ />
138
+ <Swatch
139
+ label="barPaper"
140
+ note="the same grain curve, tuned for a small saturated element."
141
+ style={{ ...(barPaper(t['status-info']) as Record<string, unknown>), backgroundColor: t['status-info'] }}
142
+ />
143
+ </View>
144
+ <Text className="text-text-tertiary text-xs">
145
+ Materials are web/RNW only — `backgroundImage` and multi-layer `boxShadow` are ignored on
146
+ native. Every material keeps a solid `backgroundColor` underneath so a surface never
147
+ depends on one, and a material must never be the only thing carrying meaning.
148
+ </Text>
149
+ </View>
150
+ ),
151
+ }
152
+
153
+ export const Elevation: StoryObj = {
154
+ name: '2. Elevation — tone below, shadow only when floating',
155
+ render: () => (
156
+ <View style={{ padding: 24, backgroundColor: greyRamp[975] }}>
157
+ <Text className="text-2xl font-bold text-text-primary mb-2">Elevation</Text>
158
+ <SectionIntro>
159
+ Levels 0–3 are separated by TONE ALONE. They cast no shadow, because a dark-on-dark drop
160
+ shadow is inert — it costs a composite layer and shows nothing. From{' '}
161
+ <Text className="font-semibold">{FLOATING_ELEVATION_MIN}</Text> up the element is
162
+ genuinely floating over a backdrop, where a large soft shadow describes separation rather
163
+ than rank, and there the shadow returns.
164
+ </SectionIntro>
165
+
166
+ <SectionTitle>Content levels — tone only</SectionTitle>
167
+ <View style={{ flexDirection: 'row', gap: 10, marginBottom: 22 }}>
168
+ {CONTENT_LEVELS.map((lv) => (
169
+ <Swatch
170
+ key={lv}
171
+ label={`elevation ${lv}`}
172
+ note={`${getElevationSurface(base, lv, 'dark')} · no shadow`}
173
+ style={{
174
+ backgroundColor: getElevationSurface(base, lv, 'dark'),
175
+ borderWidth: 1,
176
+ borderColor: t['hairline-default'],
177
+ ...(getElevationShadow(base, lv, 'dark') as Record<string, unknown>),
178
+ }}
179
+ />
180
+ ))}
181
+ </View>
182
+
183
+ <SectionTitle>Floating levels — shadow returns, always with a ring</SectionTitle>
184
+ <View style={{ flexDirection: 'row', gap: 24, marginBottom: 8 }}>
185
+ {FLOATING_LEVELS.map((lv) => (
186
+ <Swatch
187
+ key={lv}
188
+ label={`elevation ${lv}`}
189
+ note={`${getElevationSurface(base, lv, 'dark')} · casts a shadow`}
190
+ style={{
191
+ backgroundColor: getElevationSurface(base, lv, 'dark'),
192
+ borderWidth: 1,
193
+ borderColor: t['hairline-strong'],
194
+ ...(getElevationShadow(base, lv, 'dark') as Record<string, unknown>),
195
+ }}
196
+ />
197
+ ))}
198
+ </View>
199
+ <Text className="text-text-tertiary text-xs">
200
+ A floating surface should carry a hairline ring as well as its shadow — the shadow places
201
+ it above the page, the ring gives it an edge that survives on a panel where the shadow is
202
+ washed out by glare.
203
+ </Text>
204
+ </View>
205
+ ),
206
+ }
207
+
208
+ export const WhyNotNeumorphism: StoryObj = {
209
+ name: '3. Why not neumorphism',
210
+ render: () => (
211
+ <View style={{ padding: 24, backgroundColor: greyRamp[975] }}>
212
+ <Text className="text-2xl font-bold text-text-primary mb-2">Why not neumorphism</Text>
213
+ <SectionIntro>
214
+ The system used to ship a neumorphic builder — a dark shadow bottom-right paired with a
215
+ light rim top-left. It was removed rather than retuned, and the reason is a lightness
216
+ budget rather than a preference, so it is worth being able to see.
217
+ </SectionIntro>
218
+
219
+ <Text className="text-text-secondary text-xs mb-4">
220
+ Neumorphism needs a MID-TONE ground (roughly 75–90% lightness) so the dark half has
221
+ somewhere to fall and the light half has somewhere to rise. Our surfaces sit near 10%.
222
+ Below them there is almost nothing left — so the dark half disappears and the treatment
223
+ collapses to a one-sided rim, which reads as a smudge rather than as depth.
224
+ </Text>
225
+
226
+ <View style={{ flexDirection: 'row', gap: 24 }}>
227
+ <View>
228
+ <Text className="text-text-secondary text-xs mb-2">
229
+ On a mid-tone ground — both halves have room
230
+ </Text>
231
+ <View
232
+ style={{
233
+ backgroundColor: greyRamp[300],
234
+ padding: 26,
235
+ borderRadius: 12,
236
+ flexDirection: 'row',
237
+ gap: 16,
238
+ }}
239
+ >
240
+ <View
241
+ style={
242
+ {
243
+ width: 110,
244
+ height: 66,
245
+ borderRadius: 10,
246
+ backgroundColor: greyRamp[300],
247
+ boxShadow: '3px 3px 6px rgba(0,0,0,0.35), -3px -3px 6px rgba(255,255,255,0.55)',
248
+ } as Record<string, unknown>
249
+ }
250
+ />
251
+ </View>
252
+ </View>
253
+
254
+ <View>
255
+ <Text className="text-text-secondary text-xs mb-2">
256
+ On our ground — the dark half has nowhere to go
257
+ </Text>
258
+ <View
259
+ style={{
260
+ backgroundColor: greyRamp[925],
261
+ padding: 26,
262
+ borderRadius: 12,
263
+ flexDirection: 'row',
264
+ gap: 16,
265
+ }}
266
+ >
267
+ <View
268
+ style={
269
+ {
270
+ width: 110,
271
+ height: 66,
272
+ borderRadius: 10,
273
+ backgroundColor: greyRamp[925],
274
+ boxShadow: '3px 3px 6px rgba(0,0,0,0.35), -3px -3px 6px rgba(255,255,255,0.12)',
275
+ } as Record<string, unknown>
276
+ }
277
+ />
278
+ </View>
279
+ </View>
280
+
281
+ <View>
282
+ <Text className="text-text-secondary text-xs mb-2">What we do instead</Text>
283
+ <View
284
+ style={{
285
+ backgroundColor: greyRamp[925],
286
+ padding: 26,
287
+ borderRadius: 12,
288
+ flexDirection: 'row',
289
+ gap: 16,
290
+ }}
291
+ >
292
+ <View
293
+ style={{
294
+ width: 110,
295
+ height: 66,
296
+ borderRadius: 10,
297
+ backgroundColor: greyRamp[900],
298
+ borderWidth: 1,
299
+ borderColor: t['hairline-default'],
300
+ }}
301
+ />
302
+ </View>
303
+ </View>
304
+ </View>
305
+
306
+ <Text className="text-text-tertiary text-xs mt-4">
307
+ Same treatment, same offsets, different ground. The middle panel is the one that shipped.
308
+ The right panel is one tone step plus a hairline — cheaper, and it survives glare.
309
+ </Text>
310
+ </View>
311
+ ),
312
+ }