@titan-design/react-ui 0.1.1 → 0.2.5
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 +47 -1
- package/dist/{chunk-MGW37MPO.mjs → chunk-UXVRPOME.mjs} +173 -3
- package/dist/chunk-UXVRPOME.mjs.map +1 -0
- package/dist/index.d.mts +101 -12
- package/dist/index.d.ts +101 -12
- package/dist/index.js +1267 -468
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +734 -112
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.d.mts +86 -1
- package/dist/theme/index.d.ts +86 -1
- package/dist/theme/index.js +166 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +1 -1
- package/package.json +13 -2
- package/src/components/custom/Workout/RestTimer.stories.tsx +71 -0
- package/src/components/custom/Workout/RestTimer.test.tsx +145 -0
- package/src/components/custom/Workout/RestTimer.tsx +172 -0
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +144 -0
- package/src/components/custom/Workout/SupersetWrapper.test.tsx +130 -0
- package/src/components/custom/Workout/SupersetWrapper.tsx +63 -0
- package/src/components/custom/Workout/index.ts +2 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/custom/stepper/index.ts +2 -2
- package/src/components/ui/avatar/Avatar.test.tsx +17 -0
- package/src/components/ui/avatar/Avatar.tsx +13 -5
- package/src/components/ui/badge/Badge.test.tsx +17 -0
- package/src/components/ui/badge/Badge.tsx +14 -0
- package/src/components/ui/badge/index.ts +1 -0
- package/src/components/ui/button/Button.tsx +55 -0
- package/src/components/ui/card/Card.test.tsx +29 -0
- package/src/components/ui/card/Card.tsx +32 -11
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +74 -0
- package/src/components/ui/indicator/Indicator.test.tsx +55 -0
- package/src/components/ui/indicator/Indicator.tsx +73 -0
- package/src/components/ui/indicator/index.ts +2 -0
- package/src/components/ui/pill/Pill.stories.tsx +71 -0
- package/src/components/ui/pill/Pill.test.tsx +69 -0
- package/src/components/ui/pill/Pill.tsx +104 -0
- package/src/components/ui/pill/index.ts +2 -0
- package/src/components/ui/popover/Popover.test.tsx +144 -2
- package/src/components/ui/popover/Popover.tsx +81 -6
- package/src/components/ui/progress/Progress.test.tsx +29 -0
- package/src/components/ui/progress/Progress.tsx +54 -35
- package/src/components/ui/select/Select.test.tsx +20 -0
- package/src/components/ui/select/Select.tsx +9 -2
- package/src/components/ui/toast/index.ts +1 -0
- package/src/components/ui/tooltip/Tooltip.test.tsx +83 -0
- package/src/components/ui/tooltip/Tooltip.tsx +117 -26
- package/src/stories/PresetShowcase.stories.tsx +462 -0
- package/src/stories/ThemePresets.stories.tsx +85 -0
- package/src/theme/global.css +129 -1
- package/src/theme/index.ts +1 -0
- package/src/theme/presets/apply.ts +97 -0
- package/src/theme/presets/audiobook.ts +93 -0
- package/src/theme/presets/default.ts +6 -0
- package/src/theme/presets/index.ts +4 -0
- package/src/theme/presets/presets.test.ts +51 -0
- package/src/theme/presets/types.ts +76 -0
- package/src/utils/avatar-color.test.ts +30 -0
- package/src/utils/avatar-color.ts +20 -0
- package/src/web-jsx/jsx-dev-runtime.ts +28 -0
- package/src/web-jsx/jsx-runtime.ts +41 -0
- package/tailwind.config.js +53 -3
- package/dist/chunk-MGW37MPO.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@titan-design/react-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Cross-platform design system built on Gluestack UI",
|
|
5
5
|
"author": "Henry Jewkes",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,23 +41,33 @@
|
|
|
41
41
|
"build": "tsup",
|
|
42
42
|
"dev": "tsup --watch",
|
|
43
43
|
"lint": "eslint src/",
|
|
44
|
+
"lint:borders": "bash scripts/check-border-classes.sh",
|
|
44
45
|
"type-check": "tsc --noEmit",
|
|
45
46
|
"format:check": "prettier --check \"src/**/*.{ts,tsx,css}\"",
|
|
46
47
|
"test": "vitest",
|
|
47
48
|
"test:coverage": "vitest --coverage",
|
|
48
49
|
"storybook": "storybook dev -p 6006",
|
|
49
50
|
"build-storybook": "storybook build",
|
|
51
|
+
"test:visual": "playwright test",
|
|
52
|
+
"test:visual:update": "playwright test --update-snapshots",
|
|
53
|
+
"specimen": "vite --config specimen/vite.config.ts",
|
|
54
|
+
"specimen:compare": "vite --config specimen/vite.config.ts --open /comparison.html",
|
|
55
|
+
"test:visual:compare": "playwright test --config playwright.comparison.config.ts",
|
|
50
56
|
"prepublishOnly": "tsup && vitest --run"
|
|
51
57
|
},
|
|
52
58
|
"peerDependencies": {
|
|
53
59
|
"lucide-react": ">=0.400.0",
|
|
54
60
|
"lucide-react-native": ">=0.400.0",
|
|
61
|
+
"nativewind": "^4.2.1",
|
|
55
62
|
"react": "^18.0.0 || ^19.0.0",
|
|
56
63
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
57
64
|
"react-native": ">=0.72.0",
|
|
58
65
|
"react-native-web": ">=0.19.0"
|
|
59
66
|
},
|
|
60
67
|
"peerDependenciesMeta": {
|
|
68
|
+
"nativewind": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
61
71
|
"react-native": {
|
|
62
72
|
"optional": true
|
|
63
73
|
},
|
|
@@ -72,11 +82,12 @@
|
|
|
72
82
|
"@gluestack-style/react": "^1.0.0",
|
|
73
83
|
"@gluestack-ui/themed": "^1.1.0",
|
|
74
84
|
"clsx": "^2.1.0",
|
|
75
|
-
"nativewind": "^4.1.0",
|
|
76
85
|
"tailwind-merge": "^2.6.0"
|
|
77
86
|
},
|
|
78
87
|
"devDependencies": {
|
|
88
|
+
"nativewind": "^4.2.1",
|
|
79
89
|
"@eslint/js": "^10.0.1",
|
|
90
|
+
"@playwright/test": "^1.58.2",
|
|
80
91
|
"@storybook/addon-a11y": "^10.2.0",
|
|
81
92
|
"@storybook/addon-docs": "^10.2.0",
|
|
82
93
|
"@storybook/addon-react-native-web": "^0.0.29",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
3
|
+
import { RestTimer } from './RestTimer'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof RestTimer> = {
|
|
6
|
+
title: 'Custom/Workout/RestTimer',
|
|
7
|
+
component: RestTimer,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
argTypes: {
|
|
10
|
+
totalSeconds: {
|
|
11
|
+
control: 'number',
|
|
12
|
+
description: 'Total rest duration in seconds',
|
|
13
|
+
},
|
|
14
|
+
elapsedMs: {
|
|
15
|
+
control: 'number',
|
|
16
|
+
description: 'Elapsed time in milliseconds',
|
|
17
|
+
},
|
|
18
|
+
visible: {
|
|
19
|
+
control: 'boolean',
|
|
20
|
+
description: 'Whether the timer is visible',
|
|
21
|
+
},
|
|
22
|
+
nextSetInfo: {
|
|
23
|
+
control: 'text',
|
|
24
|
+
description: 'Context about the next set',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default meta
|
|
30
|
+
type Story = StoryObj<typeof RestTimer>
|
|
31
|
+
|
|
32
|
+
export const Default: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
totalSeconds: 150,
|
|
35
|
+
elapsedMs: 30000,
|
|
36
|
+
onSkip: () => {},
|
|
37
|
+
onAddTime: () => {},
|
|
38
|
+
visible: true,
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const NearlyComplete: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
totalSeconds: 150,
|
|
45
|
+
elapsedMs: 145000,
|
|
46
|
+
onSkip: () => {},
|
|
47
|
+
onAddTime: () => {},
|
|
48
|
+
visible: true,
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const WithNextSetInfo: Story = {
|
|
53
|
+
args: {
|
|
54
|
+
totalSeconds: 150,
|
|
55
|
+
elapsedMs: 30000,
|
|
56
|
+
onSkip: () => {},
|
|
57
|
+
onAddTime: () => {},
|
|
58
|
+
nextSetInfo: 'Bench Press — Set 3 of 4',
|
|
59
|
+
visible: true,
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const JustStarted: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
totalSeconds: 150,
|
|
66
|
+
elapsedMs: 0,
|
|
67
|
+
onSkip: () => {},
|
|
68
|
+
onAddTime: () => {},
|
|
69
|
+
visible: true,
|
|
70
|
+
},
|
|
71
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { RestTimer } from './RestTimer'
|
|
5
|
+
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
totalSeconds: 150,
|
|
8
|
+
elapsedMs: 30000,
|
|
9
|
+
onSkip: vi.fn(),
|
|
10
|
+
onAddTime: vi.fn(),
|
|
11
|
+
visible: true,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('RestTimer', () => {
|
|
15
|
+
describe('time display formatting', () => {
|
|
16
|
+
it('displays 2:30 for 150s total with 0 elapsed', () => {
|
|
17
|
+
render(<RestTimer {...defaultProps} elapsedMs={0} />)
|
|
18
|
+
expect(screen.getByTestId('rest-timer-time')).toHaveTextContent('2:30')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('displays 2:00 for 150s total with 30s elapsed', () => {
|
|
22
|
+
render(<RestTimer {...defaultProps} elapsedMs={30000} />)
|
|
23
|
+
expect(screen.getByTestId('rest-timer-time')).toHaveTextContent('2:00')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('displays 0:05 for 150s total with 145s elapsed', () => {
|
|
27
|
+
render(<RestTimer {...defaultProps} elapsedMs={145000} />)
|
|
28
|
+
expect(screen.getByTestId('rest-timer-time')).toHaveTextContent('0:05')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('pads seconds with leading zero', () => {
|
|
32
|
+
render(<RestTimer {...defaultProps} totalSeconds={65} elapsedMs={0} />)
|
|
33
|
+
expect(screen.getByTestId('rest-timer-time')).toHaveTextContent('1:05')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('displays 0:00 when elapsed equals total', () => {
|
|
37
|
+
render(<RestTimer {...defaultProps} elapsedMs={150000} />)
|
|
38
|
+
expect(screen.getByTestId('rest-timer-time')).toHaveTextContent('0:00')
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('progress bar', () => {
|
|
43
|
+
it('sets width to 0% when elapsed is 0', () => {
|
|
44
|
+
render(<RestTimer {...defaultProps} elapsedMs={0} />)
|
|
45
|
+
const fill = screen.getByTestId('rest-timer-progress-fill')
|
|
46
|
+
expect(fill).toHaveStyle({ width: '0%' })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('sets width to 50% when half elapsed', () => {
|
|
50
|
+
render(<RestTimer {...defaultProps} totalSeconds={100} elapsedMs={50000} />)
|
|
51
|
+
const fill = screen.getByTestId('rest-timer-progress-fill')
|
|
52
|
+
expect(fill).toHaveStyle({ width: '50%' })
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('clamps width to 100% when elapsed exceeds total', () => {
|
|
56
|
+
render(<RestTimer {...defaultProps} elapsedMs={200000} />)
|
|
57
|
+
const fill = screen.getByTestId('rest-timer-progress-fill')
|
|
58
|
+
expect(fill).toHaveStyle({ width: '100%' })
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('button interactions', () => {
|
|
63
|
+
it('fires onSkip when skip button is pressed', () => {
|
|
64
|
+
const onSkip = vi.fn()
|
|
65
|
+
render(<RestTimer {...defaultProps} onSkip={onSkip} />)
|
|
66
|
+
fireEvent.click(screen.getByTestId('rest-timer-skip'))
|
|
67
|
+
expect(onSkip).toHaveBeenCalledOnce()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('fires onAddTime when +30s button is pressed', () => {
|
|
71
|
+
const onAddTime = vi.fn()
|
|
72
|
+
render(<RestTimer {...defaultProps} onAddTime={onAddTime} />)
|
|
73
|
+
fireEvent.click(screen.getByTestId('rest-timer-add-time'))
|
|
74
|
+
expect(onAddTime).toHaveBeenCalledOnce()
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('next set info', () => {
|
|
79
|
+
it('displays nextSetInfo when provided', () => {
|
|
80
|
+
render(
|
|
81
|
+
<RestTimer
|
|
82
|
+
{...defaultProps}
|
|
83
|
+
nextSetInfo="Bench Press — Set 3 of 4"
|
|
84
|
+
/>,
|
|
85
|
+
)
|
|
86
|
+
expect(screen.getByTestId('rest-timer-next-set')).toHaveTextContent(
|
|
87
|
+
'Bench Press — Set 3 of 4',
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('does not render next set element when nextSetInfo is not provided', () => {
|
|
92
|
+
render(<RestTimer {...defaultProps} />)
|
|
93
|
+
expect(screen.queryByTestId('rest-timer-next-set')).not.toBeInTheDocument()
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('visibility', () => {
|
|
98
|
+
it('renders nothing when visible is false', () => {
|
|
99
|
+
render(<RestTimer {...defaultProps} visible={false} />)
|
|
100
|
+
expect(screen.queryByTestId('rest-timer')).not.toBeInTheDocument()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('renders when visible is true', () => {
|
|
104
|
+
render(<RestTimer {...defaultProps} visible />)
|
|
105
|
+
expect(screen.getByTestId('rest-timer')).toBeInTheDocument()
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('edge cases', () => {
|
|
110
|
+
it('clamps remaining to 0 when elapsed exceeds total', () => {
|
|
111
|
+
render(<RestTimer {...defaultProps} elapsedMs={200000} />)
|
|
112
|
+
expect(screen.getByTestId('rest-timer-time')).toHaveTextContent('0:00')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('displays full time when elapsed is 0', () => {
|
|
116
|
+
render(<RestTimer {...defaultProps} totalSeconds={90} elapsedMs={0} />)
|
|
117
|
+
expect(screen.getByTestId('rest-timer-time')).toHaveTextContent('1:30')
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('accessibility', () => {
|
|
122
|
+
it('has no accessibility violations', async () => {
|
|
123
|
+
const { container } = render(<RestTimer {...defaultProps} />)
|
|
124
|
+
const results = await axe(container)
|
|
125
|
+
expect(results).toHaveNoViolations()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('has timer role with remaining seconds in label', () => {
|
|
129
|
+
render(<RestTimer {...defaultProps} totalSeconds={90} elapsedMs={30000} />)
|
|
130
|
+
expect(
|
|
131
|
+
screen.getByLabelText('Rest timer, 60 seconds remaining'),
|
|
132
|
+
).toBeInTheDocument()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('skip button has accessible label', () => {
|
|
136
|
+
render(<RestTimer {...defaultProps} />)
|
|
137
|
+
expect(screen.getByLabelText('Skip rest')).toBeInTheDocument()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('add time button has accessible label', () => {
|
|
141
|
+
render(<RestTimer {...defaultProps} />)
|
|
142
|
+
expect(screen.getByLabelText('Add 30 seconds')).toBeInTheDocument()
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
})
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { View, Text, Pressable } from 'react-native'
|
|
4
|
+
|
|
5
|
+
export interface RestTimerProps {
|
|
6
|
+
totalSeconds: number
|
|
7
|
+
elapsedMs: number
|
|
8
|
+
onSkip: () => void
|
|
9
|
+
onAddTime: () => void
|
|
10
|
+
nextSetInfo?: string
|
|
11
|
+
visible: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function RestTimer({
|
|
15
|
+
totalSeconds,
|
|
16
|
+
elapsedMs,
|
|
17
|
+
onSkip,
|
|
18
|
+
onAddTime,
|
|
19
|
+
nextSetInfo,
|
|
20
|
+
visible,
|
|
21
|
+
}: RestTimerProps) {
|
|
22
|
+
if (!visible) return null
|
|
23
|
+
|
|
24
|
+
const remainingMs = Math.max(0, totalSeconds * 1000 - elapsedMs)
|
|
25
|
+
const remainingSec = Math.ceil(remainingMs / 1000)
|
|
26
|
+
const minutes = Math.floor(remainingSec / 60)
|
|
27
|
+
const seconds = remainingSec % 60
|
|
28
|
+
const timeDisplay = `${minutes}:${String(seconds).padStart(2, '0')}`
|
|
29
|
+
const progressPct = Math.min(100, (elapsedMs / (totalSeconds * 1000)) * 100)
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<View
|
|
33
|
+
style={{
|
|
34
|
+
width: '100%',
|
|
35
|
+
backgroundColor: '#1C1C1C',
|
|
36
|
+
borderTopWidth: 1,
|
|
37
|
+
borderTopColor: '#1F1F1F',
|
|
38
|
+
paddingVertical: 12,
|
|
39
|
+
paddingHorizontal: 16,
|
|
40
|
+
}}
|
|
41
|
+
accessibilityRole="timer"
|
|
42
|
+
accessibilityLabel={`Rest timer, ${remainingSec} seconds remaining`}
|
|
43
|
+
testID="rest-timer"
|
|
44
|
+
>
|
|
45
|
+
{/* Top row */}
|
|
46
|
+
<View
|
|
47
|
+
style={{
|
|
48
|
+
flexDirection: 'row',
|
|
49
|
+
justifyContent: 'space-between',
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
marginBottom: 8,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{/* Left side */}
|
|
55
|
+
<View style={{ flexDirection: 'column' }}>
|
|
56
|
+
<Text
|
|
57
|
+
style={{
|
|
58
|
+
fontSize: 11,
|
|
59
|
+
fontFamily: 'Inter, sans-serif',
|
|
60
|
+
fontWeight: '600',
|
|
61
|
+
textTransform: 'uppercase',
|
|
62
|
+
letterSpacing: 1,
|
|
63
|
+
color: '#9CA3AF',
|
|
64
|
+
}}
|
|
65
|
+
testID="rest-timer-label"
|
|
66
|
+
>
|
|
67
|
+
REST
|
|
68
|
+
</Text>
|
|
69
|
+
{nextSetInfo != null && (
|
|
70
|
+
<Text
|
|
71
|
+
style={{
|
|
72
|
+
fontSize: 11,
|
|
73
|
+
fontFamily: 'Inter, sans-serif',
|
|
74
|
+
color: '#6B7280',
|
|
75
|
+
marginTop: 2,
|
|
76
|
+
}}
|
|
77
|
+
testID="rest-timer-next-set"
|
|
78
|
+
>
|
|
79
|
+
{nextSetInfo}
|
|
80
|
+
</Text>
|
|
81
|
+
)}
|
|
82
|
+
</View>
|
|
83
|
+
|
|
84
|
+
{/* Right side - time display */}
|
|
85
|
+
<Text
|
|
86
|
+
style={{
|
|
87
|
+
fontSize: 28,
|
|
88
|
+
fontFamily: '"Space Grotesk", sans-serif',
|
|
89
|
+
fontWeight: '700',
|
|
90
|
+
color: '#F3F4F6',
|
|
91
|
+
fontVariant: ['tabular-nums'],
|
|
92
|
+
letterSpacing: -0.5,
|
|
93
|
+
}}
|
|
94
|
+
testID="rest-timer-time"
|
|
95
|
+
>
|
|
96
|
+
{timeDisplay}
|
|
97
|
+
</Text>
|
|
98
|
+
</View>
|
|
99
|
+
|
|
100
|
+
{/* Progress bar */}
|
|
101
|
+
<View
|
|
102
|
+
style={{
|
|
103
|
+
height: 3,
|
|
104
|
+
backgroundColor: '#1F1F1F',
|
|
105
|
+
borderRadius: 2,
|
|
106
|
+
marginBottom: 12,
|
|
107
|
+
}}
|
|
108
|
+
testID="rest-timer-progress-track"
|
|
109
|
+
>
|
|
110
|
+
<View
|
|
111
|
+
style={{
|
|
112
|
+
height: '100%',
|
|
113
|
+
backgroundColor: '#FF7900',
|
|
114
|
+
borderRadius: 2,
|
|
115
|
+
width: `${progressPct}%`,
|
|
116
|
+
}}
|
|
117
|
+
testID="rest-timer-progress-fill"
|
|
118
|
+
/>
|
|
119
|
+
</View>
|
|
120
|
+
|
|
121
|
+
{/* Actions row */}
|
|
122
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
123
|
+
<Pressable
|
|
124
|
+
onPress={onAddTime}
|
|
125
|
+
style={{
|
|
126
|
+
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
127
|
+
paddingVertical: 8,
|
|
128
|
+
paddingHorizontal: 20,
|
|
129
|
+
borderRadius: 8,
|
|
130
|
+
}}
|
|
131
|
+
accessibilityRole="button"
|
|
132
|
+
accessibilityLabel="Add 30 seconds"
|
|
133
|
+
testID="rest-timer-add-time"
|
|
134
|
+
>
|
|
135
|
+
<Text
|
|
136
|
+
style={{
|
|
137
|
+
fontSize: 11,
|
|
138
|
+
fontFamily: 'Inter, sans-serif',
|
|
139
|
+
fontWeight: '600',
|
|
140
|
+
color: '#9CA3AF',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
+30s
|
|
144
|
+
</Text>
|
|
145
|
+
</Pressable>
|
|
146
|
+
<Pressable
|
|
147
|
+
onPress={onSkip}
|
|
148
|
+
style={{
|
|
149
|
+
backgroundColor: 'rgba(255,121,0,0.12)',
|
|
150
|
+
paddingVertical: 8,
|
|
151
|
+
paddingHorizontal: 20,
|
|
152
|
+
borderRadius: 8,
|
|
153
|
+
}}
|
|
154
|
+
accessibilityRole="button"
|
|
155
|
+
accessibilityLabel="Skip rest"
|
|
156
|
+
testID="rest-timer-skip"
|
|
157
|
+
>
|
|
158
|
+
<Text
|
|
159
|
+
style={{
|
|
160
|
+
fontSize: 11,
|
|
161
|
+
fontFamily: 'Inter, sans-serif',
|
|
162
|
+
fontWeight: '600',
|
|
163
|
+
color: '#FF7900',
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
Skip
|
|
167
|
+
</Text>
|
|
168
|
+
</Pressable>
|
|
169
|
+
</View>
|
|
170
|
+
</View>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
import { SupersetWrapper } from './SupersetWrapper'
|
|
4
|
+
import { ExerciseCard } from './ExerciseCard'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof SupersetWrapper> = {
|
|
7
|
+
title: 'Custom/Workout/SupersetWrapper',
|
|
8
|
+
component: SupersetWrapper,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
decorators: [
|
|
11
|
+
(Story) => (
|
|
12
|
+
<View style={{ maxWidth: 400, padding: 16, backgroundColor: '#121212' }}>
|
|
13
|
+
<Story />
|
|
14
|
+
</View>
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default meta
|
|
20
|
+
type Story = StoryObj<typeof SupersetWrapper>
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
children: (
|
|
25
|
+
<>
|
|
26
|
+
<View
|
|
27
|
+
style={{
|
|
28
|
+
padding: 12,
|
|
29
|
+
backgroundColor: '#1C1C1C',
|
|
30
|
+
borderRadius: 8,
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
34
|
+
Exercise A
|
|
35
|
+
</Text>
|
|
36
|
+
</View>
|
|
37
|
+
<View
|
|
38
|
+
style={{
|
|
39
|
+
padding: 12,
|
|
40
|
+
backgroundColor: '#1C1C1C',
|
|
41
|
+
borderRadius: 8,
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
45
|
+
Exercise B
|
|
46
|
+
</Text>
|
|
47
|
+
</View>
|
|
48
|
+
</>
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const CustomLabel: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
label: 'A1/A2',
|
|
56
|
+
children: (
|
|
57
|
+
<>
|
|
58
|
+
<View
|
|
59
|
+
style={{
|
|
60
|
+
padding: 12,
|
|
61
|
+
backgroundColor: '#1C1C1C',
|
|
62
|
+
borderRadius: 8,
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
66
|
+
Exercise A1
|
|
67
|
+
</Text>
|
|
68
|
+
</View>
|
|
69
|
+
<View
|
|
70
|
+
style={{
|
|
71
|
+
padding: 12,
|
|
72
|
+
backgroundColor: '#1C1C1C',
|
|
73
|
+
borderRadius: 8,
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
77
|
+
Exercise A2
|
|
78
|
+
</Text>
|
|
79
|
+
</View>
|
|
80
|
+
</>
|
|
81
|
+
),
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const CustomColor: Story = {
|
|
86
|
+
args: {
|
|
87
|
+
color: '#22C55E',
|
|
88
|
+
children: (
|
|
89
|
+
<>
|
|
90
|
+
<View
|
|
91
|
+
style={{
|
|
92
|
+
padding: 12,
|
|
93
|
+
backgroundColor: '#1C1C1C',
|
|
94
|
+
borderRadius: 8,
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
98
|
+
Exercise A
|
|
99
|
+
</Text>
|
|
100
|
+
</View>
|
|
101
|
+
<View
|
|
102
|
+
style={{
|
|
103
|
+
padding: 12,
|
|
104
|
+
backgroundColor: '#1C1C1C',
|
|
105
|
+
borderRadius: 8,
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
109
|
+
Exercise B
|
|
110
|
+
</Text>
|
|
111
|
+
</View>
|
|
112
|
+
</>
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const exerciseA = {
|
|
118
|
+
name: 'Bench Press',
|
|
119
|
+
state: 'collapsed' as const,
|
|
120
|
+
onToggle: () => {},
|
|
121
|
+
summary: { sets: 3, reps: 8, weight: 185, unit: 'lbs' as const },
|
|
122
|
+
supersetPosition: 'first' as const,
|
|
123
|
+
supersetColor: '#FF7900',
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const exerciseB = {
|
|
127
|
+
name: 'Bent Over Row',
|
|
128
|
+
state: 'collapsed' as const,
|
|
129
|
+
onToggle: () => {},
|
|
130
|
+
summary: { sets: 3, reps: 8, weight: 155, unit: 'lbs' as const },
|
|
131
|
+
supersetPosition: 'last' as const,
|
|
132
|
+
supersetColor: '#FF7900',
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const WithExerciseCards: Story = {
|
|
136
|
+
args: {
|
|
137
|
+
children: (
|
|
138
|
+
<>
|
|
139
|
+
<ExerciseCard {...exerciseA} />
|
|
140
|
+
<ExerciseCard {...exerciseB} />
|
|
141
|
+
</>
|
|
142
|
+
),
|
|
143
|
+
},
|
|
144
|
+
}
|