@titan-design/react-ui 0.6.0 → 0.8.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 +956 -128
- package/dist/index.d.ts +956 -128
- package/dist/index.js +2873 -981
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2826 -982
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-DCFBqp79.d.mts} +289 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-_fI3-x7Z.d.ts} +289 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1659 -799
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1660 -800
- 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/CircularTimer/CircularTimer.stories.tsx +90 -0
- package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
- package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
- package/src/components/custom/CircularTimer/index.ts +1 -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 +85 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +86 -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 +193 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.stories.tsx +133 -3
- package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
- package/src/components/custom/Workout/RestTimer.tsx +155 -59
- 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 +70 -1
- package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
- package/src/components/custom/Workout/TempoBar.tsx +107 -22
- 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 +262 -56
- package/src/components/custom/Workout/VelocityStrip.test.tsx +440 -141
- package/src/components/custom/Workout/VelocityStrip.tsx +776 -141
- 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 +139 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +234 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +373 -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 +2 -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 +86 -3
- package/src/components/ui/alert/Alert.test.tsx +52 -0
- package/src/components/ui/alert/Alert.tsx +54 -20
- 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/progress/Progress.tsx +47 -22
- 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
|
@@ -1,260 +1,197 @@
|
|
|
1
1
|
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import { type ReactNode } from 'react'
|
|
2
3
|
import { View, Text } from 'react-native'
|
|
3
4
|
import { VelocityStrip, type VelocityZoneBandProp } from './VelocityStrip'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { roundWeight, roundRpe } from '../../../utils/workout-format'
|
|
6
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
6
7
|
import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
|
|
7
8
|
|
|
8
|
-
export type
|
|
9
|
+
export type SetRowUnit = 'lbs' | 'kg'
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
/** A set's lifecycle: logged (`done`), being performed now (`live`), or planned (`todo`). */
|
|
12
|
+
export type SetRowState = 'done' | 'live' | 'todo'
|
|
13
|
+
|
|
14
|
+
interface SetRowBase {
|
|
12
15
|
setNumber: number
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
rpe?: number | null
|
|
17
|
-
unit: 'lbs' | 'kg'
|
|
18
|
-
/** Per-rep MEAN concentric velocities (m/s). */
|
|
19
|
-
velocities?: number[]
|
|
16
|
+
unit: SetRowUnit
|
|
17
|
+
/** Optional set-type marker (e.g. "DROP", "AMRAP") shown in the SET cell. */
|
|
18
|
+
setType?: string
|
|
20
19
|
/** Optional velocity-zone bands (WA `VelocityZones.bands`); default scale when absent. */
|
|
21
20
|
velocityZones?: readonly VelocityZoneBandProp[]
|
|
22
|
-
/** Live mode: index of the newest rep bar to animate (pop / new-peak bounce). */
|
|
23
|
-
velocityLiveRepIndex?: number
|
|
24
|
-
velocityExpanded?: boolean
|
|
25
|
-
onVelocityToggle?: () => void
|
|
26
|
-
setType?: string
|
|
27
|
-
prBadges?: Array<{ type: PRType; label: string }>
|
|
28
|
-
isNextSet?: boolean
|
|
29
|
-
targets?: { reps: number; weight: number }
|
|
30
21
|
}
|
|
31
22
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
23
|
+
/**
|
|
24
|
+
* One row of the unified expanded exercise table (SET · REPS · LBS · RPE + a
|
|
25
|
+
* per-row velocity strip), as a lifecycle discriminated union:
|
|
26
|
+
* - `done` — a logged set: recorded reps / weight / rpe, a flat `mini` strip.
|
|
27
|
+
* - `live` — performed right now: shows its TARGET (never "reps-done/target"),
|
|
28
|
+
* stands out by brightness, and a compact velocity-HEIGHT spotlight strip.
|
|
29
|
+
* - `todo` — planned: shows its target, muted, a flat grey `mini` stub strip.
|
|
30
|
+
*
|
|
31
|
+
* `done` + `todo` share ONE muted treatment; only `live` is brightened. There is
|
|
32
|
+
* no PREV column (dropped in the unified design).
|
|
33
|
+
*/
|
|
34
|
+
export type SetRowProps =
|
|
35
|
+
| (SetRowBase & {
|
|
36
|
+
state: 'done'
|
|
37
|
+
reps: number
|
|
38
|
+
weight: number
|
|
39
|
+
rpe?: number | null
|
|
40
|
+
/** Per-rep MEAN concentric velocities (m/s). */
|
|
41
|
+
velocities: number[]
|
|
42
|
+
})
|
|
43
|
+
| (SetRowBase & {
|
|
44
|
+
state: 'live'
|
|
45
|
+
/** The prescribed target shown in the REPS / LBS cells (no live "5/10"). */
|
|
46
|
+
target: { reps: number; weight: number }
|
|
47
|
+
/** Reps / weight committed so far (available to hosts; the display shows `target`). */
|
|
48
|
+
reps: number
|
|
49
|
+
weight: number
|
|
50
|
+
rpe?: number | null
|
|
51
|
+
/** Per-rep MEAN concentric velocities logged so far this set. */
|
|
52
|
+
velocities: number[]
|
|
53
|
+
/** Newest rep index — reserved; the compact spotlight strip is static. */
|
|
54
|
+
liveRepIndex?: number
|
|
55
|
+
})
|
|
56
|
+
| (SetRowBase & {
|
|
57
|
+
state: 'todo'
|
|
58
|
+
/** The prescribed target shown in the REPS / LBS cells. */
|
|
59
|
+
target: { reps: number; weight: number }
|
|
60
|
+
/** Planned rep count backing the grey stub strip. Default `target.reps`. */
|
|
61
|
+
planned?: number
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const DARK = getSemanticColors('dark')
|
|
65
|
+
/** Live set — brightest (neutral 100). Done + upcoming share the muted neutral 400. */
|
|
66
|
+
const TEXT_ACTIVE = DARK['text-primary']
|
|
67
|
+
const TEXT_MUTED = DARK['text-secondary']
|
|
68
|
+
|
|
69
|
+
/** Column widths mirror SetTableHeader(showPrevious=false) so cells align under it. */
|
|
70
|
+
const COL = { set: 36, reps: 44, load: 56, rpe: 36 } as const
|
|
71
|
+
const cellText = { fontFamily: 'Inter, sans-serif', fontSize: 13, fontWeight: '600' as const }
|
|
72
|
+
const typeBadge = {
|
|
73
|
+
fontSize: 10,
|
|
74
|
+
fontWeight: '700' as const,
|
|
75
|
+
fontFamily: 'Inter, sans-serif',
|
|
76
|
+
color: WORKOUT_TOKENS.scale.orange,
|
|
77
|
+
backgroundColor: 'rgba(255,165,2,0.12)',
|
|
78
|
+
paddingVertical: 1,
|
|
79
|
+
paddingHorizontal: 5,
|
|
80
|
+
borderRadius: 3,
|
|
81
|
+
overflow: 'hidden' as const,
|
|
41
82
|
}
|
|
42
83
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
reps,
|
|
48
|
-
weight,
|
|
49
|
-
rpe,
|
|
50
|
-
unit,
|
|
51
|
-
velocities,
|
|
52
|
-
velocityZones,
|
|
53
|
-
velocityLiveRepIndex,
|
|
54
|
-
velocityExpanded,
|
|
55
|
-
onVelocityToggle,
|
|
56
|
-
setType,
|
|
57
|
-
prBadges,
|
|
58
|
-
isNextSet,
|
|
59
|
-
targets,
|
|
60
|
-
}: SetRowProps) {
|
|
61
|
-
const isActive = mode === 'active'
|
|
62
|
-
const isCompleted = mode === 'completed'
|
|
84
|
+
/** Reps to show: recorded for a `done` set, else the prescribed target. */
|
|
85
|
+
function displayReps(set: SetRowProps): number {
|
|
86
|
+
return set.state === 'done' ? set.reps : set.target.reps
|
|
87
|
+
}
|
|
63
88
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
padding: 7,
|
|
68
|
-
paddingHorizontal: 8,
|
|
69
|
-
gap: 8,
|
|
70
|
-
borderRadius: 8,
|
|
71
|
-
}
|
|
89
|
+
function displayWeight(set: SetRowProps): number {
|
|
90
|
+
return set.state === 'done' ? set.weight : set.target.weight
|
|
91
|
+
}
|
|
72
92
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
/** Planned rep count backing the row's strip. */
|
|
94
|
+
function plannedReps(set: SetRowProps): number {
|
|
95
|
+
if (set.state === 'done') return set.velocities.length
|
|
96
|
+
if (set.state === 'live') return set.target.reps
|
|
97
|
+
return set.planned ?? set.target.reps
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function accessibilityLabel(set: SetRowProps): string {
|
|
101
|
+
const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`
|
|
102
|
+
if (set.state === 'live') return `${base}, in progress`
|
|
103
|
+
if (set.state === 'todo') return `${base}, upcoming`
|
|
104
|
+
return base
|
|
105
|
+
}
|
|
78
106
|
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
function Cell({
|
|
108
|
+
width,
|
|
109
|
+
children,
|
|
110
|
+
testID,
|
|
111
|
+
}: {
|
|
112
|
+
width: number
|
|
113
|
+
children: ReactNode
|
|
114
|
+
testID?: string
|
|
115
|
+
}) {
|
|
116
|
+
return (
|
|
117
|
+
<View style={{ width, alignItems: 'center', justifyContent: 'center' }} testID={testID}>
|
|
118
|
+
{children}
|
|
119
|
+
</View>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The per-row velocity strip: compact spotlight for the live set, flat mini otherwise. */
|
|
124
|
+
function RowStrip({ set }: { set: SetRowProps }) {
|
|
125
|
+
const zones = set.velocityZones
|
|
126
|
+
if (set.state === 'live') {
|
|
127
|
+
// The active-set spotlight: the bare velocity-height `expanded` chart at 24px,
|
|
128
|
+
// fixed scale, no labels / info (both chrome flags off → the bare strip).
|
|
129
|
+
return (
|
|
130
|
+
<VelocityStrip
|
|
131
|
+
variant="expanded"
|
|
132
|
+
showNumbers={false}
|
|
133
|
+
showInfo={false}
|
|
134
|
+
height={24}
|
|
135
|
+
scale="fixed"
|
|
136
|
+
set={{ type: 'straight', velocities: set.velocities, planned: set.target.reps }}
|
|
137
|
+
zones={zones}
|
|
138
|
+
/>
|
|
139
|
+
)
|
|
81
140
|
}
|
|
141
|
+
const velocities = set.state === 'done' ? set.velocities : []
|
|
142
|
+
return (
|
|
143
|
+
<VelocityStrip
|
|
144
|
+
variant="mini"
|
|
145
|
+
set={{ type: 'straight', velocities, planned: plannedReps(set) }}
|
|
146
|
+
zones={zones}
|
|
147
|
+
/>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* ONE set row of the unified expanded exercise table. SET · REPS · LBS · RPE over
|
|
153
|
+
* a per-row {@link VelocityStrip}; `live` stands out by brightness with a compact
|
|
154
|
+
* velocity-height spotlight, `done`/`todo` are muted with a flat mini strip. Its
|
|
155
|
+
* column widths mirror {@link SetTableHeader}(`showPrevious={false}`).
|
|
156
|
+
*/
|
|
157
|
+
export function SetRow(set: SetRowProps) {
|
|
158
|
+
const live = set.state === 'live'
|
|
159
|
+
const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED
|
|
160
|
+
const rpe = set.state === 'todo' ? null : set.rpe
|
|
82
161
|
|
|
83
162
|
return (
|
|
84
163
|
<View
|
|
85
|
-
style={
|
|
86
|
-
accessibilityLabel={
|
|
87
|
-
setNumber,
|
|
88
|
-
reps,
|
|
89
|
-
weight,
|
|
90
|
-
unit,
|
|
91
|
-
)}
|
|
164
|
+
style={{ paddingVertical: 6, paddingHorizontal: 8 }}
|
|
165
|
+
accessibilityLabel={accessibilityLabel(set)}
|
|
92
166
|
testID="set-row"
|
|
93
167
|
>
|
|
94
|
-
<View
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
168
|
+
<View className="flex-row items-center" style={{ justifyContent: 'space-between' }}>
|
|
169
|
+
<Cell width={COL.set} testID="set-row-set-number">
|
|
170
|
+
{set.setType ? (
|
|
171
|
+
<Text style={typeBadge} testID="set-row-type-badge">
|
|
172
|
+
{set.setType}
|
|
173
|
+
</Text>
|
|
174
|
+
) : (
|
|
175
|
+
<Text style={{ ...cellText, color: valueColor, fontWeight: live ? '700' : '600' }}>
|
|
176
|
+
{set.setNumber}
|
|
177
|
+
</Text>
|
|
178
|
+
)}
|
|
179
|
+
</Cell>
|
|
180
|
+
<Cell width={COL.reps} testID="set-row-reps">
|
|
181
|
+
<Text style={{ ...cellText, color: valueColor }}>{displayReps(set)}</Text>
|
|
182
|
+
</Cell>
|
|
183
|
+
<Cell width={COL.load} testID="set-row-weight">
|
|
184
|
+
<Text style={{ ...cellText, color: valueColor }}>{roundWeight(displayWeight(set))}</Text>
|
|
185
|
+
</Cell>
|
|
186
|
+
<Cell width={COL.rpe} testID="set-row-rpe">
|
|
187
|
+
<Text style={{ ...cellText, color: TEXT_MUTED }}>
|
|
188
|
+
{rpe != null ? roundRpe(rpe) : '—'}
|
|
114
189
|
</Text>
|
|
115
|
-
|
|
116
|
-
<Text
|
|
117
|
-
className="text-text-secondary"
|
|
118
|
-
style={{
|
|
119
|
-
fontSize: 13,
|
|
120
|
-
fontWeight: '600',
|
|
121
|
-
fontFamily: 'Inter, sans-serif',
|
|
122
|
-
}}
|
|
123
|
-
>
|
|
124
|
-
{setNumber}
|
|
125
|
-
</Text>
|
|
126
|
-
)}
|
|
190
|
+
</Cell>
|
|
127
191
|
</View>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
className="flex-1"
|
|
131
|
-
testID="set-row-previous"
|
|
132
|
-
>
|
|
133
|
-
<Text
|
|
134
|
-
className="text-text-secondary"
|
|
135
|
-
style={{
|
|
136
|
-
fontSize: 12,
|
|
137
|
-
fontFamily: 'Inter, sans-serif',
|
|
138
|
-
}}
|
|
139
|
-
>
|
|
140
|
-
{previous
|
|
141
|
-
? `${previous.reps} x ${roundWeight(previous.weight)}`
|
|
142
|
-
: '\u2014'}
|
|
143
|
-
</Text>
|
|
144
|
-
</View>
|
|
145
|
-
|
|
146
|
-
<View
|
|
147
|
-
style={{ width: 44, alignItems: 'center', justifyContent: 'center' }}
|
|
148
|
-
testID="set-row-reps"
|
|
149
|
-
>
|
|
150
|
-
{isActive && reps == null && targets ? (
|
|
151
|
-
<Text
|
|
152
|
-
className="text-text-tertiary"
|
|
153
|
-
style={{
|
|
154
|
-
fontSize: 13,
|
|
155
|
-
fontStyle: 'italic',
|
|
156
|
-
fontFamily: 'Inter, sans-serif',
|
|
157
|
-
}}
|
|
158
|
-
testID="set-row-target-reps"
|
|
159
|
-
>
|
|
160
|
-
{targets.reps}
|
|
161
|
-
</Text>
|
|
162
|
-
) : (
|
|
163
|
-
<Text
|
|
164
|
-
className={reps != null ? 'text-text-primary' : 'text-text-tertiary'}
|
|
165
|
-
style={{
|
|
166
|
-
fontSize: 14,
|
|
167
|
-
fontWeight: '600',
|
|
168
|
-
fontFamily: 'Inter, sans-serif',
|
|
169
|
-
}}
|
|
170
|
-
>
|
|
171
|
-
{reps != null ? reps : '\u2014'}
|
|
172
|
-
</Text>
|
|
173
|
-
)}
|
|
192
|
+
<View style={{ marginTop: live ? 6 : 4 }} testID="set-row-strip">
|
|
193
|
+
<RowStrip set={set} />
|
|
174
194
|
</View>
|
|
175
|
-
|
|
176
|
-
<View
|
|
177
|
-
style={{ width: 56, alignItems: 'center', justifyContent: 'center' }}
|
|
178
|
-
testID="set-row-weight"
|
|
179
|
-
>
|
|
180
|
-
{isActive && weight == null && targets ? (
|
|
181
|
-
<Text
|
|
182
|
-
className="text-text-tertiary"
|
|
183
|
-
style={{
|
|
184
|
-
fontSize: 13,
|
|
185
|
-
fontStyle: 'italic',
|
|
186
|
-
fontFamily: 'Inter, sans-serif',
|
|
187
|
-
}}
|
|
188
|
-
testID="set-row-target-weight"
|
|
189
|
-
>
|
|
190
|
-
{roundWeight(targets.weight)}
|
|
191
|
-
</Text>
|
|
192
|
-
) : (
|
|
193
|
-
<Text
|
|
194
|
-
className={weight != null ? 'text-text-primary' : 'text-text-tertiary'}
|
|
195
|
-
style={{
|
|
196
|
-
fontSize: 14,
|
|
197
|
-
fontWeight: '600',
|
|
198
|
-
fontFamily: 'Inter, sans-serif',
|
|
199
|
-
}}
|
|
200
|
-
>
|
|
201
|
-
{weight != null ? roundWeight(weight) : '\u2014'}
|
|
202
|
-
</Text>
|
|
203
|
-
)}
|
|
204
|
-
</View>
|
|
205
|
-
|
|
206
|
-
<View
|
|
207
|
-
style={{ width: 36, alignItems: 'center', justifyContent: 'center' }}
|
|
208
|
-
testID="set-row-rpe"
|
|
209
|
-
>
|
|
210
|
-
<Text
|
|
211
|
-
className={rpe != null ? undefined : 'text-text-tertiary'}
|
|
212
|
-
style={{
|
|
213
|
-
fontSize: 13,
|
|
214
|
-
fontWeight: '600',
|
|
215
|
-
fontFamily: 'Inter, sans-serif',
|
|
216
|
-
// rpeColor returns a concrete hex (native-safe); only the tertiary
|
|
217
|
-
// fallback needs the className token.
|
|
218
|
-
color: rpe != null ? rpeColor(rpe) : undefined,
|
|
219
|
-
}}
|
|
220
|
-
>
|
|
221
|
-
{rpe != null ? roundRpe(rpe) : '\u2014'}
|
|
222
|
-
</Text>
|
|
223
|
-
</View>
|
|
224
|
-
|
|
225
|
-
{prBadges && prBadges.length > 0 && (
|
|
226
|
-
<View
|
|
227
|
-
className="flex-row items-center"
|
|
228
|
-
style={{ gap: 4 }}
|
|
229
|
-
testID="set-row-pr-badges"
|
|
230
|
-
>
|
|
231
|
-
{prBadges.map((badge, i) => (
|
|
232
|
-
<PrBadge
|
|
233
|
-
key={i}
|
|
234
|
-
type={badge.type}
|
|
235
|
-
label={badge.label}
|
|
236
|
-
animate={false}
|
|
237
|
-
compact
|
|
238
|
-
/>
|
|
239
|
-
))}
|
|
240
|
-
</View>
|
|
241
|
-
)}
|
|
242
|
-
|
|
243
|
-
{velocities && velocities.length > 0 && (
|
|
244
|
-
<View
|
|
245
|
-
style={{ position: 'absolute', bottom: 0, left: 8, right: 8 }}
|
|
246
|
-
testID="set-row-velocity-strip"
|
|
247
|
-
>
|
|
248
|
-
<VelocityStrip
|
|
249
|
-
velocities={velocities}
|
|
250
|
-
zones={velocityZones}
|
|
251
|
-
liveRepIndex={velocityLiveRepIndex}
|
|
252
|
-
expanded={velocityExpanded}
|
|
253
|
-
onToggle={onVelocityToggle}
|
|
254
|
-
variant={onVelocityToggle ? 'full' : 'mini'}
|
|
255
|
-
/>
|
|
256
|
-
</View>
|
|
257
|
-
)}
|
|
258
195
|
</View>
|
|
259
196
|
)
|
|
260
197
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
import { SetStrip, type SetStripSet } from './SetStrip'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `SetStrip` — the per-set segmented performance strip. One continuous bar per
|
|
7
|
+
* set (rep intensities as butted color segments, no rep gaps); sets separated by
|
|
8
|
+
* a fixed gap. Colors are the real titan ramp pins (red-600 · orange-400 ·
|
|
9
|
+
* amber-300 · green-300).
|
|
10
|
+
*/
|
|
11
|
+
const meta: Meta<typeof SetStrip> = {
|
|
12
|
+
title: 'Workout/SetStrip',
|
|
13
|
+
component: SetStrip,
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
parameters: {
|
|
16
|
+
docs: {
|
|
17
|
+
description: {
|
|
18
|
+
component:
|
|
19
|
+
'**Molecule.** The per-set segmented performance strip — one bar per set, rep ' +
|
|
20
|
+
'intensities as butted ramp-pin segments (no rep gaps), active set pulses. Composes ' +
|
|
21
|
+
'[SetBar](?path=/docs/workout-setbar--docs) × N. ' +
|
|
22
|
+
'Used-by ↑ [ExerciseCardHeading](?path=/docs/workout-exercisecardheading--docs).',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
argTypes: {
|
|
27
|
+
height: { control: { type: 'range', min: 2, max: 16, step: 1 } },
|
|
28
|
+
},
|
|
29
|
+
decorators: [
|
|
30
|
+
(Story) => (
|
|
31
|
+
<View style={{ width: 210, padding: 16, backgroundColor: '#131313' }}>
|
|
32
|
+
<Story />
|
|
33
|
+
</View>
|
|
34
|
+
),
|
|
35
|
+
],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default meta
|
|
39
|
+
type Story = StoryObj<typeof SetStrip>
|
|
40
|
+
|
|
41
|
+
const decay = (n: number, start: number, span = 0.4): number[] =>
|
|
42
|
+
Array.from({ length: n }, (_, r) => +(start - (span * r) / Math.max(1, n - 1)).toFixed(3))
|
|
43
|
+
|
|
44
|
+
export const Completed: Story = {
|
|
45
|
+
args: {
|
|
46
|
+
height: 8,
|
|
47
|
+
sets: [1.0, 0.9, 0.8, 0.7, 0.6].map(
|
|
48
|
+
(s): SetStripSet => ({ status: 'done', velocities: decay(8, s) })
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const InProgress: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
height: 8,
|
|
56
|
+
sets: [
|
|
57
|
+
{ status: 'done', velocities: decay(10, 0.72) },
|
|
58
|
+
{ status: 'active', velocities: decay(5, 0.62), planned: 10 },
|
|
59
|
+
{ status: 'todo', planned: 10 },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
parameters: {
|
|
63
|
+
docs: { description: { story: 'Active set: performed reps pulse; the remainder is greyed.' } },
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const Upcoming: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
height: 8,
|
|
70
|
+
sets: [1, 2, 3, 4, 5].map((): SetStripSet => ({ status: 'todo', planned: 20 })),
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const ShortStrip: Story = {
|
|
75
|
+
args: { ...Completed.args, height: 4 },
|
|
76
|
+
parameters: {
|
|
77
|
+
docs: { description: { story: 'The 4px height alternative (default is 8px).' } },
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---- set-type variant sheet: every variant at the real rail width, for eyeballing
|
|
82
|
+
function SheetRow({ label, meta, sets }: { label: string; meta: string; sets: SetStripSet[] }) {
|
|
83
|
+
return (
|
|
84
|
+
<View style={{ gap: 6 }}>
|
|
85
|
+
<View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 8 }}>
|
|
86
|
+
<Text style={{ color: '#F3F4F6', fontSize: 13, fontWeight: '800' }}>{label}</Text>
|
|
87
|
+
<Text style={{ color: '#6B7280', fontSize: 10, fontFamily: 'monospace' }}>{meta}</Text>
|
|
88
|
+
</View>
|
|
89
|
+
<SetStrip sets={sets} />
|
|
90
|
+
</View>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Every set-type variant at the real 232px rail width, alongside the flat sets — so the
|
|
96
|
+
* operator can eyeball the gap hierarchy (butted reps < 2px notch < 3px cluster < 5px set gap)
|
|
97
|
+
* and the shared cyan "variable / unknown / opportunity" pin across range + myo-upcoming.
|
|
98
|
+
*/
|
|
99
|
+
export const SetTypes: Story = {
|
|
100
|
+
render: () => (
|
|
101
|
+
<View style={{ width: 232, gap: 18 }}>
|
|
102
|
+
<SheetRow
|
|
103
|
+
label="Flat sets"
|
|
104
|
+
meta="5 × 8 — done + active + todo"
|
|
105
|
+
sets={[
|
|
106
|
+
{ status: 'done', velocities: decay(8, 0.9) },
|
|
107
|
+
{ status: 'active', velocities: decay(5, 0.62), planned: 8 },
|
|
108
|
+
{ status: 'todo', planned: 8 },
|
|
109
|
+
]}
|
|
110
|
+
/>
|
|
111
|
+
<SheetRow
|
|
112
|
+
label="Variable range"
|
|
113
|
+
meta="3 × 15–20 — todo · 12 · 17"
|
|
114
|
+
sets={[
|
|
115
|
+
{ status: 'range', floor: 15, max: 20, doneVels: [] },
|
|
116
|
+
{ status: 'range', floor: 15, max: 20, doneVels: decay(12, 0.9) },
|
|
117
|
+
{ status: 'range', floor: 15, max: 20, doneVels: decay(17, 0.8) },
|
|
118
|
+
]}
|
|
119
|
+
/>
|
|
120
|
+
<SheetRow
|
|
121
|
+
label="Drop set"
|
|
122
|
+
meta="1 drop — 10→8→6"
|
|
123
|
+
sets={[{ status: 'drop', subloads: [decay(10, 0.85), decay(8, 0.7), decay(6, 0.6)] }]}
|
|
124
|
+
/>
|
|
125
|
+
<SheetRow
|
|
126
|
+
label="Myo-rep"
|
|
127
|
+
meta="done — 15 + 5 + 5 + 5"
|
|
128
|
+
sets={[
|
|
129
|
+
{
|
|
130
|
+
status: 'myo',
|
|
131
|
+
activation: decay(15, 0.75),
|
|
132
|
+
clusters: [decay(5, 0.6), decay(5, 0.55), decay(5, 0.5)],
|
|
133
|
+
},
|
|
134
|
+
]}
|
|
135
|
+
/>
|
|
136
|
+
<SheetRow
|
|
137
|
+
label="Myo — upcoming"
|
|
138
|
+
meta="~15 + clusters to failure"
|
|
139
|
+
sets={[{ status: 'myo-upcoming', activationLen: 15 }]}
|
|
140
|
+
/>
|
|
141
|
+
</View>
|
|
142
|
+
),
|
|
143
|
+
parameters: {
|
|
144
|
+
layout: 'padded',
|
|
145
|
+
docs: {
|
|
146
|
+
description: {
|
|
147
|
+
story:
|
|
148
|
+
'The four set-type variants (range / drop / myo / myo-upcoming) plus the flat sets, ' +
|
|
149
|
+
'at rail width. Gap hierarchy: butted reps (0) < notch (2px, drop) < cluster (3px, myo) ' +
|
|
150
|
+
'< set gap (5px). Cyan is the shared variable/unknown/opportunity pin.',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}
|