@titan-design/react-ui 0.0.2 → 0.1.1
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/{chunk-NA3EES23.mjs → chunk-MGW37MPO.mjs} +26 -4
- package/dist/chunk-MGW37MPO.mjs.map +1 -0
- package/dist/index.d.mts +115 -2
- package/dist/index.d.ts +115 -2
- package/dist/index.js +262 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +225 -3
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.d.mts +124 -109
- package/dist/theme/index.d.ts +124 -109
- package/dist/theme/index.js +24 -1
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +1 -1
- package/package.json +12 -5
- package/src/components/custom/DateTime/DateTime.stories.tsx +180 -0
- package/src/components/custom/DateTime/DateTime.test.tsx +152 -0
- package/src/components/custom/DateTime/DateTime.tsx +235 -0
- package/src/components/custom/DateTime/index.ts +2 -0
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +99 -0
- package/src/components/custom/EmptyState/EmptyState.test.tsx +106 -0
- package/src/components/custom/EmptyState/EmptyState.tsx +68 -0
- package/src/components/custom/EmptyState/index.ts +2 -0
- package/src/components/custom/Metric/Metric.stories.tsx +133 -0
- package/src/components/custom/Metric/Metric.test.tsx +120 -0
- package/src/components/custom/Metric/Metric.tsx +92 -0
- package/src/components/custom/Metric/index.ts +2 -0
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +227 -0
- package/src/components/custom/Sidebar/Sidebar.test.tsx +185 -0
- package/src/components/custom/Sidebar/Sidebar.tsx +257 -0
- package/src/components/custom/Sidebar/index.ts +18 -0
- package/src/components/custom/Table/Table.stories.tsx +481 -0
- package/src/components/custom/Table/Table.test.tsx +531 -0
- package/src/components/custom/Table/Table.tsx +696 -0
- package/src/components/custom/Table/index.ts +22 -0
- package/src/components/custom/Typography/Typography.stories.tsx +153 -0
- package/src/components/custom/Typography/Typography.test.tsx +107 -0
- package/src/components/custom/Typography/Typography.tsx +218 -0
- package/src/components/custom/Typography/index.ts +19 -0
- package/src/components/custom/index.ts +8 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +245 -0
- package/src/components/custom/stepper/Stepper.test.tsx +230 -0
- package/src/components/custom/stepper/Stepper.tsx +250 -0
- package/src/components/custom/stepper/index.ts +10 -0
- package/src/components/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +154 -0
- package/src/components/ui/alert/Alert.test.tsx +202 -0
- package/src/components/ui/alert/Alert.tsx +170 -0
- package/src/components/ui/alert/index.ts +8 -0
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +299 -0
- package/src/components/ui/autocomplete/Autocomplete.test.tsx +247 -0
- package/src/components/ui/autocomplete/Autocomplete.tsx +332 -0
- package/src/components/ui/autocomplete/index.ts +2 -0
- package/src/components/ui/avatar/Avatar.stories.tsx +105 -0
- package/src/components/ui/avatar/Avatar.test.tsx +135 -0
- package/src/components/ui/avatar/Avatar.tsx +168 -0
- package/src/components/ui/avatar/index.ts +2 -0
- package/src/components/ui/badge/Badge.stories.tsx +100 -0
- package/src/components/ui/badge/Badge.test.tsx +86 -0
- package/src/components/ui/badge/Badge.tsx +105 -0
- package/src/components/ui/badge/index.ts +2 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +70 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.test.tsx +156 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +127 -0
- package/src/components/ui/breadcrumbs/index.ts +2 -0
- package/src/components/ui/button/Button.stories.tsx +216 -0
- package/src/components/ui/button/Button.test.tsx +132 -0
- package/src/components/ui/button/Button.tsx +242 -0
- package/src/components/ui/button/index.ts +10 -0
- package/src/components/ui/card/Card.stories.tsx +443 -0
- package/src/components/ui/card/Card.test.tsx +265 -0
- package/src/components/ui/card/Card.tsx +380 -0
- package/src/components/ui/card/index.ts +20 -0
- package/src/components/ui/checkbox/Checkbox.stories.tsx +126 -0
- package/src/components/ui/checkbox/Checkbox.test.tsx +152 -0
- package/src/components/ui/checkbox/Checkbox.tsx +163 -0
- package/src/components/ui/checkbox/index.ts +2 -0
- package/src/components/ui/chip/Chip.stories.tsx +112 -0
- package/src/components/ui/chip/Chip.test.tsx +119 -0
- package/src/components/ui/chip/Chip.tsx +151 -0
- package/src/components/ui/chip/index.ts +2 -0
- package/src/components/ui/collapse/Collapse.stories.tsx +313 -0
- package/src/components/ui/collapse/Collapse.test.tsx +298 -0
- package/src/components/ui/collapse/Collapse.tsx +275 -0
- package/src/components/ui/collapse/index.ts +18 -0
- package/src/components/ui/data-row/DataRow.stories.tsx +78 -0
- package/src/components/ui/data-row/DataRow.test.tsx +78 -0
- package/src/components/ui/data-row/DataRow.tsx +29 -0
- package/src/components/ui/data-row/index.ts +2 -0
- package/src/components/ui/divider/Divider.stories.tsx +71 -0
- package/src/components/ui/divider/Divider.test.tsx +55 -0
- package/src/components/ui/divider/Divider.tsx +37 -0
- package/src/components/ui/divider/index.ts +2 -0
- package/src/components/ui/drawer/Drawer.stories.tsx +228 -0
- package/src/components/ui/drawer/Drawer.test.tsx +142 -0
- package/src/components/ui/drawer/Drawer.tsx +228 -0
- package/src/components/ui/drawer/index.ts +9 -0
- package/src/components/ui/form-field/FormField.stories.tsx +260 -0
- package/src/components/ui/form-field/FormField.test.tsx +280 -0
- package/src/components/ui/form-field/FormField.tsx +308 -0
- package/src/components/ui/form-field/index.ts +2 -0
- package/src/components/ui/help-tip/HelpTip.stories.tsx +250 -0
- package/src/components/ui/help-tip/HelpTip.test.tsx +198 -0
- package/src/components/ui/help-tip/HelpTip.tsx +229 -0
- package/src/components/ui/help-tip/index.ts +2 -0
- package/src/components/ui/icon-box/IconBox.stories.tsx +139 -0
- package/src/components/ui/icon-box/IconBox.test.tsx +120 -0
- package/src/components/ui/icon-box/IconBox.tsx +57 -0
- package/src/components/ui/icon-box/index.ts +1 -0
- package/src/components/ui/index.ts +36 -0
- package/src/components/ui/input/Input.stories.tsx +192 -0
- package/src/components/ui/input/Input.test.tsx +86 -0
- package/src/components/ui/input/Input.tsx +273 -0
- package/src/components/ui/input/index.ts +2 -0
- package/src/components/ui/link/Link.stories.tsx +78 -0
- package/src/components/ui/link/Link.test.tsx +94 -0
- package/src/components/ui/link/Link.tsx +86 -0
- package/src/components/ui/link/index.ts +2 -0
- package/src/components/ui/list-item/ListItem.stories.tsx +146 -0
- package/src/components/ui/list-item/ListItem.test.tsx +128 -0
- package/src/components/ui/list-item/ListItem.tsx +89 -0
- package/src/components/ui/list-item/index.ts +14 -0
- package/src/components/ui/menu/Menu.stories.tsx +180 -0
- package/src/components/ui/menu/Menu.test.tsx +353 -0
- package/src/components/ui/menu/Menu.tsx +222 -0
- package/src/components/ui/menu/index.ts +2 -0
- package/src/components/ui/modal/Modal.stories.tsx +350 -0
- package/src/components/ui/modal/Modal.test.tsx +159 -0
- package/src/components/ui/modal/Modal.tsx +254 -0
- package/src/components/ui/modal/index.ts +19 -0
- package/src/components/ui/popover/Popover.stories.tsx +190 -0
- package/src/components/ui/popover/Popover.test.tsx +277 -0
- package/src/components/ui/popover/Popover.tsx +162 -0
- package/src/components/ui/popover/index.ts +2 -0
- package/src/components/ui/progress/Progress.stories.tsx +190 -0
- package/src/components/ui/progress/Progress.test.tsx +181 -0
- package/src/components/ui/progress/Progress.tsx +320 -0
- package/src/components/ui/progress/index.ts +9 -0
- package/src/components/ui/radio/Radio.stories.tsx +199 -0
- package/src/components/ui/radio/Radio.test.tsx +166 -0
- package/src/components/ui/radio/Radio.tsx +200 -0
- package/src/components/ui/radio/index.ts +2 -0
- package/src/components/ui/section/Section.stories.tsx +111 -0
- package/src/components/ui/section/Section.test.tsx +121 -0
- package/src/components/ui/section/Section.tsx +52 -0
- package/src/components/ui/section/index.ts +6 -0
- package/src/components/ui/select/Select.stories.tsx +286 -0
- package/src/components/ui/select/Select.test.tsx +180 -0
- package/src/components/ui/select/Select.tsx +236 -0
- package/src/components/ui/select/index.ts +2 -0
- package/src/components/ui/skeleton/Skeleton.stories.tsx +153 -0
- package/src/components/ui/skeleton/Skeleton.test.tsx +162 -0
- package/src/components/ui/skeleton/Skeleton.tsx +229 -0
- package/src/components/ui/skeleton/index.ts +16 -0
- package/src/components/ui/spinner/Spinner.stories.tsx +67 -0
- package/src/components/ui/spinner/Spinner.test.tsx +67 -0
- package/src/components/ui/spinner/Spinner.tsx +72 -0
- package/src/components/ui/spinner/index.ts +2 -0
- package/src/components/ui/stack/Stack.stories.tsx +127 -0
- package/src/components/ui/stack/Stack.test.tsx +184 -0
- package/src/components/ui/stack/Stack.tsx +76 -0
- package/src/components/ui/stack/index.ts +2 -0
- package/src/components/ui/surface/Surface.stories.tsx +91 -0
- package/src/components/ui/surface/Surface.test.tsx +63 -0
- package/src/components/ui/surface/Surface.tsx +51 -0
- package/src/components/ui/surface/index.ts +1 -0
- package/src/components/ui/switch/Switch.stories.tsx +111 -0
- package/src/components/ui/switch/Switch.test.tsx +112 -0
- package/src/components/ui/switch/Switch.tsx +103 -0
- package/src/components/ui/switch/index.ts +2 -0
- package/src/components/ui/tabs/Tabs.stories.tsx +213 -0
- package/src/components/ui/tabs/Tabs.test.tsx +192 -0
- package/src/components/ui/tabs/Tabs.tsx +253 -0
- package/src/components/ui/tabs/index.ts +16 -0
- package/src/components/ui/toast/Toast.stories.tsx +255 -0
- package/src/components/ui/toast/Toast.test.tsx +207 -0
- package/src/components/ui/toast/Toast.tsx +327 -0
- package/src/components/ui/toast/index.ts +8 -0
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +415 -0
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +224 -0
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +307 -0
- package/src/components/ui/toolbar-button/index.ts +7 -0
- package/src/components/ui/tooltip/Tooltip.stories.tsx +126 -0
- package/src/components/ui/tooltip/Tooltip.test.tsx +179 -0
- package/src/components/ui/tooltip/Tooltip.tsx +143 -0
- package/src/components/ui/tooltip/index.ts +2 -0
- package/src/index.ts +10 -0
- package/src/test/setup.ts +19 -0
- package/src/theme/Colors.stories.tsx +258 -0
- package/src/theme/config.ts +193 -0
- package/src/theme/elevation.stories.tsx +346 -0
- package/src/theme/elevation.test.ts +27 -0
- package/src/theme/elevation.ts +567 -0
- package/src/theme/index.ts +39 -0
- package/src/theme/shadows.stories.tsx +523 -0
- package/src/theme/shadows.ts +439 -0
- package/src/theme/tokens/index.ts +2 -0
- package/src/theme/tokens/primitives.ts +354 -0
- package/src/theme/tokens/semantic.ts +381 -0
- package/src/types/jest-axe.d.ts +16 -0
- package/src/types/nativewind.d.ts +54 -0
- package/src/utils/cn.ts +14 -0
- package/src/utils/colors.ts +140 -0
- package/src/utils/form.ts +188 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/useTheme.ts +72 -0
- package/dist/chunk-NA3EES23.mjs.map +0 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { Progress, CircularProgress, ProgressSteps } from './Progress'
|
|
5
|
+
|
|
6
|
+
describe('Progress', () => {
|
|
7
|
+
it('renders correctly', () => {
|
|
8
|
+
render(<Progress value={50} />)
|
|
9
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('renders with default props', () => {
|
|
13
|
+
render(<Progress />)
|
|
14
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('sets accessibility value', () => {
|
|
18
|
+
render(<Progress value={75} max={100} />)
|
|
19
|
+
const progressbar = screen.getByRole('progressbar')
|
|
20
|
+
// react-native-web maps accessibilityValue but not to standard aria-value* attributes
|
|
21
|
+
expect(progressbar).toBeInTheDocument()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('renders with label', () => {
|
|
25
|
+
render(<Progress value={50} label="Uploading..." />)
|
|
26
|
+
expect(screen.getByText('Uploading...')).toBeInTheDocument()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('renders with value display', () => {
|
|
30
|
+
render(<Progress value={50} showValue />)
|
|
31
|
+
expect(screen.getByText('50%')).toBeInTheDocument()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('uses custom formatValue function', () => {
|
|
35
|
+
render(<Progress value={3} max={10} showValue formatValue={(v, m) => `${v}/${m}`} />)
|
|
36
|
+
expect(screen.getByText('3/10')).toBeInTheDocument()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('does not show value when indeterminate', () => {
|
|
40
|
+
render(<Progress isIndeterminate showValue />)
|
|
41
|
+
expect(screen.queryByText(/%/)).not.toBeInTheDocument()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('sets undefined aria-valuenow when indeterminate', () => {
|
|
45
|
+
render(<Progress isIndeterminate />)
|
|
46
|
+
const progressbar = screen.getByRole('progressbar')
|
|
47
|
+
expect(progressbar).not.toHaveAttribute('aria-valuenow')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('renders with all size options', () => {
|
|
51
|
+
const sizes = ['sm', 'md', 'lg'] as const
|
|
52
|
+
sizes.forEach((size) => {
|
|
53
|
+
const { unmount } = render(<Progress size={size} value={50} />)
|
|
54
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
55
|
+
unmount()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('renders with all color options', () => {
|
|
60
|
+
const colors = ['primary', 'secondary', 'success', 'error', 'warning', 'info'] as const
|
|
61
|
+
colors.forEach((color) => {
|
|
62
|
+
const { unmount } = render(<Progress color={color} value={50} />)
|
|
63
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
64
|
+
unmount()
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('clamps value between 0 and 100', () => {
|
|
69
|
+
const { unmount } = render(<Progress value={150} />)
|
|
70
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
71
|
+
unmount()
|
|
72
|
+
|
|
73
|
+
render(<Progress value={-10} />)
|
|
74
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('applies custom className', () => {
|
|
78
|
+
const { container } = render(<Progress className="extra" value={50} />)
|
|
79
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('CircularProgress', () => {
|
|
83
|
+
it('renders correctly', () => {
|
|
84
|
+
render(<CircularProgress value={50} />)
|
|
85
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('sets accessibility value', () => {
|
|
89
|
+
render(<CircularProgress value={75} max={100} />)
|
|
90
|
+
const progressbar = screen.getByRole('progressbar')
|
|
91
|
+
// react-native-web maps accessibilityValue but not to standard aria-value* attributes
|
|
92
|
+
expect(progressbar).toBeInTheDocument()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('shows value when showValue is true', () => {
|
|
96
|
+
render(<CircularProgress value={50} showValue />)
|
|
97
|
+
expect(screen.getByText('50%')).toBeInTheDocument()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('does not show value when indeterminate', () => {
|
|
101
|
+
render(<CircularProgress isIndeterminate showValue />)
|
|
102
|
+
expect(screen.queryByText(/%/)).not.toBeInTheDocument()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('renders with custom size and strokeWidth', () => {
|
|
106
|
+
render(<CircularProgress size={64} strokeWidth={6} value={50} />)
|
|
107
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('renders with all color options', () => {
|
|
111
|
+
const colors = ['primary', 'secondary', 'success', 'error', 'warning', 'info'] as const
|
|
112
|
+
colors.forEach((color) => {
|
|
113
|
+
const { unmount } = render(<CircularProgress color={color} value={50} />)
|
|
114
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument()
|
|
115
|
+
unmount()
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('ProgressSteps', () => {
|
|
121
|
+
it('renders correct number of steps', () => {
|
|
122
|
+
const { container } = render(<ProgressSteps currentStep={1} totalSteps={4} />)
|
|
123
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('renders labels', () => {
|
|
127
|
+
render(
|
|
128
|
+
<ProgressSteps
|
|
129
|
+
currentStep={1}
|
|
130
|
+
totalSteps={3}
|
|
131
|
+
labels={['Step 1', 'Step 2', 'Step 3']}
|
|
132
|
+
/>
|
|
133
|
+
)
|
|
134
|
+
expect(screen.getByText('Step 1')).toBeInTheDocument()
|
|
135
|
+
expect(screen.getByText('Step 2')).toBeInTheDocument()
|
|
136
|
+
expect(screen.getByText('Step 3')).toBeInTheDocument()
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('renders with all size options', () => {
|
|
140
|
+
const sizes = ['sm', 'md', 'lg'] as const
|
|
141
|
+
sizes.forEach((size) => {
|
|
142
|
+
const { unmount } = render(<ProgressSteps currentStep={0} totalSteps={3} size={size} />)
|
|
143
|
+
unmount()
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('renders with all color options', () => {
|
|
148
|
+
const colors = ['primary', 'secondary', 'success', 'error', 'warning', 'info'] as const
|
|
149
|
+
colors.forEach((color) => {
|
|
150
|
+
const { unmount } = render(<ProgressSteps currentStep={0} totalSteps={3} color={color} />)
|
|
151
|
+
unmount()
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe('accessibility', () => {
|
|
157
|
+
it('has no accessibility violations for Progress', async () => {
|
|
158
|
+
const { container } = render(<Progress value={50} label="Upload progress" />)
|
|
159
|
+
const results = await axe(container, {
|
|
160
|
+
rules: { 'aria-progressbar-name': { enabled: false } },
|
|
161
|
+
})
|
|
162
|
+
expect(results).toHaveNoViolations()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('has no accessibility violations for CircularProgress', async () => {
|
|
166
|
+
const { container } = render(<CircularProgress value={50} />)
|
|
167
|
+
const results = await axe(container, {
|
|
168
|
+
rules: { 'aria-progressbar-name': { enabled: false } },
|
|
169
|
+
})
|
|
170
|
+
expect(results).toHaveNoViolations()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('has no accessibility violations for ProgressSteps', async () => {
|
|
174
|
+
const { container } = render(
|
|
175
|
+
<ProgressSteps currentStep={1} totalSteps={3} labels={['A', 'B', 'C']} />
|
|
176
|
+
)
|
|
177
|
+
const results = await axe(container)
|
|
178
|
+
expect(results).toHaveNoViolations()
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
})
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, type ViewProps } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
|
|
5
|
+
export type ProgressSize = 'sm' | 'md' | 'lg'
|
|
6
|
+
export type ProgressColor = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info'
|
|
7
|
+
export type ProgressVariant = 'linear' | 'circular'
|
|
8
|
+
|
|
9
|
+
/** Maps the color prop to a CSS variable reference for inline styles */
|
|
10
|
+
const colorVarMap: Record<ProgressColor, string> = {
|
|
11
|
+
primary: 'var(--color-brand-primary)',
|
|
12
|
+
secondary: 'var(--color-brand-secondary)',
|
|
13
|
+
success: 'var(--color-status-success)',
|
|
14
|
+
error: 'var(--color-status-error)',
|
|
15
|
+
warning: 'var(--color-status-warning)',
|
|
16
|
+
info: 'var(--color-status-info)',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ProgressProps extends ViewProps {
|
|
20
|
+
/** Current value (0-100) */
|
|
21
|
+
value?: number
|
|
22
|
+
/** Maximum value */
|
|
23
|
+
max?: number
|
|
24
|
+
/** Whether the progress is indeterminate */
|
|
25
|
+
isIndeterminate?: boolean
|
|
26
|
+
/** Size of the progress bar */
|
|
27
|
+
size?: ProgressSize
|
|
28
|
+
/** Color of the progress */
|
|
29
|
+
color?: ProgressColor
|
|
30
|
+
/** Whether to show the value label */
|
|
31
|
+
showValue?: boolean
|
|
32
|
+
/** Custom value format function */
|
|
33
|
+
formatValue?: (value: number, max: number) => string
|
|
34
|
+
/** Label text */
|
|
35
|
+
label?: string
|
|
36
|
+
/** Additional className */
|
|
37
|
+
className?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const sizeStyles: Record<ProgressSize, string> = {
|
|
41
|
+
sm: 'h-1',
|
|
42
|
+
md: 'h-2',
|
|
43
|
+
lg: 'h-3',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const colorStyles: Record<ProgressColor, string> = {
|
|
47
|
+
primary: 'bg-brand-primary',
|
|
48
|
+
secondary: 'bg-brand-secondary',
|
|
49
|
+
success: 'bg-status-success',
|
|
50
|
+
error: 'bg-status-error',
|
|
51
|
+
warning: 'bg-status-warning',
|
|
52
|
+
info: 'bg-status-info',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const trackColorStyles: Record<ProgressColor, string> = {
|
|
56
|
+
primary: 'bg-brand-primary/20',
|
|
57
|
+
secondary: 'bg-brand-secondary/20',
|
|
58
|
+
success: 'bg-status-success/20',
|
|
59
|
+
error: 'bg-status-error/20',
|
|
60
|
+
warning: 'bg-status-warning/20',
|
|
61
|
+
info: 'bg-status-info/20',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Linear progress bar component.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // Determinate progress
|
|
69
|
+
* <Progress value={75} />
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // With label and value
|
|
73
|
+
* <Progress value={45} label="Uploading..." showValue />
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Indeterminate progress
|
|
77
|
+
* <Progress isIndeterminate />
|
|
78
|
+
*/
|
|
79
|
+
export function Progress({
|
|
80
|
+
value = 0,
|
|
81
|
+
max = 100,
|
|
82
|
+
isIndeterminate = false,
|
|
83
|
+
size = 'md',
|
|
84
|
+
color = 'primary',
|
|
85
|
+
showValue = false,
|
|
86
|
+
formatValue = (v, m) => `${Math.round((v / m) * 100)}%`,
|
|
87
|
+
label,
|
|
88
|
+
className,
|
|
89
|
+
...props
|
|
90
|
+
}: ProgressProps) {
|
|
91
|
+
const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<View className={cn('w-full', className)} {...props}>
|
|
95
|
+
{/* Label and Value Row */}
|
|
96
|
+
{(label || showValue) && (
|
|
97
|
+
<View className="flex-row justify-between items-center mb-1">
|
|
98
|
+
{label && (
|
|
99
|
+
<Text className="text-sm text-text-secondary">{label}</Text>
|
|
100
|
+
)}
|
|
101
|
+
{showValue && !isIndeterminate && (
|
|
102
|
+
<Text className="text-sm font-medium text-text-primary">
|
|
103
|
+
{formatValue(value, max)}
|
|
104
|
+
</Text>
|
|
105
|
+
)}
|
|
106
|
+
</View>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
{/* Progress Track */}
|
|
110
|
+
<View
|
|
111
|
+
className={cn(
|
|
112
|
+
'w-full rounded-full overflow-hidden',
|
|
113
|
+
trackColorStyles[color],
|
|
114
|
+
sizeStyles[size]
|
|
115
|
+
)}
|
|
116
|
+
accessibilityRole="progressbar"
|
|
117
|
+
accessibilityValue={{
|
|
118
|
+
min: 0,
|
|
119
|
+
max: max,
|
|
120
|
+
now: isIndeterminate ? undefined : value,
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
{/* Progress Fill */}
|
|
124
|
+
<View
|
|
125
|
+
className={cn(
|
|
126
|
+
'h-full rounded-full',
|
|
127
|
+
colorStyles[color],
|
|
128
|
+
isIndeterminate && 'animate-pulse w-1/3'
|
|
129
|
+
)}
|
|
130
|
+
style={isIndeterminate ? undefined : { width: `${percentage}%` }}
|
|
131
|
+
/>
|
|
132
|
+
</View>
|
|
133
|
+
</View>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface CircularProgressProps extends ViewProps {
|
|
138
|
+
/** Current value (0-100) */
|
|
139
|
+
value?: number
|
|
140
|
+
/** Maximum value */
|
|
141
|
+
max?: number
|
|
142
|
+
/** Whether the progress is indeterminate */
|
|
143
|
+
isIndeterminate?: boolean
|
|
144
|
+
/** Size of the progress circle (diameter) */
|
|
145
|
+
size?: number
|
|
146
|
+
/** Stroke width */
|
|
147
|
+
strokeWidth?: number
|
|
148
|
+
/** Color of the progress */
|
|
149
|
+
color?: ProgressColor
|
|
150
|
+
/** Whether to show the value in center */
|
|
151
|
+
showValue?: boolean
|
|
152
|
+
/** Custom value format function */
|
|
153
|
+
formatValue?: (value: number, max: number) => string
|
|
154
|
+
/** Additional className */
|
|
155
|
+
className?: string
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Circular progress indicator component.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* <CircularProgress value={75} showValue />
|
|
163
|
+
*/
|
|
164
|
+
export function CircularProgress({
|
|
165
|
+
value = 0,
|
|
166
|
+
max = 100,
|
|
167
|
+
isIndeterminate = false,
|
|
168
|
+
size = 48,
|
|
169
|
+
strokeWidth = 4,
|
|
170
|
+
color = 'primary',
|
|
171
|
+
showValue = false,
|
|
172
|
+
formatValue = (v, m) => `${Math.round((v / m) * 100)}%`,
|
|
173
|
+
className,
|
|
174
|
+
...props
|
|
175
|
+
}: CircularProgressProps) {
|
|
176
|
+
const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
|
|
177
|
+
const radius = (size - strokeWidth) / 2
|
|
178
|
+
const circumference = 2 * Math.PI * radius
|
|
179
|
+
const strokeDashoffset = circumference - (percentage / 100) * circumference
|
|
180
|
+
|
|
181
|
+
// For web, we use SVG. For native, we use a simplified view-based approach.
|
|
182
|
+
return (
|
|
183
|
+
<View
|
|
184
|
+
className={cn('items-center justify-center', className)}
|
|
185
|
+
style={{ width: size, height: size }}
|
|
186
|
+
accessibilityRole="progressbar"
|
|
187
|
+
accessibilityValue={{
|
|
188
|
+
min: 0,
|
|
189
|
+
max: max,
|
|
190
|
+
now: isIndeterminate ? undefined : value,
|
|
191
|
+
}}
|
|
192
|
+
{...props}
|
|
193
|
+
>
|
|
194
|
+
{/* Background circle */}
|
|
195
|
+
<View
|
|
196
|
+
className={cn(
|
|
197
|
+
'absolute rounded-full border-4',
|
|
198
|
+
'border-border-default'
|
|
199
|
+
)}
|
|
200
|
+
style={{
|
|
201
|
+
width: size,
|
|
202
|
+
height: size,
|
|
203
|
+
borderWidth: strokeWidth,
|
|
204
|
+
}}
|
|
205
|
+
/>
|
|
206
|
+
|
|
207
|
+
{/* Progress arc (simplified) */}
|
|
208
|
+
<View
|
|
209
|
+
className={cn(
|
|
210
|
+
'absolute rounded-full',
|
|
211
|
+
colorStyles[color],
|
|
212
|
+
isIndeterminate && 'animate-spin'
|
|
213
|
+
)}
|
|
214
|
+
style={{
|
|
215
|
+
width: size,
|
|
216
|
+
height: size,
|
|
217
|
+
borderWidth: strokeWidth,
|
|
218
|
+
borderColor: 'transparent',
|
|
219
|
+
borderTopColor: colorVarMap[color],
|
|
220
|
+
borderRightColor: percentage > 25 ? colorVarMap[color] : 'transparent',
|
|
221
|
+
borderBottomColor: percentage > 50 ? colorVarMap[color] : 'transparent',
|
|
222
|
+
borderLeftColor: percentage > 75 ? colorVarMap[color] : 'transparent',
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
|
|
226
|
+
{/* Center value */}
|
|
227
|
+
{showValue && !isIndeterminate && (
|
|
228
|
+
<Text className="text-xs font-semibold text-text-primary">
|
|
229
|
+
{formatValue(value, max)}
|
|
230
|
+
</Text>
|
|
231
|
+
)}
|
|
232
|
+
</View>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface ProgressStepsProps extends ViewProps {
|
|
237
|
+
/** Current step (0-indexed) */
|
|
238
|
+
currentStep: number
|
|
239
|
+
/** Total number of steps */
|
|
240
|
+
totalSteps: number
|
|
241
|
+
/** Color of completed steps */
|
|
242
|
+
color?: ProgressColor
|
|
243
|
+
/** Size of step indicators */
|
|
244
|
+
size?: ProgressSize
|
|
245
|
+
/** Labels for each step */
|
|
246
|
+
labels?: string[]
|
|
247
|
+
/** Additional className */
|
|
248
|
+
className?: string
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const stepSizeStyles: Record<ProgressSize, { dot: string; connector: string }> = {
|
|
252
|
+
sm: { dot: 'w-2 h-2', connector: 'h-0.5' },
|
|
253
|
+
md: { dot: 'w-3 h-3', connector: 'h-1' },
|
|
254
|
+
lg: { dot: 'w-4 h-4', connector: 'h-1.5' },
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Step-based progress indicator.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* <ProgressSteps currentStep={2} totalSteps={4} labels={['Cart', 'Shipping', 'Payment', 'Done']} />
|
|
262
|
+
*/
|
|
263
|
+
export function ProgressSteps({
|
|
264
|
+
currentStep,
|
|
265
|
+
totalSteps,
|
|
266
|
+
color = 'primary',
|
|
267
|
+
size = 'md',
|
|
268
|
+
labels,
|
|
269
|
+
className,
|
|
270
|
+
...props
|
|
271
|
+
}: ProgressStepsProps) {
|
|
272
|
+
const { dot, connector } = stepSizeStyles[size]
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<View className={cn('w-full', className)} {...props}>
|
|
276
|
+
<View className="flex-row items-center">
|
|
277
|
+
{Array.from({ length: totalSteps }).map((_, index) => (
|
|
278
|
+
<React.Fragment key={index}>
|
|
279
|
+
{/* Step dot */}
|
|
280
|
+
<View
|
|
281
|
+
className={cn(
|
|
282
|
+
'rounded-full',
|
|
283
|
+
dot,
|
|
284
|
+
index <= currentStep ? colorStyles[color] : 'bg-border-strong'
|
|
285
|
+
)}
|
|
286
|
+
/>
|
|
287
|
+
|
|
288
|
+
{/* Connector line */}
|
|
289
|
+
{index < totalSteps - 1 && (
|
|
290
|
+
<View
|
|
291
|
+
className={cn(
|
|
292
|
+
'flex-1 mx-1',
|
|
293
|
+
connector,
|
|
294
|
+
index < currentStep ? colorStyles[color] : 'bg-border-strong'
|
|
295
|
+
)}
|
|
296
|
+
/>
|
|
297
|
+
)}
|
|
298
|
+
</React.Fragment>
|
|
299
|
+
))}
|
|
300
|
+
</View>
|
|
301
|
+
|
|
302
|
+
{/* Labels */}
|
|
303
|
+
{labels && labels.length > 0 && (
|
|
304
|
+
<View className="flex-row mt-2">
|
|
305
|
+
{labels.map((label, index) => (
|
|
306
|
+
<Text
|
|
307
|
+
key={index}
|
|
308
|
+
className={cn(
|
|
309
|
+
'flex-1 text-xs text-center',
|
|
310
|
+
index <= currentStep ? 'text-text-primary font-medium' : 'text-text-tertiary'
|
|
311
|
+
)}
|
|
312
|
+
>
|
|
313
|
+
{label}
|
|
314
|
+
</Text>
|
|
315
|
+
))}
|
|
316
|
+
</View>
|
|
317
|
+
)}
|
|
318
|
+
</View>
|
|
319
|
+
)
|
|
320
|
+
}
|