@titan-design/react-ui 0.13.0 → 0.14.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/README.md +14 -0
- package/dist/bodymap.js +129 -76
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +129 -76
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +117 -26
- package/dist/index.d.ts +117 -26
- package/dist/index.js +677 -450
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +673 -451
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-_3Ajpiqu.d.ts → pages-CsjsUn0G.d.ts} +30 -13
- package/dist/{pages-CpW3OB3g.d.mts → pages-ZbT_pD_U.d.mts} +30 -13
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +676 -701
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +676 -701
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +31 -7
- package/dist/theme/index.d.ts +31 -7
- package/dist/theme/index.js +84 -20
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +83 -21
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +84 -20
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +84 -20
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/dist/theme/tokens.d.mts +56 -7
- package/dist/theme/tokens.d.ts +56 -7
- package/dist/theme/tokens.js +72 -20
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/tokens.mjs +71 -21
- package/dist/theme/tokens.mjs.map +1 -1
- package/docs/render-testing-pattern.md +130 -0
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +3 -1
- package/src/arch/arch-graph.json +74 -72
- package/src/components/custom/ActiveWork/InitiativeBrief.tsx +2 -1
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +8 -3
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +4 -1
- package/src/components/custom/Gauge/Gauge.tsx +3 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +10 -7
- package/src/components/custom/Scatter/Scatter.tsx +6 -4
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/Table/Table.tsx +2 -2
- package/src/components/custom/Treemap/Treemap.stories.tsx +13 -10
- package/src/components/custom/Treemap/Treemap.tsx +4 -1
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +2 -1
- package/src/components/custom/Workout/BaseBadge.stories.tsx +11 -7
- package/src/components/custom/Workout/BaseBadge.tsx +3 -2
- package/src/components/custom/Workout/BodyMap.tsx +5 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +13 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +5 -3
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +2 -1
- package/src/components/custom/Workout/CapacityBandChart.tsx +5 -3
- package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
- package/src/components/custom/Workout/DeviationBar.tsx +5 -2
- package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +7 -6
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +29 -16
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -8
- package/src/components/custom/Workout/ExerciseCard.test.tsx +39 -19
- package/src/components/custom/Workout/ExerciseCard.tsx +92 -167
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +89 -6
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
- package/src/components/custom/Workout/ExerciseHeading.tsx +192 -47
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -5
- package/src/components/custom/Workout/InputBar.tsx +4 -2
- package/src/components/custom/Workout/IntensityBar.tsx +4 -1
- package/src/components/custom/Workout/MesoCard.tsx +2 -1
- package/src/components/custom/Workout/MesoStatusCard.tsx +11 -10
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +8 -3
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +12 -2
- package/src/components/custom/Workout/README.md +58 -12
- package/src/components/custom/Workout/ReadinessCheck.tsx +4 -2
- package/src/components/custom/Workout/RestTimer.stories.tsx +13 -2
- package/src/components/custom/Workout/RestTimer.tsx +4 -2
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +16 -12
- package/src/components/custom/Workout/SegmentedBar.tsx +4 -3
- package/src/components/custom/Workout/SetStrip.stories.tsx +3 -2
- package/src/components/custom/Workout/SetsRepsLoad.tsx +23 -9
- package/src/components/custom/Workout/Sparkline.stories.tsx +13 -10
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +2 -1
- package/src/components/custom/Workout/StrengthTrendChart.tsx +8 -6
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +4 -3
- package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +3 -2
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +40 -0
- package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
- package/src/components/custom/Workout/VelocityStrip.tsx +23 -14
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +3 -1
- package/src/components/custom/Workout/exerciseRowState.ts +29 -0
- package/src/components/custom/Workout/index.ts +12 -2
- package/src/components/custom/Workout/metricText.stories.tsx +9 -8
- package/src/components/custom/Workout/metricText.tsx +4 -2
- package/src/components/custom/Workout/setHeadingKit.tsx +12 -10
- package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
- package/src/components/custom/charts/README.md +5 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +16 -0
- package/src/components/custom/charts/SetBarChart.tsx +14 -5
- package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
- package/src/components/custom/charts/flatBarGeometry.ts +32 -0
- package/src/components/custom/stepper/Stepper.tsx +8 -6
- package/src/components/ui/alert/Alert.stories.tsx +14 -4
- package/src/components/ui/alert/Alert.tsx +24 -3
- package/src/components/ui/button/Button.tsx +12 -12
- package/src/components/ui/checkbox/Checkbox.tsx +2 -2
- package/src/components/ui/data-row/DataRow.stories.tsx +6 -2
- package/src/components/ui/drawer/Drawer.tsx +4 -1
- package/src/components/ui/icon-box/IconBox.tsx +8 -6
- package/src/components/ui/indicator/Indicator.stories.tsx +4 -3
- package/src/components/ui/list-item/ListItem.stories.tsx +7 -4
- package/src/components/ui/modal/Modal.tsx +2 -0
- package/src/components/ui/pill/Pill.stories.tsx +53 -0
- package/src/components/ui/pill/Pill.tsx +10 -6
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +9 -6
- package/src/components/ui/radio/Radio.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +10 -10
- package/src/components/ui/select/Select.tsx +2 -1
- package/src/components/ui/spinner/Spinner.tsx +3 -1
- package/src/components/ui/stack/Stack.stories.tsx +7 -7
- package/src/components/ui/surface/Surface.stories.tsx +75 -28
- package/src/components/ui/switch/Switch.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +4 -2
- package/src/components/ui/toast/Toast.tsx +6 -4
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +13 -6
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/test/no-raw-color.test.ts +39 -0
- package/src/theme/Alpha.stories.tsx +103 -0
- package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
- package/src/theme/config.ts +12 -0
- package/src/theme/global.css +15 -3
- package/src/theme/subtle-tone-weight.test.ts +163 -0
- package/src/theme/tailwind-var-opacity.test.ts +163 -0
- package/src/theme/tokens/primitives.ts +33 -0
- package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
- package/src/theme/tokens/semantic.ts +56 -20
- package/src/utils/colors.alpha-adoption.test.ts +408 -0
- package/tailwind.config.js +50 -9
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import { Treemap, type TreemapDatum } from './Treemap'
|
|
3
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
4
|
+
|
|
5
|
+
const t = getSemanticColors('dark')
|
|
3
6
|
|
|
4
7
|
const meta: Meta<typeof Treemap> = {
|
|
5
8
|
title: 'Custom/Charts/Treemap',
|
|
@@ -16,14 +19,14 @@ export default meta
|
|
|
16
19
|
type Story = StoryObj<typeof Treemap>
|
|
17
20
|
|
|
18
21
|
const sample: TreemapDatum[] = [
|
|
19
|
-
{ id: 'router', value: 5035, color: '
|
|
20
|
-
{ id: 'engine', value: 4378, color: '
|
|
21
|
-
{ id: 'linker', value: 1695, color: '
|
|
22
|
-
{ id: 'validate', value: 1288, color: '
|
|
23
|
-
{ id: 'hook', value: 1050, color: '
|
|
24
|
-
{ id: 'update', value: 890, color: '
|
|
25
|
-
{ id: 'pool', value: 504, color: '
|
|
26
|
-
{ id: 'log', value: 342, color: '
|
|
22
|
+
{ id: 'router', value: 5035, color: t['data-4'], label: 'router.ts' },
|
|
23
|
+
{ id: 'engine', value: 4378, color: t['data-4'], label: 'engine.ts' },
|
|
24
|
+
{ id: 'linker', value: 1695, color: t['data-6'], label: 'linker.ts' },
|
|
25
|
+
{ id: 'validate', value: 1288, color: t['data-6'], label: 'validate.ts' },
|
|
26
|
+
{ id: 'hook', value: 1050, color: t['data-6'], label: 'hook.ts' },
|
|
27
|
+
{ id: 'update', value: 890, color: t['data-7'], label: 'update.ts' },
|
|
28
|
+
{ id: 'pool', value: 504, color: t['data-7'], label: 'pool.ts' },
|
|
29
|
+
{ id: 'log', value: 342, color: t['data-7'], label: 'log.ts' },
|
|
27
30
|
]
|
|
28
31
|
|
|
29
32
|
export const Default: Story = {
|
|
@@ -33,7 +36,7 @@ export const Default: Story = {
|
|
|
33
36
|
/** A single outlier under a linear scale swallows the canvas... */
|
|
34
37
|
export const OutlierLinear: Story = {
|
|
35
38
|
args: {
|
|
36
|
-
data: [{ id: 'mega', value: 205422, color: '
|
|
39
|
+
data: [{ id: 'mega', value: 205422, color: t['data-4'], label: 'mega.ts' }, ...sample],
|
|
37
40
|
width: 560,
|
|
38
41
|
height: 300,
|
|
39
42
|
scale: 'linear',
|
|
@@ -43,7 +46,7 @@ export const OutlierLinear: Story = {
|
|
|
43
46
|
/** ...the same data with a sqrt scale keeps every tile legible. */
|
|
44
47
|
export const OutlierSqrt: Story = {
|
|
45
48
|
args: {
|
|
46
|
-
data: [{ id: 'mega', value: 205422, color: '
|
|
49
|
+
data: [{ id: 'mega', value: 205422, color: t['data-4'], label: 'mega.ts' }, ...sample],
|
|
47
50
|
width: 560,
|
|
48
51
|
height: 300,
|
|
49
52
|
scale: 'sqrt',
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import { View, Text, Pressable, type ViewProps } from 'react-native'
|
|
3
3
|
import { cn } from '../../../utils/cn'
|
|
4
4
|
import { DATAVIZ_CATEGORICAL_PALETTE } from '../../../theme/extracted-colors-dataviz'
|
|
5
|
+
import { primitiveColors } from '../../../theme/tokens/primitives'
|
|
5
6
|
|
|
6
7
|
export interface TreemapDatum {
|
|
7
8
|
/** Stable identity — returned by onPress and used as the React key. */
|
|
@@ -146,6 +147,7 @@ export function Treemap({
|
|
|
146
147
|
{
|
|
147
148
|
id: '__more__',
|
|
148
149
|
value: restValue,
|
|
150
|
+
// VW-82: no token at this value (nearest surface-overlay #373635); proposed, not swapped.
|
|
149
151
|
color: '#3a3a3a',
|
|
150
152
|
label: `+${clean.length - (maxTiles - 1)} more`,
|
|
151
153
|
},
|
|
@@ -177,12 +179,13 @@ export function Treemap({
|
|
|
177
179
|
borderRadius: 3,
|
|
178
180
|
opacity: selected ? 1 : 0.9,
|
|
179
181
|
borderWidth: selected ? 2 : 0,
|
|
180
|
-
borderColor:
|
|
182
|
+
borderColor: primitiveColors.white,
|
|
181
183
|
padding: 4,
|
|
182
184
|
overflow: 'hidden',
|
|
183
185
|
}}
|
|
184
186
|
>
|
|
185
187
|
{labelFits && (
|
|
188
|
+
// VW-82: label on a data-coloured tile; no on-data token exists yet.
|
|
186
189
|
<Text numberOfLines={2} className="text-[10px] font-semibold text-[#0b0b0b]">
|
|
187
190
|
{r.datum.label ?? r.datum.id}
|
|
188
191
|
</Text>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import { ActiveWorkoutPage, type ActiveWorkoutExercise } from './ActiveWorkoutPage'
|
|
3
3
|
import type { SetRowProps } from './SetRow'
|
|
4
|
+
import { primitiveRamps } from '../../../theme/tokens/primitives'
|
|
4
5
|
|
|
5
6
|
const velocities = (base: number): number[] =>
|
|
6
7
|
Array.from({ length: 8 }, (_, i) => Number((base - i * 0.03).toFixed(2)))
|
|
@@ -109,7 +110,7 @@ const meta: Meta<typeof ActiveWorkoutPage> = {
|
|
|
109
110
|
title: 'Push Day A',
|
|
110
111
|
subtitle: 'Hypertrophy · Week 6 of 8',
|
|
111
112
|
exercises,
|
|
112
|
-
supersets: [{ id: 'ss1', label: 'SS1', color:
|
|
113
|
+
supersets: [{ id: 'ss1', label: 'SS1', color: primitiveRamps.cyan[300] }],
|
|
113
114
|
input: { reps: '8', weight: '195' },
|
|
114
115
|
rest: { totalSeconds: 120, elapsedMs: 42_000, nextSetInfo: 'Set 3 · 8 × 195 lbs' },
|
|
115
116
|
},
|
|
@@ -2,6 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
import { View, Text } from 'react-native'
|
|
3
3
|
import { DumbbellIcon } from './icons'
|
|
4
4
|
import { BaseBadge } from './BaseBadge'
|
|
5
|
+
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
6
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
7
|
+
|
|
8
|
+
const t = getSemanticColors('dark')
|
|
5
9
|
|
|
6
10
|
const meta: Meta<typeof BaseBadge> = {
|
|
7
11
|
title: 'Custom/Workout/BaseBadge',
|
|
@@ -17,7 +21,7 @@ const meta: Meta<typeof BaseBadge> = {
|
|
|
17
21
|
export default meta
|
|
18
22
|
type Story = StoryObj<typeof BaseBadge>
|
|
19
23
|
|
|
20
|
-
const Label = ({ children, color =
|
|
24
|
+
const Label = ({ children, color = greyRamp[400] }: { children: string; color?: string }) => (
|
|
21
25
|
<Text
|
|
22
26
|
style={{
|
|
23
27
|
fontFamily: '"Space Grotesk", sans-serif',
|
|
@@ -40,14 +44,14 @@ export const Plain: Story = {
|
|
|
40
44
|
export const Pr: Story = {
|
|
41
45
|
args: {
|
|
42
46
|
variant: 'pr',
|
|
43
|
-
children: <Label color=
|
|
47
|
+
children: <Label color={t['brand-primary']}>PR</Label>,
|
|
44
48
|
},
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
export const WithIcon: Story = {
|
|
48
52
|
args: {
|
|
49
53
|
variant: 'plain',
|
|
50
|
-
icon: <DumbbellIcon size={12} color=
|
|
54
|
+
icon: <DumbbellIcon size={12} color={greyRamp[400]} strokeWidth={2} />,
|
|
51
55
|
children: <Label>225 lbs</Label>,
|
|
52
56
|
},
|
|
53
57
|
}
|
|
@@ -68,17 +72,17 @@ export const AllVariants: Story = {
|
|
|
68
72
|
</View>
|
|
69
73
|
<View style={{ flexDirection: 'row', gap: 12, alignItems: 'center' }}>
|
|
70
74
|
<BaseBadge variant="pr" size="sm">
|
|
71
|
-
<Label color=
|
|
75
|
+
<Label color={t['brand-primary']}>PR</Label>
|
|
72
76
|
</BaseBadge>
|
|
73
77
|
<BaseBadge variant="pr" size="md">
|
|
74
|
-
<Label color=
|
|
78
|
+
<Label color={t['brand-primary']}>PR</Label>
|
|
75
79
|
</BaseBadge>
|
|
76
80
|
<BaseBadge variant="pr" size="lg">
|
|
77
|
-
<Label color=
|
|
81
|
+
<Label color={t['brand-primary']}>PR</Label>
|
|
78
82
|
</BaseBadge>
|
|
79
83
|
</View>
|
|
80
84
|
<View style={{ flexDirection: 'row', gap: 12, alignItems: 'center' }}>
|
|
81
|
-
<BaseBadge icon={<DumbbellIcon size={12} color=
|
|
85
|
+
<BaseBadge icon={<DumbbellIcon size={12} color={greyRamp[400]} strokeWidth={2} />}>
|
|
82
86
|
<Label>225 lbs</Label>
|
|
83
87
|
</BaseBadge>
|
|
84
88
|
</View>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useState, type ReactNode } from 'react'
|
|
3
3
|
import { View, Pressable, Animated, type ViewProps } from 'react-native'
|
|
4
4
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
5
|
+
import { alpha } from '../../../utils/colors'
|
|
5
6
|
|
|
6
7
|
const t = getSemanticColors('dark')
|
|
7
8
|
|
|
@@ -32,8 +33,8 @@ export const baseBadgeSizeConfig: Record<
|
|
|
32
33
|
const variantColors: Record<BaseBadgeVariant, { backgroundColor: string; borderColor: string }> = {
|
|
33
34
|
plain: { backgroundColor: t['surface-raised'], borderColor: t['hairline-default'] },
|
|
34
35
|
pr: {
|
|
35
|
-
backgroundColor: '
|
|
36
|
-
borderColor: '
|
|
36
|
+
backgroundColor: alpha(t['brand-primary'], 0.12),
|
|
37
|
+
borderColor: alpha(t['brand-primary'], 0.3),
|
|
37
38
|
},
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -5,6 +5,8 @@ import BodyHighlighter, { type ExtendedBodyPart, type Slug } from 'react-native-
|
|
|
5
5
|
import { cn } from '../../../utils/cn'
|
|
6
6
|
import { resolveColor } from '../../../theme/resolve-color'
|
|
7
7
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
8
|
+
import { primitiveColors } from '../../../theme/tokens/primitives'
|
|
9
|
+
import { alpha } from '../../../utils/colors'
|
|
8
10
|
import { getGlowShadow } from '../../../theme/elevation'
|
|
9
11
|
import {
|
|
10
12
|
MuscleGroup,
|
|
@@ -29,8 +31,8 @@ const Body = ((BodyHighlighter as unknown as { default?: typeof BodyHighlighter
|
|
|
29
31
|
|
|
30
32
|
const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
|
|
31
33
|
|
|
32
|
-
const OUTLINE_FILL =
|
|
33
|
-
const OUTLINE_BORDER =
|
|
34
|
+
const OUTLINE_FILL = alpha(primitiveColors.white, 0.08)
|
|
35
|
+
const OUTLINE_BORDER = alpha(primitiveColors.white, 0.12)
|
|
34
36
|
const BODY_SCALE = 0.8 // 200x400 intrinsic -> ~160x320 px
|
|
35
37
|
|
|
36
38
|
export interface BodyMapData {
|
|
@@ -278,7 +280,7 @@ function ViewToggle({ view, onViewChange }: ViewToggleProps) {
|
|
|
278
280
|
paddingHorizontal: 12,
|
|
279
281
|
paddingVertical: 4,
|
|
280
282
|
borderRadius: 9999,
|
|
281
|
-
backgroundColor: active ?
|
|
283
|
+
backgroundColor: active ? alpha(BRAND_PRIMARY, 0.16) : 'transparent',
|
|
282
284
|
borderWidth: 1,
|
|
283
285
|
borderColor: active ? BRAND_PRIMARY : resolveColor('hairline-strong'),
|
|
284
286
|
}}
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
} from './BodyMapDetailPanel'
|
|
9
9
|
import { MuscleGroup } from './muscleTaxonomy'
|
|
10
10
|
import { Surface } from '../../ui/surface'
|
|
11
|
+
import { primitiveColors } from '../../../theme/tokens/primitives'
|
|
12
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
13
|
+
|
|
14
|
+
const t = getSemanticColors('dark')
|
|
11
15
|
|
|
12
16
|
const contributing: ContributingExercise[] = [
|
|
13
17
|
{ name: 'Barbell Bench Press', sets: 4, contributionWeight: 1 },
|
|
@@ -141,7 +145,7 @@ function InteractiveBodyMapDetailPanel() {
|
|
|
141
145
|
position: 'relative',
|
|
142
146
|
height: 640,
|
|
143
147
|
width: 380,
|
|
144
|
-
backgroundColor: '
|
|
148
|
+
backgroundColor: t['background-frame'],
|
|
145
149
|
padding: 16,
|
|
146
150
|
}}
|
|
147
151
|
>
|
|
@@ -150,13 +154,19 @@ function InteractiveBodyMapDetailPanel() {
|
|
|
150
154
|
accessibilityRole="button"
|
|
151
155
|
style={{
|
|
152
156
|
alignSelf: 'flex-start',
|
|
153
|
-
backgroundColor: '
|
|
157
|
+
backgroundColor: t['brand-primary'],
|
|
154
158
|
paddingVertical: 10,
|
|
155
159
|
paddingHorizontal: 20,
|
|
156
160
|
borderRadius: 8,
|
|
157
161
|
}}
|
|
158
162
|
>
|
|
159
|
-
<Text
|
|
163
|
+
<Text
|
|
164
|
+
style={{
|
|
165
|
+
color: primitiveColors.white,
|
|
166
|
+
fontWeight: '700',
|
|
167
|
+
fontFamily: 'Inter, sans-serif',
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
160
170
|
Tap Chest
|
|
161
171
|
</Text>
|
|
162
172
|
</Pressable>
|
|
@@ -19,7 +19,9 @@ import {
|
|
|
19
19
|
type VolumeStatus,
|
|
20
20
|
} from './muscleTaxonomy'
|
|
21
21
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
22
|
+
import { primitiveColors } from '../../../theme/tokens/primitives'
|
|
22
23
|
import { liftStyle } from '../../../theme/lift'
|
|
24
|
+
import { alpha } from '../../../utils/colors'
|
|
23
25
|
|
|
24
26
|
const t = getSemanticColors('dark')
|
|
25
27
|
|
|
@@ -191,7 +193,7 @@ export function BodyMapDetailPanel({
|
|
|
191
193
|
left: 0,
|
|
192
194
|
right: 0,
|
|
193
195
|
bottom: 0,
|
|
194
|
-
backgroundColor:
|
|
196
|
+
backgroundColor: primitiveColors.black,
|
|
195
197
|
opacity: backdrop,
|
|
196
198
|
}}
|
|
197
199
|
testID="body-map-detail-panel-backdrop-anim"
|
|
@@ -488,9 +490,9 @@ export function BodyMapDetailPanel({
|
|
|
488
490
|
paddingVertical: 10,
|
|
489
491
|
borderRadius: 8,
|
|
490
492
|
alignItems: 'center',
|
|
491
|
-
backgroundColor:
|
|
493
|
+
backgroundColor: alpha(BRAND_PRIMARY, 0.12),
|
|
492
494
|
borderWidth: 1,
|
|
493
|
-
borderColor:
|
|
495
|
+
borderColor: alpha(BRAND_PRIMARY, 0.3),
|
|
494
496
|
}}
|
|
495
497
|
testID="body-map-detail-panel-view-exercises"
|
|
496
498
|
>
|
|
@@ -3,6 +3,7 @@ import { useState } from 'react'
|
|
|
3
3
|
import { View, Text } from 'react-native'
|
|
4
4
|
import { CapacityBandChart } from './CapacityBandChart'
|
|
5
5
|
import type { CapacityBandDataPoint, CapacityBandProjection, WorkoutDot } from './CapacityBandChart'
|
|
6
|
+
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
6
7
|
|
|
7
8
|
const band: CapacityBandDataPoint[] = [
|
|
8
9
|
{ date: '2026-06-01', bandLow: 40, bandHigh: 70 },
|
|
@@ -113,7 +114,7 @@ function InteractiveCapacityBandChart() {
|
|
|
113
114
|
height={200}
|
|
114
115
|
onWorkoutPress={setSelected}
|
|
115
116
|
/>
|
|
116
|
-
<Text style={{ fontSize: 12, fontFamily: 'Inter, sans-serif', color:
|
|
117
|
+
<Text style={{ fontSize: 12, fontFamily: 'Inter, sans-serif', color: greyRamp[400] }}>
|
|
117
118
|
{selected
|
|
118
119
|
? `Selected ${selected.date}: load ${selected.load} (${selected.status})`
|
|
119
120
|
: 'Tap a workout dot to inspect the session.'}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useEffect, useState, useMemo } from 'react'
|
|
3
3
|
import { View, Text, Pressable, Animated, Easing, type ViewProps } from 'react-native'
|
|
4
4
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
5
|
+
import { alpha } from '../../../utils/colors'
|
|
5
6
|
|
|
6
7
|
const t = getSemanticColors('dark')
|
|
7
8
|
|
|
@@ -10,10 +11,11 @@ const STATUS_WARNING = t['status-warning']
|
|
|
10
11
|
const STATUS_INFO = t['status-info']
|
|
11
12
|
/** Dot outline: near-white ring so load dots read on the band fill and any
|
|
12
13
|
* surface (matches the MesoStatusCard gauge-marker convention). */
|
|
14
|
+
// VW-82: no token at this value (nearest text-primary #F9F6F3). Left raw, proposed.
|
|
13
15
|
const DOT_BORDER = '#F3F4F6'
|
|
14
|
-
const BAND_FILL =
|
|
15
|
-
const BAND_EDGE =
|
|
16
|
-
const PROJECTION_FILL =
|
|
16
|
+
const BAND_FILL = alpha(STATUS_SUCCESS, 0.1)
|
|
17
|
+
const BAND_EDGE = alpha(STATUS_SUCCESS, 0.45)
|
|
18
|
+
const PROJECTION_FILL = alpha(STATUS_SUCCESS, 0.05)
|
|
17
19
|
|
|
18
20
|
const PADDING_LEFT = 28
|
|
19
21
|
const PADDING_RIGHT = 10
|
|
@@ -3,6 +3,10 @@ import { render, screen } from '@testing-library/react'
|
|
|
3
3
|
import { axe } from 'jest-axe'
|
|
4
4
|
import { DeviationBar } from './DeviationBar'
|
|
5
5
|
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
6
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
7
|
+
import { alpha } from '../../../utils/colors'
|
|
8
|
+
|
|
9
|
+
const t = getSemanticColors('dark')
|
|
6
10
|
|
|
7
11
|
describe('DeviationBar', () => {
|
|
8
12
|
it('renders the bar and dot', () => {
|
|
@@ -75,8 +79,7 @@ describe('DeviationBar', () => {
|
|
|
75
79
|
const bar = getByTestId('deviation-bar')
|
|
76
80
|
const track = bar.firstElementChild as HTMLElement
|
|
77
81
|
expect(track).toHaveStyle({
|
|
78
|
-
backgroundImage:
|
|
79
|
-
'linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(107,114,128,0.15) 50%, rgba(249,180,21,0.25) 100%)',
|
|
82
|
+
backgroundImage: `linear-gradient(90deg, ${alpha(t['status-success'], 0.25)} 0%, rgba(107,114,128,0.15) 50%, ${alpha(t['status-warning'], 0.25)} 100%)`,
|
|
80
83
|
})
|
|
81
84
|
})
|
|
82
85
|
|
|
@@ -3,6 +3,7 @@ import { View, type ViewProps, type ViewStyle } from 'react-native'
|
|
|
3
3
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
4
4
|
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
5
5
|
import { liftStyle } from '../../../theme/lift'
|
|
6
|
+
import { alpha } from '../../../utils/colors'
|
|
6
7
|
|
|
7
8
|
const t = getSemanticColors('dark')
|
|
8
9
|
|
|
@@ -60,8 +61,10 @@ export function DeviationBar({ deviation, width, className, ...props }: Deviatio
|
|
|
60
61
|
width: resolvedWidth,
|
|
61
62
|
borderRadius: 3,
|
|
62
63
|
// react-native-web renders backgroundImage at runtime; not in RN ViewStyle types
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
// rgba(107,114,128,0.15) at 50% has no exact token match (nearest:
|
|
65
|
+
// semanticPins.textSecondaryLight #65748B, ΔRGB ~12.7) — left as a
|
|
66
|
+
// raw literal (VW-78).
|
|
67
|
+
backgroundImage: `linear-gradient(90deg, ${alpha(t['status-success'], 0.25)} 0%, rgba(107,114,128,0.15) 50%, ${alpha(t['status-warning'], 0.25)} 100%)`,
|
|
65
68
|
} as ViewStyle
|
|
66
69
|
}
|
|
67
70
|
/>
|
|
@@ -27,13 +27,14 @@ const meta: Meta<typeof DualVelocityStrip> = {
|
|
|
27
27
|
'silhouette. Reuses VelocityStrip’s slot vocabulary (rep / todo / variable / ' +
|
|
28
28
|
'continue), its velocity-zone colors, hero geometry, and the live-rep pop. Color is ' +
|
|
29
29
|
'ALWAYS the velocity zone; SIDE is encoded by position only, never hue. `variant="hero"` ' +
|
|
30
|
-
'is the wall scale (value labels + per-side running-best reference lines);
|
|
31
|
-
'is the compact rail-expanded scale
|
|
30
|
+
'is the wall scale (value labels + per-side running-best reference lines); ' +
|
|
31
|
+
'`variant="dual-expanded"` is the compact rail-expanded scale (`"rail"` is a deprecated ' +
|
|
32
|
+
'alias for the same renderer). Single-voltra sets keep using VelocityStrip.',
|
|
32
33
|
},
|
|
33
34
|
},
|
|
34
35
|
},
|
|
35
36
|
argTypes: {
|
|
36
|
-
variant: { control: 'inline-radio', options: ['hero', '
|
|
37
|
+
variant: { control: 'inline-radio', options: ['hero', 'dual-expanded'] },
|
|
37
38
|
targetReps: { control: 'number' },
|
|
38
39
|
liveRepIndex: { control: 'number' },
|
|
39
40
|
height: { control: { type: 'number', min: 48, max: 320, step: 4 } },
|
|
@@ -81,9 +82,9 @@ export const Hero: Story = {
|
|
|
81
82
|
),
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
/**
|
|
85
|
-
export const
|
|
86
|
-
args: { variant: '
|
|
85
|
+
/** DUAL-EXPANDED — the same diverging language at the compact rail-expanded scale (no labels/refs). */
|
|
86
|
+
export const DualExpanded: Story = {
|
|
87
|
+
args: { variant: 'dual-expanded', targetReps: 8, height: 84 },
|
|
87
88
|
render: (args) => (
|
|
88
89
|
<Page>
|
|
89
90
|
<Panel width={320}>
|
|
@@ -182,16 +182,16 @@ describe('DualVelocityStrip side labels', () => {
|
|
|
182
182
|
expect(screen.queryByTestId('dual-velocity-side-label-L')).not.toBeInTheDocument()
|
|
183
183
|
})
|
|
184
184
|
|
|
185
|
-
it('drops the gutter/slot labels AND the centre axis at
|
|
185
|
+
it('drops the gutter/slot labels AND the centre axis at dual-expanded scale', () => {
|
|
186
186
|
render(
|
|
187
187
|
<DualVelocityStrip
|
|
188
188
|
left={{ velocities: [0.9], label: 'Left Arm' }}
|
|
189
189
|
right={{ velocities: [0.8], label: 'Right Arm' }}
|
|
190
|
-
variant="
|
|
190
|
+
variant="dual-expanded"
|
|
191
191
|
/>
|
|
192
192
|
)
|
|
193
|
-
//
|
|
194
|
-
// separate rows via the vertical gap), and no centre axis — only the bolder hero keeps those.
|
|
193
|
+
// dual-expanded = the lean renderer: no gutter/vertical-axis + no slot labels (the two wings
|
|
194
|
+
// read as separate rows via the vertical gap), and no centre axis — only the bolder hero keeps those.
|
|
195
195
|
expect(screen.queryByTestId('dual-velocity-side-label-L')).not.toBeInTheDocument()
|
|
196
196
|
expect(screen.queryByTestId('dual-velocity-side-label-R')).not.toBeInTheDocument()
|
|
197
197
|
expect(screen.queryByTestId('dual-velocity-axis')).not.toBeInTheDocument()
|
|
@@ -225,12 +225,12 @@ describe('DualVelocityStrip reference lines', () => {
|
|
|
225
225
|
expect(wingDown().getByTestId('velocity-hero-reference')).toBeInTheDocument()
|
|
226
226
|
})
|
|
227
227
|
|
|
228
|
-
it('omits reference lines at
|
|
228
|
+
it('omits reference lines at dual-expanded scale', () => {
|
|
229
229
|
render(
|
|
230
230
|
<DualVelocityStrip
|
|
231
231
|
left={{ velocities: [0.9, 0.85] }}
|
|
232
232
|
right={{ velocities: [0.82, 0.74] }}
|
|
233
|
-
variant="
|
|
233
|
+
variant="dual-expanded"
|
|
234
234
|
/>
|
|
235
235
|
)
|
|
236
236
|
expect(screen.queryByTestId('velocity-hero-reference')).not.toBeInTheDocument()
|
|
@@ -478,12 +478,12 @@ describe('DualVelocityStrip live mode', () => {
|
|
|
478
478
|
])
|
|
479
479
|
})
|
|
480
480
|
|
|
481
|
-
it('arms both wings at
|
|
481
|
+
it('arms both wings at dual-expanded scale too — its wings are composed strips as well', () => {
|
|
482
482
|
render(
|
|
483
483
|
<DualVelocityStrip
|
|
484
484
|
left={{ velocities: [0.9, 0.85, 0.8] }}
|
|
485
485
|
right={{ velocities: [0.82, 0.78, 0.7] }}
|
|
486
|
-
variant="
|
|
486
|
+
variant="dual-expanded"
|
|
487
487
|
liveRepIndex={2}
|
|
488
488
|
/>
|
|
489
489
|
)
|
|
@@ -591,16 +591,16 @@ describe('DualVelocityStrip compact variant (folded)', () => {
|
|
|
591
591
|
})
|
|
592
592
|
})
|
|
593
593
|
|
|
594
|
-
describe('DualVelocityStrip
|
|
594
|
+
describe('DualVelocityStrip dual-expanded variant', () => {
|
|
595
595
|
it('renders compact bars but no value labels or reference lines', () => {
|
|
596
596
|
render(
|
|
597
597
|
<DualVelocityStrip
|
|
598
598
|
left={{ velocities: [0.9, 0.85] }}
|
|
599
599
|
right={{ velocities: [0.82, 0.74] }}
|
|
600
|
-
variant="
|
|
600
|
+
variant="dual-expanded"
|
|
601
601
|
/>
|
|
602
602
|
)
|
|
603
|
-
//
|
|
603
|
+
// dual-expanded is COMPOSED from two bare `expanded` strips, so each wing emits the shared
|
|
604
604
|
// SetBarChart testIDs rather than the old bespoke `dual-velocity-bar-*` ones. Query per wing.
|
|
605
605
|
const up = within(screen.getByTestId('dual-velocity-wing-up'))
|
|
606
606
|
const down = within(screen.getByTestId('dual-velocity-wing-down'))
|
|
@@ -616,7 +616,7 @@ describe('DualVelocityStrip rail variant', () => {
|
|
|
616
616
|
<DualVelocityStrip
|
|
617
617
|
left={{ velocities: [0.9] }}
|
|
618
618
|
right={{ velocities: [0.8] }}
|
|
619
|
-
variant="
|
|
619
|
+
variant="dual-expanded"
|
|
620
620
|
targetReps={2}
|
|
621
621
|
/>
|
|
622
622
|
)
|
|
@@ -633,7 +633,7 @@ describe('DualVelocityStrip rail variant', () => {
|
|
|
633
633
|
<DualVelocityStrip
|
|
634
634
|
left={{ velocities: [0.9, 0.86, 0.82] }}
|
|
635
635
|
right={{ velocities: [0.85, 0.8] }}
|
|
636
|
-
variant="
|
|
636
|
+
variant="dual-expanded"
|
|
637
637
|
/>
|
|
638
638
|
)
|
|
639
639
|
const up = within(screen.getByTestId('dual-velocity-wing-up'))
|
|
@@ -650,7 +650,7 @@ describe('DualVelocityStrip rail variant', () => {
|
|
|
650
650
|
<DualVelocityStrip
|
|
651
651
|
left={{ set: { type: 'range', velocities: [0.9, 0.86], floor: 3, max: 4 } }}
|
|
652
652
|
right={{ set: { type: 'range', velocities: [0.85, 0.8], floor: 3, max: 4 } }}
|
|
653
|
-
variant="
|
|
653
|
+
variant="dual-expanded"
|
|
654
654
|
/>
|
|
655
655
|
)
|
|
656
656
|
const up = within(screen.getByTestId('dual-velocity-wing-up'))
|
|
@@ -658,6 +658,19 @@ describe('DualVelocityStrip rail variant', () => {
|
|
|
658
658
|
expect(up.queryAllByTestId('velocity-slot-variable').length).toBeGreaterThan(0)
|
|
659
659
|
expect(down.queryAllByTestId('velocity-slot-variable').length).toBeGreaterThan(0)
|
|
660
660
|
})
|
|
661
|
+
|
|
662
|
+
it('accepts the deprecated `rail` alias, rendering identically to `dual-expanded`', () => {
|
|
663
|
+
const props = {
|
|
664
|
+
left: { velocities: [0.9, 0.86, 0.82] },
|
|
665
|
+
right: { velocities: [0.85, 0.8] },
|
|
666
|
+
targetReps: 4,
|
|
667
|
+
}
|
|
668
|
+
const viaAlias = render(<DualVelocityStrip {...props} variant="rail" />)
|
|
669
|
+
const aliasHtml = viaAlias.container.innerHTML
|
|
670
|
+
viaAlias.unmount()
|
|
671
|
+
const viaCanonical = render(<DualVelocityStrip {...props} variant="dual-expanded" />)
|
|
672
|
+
expect(viaCanonical.container.innerHTML).toBe(aliasHtml)
|
|
673
|
+
})
|
|
661
674
|
})
|
|
662
675
|
|
|
663
676
|
describe('DualVelocityStrip accessibility', () => {
|
|
@@ -673,12 +686,12 @@ describe('DualVelocityStrip accessibility', () => {
|
|
|
673
686
|
expect(await axe(container)).toHaveNoViolations()
|
|
674
687
|
})
|
|
675
688
|
|
|
676
|
-
it('has no violations at
|
|
689
|
+
it('has no violations at dual-expanded scale', async () => {
|
|
677
690
|
const { container } = render(
|
|
678
691
|
<DualVelocityStrip
|
|
679
692
|
left={{ velocities: [0.9] }}
|
|
680
693
|
right={{ velocities: [0.8] }}
|
|
681
|
-
variant="
|
|
694
|
+
variant="dual-expanded"
|
|
682
695
|
/>
|
|
683
696
|
)
|
|
684
697
|
expect(await axe(container)).toHaveNoViolations()
|
|
@@ -11,14 +11,14 @@ const meta: Meta<typeof ExerciseCard> = {
|
|
|
11
11
|
docs: {
|
|
12
12
|
description: {
|
|
13
13
|
component:
|
|
14
|
-
'**Organism** (data-contract card). Three representations
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
' Composes **
|
|
14
|
+
'**Organism** (data-contract card). Three representations, all ONE ' +
|
|
15
|
+
'[ExerciseCardHeading](?path=/docs/custom-workout-exercisecardheading--docs) selected by its ' +
|
|
16
|
+
'`density`: `upcoming` (a dimmed, not-yet-reached row), collapsed (name + prescription + the ' +
|
|
17
|
+
'per-set strip) and expanded — the `rail` heading over a SET · REPS · LBS · RPE body of ' +
|
|
18
|
+
'[SetRow](?path=/docs/custom-workout-setrow--docs)s (compact spotlight on the live set). ' +
|
|
19
|
+
'Expand is controlled (`expanded` + `onExpandedChange`) or uncontrolled (`defaultExpanded`); ' +
|
|
20
|
+
'`upcoming` overrides expand.' +
|
|
21
|
+
' Composes **ExerciseCardHeading** · **SetRow** · **SetTableHeader**.',
|
|
22
22
|
},
|
|
23
23
|
},
|
|
24
24
|
},
|