@titan-design/react-ui 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bodymap.js +12 -7
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +12 -7
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +855 -124
- package/dist/index.d.ts +855 -124
- package/dist/index.js +2276 -801
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2230 -802
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-8u_4WbL-.d.mts} +280 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-DaWiBOGa.d.ts} +280 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1255 -798
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1255 -798
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +16 -82
- package/dist/theme/index.d.ts +16 -82
- package/dist/theme/index.js +13 -115
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +13 -115
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +14 -6
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +14 -6
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +870 -0
- package/src/arch/annotations.json +48 -0
- package/src/arch/arch-graph.json +4301 -0
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
- package/src/components/custom/Metric/Metric.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
- package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
- package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
- package/src/components/custom/TimerReadout/index.ts +1 -0
- package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
- package/src/components/custom/Typography/Typography.stories.tsx +1 -1
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
- package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
- package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
- package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
- package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
- package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
- package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
- package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
- package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
- package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
- package/src/components/custom/Workout/MetricTiles.tsx +48 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
- package/src/components/custom/Workout/README.md +146 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.tsx +15 -10
- package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
- package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
- package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
- package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
- package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
- package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
- package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
- package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
- package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
- package/src/components/custom/Workout/SessionHeader.tsx +152 -0
- package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
- package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
- package/src/components/custom/Workout/SessionRail.tsx +126 -0
- package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
- package/src/components/custom/Workout/SetBar.test.tsx +123 -0
- package/src/components/custom/Workout/SetBar.tsx +171 -0
- package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
- package/src/components/custom/Workout/SetRow.test.tsx +94 -166
- package/src/components/custom/Workout/SetRow.tsx +169 -232
- package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
- package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
- package/src/components/custom/Workout/SetStrip.tsx +71 -0
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
- package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
- package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
- package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
- package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
- package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
- package/src/components/custom/Workout/StatusPill.tsx +88 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
- package/src/components/custom/Workout/VelocityStrip.test.tsx +376 -141
- package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
- package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
- package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
- package/src/components/custom/Workout/index.ts +55 -2
- package/src/components/custom/Workout/metricText.stories.tsx +69 -0
- package/src/components/custom/Workout/metricText.tsx +34 -0
- package/src/components/custom/Workout/paceTone.test.ts +29 -0
- package/src/components/custom/Workout/paceTone.ts +27 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/icons/icons.stories.tsx +67 -2
- package/src/components/icons/icons.test.tsx +32 -2
- package/src/components/icons/icons.tsx +108 -0
- package/src/components/shell/BrandLockup.stories.tsx +3 -3
- package/src/components/shell/DashboardShell.stories.tsx +65 -0
- package/src/components/shell/DashboardShell.test.tsx +42 -0
- package/src/components/shell/DashboardShell.tsx +74 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
- package/src/components/shell/DeviceMenu.stories.tsx +5 -5
- package/src/components/shell/DeviceRow.stories.tsx +3 -3
- package/src/components/shell/NavItem.stories.tsx +52 -0
- package/src/components/shell/NavItem.test.tsx +35 -0
- package/src/components/shell/NavItem.tsx +75 -0
- package/src/components/shell/README.md +30 -0
- package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
- package/src/components/shell/SessionStatePill.stories.tsx +3 -3
- package/src/components/shell/SessionStatePill.tsx +1 -1
- package/src/components/shell/SideNav.stories.tsx +81 -0
- package/src/components/shell/SideNav.test.tsx +51 -0
- package/src/components/shell/SideNav.tsx +74 -0
- package/src/components/shell/TopBar.stories.tsx +5 -5
- package/src/components/shell/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/badge/Badge.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/button/Button.stories.tsx +1 -1
- package/src/components/ui/card/Card.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/chip/Chip.stories.tsx +1 -1
- package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
- package/src/components/ui/form-field/FormField.stories.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
- package/src/components/ui/indicator/Indicator.test.tsx +1 -0
- package/src/components/ui/indicator/Indicator.tsx +6 -1
- package/src/components/ui/input/Input.stories.tsx +1 -1
- package/src/components/ui/link/Link.stories.tsx +1 -1
- package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
- package/src/components/ui/menu/Menu.stories.tsx +1 -1
- package/src/components/ui/modal/Modal.stories.tsx +1 -1
- package/src/components/ui/pill/Pill.stories.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +1 -1
- package/src/components/ui/select/Select.stories.tsx +1 -1
- package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.stories.tsx +79 -0
- package/src/components/ui/tile/Tile.test.tsx +48 -0
- package/src/components/ui/tile/Tile.tsx +64 -0
- package/src/components/ui/tile/index.ts +2 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTimer.test.ts +150 -0
- package/src/hooks/useTimer.ts +77 -0
- package/src/index.ts +3 -0
- package/src/pages.ts +1 -1
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/stories/ThemePresets.stories.tsx +1 -1
- package/src/theme/ColorSystem.stories.tsx +1 -1
- package/src/theme/Colors.stories.tsx +5 -8
- package/src/theme/config.ts +4 -0
- package/src/theme/elevation.stories.tsx +1 -1
- package/src/theme/global.css +5 -1
- package/src/theme/shadows.stories.tsx +1 -1
- package/src/theme/tokens/primitives.ts +11 -107
- package/src/theme/tokens/semantic.ts +8 -0
- package/src/theme/workout-tokens.ts +11 -7
- package/tailwind.config.js +5 -0
|
@@ -3,7 +3,7 @@ import { View, Text } from 'react-native'
|
|
|
3
3
|
import { DateTime, type DateTimeFormat } from './DateTime'
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof DateTime> = {
|
|
6
|
-
title: '
|
|
6
|
+
title: 'Components/Molecules/DateTime',
|
|
7
7
|
component: DateTime,
|
|
8
8
|
tags: ['autodocs'],
|
|
9
9
|
argTypes: {
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
import { Scatter, type ScatterDatum } from './Scatter'
|
|
3
3
|
|
|
4
4
|
const meta: Meta<typeof Scatter> = {
|
|
5
|
-
title: '
|
|
5
|
+
title: 'Components/DataViz/Scatter',
|
|
6
6
|
component: Scatter,
|
|
7
7
|
tags: ['autodocs'],
|
|
8
8
|
argTypes: {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { TimerReadout } from './TimerReadout'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `TimerReadout` — a small textual timer (⏱ + mono, right-justified) built on the
|
|
7
|
+
* `useTimer` hook. Only the live/current value goes green (status-live-muted) while
|
|
8
|
+
* `running`; the ` / total` suffix stays dim tertiary.
|
|
9
|
+
*/
|
|
10
|
+
const meta: Meta<typeof TimerReadout> = {
|
|
11
|
+
title: 'Custom/TimerReadout',
|
|
12
|
+
component: TimerReadout,
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
parameters: {
|
|
15
|
+
docs: {
|
|
16
|
+
description: {
|
|
17
|
+
component: '**Atom.** A small textual timer on [useTimer]. Used-by ↑ SessionHeader.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
decorators: [
|
|
22
|
+
(Story) => (
|
|
23
|
+
<View style={{ padding: 16, backgroundColor: '#131313', alignItems: 'flex-end', width: 200 }}>
|
|
24
|
+
<Story />
|
|
25
|
+
</View>
|
|
26
|
+
),
|
|
27
|
+
],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default meta
|
|
31
|
+
type Story = StoryObj<typeof TimerReadout>
|
|
32
|
+
|
|
33
|
+
/** Live stopwatch (controlled), ticking → the elapsed value is green. */
|
|
34
|
+
export const Running: Story = {
|
|
35
|
+
args: { mode: 'up', elapsedMs: 42 * 60000 + 18 * 1000, running: true },
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Paused → the value drops to secondary (no live cue). */
|
|
39
|
+
export const Paused: Story = {
|
|
40
|
+
args: { mode: 'up', elapsedMs: 42 * 60000 + 18 * 1000, running: false },
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Countdown, running → shows remaining time, green while live. */
|
|
44
|
+
export const CountdownRunning: Story = {
|
|
45
|
+
args: { mode: 'down', durationMs: 90 * 1000, elapsedMs: 27 * 1000, running: true },
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** With a ` / total` budget suffix (dim tertiary). */
|
|
49
|
+
export const WithTotal: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
mode: 'up',
|
|
52
|
+
elapsedMs: 42 * 60000 + 18 * 1000,
|
|
53
|
+
durationMs: 60 * 60000,
|
|
54
|
+
showTotal: true,
|
|
55
|
+
running: true,
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Self-ticking stopwatch — the internal interval advances the value once per second. */
|
|
60
|
+
export const LiveAutoTick: Story = {
|
|
61
|
+
args: { mode: 'up', autoTick: true, running: true },
|
|
62
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
2
|
+
import { render, screen, act } from '@testing-library/react'
|
|
3
|
+
import { TimerReadout } from './TimerReadout'
|
|
4
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
5
|
+
import { primitiveRamps } from '../../../theme/tokens/primitives'
|
|
6
|
+
|
|
7
|
+
const semantic = getSemanticColors('dark')
|
|
8
|
+
const LIVE_GREEN = primitiveRamps.green[500]
|
|
9
|
+
const SECONDARY = semantic['text-secondary']
|
|
10
|
+
const TERTIARY = semantic['text-tertiary']
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.useRealTimers()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('TimerReadout', () => {
|
|
17
|
+
it('renders elapsed mm:ss from a controlled up-timer', () => {
|
|
18
|
+
render(<TimerReadout mode="up" elapsedMs={65 * 1000} />)
|
|
19
|
+
expect(screen.getByTestId('timer-readout-current')).toHaveTextContent('1:05')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('renders the default ⏱ glyph', () => {
|
|
23
|
+
render(<TimerReadout elapsedMs={0} />)
|
|
24
|
+
expect(screen.getByTestId('timer-readout-glyph')).toHaveTextContent('⏱')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('accepts a custom glyph', () => {
|
|
28
|
+
render(<TimerReadout elapsedMs={0} glyph="◷" />)
|
|
29
|
+
expect(screen.getByTestId('timer-readout-glyph')).toHaveTextContent('◷')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('shows the current value in live green while running', () => {
|
|
33
|
+
render(<TimerReadout mode="up" elapsedMs={0} running />)
|
|
34
|
+
expect(screen.getByTestId('timer-readout-current')).toHaveStyle({ color: LIVE_GREEN })
|
|
35
|
+
expect(screen.getByTestId('timer-readout-glyph')).toHaveStyle({ color: LIVE_GREEN })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('shows the current value in secondary when paused', () => {
|
|
39
|
+
render(<TimerReadout mode="up" elapsedMs={0} running={false} />)
|
|
40
|
+
expect(screen.getByTestId('timer-readout-current')).toHaveStyle({ color: SECONDARY })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('shows remaining time in down mode', () => {
|
|
44
|
+
render(<TimerReadout mode="down" durationMs={90 * 1000} elapsedMs={27 * 1000} running />)
|
|
45
|
+
expect(screen.getByTestId('timer-readout-current')).toHaveTextContent('1:03')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('appends the total in dim tertiary when showTotal is set', () => {
|
|
49
|
+
render(
|
|
50
|
+
<TimerReadout mode="up" elapsedMs={42 * 1000} durationMs={60 * 60000} showTotal running />
|
|
51
|
+
)
|
|
52
|
+
const total = screen.getByTestId('timer-readout-total')
|
|
53
|
+
expect(total).toHaveTextContent('/ 60:00')
|
|
54
|
+
expect(total).toHaveStyle({ color: TERTIARY })
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('omits the total when showTotal is false', () => {
|
|
58
|
+
render(<TimerReadout mode="up" elapsedMs={0} durationMs={60000} />)
|
|
59
|
+
expect(screen.queryByTestId('timer-readout-total')).toBeNull()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('self-ticks once per second while running with autoTick', () => {
|
|
63
|
+
vi.useFakeTimers()
|
|
64
|
+
render(<TimerReadout mode="up" autoTick running />)
|
|
65
|
+
expect(screen.getByTestId('timer-readout-current')).toHaveTextContent('0:00')
|
|
66
|
+
|
|
67
|
+
act(() => {
|
|
68
|
+
vi.advanceTimersByTime(3000)
|
|
69
|
+
})
|
|
70
|
+
expect(screen.getByTestId('timer-readout-current')).toHaveTextContent('0:03')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
import { View, Text, type ViewProps } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
import { useTimer, formatDuration, type TimerMode } from '../../../hooks/useTimer'
|
|
5
|
+
import { Typography } from '../Typography'
|
|
6
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
7
|
+
import { primitiveRamps } from '../../../theme/tokens/primitives'
|
|
8
|
+
|
|
9
|
+
const semantic = getSemanticColors('dark')
|
|
10
|
+
// status-live-muted — the SideNav "Live" cue; the elapsed value goes green while ticking.
|
|
11
|
+
const LIVE_GREEN = primitiveRamps.green[500]
|
|
12
|
+
const SECONDARY = semantic['text-secondary']
|
|
13
|
+
const TERTIARY = semantic['text-tertiary']
|
|
14
|
+
|
|
15
|
+
const DEFAULT_GLYPH = '⏱'
|
|
16
|
+
|
|
17
|
+
export interface TimerReadoutProps extends ViewProps {
|
|
18
|
+
/** 'up' counts elapsed upward; 'down' counts remaining toward 0. Default 'up'. */
|
|
19
|
+
mode?: TimerMode
|
|
20
|
+
/** Controlled elapsed time in ms. When set, the value is derived from it and never self-ticks. */
|
|
21
|
+
elapsedMs?: number
|
|
22
|
+
/** Total duration in ms — required for 'down' mode and for the ` / total` suffix. */
|
|
23
|
+
durationMs?: number
|
|
24
|
+
/** Live cue: the current value goes green while true. */
|
|
25
|
+
running?: boolean
|
|
26
|
+
/** Enable the internal 1s interval (ignored when `elapsedMs` is controlled). */
|
|
27
|
+
autoTick?: boolean
|
|
28
|
+
/** When `durationMs` is set, append ` / mm:ss`. */
|
|
29
|
+
showTotal?: boolean
|
|
30
|
+
/** Leading glyph; defaults to the ⏱ unicode. */
|
|
31
|
+
glyph?: ReactNode
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Atom · TimerReadout — a small textual timer (⏱ + mono, right-justified) built on
|
|
36
|
+
* [useTimer]. Matches the TopBar clock treatment: only the live/current value goes
|
|
37
|
+
* green (status-live-muted) while `running`; the ` / total` suffix stays dim tertiary.
|
|
38
|
+
*/
|
|
39
|
+
export function TimerReadout({
|
|
40
|
+
mode = 'up',
|
|
41
|
+
elapsedMs,
|
|
42
|
+
durationMs,
|
|
43
|
+
running = false,
|
|
44
|
+
autoTick = false,
|
|
45
|
+
showTotal = false,
|
|
46
|
+
glyph = DEFAULT_GLYPH,
|
|
47
|
+
className,
|
|
48
|
+
...rest
|
|
49
|
+
}: TimerReadoutProps) {
|
|
50
|
+
const { label } = useTimer({ mode, durationMs, elapsedMs, running, autoTick })
|
|
51
|
+
|
|
52
|
+
const currentColor = running ? LIVE_GREEN : SECONDARY
|
|
53
|
+
const total = showTotal && durationMs != null ? formatDuration(durationMs) : null
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<View
|
|
57
|
+
accessibilityRole="timer"
|
|
58
|
+
accessibilityLabel={total != null ? `${label} of ${total}` : label}
|
|
59
|
+
className={cn('flex-row items-baseline justify-end', className)}
|
|
60
|
+
{...rest}
|
|
61
|
+
>
|
|
62
|
+
<Typography variant="mono" style={{ fontSize: 11, textAlign: 'right' }}>
|
|
63
|
+
<Text testID="timer-readout-glyph" style={{ color: currentColor, marginRight: 3 }}>
|
|
64
|
+
{glyph}
|
|
65
|
+
</Text>
|
|
66
|
+
<Text testID="timer-readout-current" style={{ color: currentColor }}>
|
|
67
|
+
{label}
|
|
68
|
+
</Text>
|
|
69
|
+
{total != null ? (
|
|
70
|
+
<Text testID="timer-readout-total" style={{ color: TERTIARY }}>{` / ${total}`}</Text>
|
|
71
|
+
) : null}
|
|
72
|
+
</Typography>
|
|
73
|
+
</View>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TimerReadout, type TimerReadoutProps } from './TimerReadout'
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
import { Treemap, type TreemapDatum } from './Treemap'
|
|
3
3
|
|
|
4
4
|
const meta: Meta<typeof Treemap> = {
|
|
5
|
-
title: '
|
|
5
|
+
title: 'Components/DataViz/Treemap',
|
|
6
6
|
component: Treemap,
|
|
7
7
|
tags: ['autodocs'],
|
|
8
8
|
argTypes: {
|
|
@@ -3,7 +3,7 @@ import { View } from 'react-native'
|
|
|
3
3
|
import { Typography, Heading, Paragraph, Caption, Label, Overline } from './Typography'
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof Typography> = {
|
|
6
|
-
title: '
|
|
6
|
+
title: 'Foundations/Typography',
|
|
7
7
|
component: Typography,
|
|
8
8
|
tags: ['autodocs'],
|
|
9
9
|
argTypes: {
|
|
@@ -2,36 +2,45 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
import { ActiveWorkoutPage, type ActiveWorkoutExercise } from './ActiveWorkoutPage'
|
|
3
3
|
import type { SetRowProps } from './SetRow'
|
|
4
4
|
|
|
5
|
+
const velocities = (base: number): number[] =>
|
|
6
|
+
Array.from({ length: 8 }, (_, i) => Number((base - i * 0.03).toFixed(2)))
|
|
7
|
+
|
|
8
|
+
// logged sets are done; the next set is being performed (live, a couple reps in);
|
|
9
|
+
// the remainder are planned todo. Mirrors an active workout mid-set.
|
|
5
10
|
function activeSets(weight: number, logged: number, total: number): SetRowProps[] {
|
|
6
11
|
return Array.from({ length: total }, (_, i) => {
|
|
7
12
|
const setNumber = i + 1
|
|
8
13
|
if (setNumber <= logged) {
|
|
9
14
|
return {
|
|
10
|
-
|
|
15
|
+
state: 'done' as const,
|
|
11
16
|
setNumber,
|
|
12
|
-
previous: { reps: 8, weight: weight - 5 },
|
|
13
17
|
reps: 8,
|
|
14
18
|
weight,
|
|
15
19
|
rpe: 7.5 + setNumber * 0.5,
|
|
16
20
|
unit: 'lbs' as const,
|
|
21
|
+
velocities: velocities(0.74 - (setNumber - 1) * 0.03),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (setNumber === logged + 1) {
|
|
25
|
+
return {
|
|
26
|
+
state: 'live' as const,
|
|
27
|
+
setNumber,
|
|
28
|
+
unit: 'lbs' as const,
|
|
29
|
+
target: { reps: 8, weight },
|
|
30
|
+
reps: 8,
|
|
31
|
+
weight,
|
|
32
|
+
velocities: velocities(0.7).slice(0, 3),
|
|
17
33
|
}
|
|
18
34
|
}
|
|
19
35
|
return {
|
|
20
|
-
|
|
36
|
+
state: 'todo' as const,
|
|
21
37
|
setNumber,
|
|
22
|
-
previous: { reps: 8, weight: weight - 5 },
|
|
23
|
-
reps: null,
|
|
24
|
-
weight: null,
|
|
25
38
|
unit: 'lbs' as const,
|
|
26
|
-
|
|
27
|
-
targets: { reps: 8, weight },
|
|
39
|
+
target: { reps: 8, weight },
|
|
28
40
|
}
|
|
29
41
|
})
|
|
30
42
|
}
|
|
31
43
|
|
|
32
|
-
const velocities = (base: number): number[] =>
|
|
33
|
-
Array.from({ length: 8 }, (_, i) => Number((base - i * 0.03).toFixed(2)))
|
|
34
|
-
|
|
35
44
|
const exercises: ActiveWorkoutExercise[] = [
|
|
36
45
|
{
|
|
37
46
|
id: 'warmup',
|
|
@@ -48,7 +57,6 @@ const exercises: ActiveWorkoutExercise[] = [
|
|
|
48
57
|
status: 'active',
|
|
49
58
|
unit: 'lbs',
|
|
50
59
|
totalPlannedSets: 4,
|
|
51
|
-
e1rm: { value: 248, unit: 'lbs' },
|
|
52
60
|
tempo: [3, 1, 1, 0],
|
|
53
61
|
setVelocities: [velocities(0.72), velocities(0.68)],
|
|
54
62
|
sets: activeSets(195, 2, 4),
|
|
@@ -85,7 +93,7 @@ const exercises: ActiveWorkoutExercise[] = [
|
|
|
85
93
|
]
|
|
86
94
|
|
|
87
95
|
const meta: Meta<typeof ActiveWorkoutPage> = {
|
|
88
|
-
title: '
|
|
96
|
+
title: 'Pages/Active Workout',
|
|
89
97
|
component: ActiveWorkoutPage,
|
|
90
98
|
parameters: { layout: 'fullscreen' },
|
|
91
99
|
tags: ['autodocs'],
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
countCompletedSets,
|
|
6
6
|
deriveWorkoutProgress,
|
|
7
7
|
findActiveExercise,
|
|
8
|
-
|
|
8
|
+
isUpcomingExercise,
|
|
9
9
|
groupExercises,
|
|
10
10
|
type ActiveWorkoutExercise,
|
|
11
11
|
type ActiveWorkoutPageProps,
|
|
@@ -29,8 +29,16 @@ const exercises: ActiveWorkoutExercise[] = [
|
|
|
29
29
|
totalPlannedSets: 4,
|
|
30
30
|
setVelocities: [[0.72], [0.68]],
|
|
31
31
|
sets: [
|
|
32
|
-
{
|
|
33
|
-
|
|
32
|
+
{
|
|
33
|
+
state: 'done',
|
|
34
|
+
setNumber: 1,
|
|
35
|
+
unit: 'lbs',
|
|
36
|
+
reps: 8,
|
|
37
|
+
weight: 195,
|
|
38
|
+
rpe: 8,
|
|
39
|
+
velocities: [0.72],
|
|
40
|
+
},
|
|
41
|
+
{ state: 'todo', setNumber: 3, unit: 'lbs', target: { reps: 8, weight: 195 } },
|
|
34
42
|
],
|
|
35
43
|
},
|
|
36
44
|
{
|
|
@@ -107,16 +115,11 @@ describe('findActiveExercise', () => {
|
|
|
107
115
|
})
|
|
108
116
|
})
|
|
109
117
|
|
|
110
|
-
describe('
|
|
111
|
-
it('
|
|
112
|
-
expect(
|
|
113
|
-
expect(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
it('expands only the focused active/completed exercise', () => {
|
|
117
|
-
expect(statusToCardState('active', true)).toBe('expanded')
|
|
118
|
-
expect(statusToCardState('active', false)).toBe('collapsed')
|
|
119
|
-
expect(statusToCardState('completed', true)).toBe('expanded')
|
|
118
|
+
describe('isUpcomingExercise', () => {
|
|
119
|
+
it('is true only for upcoming exercises', () => {
|
|
120
|
+
expect(isUpcomingExercise('upcoming')).toBe(true)
|
|
121
|
+
expect(isUpcomingExercise('active')).toBe(false)
|
|
122
|
+
expect(isUpcomingExercise('completed')).toBe(false)
|
|
120
123
|
})
|
|
121
124
|
})
|
|
122
125
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
2
|
import { useMemo, useState } from 'react'
|
|
3
3
|
import { View, Text, type ViewProps } from 'react-native'
|
|
4
|
-
import { ExerciseCard, type ExerciseCardProps
|
|
4
|
+
import { ExerciseCard, type ExerciseCardProps } from './ExerciseCard'
|
|
5
5
|
import { InputBar } from './InputBar'
|
|
6
6
|
import { RestTimer } from './RestTimer'
|
|
7
7
|
import { SupersetWrapper } from './SupersetWrapper'
|
|
@@ -32,8 +32,6 @@ export interface ActiveWorkoutExercise {
|
|
|
32
32
|
totalPlannedSets: number
|
|
33
33
|
/** Collapsed summary (sets × reps @ weight) for a completed exercise. */
|
|
34
34
|
summary?: ExerciseCardProps['summary']
|
|
35
|
-
/** Estimated one-rep max badge. */
|
|
36
|
-
e1rm?: ExerciseCardProps['e1rm']
|
|
37
35
|
/** Flags a personal-record exercise. */
|
|
38
36
|
isPR?: boolean
|
|
39
37
|
/** Prescribed tempo, revealed when expanded. */
|
|
@@ -148,13 +146,9 @@ export function findActiveExercise(
|
|
|
148
146
|
return exercises.find((exercise) => exercise.status === 'active') ?? null
|
|
149
147
|
}
|
|
150
148
|
|
|
151
|
-
/**
|
|
152
|
-
export function
|
|
153
|
-
status
|
|
154
|
-
focused: boolean
|
|
155
|
-
): ExerciseCardState {
|
|
156
|
-
if (status === 'upcoming') return 'upcoming'
|
|
157
|
-
return focused ? 'expanded' : 'collapsed'
|
|
149
|
+
/** Whether an exercise renders as the dimmed, not-yet-reached representation (pure). */
|
|
150
|
+
export function isUpcomingExercise(status: ActiveExerciseStatus): boolean {
|
|
151
|
+
return status === 'upcoming'
|
|
158
152
|
}
|
|
159
153
|
|
|
160
154
|
/** Project an exercise onto ExerciseCard props, wiring zoom focus (pure). */
|
|
@@ -165,18 +159,18 @@ export function toActiveCardProps(
|
|
|
165
159
|
supersetPosition: ExerciseCardProps['supersetPosition'] = null,
|
|
166
160
|
supersetColor?: string
|
|
167
161
|
): ExerciseCardProps {
|
|
168
|
-
const
|
|
162
|
+
const upcoming = isUpcomingExercise(exercise.status)
|
|
169
163
|
return {
|
|
170
164
|
name: exercise.name,
|
|
171
|
-
|
|
172
|
-
|
|
165
|
+
upcoming,
|
|
166
|
+
expanded: focused,
|
|
167
|
+
onExpandedChange: onToggle,
|
|
173
168
|
summary: exercise.summary,
|
|
174
|
-
e1rm: exercise.e1rm,
|
|
175
169
|
isPR: exercise.isPR,
|
|
176
170
|
tempo: exercise.tempo,
|
|
177
171
|
setVelocities: exercise.setVelocities,
|
|
178
172
|
totalPlannedSets: exercise.totalPlannedSets,
|
|
179
|
-
sets:
|
|
173
|
+
sets: focused ? exercise.sets : undefined,
|
|
180
174
|
prescription: exercise.prescription,
|
|
181
175
|
previousBest: exercise.previousBest,
|
|
182
176
|
supersetPosition,
|
|
@@ -21,7 +21,7 @@ const upcoming: UpcomingExercise[] = [
|
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
const meta: Meta<typeof BodyMapDetailPanel> = {
|
|
24
|
-
title: '
|
|
24
|
+
title: 'Workout/DataViz/BodyMapDetailPanel',
|
|
25
25
|
component: BodyMapDetailPanel,
|
|
26
26
|
tags: ['autodocs'],
|
|
27
27
|
decorators: [
|
|
@@ -39,7 +39,7 @@ const projection: CapacityBandProjection = {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const meta: Meta<typeof CapacityBandChart> = {
|
|
42
|
-
title: '
|
|
42
|
+
title: 'Workout/DataViz/CapacityBandChart',
|
|
43
43
|
component: CapacityBandChart,
|
|
44
44
|
tags: ['autodocs'],
|
|
45
45
|
argTypes: {
|
|
@@ -3,7 +3,7 @@ import { View } from 'react-native'
|
|
|
3
3
|
import { DeviationBar } from './DeviationBar'
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof DeviationBar> = {
|
|
6
|
-
title: '
|
|
6
|
+
title: 'Workout/DataViz/DeviationBar',
|
|
7
7
|
component: DeviationBar,
|
|
8
8
|
tags: ['autodocs'],
|
|
9
9
|
argTypes: {
|