@titan-design/react-ui 0.1.0 → 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 +22 -6
- 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/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 +9 -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 +152 -0
- package/src/components/ui/avatar/Avatar.tsx +176 -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 +103 -0
- package/src/components/ui/badge/Badge.tsx +119 -0
- package/src/components/ui/badge/index.ts +3 -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 +297 -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 +294 -0
- package/src/components/ui/card/Card.tsx +401 -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 +38 -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/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/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.stories.tsx +190 -0
- package/src/components/ui/popover/Popover.test.tsx +419 -0
- package/src/components/ui/popover/Popover.tsx +237 -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 +210 -0
- package/src/components/ui/progress/Progress.tsx +339 -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 +200 -0
- package/src/components/ui/select/Select.tsx +243 -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 +9 -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 +262 -0
- package/src/components/ui/tooltip/Tooltip.tsx +234 -0
- package/src/components/ui/tooltip/index.ts +2 -0
- package/src/index.ts +10 -0
- package/src/stories/PresetShowcase.stories.tsx +462 -0
- package/src/stories/ThemePresets.stories.tsx +85 -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/global.css +129 -1
- package/src/theme/index.ts +40 -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/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/avatar-color.test.ts +30 -0
- package/src/utils/avatar-color.ts +20 -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/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
|
@@ -0,0 +1,339 @@
|
|
|
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
|
+
/** Fixed track width in pixels (default: full width) */
|
|
37
|
+
trackWidth?: number
|
|
38
|
+
/** Custom hex color override for the fill bar */
|
|
39
|
+
customColor?: string
|
|
40
|
+
/** Additional className */
|
|
41
|
+
className?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sizeStyles: Record<ProgressSize, string> = {
|
|
45
|
+
sm: 'h-1',
|
|
46
|
+
md: 'h-2',
|
|
47
|
+
lg: 'h-3',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const colorStyles: Record<ProgressColor, string> = {
|
|
51
|
+
primary: 'bg-brand-primary',
|
|
52
|
+
secondary: 'bg-brand-secondary',
|
|
53
|
+
success: 'bg-status-success',
|
|
54
|
+
error: 'bg-status-error',
|
|
55
|
+
warning: 'bg-status-warning',
|
|
56
|
+
info: 'bg-status-info',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const trackColorStyles: Record<ProgressColor, string> = {
|
|
60
|
+
primary: 'bg-brand-primary/20',
|
|
61
|
+
secondary: 'bg-brand-secondary/20',
|
|
62
|
+
success: 'bg-status-success/20',
|
|
63
|
+
error: 'bg-status-error/20',
|
|
64
|
+
warning: 'bg-status-warning/20',
|
|
65
|
+
info: 'bg-status-info/20',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Linear progress bar component.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Determinate progress
|
|
73
|
+
* <Progress value={75} />
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // With label and value
|
|
77
|
+
* <Progress value={45} label="Uploading..." showValue />
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Indeterminate progress
|
|
81
|
+
* <Progress isIndeterminate />
|
|
82
|
+
*/
|
|
83
|
+
export function Progress({
|
|
84
|
+
value = 0,
|
|
85
|
+
max = 100,
|
|
86
|
+
isIndeterminate = false,
|
|
87
|
+
size = 'md',
|
|
88
|
+
color = 'primary',
|
|
89
|
+
showValue = false,
|
|
90
|
+
formatValue = (v, m) => `${Math.round((v / m) * 100)}%`,
|
|
91
|
+
label,
|
|
92
|
+
trackWidth,
|
|
93
|
+
customColor,
|
|
94
|
+
className,
|
|
95
|
+
...props
|
|
96
|
+
}: ProgressProps) {
|
|
97
|
+
const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<View className={cn('w-full', className)} {...props}>
|
|
101
|
+
{/* Label and Value Row */}
|
|
102
|
+
{(label || showValue) && (
|
|
103
|
+
<View className="flex-row justify-between items-center mb-1">
|
|
104
|
+
{label && (
|
|
105
|
+
<Text className="text-sm text-text-secondary">{label}</Text>
|
|
106
|
+
)}
|
|
107
|
+
{showValue && !isIndeterminate && (
|
|
108
|
+
<Text className="text-sm font-medium text-text-primary">
|
|
109
|
+
{formatValue(value, max)}
|
|
110
|
+
</Text>
|
|
111
|
+
)}
|
|
112
|
+
</View>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
{/* Progress Track */}
|
|
116
|
+
<View
|
|
117
|
+
className={cn(
|
|
118
|
+
'rounded-full overflow-hidden',
|
|
119
|
+
trackWidth ? undefined : 'w-full',
|
|
120
|
+
trackColorStyles[color],
|
|
121
|
+
sizeStyles[size]
|
|
122
|
+
)}
|
|
123
|
+
style={trackWidth ? { width: trackWidth } : undefined}
|
|
124
|
+
accessibilityRole="progressbar"
|
|
125
|
+
accessibilityValue={{
|
|
126
|
+
min: 0,
|
|
127
|
+
max: max,
|
|
128
|
+
now: isIndeterminate ? undefined : value,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{/* Progress Fill */}
|
|
132
|
+
<View
|
|
133
|
+
className={cn(
|
|
134
|
+
'h-full rounded-full',
|
|
135
|
+
!customColor && colorStyles[color],
|
|
136
|
+
isIndeterminate && 'animate-pulse w-1/3'
|
|
137
|
+
)}
|
|
138
|
+
style={{
|
|
139
|
+
...(isIndeterminate ? {} : { width: `${percentage}%` }),
|
|
140
|
+
...(customColor ? { backgroundColor: customColor } : {}),
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
</View>
|
|
144
|
+
</View>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface CircularProgressProps extends ViewProps {
|
|
149
|
+
/** Current value (0-100) */
|
|
150
|
+
value?: number
|
|
151
|
+
/** Maximum value */
|
|
152
|
+
max?: number
|
|
153
|
+
/** Whether the progress is indeterminate */
|
|
154
|
+
isIndeterminate?: boolean
|
|
155
|
+
/** Size of the progress circle (diameter) */
|
|
156
|
+
size?: number
|
|
157
|
+
/** Stroke width */
|
|
158
|
+
strokeWidth?: number
|
|
159
|
+
/** Color of the progress */
|
|
160
|
+
color?: ProgressColor
|
|
161
|
+
/** Whether to show the value in center */
|
|
162
|
+
showValue?: boolean
|
|
163
|
+
/** Custom value format function */
|
|
164
|
+
formatValue?: (value: number, max: number) => string
|
|
165
|
+
/** Additional className */
|
|
166
|
+
className?: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Circular progress indicator component.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* <CircularProgress value={75} showValue />
|
|
174
|
+
*/
|
|
175
|
+
export function CircularProgress({
|
|
176
|
+
value = 0,
|
|
177
|
+
max = 100,
|
|
178
|
+
isIndeterminate = false,
|
|
179
|
+
size = 48,
|
|
180
|
+
strokeWidth = 4,
|
|
181
|
+
color = 'primary',
|
|
182
|
+
showValue = false,
|
|
183
|
+
formatValue = (v, m) => `${Math.round((v / m) * 100)}%`,
|
|
184
|
+
className,
|
|
185
|
+
...props
|
|
186
|
+
}: CircularProgressProps) {
|
|
187
|
+
const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
|
|
188
|
+
const radius = (size - strokeWidth) / 2
|
|
189
|
+
const circumference = 2 * Math.PI * radius
|
|
190
|
+
const strokeDashoffset = circumference - (percentage / 100) * circumference
|
|
191
|
+
const center = size / 2
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<View
|
|
195
|
+
className={cn('items-center justify-center', className)}
|
|
196
|
+
style={{ width: size, height: size }}
|
|
197
|
+
accessibilityRole="progressbar"
|
|
198
|
+
accessibilityValue={{
|
|
199
|
+
min: 0,
|
|
200
|
+
max: max,
|
|
201
|
+
now: isIndeterminate ? undefined : value,
|
|
202
|
+
}}
|
|
203
|
+
{...props}
|
|
204
|
+
>
|
|
205
|
+
{/* SVG implementation for web */}
|
|
206
|
+
<View
|
|
207
|
+
className={cn(isIndeterminate && 'animate-spin')}
|
|
208
|
+
style={{ width: size, height: size }}
|
|
209
|
+
>
|
|
210
|
+
<svg
|
|
211
|
+
width={size}
|
|
212
|
+
height={size}
|
|
213
|
+
viewBox={`0 0 ${size} ${size}`}
|
|
214
|
+
style={{ position: 'absolute' }}
|
|
215
|
+
>
|
|
216
|
+
{/* Background track */}
|
|
217
|
+
<circle
|
|
218
|
+
cx={center}
|
|
219
|
+
cy={center}
|
|
220
|
+
r={radius}
|
|
221
|
+
fill="none"
|
|
222
|
+
stroke="var(--color-border-default)"
|
|
223
|
+
strokeWidth={strokeWidth}
|
|
224
|
+
/>
|
|
225
|
+
{/* Progress arc */}
|
|
226
|
+
<circle
|
|
227
|
+
cx={center}
|
|
228
|
+
cy={center}
|
|
229
|
+
r={radius}
|
|
230
|
+
fill="none"
|
|
231
|
+
stroke={colorVarMap[color]}
|
|
232
|
+
strokeWidth={strokeWidth}
|
|
233
|
+
strokeLinecap="round"
|
|
234
|
+
strokeDasharray={circumference}
|
|
235
|
+
strokeDashoffset={isIndeterminate ? circumference * 0.75 : strokeDashoffset}
|
|
236
|
+
transform={`rotate(-90 ${center} ${center})`}
|
|
237
|
+
style={{ transition: 'stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)' }}
|
|
238
|
+
/>
|
|
239
|
+
</svg>
|
|
240
|
+
</View>
|
|
241
|
+
|
|
242
|
+
{/* Center value */}
|
|
243
|
+
{showValue && !isIndeterminate && (
|
|
244
|
+
<Text
|
|
245
|
+
className="text-xs font-semibold text-text-primary absolute"
|
|
246
|
+
style={{ fontSize: Math.max(size * 0.22, 10) }}
|
|
247
|
+
>
|
|
248
|
+
{formatValue(value, max)}
|
|
249
|
+
</Text>
|
|
250
|
+
)}
|
|
251
|
+
</View>
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface ProgressStepsProps extends ViewProps {
|
|
256
|
+
/** Current step (0-indexed) */
|
|
257
|
+
currentStep: number
|
|
258
|
+
/** Total number of steps */
|
|
259
|
+
totalSteps: number
|
|
260
|
+
/** Color of completed steps */
|
|
261
|
+
color?: ProgressColor
|
|
262
|
+
/** Size of step indicators */
|
|
263
|
+
size?: ProgressSize
|
|
264
|
+
/** Labels for each step */
|
|
265
|
+
labels?: string[]
|
|
266
|
+
/** Additional className */
|
|
267
|
+
className?: string
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const stepSizeStyles: Record<ProgressSize, { dot: string; connector: string }> = {
|
|
271
|
+
sm: { dot: 'w-2 h-2', connector: 'h-0.5' },
|
|
272
|
+
md: { dot: 'w-3 h-3', connector: 'h-1' },
|
|
273
|
+
lg: { dot: 'w-4 h-4', connector: 'h-1.5' },
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Step-based progress indicator.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* <ProgressSteps currentStep={2} totalSteps={4} labels={['Cart', 'Shipping', 'Payment', 'Done']} />
|
|
281
|
+
*/
|
|
282
|
+
export function ProgressSteps({
|
|
283
|
+
currentStep,
|
|
284
|
+
totalSteps,
|
|
285
|
+
color = 'primary',
|
|
286
|
+
size = 'md',
|
|
287
|
+
labels,
|
|
288
|
+
className,
|
|
289
|
+
...props
|
|
290
|
+
}: ProgressStepsProps) {
|
|
291
|
+
const { dot, connector } = stepSizeStyles[size]
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<View className={cn('w-full', className)} {...props}>
|
|
295
|
+
<View className="flex-row items-center">
|
|
296
|
+
{Array.from({ length: totalSteps }).map((_, index) => (
|
|
297
|
+
<React.Fragment key={index}>
|
|
298
|
+
{/* Step dot */}
|
|
299
|
+
<View
|
|
300
|
+
className={cn(
|
|
301
|
+
'rounded-full',
|
|
302
|
+
dot,
|
|
303
|
+
index <= currentStep ? colorStyles[color] : 'bg-border-strong'
|
|
304
|
+
)}
|
|
305
|
+
/>
|
|
306
|
+
|
|
307
|
+
{/* Connector line */}
|
|
308
|
+
{index < totalSteps - 1 && (
|
|
309
|
+
<View
|
|
310
|
+
className={cn(
|
|
311
|
+
'flex-1 mx-1',
|
|
312
|
+
connector,
|
|
313
|
+
index < currentStep ? colorStyles[color] : 'bg-border-strong'
|
|
314
|
+
)}
|
|
315
|
+
/>
|
|
316
|
+
)}
|
|
317
|
+
</React.Fragment>
|
|
318
|
+
))}
|
|
319
|
+
</View>
|
|
320
|
+
|
|
321
|
+
{/* Labels */}
|
|
322
|
+
{labels && labels.length > 0 && (
|
|
323
|
+
<View className="flex-row mt-2">
|
|
324
|
+
{labels.map((label, index) => (
|
|
325
|
+
<Text
|
|
326
|
+
key={index}
|
|
327
|
+
className={cn(
|
|
328
|
+
'flex-1 text-xs text-center',
|
|
329
|
+
index <= currentStep ? 'text-text-primary font-medium' : 'text-text-tertiary'
|
|
330
|
+
)}
|
|
331
|
+
>
|
|
332
|
+
{label}
|
|
333
|
+
</Text>
|
|
334
|
+
))}
|
|
335
|
+
</View>
|
|
336
|
+
)}
|
|
337
|
+
</View>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { View, Text } from 'react-native'
|
|
4
|
+
import { Radio, RadioGroup } from './Radio'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof RadioGroup> = {
|
|
7
|
+
title: 'Components/Radio',
|
|
8
|
+
component: RadioGroup,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
argTypes: {
|
|
11
|
+
size: {
|
|
12
|
+
control: 'select',
|
|
13
|
+
options: ['sm', 'md', 'lg'],
|
|
14
|
+
},
|
|
15
|
+
color: {
|
|
16
|
+
control: 'select',
|
|
17
|
+
options: ['primary', 'secondary', 'success', 'error'],
|
|
18
|
+
},
|
|
19
|
+
orientation: {
|
|
20
|
+
control: 'select',
|
|
21
|
+
options: ['horizontal', 'vertical'],
|
|
22
|
+
},
|
|
23
|
+
isDisabled: {
|
|
24
|
+
control: 'boolean',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default meta
|
|
30
|
+
type Story = StoryObj<typeof RadioGroup>
|
|
31
|
+
|
|
32
|
+
export const Default: Story = {
|
|
33
|
+
render: function Render() {
|
|
34
|
+
const [value, setValue] = useState<string | null>('option1')
|
|
35
|
+
return (
|
|
36
|
+
<RadioGroup value={value} onChange={setValue}>
|
|
37
|
+
<Radio value="option1">Option 1</Radio>
|
|
38
|
+
<Radio value="option2">Option 2</Radio>
|
|
39
|
+
<Radio value="option3">Option 3</Radio>
|
|
40
|
+
</RadioGroup>
|
|
41
|
+
)
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const WithLabel: Story = {
|
|
46
|
+
render: function Render() {
|
|
47
|
+
const [value, setValue] = useState<string | null>('email')
|
|
48
|
+
return (
|
|
49
|
+
<RadioGroup value={value} onChange={setValue} label="Notification Preference">
|
|
50
|
+
<Radio value="email">Email</Radio>
|
|
51
|
+
<Radio value="sms">SMS</Radio>
|
|
52
|
+
<Radio value="push">Push Notification</Radio>
|
|
53
|
+
<Radio value="none">None</Radio>
|
|
54
|
+
</RadioGroup>
|
|
55
|
+
)
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const Horizontal: Story = {
|
|
60
|
+
render: function Render() {
|
|
61
|
+
const [value, setValue] = useState<string | null>('monthly')
|
|
62
|
+
return (
|
|
63
|
+
<RadioGroup
|
|
64
|
+
value={value}
|
|
65
|
+
onChange={setValue}
|
|
66
|
+
orientation="horizontal"
|
|
67
|
+
label="Billing Cycle"
|
|
68
|
+
>
|
|
69
|
+
<Radio value="monthly">Monthly</Radio>
|
|
70
|
+
<Radio value="quarterly">Quarterly</Radio>
|
|
71
|
+
<Radio value="yearly">Yearly</Radio>
|
|
72
|
+
</RadioGroup>
|
|
73
|
+
)
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const AllSizes: Story = {
|
|
78
|
+
render: function Render() {
|
|
79
|
+
const [small, setSmall] = useState<string | null>('a')
|
|
80
|
+
const [medium, setMedium] = useState<string | null>('a')
|
|
81
|
+
const [large, setLarge] = useState<string | null>('a')
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<View className="gap-6">
|
|
85
|
+
<View>
|
|
86
|
+
<Text className="text-text-secondary text-sm mb-2">Small</Text>
|
|
87
|
+
<RadioGroup value={small} onChange={setSmall} size="sm" orientation="horizontal">
|
|
88
|
+
<Radio value="a">Option A</Radio>
|
|
89
|
+
<Radio value="b">Option B</Radio>
|
|
90
|
+
</RadioGroup>
|
|
91
|
+
</View>
|
|
92
|
+
<View>
|
|
93
|
+
<Text className="text-text-secondary text-sm mb-2">Medium (default)</Text>
|
|
94
|
+
<RadioGroup value={medium} onChange={setMedium} size="md" orientation="horizontal">
|
|
95
|
+
<Radio value="a">Option A</Radio>
|
|
96
|
+
<Radio value="b">Option B</Radio>
|
|
97
|
+
</RadioGroup>
|
|
98
|
+
</View>
|
|
99
|
+
<View>
|
|
100
|
+
<Text className="text-text-secondary text-sm mb-2">Large</Text>
|
|
101
|
+
<RadioGroup value={large} onChange={setLarge} size="lg" orientation="horizontal">
|
|
102
|
+
<Radio value="a">Option A</Radio>
|
|
103
|
+
<Radio value="b">Option B</Radio>
|
|
104
|
+
</RadioGroup>
|
|
105
|
+
</View>
|
|
106
|
+
</View>
|
|
107
|
+
)
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const AllColors: Story = {
|
|
112
|
+
render: function Render() {
|
|
113
|
+
const [primary, setPrimary] = useState<string | null>('a')
|
|
114
|
+
const [secondary, setSecondary] = useState<string | null>('a')
|
|
115
|
+
const [success, setSuccess] = useState<string | null>('a')
|
|
116
|
+
const [error, setError] = useState<string | null>('a')
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<View className="gap-6">
|
|
120
|
+
<RadioGroup value={primary} onChange={setPrimary} color="primary" orientation="horizontal" label="Primary">
|
|
121
|
+
<Radio value="a">Option A</Radio>
|
|
122
|
+
<Radio value="b">Option B</Radio>
|
|
123
|
+
</RadioGroup>
|
|
124
|
+
<RadioGroup value={secondary} onChange={setSecondary} color="secondary" orientation="horizontal" label="Secondary">
|
|
125
|
+
<Radio value="a">Option A</Radio>
|
|
126
|
+
<Radio value="b">Option B</Radio>
|
|
127
|
+
</RadioGroup>
|
|
128
|
+
<RadioGroup value={success} onChange={setSuccess} color="success" orientation="horizontal" label="Success">
|
|
129
|
+
<Radio value="a">Option A</Radio>
|
|
130
|
+
<Radio value="b">Option B</Radio>
|
|
131
|
+
</RadioGroup>
|
|
132
|
+
<RadioGroup value={error} onChange={setError} color="error" orientation="horizontal" label="Error">
|
|
133
|
+
<Radio value="a">Option A</Radio>
|
|
134
|
+
<Radio value="b">Option B</Radio>
|
|
135
|
+
</RadioGroup>
|
|
136
|
+
</View>
|
|
137
|
+
)
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const Disabled: Story = {
|
|
142
|
+
render: function Render() {
|
|
143
|
+
const [value, setValue] = useState<string | null>('option1')
|
|
144
|
+
return (
|
|
145
|
+
<View className="gap-6">
|
|
146
|
+
<View>
|
|
147
|
+
<Text className="text-text-secondary text-sm mb-2">Entire group disabled</Text>
|
|
148
|
+
<RadioGroup value={value} onChange={setValue} isDisabled>
|
|
149
|
+
<Radio value="option1">Option 1</Radio>
|
|
150
|
+
<Radio value="option2">Option 2</Radio>
|
|
151
|
+
</RadioGroup>
|
|
152
|
+
</View>
|
|
153
|
+
<View>
|
|
154
|
+
<Text className="text-text-secondary text-sm mb-2">Individual option disabled</Text>
|
|
155
|
+
<RadioGroup value={value} onChange={setValue}>
|
|
156
|
+
<Radio value="option1">Option 1 (enabled)</Radio>
|
|
157
|
+
<Radio value="option2" isDisabled>Option 2 (disabled)</Radio>
|
|
158
|
+
<Radio value="option3">Option 3 (enabled)</Radio>
|
|
159
|
+
</RadioGroup>
|
|
160
|
+
</View>
|
|
161
|
+
</View>
|
|
162
|
+
)
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const FormExample: Story = {
|
|
167
|
+
render: function Render() {
|
|
168
|
+
const [plan, setPlan] = useState<string | null>('pro')
|
|
169
|
+
const [billing, setBilling] = useState<string | null>('monthly')
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<View className="gap-6 p-4 bg-surface-base rounded-lg max-w-md">
|
|
173
|
+
<Text className="text-lg font-semibold text-text-primary">Subscription Settings</Text>
|
|
174
|
+
|
|
175
|
+
<RadioGroup value={plan} onChange={setPlan} label="Select Plan">
|
|
176
|
+
<Radio value="free">Free - Basic features</Radio>
|
|
177
|
+
<Radio value="pro">Pro - Advanced features</Radio>
|
|
178
|
+
<Radio value="enterprise">Enterprise - Custom solutions</Radio>
|
|
179
|
+
</RadioGroup>
|
|
180
|
+
|
|
181
|
+
<RadioGroup
|
|
182
|
+
value={billing}
|
|
183
|
+
onChange={setBilling}
|
|
184
|
+
orientation="horizontal"
|
|
185
|
+
label="Billing Frequency"
|
|
186
|
+
>
|
|
187
|
+
<Radio value="monthly">Monthly</Radio>
|
|
188
|
+
<Radio value="yearly">Yearly (save 20%)</Radio>
|
|
189
|
+
</RadioGroup>
|
|
190
|
+
|
|
191
|
+
<View className="pt-4 border-t border-border-default">
|
|
192
|
+
<Text className="text-sm text-text-secondary">
|
|
193
|
+
Selected: {plan} plan, billed {billing}
|
|
194
|
+
</Text>
|
|
195
|
+
</View>
|
|
196
|
+
</View>
|
|
197
|
+
)
|
|
198
|
+
},
|
|
199
|
+
}
|