@titan-design/react-ui 0.10.0 → 0.11.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.
- package/dist/index.d.mts +399 -4
- package/dist/index.d.ts +399 -4
- package/dist/index.js +2295 -895
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2279 -905
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
- package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1974 -1740
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1975 -1741
- package/dist/pages.mjs.map +1 -1
- package/package.json +8 -3
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
- package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
- package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
- package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
- package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
- package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
- package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
- package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
- package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
- package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
- package/src/components/custom/Fatigue/README.md +93 -0
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
- package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
- package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
- package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
- package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
- package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
- package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
- package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
- package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
- package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
- package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
- package/src/components/custom/Fatigue/index.ts +41 -0
- package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
- package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseCard.tsx +3 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +7 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +50 -13
- package/src/components/custom/Workout/SetRow.test.tsx +6 -6
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
- package/src/components/custom/Workout/VelocityStrip.test.tsx +196 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
- package/src/components/custom/charts/SetBarChart.tsx +651 -0
- package/src/components/custom/charts/live-rep-growth.ts +91 -0
- package/src/components/custom/index.ts +1 -0
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +871 -0
- package/src/theme/ColorPrimitives.stories.tsx +300 -0
- package/src/theme/bar-paper.ts +59 -0
- package/src/theme/color-stories.coverage.test.ts +130 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
- package/src/theme/ColorSystem.stories.tsx +0 -508
- package/src/theme/Colors.stories.tsx +0 -323
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react'
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
3
|
-
import { View, Text } from 'react-native'
|
|
4
|
-
import {
|
|
5
|
-
primitiveColors,
|
|
6
|
-
primitiveRamps,
|
|
7
|
-
categoricalPalette,
|
|
8
|
-
getCategoricalColor,
|
|
9
|
-
CATEGORICAL_CVD_SAFE_MAX,
|
|
10
|
-
divergingScale,
|
|
11
|
-
sequentialEffort,
|
|
12
|
-
bestTextColor,
|
|
13
|
-
} from './tokens/primitives'
|
|
14
|
-
import { getSemanticColors } from './tokens/semantic'
|
|
15
|
-
|
|
16
|
-
const t = getSemanticColors('dark')
|
|
17
|
-
|
|
18
|
-
const meta: Meta = {
|
|
19
|
-
title: 'Foundations/Color/System',
|
|
20
|
-
tags: ['autodocs'],
|
|
21
|
-
}
|
|
22
|
-
export default meta
|
|
23
|
-
|
|
24
|
-
// ---- color math (self-contained; drives the accessibility panel) ----
|
|
25
|
-
const hexToRgb = (hex: string): [number, number, number] => {
|
|
26
|
-
const h = hex.replace('#', '')
|
|
27
|
-
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16) / 255) as [number, number, number]
|
|
28
|
-
}
|
|
29
|
-
const toHex = (v: number) =>
|
|
30
|
-
Math.round(Math.min(1, Math.max(0, v)) * 255)
|
|
31
|
-
.toString(16)
|
|
32
|
-
.padStart(2, '0')
|
|
33
|
-
const lin = (c: number) => (c >= 0.04045 ? ((c + 0.055) / 1.055) ** 2.4 : c / 12.92)
|
|
34
|
-
const gamma = (c: number) =>
|
|
35
|
-
c >= 0.0031308 ? 1.055 * Math.max(0, c) ** (1 / 2.4) - 0.055 : 12.92 * c
|
|
36
|
-
const relLum = (hex: string) => {
|
|
37
|
-
const [r, g, b] = hexToRgb(hex).map(lin)
|
|
38
|
-
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
39
|
-
}
|
|
40
|
-
const contrast = (a: string, b: string) => {
|
|
41
|
-
const [l1, l2] = [relLum(a), relLum(b)].sort((x, y) => y - x)
|
|
42
|
-
return (l1 + 0.05) / (l2 + 0.05)
|
|
43
|
-
}
|
|
44
|
-
const bestText = (hex: string) =>
|
|
45
|
-
contrast(hex, '#000000') >= contrast(hex, '#FFFFFF') ? '#000000' : '#FFFFFF'
|
|
46
|
-
// Machado-2009 dichromacy simulation (severity 1.0)
|
|
47
|
-
const CVD = {
|
|
48
|
-
deuteranopia: [
|
|
49
|
-
0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.01182, 0.04294, 0.968881,
|
|
50
|
-
],
|
|
51
|
-
protanopia: [
|
|
52
|
-
0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -0.003882, -0.048116, 1.051998,
|
|
53
|
-
],
|
|
54
|
-
} as const
|
|
55
|
-
const simulate = (M: readonly number[], hex: string) => {
|
|
56
|
-
const [r, g, b] = hexToRgb(hex).map(lin)
|
|
57
|
-
const out = [
|
|
58
|
-
M[0] * r + M[1] * g + M[2] * b,
|
|
59
|
-
M[3] * r + M[4] * g + M[5] * b,
|
|
60
|
-
M[6] * r + M[7] * g + M[8] * b,
|
|
61
|
-
].map(gamma)
|
|
62
|
-
return '#' + out.map(toHex).join('')
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const
|
|
66
|
-
|
|
67
|
-
function SectionTitle({ children }: { children: string }) {
|
|
68
|
-
return <Text className="text-lg font-bold text-text-primary mb-3 mt-2">{children}</Text>
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function Ramp({ name, ramp }: { name: string; ramp: Record<number, string> }) {
|
|
72
|
-
return (
|
|
73
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, marginBottom: 6 }}>
|
|
74
|
-
<Text className="text-text-secondary text-xs" style={{ width: 150 }}>
|
|
75
|
-
{name}
|
|
76
|
-
</Text>
|
|
77
|
-
<View
|
|
78
|
-
style={{
|
|
79
|
-
flexDirection: 'row',
|
|
80
|
-
flex: 1,
|
|
81
|
-
borderRadius: 6,
|
|
82
|
-
overflow: 'hidden',
|
|
83
|
-
borderWidth: 1,
|
|
84
|
-
borderColor: '#2C2C2C',
|
|
85
|
-
}}
|
|
86
|
-
>
|
|
87
|
-
{STEPS.map((s) => (
|
|
88
|
-
<View key={s} style={{ flex: 1, height: 30, backgroundColor: ramp[s] }} />
|
|
89
|
-
))}
|
|
90
|
-
</View>
|
|
91
|
-
</View>
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function Chip({ hex, label, sub }: { hex: string; label?: string; sub?: string }) {
|
|
96
|
-
return (
|
|
97
|
-
<View style={{ alignItems: 'center' }}>
|
|
98
|
-
<View
|
|
99
|
-
style={{
|
|
100
|
-
width: 58,
|
|
101
|
-
height: 44,
|
|
102
|
-
borderRadius: 8,
|
|
103
|
-
backgroundColor: hex,
|
|
104
|
-
borderWidth: 1,
|
|
105
|
-
borderColor: '#2C2C2C',
|
|
106
|
-
alignItems: 'center',
|
|
107
|
-
justifyContent: 'center',
|
|
108
|
-
}}
|
|
109
|
-
>
|
|
110
|
-
{label ? (
|
|
111
|
-
<Text style={{ color: bestText(hex), fontSize: 11, fontWeight: '700' }}>{label}</Text>
|
|
112
|
-
) : null}
|
|
113
|
-
</View>
|
|
114
|
-
{sub ? (
|
|
115
|
-
<Text className="text-text-secondary" style={{ fontSize: 8, marginTop: 2 }}>
|
|
116
|
-
{sub}
|
|
117
|
-
</Text>
|
|
118
|
-
) : null}
|
|
119
|
-
</View>
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const RAMP_NAMES: Array<[keyof typeof primitiveRamps, string]> = [
|
|
124
|
-
['red', 'Red'],
|
|
125
|
-
['orange', 'Orange'],
|
|
126
|
-
['amber', 'Amber'],
|
|
127
|
-
['green', 'Green'],
|
|
128
|
-
['cyan', 'Cyan'],
|
|
129
|
-
['blue', 'Blue'],
|
|
130
|
-
['magenta', 'Magenta'],
|
|
131
|
-
]
|
|
132
|
-
|
|
133
|
-
const SEM = {
|
|
134
|
-
success: primitiveRamps.green[300],
|
|
135
|
-
warning: primitiveRamps.amber[300],
|
|
136
|
-
error: primitiveRamps.red[600],
|
|
137
|
-
info: primitiveRamps.blue[500],
|
|
138
|
-
deload: primitiveRamps.magenta[600],
|
|
139
|
-
brand: primitiveRamps.orange[400],
|
|
140
|
-
brandDark: primitiveRamps.orange[600],
|
|
141
|
-
brandSecondary: primitiveRamps.cyan[600],
|
|
142
|
-
spinner: primitiveRamps.cyan[600],
|
|
143
|
-
neutral: primitiveColors.neutral[500],
|
|
144
|
-
} as const
|
|
145
|
-
|
|
146
|
-
// BodyMap training-status diverging scale (from the divergingScale token).
|
|
147
|
-
const D3_STATUS = divergingScale
|
|
148
|
-
|
|
149
|
-
export const Overview: StoryObj = {
|
|
150
|
-
render: () => (
|
|
151
|
-
<View style={{ padding: 24, gap: 6 }}>
|
|
152
|
-
<Text className="text-2xl font-bold text-text-primary mb-1">Color System</Text>
|
|
153
|
-
<Text className="text-text-secondary mb-4">
|
|
154
|
-
Seven OKLCH tonal ramps and an ordered, colorblind-safe categorical palette. Ramps are the
|
|
155
|
-
single source of truth; the categorical references ramp steps.
|
|
156
|
-
</Text>
|
|
157
|
-
|
|
158
|
-
<SectionTitle>Tonal ramps</SectionTitle>
|
|
159
|
-
{RAMP_NAMES.map(([key, name]) => (
|
|
160
|
-
<Ramp key={key} name={name} ramp={primitiveRamps[key]} />
|
|
161
|
-
))}
|
|
162
|
-
|
|
163
|
-
<SectionTitle>Semantic tokens</SectionTitle>
|
|
164
|
-
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 18 }}>
|
|
165
|
-
{Object.entries(SEM).map(([label, hex]) => (
|
|
166
|
-
<View key={label} style={{ alignItems: 'center' }}>
|
|
167
|
-
<Chip hex={hex} />
|
|
168
|
-
<Text className="text-text-secondary" style={{ fontSize: 9, marginTop: 3 }}>
|
|
169
|
-
{label}
|
|
170
|
-
</Text>
|
|
171
|
-
</View>
|
|
172
|
-
))}
|
|
173
|
-
</View>
|
|
174
|
-
|
|
175
|
-
<SectionTitle>Categorical — ordered, nested, CVD-safe to 6</SectionTitle>
|
|
176
|
-
{(['default', 'dark'] as const).map((variant) => (
|
|
177
|
-
<View key={variant} style={{ marginBottom: 10 }}>
|
|
178
|
-
<Text
|
|
179
|
-
className="text-text-secondary text-xs mb-1"
|
|
180
|
-
style={{ textTransform: 'capitalize' }}
|
|
181
|
-
>
|
|
182
|
-
{variant}
|
|
183
|
-
</Text>
|
|
184
|
-
<View style={{ flexDirection: 'row', gap: 6 }}>
|
|
185
|
-
{categoricalPalette[variant].map((hex, i) => (
|
|
186
|
-
<Chip
|
|
187
|
-
key={i}
|
|
188
|
-
hex={hex}
|
|
189
|
-
label={`${i + 1}`}
|
|
190
|
-
sub={i < CATEGORICAL_CVD_SAFE_MAX ? undefined : 'ext'}
|
|
191
|
-
/>
|
|
192
|
-
))}
|
|
193
|
-
</View>
|
|
194
|
-
</View>
|
|
195
|
-
))}
|
|
196
|
-
|
|
197
|
-
<SectionTitle>Data-viz scales</SectionTitle>
|
|
198
|
-
<Text className="text-text-secondary text-xs mb-2">
|
|
199
|
-
Diverging (BodyMap training-status) — light-centered so it reads in lightness and stays
|
|
200
|
-
colorblind-stable; direction is the blue↔red axis. Labels use per-cell best-contrast text.
|
|
201
|
-
</Text>
|
|
202
|
-
<View style={{ flexDirection: 'row', gap: 6, marginBottom: 12 }}>
|
|
203
|
-
{['under', 'maint', 'optimal', 'approach', 'over'].map((l, i) => (
|
|
204
|
-
<View
|
|
205
|
-
key={l}
|
|
206
|
-
style={{
|
|
207
|
-
flex: 1,
|
|
208
|
-
height: 40,
|
|
209
|
-
borderRadius: 6,
|
|
210
|
-
backgroundColor: divergingScale[i],
|
|
211
|
-
alignItems: 'center',
|
|
212
|
-
justifyContent: 'center',
|
|
213
|
-
}}
|
|
214
|
-
>
|
|
215
|
-
<Text
|
|
216
|
-
style={{ color: bestTextColor(divergingScale[i]), fontSize: 10, fontWeight: '700' }}
|
|
217
|
-
>
|
|
218
|
-
{l}
|
|
219
|
-
</Text>
|
|
220
|
-
</View>
|
|
221
|
-
))}
|
|
222
|
-
</View>
|
|
223
|
-
<Text className="text-text-secondary text-xs mb-2">
|
|
224
|
-
Sequential effort (low → high) — an ordinal green→yellow→orange→red scale that IntensityBar
|
|
225
|
-
uses in full and VelocityStrip/RPE sub-samples.
|
|
226
|
-
</Text>
|
|
227
|
-
<View style={{ flexDirection: 'row', gap: 4, marginBottom: 4 }}>
|
|
228
|
-
{sequentialEffort.map((hex, i) => (
|
|
229
|
-
<View
|
|
230
|
-
key={i}
|
|
231
|
-
style={{
|
|
232
|
-
flex: 1,
|
|
233
|
-
height: 34,
|
|
234
|
-
borderRadius: 5,
|
|
235
|
-
backgroundColor: hex,
|
|
236
|
-
alignItems: 'center',
|
|
237
|
-
justifyContent: 'center',
|
|
238
|
-
}}
|
|
239
|
-
>
|
|
240
|
-
<Text style={{ color: bestTextColor(hex), fontSize: 10, fontWeight: '700' }}>
|
|
241
|
-
{i + 1}
|
|
242
|
-
</Text>
|
|
243
|
-
</View>
|
|
244
|
-
))}
|
|
245
|
-
</View>
|
|
246
|
-
|
|
247
|
-
<SectionTitle>Component examples — correct color roles</SectionTitle>
|
|
248
|
-
<View style={{ flexDirection: 'row', gap: 20, flexWrap: 'wrap', alignItems: 'flex-start' }}>
|
|
249
|
-
{/* BodyMap muscle heatmap — training-status diverging (D3) */}
|
|
250
|
-
<Demo label="BodyMap · training status">
|
|
251
|
-
<View style={{ flexDirection: 'row', flexWrap: 'wrap', width: 132, gap: 4 }}>
|
|
252
|
-
{[
|
|
253
|
-
['Chest', 4],
|
|
254
|
-
['Quads', 2],
|
|
255
|
-
['Back', 3],
|
|
256
|
-
['Delts', 0],
|
|
257
|
-
['Biceps', 4],
|
|
258
|
-
['Calves', 1],
|
|
259
|
-
].map(([m, idx]) => (
|
|
260
|
-
<View
|
|
261
|
-
key={m as string}
|
|
262
|
-
style={{
|
|
263
|
-
width: 40,
|
|
264
|
-
backgroundColor: D3_STATUS[idx as number],
|
|
265
|
-
borderRadius: 5,
|
|
266
|
-
paddingVertical: 6,
|
|
267
|
-
}}
|
|
268
|
-
>
|
|
269
|
-
<Text
|
|
270
|
-
style={{
|
|
271
|
-
color: bestTextColor(D3_STATUS[idx as number]),
|
|
272
|
-
fontSize: 8,
|
|
273
|
-
fontWeight: '700',
|
|
274
|
-
textAlign: 'center',
|
|
275
|
-
}}
|
|
276
|
-
>
|
|
277
|
-
{m as string}
|
|
278
|
-
</Text>
|
|
279
|
-
</View>
|
|
280
|
-
))}
|
|
281
|
-
</View>
|
|
282
|
-
</Demo>
|
|
283
|
-
{/* StatusDot — semantic */}
|
|
284
|
-
<Demo label="StatusDot · semantic">
|
|
285
|
-
<View style={{ gap: 6 }}>
|
|
286
|
-
{[
|
|
287
|
-
['ok', SEM.success],
|
|
288
|
-
['warn', SEM.warning],
|
|
289
|
-
['err', SEM.error],
|
|
290
|
-
['neutral', SEM.neutral],
|
|
291
|
-
].map(([l, c]) => (
|
|
292
|
-
<View
|
|
293
|
-
key={l as string}
|
|
294
|
-
style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}
|
|
295
|
-
>
|
|
296
|
-
<View
|
|
297
|
-
style={{ width: 12, height: 12, borderRadius: 6, backgroundColor: c as string }}
|
|
298
|
-
/>
|
|
299
|
-
<Text className="text-text-secondary" style={{ fontSize: 11 }}>
|
|
300
|
-
{l as string}
|
|
301
|
-
</Text>
|
|
302
|
-
</View>
|
|
303
|
-
))}
|
|
304
|
-
</View>
|
|
305
|
-
</Demo>
|
|
306
|
-
{/* WorkoutPill — status */}
|
|
307
|
-
<Demo label="WorkoutPill">
|
|
308
|
-
<View style={{ flexDirection: 'row', gap: 6, flexWrap: 'wrap', maxWidth: 220 }}>
|
|
309
|
-
{[
|
|
310
|
-
['Done', SEM.success],
|
|
311
|
-
['Now', SEM.brand],
|
|
312
|
-
['Next', SEM.neutral],
|
|
313
|
-
['Deload', SEM.deload],
|
|
314
|
-
].map(([t, c]) => (
|
|
315
|
-
<View
|
|
316
|
-
key={t as string}
|
|
317
|
-
style={{
|
|
318
|
-
borderRadius: 99,
|
|
319
|
-
borderWidth: 1,
|
|
320
|
-
borderColor: c as string,
|
|
321
|
-
paddingHorizontal: 10,
|
|
322
|
-
paddingVertical: 3,
|
|
323
|
-
}}
|
|
324
|
-
>
|
|
325
|
-
<Text style={{ color: c as string, fontSize: 11, fontWeight: '700' }}>
|
|
326
|
-
{t as string}
|
|
327
|
-
</Text>
|
|
328
|
-
</View>
|
|
329
|
-
))}
|
|
330
|
-
</View>
|
|
331
|
-
</Demo>
|
|
332
|
-
{/* StrengthTrend legend — series → semantic */}
|
|
333
|
-
<Demo label="StrengthTrend · series">
|
|
334
|
-
<View style={{ flexDirection: 'row', gap: 12, flexWrap: 'wrap' }}>
|
|
335
|
-
{[
|
|
336
|
-
['1RM', SEM.brand],
|
|
337
|
-
['vel', SEM.success],
|
|
338
|
-
['miss', SEM.error],
|
|
339
|
-
['RPE', SEM.warning],
|
|
340
|
-
].map(([l, c]) => (
|
|
341
|
-
<View
|
|
342
|
-
key={l as string}
|
|
343
|
-
style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}
|
|
344
|
-
>
|
|
345
|
-
<View
|
|
346
|
-
style={{ width: 14, height: 3, backgroundColor: c as string, borderRadius: 2 }}
|
|
347
|
-
/>
|
|
348
|
-
<Text className="text-text-secondary" style={{ fontSize: 10 }}>
|
|
349
|
-
{l as string}
|
|
350
|
-
</Text>
|
|
351
|
-
</View>
|
|
352
|
-
))}
|
|
353
|
-
</View>
|
|
354
|
-
</Demo>
|
|
355
|
-
{/* Treemap — ordered categorical */}
|
|
356
|
-
<Demo label="Treemap · categorical">
|
|
357
|
-
<View style={{ flexDirection: 'row', flexWrap: 'wrap', width: 140, gap: 2 }}>
|
|
358
|
-
{[46, 30, 30, 24, 24, 24, 20].map((h, i) => (
|
|
359
|
-
<View
|
|
360
|
-
key={i}
|
|
361
|
-
style={{
|
|
362
|
-
height: h,
|
|
363
|
-
flexGrow: 1,
|
|
364
|
-
flexBasis: h > 40 ? 60 : 40,
|
|
365
|
-
backgroundColor: getCategoricalColor(i),
|
|
366
|
-
borderRadius: 3,
|
|
367
|
-
}}
|
|
368
|
-
/>
|
|
369
|
-
))}
|
|
370
|
-
</View>
|
|
371
|
-
</Demo>
|
|
372
|
-
{/* Spinner */}
|
|
373
|
-
<Demo label="Spinner">
|
|
374
|
-
<View
|
|
375
|
-
style={{
|
|
376
|
-
width: 24,
|
|
377
|
-
height: 24,
|
|
378
|
-
borderRadius: 12,
|
|
379
|
-
borderWidth: 3,
|
|
380
|
-
borderColor: '#2C2C2C',
|
|
381
|
-
borderTopColor: SEM.spinner,
|
|
382
|
-
}}
|
|
383
|
-
/>
|
|
384
|
-
</Demo>
|
|
385
|
-
{/* IntensityBar — full effort scale */}
|
|
386
|
-
<Demo label="IntensityBar · effort">
|
|
387
|
-
<View style={{ flexDirection: 'row', gap: 2, width: 132 }}>
|
|
388
|
-
{sequentialEffort.map((hex, i) => (
|
|
389
|
-
<View
|
|
390
|
-
key={i}
|
|
391
|
-
style={{ flex: 1, height: 20, backgroundColor: hex, borderRadius: 2 }}
|
|
392
|
-
/>
|
|
393
|
-
))}
|
|
394
|
-
</View>
|
|
395
|
-
</Demo>
|
|
396
|
-
{/* VelocityStrip / RPE — effort sub-sample */}
|
|
397
|
-
<Demo label="VelocityStrip · RPE">
|
|
398
|
-
<View style={{ flexDirection: 'row', gap: 3 }}>
|
|
399
|
-
{[
|
|
400
|
-
sequentialEffort[0],
|
|
401
|
-
sequentialEffort[2],
|
|
402
|
-
sequentialEffort[3],
|
|
403
|
-
sequentialEffort[5],
|
|
404
|
-
].map((hex, i) => (
|
|
405
|
-
<View
|
|
406
|
-
key={i}
|
|
407
|
-
style={{
|
|
408
|
-
width: 30,
|
|
409
|
-
height: 26,
|
|
410
|
-
borderRadius: 4,
|
|
411
|
-
backgroundColor: hex,
|
|
412
|
-
alignItems: 'center',
|
|
413
|
-
justifyContent: 'center',
|
|
414
|
-
}}
|
|
415
|
-
>
|
|
416
|
-
<Text style={{ color: bestTextColor(hex), fontSize: 9, fontWeight: '700' }}>
|
|
417
|
-
{['S', 'M', 'F', 'X'][i]}
|
|
418
|
-
</Text>
|
|
419
|
-
</View>
|
|
420
|
-
))}
|
|
421
|
-
</View>
|
|
422
|
-
</Demo>
|
|
423
|
-
</View>
|
|
424
|
-
</View>
|
|
425
|
-
),
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function Demo({ label, children }: { label: string; children: ReactNode }) {
|
|
429
|
-
return (
|
|
430
|
-
<View style={{ padding: 10, backgroundColor: t['surface-raised'], borderRadius: 8 }}>
|
|
431
|
-
<Text className="text-text-secondary" style={{ fontSize: 9, marginBottom: 6 }}>
|
|
432
|
-
{label}
|
|
433
|
-
</Text>
|
|
434
|
-
{children}
|
|
435
|
-
</View>
|
|
436
|
-
)
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
export const Accessibility: StoryObj = {
|
|
440
|
-
render: () => (
|
|
441
|
-
<View style={{ padding: 24 }}>
|
|
442
|
-
<Text className="text-2xl font-bold text-text-primary mb-1">Categorical Accessibility</Text>
|
|
443
|
-
<Text className="text-text-secondary mb-5">
|
|
444
|
-
In-place text contrast and colorblind simulation for the categorical series. Converging
|
|
445
|
-
strips across two swatches signal a collision for that viewer; all pairs hold ΔE ≥ 8 through
|
|
446
|
-
the first {CATEGORICAL_CVD_SAFE_MAX}.
|
|
447
|
-
</Text>
|
|
448
|
-
|
|
449
|
-
{(['default', 'dark'] as const).map((variant) => {
|
|
450
|
-
const colors = categoricalPalette[variant]
|
|
451
|
-
const textColor = variant === 'dark' ? '#FFFFFF' : '#000000'
|
|
452
|
-
return (
|
|
453
|
-
<View key={variant} style={{ marginBottom: 22 }}>
|
|
454
|
-
<Text
|
|
455
|
-
className="text-lg font-bold text-text-primary mb-2"
|
|
456
|
-
style={{ textTransform: 'capitalize' }}
|
|
457
|
-
>
|
|
458
|
-
{variant}
|
|
459
|
-
</Text>
|
|
460
|
-
{/* swatches with in-place text + contrast ratio */}
|
|
461
|
-
<View style={{ flexDirection: 'row', gap: 6, marginBottom: 6 }}>
|
|
462
|
-
{colors.map((hex, i) => (
|
|
463
|
-
<View key={i} style={{ alignItems: 'center' }}>
|
|
464
|
-
<View
|
|
465
|
-
style={{
|
|
466
|
-
width: 56,
|
|
467
|
-
height: 40,
|
|
468
|
-
borderRadius: 6,
|
|
469
|
-
backgroundColor: hex,
|
|
470
|
-
alignItems: 'center',
|
|
471
|
-
justifyContent: 'center',
|
|
472
|
-
}}
|
|
473
|
-
>
|
|
474
|
-
<Text style={{ color: textColor, fontSize: 11, fontWeight: '700' }}>Aa</Text>
|
|
475
|
-
</View>
|
|
476
|
-
<Text className="text-text-secondary" style={{ fontSize: 8, marginTop: 2 }}>
|
|
477
|
-
{contrast(hex, textColor).toFixed(1)}:1
|
|
478
|
-
</Text>
|
|
479
|
-
</View>
|
|
480
|
-
))}
|
|
481
|
-
</View>
|
|
482
|
-
{/* CVD simulation strips */}
|
|
483
|
-
{(['deuteranopia', 'protanopia'] as const).map((mode) => (
|
|
484
|
-
<View key={mode} style={{ flexDirection: 'row', alignItems: 'center', marginTop: 3 }}>
|
|
485
|
-
<Text className="text-text-secondary" style={{ fontSize: 9, width: 96 }}>
|
|
486
|
-
{mode}
|
|
487
|
-
</Text>
|
|
488
|
-
<View style={{ flexDirection: 'row', gap: 6 }}>
|
|
489
|
-
{colors.map((hex, i) => (
|
|
490
|
-
<View
|
|
491
|
-
key={i}
|
|
492
|
-
style={{
|
|
493
|
-
width: 56,
|
|
494
|
-
height: 14,
|
|
495
|
-
borderRadius: 3,
|
|
496
|
-
backgroundColor: simulate(CVD[mode], hex),
|
|
497
|
-
}}
|
|
498
|
-
/>
|
|
499
|
-
))}
|
|
500
|
-
</View>
|
|
501
|
-
</View>
|
|
502
|
-
))}
|
|
503
|
-
</View>
|
|
504
|
-
)
|
|
505
|
-
})}
|
|
506
|
-
</View>
|
|
507
|
-
),
|
|
508
|
-
}
|