@titan-design/react-ui 0.6.0 → 0.7.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/bodymap.js +12 -7
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +12 -7
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +855 -124
- package/dist/index.d.ts +855 -124
- package/dist/index.js +2276 -801
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2230 -802
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-8u_4WbL-.d.mts} +280 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-DaWiBOGa.d.ts} +280 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1255 -798
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1255 -798
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +16 -82
- package/dist/theme/index.d.ts +16 -82
- package/dist/theme/index.js +13 -115
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +13 -115
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +14 -6
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +14 -6
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +870 -0
- package/src/arch/annotations.json +48 -0
- package/src/arch/arch-graph.json +4301 -0
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
- package/src/components/custom/Metric/Metric.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
- package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
- package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
- package/src/components/custom/TimerReadout/index.ts +1 -0
- package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
- package/src/components/custom/Typography/Typography.stories.tsx +1 -1
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
- package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
- package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
- package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
- package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
- package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
- package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
- package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
- package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
- package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
- package/src/components/custom/Workout/MetricTiles.tsx +48 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
- package/src/components/custom/Workout/README.md +146 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.tsx +15 -10
- package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
- package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
- package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
- package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
- package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
- package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
- package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
- package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
- package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
- package/src/components/custom/Workout/SessionHeader.tsx +152 -0
- package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
- package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
- package/src/components/custom/Workout/SessionRail.tsx +126 -0
- package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
- package/src/components/custom/Workout/SetBar.test.tsx +123 -0
- package/src/components/custom/Workout/SetBar.tsx +171 -0
- package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
- package/src/components/custom/Workout/SetRow.test.tsx +94 -166
- package/src/components/custom/Workout/SetRow.tsx +169 -232
- package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
- package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
- package/src/components/custom/Workout/SetStrip.tsx +71 -0
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
- package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
- package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
- package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
- package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
- package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
- package/src/components/custom/Workout/StatusPill.tsx +88 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
- package/src/components/custom/Workout/VelocityStrip.test.tsx +376 -141
- package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
- package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
- package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
- package/src/components/custom/Workout/index.ts +55 -2
- package/src/components/custom/Workout/metricText.stories.tsx +69 -0
- package/src/components/custom/Workout/metricText.tsx +34 -0
- package/src/components/custom/Workout/paceTone.test.ts +29 -0
- package/src/components/custom/Workout/paceTone.ts +27 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/icons/icons.stories.tsx +67 -2
- package/src/components/icons/icons.test.tsx +32 -2
- package/src/components/icons/icons.tsx +108 -0
- package/src/components/shell/BrandLockup.stories.tsx +3 -3
- package/src/components/shell/DashboardShell.stories.tsx +65 -0
- package/src/components/shell/DashboardShell.test.tsx +42 -0
- package/src/components/shell/DashboardShell.tsx +74 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
- package/src/components/shell/DeviceMenu.stories.tsx +5 -5
- package/src/components/shell/DeviceRow.stories.tsx +3 -3
- package/src/components/shell/NavItem.stories.tsx +52 -0
- package/src/components/shell/NavItem.test.tsx +35 -0
- package/src/components/shell/NavItem.tsx +75 -0
- package/src/components/shell/README.md +30 -0
- package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
- package/src/components/shell/SessionStatePill.stories.tsx +3 -3
- package/src/components/shell/SessionStatePill.tsx +1 -1
- package/src/components/shell/SideNav.stories.tsx +81 -0
- package/src/components/shell/SideNav.test.tsx +51 -0
- package/src/components/shell/SideNav.tsx +74 -0
- package/src/components/shell/TopBar.stories.tsx +5 -5
- package/src/components/shell/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/badge/Badge.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/button/Button.stories.tsx +1 -1
- package/src/components/ui/card/Card.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/chip/Chip.stories.tsx +1 -1
- package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
- package/src/components/ui/form-field/FormField.stories.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
- package/src/components/ui/indicator/Indicator.test.tsx +1 -0
- package/src/components/ui/indicator/Indicator.tsx +6 -1
- package/src/components/ui/input/Input.stories.tsx +1 -1
- package/src/components/ui/link/Link.stories.tsx +1 -1
- package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
- package/src/components/ui/menu/Menu.stories.tsx +1 -1
- package/src/components/ui/modal/Modal.stories.tsx +1 -1
- package/src/components/ui/pill/Pill.stories.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +1 -1
- package/src/components/ui/select/Select.stories.tsx +1 -1
- package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.stories.tsx +79 -0
- package/src/components/ui/tile/Tile.test.tsx +48 -0
- package/src/components/ui/tile/Tile.tsx +64 -0
- package/src/components/ui/tile/index.ts +2 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTimer.test.ts +150 -0
- package/src/hooks/useTimer.ts +77 -0
- package/src/index.ts +3 -0
- package/src/pages.ts +1 -1
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/stories/ThemePresets.stories.tsx +1 -1
- package/src/theme/ColorSystem.stories.tsx +1 -1
- package/src/theme/Colors.stories.tsx +5 -8
- package/src/theme/config.ts +4 -0
- package/src/theme/elevation.stories.tsx +1 -1
- package/src/theme/global.css +5 -1
- package/src/theme/shadows.stories.tsx +1 -1
- package/src/theme/tokens/primitives.ts +11 -107
- package/src/theme/tokens/semantic.ts +8 -0
- package/src/theme/workout-tokens.ts +11 -7
- package/tailwind.config.js +5 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import { View, Text, type ViewProps } from 'react-native'
|
|
3
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
4
|
+
import { alpha } from '../../../utils/colors'
|
|
5
|
+
import { StatusDot, type StatusDotVariant } from './StatusDot'
|
|
6
|
+
|
|
7
|
+
const t = getSemanticColors('dark')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Auto-regulation verdict — the human-readable read-once summary of a set's
|
|
11
|
+
* velocity-loss state.
|
|
12
|
+
* - `productive` — in the band, keep going (success)
|
|
13
|
+
* - `threshold` — VL20 reached, approaching fatigue (warning)
|
|
14
|
+
* - `stop` — VL28+, terminate the set (error)
|
|
15
|
+
*/
|
|
16
|
+
export type StatusPillStatus = 'productive' | 'threshold' | 'stop'
|
|
17
|
+
|
|
18
|
+
export interface StatusPillProps extends ViewProps {
|
|
19
|
+
status: StatusPillStatus
|
|
20
|
+
/** Overrides the default verdict text for the status. */
|
|
21
|
+
label?: string
|
|
22
|
+
className?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const statusToken: Record<StatusPillStatus, 'status-success' | 'status-warning' | 'status-error'> =
|
|
26
|
+
{
|
|
27
|
+
productive: 'status-success',
|
|
28
|
+
threshold: 'status-warning',
|
|
29
|
+
stop: 'status-error',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const statusDotVariant: Record<StatusPillStatus, StatusDotVariant> = {
|
|
33
|
+
productive: 'success',
|
|
34
|
+
threshold: 'warning',
|
|
35
|
+
stop: 'error',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const defaultLabel: Record<StatusPillStatus, string> = {
|
|
39
|
+
productive: 'Productive',
|
|
40
|
+
threshold: 'Threshold',
|
|
41
|
+
stop: 'Stop',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Resolved semantic color for a verdict status. */
|
|
45
|
+
export function statusPillColor(status: StatusPillStatus): string {
|
|
46
|
+
return t[statusToken[status]]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Verdict pill: a glowing status dot + colored verdict text, in a tinted,
|
|
51
|
+
* bordered capsule. Composes {@link StatusDot} for the dot so the glow/color
|
|
52
|
+
* stays consistent with the atom. Tint and border are derived from the same
|
|
53
|
+
* semantic token via `alpha()` — no per-status raw hex.
|
|
54
|
+
*/
|
|
55
|
+
export function StatusPill({ status, label, className, style, ...props }: StatusPillProps) {
|
|
56
|
+
const color = statusPillColor(status)
|
|
57
|
+
const text = label ?? defaultLabel[status]
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<View
|
|
61
|
+
testID="status-pill"
|
|
62
|
+
className={className}
|
|
63
|
+
style={[
|
|
64
|
+
{
|
|
65
|
+
flexDirection: 'row',
|
|
66
|
+
alignItems: 'center',
|
|
67
|
+
gap: 7,
|
|
68
|
+
alignSelf: 'flex-start',
|
|
69
|
+
paddingVertical: 7,
|
|
70
|
+
paddingHorizontal: 13,
|
|
71
|
+
borderRadius: 9,
|
|
72
|
+
backgroundColor: alpha(color, 0.14),
|
|
73
|
+
borderWidth: 1,
|
|
74
|
+
borderColor: alpha(color, 0.4),
|
|
75
|
+
},
|
|
76
|
+
style,
|
|
77
|
+
]}
|
|
78
|
+
{...props}
|
|
79
|
+
>
|
|
80
|
+
<View accessibilityElementsHidden>
|
|
81
|
+
<StatusDot variant={statusDotVariant[status]} glow />
|
|
82
|
+
</View>
|
|
83
|
+
<Text testID="status-pill-label" style={{ fontSize: 13, fontWeight: '800', color }}>
|
|
84
|
+
{text}
|
|
85
|
+
</Text>
|
|
86
|
+
</View>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -32,7 +32,7 @@ const decline: StrengthTrendDataPoint[] = [
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
const meta: Meta<typeof StrengthTrendChart> = {
|
|
35
|
-
title: '
|
|
35
|
+
title: 'Workout/DataViz/StrengthTrendChart',
|
|
36
36
|
component: StrengthTrendChart,
|
|
37
37
|
tags: ['autodocs'],
|
|
38
38
|
argTypes: {
|
|
@@ -4,7 +4,7 @@ import { SupersetWrapper } from './SupersetWrapper'
|
|
|
4
4
|
import { ExerciseCard } from './ExerciseCard'
|
|
5
5
|
|
|
6
6
|
const meta: Meta<typeof SupersetWrapper> = {
|
|
7
|
-
title: '
|
|
7
|
+
title: 'Workout/SupersetWrapper',
|
|
8
8
|
component: SupersetWrapper,
|
|
9
9
|
tags: ['autodocs'],
|
|
10
10
|
decorators: [
|
|
@@ -5,9 +5,20 @@ import { TempoDisplay, type TempoLivePhase, type TempoLiveState } from './TempoD
|
|
|
5
5
|
|
|
6
6
|
// tempo = [eccentric, pauseBottom, concentric, pauseTop]
|
|
7
7
|
const meta: Meta<typeof TempoDisplay> = {
|
|
8
|
-
title: '
|
|
8
|
+
title: 'Workout/TempoDisplay',
|
|
9
9
|
component: TempoDisplay,
|
|
10
10
|
tags: ['autodocs'],
|
|
11
|
+
parameters: {
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component:
|
|
15
|
+
'**Molecule.** The eccentric/pause/concentric/pause tempo display. Composes ' +
|
|
16
|
+
'[MetricCell](?path=/docs/workout-metriccell--docs) ' +
|
|
17
|
+
'(shared value/separator cell). Used-by ↑ ' +
|
|
18
|
+
'[ExerciseHeading](?path=/docs/workout-exerciseheading--docs) (`showLabel={false}`).',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
11
22
|
argTypes: {
|
|
12
23
|
tempo: {
|
|
13
24
|
control: 'object',
|
|
@@ -5,6 +5,7 @@ import { roundTempo } from '../../../utils/workout-format'
|
|
|
5
5
|
import { alpha } from '../../../utils/colors'
|
|
6
6
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
7
7
|
import { getTempoFillPct, getTempoPacingState } from './TempoBar'
|
|
8
|
+
import { MetricCell } from './metricText'
|
|
8
9
|
|
|
9
10
|
const t = getSemanticColors('dark')
|
|
10
11
|
|
|
@@ -29,6 +30,8 @@ export interface TempoDisplayProps extends ViewProps {
|
|
|
29
30
|
size?: 'sm' | 'md'
|
|
30
31
|
/** Colored phases or mono (all gray) */
|
|
31
32
|
colored?: boolean
|
|
33
|
+
/** Show the "TEMPO" caption before the values. Default true. */
|
|
34
|
+
showLabel?: boolean
|
|
32
35
|
/** Show info tooltip on press */
|
|
33
36
|
showInfo?: boolean
|
|
34
37
|
/**
|
|
@@ -64,33 +67,17 @@ function TempoValue({
|
|
|
64
67
|
fontSize: number
|
|
65
68
|
}) {
|
|
66
69
|
return (
|
|
67
|
-
<
|
|
68
|
-
style={{
|
|
69
|
-
fontFamily: INTER,
|
|
70
|
-
fontSize,
|
|
71
|
-
color,
|
|
72
|
-
fontWeight: '600',
|
|
73
|
-
letterSpacing: 1,
|
|
74
|
-
}}
|
|
75
|
-
>
|
|
70
|
+
<MetricCell color={color} fontSize={fontSize}>
|
|
76
71
|
{value}
|
|
77
|
-
</
|
|
72
|
+
</MetricCell>
|
|
78
73
|
)
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
function TempoSeparator({ color, fontSize }: { color: string; fontSize: number }) {
|
|
82
77
|
return (
|
|
83
|
-
<
|
|
84
|
-
style={{
|
|
85
|
-
fontFamily: INTER,
|
|
86
|
-
fontSize,
|
|
87
|
-
color,
|
|
88
|
-
fontWeight: '600',
|
|
89
|
-
letterSpacing: 1,
|
|
90
|
-
}}
|
|
91
|
-
>
|
|
78
|
+
<MetricCell color={color} fontSize={fontSize}>
|
|
92
79
|
-
|
|
93
|
-
</
|
|
80
|
+
</MetricCell>
|
|
94
81
|
)
|
|
95
82
|
}
|
|
96
83
|
|
|
@@ -170,6 +157,7 @@ export function TempoDisplay({
|
|
|
170
157
|
tempo,
|
|
171
158
|
size = 'md',
|
|
172
159
|
colored = true,
|
|
160
|
+
showLabel = true,
|
|
173
161
|
showInfo = true,
|
|
174
162
|
live,
|
|
175
163
|
onPress,
|
|
@@ -205,19 +193,21 @@ export function TempoDisplay({
|
|
|
205
193
|
}}
|
|
206
194
|
{...props}
|
|
207
195
|
>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
196
|
+
{showLabel && (
|
|
197
|
+
<Text
|
|
198
|
+
style={{
|
|
199
|
+
fontFamily: INTER,
|
|
200
|
+
fontSize: 9,
|
|
201
|
+
fontWeight: '500',
|
|
202
|
+
color: TEXT_TERTIARY,
|
|
203
|
+
letterSpacing: 0.5,
|
|
204
|
+
textTransform: 'uppercase',
|
|
205
|
+
marginRight: 6,
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
TEMPO
|
|
209
|
+
</Text>
|
|
210
|
+
)}
|
|
221
211
|
<View style={{ flexDirection: 'row' }} testID="tempo-value">
|
|
222
212
|
{live ? (
|
|
223
213
|
<LiveTempoRow
|
|
@@ -181,7 +181,7 @@ const muscles: TrainingStatusMuscle[] = [
|
|
|
181
181
|
]
|
|
182
182
|
|
|
183
183
|
const meta: Meta<typeof TrainingStatusPage> = {
|
|
184
|
-
title: '
|
|
184
|
+
title: 'Pages/Training Status',
|
|
185
185
|
component: TrainingStatusPage,
|
|
186
186
|
parameters: { layout: 'fullscreen' },
|
|
187
187
|
tags: ['autodocs'],
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set modalities — every per-rep strength set-type rendered by the REAL
|
|
3
|
+
* {@link VelocityStrip}. One strip model for every set type: it defaults to the
|
|
4
|
+
* target rep count as grey slots, overshoot grows the strip, and open-ended sets
|
|
5
|
+
* (AMRAP · the myo tail) carry a persistent cyan "continue" slot. Each card
|
|
6
|
+
* carries a `Collapse` accordion showing how to configure the `set` prop for that
|
|
7
|
+
* view. Promoted from the Lab/Explorations "Set Modalities" specimen once the
|
|
8
|
+
* vocabulary hardened into VelocityStrip's `set` descriptor.
|
|
9
|
+
*/
|
|
10
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
11
|
+
import { View, Text } from 'react-native'
|
|
12
|
+
import { VelocityStrip, type VelocitySet } from './VelocityStrip'
|
|
13
|
+
import { Collapse, CollapseButton, CollapseContent } from '../../ui/collapse/Collapse'
|
|
14
|
+
import { SET_STRIP_VARIABLE_COLOR as CYAN } from './SetStrip'
|
|
15
|
+
import {
|
|
16
|
+
GREY,
|
|
17
|
+
velColor,
|
|
18
|
+
T_PRIMARY,
|
|
19
|
+
T_SECONDARY,
|
|
20
|
+
T_TERTIARY,
|
|
21
|
+
INSET,
|
|
22
|
+
BORDER_SUBTLE,
|
|
23
|
+
Page,
|
|
24
|
+
} from './setHeadingKit'
|
|
25
|
+
|
|
26
|
+
const INTER = 'Inter, sans-serif'
|
|
27
|
+
const VAR_EDGE = '#22465F' // cyan-800 — the continue-slot outline
|
|
28
|
+
const CYAN_TEXT = '#22D3EE'
|
|
29
|
+
|
|
30
|
+
/** Pretty-print a value as a compact TS literal (single-line arrays). */
|
|
31
|
+
function formatValue(value: unknown): string {
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
if (value.length > 0 && Array.isArray(value[0])) {
|
|
34
|
+
return `[${value.map((v) => formatValue(v)).join(', ')}]`
|
|
35
|
+
}
|
|
36
|
+
return `[${(value as number[]).join(', ')}]`
|
|
37
|
+
}
|
|
38
|
+
return typeof value === 'string' ? `'${value}'` : String(value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The exact `set` prop, pretty-printed as a TS object for the accordion. */
|
|
42
|
+
function formatSet(set: VelocitySet): string {
|
|
43
|
+
const body = Object.entries(set)
|
|
44
|
+
.map(([key, value]) => ` ${key}: ${formatValue(value)},`)
|
|
45
|
+
.join('\n')
|
|
46
|
+
return `set={{\n${body}\n}}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type Modality = {
|
|
50
|
+
name: string
|
|
51
|
+
alias: string
|
|
52
|
+
tags: string[]
|
|
53
|
+
def: string
|
|
54
|
+
useWhen: string
|
|
55
|
+
set: VelocitySet
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Representative velocities per colour bucket, matching the exploration's VEL map
|
|
59
|
+
// (g/a/o/r → the real velColor() ramp: fastest green → slowest red).
|
|
60
|
+
const G = 1.1
|
|
61
|
+
const A = 0.85
|
|
62
|
+
const O = 0.6
|
|
63
|
+
const R = 0.4
|
|
64
|
+
|
|
65
|
+
const MODS: Modality[] = [
|
|
66
|
+
{
|
|
67
|
+
name: 'Straight set',
|
|
68
|
+
alias: '3 × 10',
|
|
69
|
+
tags: ['fixed reps'],
|
|
70
|
+
def: 'A fixed number of reps at a fixed load — the baseline every other type varies from. Done reps colour; the remainder to the planned count sits grey.',
|
|
71
|
+
useWhen: 'A prescribed rep count at a fixed load — the default set.',
|
|
72
|
+
set: { type: 'straight', velocities: [G, A, A, O, O, R], planned: 10 },
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'Rep-range',
|
|
76
|
+
alias: '8–12',
|
|
77
|
+
tags: ['bounded reps'],
|
|
78
|
+
def: 'A floor and a ceiling — committed reps to the floor (grey todo), then a variable window (cyan) to the ceiling. A done variable rep is a real rep and colours normally.',
|
|
79
|
+
useWhen: 'A hypertrophy set prescribed as a range (e.g. 8–12) rather than a fixed count.',
|
|
80
|
+
set: { type: 'range', velocities: [G, A, A, O, O, O, R, R, A], floor: 8, max: 12 },
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'AMRAP',
|
|
84
|
+
alias: 'as many reps as possible',
|
|
85
|
+
tags: ['open-ended'],
|
|
86
|
+
def: 'One set, no cap — the target is a floor to beat. Velocity decay is the story; the trailing cyan slot never closes.',
|
|
87
|
+
useWhen: 'A final "leave nothing" set, or a rep-out test where the count is the outcome.',
|
|
88
|
+
set: { type: 'amrap', velocities: [G, G, A, A, O, O, O, R, R, R], target: 8 },
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'Drop set',
|
|
92
|
+
alias: 'strip / dropset',
|
|
93
|
+
tags: ['load drops', 'open-ended'],
|
|
94
|
+
def: 'Reps to failure at a load, then DROP the load (no rest) and continue — 1–3 drops. Sub-loads split by a WIDE notch gap.',
|
|
95
|
+
useWhen: 'Extending a set past failure by shedding load — the notch marks each weight drop.',
|
|
96
|
+
set: { type: 'drop', subloads: [[G, A, O, O, R], [A, O, R], [O, R]] },
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Myo-reps',
|
|
100
|
+
alias: 'rest-pause',
|
|
101
|
+
tags: ['clusters', 'open-ended'],
|
|
102
|
+
def: 'Activation set to near-failure, then rest-pause mini-clusters at the SAME load until a cluster fails. Open tail → cyan continue.',
|
|
103
|
+
useWhen: 'High-density hypertrophy: one activation set milked for extra clusters.',
|
|
104
|
+
set: { type: 'myo', activation: [G, A, A, O, O, R], clusters: [[A, O, R], [O, R]], open: true },
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'Cluster set',
|
|
108
|
+
alias: 'intra-set rest',
|
|
109
|
+
tags: ['fixed reps', 'intra-rest'],
|
|
110
|
+
def: 'A fixed rep count at a HIGH load, broken by short intra-set rests so every rep stays fast — power, not failure. Fixed count → no cyan tail.',
|
|
111
|
+
useWhen: 'Strength/power work where each rep must stay fast; rests are planned, count is fixed.',
|
|
112
|
+
set: { type: 'cluster', velocities: [G, G, G, A, G, G, A, G], groupSize: 2, planned: 10 },
|
|
113
|
+
},
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
function tagStyle(open: boolean) {
|
|
117
|
+
return {
|
|
118
|
+
fontFamily: INTER,
|
|
119
|
+
fontSize: 10,
|
|
120
|
+
fontWeight: '600' as const,
|
|
121
|
+
letterSpacing: 0.5,
|
|
122
|
+
textTransform: 'uppercase' as const,
|
|
123
|
+
color: open ? CYAN_TEXT : T_TERTIARY,
|
|
124
|
+
borderWidth: 1,
|
|
125
|
+
borderColor: open ? VAR_EDGE : '#2A2A2E',
|
|
126
|
+
borderRadius: 999,
|
|
127
|
+
paddingVertical: 3,
|
|
128
|
+
paddingHorizontal: 8,
|
|
129
|
+
overflow: 'hidden' as const,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function ConfigAccordion({ set, useWhen }: { set: VelocitySet; useWhen: string }) {
|
|
134
|
+
return (
|
|
135
|
+
<Collapse>
|
|
136
|
+
<CollapseButton className="px-0 py-2 rounded-none">
|
|
137
|
+
<Text style={{ fontFamily: INTER, fontSize: 11, fontWeight: '700', letterSpacing: 0.4, color: CYAN_TEXT }}>
|
|
138
|
+
How to configure this view
|
|
139
|
+
</Text>
|
|
140
|
+
</CollapseButton>
|
|
141
|
+
<CollapseContent className="px-0">
|
|
142
|
+
<View style={{ backgroundColor: '#0A0A0A', borderWidth: 1, borderColor: '#2A2A2E', borderRadius: 8, padding: 12, gap: 8 }}>
|
|
143
|
+
<Text style={{ fontFamily: 'monospace', fontSize: 11, lineHeight: 17, color: T_SECONDARY }}>
|
|
144
|
+
{`<VelocityStrip\n variant="mini"\n ${formatSet(set).replace(/\n/g, '\n ')}\n/>`}
|
|
145
|
+
</Text>
|
|
146
|
+
<Text style={{ fontFamily: INTER, fontSize: 12, color: T_TERTIARY, lineHeight: 17 }}>
|
|
147
|
+
Use when: {useWhen}
|
|
148
|
+
</Text>
|
|
149
|
+
</View>
|
|
150
|
+
</CollapseContent>
|
|
151
|
+
</Collapse>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Card({ m }: { m: Modality }) {
|
|
156
|
+
return (
|
|
157
|
+
<View style={{ backgroundColor: INSET, borderWidth: 1, borderColor: '#2A2A2E', borderRadius: 12, padding: 18, gap: 12, width: 460 }}>
|
|
158
|
+
<View style={{ flexDirection: 'row', alignItems: 'baseline', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8 }}>
|
|
159
|
+
<Text style={{ fontFamily: INTER, fontSize: 16, fontWeight: '700', color: T_PRIMARY }}>
|
|
160
|
+
{m.name} <Text style={{ fontSize: 12, fontWeight: '500', color: T_TERTIARY }}>{m.alias}</Text>
|
|
161
|
+
</Text>
|
|
162
|
+
<View style={{ flexDirection: 'row', gap: 6 }}>
|
|
163
|
+
{m.tags.map((t) => {
|
|
164
|
+
const open = /open-ended/.test(t)
|
|
165
|
+
return (
|
|
166
|
+
<Text key={t} style={tagStyle(open)}>
|
|
167
|
+
{t}
|
|
168
|
+
</Text>
|
|
169
|
+
)
|
|
170
|
+
})}
|
|
171
|
+
</View>
|
|
172
|
+
</View>
|
|
173
|
+
<Text style={{ fontFamily: INTER, fontSize: 13, color: T_SECONDARY, lineHeight: 19 }}>{m.def}</Text>
|
|
174
|
+
<View style={{ paddingVertical: 6 }}>
|
|
175
|
+
<VelocityStrip variant="mini" set={m.set} />
|
|
176
|
+
</View>
|
|
177
|
+
<View style={{ height: 1, backgroundColor: BORDER_SUBTLE }} />
|
|
178
|
+
<ConfigAccordion set={m.set} useWhen={m.useWhen} />
|
|
179
|
+
</View>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function Principle({ k, title, body }: { k: string; title: string; body: string }) {
|
|
184
|
+
return (
|
|
185
|
+
<View style={{ flex: 1, backgroundColor: INSET, borderWidth: 1, borderColor: '#2A2A2E', borderRadius: 10, padding: 16, gap: 7, minWidth: 220 }}>
|
|
186
|
+
<Text style={{ fontFamily: INTER, fontSize: 10, fontWeight: '600', letterSpacing: 1.2, textTransform: 'uppercase', color: CYAN_TEXT }}>{k}</Text>
|
|
187
|
+
<Text style={{ fontFamily: INTER, fontSize: 14, fontWeight: '600', color: T_PRIMARY }}>{title}</Text>
|
|
188
|
+
<Text style={{ fontFamily: INTER, fontSize: 12.5, color: T_SECONDARY, lineHeight: 18 }}>{body}</Text>
|
|
189
|
+
</View>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function Swatch({ color, label, outlined }: { color: string; label: string; outlined?: boolean }) {
|
|
194
|
+
return (
|
|
195
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 7 }}>
|
|
196
|
+
<View style={{ width: 22, height: 10, borderRadius: 2, backgroundColor: color, ...(outlined ? { borderWidth: 1, borderColor: VAR_EDGE } : {}) }} />
|
|
197
|
+
<Text style={{ fontFamily: INTER, fontSize: 12, color: T_SECONDARY }}>{label}</Text>
|
|
198
|
+
</View>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const meta: Meta = {
|
|
203
|
+
title: 'Workout/DataViz/VelocityStrip/Set Modalities',
|
|
204
|
+
parameters: { layout: 'fullscreen' },
|
|
205
|
+
}
|
|
206
|
+
export default meta
|
|
207
|
+
type Story = StoryObj
|
|
208
|
+
|
|
209
|
+
export const StripVocabulary: Story = {
|
|
210
|
+
name: 'One strip vocabulary · every set type',
|
|
211
|
+
render: () => (
|
|
212
|
+
<Page
|
|
213
|
+
title="Set modalities — the VelocityStrip set-type vocabulary"
|
|
214
|
+
note="Every per-rep strength set-type, rendered by the real VelocityStrip via its `set` prop. One strip model: defaults to the target rep count (grey slots) · overshoot grows the strip · open-ended sets (AMRAP · the myo tail) carry a persistent cyan 'continue' slot. Real velocity colours + the cyan variable token. Open each card's accordion for the exact `set` configuration and a 'use when' note."
|
|
215
|
+
>
|
|
216
|
+
<View style={{ flexDirection: 'row', gap: 14, flexWrap: 'wrap', maxWidth: 940 }}>
|
|
217
|
+
<Principle k="default" title="Target reps as slots" body="A set opens as N grey placeholder slots — the prescribed count. Each done rep fills its slot with its velocity colour." />
|
|
218
|
+
<Principle k="overshoot" title="Extra reps grow it" body="Beat the prescription and the strip keeps adding slots. The plan is a floor to draw against, not a cap." />
|
|
219
|
+
<Principle k="open-ended" title="A cyan continue tail" body="AMRAP and the myo tail carry a persistent cyan slot; new reps insert before it — it argues 'there's more'." />
|
|
220
|
+
</View>
|
|
221
|
+
|
|
222
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 14, alignItems: 'center', marginTop: 4 }}>
|
|
223
|
+
<Swatch color={velColor(G)} label="fastest" />
|
|
224
|
+
<Swatch color={velColor(A)} label="fast" />
|
|
225
|
+
<Swatch color={velColor(O)} label="moderate" />
|
|
226
|
+
<Swatch color={velColor(R)} label="slow" />
|
|
227
|
+
<Swatch color={GREY} label="planned / to-do" />
|
|
228
|
+
<Swatch color={CYAN} label="variable · continue" outlined />
|
|
229
|
+
</View>
|
|
230
|
+
|
|
231
|
+
<View style={{ height: 1, backgroundColor: BORDER_SUBTLE, marginVertical: 4 }} />
|
|
232
|
+
|
|
233
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 16 }}>
|
|
234
|
+
{MODS.map((m) => (
|
|
235
|
+
<Card key={m.name} m={m} />
|
|
236
|
+
))}
|
|
237
|
+
</View>
|
|
238
|
+
</Page>
|
|
239
|
+
),
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export const ActiveSetExpanded: Story = {
|
|
243
|
+
name: 'Straight set · expanded spotlight',
|
|
244
|
+
render: () => (
|
|
245
|
+
<Page
|
|
246
|
+
title="Straight set — the expanded active-set spotlight"
|
|
247
|
+
note="The expanded VelocityStrip for a live straight set: done reps as velocity-height bars, the planned remainder as short grey stubs, and the mean · loss summary derived from the done reps."
|
|
248
|
+
>
|
|
249
|
+
<View style={{ width: 520, gap: 10 }}>
|
|
250
|
+
<VelocityStrip set={{ type: 'straight', velocities: [1.12, 1.05, 0.98, 0.9, 0.82, 0.7], planned: 10 }} expanded />
|
|
251
|
+
</View>
|
|
252
|
+
</Page>
|
|
253
|
+
),
|
|
254
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notch across widths — the between-group notch (drop sub-loads / myo clusters /
|
|
3
|
+
* cluster intra-rests) is a fixed 8px gap, held constant at every strip width. This
|
|
4
|
+
* page renders the real {@link VelocityStrip} across a ladder of widths (session-rail
|
|
5
|
+
* → wall dashboard) and across rep / rep-grouping counts, so the notch can be judged
|
|
6
|
+
* at the size it will actually ship at. Same set, stacked and left-aligned across
|
|
7
|
+
* widths: the notch stays put while the bars grow, so it reads as a group break at
|
|
8
|
+
* rail scale without ballooning at wall scale.
|
|
9
|
+
*/
|
|
10
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
11
|
+
import { View, Text } from 'react-native'
|
|
12
|
+
import { VelocityStrip, type VelocitySet } from './VelocityStrip'
|
|
13
|
+
import { T_PRIMARY, T_SECONDARY, T_TERTIARY, INSET, BORDER_SUBTLE, Page } from './setHeadingKit'
|
|
14
|
+
|
|
15
|
+
const INTER = 'Inter, sans-serif'
|
|
16
|
+
const MONO = 'monospace'
|
|
17
|
+
|
|
18
|
+
// The width ladder: a session-rail row, two mid app widths, a modality card, a wall
|
|
19
|
+
// dashboard. These bracket everywhere the strip renders.
|
|
20
|
+
const WIDTHS = [130, 200, 300, 440, 620] as const
|
|
21
|
+
|
|
22
|
+
// Preview height — the production mini strip is 3px tall; shown a touch taller here so
|
|
23
|
+
// the horizontal notch is legible on screen. Width (the axis under test) is untouched.
|
|
24
|
+
const PREVIEW_HEIGHT = 8
|
|
25
|
+
|
|
26
|
+
// Velocity colour buckets (match the modalities exploration: fastest green → slowest red).
|
|
27
|
+
const G = 1.1
|
|
28
|
+
const A = 0.85
|
|
29
|
+
const O = 0.6
|
|
30
|
+
const R = 0.4
|
|
31
|
+
|
|
32
|
+
/** A descending velocity ramp of length n (1.15 → 0.40), for realistic fatigue. */
|
|
33
|
+
function ramp(n: number): number[] {
|
|
34
|
+
if (n <= 1) return [G]
|
|
35
|
+
return Array.from({ length: n }, (_, i) => {
|
|
36
|
+
const t = i / (n - 1)
|
|
37
|
+
return Math.round((1.15 - t * 0.75) * 100) / 100
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type Config = { name: string; detail: string; set: VelocitySet }
|
|
42
|
+
|
|
43
|
+
const CONFIGS: Config[] = [
|
|
44
|
+
{
|
|
45
|
+
name: 'Straight · 5',
|
|
46
|
+
detail: '5 reps · one group · no notch',
|
|
47
|
+
set: { type: 'straight', velocities: [G, A, A, O, R], planned: 5 },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Straight · 12',
|
|
51
|
+
detail: '12 reps · one group · no notch',
|
|
52
|
+
set: { type: 'straight', velocities: ramp(12), planned: 12 },
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'Drop · 3 loads',
|
|
56
|
+
detail: '3 groups (5 / 3 / 2) · 2 notches',
|
|
57
|
+
set: { type: 'drop', subloads: [[G, A, O, O, R], [A, O, R], [O, R]] },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'Cluster · groups of 2',
|
|
61
|
+
detail: '10 reps · 5 groups · 4 notches',
|
|
62
|
+
set: { type: 'cluster', velocities: ramp(10), groupSize: 2, planned: 10 },
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'Cluster · groups of 3',
|
|
66
|
+
detail: '15 reps · 5 groups · 4 notches (dense)',
|
|
67
|
+
set: { type: 'cluster', velocities: ramp(15), groupSize: 3, planned: 15 },
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'Myo · open',
|
|
71
|
+
detail: 'activation + 2 clusters · 2 notches + cyan tail',
|
|
72
|
+
set: { type: 'myo', activation: [G, A, A, O, O, R], clusters: [[A, O, R], [O, R]], open: true },
|
|
73
|
+
},
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
function WidthRow({ set, width }: { set: VelocitySet; width: number }) {
|
|
77
|
+
return (
|
|
78
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
|
|
79
|
+
<Text style={{ fontFamily: MONO, fontSize: 10, color: T_TERTIARY, width: 44, textAlign: 'right' }}>
|
|
80
|
+
{width}px
|
|
81
|
+
</Text>
|
|
82
|
+
<View style={{ width }}>
|
|
83
|
+
<VelocityStrip variant="mini" set={set} style={{ height: PREVIEW_HEIGHT }} />
|
|
84
|
+
</View>
|
|
85
|
+
</View>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ConfigPanel({ c }: { c: Config }) {
|
|
90
|
+
return (
|
|
91
|
+
<View
|
|
92
|
+
style={{
|
|
93
|
+
backgroundColor: INSET,
|
|
94
|
+
borderWidth: 1,
|
|
95
|
+
borderColor: '#2A2A2E',
|
|
96
|
+
borderRadius: 12,
|
|
97
|
+
padding: 18,
|
|
98
|
+
gap: 14,
|
|
99
|
+
width: 620 + 44 + 12 + 36, // widest strip + label col + gap + panel padding
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<View style={{ gap: 3 }}>
|
|
103
|
+
<Text style={{ fontFamily: INTER, fontSize: 15, fontWeight: '700', color: T_PRIMARY }}>{c.name}</Text>
|
|
104
|
+
<Text style={{ fontFamily: INTER, fontSize: 12, color: T_TERTIARY }}>{c.detail}</Text>
|
|
105
|
+
</View>
|
|
106
|
+
<View style={{ height: 1, backgroundColor: BORDER_SUBTLE }} />
|
|
107
|
+
<View style={{ gap: 12 }}>
|
|
108
|
+
{WIDTHS.map((w) => (
|
|
109
|
+
<WidthRow key={w} set={c.set} width={w} />
|
|
110
|
+
))}
|
|
111
|
+
</View>
|
|
112
|
+
</View>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const meta: Meta = {
|
|
117
|
+
title: 'Workout/DataViz/VelocityStrip/Responsive Width',
|
|
118
|
+
parameters: { layout: 'fullscreen' },
|
|
119
|
+
}
|
|
120
|
+
export default meta
|
|
121
|
+
type Story = StoryObj
|
|
122
|
+
|
|
123
|
+
export const NotchAcrossWidths: Story = {
|
|
124
|
+
name: 'Between-group notch · across widths',
|
|
125
|
+
render: () => (
|
|
126
|
+
<Page
|
|
127
|
+
title="Notch across widths — a fixed 8px group break"
|
|
128
|
+
note="The notch that separates drop sub-loads, myo clusters and cluster intra-rests is a fixed 8px gap, held constant at every strip width — so it reads as a group break at session-rail scale AND at wall-dashboard scale without ballooning as the strip grows. Each panel is one set type, stacked and left-aligned across the width ladder (130px rail → 620px wall); both the 2px rep gap and the 8px notch hold fixed while only the bars grow. Straight sets carry no notch (baseline). Preview strips are shown at 8px tall for legibility; the production mini strip is 3px."
|
|
129
|
+
>
|
|
130
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 18 }}>
|
|
131
|
+
{CONFIGS.map((c) => (
|
|
132
|
+
<ConfigPanel key={c.name} c={c} />
|
|
133
|
+
))}
|
|
134
|
+
</View>
|
|
135
|
+
|
|
136
|
+
<Text style={{ fontFamily: INTER, fontSize: 12.5, color: T_SECONDARY, lineHeight: 19, maxWidth: 760, marginTop: 4 }}>
|
|
137
|
+
Reading the ladder: the notch holds at 8px everywhere, so at rail scale it is a clear group break
|
|
138
|
+
(4× the rep gap) and at wall scale it stays a tidy break rather than a canyon. Watch the dense
|
|
139
|
+
cluster (groups of 3, 15 reps): even where the bars are thin, the 8px notch separates groups without
|
|
140
|
+
a proportional gap swallowing the strip at large sizes. The single knob is WIDE_GAP in
|
|
141
|
+
VelocityStrip.tsx.
|
|
142
|
+
</Text>
|
|
143
|
+
</Page>
|
|
144
|
+
),
|
|
145
|
+
}
|