@titan-design/react-ui 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bodymap.js +12 -7
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +12 -7
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +956 -128
- package/dist/index.d.ts +956 -128
- package/dist/index.js +2873 -981
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2826 -982
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-DCFBqp79.d.mts} +289 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-_fI3-x7Z.d.ts} +289 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1659 -799
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1660 -800
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +16 -82
- package/dist/theme/index.d.ts +16 -82
- package/dist/theme/index.js +13 -115
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +13 -115
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +14 -6
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +14 -6
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +870 -0
- package/src/arch/annotations.json +48 -0
- package/src/arch/arch-graph.json +4301 -0
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +90 -0
- package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
- package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
- package/src/components/custom/CircularTimer/index.ts +1 -0
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
- package/src/components/custom/Metric/Metric.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
- package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
- package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
- package/src/components/custom/TimerReadout/index.ts +1 -0
- package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
- package/src/components/custom/Typography/Typography.stories.tsx +1 -1
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
- package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
- package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
- package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
- package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
- package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
- package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
- package/src/components/custom/Workout/FatigueMeter.test.tsx +85 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +86 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
- package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
- package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
- package/src/components/custom/Workout/MetricTiles.tsx +48 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
- package/src/components/custom/Workout/README.md +193 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.stories.tsx +133 -3
- package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
- package/src/components/custom/Workout/RestTimer.tsx +155 -59
- package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
- package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
- package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
- package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
- package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
- package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
- package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
- package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
- package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
- package/src/components/custom/Workout/SessionHeader.tsx +152 -0
- package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
- package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
- package/src/components/custom/Workout/SessionRail.tsx +126 -0
- package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
- package/src/components/custom/Workout/SetBar.test.tsx +123 -0
- package/src/components/custom/Workout/SetBar.tsx +171 -0
- package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
- package/src/components/custom/Workout/SetRow.test.tsx +94 -166
- package/src/components/custom/Workout/SetRow.tsx +169 -232
- package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
- package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
- package/src/components/custom/Workout/SetStrip.tsx +71 -0
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
- package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
- package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
- package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
- package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
- package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
- package/src/components/custom/Workout/StatusPill.tsx +88 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoBar.stories.tsx +70 -1
- package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
- package/src/components/custom/Workout/TempoBar.tsx +107 -22
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +262 -56
- package/src/components/custom/Workout/VelocityStrip.test.tsx +440 -141
- package/src/components/custom/Workout/VelocityStrip.tsx +776 -141
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
- package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
- package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +139 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +234 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +373 -0
- package/src/components/custom/Workout/index.ts +55 -2
- package/src/components/custom/Workout/metricText.stories.tsx +69 -0
- package/src/components/custom/Workout/metricText.tsx +34 -0
- package/src/components/custom/Workout/paceTone.test.ts +29 -0
- package/src/components/custom/Workout/paceTone.ts +27 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
- package/src/components/custom/index.ts +2 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/icons/icons.stories.tsx +67 -2
- package/src/components/icons/icons.test.tsx +32 -2
- package/src/components/icons/icons.tsx +108 -0
- package/src/components/shell/BrandLockup.stories.tsx +3 -3
- package/src/components/shell/DashboardShell.stories.tsx +65 -0
- package/src/components/shell/DashboardShell.test.tsx +42 -0
- package/src/components/shell/DashboardShell.tsx +74 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
- package/src/components/shell/DeviceMenu.stories.tsx +5 -5
- package/src/components/shell/DeviceRow.stories.tsx +3 -3
- package/src/components/shell/NavItem.stories.tsx +52 -0
- package/src/components/shell/NavItem.test.tsx +35 -0
- package/src/components/shell/NavItem.tsx +75 -0
- package/src/components/shell/README.md +30 -0
- package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
- package/src/components/shell/SessionStatePill.stories.tsx +3 -3
- package/src/components/shell/SessionStatePill.tsx +1 -1
- package/src/components/shell/SideNav.stories.tsx +81 -0
- package/src/components/shell/SideNav.test.tsx +51 -0
- package/src/components/shell/SideNav.tsx +74 -0
- package/src/components/shell/TopBar.stories.tsx +5 -5
- package/src/components/shell/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +86 -3
- package/src/components/ui/alert/Alert.test.tsx +52 -0
- package/src/components/ui/alert/Alert.tsx +54 -20
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/badge/Badge.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/button/Button.stories.tsx +1 -1
- package/src/components/ui/card/Card.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/chip/Chip.stories.tsx +1 -1
- package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
- package/src/components/ui/form-field/FormField.stories.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
- package/src/components/ui/indicator/Indicator.test.tsx +1 -0
- package/src/components/ui/indicator/Indicator.tsx +6 -1
- package/src/components/ui/input/Input.stories.tsx +1 -1
- package/src/components/ui/link/Link.stories.tsx +1 -1
- package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
- package/src/components/ui/menu/Menu.stories.tsx +1 -1
- package/src/components/ui/modal/Modal.stories.tsx +1 -1
- package/src/components/ui/pill/Pill.stories.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +47 -22
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +1 -1
- package/src/components/ui/select/Select.stories.tsx +1 -1
- package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.stories.tsx +79 -0
- package/src/components/ui/tile/Tile.test.tsx +48 -0
- package/src/components/ui/tile/Tile.tsx +64 -0
- package/src/components/ui/tile/index.ts +2 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTimer.test.ts +150 -0
- package/src/hooks/useTimer.ts +77 -0
- package/src/index.ts +3 -0
- package/src/pages.ts +1 -1
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/stories/ThemePresets.stories.tsx +1 -1
- package/src/theme/ColorSystem.stories.tsx +1 -1
- package/src/theme/Colors.stories.tsx +5 -8
- package/src/theme/config.ts +4 -0
- package/src/theme/elevation.stories.tsx +1 -1
- package/src/theme/global.css +5 -1
- package/src/theme/shadows.stories.tsx +1 -1
- package/src/theme/tokens/primitives.ts +11 -107
- package/src/theme/tokens/semantic.ts +8 -0
- package/src/theme/workout-tokens.ts +11 -7
- package/tailwind.config.js +5 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Decorator, Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View, Text, Pressable } from 'react-native'
|
|
3
|
+
import { CircularTimer } from './CircularTimer'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof CircularTimer> = {
|
|
6
|
+
title: 'Custom/CircularTimer',
|
|
7
|
+
component: CircularTimer,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'A circular countdown / countup with an mm:ss readout in the center — the graphical ' +
|
|
14
|
+
'sibling of `TimerReadout`. Batteries-included: owns `useTimer` (pass controlled ' +
|
|
15
|
+
'`durationMs` + `elapsedMs`) and composes `CircularProgress` for the arc (`down` drains, ' +
|
|
16
|
+
'`up` fills). On completion a `down` timer with a `doneLabel` fills fully in `doneColor` ' +
|
|
17
|
+
'and reads the label (e.g. "GO"). Pass `controls` to render buttons below the ring, or ' +
|
|
18
|
+
"`fill` to make it responsive to its container. `RestTimer`'s `ring` variant wraps this.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
argTypes: {
|
|
23
|
+
mode: { control: 'inline-radio', options: ['down', 'up'] },
|
|
24
|
+
size: { control: { type: 'number', min: 96, max: 280, step: 4 } },
|
|
25
|
+
fill: { control: 'boolean' },
|
|
26
|
+
doneLabel: { control: 'text' },
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default meta
|
|
31
|
+
type Story = StoryObj<typeof CircularTimer>
|
|
32
|
+
|
|
33
|
+
const dark: Decorator = (Story) => (
|
|
34
|
+
<View style={{ padding: 32, alignItems: 'center', backgroundColor: '#0E0E0E' }}>
|
|
35
|
+
<Story />
|
|
36
|
+
</View>
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
/** Controls-driven: flip `mode` / `durationMs` / `elapsedMs` / `size` / `doneLabel`. */
|
|
40
|
+
export const Playground: Story = {
|
|
41
|
+
args: { durationMs: 120000, elapsedMs: 47000, doneLabel: 'GO', size: 180 },
|
|
42
|
+
decorators: [dark],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Countdown, midway — the ring drains from full. */
|
|
46
|
+
export const CountdownMidway: Story = {
|
|
47
|
+
args: { mode: 'down', durationMs: 120000, elapsedMs: 60000, size: 180 },
|
|
48
|
+
decorators: [dark],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Countup — the ring fills as time elapses. */
|
|
52
|
+
export const CountupMidway: Story = {
|
|
53
|
+
args: { mode: 'up', durationMs: 120000, elapsedMs: 40000, size: 180 },
|
|
54
|
+
decorators: [dark],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Completed countdown with a done label — full ring, flips to success, reads "GO". */
|
|
58
|
+
export const DoneWithLabel: Story = {
|
|
59
|
+
args: { mode: 'down', durationMs: 120000, elapsedMs: 120000, doneLabel: 'GO', size: 180 },
|
|
60
|
+
decorators: [dark],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const DemoButton = ({ label }: { label: string }) => (
|
|
64
|
+
<Pressable
|
|
65
|
+
style={{
|
|
66
|
+
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
67
|
+
paddingVertical: 8,
|
|
68
|
+
paddingHorizontal: 20,
|
|
69
|
+
borderRadius: 8,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<Text style={{ color: '#DADADA', fontSize: 11, fontWeight: '600' }}>{label}</Text>
|
|
73
|
+
</Pressable>
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
/** With controls rendered below the ring. */
|
|
77
|
+
export const WithControls: Story = {
|
|
78
|
+
args: {
|
|
79
|
+
durationMs: 120000,
|
|
80
|
+
elapsedMs: 47000,
|
|
81
|
+
size: 180,
|
|
82
|
+
controls: (
|
|
83
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
84
|
+
<DemoButton label="+30s" />
|
|
85
|
+
<DemoButton label="Skip" />
|
|
86
|
+
</View>
|
|
87
|
+
),
|
|
88
|
+
},
|
|
89
|
+
decorators: [dark],
|
|
90
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { Text } from 'react-native'
|
|
5
|
+
import { CircularTimer } from './CircularTimer'
|
|
6
|
+
|
|
7
|
+
describe('CircularTimer', () => {
|
|
8
|
+
it('renders a timer with a composed progressbar ring', () => {
|
|
9
|
+
render(<CircularTimer durationMs={120000} elapsedMs={30000} />)
|
|
10
|
+
expect(screen.getByTestId('circular-timer')).toBeInTheDocument()
|
|
11
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('shows the mm:ss remaining in the center while counting down', () => {
|
|
15
|
+
render(<CircularTimer durationMs={120000} elapsedMs={60000} />)
|
|
16
|
+
expect(screen.getByTestId('circular-timer-label')).toHaveTextContent('1:00')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('shows the elapsed mm:ss in up mode', () => {
|
|
20
|
+
render(<CircularTimer mode="up" durationMs={120000} elapsedMs={45000} />)
|
|
21
|
+
expect(screen.getByTestId('circular-timer-label')).toHaveTextContent('0:45')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('renders the done label (not a time) when a countdown completes', () => {
|
|
25
|
+
render(<CircularTimer durationMs={120000} elapsedMs={120000} doneLabel="GO" />)
|
|
26
|
+
expect(screen.getByTestId('circular-timer-label')).toHaveTextContent('GO')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('keeps showing 0:00 at completion when no doneLabel is given', () => {
|
|
30
|
+
render(<CircularTimer durationMs={120000} elapsedMs={120000} />)
|
|
31
|
+
expect(screen.getByTestId('circular-timer-label')).toHaveTextContent('0:00')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('renders optional controls below the ring', () => {
|
|
35
|
+
render(<CircularTimer durationMs={120000} elapsedMs={0} controls={<Text>+30s</Text>} />)
|
|
36
|
+
expect(screen.getByText('+30s')).toBeInTheDocument()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('defaults its accessibility label to seconds remaining', () => {
|
|
40
|
+
render(<CircularTimer durationMs={120000} elapsedMs={60000} />)
|
|
41
|
+
expect(screen.getAllByLabelText('60 seconds remaining').length).toBeGreaterThan(0)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('uses a supplied accessibility label', () => {
|
|
45
|
+
render(
|
|
46
|
+
<CircularTimer durationMs={120000} elapsedMs={60000} accessibilityLabel="Rest, 60s left" />
|
|
47
|
+
)
|
|
48
|
+
expect(screen.getAllByLabelText('Rest, 60s left').length).toBeGreaterThan(0)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('emits no NaN when durationMs is 0 (guard)', () => {
|
|
52
|
+
const { container } = render(<CircularTimer durationMs={0} elapsedMs={0} />)
|
|
53
|
+
expect(container.innerHTML).not.toContain('NaN')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('has no accessibility violations', async () => {
|
|
57
|
+
const { container } = render(
|
|
58
|
+
<CircularTimer durationMs={120000} elapsedMs={30000} accessibilityLabel="Rest countdown" />
|
|
59
|
+
)
|
|
60
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import { View, Text } from 'react-native'
|
|
4
|
+
import { CircularProgress, colorVarMap, type ProgressColor } from '../../ui/progress/Progress'
|
|
5
|
+
import { useTimer, type TimerMode } from '../../../hooks/useTimer'
|
|
6
|
+
|
|
7
|
+
export interface CircularTimerProps {
|
|
8
|
+
/** Total duration (ms). `down`: the countdown length; `up`: the target for progress. */
|
|
9
|
+
durationMs: number
|
|
10
|
+
/** Controlled elapsed time (ms). */
|
|
11
|
+
elapsedMs: number
|
|
12
|
+
/** `down` (default) counts remaining toward 0 (ring drains); `up` counts elapsed up (ring fills). */
|
|
13
|
+
mode?: TimerMode
|
|
14
|
+
/** Ring diameter (px) — the geometry/stroke reference. Default 160. */
|
|
15
|
+
size?: number
|
|
16
|
+
/** Ring stroke width (px). Default 8. */
|
|
17
|
+
strokeWidth?: number
|
|
18
|
+
/** Fill the parent container (responsive) instead of a fixed `size` box. */
|
|
19
|
+
fill?: boolean
|
|
20
|
+
/** Ring color while running. Default `primary`. */
|
|
21
|
+
color?: ProgressColor
|
|
22
|
+
/**
|
|
23
|
+
* `down` mode: the text shown in the center — and the color the ring fills to — when
|
|
24
|
+
* the countdown completes (e.g. `doneLabel="GO"`). Omit to keep showing `0:00`.
|
|
25
|
+
*/
|
|
26
|
+
doneLabel?: string
|
|
27
|
+
/** Ring + center color when complete (with {@link doneLabel}). Default `success`. */
|
|
28
|
+
doneColor?: ProgressColor
|
|
29
|
+
/** Optional controls rendered below the ring (e.g. +30s / Skip). */
|
|
30
|
+
controls?: ReactNode
|
|
31
|
+
/** Accessibility label for the timer. Defaults to a seconds-remaining description. */
|
|
32
|
+
accessibilityLabel?: string
|
|
33
|
+
testID?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A circular countdown / countup with a time readout in the center — the graphical
|
|
38
|
+
* sibling of {@link TimerReadout}. Batteries-included: owns {@link useTimer} (pass
|
|
39
|
+
* controlled `durationMs` + `elapsedMs`) and composes {@link CircularProgress} for the
|
|
40
|
+
* arc (`down` drains as time runs out, `up` fills), rendering the mm:ss label in its
|
|
41
|
+
* center slot. On completion (`down` + {@link doneLabel}) the ring fills fully in
|
|
42
|
+
* {@link doneColor} and the center reads the done label — the "go" moment. An optional
|
|
43
|
+
* {@link controls} node renders below the ring. Consumers (e.g. `RestTimer`'s `ring`
|
|
44
|
+
* variant) wrap this and add their own domain chrome.
|
|
45
|
+
*/
|
|
46
|
+
export function CircularTimer({
|
|
47
|
+
durationMs,
|
|
48
|
+
elapsedMs,
|
|
49
|
+
mode = 'down',
|
|
50
|
+
size = 160,
|
|
51
|
+
strokeWidth = 8,
|
|
52
|
+
fill = false,
|
|
53
|
+
color = 'primary',
|
|
54
|
+
doneLabel,
|
|
55
|
+
doneColor = 'success',
|
|
56
|
+
controls,
|
|
57
|
+
accessibilityLabel,
|
|
58
|
+
testID,
|
|
59
|
+
}: CircularTimerProps) {
|
|
60
|
+
const { remainingMs, progress, label, done } = useTimer({ mode, durationMs, elapsedMs })
|
|
61
|
+
|
|
62
|
+
const isDone = done && mode === 'down'
|
|
63
|
+
const showDone = isDone && doneLabel != null
|
|
64
|
+
// Down → the remaining fraction (ring drains); up → elapsed progress (ring fills).
|
|
65
|
+
// A completed countdown with a done label fills fully to signal "go".
|
|
66
|
+
const remainingFrac = durationMs > 0 ? Math.max(0, Math.min(1, remainingMs / durationMs)) : 0
|
|
67
|
+
const arc = mode === 'down' ? (showDone ? 1 : remainingFrac) : progress
|
|
68
|
+
const ringColor: ProgressColor = showDone ? doneColor : color
|
|
69
|
+
|
|
70
|
+
const remainingSec = Math.ceil(Math.max(0, remainingMs) / 1000)
|
|
71
|
+
const a11y =
|
|
72
|
+
accessibilityLabel ?? (isDone ? 'Timer complete' : `${remainingSec} seconds remaining`)
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<View
|
|
76
|
+
style={{ alignItems: 'center', gap: 16 }}
|
|
77
|
+
accessibilityRole="timer"
|
|
78
|
+
accessibilityLabel={a11y}
|
|
79
|
+
testID={testID ?? 'circular-timer'}
|
|
80
|
+
>
|
|
81
|
+
<CircularProgress
|
|
82
|
+
value={arc}
|
|
83
|
+
max={1}
|
|
84
|
+
size={size}
|
|
85
|
+
strokeWidth={strokeWidth}
|
|
86
|
+
fill={fill}
|
|
87
|
+
color={ringColor}
|
|
88
|
+
// A distinct static name for the ring (the live countdown text lives on the
|
|
89
|
+
// enclosing timer role) — satisfies the progressbar-name a11y rule without
|
|
90
|
+
// duplicating the timer label.
|
|
91
|
+
accessibilityLabel="Timer ring"
|
|
92
|
+
testID="circular-timer-ring"
|
|
93
|
+
>
|
|
94
|
+
<Text
|
|
95
|
+
className={showDone ? undefined : 'text-text-primary'}
|
|
96
|
+
style={{
|
|
97
|
+
fontSize: Math.round(size * (showDone ? 0.28 : 0.24)),
|
|
98
|
+
fontFamily: '"Space Grotesk", sans-serif',
|
|
99
|
+
fontWeight: '800',
|
|
100
|
+
fontVariant: ['tabular-nums'],
|
|
101
|
+
letterSpacing: showDone ? 1 : -1,
|
|
102
|
+
...(showDone ? { color: colorVarMap[doneColor] } : null),
|
|
103
|
+
}}
|
|
104
|
+
testID="circular-timer-label"
|
|
105
|
+
>
|
|
106
|
+
{showDone ? doneLabel : label}
|
|
107
|
+
</Text>
|
|
108
|
+
</CircularProgress>
|
|
109
|
+
{controls}
|
|
110
|
+
</View>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CircularTimer, type CircularTimerProps } from './CircularTimer'
|
|
@@ -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'],
|