@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
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { SetBarChart, sideLabelText, valueLabelFontSize, type SetSlot } from './SetBarChart'
|
|
5
|
+
|
|
6
|
+
const reps = (values: number[]): SetSlot[] => values.map((v) => ({ kind: 'rep', value: v }))
|
|
7
|
+
const silver = () => '#C7CBD1'
|
|
8
|
+
|
|
9
|
+
describe('valueLabelFontSize', () => {
|
|
10
|
+
it('reads the full 12px at the standard single-hero (220) and dual-wing (110) scales', () => {
|
|
11
|
+
expect(valueLabelFontSize(220)).toBe(12)
|
|
12
|
+
expect(valueLabelFontSize(110)).toBe(12)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('floors at 8px on tiny charts (a 72px dual → 36px wing)', () => {
|
|
16
|
+
expect(valueLabelFontSize(36)).toBe(8)
|
|
17
|
+
expect(valueLabelFontSize(60)).toBe(8)
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('sideLabelText', () => {
|
|
22
|
+
it('keeps the full name when the vertical extent has room', () => {
|
|
23
|
+
expect(sideLabelText('Left Arm', 200)).toBe('Left Arm')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('collapses a single-word name to its initial below the threshold', () => {
|
|
27
|
+
expect(sideLabelText('Left', 40)).toBe('L')
|
|
28
|
+
expect(sideLabelText('Right', 40)).toBe('R')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('collapses a multi-word name to per-word initials below the threshold', () => {
|
|
32
|
+
expect(sideLabelText('Left Arm', 40)).toBe('L A')
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('SetBarChart bars', () => {
|
|
37
|
+
it('renders one bar per rep slot with the prefixed testID', () => {
|
|
38
|
+
render(
|
|
39
|
+
<SetBarChart slots={reps([0.9, 0.8, 0.7])} colorFor={silver} height={200} testIDPrefix="t" />
|
|
40
|
+
)
|
|
41
|
+
expect(screen.getAllByTestId(/^t-bar-\d+$/)).toHaveLength(3)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('colors each bar via colorFor(value)', () => {
|
|
45
|
+
render(
|
|
46
|
+
<SetBarChart
|
|
47
|
+
slots={reps([1.0, 0.4])}
|
|
48
|
+
colorFor={(v) => (v >= 0.75 ? '#2ED573' : '#D14343')}
|
|
49
|
+
height={200}
|
|
50
|
+
testIDPrefix="t"
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
expect(screen.getByTestId('t-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
|
|
54
|
+
expect(screen.getByTestId('t-bar-1')).toHaveStyle({ backgroundColor: '#D14343' })
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('shows a per-bar value label only when showValueLabels is on', () => {
|
|
58
|
+
const { rerender } = render(
|
|
59
|
+
<SetBarChart slots={reps([0.9])} colorFor={silver} height={200} testIDPrefix="t" />
|
|
60
|
+
)
|
|
61
|
+
expect(screen.queryByTestId('t-label-0')).not.toBeInTheDocument()
|
|
62
|
+
rerender(
|
|
63
|
+
<SetBarChart
|
|
64
|
+
slots={reps([0.9])}
|
|
65
|
+
colorFor={silver}
|
|
66
|
+
height={200}
|
|
67
|
+
testIDPrefix="t"
|
|
68
|
+
showValueLabels
|
|
69
|
+
formatValue={(v) => v.toFixed(2)}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
expect(screen.getByTestId('t-label-0')).toHaveTextContent('0.90')
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('SetBarChart todo placeholders', () => {
|
|
77
|
+
it('appends dashed todo stubs up to targetReps', () => {
|
|
78
|
+
render(
|
|
79
|
+
<SetBarChart
|
|
80
|
+
slots={reps([0.9, 0.8])}
|
|
81
|
+
colorFor={silver}
|
|
82
|
+
height={200}
|
|
83
|
+
targetReps={5}
|
|
84
|
+
testIDPrefix="t"
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
87
|
+
expect(screen.getAllByTestId(/^t-bar-\d+$/)).toHaveLength(2)
|
|
88
|
+
expect(screen.getAllByTestId('t-slot-todo')).toHaveLength(3)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('appends no todo stubs when the slots already meet targetReps', () => {
|
|
92
|
+
render(
|
|
93
|
+
<SetBarChart
|
|
94
|
+
slots={reps([0.9, 0.8, 0.7])}
|
|
95
|
+
colorFor={silver}
|
|
96
|
+
height={200}
|
|
97
|
+
targetReps={3}
|
|
98
|
+
testIDPrefix="t"
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
expect(screen.queryByTestId('t-slot-todo')).not.toBeInTheDocument()
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('SetBarChart minColumns padding', () => {
|
|
106
|
+
it('pads the rendered columns to minColumns with todo stubs (dual alignment)', () => {
|
|
107
|
+
render(
|
|
108
|
+
<SetBarChart
|
|
109
|
+
slots={reps([0.9, 0.8])}
|
|
110
|
+
colorFor={silver}
|
|
111
|
+
height={200}
|
|
112
|
+
minColumns={6}
|
|
113
|
+
testIDPrefix="t"
|
|
114
|
+
/>
|
|
115
|
+
)
|
|
116
|
+
expect(screen.getAllByTestId(/^t-bar-\d+$/)).toHaveLength(2)
|
|
117
|
+
// 6 columns − 2 rep bars = 4 padded todo cells.
|
|
118
|
+
expect(screen.getAllByTestId('t-slot-todo')).toHaveLength(4)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('does not shrink below the natural slot count when minColumns is smaller', () => {
|
|
122
|
+
render(
|
|
123
|
+
<SetBarChart
|
|
124
|
+
slots={reps([0.9, 0.8, 0.7])}
|
|
125
|
+
colorFor={silver}
|
|
126
|
+
height={200}
|
|
127
|
+
minColumns={1}
|
|
128
|
+
testIDPrefix="t"
|
|
129
|
+
/>
|
|
130
|
+
)
|
|
131
|
+
expect(screen.getAllByTestId(/^t-bar-\d+$/)).toHaveLength(3)
|
|
132
|
+
expect(screen.queryByTestId('t-slot-todo')).not.toBeInTheDocument()
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('SetBarChart todoVariant', () => {
|
|
137
|
+
it('solid (default) draws a filled to-do section (no dashed border)', () => {
|
|
138
|
+
render(
|
|
139
|
+
<SetBarChart
|
|
140
|
+
slots={reps([0.9])}
|
|
141
|
+
colorFor={silver}
|
|
142
|
+
height={200}
|
|
143
|
+
targetReps={2}
|
|
144
|
+
testIDPrefix="t"
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
147
|
+
expect(screen.getByTestId('t-slot-todo')).not.toHaveStyle({ borderTopStyle: 'dashed' })
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('dashed draws a dashed outline to-do stub (ROM keeps this)', () => {
|
|
151
|
+
render(
|
|
152
|
+
<SetBarChart
|
|
153
|
+
slots={reps([0.9])}
|
|
154
|
+
colorFor={silver}
|
|
155
|
+
height={200}
|
|
156
|
+
targetReps={2}
|
|
157
|
+
todoVariant="dashed"
|
|
158
|
+
testIDPrefix="t"
|
|
159
|
+
/>
|
|
160
|
+
)
|
|
161
|
+
expect(screen.getByTestId('t-slot-todo')).toHaveStyle({ borderTopStyle: 'dashed' })
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('SetBarChart empty columns', () => {
|
|
166
|
+
it('renders an empty cell for a rep column with no value (dual index-lock)', () => {
|
|
167
|
+
const slots: SetSlot[] = [
|
|
168
|
+
{ kind: 'rep', value: 0.9 },
|
|
169
|
+
{ kind: 'empty' },
|
|
170
|
+
{ kind: 'rep', value: 0.8 },
|
|
171
|
+
]
|
|
172
|
+
render(<SetBarChart slots={slots} colorFor={silver} height={200} testIDPrefix="t" />)
|
|
173
|
+
expect(screen.getAllByTestId(/^t-bar-\d+$/)).toHaveLength(2)
|
|
174
|
+
expect(screen.getByTestId('t-slot-empty')).toBeInTheDocument()
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('SetBarChart set-type windows', () => {
|
|
179
|
+
it('renders variable and continue window stubs from explicit slots', () => {
|
|
180
|
+
const slots: SetSlot[] = [
|
|
181
|
+
{ kind: 'rep', value: 0.9 },
|
|
182
|
+
{ kind: 'variable' },
|
|
183
|
+
{ kind: 'continue' },
|
|
184
|
+
]
|
|
185
|
+
render(<SetBarChart slots={slots} colorFor={silver} height={200} testIDPrefix="t" />)
|
|
186
|
+
expect(screen.getByTestId('t-slot-variable')).toBeInTheDocument()
|
|
187
|
+
expect(screen.getByTestId('t-slot-continue')).toBeInTheDocument()
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe('SetBarChart reference overlay', () => {
|
|
192
|
+
it('hands the painter the chart geometry (scaleDenom, plotHeight, yOf, best)', () => {
|
|
193
|
+
let captured: { best: number; plotHeight: number } | null = null
|
|
194
|
+
render(
|
|
195
|
+
<SetBarChart
|
|
196
|
+
slots={reps([0.6, 1.0, 0.8])}
|
|
197
|
+
colorFor={silver}
|
|
198
|
+
height={200}
|
|
199
|
+
showValueLabels
|
|
200
|
+
renderReference={(g) => {
|
|
201
|
+
captured = { best: g.best, plotHeight: g.plotHeight }
|
|
202
|
+
return null
|
|
203
|
+
}}
|
|
204
|
+
testIDPrefix="t"
|
|
205
|
+
/>
|
|
206
|
+
)
|
|
207
|
+
expect(captured).not.toBeNull()
|
|
208
|
+
expect(captured!.best).toBeCloseTo(1.0)
|
|
209
|
+
// plotHeight = height − (label-row height 16 + label gap 3) when labels show.
|
|
210
|
+
expect(captured!.plotHeight).toBe(181)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe('SetBarChart shared side-rail (label gutter)', () => {
|
|
215
|
+
it('renders the rotated stream-name label when `label` is set', () => {
|
|
216
|
+
render(
|
|
217
|
+
<SetBarChart
|
|
218
|
+
slots={reps([0.9, 0.8])}
|
|
219
|
+
colorFor={silver}
|
|
220
|
+
height={200}
|
|
221
|
+
label="Left Arm"
|
|
222
|
+
testIDPrefix="t"
|
|
223
|
+
/>
|
|
224
|
+
)
|
|
225
|
+
expect(screen.getByTestId('t-side-label')).toHaveTextContent('Left Arm')
|
|
226
|
+
expect(screen.getByTestId('t-side-label')).toHaveStyle({ textTransform: 'uppercase' })
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('omits the gutter when no `label` is given', () => {
|
|
230
|
+
render(<SetBarChart slots={reps([0.9, 0.8])} colorFor={silver} height={200} testIDPrefix="t" />)
|
|
231
|
+
expect(screen.queryByTestId('t-side-label')).not.toBeInTheDocument()
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('collapses the label to initials at a tiny height (same rule as the dual wings)', () => {
|
|
235
|
+
render(
|
|
236
|
+
<SetBarChart
|
|
237
|
+
slots={reps([0.9])}
|
|
238
|
+
colorFor={silver}
|
|
239
|
+
height={40}
|
|
240
|
+
label="Left"
|
|
241
|
+
testIDPrefix="t"
|
|
242
|
+
/>
|
|
243
|
+
)
|
|
244
|
+
expect(screen.getByTestId('t-side-label')).toHaveTextContent('L')
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
describe('SetBarChart accessibility', () => {
|
|
249
|
+
it('reads as an image with the supplied label', () => {
|
|
250
|
+
render(
|
|
251
|
+
<SetBarChart
|
|
252
|
+
slots={reps([0.9, 0.8])}
|
|
253
|
+
colorFor={silver}
|
|
254
|
+
height={200}
|
|
255
|
+
accessibilityLabel="Depth chart, 2 reps"
|
|
256
|
+
testIDPrefix="t"
|
|
257
|
+
/>
|
|
258
|
+
)
|
|
259
|
+
expect(screen.getByLabelText('Depth chart, 2 reps')).toBeInTheDocument()
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('has no violations', async () => {
|
|
263
|
+
const { container } = render(
|
|
264
|
+
<SetBarChart
|
|
265
|
+
slots={reps([0.9, 0.8, 0.7])}
|
|
266
|
+
colorFor={silver}
|
|
267
|
+
height={200}
|
|
268
|
+
targetReps={5}
|
|
269
|
+
accessibilityLabel="chart"
|
|
270
|
+
testIDPrefix="t"
|
|
271
|
+
/>
|
|
272
|
+
)
|
|
273
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
274
|
+
})
|
|
275
|
+
})
|