@titan-design/react-ui 0.10.0 → 0.12.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 +173 -343
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +173 -343
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +416 -18
- package/dist/index.d.ts +416 -18
- package/dist/index.js +2512 -1307
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2480 -1299
- 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 +1644 -1575
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1645 -1576
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +409 -415
- package/dist/theme/index.d.ts +409 -415
- package/dist/theme/index.js +234 -408
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +227 -399
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +141 -140
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +141 -140
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +8 -3
- package/src/arch/Architecture.stories.tsx +9 -9
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- 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/Sidebar/Sidebar.tsx +4 -4
- package/src/components/custom/Table/Table.tsx +2 -2
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
- package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
- package/src/components/custom/Workout/BaseBadge.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.tsx +2 -2
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
- package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
- package/src/components/custom/Workout/DeviationBar.tsx +3 -2
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +7 -7
- package/src/components/custom/Workout/ExerciseCard.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +11 -5
- package/src/components/custom/Workout/InputBar.tsx +2 -2
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
- package/src/components/custom/Workout/MesoCard.tsx +3 -1
- package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
- package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
- package/src/components/custom/Workout/RestTimer.tsx +2 -2
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionHeader.tsx +1 -1
- package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +63 -18
- package/src/components/custom/Workout/SetBar.tsx +3 -3
- package/src/components/custom/Workout/SetRow.test.tsx +12 -9
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/StatusDot.tsx +5 -4
- package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
- package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -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 +199 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
- package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
- package/src/components/custom/Workout/WeightBadge.tsx +2 -1
- package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
- package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
- 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/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/custom/stepper/Stepper.tsx +3 -3
- package/src/components/shell/DeviceMenu.tsx +2 -2
- package/src/components/shell/SideNav.tsx +1 -1
- package/src/components/shell/TopBar.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -2
- package/src/components/ui/badge/Badge.tsx +2 -2
- package/src/components/ui/card/Card.tsx +3 -3
- package/src/components/ui/checkbox/Checkbox.tsx +1 -1
- package/src/components/ui/collapse/Collapse.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.tsx +3 -3
- package/src/components/ui/form-field/FormField.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.tsx +1 -1
- package/src/components/ui/menu/Menu.tsx +2 -2
- package/src/components/ui/pill/Pill.tsx +2 -2
- package/src/components/ui/popover/Popover.stories.tsx +2 -2
- package/src/components/ui/popover/Popover.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +3 -3
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.tsx +4 -4
- package/src/components/ui/select/Select.tsx +3 -3
- package/src/components/ui/surface/Surface.stories.tsx +3 -3
- package/src/components/ui/surface/Surface.test.tsx +32 -19
- package/src/components/ui/surface/Surface.tsx +3 -3
- package/src/components/ui/surface/SurfaceContext.ts +19 -29
- package/src/components/ui/surface/surface.contract.test.ts +57 -44
- package/src/components/ui/switch/Switch.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +2 -2
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
- package/src/stories/PresetShowcase.stories.tsx +21 -21
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +874 -0
- package/src/theme/ColorPrimitives.stories.tsx +348 -0
- package/src/theme/Depth.stories.tsx +312 -0
- package/src/theme/DepthCalibration.stories.tsx +655 -0
- package/src/theme/ThemeProvider.tsx +1 -1
- package/src/theme/barrel.ts +7 -14
- package/src/theme/color-stories.coverage.test.ts +125 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/theme/color-utils.ts +197 -0
- package/src/theme/config.ts +0 -8
- package/src/theme/depth-calibration.test.tsx +97 -0
- package/src/theme/elevation.test.ts +62 -1
- package/src/theme/elevation.ts +34 -8
- package/src/theme/global.css +47 -53
- package/src/theme/grey-ramp.test.ts +166 -0
- package/src/theme/materials.test.ts +106 -0
- package/src/theme/materials.ts +211 -0
- package/src/theme/presets/audiobook.ts +4 -4
- package/src/theme/presets/types.ts +3 -3
- package/src/theme/semantic-contrast.test.ts +83 -0
- package/src/theme/tokens/primitives.ts +62 -115
- package/src/theme/tokens/semantic.ts +130 -82
- package/src/utils/colors.ts +1 -1
- package/tailwind.config.js +2 -5
- 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
- package/src/theme/shadows.stories.tsx +0 -523
- package/src/theme/shadows.ts +0 -439
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
2
|
import { View, Text, type ViewProps } from 'react-native'
|
|
3
3
|
import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
|
|
4
|
-
import {
|
|
4
|
+
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
5
5
|
import { ZoneTrack } from './ZoneTrack'
|
|
6
6
|
import { DataRow } from '../../ui/data-row/DataRow'
|
|
7
7
|
import type { VolumeLandmarks } from './muscleTaxonomy'
|
|
@@ -11,9 +11,9 @@ import type { VolumeLandmarks } from './muscleTaxonomy'
|
|
|
11
11
|
// BodyMap / MesoProgressBar so a muscle reads identically across the app.
|
|
12
12
|
const HEAT = WORKOUT_TOKENS.heatmap
|
|
13
13
|
|
|
14
|
-
// The muted, un-reached track colour — the same
|
|
14
|
+
// The muted, un-reached track colour — the same grey step ZoneTrack defaults
|
|
15
15
|
// to, so the bar sits on the shared gauge-track surface.
|
|
16
|
-
const NEUTRAL_TRACK =
|
|
16
|
+
const NEUTRAL_TRACK = greyRamp[800]
|
|
17
17
|
const MONO = 'monospace'
|
|
18
18
|
|
|
19
19
|
export type VolumeZone = 'under' | 'maintenance' | 'productive' | 'approaching' | 'over'
|
|
@@ -2,6 +2,11 @@ import { describe, it, expect, vi } from 'vitest'
|
|
|
2
2
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
3
|
import { axe } from 'jest-axe'
|
|
4
4
|
import { WeightBadge } from './WeightBadge'
|
|
5
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
6
|
+
|
|
7
|
+
// Read from the token, not pinned: these used to be hand-copied hexes and
|
|
8
|
+
// silently desynced when borders became alpha hairlines (TD-07.16).
|
|
9
|
+
const T = getSemanticColors('dark')
|
|
5
10
|
|
|
6
11
|
describe('WeightBadge', () => {
|
|
7
12
|
it('renders value and default unit', () => {
|
|
@@ -71,7 +76,7 @@ describe('WeightBadge', () => {
|
|
|
71
76
|
it('uses default border color when not a PR', () => {
|
|
72
77
|
render(<WeightBadge value={225} />)
|
|
73
78
|
expect(screen.getByTestId('weight-badge')).toHaveStyle({
|
|
74
|
-
borderTopColor: '
|
|
79
|
+
borderTopColor: T['hairline-default'],
|
|
75
80
|
})
|
|
76
81
|
})
|
|
77
82
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type BaseBadgeProps,
|
|
9
9
|
} from './BaseBadge'
|
|
10
10
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
11
|
+
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
11
12
|
|
|
12
13
|
export type WeightBadgeSize = BaseBadgeSize
|
|
13
14
|
|
|
@@ -43,7 +44,7 @@ export function WeightBadge({
|
|
|
43
44
|
...props
|
|
44
45
|
}: WeightBadgeProps) {
|
|
45
46
|
const config = baseBadgeSizeConfig[size]
|
|
46
|
-
const textColor = isPr ? getSemanticColors('dark')['brand-primary'] :
|
|
47
|
+
const textColor = isPr ? getSemanticColors('dark')['brand-primary'] : greyRamp[400]
|
|
47
48
|
// Dumbbell reads clearly as a weight/strength metric and is unit-agnostic
|
|
48
49
|
// across estimated-1RM and N-rep-max contexts (vs. TrendingUp which implies
|
|
49
50
|
// change/trend rather than a load value).
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from './MuscleGroupChip'
|
|
9
9
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
10
10
|
|
|
11
|
+
const HAIRLINE_DEFAULT = getSemanticColors('dark')['hairline-default']
|
|
12
|
+
|
|
11
13
|
const t = getSemanticColors('dark')
|
|
12
14
|
|
|
13
15
|
export type WorkoutStatus = 'completed' | 'today' | 'upcoming'
|
|
@@ -48,7 +50,7 @@ export interface WorkoutCardProps extends ViewProps {
|
|
|
48
50
|
const COLORS = {
|
|
49
51
|
statusSuccess: t['status-success'],
|
|
50
52
|
brandPrimary: t['brand-primary'],
|
|
51
|
-
borderDefault:
|
|
53
|
+
borderDefault: HAIRLINE_DEFAULT,
|
|
52
54
|
brandPrimarySubtle: 'rgba(255,121,0,0.06)',
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -3,6 +3,9 @@ import { useEffect, useState } from 'react'
|
|
|
3
3
|
import { View, Text, Pressable, Animated, Easing, type ViewProps } from 'react-native'
|
|
4
4
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
5
5
|
import { WORKOUT_PILL_DELOAD } from '../../../theme/extracted-colors-dataviz'
|
|
6
|
+
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
7
|
+
|
|
8
|
+
const HAIRLINE_DEFAULT = getSemanticColors('dark')['hairline-default']
|
|
6
9
|
|
|
7
10
|
const t = getSemanticColors('dark')
|
|
8
11
|
|
|
@@ -35,7 +38,7 @@ const statusBorderStyles: Record<WorkoutPillStatus, Record<string, unknown>> = {
|
|
|
35
38
|
completed: { borderWidth: 1, borderColor: 'rgba(46,213,115,0.3)' },
|
|
36
39
|
current: { borderWidth: 1, borderColor: 'rgba(255,121,0,0.3)' },
|
|
37
40
|
next: { borderWidth: 1, borderColor: 'rgba(255,121,0,0.4)' },
|
|
38
|
-
upcoming: { borderWidth: 1, borderColor:
|
|
41
|
+
upcoming: { borderWidth: 1, borderColor: HAIRLINE_DEFAULT },
|
|
39
42
|
missed: { borderWidth: 1, borderColor: 'rgba(209,67,67,0.25)' },
|
|
40
43
|
deload: { borderWidth: 1, borderColor: 'rgba(186,41,150,0.3)' },
|
|
41
44
|
}
|
|
@@ -44,7 +47,7 @@ const textColors: Record<WorkoutPillStatus, string> = {
|
|
|
44
47
|
completed: t['status-success'],
|
|
45
48
|
current: t['brand-primary'],
|
|
46
49
|
next: t['brand-primary'],
|
|
47
|
-
upcoming:
|
|
50
|
+
upcoming: greyRamp[500],
|
|
48
51
|
missed: 'rgba(209,67,67,0.7)',
|
|
49
52
|
deload: WORKOUT_PILL_DELOAD,
|
|
50
53
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useState } from 'react'
|
|
3
3
|
import { View, Text, type ViewProps, type DimensionValue, type LayoutChangeEvent } from 'react-native'
|
|
4
4
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
5
|
-
import { primitiveColors } from '../../../theme/tokens/primitives'
|
|
5
|
+
import { primitiveColors, greyRamp } from '../../../theme/tokens/primitives'
|
|
6
6
|
import { alpha } from '../../../utils/colors'
|
|
7
7
|
import { Tooltip } from '../../ui/tooltip/Tooltip'
|
|
8
8
|
|
|
@@ -13,8 +13,8 @@ function glowShadow(color: string): string {
|
|
|
13
13
|
return `0 0 5px 1px ${alpha(color, 0.3)}, 0 0 10px 3px ${alpha(color, 0.12)}`
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/** Muted, un-reached track colour — a
|
|
17
|
-
const DEFAULT_TRACK_COLOR =
|
|
16
|
+
/** Muted, un-reached track colour — a grey step, matches the IntensityBar track family. */
|
|
17
|
+
const DEFAULT_TRACK_COLOR = greyRamp[800]
|
|
18
18
|
/** Default needle / fill-marker colour. */
|
|
19
19
|
const DEFAULT_MARKER_COLOR = primitiveColors.white
|
|
20
20
|
/** Tick mark + tick label colour. */
|
|
@@ -139,7 +139,7 @@ export interface ZoneTrackProps extends ViewProps {
|
|
|
139
139
|
trackHeight?: number
|
|
140
140
|
/** How far a needle marker overhangs the track top + bottom, in px. Overrides the `size` default (6 default / 9 wall). */
|
|
141
141
|
needleOverhang?: number
|
|
142
|
-
/** Muted colour of the track behind / beyond the fill. Default a
|
|
142
|
+
/** Muted colour of the track behind / beyond the fill. Default a grey step. */
|
|
143
143
|
trackColor?: string
|
|
144
144
|
className?: string
|
|
145
145
|
}
|
|
@@ -27,6 +27,9 @@ export {
|
|
|
27
27
|
getVelocityZoneName,
|
|
28
28
|
calculateVelocityLoss,
|
|
29
29
|
calculateMeanVelocity,
|
|
30
|
+
DualVelocityStrip,
|
|
31
|
+
type DualVelocityStripProps,
|
|
32
|
+
type DualVelocityStream,
|
|
30
33
|
} from './VelocityStrip'
|
|
31
34
|
export { MuscleGroupChip, type MuscleGroupChipProps, type VolumeStatus } from './MuscleGroupChip'
|
|
32
35
|
export { Sparkline, type SparklineProps } from './Sparkline'
|
|
@@ -5,19 +5,25 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ReactNode } from 'react'
|
|
7
7
|
import { TempoDisplay } from './TempoDisplay'
|
|
8
|
+
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
9
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
8
10
|
|
|
9
11
|
// ---- geometry
|
|
10
12
|
export const RAIL_W = 232
|
|
11
13
|
export const SET_GAP = 5
|
|
12
14
|
|
|
13
|
-
// ---- surface + text tokens (dark theme
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export const
|
|
19
|
-
export const
|
|
20
|
-
export const
|
|
15
|
+
// ---- surface + text tokens (dark theme)
|
|
16
|
+
// These were hand-copied hexes labelled with their grey/neutral steps, which
|
|
17
|
+
// is exactly how they silently stopped matching the ramps they named. They read
|
|
18
|
+
// from the tokens now, so a retune reaches them.
|
|
19
|
+
const T = getSemanticColors('dark')
|
|
20
|
+
export const GREY = greyRamp[900] // upcoming/placeholder
|
|
21
|
+
export const BORDER_SUBTLE = greyRamp[950] // row dividers
|
|
22
|
+
export const T_PRIMARY = T['text-primary']
|
|
23
|
+
export const T_SECONDARY = T['text-secondary']
|
|
24
|
+
export const T_TERTIARY = T['text-tertiary']
|
|
25
|
+
export const RAISED = greyRamp[925] // nav + heading raised plane
|
|
26
|
+
export const INSET = greyRamp[975] // sunk exercise-list column
|
|
21
27
|
// depth lives on the sunk list: inner shadow from the heading (top) + nav (left). Raised plane is flat.
|
|
22
28
|
export const INSET_SHADOW =
|
|
23
29
|
'inset 0 9px 12px -6px rgba(0,0,0,0.85), inset 7px 0 10px -6px rgba(0,0,0,0.7), inset 0 -1px 0 rgba(255,255,255,0.02)'
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared fixtures and framing for the VelocityStrip story groups.
|
|
3
|
+
*
|
|
4
|
+
* The strip's stories are split by VIEW (compact / expanded / hero), and each
|
|
5
|
+
* group walks the SAME scenario set against the SAME datasets. That only proves
|
|
6
|
+
* anything if every group is genuinely showing the same data — so the datasets,
|
|
7
|
+
* the single-above-dual pairing, and the set-type board all live here rather
|
|
8
|
+
* than being re-typed per file.
|
|
9
|
+
*
|
|
10
|
+
* Dual is not a fourth view. It is the same strip when the exercise used two
|
|
11
|
+
* Voltras, so every scenario renders single ABOVE dual on one dataset and the
|
|
12
|
+
* pairing itself carries the point: dual = single + a lower half.
|
|
13
|
+
*
|
|
14
|
+
* Not a `.stories` file on purpose — it exports no stories, and keeping it out
|
|
15
|
+
* of the story glob means the chrome below is held to the same token rules as
|
|
16
|
+
* component code.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ReactNode } from 'react'
|
|
20
|
+
import type { Decorator } from '@storybook/react-vite'
|
|
21
|
+
import { View, Text } from 'react-native'
|
|
22
|
+
import { VelocityStrip, DualVelocityStrip, type VelocitySet } from './VelocityStrip'
|
|
23
|
+
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
24
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
25
|
+
|
|
26
|
+
const t = getSemanticColors('dark')
|
|
27
|
+
|
|
28
|
+
/** The real dark surface-background plane — stories sit on the token, not an ad-hoc grey. */
|
|
29
|
+
export const SURFACE_BG = greyRamp[950]
|
|
30
|
+
|
|
31
|
+
export const VIEW_LABEL = t['text-secondary']
|
|
32
|
+
export const VIEW_NOTE = t['text-tertiary']
|
|
33
|
+
export const VIEW_HEADING = t['text-primary']
|
|
34
|
+
|
|
35
|
+
export const LEFT_SLOT = 'Left'
|
|
36
|
+
export const RIGHT_SLOT = 'Right'
|
|
37
|
+
|
|
38
|
+
// ── Datasets ────────────────────────────────────────────────────────────────
|
|
39
|
+
// One representative set reused across every view, so a drift in one view is
|
|
40
|
+
// visible as a mismatch against the others rather than hiding behind new data.
|
|
41
|
+
|
|
42
|
+
/** The representative set: a clean, mildly-declining working set. */
|
|
43
|
+
export const REP_SET = [0.95, 0.9, 0.86, 0.8, 0.72]
|
|
44
|
+
/** The lagging partner — 4 reps to REP_SET's 5, so the index-lock shows an empty. */
|
|
45
|
+
export const REP_SET_LAGGING = [0.9, 0.83, 0.75, 0.66]
|
|
46
|
+
|
|
47
|
+
/** A hard fatigue decline — the loss colouring and VL bands have something to say. */
|
|
48
|
+
export const FATIGUE_SET = [0.96, 0.91, 0.83, 0.72, 0.61, 0.5]
|
|
49
|
+
export const FATIGUE_SET_LAGGING = [0.94, 0.87, 0.76, 0.63, 0.48]
|
|
50
|
+
|
|
51
|
+
/** Mid-set, still going: 4 done of 8 planned, newest bar live. */
|
|
52
|
+
export const IN_PROGRESS_SET = [0.82, 0.79, 0.81, 0.76]
|
|
53
|
+
export const IN_PROGRESS_LAGGING = [0.8, 0.77, 0.75]
|
|
54
|
+
|
|
55
|
+
export const dualOf = (velocities: number[], label: string) => ({ velocities, label })
|
|
56
|
+
|
|
57
|
+
// ── Framing ─────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export function ViewLabel({ text }: { text: string }) {
|
|
60
|
+
return (
|
|
61
|
+
<Text
|
|
62
|
+
style={{
|
|
63
|
+
color: VIEW_LABEL,
|
|
64
|
+
fontSize: 10,
|
|
65
|
+
fontWeight: '700',
|
|
66
|
+
letterSpacing: 1,
|
|
67
|
+
textTransform: 'uppercase',
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{text}
|
|
71
|
+
</Text>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function Note({ children }: { children: ReactNode }) {
|
|
76
|
+
return <Text style={{ color: VIEW_NOTE, fontSize: 10, lineHeight: 15 }}>{children}</Text>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Page frame every scenario story sits in — one surface, one rhythm. */
|
|
80
|
+
export function Sheet({ children, width = 640 }: { children: ReactNode; width?: number }) {
|
|
81
|
+
return (
|
|
82
|
+
<View style={{ padding: 24, backgroundColor: SURFACE_BG, gap: 26, width }}>{children}</View>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Wall frame for hero stories. The eyebrow is ORGANISM chrome — the north-star
|
|
88
|
+
* live page renders it, the primitive does not. It sits here only so the hero
|
|
89
|
+
* is judged in the context it will live in.
|
|
90
|
+
*/
|
|
91
|
+
export const wallDecorator: Decorator = (Story) => (
|
|
92
|
+
<View style={{ width: 620, padding: 28, backgroundColor: SURFACE_BG }}>
|
|
93
|
+
<Text
|
|
94
|
+
style={{
|
|
95
|
+
color: VIEW_NOTE,
|
|
96
|
+
fontSize: 10,
|
|
97
|
+
fontWeight: '700',
|
|
98
|
+
letterSpacing: 1,
|
|
99
|
+
marginBottom: 12,
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
CONCENTRIC VELOCITY · THIS SET · (organism chrome — not the component)
|
|
103
|
+
</Text>
|
|
104
|
+
<Story />
|
|
105
|
+
</View>
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
// ── The single-above-dual pairing ───────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/** Per-view heights. Dual expanded is taller because value-height genuinely needs 2×. */
|
|
111
|
+
export const VIEW_HEIGHT: Record<StripView, { single: number; dual: number }> = {
|
|
112
|
+
compact: { single: 11.5, dual: 11.5 },
|
|
113
|
+
expanded: { single: 60, dual: 64 },
|
|
114
|
+
hero: { single: 180, dual: 200 },
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type StripView = 'compact' | 'expanded' | 'hero'
|
|
118
|
+
|
|
119
|
+
/** The dual renderer for a view. `expanded`'s dual is the lean `rail` (a verified misnomer). */
|
|
120
|
+
export const dualVariantFor = (view: StripView) => (view === 'expanded' ? 'rail' : view)
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* One scenario, rendered single above dual on the same dataset.
|
|
124
|
+
*
|
|
125
|
+
* Takes either raw velocities or full `VelocitySet` descriptors, because the
|
|
126
|
+
* set-type scenarios need the descriptor form and the rest do not.
|
|
127
|
+
*/
|
|
128
|
+
export function ScenarioPair({
|
|
129
|
+
view,
|
|
130
|
+
title,
|
|
131
|
+
note,
|
|
132
|
+
single,
|
|
133
|
+
left,
|
|
134
|
+
right,
|
|
135
|
+
singleSet,
|
|
136
|
+
leftSet,
|
|
137
|
+
rightSet,
|
|
138
|
+
scale = 'peak',
|
|
139
|
+
width,
|
|
140
|
+
...rest
|
|
141
|
+
}: {
|
|
142
|
+
view: StripView
|
|
143
|
+
title: string
|
|
144
|
+
note?: string
|
|
145
|
+
single?: number[]
|
|
146
|
+
left?: number[]
|
|
147
|
+
right?: number[]
|
|
148
|
+
singleSet?: VelocitySet
|
|
149
|
+
leftSet?: VelocitySet
|
|
150
|
+
rightSet?: VelocitySet
|
|
151
|
+
scale?: 'peak' | 'fixed'
|
|
152
|
+
/** Constrains the pair, for the width-degrade ladders. Unset = fill the sheet. */
|
|
153
|
+
width?: number
|
|
154
|
+
targetReps?: number
|
|
155
|
+
liveRepIndex?: number
|
|
156
|
+
barColor?: 'zone' | 'loss'
|
|
157
|
+
}) {
|
|
158
|
+
const h = VIEW_HEIGHT[view]
|
|
159
|
+
const heroProps = view === 'hero' ? { label: 'Set' } : {}
|
|
160
|
+
const bareExpanded = view === 'expanded' ? { showNumbers: false, showInfo: false } : {}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<View style={{ gap: 10, ...(width == null ? null : { width }) }}>
|
|
164
|
+
<View style={{ gap: 3 }}>
|
|
165
|
+
<ViewLabel text={title} />
|
|
166
|
+
{note ? <Note>{note}</Note> : null}
|
|
167
|
+
</View>
|
|
168
|
+
|
|
169
|
+
<ViewLabel text="single" />
|
|
170
|
+
<VelocityStrip
|
|
171
|
+
{...(singleSet ? { set: singleSet } : { velocities: single ?? [] })}
|
|
172
|
+
variant={view}
|
|
173
|
+
height={h.single}
|
|
174
|
+
scale={scale}
|
|
175
|
+
{...heroProps}
|
|
176
|
+
{...bareExpanded}
|
|
177
|
+
{...rest}
|
|
178
|
+
/>
|
|
179
|
+
|
|
180
|
+
<ViewLabel text="dual" />
|
|
181
|
+
<DualVelocityStrip
|
|
182
|
+
left={leftSet ? { set: leftSet, label: LEFT_SLOT } : dualOf(left ?? [], LEFT_SLOT)}
|
|
183
|
+
right={rightSet ? { set: rightSet, label: RIGHT_SLOT } : dualOf(right ?? [], RIGHT_SLOT)}
|
|
184
|
+
variant={dualVariantFor(view)}
|
|
185
|
+
height={h.dual}
|
|
186
|
+
scale={scale}
|
|
187
|
+
{...rest}
|
|
188
|
+
/>
|
|
189
|
+
</View>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── Set-type board ──────────────────────────────────────────────────────────
|
|
194
|
+
// The full set-type vocabulary, one row per type, each paired single-above-dual.
|
|
195
|
+
// Generalised over `view` so compact / expanded / hero all walk the same board
|
|
196
|
+
// instead of the hero owning a private copy.
|
|
197
|
+
|
|
198
|
+
export type BoardRow = {
|
|
199
|
+
type: string
|
|
200
|
+
note: string
|
|
201
|
+
single: VelocitySet
|
|
202
|
+
left: VelocitySet
|
|
203
|
+
right: VelocitySet
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export const SET_TYPE_BOARD: BoardRow[] = [
|
|
207
|
+
{
|
|
208
|
+
type: 'straight',
|
|
209
|
+
note: 'done reps + solid to-do remainder to the planned count',
|
|
210
|
+
single: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78], planned: 6 },
|
|
211
|
+
left: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78], planned: 6 },
|
|
212
|
+
right: { type: 'straight', velocities: [0.84, 0.79], planned: 6 },
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
type: 'range',
|
|
216
|
+
note: 'committed rep bars + solid floor + the cyan variable window (floor..max)',
|
|
217
|
+
single: { type: 'range', velocities: [0.9, 0.86, 0.82, 0.8], floor: 6, max: 8 },
|
|
218
|
+
left: { type: 'range', velocities: [0.9, 0.86, 0.82, 0.8], floor: 6, max: 8 },
|
|
219
|
+
right: { type: 'range', velocities: [0.83, 0.78], floor: 6, max: 8 },
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
type: 'amrap',
|
|
223
|
+
note: 'performed reps + the trailing cyan-outline "continue" window',
|
|
224
|
+
single: { type: 'amrap', velocities: [0.9, 0.85, 0.8, 0.75] },
|
|
225
|
+
left: { type: 'amrap', velocities: [0.9, 0.85, 0.8, 0.75] },
|
|
226
|
+
right: { type: 'amrap', velocities: [0.82, 0.76] },
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: 'drop',
|
|
230
|
+
note: 'one bar per rep with WIDE chunk-notch gaps splitting each sub-load',
|
|
231
|
+
single: {
|
|
232
|
+
type: 'drop',
|
|
233
|
+
subloads: [
|
|
234
|
+
[0.95, 0.9],
|
|
235
|
+
[0.82, 0.76],
|
|
236
|
+
[0.7, 0.64],
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
left: {
|
|
240
|
+
type: 'drop',
|
|
241
|
+
subloads: [
|
|
242
|
+
[0.95, 0.9],
|
|
243
|
+
[0.82, 0.76],
|
|
244
|
+
[0.7, 0.64],
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
right: { type: 'drop', subloads: [[0.88, 0.83], [0.75, 0.68], [0.62]] },
|
|
248
|
+
},
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
export function SetTypeBoard({ view }: { view: StripView }) {
|
|
252
|
+
const h = VIEW_HEIGHT[view]
|
|
253
|
+
return (
|
|
254
|
+
<View style={{ gap: 30 }}>
|
|
255
|
+
{SET_TYPE_BOARD.map((row) => (
|
|
256
|
+
<View key={row.type} style={{ gap: 8 }}>
|
|
257
|
+
<View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
|
|
258
|
+
<Text
|
|
259
|
+
style={{
|
|
260
|
+
color: VIEW_HEADING,
|
|
261
|
+
fontSize: 13,
|
|
262
|
+
fontWeight: '800',
|
|
263
|
+
letterSpacing: 1,
|
|
264
|
+
textTransform: 'uppercase',
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
{row.type}
|
|
268
|
+
</Text>
|
|
269
|
+
<Note>{row.note}</Note>
|
|
270
|
+
</View>
|
|
271
|
+
|
|
272
|
+
<ViewLabel text="single" />
|
|
273
|
+
<VelocityStrip
|
|
274
|
+
variant={view}
|
|
275
|
+
set={row.single}
|
|
276
|
+
height={h.single}
|
|
277
|
+
scale="fixed"
|
|
278
|
+
{...(view === 'hero' ? { label: 'This Set' } : {})}
|
|
279
|
+
{...(view === 'expanded' ? { showNumbers: false, showInfo: false } : {})}
|
|
280
|
+
/>
|
|
281
|
+
|
|
282
|
+
<ViewLabel text="dual · symmetric structure, right logs fewer → aligned empties" />
|
|
283
|
+
<DualVelocityStrip
|
|
284
|
+
left={{ set: row.left, label: LEFT_SLOT }}
|
|
285
|
+
right={{ set: row.right, label: RIGHT_SLOT }}
|
|
286
|
+
variant={dualVariantFor(view)}
|
|
287
|
+
height={h.dual}
|
|
288
|
+
scale="fixed"
|
|
289
|
+
/>
|
|
290
|
+
</View>
|
|
291
|
+
))}
|
|
292
|
+
</View>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ── Rep-type coverage ───────────────────────────────────────────────────────
|
|
297
|
+
// Every state a single rep column can be in, in one board. The velocity zones
|
|
298
|
+
// are only three of them; the rest come from the set descriptor (planned
|
|
299
|
+
// remainder, the repeatable windows, drop sub-load splits) or from the dual's
|
|
300
|
+
// index-lock (a rep one side logged and the other did not).
|
|
301
|
+
//
|
|
302
|
+
// Generalised over `view` for the same reason the set-type board is: a state
|
|
303
|
+
// that renders on hero but not compact is exactly the class of bug this is
|
|
304
|
+
// meant to surface.
|
|
305
|
+
|
|
306
|
+
export type RepTypeRow = {
|
|
307
|
+
key: string
|
|
308
|
+
label: string
|
|
309
|
+
note: string
|
|
310
|
+
single: VelocitySet
|
|
311
|
+
left: VelocitySet
|
|
312
|
+
right: VelocitySet
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Velocities chosen to land in each zone: fast, moderate, slow, grinding. */
|
|
316
|
+
const ZONE_SET = [1.15, 0.88, 0.66, 0.42]
|
|
317
|
+
|
|
318
|
+
export const REP_TYPE_ROWS: RepTypeRow[] = [
|
|
319
|
+
{
|
|
320
|
+
key: 'zones',
|
|
321
|
+
label: 'Logged reps · the velocity zones',
|
|
322
|
+
note: 'fast → moderate → slow → grinding. One bar per rep, coloured by the zone it landed in.',
|
|
323
|
+
single: { type: 'straight', velocities: ZONE_SET },
|
|
324
|
+
left: { type: 'straight', velocities: ZONE_SET },
|
|
325
|
+
right: { type: 'straight', velocities: ZONE_SET },
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
key: 'upcoming',
|
|
329
|
+
label: 'Upcoming · the planned remainder',
|
|
330
|
+
note: 'Two logged of six planned. The remainder holds its columns so the set never reflows as reps land.',
|
|
331
|
+
single: { type: 'straight', velocities: [0.9, 0.86], planned: 6 },
|
|
332
|
+
left: { type: 'straight', velocities: [0.9, 0.86], planned: 6 },
|
|
333
|
+
right: { type: 'straight', velocities: [0.88, 0.84], planned: 6 },
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
key: 'missed-bottom',
|
|
337
|
+
label: 'Missed rep · lower slot',
|
|
338
|
+
note: 'Both slots logged four, but the LOWER one missed rep 3. Its column stays as an index-locked empty, fainter than a planned rep — so the gap reads as missing, not upcoming.',
|
|
339
|
+
single: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78] },
|
|
340
|
+
left: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78] },
|
|
341
|
+
right: { type: 'straight', velocities: [0.88, 0.84, 0.76] },
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
key: 'missed-top',
|
|
345
|
+
label: 'Missed rep · upper slot',
|
|
346
|
+
note: 'The mirror case — the UPPER slot is the one short a rep. The empty sits above the axis instead of below it.',
|
|
347
|
+
single: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78] },
|
|
348
|
+
left: { type: 'straight', velocities: [0.9, 0.86, 0.82] },
|
|
349
|
+
right: { type: 'straight', velocities: [0.88, 0.84, 0.8, 0.76] },
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
key: 'variable',
|
|
353
|
+
label: 'Variable · a rep range',
|
|
354
|
+
note: 'A range set: committed reps to the floor, then the variable window up to the max. Outlined, never filled — a rep that MIGHT happen must not read as one that did.',
|
|
355
|
+
single: { type: 'range', velocities: [0.9, 0.86, 0.82], floor: 4, max: 6 },
|
|
356
|
+
left: { type: 'range', velocities: [0.9, 0.86, 0.82], floor: 4, max: 6 },
|
|
357
|
+
right: { type: 'range', velocities: [0.88, 0.84, 0.8], floor: 4, max: 6 },
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
key: 'repeatable',
|
|
361
|
+
label: 'Repeatable · AMRAP / myo continue',
|
|
362
|
+
note: 'No planned ceiling — the trailing continue window says another rep is allowed at any time.',
|
|
363
|
+
single: { type: 'amrap', velocities: [0.9, 0.85, 0.8] },
|
|
364
|
+
left: { type: 'amrap', velocities: [0.9, 0.85, 0.8] },
|
|
365
|
+
right: { type: 'amrap', velocities: [0.88, 0.83, 0.78] },
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
key: 'drop',
|
|
369
|
+
label: 'Drop · sub-load splits',
|
|
370
|
+
note: 'One bar per rep, with a wide chunk-notch separating each sub-load. The notch is a gap in the columns, so it survives even at compact height.',
|
|
371
|
+
single: {
|
|
372
|
+
type: 'drop',
|
|
373
|
+
subloads: [
|
|
374
|
+
[0.95, 0.9],
|
|
375
|
+
[0.82, 0.76],
|
|
376
|
+
[0.7, 0.64],
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
left: {
|
|
380
|
+
type: 'drop',
|
|
381
|
+
subloads: [
|
|
382
|
+
[0.95, 0.9],
|
|
383
|
+
[0.82, 0.76],
|
|
384
|
+
[0.7, 0.64],
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
right: {
|
|
388
|
+
type: 'drop',
|
|
389
|
+
subloads: [
|
|
390
|
+
[0.93, 0.88],
|
|
391
|
+
[0.8, 0.74],
|
|
392
|
+
[0.68, 0.62],
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
export function RepTypeBoard({ view }: { view: StripView }) {
|
|
399
|
+
const h = VIEW_HEIGHT[view]
|
|
400
|
+
return (
|
|
401
|
+
<View style={{ gap: 30 }}>
|
|
402
|
+
{REP_TYPE_ROWS.map((row) => (
|
|
403
|
+
<View key={row.key} style={{ gap: 8 }}>
|
|
404
|
+
<Text
|
|
405
|
+
style={{
|
|
406
|
+
color: VIEW_HEADING,
|
|
407
|
+
fontSize: 13,
|
|
408
|
+
fontWeight: '800',
|
|
409
|
+
letterSpacing: 0.5,
|
|
410
|
+
}}
|
|
411
|
+
>
|
|
412
|
+
{row.label}
|
|
413
|
+
</Text>
|
|
414
|
+
<Note>{row.note}</Note>
|
|
415
|
+
|
|
416
|
+
<ViewLabel text="single" />
|
|
417
|
+
<VelocityStrip
|
|
418
|
+
variant={view}
|
|
419
|
+
set={row.single}
|
|
420
|
+
height={h.single}
|
|
421
|
+
scale="fixed"
|
|
422
|
+
{...(view === 'hero' ? { label: 'Set' } : {})}
|
|
423
|
+
{...(view === 'expanded' ? { showNumbers: false, showInfo: false } : {})}
|
|
424
|
+
/>
|
|
425
|
+
|
|
426
|
+
<ViewLabel text="dual" />
|
|
427
|
+
<DualVelocityStrip
|
|
428
|
+
left={{ set: row.left, label: LEFT_SLOT }}
|
|
429
|
+
right={{ set: row.right, label: RIGHT_SLOT }}
|
|
430
|
+
variant={dualVariantFor(view)}
|
|
431
|
+
height={h.dual}
|
|
432
|
+
scale="fixed"
|
|
433
|
+
/>
|
|
434
|
+
</View>
|
|
435
|
+
))}
|
|
436
|
+
</View>
|
|
437
|
+
)
|
|
438
|
+
}
|