@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,186 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { DualVelocityStrip } from './VelocityStrip'
|
|
3
|
+
import {
|
|
4
|
+
LEFT_SLOT,
|
|
5
|
+
RIGHT_SLOT,
|
|
6
|
+
REP_SET,
|
|
7
|
+
REP_SET_LAGGING,
|
|
8
|
+
IN_PROGRESS_SET,
|
|
9
|
+
IN_PROGRESS_LAGGING,
|
|
10
|
+
dualOf,
|
|
11
|
+
wallDecorator,
|
|
12
|
+
} from './velocity-story-kit'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `DualVelocityStrip` — the strip when the exercise used TWO Voltras.
|
|
16
|
+
*
|
|
17
|
+
* Dual is **not a fourth view**, so its scenario coverage lives in the
|
|
18
|
+
* per-view groups, where every scenario renders single ABOVE dual on one
|
|
19
|
+
* dataset:
|
|
20
|
+
* [Compact](?path=/docs/workout-dataviz-velocitystrip-compact--docs) ·
|
|
21
|
+
* [Expanded](?path=/docs/workout-dataviz-velocitystrip-expanded--docs) ·
|
|
22
|
+
* [Hero](?path=/docs/workout-dataviz-velocitystrip-hero--docs).
|
|
23
|
+
*
|
|
24
|
+
* This group exists for the two things a pairing cannot carry: the exported
|
|
25
|
+
* **API** (`DualVelocityStripProps`, in the args table below) and the layout
|
|
26
|
+
* **extremes** — heights at which the labels have to degrade, and mismatched
|
|
27
|
+
* rep counts that must stay index-locked.
|
|
28
|
+
*
|
|
29
|
+
* The `hero` variant is literally two composed `VelocityStrip` heroes — an
|
|
30
|
+
* `orientation="up"` hero over a mirrored `orientation="down"` one, sharing ONE
|
|
31
|
+
* height scale and meeting at one centre axis — so every hero improvement
|
|
32
|
+
* reaches the dual for free. The up wing grows UP and the down wing grows DOWN,
|
|
33
|
+
* so asymmetry reads pre-attentively as the silhouette: a stronger side reads
|
|
34
|
+
* TALLER, while each wing still colours by its OWN best. **Side is POSITION
|
|
35
|
+
* only, never hue.** Each side takes a `DualVelocityStream` (`velocities` OR a
|
|
36
|
+
* structured `set`, plus an optional `label`); the vertical edge label is DATA,
|
|
37
|
+
* not a hardcoded side. Single-slot sets keep using `VelocityStrip`.
|
|
38
|
+
*/
|
|
39
|
+
const meta: Meta<typeof DualVelocityStrip> = {
|
|
40
|
+
title: 'Workout/DataViz/VelocityStrip/Dual',
|
|
41
|
+
component: DualVelocityStrip,
|
|
42
|
+
tags: ['autodocs'],
|
|
43
|
+
decorators: [wallDecorator],
|
|
44
|
+
argTypes: {
|
|
45
|
+
left: {
|
|
46
|
+
control: 'object',
|
|
47
|
+
description:
|
|
48
|
+
'Up-wing stream — `{ velocities | set, label }`. Edit `label` here to change the slot name.',
|
|
49
|
+
},
|
|
50
|
+
right: {
|
|
51
|
+
control: 'object',
|
|
52
|
+
description:
|
|
53
|
+
'Down-wing stream — `{ velocities | set, label }`. Edit `label` here to change the slot name.',
|
|
54
|
+
},
|
|
55
|
+
variant: {
|
|
56
|
+
control: 'inline-radio',
|
|
57
|
+
options: ['hero', 'compact', 'rail'],
|
|
58
|
+
description:
|
|
59
|
+
'hero (wall: labels + reference lines), compact (flat resting fold), or rail (lean, neither)',
|
|
60
|
+
},
|
|
61
|
+
scale: {
|
|
62
|
+
control: 'inline-radio',
|
|
63
|
+
options: ['peak', 'fixed'],
|
|
64
|
+
description:
|
|
65
|
+
'bar scaling, shared across both wings: peak (pair max) or fixed (cross-set ceiling)',
|
|
66
|
+
},
|
|
67
|
+
barColor: {
|
|
68
|
+
control: 'inline-radio',
|
|
69
|
+
options: ['zone', 'loss'],
|
|
70
|
+
description: 'per-wing loss (default) or the shared absolute zone scale; never encodes side',
|
|
71
|
+
},
|
|
72
|
+
targetReps: {
|
|
73
|
+
control: 'number',
|
|
74
|
+
description:
|
|
75
|
+
'planned rep count — reps beyond a side’s done count draw as mirrored dashed stubs',
|
|
76
|
+
},
|
|
77
|
+
liveRepIndex: {
|
|
78
|
+
control: 'number',
|
|
79
|
+
description: 'index of the newest rep; that mirrored pair animates in (hero only)',
|
|
80
|
+
},
|
|
81
|
+
height: {
|
|
82
|
+
control: { type: 'number', min: 60, max: 260, step: 4 },
|
|
83
|
+
description: 'total plot height (px), split evenly into the up (L) and down (R) wings',
|
|
84
|
+
},
|
|
85
|
+
zones: { control: false, description: 'Velocity-zone bands (WA); default scale when absent' },
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
export default meta
|
|
89
|
+
type Story = StoryObj<typeof DualVelocityStrip>
|
|
90
|
+
|
|
91
|
+
// ── The API surface ─────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
/** Controls-driven: flip `variant` / `scale` / `barColor` / `targetReps` / `height`, edit each stream's `label` + data. */
|
|
94
|
+
export const Playground: Story = {
|
|
95
|
+
args: {
|
|
96
|
+
left: dualOf(REP_SET, LEFT_SLOT),
|
|
97
|
+
right: dualOf(REP_SET_LAGGING, RIGHT_SLOT),
|
|
98
|
+
variant: 'hero',
|
|
99
|
+
scale: 'peak',
|
|
100
|
+
height: 200,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── The two canonical states ────────────────────────────────────────────────
|
|
105
|
+
// Deliberately minimal — the scenario pairs in the view groups carry breadth.
|
|
106
|
+
|
|
107
|
+
/** Both sides done, the lagging side one rep short — the everyday finished dual set. */
|
|
108
|
+
export const Default: Story = {
|
|
109
|
+
args: {
|
|
110
|
+
left: dualOf(REP_SET, LEFT_SLOT),
|
|
111
|
+
right: dualOf(REP_SET_LAGGING, RIGHT_SLOT),
|
|
112
|
+
variant: 'hero',
|
|
113
|
+
scale: 'fixed',
|
|
114
|
+
height: 200,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Mid-set and live: 4 of 8 planned on the up wing, 3 on the down, newest rep
|
|
120
|
+
* animating in. The remainder draws as mirrored dashed stubs on both wings, so
|
|
121
|
+
* the columns are reserved and the set never reflows as reps land — which is
|
|
122
|
+
* also why a live dual should run `scale="fixed"`.
|
|
123
|
+
*/
|
|
124
|
+
export const InProgress: Story = {
|
|
125
|
+
args: {
|
|
126
|
+
left: dualOf(IN_PROGRESS_SET, LEFT_SLOT),
|
|
127
|
+
right: dualOf(IN_PROGRESS_LAGGING, RIGHT_SLOT),
|
|
128
|
+
variant: 'hero',
|
|
129
|
+
scale: 'fixed',
|
|
130
|
+
targetReps: 8,
|
|
131
|
+
liveRepIndex: 3,
|
|
132
|
+
height: 200,
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Layout extremes ─────────────────────────────────────────────────────────
|
|
137
|
+
// Recovered from the pre-reorg DualVelocityStrip stories. Unit tests cover the
|
|
138
|
+
// behaviour; these cover the SURFACE — label placement and collision at full
|
|
139
|
+
// width is a class of bug the tests have already missed once (TD-07.10).
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Symmetric index-lock — the up wing logs 5 reps, the down wing 3, same
|
|
143
|
+
* structure. The dual renders 5 aligned columns and the down side's reps 4–5
|
|
144
|
+
* render as EMPTY cells directly under the up side's (index-locked, never
|
|
145
|
+
* shifted). The next rep a lagging side performs lands at its own column.
|
|
146
|
+
*/
|
|
147
|
+
export const SymmetricEmpties: Story = {
|
|
148
|
+
args: {
|
|
149
|
+
left: dualOf([0.9, 0.88, 0.86, 0.84, 0.82], LEFT_SLOT),
|
|
150
|
+
right: dualOf([0.85, 0.8, 0.75], RIGHT_SLOT),
|
|
151
|
+
variant: 'hero',
|
|
152
|
+
scale: 'fixed',
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Small height (120px) — each wing gets ~59px rather than the full hero
|
|
158
|
+
* sentinel, and the responsive text DEGRADES: the per-side slot names collapse
|
|
159
|
+
* to initials ("Left"/"Right" → "L"/"R") and the VL20 / VL30 band labels drop
|
|
160
|
+
* entirely, while the dashed lines + washes stay.
|
|
161
|
+
*/
|
|
162
|
+
export const SmallHeightLabels: Story = {
|
|
163
|
+
args: {
|
|
164
|
+
left: dualOf([0.96, 0.9, 0.83, 0.72], LEFT_SLOT),
|
|
165
|
+
right: dualOf([0.9, 0.82, 0.71, 0.6], RIGHT_SLOT),
|
|
166
|
+
variant: 'hero',
|
|
167
|
+
scale: 'fixed',
|
|
168
|
+
height: 120,
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Very small height (72px) — the floor of graceful degradation. Below the
|
|
174
|
+
* thresholds the VL20 / VL30 labels are gone and the slot names are down to
|
|
175
|
+
* initials (a multi-word name would show its initials, e.g. "L A"). No
|
|
176
|
+
* shrinking into an illegible smear.
|
|
177
|
+
*/
|
|
178
|
+
export const TinyHeight: Story = {
|
|
179
|
+
args: {
|
|
180
|
+
left: dualOf([0.96, 0.9, 0.83, 0.72], LEFT_SLOT),
|
|
181
|
+
right: dualOf([0.9, 0.82, 0.71, 0.6], RIGHT_SLOT),
|
|
182
|
+
variant: 'hero',
|
|
183
|
+
scale: 'fixed',
|
|
184
|
+
height: 72,
|
|
185
|
+
},
|
|
186
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
3
|
+
import { View, Pressable } from 'react-native'
|
|
4
|
+
import { VelocityStrip, DualVelocityStrip } from './VelocityStrip'
|
|
5
|
+
import {
|
|
6
|
+
Sheet,
|
|
7
|
+
Note,
|
|
8
|
+
ViewLabel,
|
|
9
|
+
ScenarioPair,
|
|
10
|
+
SetTypeBoard,
|
|
11
|
+
RepTypeBoard,
|
|
12
|
+
REP_SET,
|
|
13
|
+
REP_SET_LAGGING,
|
|
14
|
+
FATIGUE_SET,
|
|
15
|
+
FATIGUE_SET_LAGGING,
|
|
16
|
+
IN_PROGRESS_SET,
|
|
17
|
+
IN_PROGRESS_LAGGING,
|
|
18
|
+
} from './velocity-story-kit'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `expanded` — the velocity-HEIGHT bar chart. Bars are scaled to their value,
|
|
22
|
+
* rounded on top and flat on the bottom so the set reads as grounded.
|
|
23
|
+
*
|
|
24
|
+
* Chrome is a prop spectrum rather than a variant: with `showNumbers`/`showInfo`
|
|
25
|
+
* ON it is the framed chart (per-bar m/s labels, mean/loss info row, tap to
|
|
26
|
+
* expand); with both OFF it is the bare active-set spotlight. The framed form is
|
|
27
|
+
* SINGLE-ONLY — there is no dual framed chart, and this group does not fake one.
|
|
28
|
+
*
|
|
29
|
+
* The dual expanded is the lean `rail` renderer: value-height wings separated by
|
|
30
|
+
* the shared 2px gap, with no gutter, slot labels or axis. It is the one view
|
|
31
|
+
* where the dual genuinely needs 2x the height, because here height carries
|
|
32
|
+
* meaning.
|
|
33
|
+
*/
|
|
34
|
+
const meta: Meta<typeof VelocityStrip> = {
|
|
35
|
+
title: 'Workout/DataViz/VelocityStrip/Expanded',
|
|
36
|
+
component: VelocityStrip,
|
|
37
|
+
tags: ['autodocs'],
|
|
38
|
+
}
|
|
39
|
+
export default meta
|
|
40
|
+
type Story = StoryObj<typeof VelocityStrip>
|
|
41
|
+
|
|
42
|
+
export const Default: Story = {
|
|
43
|
+
name: 'Default',
|
|
44
|
+
render: () => (
|
|
45
|
+
<Sheet>
|
|
46
|
+
<Note>
|
|
47
|
+
The representative set at value height. Bars sit at identical x-positions to the compact
|
|
48
|
+
view — only the heights change.
|
|
49
|
+
</Note>
|
|
50
|
+
<ScenarioPair
|
|
51
|
+
view="expanded"
|
|
52
|
+
title="Default"
|
|
53
|
+
single={REP_SET}
|
|
54
|
+
left={REP_SET}
|
|
55
|
+
right={REP_SET_LAGGING}
|
|
56
|
+
/>
|
|
57
|
+
</Sheet>
|
|
58
|
+
),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const SetTypes: Story = {
|
|
62
|
+
name: 'Set Types',
|
|
63
|
+
render: () => (
|
|
64
|
+
<Sheet>
|
|
65
|
+
<Note>
|
|
66
|
+
The set-type vocabulary at value height — to-do remainders, the cyan variable window, the
|
|
67
|
+
AMRAP continue window, and drop sub-loads split by the chunk-notch.
|
|
68
|
+
</Note>
|
|
69
|
+
<SetTypeBoard view="expanded" />
|
|
70
|
+
</Sheet>
|
|
71
|
+
),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const InProgress: Story = {
|
|
75
|
+
name: 'In Progress',
|
|
76
|
+
render: () => (
|
|
77
|
+
<Sheet>
|
|
78
|
+
<Note>
|
|
79
|
+
Mid-set: performed reps, the newest bar live, and the planned remainder still to come. Use
|
|
80
|
+
`scale="fixed"` while a set is live so bar heights never reflow as reps land.
|
|
81
|
+
</Note>
|
|
82
|
+
<ScenarioPair
|
|
83
|
+
view="expanded"
|
|
84
|
+
title="4 of 8 done"
|
|
85
|
+
single={IN_PROGRESS_SET}
|
|
86
|
+
left={IN_PROGRESS_SET}
|
|
87
|
+
right={IN_PROGRESS_LAGGING}
|
|
88
|
+
scale="fixed"
|
|
89
|
+
targetReps={8}
|
|
90
|
+
liveRepIndex={3}
|
|
91
|
+
/>
|
|
92
|
+
</Sheet>
|
|
93
|
+
),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const FatigueDecline: Story = {
|
|
97
|
+
name: 'Fatigue Decline',
|
|
98
|
+
render: () => (
|
|
99
|
+
<Sheet>
|
|
100
|
+
<Note>
|
|
101
|
+
Loss colouring against the set's own best. At value height the colour and the height
|
|
102
|
+
agree, which is what makes the compact view legible on colour alone.
|
|
103
|
+
</Note>
|
|
104
|
+
<ScenarioPair
|
|
105
|
+
view="expanded"
|
|
106
|
+
title="Fatigue decline"
|
|
107
|
+
single={FATIGUE_SET}
|
|
108
|
+
left={FATIGUE_SET}
|
|
109
|
+
right={FATIGUE_SET_LAGGING}
|
|
110
|
+
barColor="loss"
|
|
111
|
+
/>
|
|
112
|
+
</Sheet>
|
|
113
|
+
),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const EmptyColdBoot: Story = {
|
|
117
|
+
name: 'Empty / Cold Boot',
|
|
118
|
+
render: () => (
|
|
119
|
+
<Sheet>
|
|
120
|
+
<Note>
|
|
121
|
+
Pre-first-rep. A planned set draws its upcoming reps as placeholders, so the plot holds its
|
|
122
|
+
height AND its columns — the card does not jump, and the bars do not re-space, on rep one.
|
|
123
|
+
</Note>
|
|
124
|
+
<ScenarioPair
|
|
125
|
+
view="expanded"
|
|
126
|
+
title="Cold boot · 6 planned, none logged"
|
|
127
|
+
singleSet={{ type: 'straight', velocities: [], planned: 6 }}
|
|
128
|
+
leftSet={{ type: 'straight', velocities: [], planned: 6 }}
|
|
129
|
+
rightSet={{ type: 'straight', velocities: [], planned: 6 }}
|
|
130
|
+
/>
|
|
131
|
+
<ScenarioPair
|
|
132
|
+
view="expanded"
|
|
133
|
+
title="Truly empty · no plan to draw"
|
|
134
|
+
single={[]}
|
|
135
|
+
left={[]}
|
|
136
|
+
right={[]}
|
|
137
|
+
/>
|
|
138
|
+
</Sheet>
|
|
139
|
+
),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const DualLaggingSide: Story = {
|
|
143
|
+
name: 'Dual · Lagging Side',
|
|
144
|
+
render: () => (
|
|
145
|
+
<Sheet>
|
|
146
|
+
<Note>
|
|
147
|
+
The right slot trails. Its missing reps hold their columns as empties rather than shifting
|
|
148
|
+
the remaining bars left.
|
|
149
|
+
</Note>
|
|
150
|
+
<ScenarioPair
|
|
151
|
+
view="expanded"
|
|
152
|
+
title="Lagging right"
|
|
153
|
+
single={REP_SET}
|
|
154
|
+
left={REP_SET}
|
|
155
|
+
right={REP_SET_LAGGING.slice(0, 2)}
|
|
156
|
+
/>
|
|
157
|
+
</Sheet>
|
|
158
|
+
),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const Responsive: Story = {
|
|
162
|
+
name: 'Responsive',
|
|
163
|
+
render: () => (
|
|
164
|
+
<Sheet width={520}>
|
|
165
|
+
<Note>
|
|
166
|
+
Two ladders. WIDTH first — bars and gaps scale together from the shared layout maths.
|
|
167
|
+
</Note>
|
|
168
|
+
{[440, 300, 180, 110].map((w) => (
|
|
169
|
+
<ScenarioPair
|
|
170
|
+
key={w}
|
|
171
|
+
view="expanded"
|
|
172
|
+
title={`${w}px`}
|
|
173
|
+
width={w}
|
|
174
|
+
single={REP_SET}
|
|
175
|
+
left={REP_SET}
|
|
176
|
+
right={REP_SET_LAGGING}
|
|
177
|
+
/>
|
|
178
|
+
))}
|
|
179
|
+
|
|
180
|
+
<Note>
|
|
181
|
+
Then HEIGHT. The expanded plot compresses toward the compact language as it shrinks — at the
|
|
182
|
+
bottom of the ladder it is effectively the resting strip, which is what makes the
|
|
183
|
+
compact↔expanded toggle a morph rather than a swap.
|
|
184
|
+
</Note>
|
|
185
|
+
{[80, 60, 40, 24, 12].map((h) => (
|
|
186
|
+
<View key={h} style={{ gap: 8 }}>
|
|
187
|
+
<ViewLabel text={`${h}px`} />
|
|
188
|
+
<VelocityStrip
|
|
189
|
+
velocities={REP_SET}
|
|
190
|
+
variant="expanded"
|
|
191
|
+
height={h}
|
|
192
|
+
scale="fixed"
|
|
193
|
+
showNumbers={false}
|
|
194
|
+
showInfo={false}
|
|
195
|
+
/>
|
|
196
|
+
<DualVelocityStrip
|
|
197
|
+
left={{ velocities: REP_SET, label: 'Left' }}
|
|
198
|
+
right={{ velocities: REP_SET_LAGGING, label: 'Right' }}
|
|
199
|
+
variant="rail"
|
|
200
|
+
height={h}
|
|
201
|
+
scale="fixed"
|
|
202
|
+
/>
|
|
203
|
+
</View>
|
|
204
|
+
))}
|
|
205
|
+
</Sheet>
|
|
206
|
+
),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ── Expanded-only: the framed chrome spectrum ───────────────────────────────
|
|
210
|
+
|
|
211
|
+
export const FramedChrome: Story = {
|
|
212
|
+
name: 'Framed Chrome (single-only)',
|
|
213
|
+
render: () => (
|
|
214
|
+
<Sheet width={420}>
|
|
215
|
+
<Note>
|
|
216
|
+
The chrome spectrum, single-only. There is no dual framed chart — the diverging form uses
|
|
217
|
+
the lean rail instead.
|
|
218
|
+
</Note>
|
|
219
|
+
|
|
220
|
+
<View style={{ gap: 8 }}>
|
|
221
|
+
<ViewLabel text="framed · numbers + info" />
|
|
222
|
+
<VelocityStrip velocities={REP_SET} variant="expanded" expanded showNumbers showInfo />
|
|
223
|
+
</View>
|
|
224
|
+
|
|
225
|
+
<View style={{ gap: 8 }}>
|
|
226
|
+
<ViewLabel text="numbers only" />
|
|
227
|
+
<VelocityStrip
|
|
228
|
+
velocities={REP_SET}
|
|
229
|
+
variant="expanded"
|
|
230
|
+
expanded
|
|
231
|
+
showNumbers
|
|
232
|
+
showInfo={false}
|
|
233
|
+
/>
|
|
234
|
+
</View>
|
|
235
|
+
|
|
236
|
+
<View style={{ gap: 8 }}>
|
|
237
|
+
<ViewLabel text="bare spotlight · numbers + info off" />
|
|
238
|
+
<VelocityStrip
|
|
239
|
+
velocities={REP_SET}
|
|
240
|
+
variant="expanded"
|
|
241
|
+
showNumbers={false}
|
|
242
|
+
showInfo={false}
|
|
243
|
+
height={24}
|
|
244
|
+
scale="fixed"
|
|
245
|
+
/>
|
|
246
|
+
</View>
|
|
247
|
+
</Sheet>
|
|
248
|
+
),
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function TapToToggle() {
|
|
252
|
+
const [open, setOpen] = useState(false)
|
|
253
|
+
return (
|
|
254
|
+
<Pressable onPress={() => setOpen((v) => !v)} accessibilityRole="button">
|
|
255
|
+
<VelocityStrip
|
|
256
|
+
velocities={REP_SET}
|
|
257
|
+
variant={open ? 'expanded' : 'compact'}
|
|
258
|
+
height={open ? 60 : 8}
|
|
259
|
+
scale="fixed"
|
|
260
|
+
showNumbers={false}
|
|
261
|
+
showInfo={false}
|
|
262
|
+
/>
|
|
263
|
+
</Pressable>
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export const Interactive: Story = {
|
|
268
|
+
name: 'Interactive · compact ↔ expanded',
|
|
269
|
+
render: () => (
|
|
270
|
+
<Sheet width={420}>
|
|
271
|
+
<Note>
|
|
272
|
+
Tap to toggle between the resting compact strip and the value-height expanded one. Because
|
|
273
|
+
both go through the same geometry, only the bar HEIGHTS change — the columns never move, so
|
|
274
|
+
the transition reads as a morph rather than a swap.
|
|
275
|
+
</Note>
|
|
276
|
+
<TapToToggle />
|
|
277
|
+
<Note>
|
|
278
|
+
Compact sits at its real 8px resting height here, not a padded stand-in — the toggle is only
|
|
279
|
+
honest if the closed state is the size it ships at.
|
|
280
|
+
</Note>
|
|
281
|
+
</Sheet>
|
|
282
|
+
),
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export const RepTypes: Story = {
|
|
286
|
+
name: 'Rep Types',
|
|
287
|
+
render: () => (
|
|
288
|
+
<Sheet>
|
|
289
|
+
<Note>
|
|
290
|
+
Every state a rep column can be in at value height. Here height and colour agree, which is
|
|
291
|
+
the reference the compact view is judged against.
|
|
292
|
+
</Note>
|
|
293
|
+
<RepTypeBoard view="expanded" />
|
|
294
|
+
</Sheet>
|
|
295
|
+
),
|
|
296
|
+
}
|