@titan-design/react-ui 0.1.0 → 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/package.json +10 -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
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import { Pressable, Text, View, ActivityIndicator, type PressableProps } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
import { semanticColorsDark } from '../../../theme/tokens/semantic'
|
|
5
|
+
|
|
6
|
+
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'link'
|
|
7
|
+
export type ButtonSize = 'sm' | 'md' | 'lg'
|
|
8
|
+
export type ButtonColor = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info'
|
|
9
|
+
|
|
10
|
+
export interface ButtonProps extends Omit<PressableProps, 'children'> {
|
|
11
|
+
/** Visual style variant */
|
|
12
|
+
variant?: ButtonVariant
|
|
13
|
+
/** Size of the button */
|
|
14
|
+
size?: ButtonSize
|
|
15
|
+
/** Color scheme */
|
|
16
|
+
color?: ButtonColor
|
|
17
|
+
/** Whether this is an icon-only button (square aspect ratio) */
|
|
18
|
+
isIconButton?: boolean
|
|
19
|
+
/** Whether the button is disabled */
|
|
20
|
+
isDisabled?: boolean
|
|
21
|
+
/** Whether the button is in a loading state */
|
|
22
|
+
isLoading?: boolean
|
|
23
|
+
/** Loading text to display */
|
|
24
|
+
loadingText?: string
|
|
25
|
+
/** Whether the button should take full width */
|
|
26
|
+
fullWidth?: boolean
|
|
27
|
+
/** Additional className for styling */
|
|
28
|
+
className?: string
|
|
29
|
+
/** Button content */
|
|
30
|
+
children?: React.ReactNode
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const variantStyles: Record<ButtonVariant, Record<ButtonColor, string>> = {
|
|
34
|
+
solid: {
|
|
35
|
+
primary: 'bg-brand-primary active:bg-brand-primary-dark active:scale-[0.98] web:hover:bg-brand-primary-dark web:active:scale-[0.98]',
|
|
36
|
+
secondary: 'bg-brand-secondary active:bg-brand-secondary-dark active:scale-[0.98] web:hover:bg-brand-secondary-dark web:active:scale-[0.98]',
|
|
37
|
+
success: 'bg-status-success active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
|
|
38
|
+
error: 'bg-status-error active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
|
|
39
|
+
warning: 'bg-status-warning active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
|
|
40
|
+
info: 'bg-status-info active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
|
|
41
|
+
},
|
|
42
|
+
outline: {
|
|
43
|
+
primary: 'border-2 border-brand-primary bg-transparent active:bg-brand-primary-subtle active:scale-[0.98] web:hover:bg-brand-primary-subtle web:active:scale-[0.98]',
|
|
44
|
+
secondary: 'border-2 border-brand-secondary bg-transparent active:bg-brand-secondary-subtle active:scale-[0.98] web:hover:bg-brand-secondary-subtle web:active:scale-[0.98]',
|
|
45
|
+
success: 'border-2 border-status-success bg-transparent active:bg-status-success-subtle active:scale-[0.98] web:hover:bg-status-success-subtle web:active:scale-[0.98]',
|
|
46
|
+
error: 'border-2 border-status-error bg-transparent active:bg-status-error-subtle active:scale-[0.98] web:hover:bg-status-error-subtle web:active:scale-[0.98]',
|
|
47
|
+
warning: 'border-2 border-status-warning bg-transparent active:bg-status-warning-subtle active:scale-[0.98] web:hover:bg-status-warning-subtle web:active:scale-[0.98]',
|
|
48
|
+
info: 'border-2 border-status-info bg-transparent active:bg-status-info-subtle active:scale-[0.98] web:hover:bg-status-info-subtle web:active:scale-[0.98]',
|
|
49
|
+
},
|
|
50
|
+
ghost: {
|
|
51
|
+
primary: 'bg-transparent active:bg-brand-primary-subtle active:scale-[0.98] web:hover:bg-brand-primary-subtle web:active:scale-[0.98]',
|
|
52
|
+
secondary: 'bg-transparent active:bg-brand-secondary-subtle active:scale-[0.98] web:hover:bg-brand-secondary-subtle web:active:scale-[0.98]',
|
|
53
|
+
success: 'bg-transparent active:bg-status-success-subtle active:scale-[0.98] web:hover:bg-status-success-subtle web:active:scale-[0.98]',
|
|
54
|
+
error: 'bg-transparent active:bg-status-error-subtle active:scale-[0.98] web:hover:bg-status-error-subtle web:active:scale-[0.98]',
|
|
55
|
+
warning: 'bg-transparent active:bg-status-warning-subtle active:scale-[0.98] web:hover:bg-status-warning-subtle web:active:scale-[0.98]',
|
|
56
|
+
info: 'bg-transparent active:bg-status-info-subtle active:scale-[0.98] web:hover:bg-status-info-subtle web:active:scale-[0.98]',
|
|
57
|
+
},
|
|
58
|
+
link: {
|
|
59
|
+
primary: 'bg-transparent',
|
|
60
|
+
secondary: 'bg-transparent',
|
|
61
|
+
success: 'bg-transparent',
|
|
62
|
+
error: 'bg-transparent',
|
|
63
|
+
warning: 'bg-transparent',
|
|
64
|
+
info: 'bg-transparent',
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const textStyles: Record<ButtonVariant, Record<ButtonColor, string>> = {
|
|
69
|
+
solid: {
|
|
70
|
+
primary: 'text-white',
|
|
71
|
+
secondary: 'text-white',
|
|
72
|
+
success: 'text-white',
|
|
73
|
+
error: 'text-white',
|
|
74
|
+
warning: 'text-white',
|
|
75
|
+
info: 'text-white',
|
|
76
|
+
},
|
|
77
|
+
outline: {
|
|
78
|
+
primary: 'text-brand-primary',
|
|
79
|
+
secondary: 'text-brand-secondary',
|
|
80
|
+
success: 'text-status-success',
|
|
81
|
+
error: 'text-status-error',
|
|
82
|
+
warning: 'text-status-warning',
|
|
83
|
+
info: 'text-status-info',
|
|
84
|
+
},
|
|
85
|
+
ghost: {
|
|
86
|
+
primary: 'text-brand-primary',
|
|
87
|
+
secondary: 'text-brand-secondary',
|
|
88
|
+
success: 'text-status-success',
|
|
89
|
+
error: 'text-status-error',
|
|
90
|
+
warning: 'text-status-warning',
|
|
91
|
+
info: 'text-status-info',
|
|
92
|
+
},
|
|
93
|
+
link: {
|
|
94
|
+
primary: 'text-brand-primary web:hover:underline',
|
|
95
|
+
secondary: 'text-brand-secondary web:hover:underline',
|
|
96
|
+
success: 'text-status-success web:hover:underline',
|
|
97
|
+
error: 'text-status-error web:hover:underline',
|
|
98
|
+
warning: 'text-status-warning web:hover:underline',
|
|
99
|
+
info: 'text-status-info web:hover:underline',
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const sizeStyles: Record<ButtonSize, string> = {
|
|
104
|
+
sm: 'px-4 py-1.5 min-h-[32px]',
|
|
105
|
+
md: 'px-5 py-2 min-h-[40px]',
|
|
106
|
+
lg: 'px-6 py-2.5 min-h-[48px]',
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Icon button sizes (square aspect ratio)
|
|
110
|
+
const iconButtonSizeStyles: Record<ButtonSize, string> = {
|
|
111
|
+
sm: 'w-8 h-8 p-0',
|
|
112
|
+
md: 'w-10 h-10 p-0',
|
|
113
|
+
lg: 'w-12 h-12 p-0',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const textSizeStyles: Record<ButtonSize, string> = {
|
|
117
|
+
sm: 'text-sm',
|
|
118
|
+
md: 'text-sm',
|
|
119
|
+
lg: 'text-base',
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Button component following compound component pattern.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* <Button variant="solid" color="primary">
|
|
127
|
+
* <ButtonText>Click me</ButtonText>
|
|
128
|
+
* </Button>
|
|
129
|
+
*
|
|
130
|
+
* // Or with icon
|
|
131
|
+
* <Button variant="outline" color="secondary">
|
|
132
|
+
* <ButtonIcon as={PlusIcon} />
|
|
133
|
+
* <ButtonText>Add item</ButtonText>
|
|
134
|
+
* </Button>
|
|
135
|
+
*/
|
|
136
|
+
export const Button = forwardRef<View, ButtonProps>(function Button(
|
|
137
|
+
{
|
|
138
|
+
variant = 'solid',
|
|
139
|
+
size = 'md',
|
|
140
|
+
color = 'primary',
|
|
141
|
+
isIconButton = false,
|
|
142
|
+
isDisabled = false,
|
|
143
|
+
isLoading = false,
|
|
144
|
+
loadingText,
|
|
145
|
+
fullWidth = false,
|
|
146
|
+
className,
|
|
147
|
+
children,
|
|
148
|
+
...props
|
|
149
|
+
},
|
|
150
|
+
ref
|
|
151
|
+
) {
|
|
152
|
+
const disabled = isDisabled || isLoading
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Pressable
|
|
156
|
+
ref={ref}
|
|
157
|
+
disabled={disabled}
|
|
158
|
+
accessibilityRole="button"
|
|
159
|
+
accessibilityState={{ disabled }}
|
|
160
|
+
className={cn(
|
|
161
|
+
// Base styles
|
|
162
|
+
'flex-row items-center justify-center rounded-md transition-all duration-150',
|
|
163
|
+
// Variant + color styles
|
|
164
|
+
variantStyles[variant][color],
|
|
165
|
+
// Size styles (icon button vs regular)
|
|
166
|
+
isIconButton ? iconButtonSizeStyles[size] : sizeStyles[size],
|
|
167
|
+
// Full width
|
|
168
|
+
fullWidth && 'w-full',
|
|
169
|
+
// Disabled styles - more refined
|
|
170
|
+
disabled && 'opacity-40 cursor-not-allowed active:scale-100 web:active:scale-100',
|
|
171
|
+
// Link variant has no padding
|
|
172
|
+
variant === 'link' && 'px-0 py-0 min-h-0',
|
|
173
|
+
className
|
|
174
|
+
)}
|
|
175
|
+
{...props}
|
|
176
|
+
>
|
|
177
|
+
{isLoading && (
|
|
178
|
+
<ActivityIndicator
|
|
179
|
+
size="small"
|
|
180
|
+
color={variant === 'solid' ? semanticColorsDark['on-brand-primary'] : undefined}
|
|
181
|
+
className="mr-2"
|
|
182
|
+
/>
|
|
183
|
+
)}
|
|
184
|
+
{isLoading && loadingText ? (
|
|
185
|
+
<Text
|
|
186
|
+
className={cn(
|
|
187
|
+
'font-semibold',
|
|
188
|
+
textSizeStyles[size],
|
|
189
|
+
textStyles[variant][color]
|
|
190
|
+
)}
|
|
191
|
+
>
|
|
192
|
+
{loadingText}
|
|
193
|
+
</Text>
|
|
194
|
+
) : (
|
|
195
|
+
children
|
|
196
|
+
)}
|
|
197
|
+
</Pressable>
|
|
198
|
+
)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
export interface ButtonTextProps {
|
|
202
|
+
children: React.ReactNode
|
|
203
|
+
className?: string
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Text component for Button. Use inside Button for consistent styling.
|
|
208
|
+
*/
|
|
209
|
+
export function ButtonText({ children, className }: ButtonTextProps) {
|
|
210
|
+
return (
|
|
211
|
+
<Text className={cn('font-semibold text-inherit', className)}>
|
|
212
|
+
{children}
|
|
213
|
+
</Text>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface ButtonIconProps {
|
|
218
|
+
/** Icon component to render */
|
|
219
|
+
as: React.ComponentType<{ size?: number; color?: string; className?: string }>
|
|
220
|
+
/** Icon size */
|
|
221
|
+
size?: number
|
|
222
|
+
/** Additional className */
|
|
223
|
+
className?: string
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Icon component for Button. Use inside Button for icons.
|
|
228
|
+
*/
|
|
229
|
+
export function ButtonIcon({ as: Icon, size = 16, className }: ButtonIconProps) {
|
|
230
|
+
return <Icon size={size} className={cn('text-inherit', className)} />
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface ButtonSpinnerProps {
|
|
234
|
+
className?: string
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Spinner component for Button loading state.
|
|
239
|
+
*/
|
|
240
|
+
export function ButtonSpinner({ className }: ButtonSpinnerProps) {
|
|
241
|
+
return <ActivityIndicator size="small" className={cn('text-inherit', className)} />
|
|
242
|
+
}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardTitle,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardFooter,
|
|
10
|
+
CardSkeleton,
|
|
11
|
+
CardInset,
|
|
12
|
+
} from './Card'
|
|
13
|
+
import { Button, ButtonText } from '../button'
|
|
14
|
+
|
|
15
|
+
const meta: Meta<typeof Card> = {
|
|
16
|
+
title: 'Components/Card',
|
|
17
|
+
component: Card,
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
argTypes: {
|
|
20
|
+
variant: {
|
|
21
|
+
control: 'select',
|
|
22
|
+
options: ['elevated', 'outline', 'filled'],
|
|
23
|
+
},
|
|
24
|
+
elevation: {
|
|
25
|
+
control: 'select',
|
|
26
|
+
options: [1, 2, 3],
|
|
27
|
+
description: 'Elevation level (1=subtle, 2=standard, 3=prominent)',
|
|
28
|
+
},
|
|
29
|
+
isInteractive: { control: 'boolean' },
|
|
30
|
+
isLoading: { control: 'boolean' },
|
|
31
|
+
borderColor: { control: 'color' },
|
|
32
|
+
bgColor: { control: 'color' },
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default meta
|
|
37
|
+
type Story = StoryObj<typeof Card>
|
|
38
|
+
|
|
39
|
+
export const Default: Story = {
|
|
40
|
+
render: (args) => (
|
|
41
|
+
<Card {...args} style={{ width: 320 }}>
|
|
42
|
+
<CardHeader>
|
|
43
|
+
<CardTitle>Card Title</CardTitle>
|
|
44
|
+
<CardDescription>Card description goes here</CardDescription>
|
|
45
|
+
</CardHeader>
|
|
46
|
+
<CardContent>
|
|
47
|
+
<Text className="text-text-secondary">
|
|
48
|
+
This is the card content. You can put any content here.
|
|
49
|
+
</Text>
|
|
50
|
+
</CardContent>
|
|
51
|
+
<CardFooter>
|
|
52
|
+
<Button variant="ghost" size="sm">
|
|
53
|
+
<ButtonText>Cancel</ButtonText>
|
|
54
|
+
</Button>
|
|
55
|
+
<Button size="sm">
|
|
56
|
+
<ButtonText>Save</ButtonText>
|
|
57
|
+
</Button>
|
|
58
|
+
</CardFooter>
|
|
59
|
+
</Card>
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const Elevated: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
variant: 'elevated',
|
|
66
|
+
},
|
|
67
|
+
render: (args) => (
|
|
68
|
+
<Card {...args} style={{ width: 320 }}>
|
|
69
|
+
<CardContent>
|
|
70
|
+
<Text className="text-text-primary">Elevated card with elevation</Text>
|
|
71
|
+
</CardContent>
|
|
72
|
+
</Card>
|
|
73
|
+
),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const Outline: Story = {
|
|
77
|
+
args: {
|
|
78
|
+
variant: 'outline',
|
|
79
|
+
},
|
|
80
|
+
render: (args) => (
|
|
81
|
+
<Card {...args} style={{ width: 320 }}>
|
|
82
|
+
<CardContent>
|
|
83
|
+
<Text className="text-text-primary">Outlined card with border</Text>
|
|
84
|
+
</CardContent>
|
|
85
|
+
</Card>
|
|
86
|
+
),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const Filled: Story = {
|
|
90
|
+
args: {
|
|
91
|
+
variant: 'filled',
|
|
92
|
+
},
|
|
93
|
+
render: (args) => (
|
|
94
|
+
<Card {...args} style={{ width: 320 }}>
|
|
95
|
+
<CardContent>
|
|
96
|
+
<Text className="text-text-primary">Filled card with background</Text>
|
|
97
|
+
</CardContent>
|
|
98
|
+
</Card>
|
|
99
|
+
),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const Interactive: Story = {
|
|
103
|
+
args: {
|
|
104
|
+
isInteractive: true,
|
|
105
|
+
},
|
|
106
|
+
render: (args) => (
|
|
107
|
+
<Card {...args} style={{ width: 320 }}>
|
|
108
|
+
<CardHeader>
|
|
109
|
+
<CardTitle>Interactive Card</CardTitle>
|
|
110
|
+
<CardDescription>Hover to see the effect</CardDescription>
|
|
111
|
+
</CardHeader>
|
|
112
|
+
<CardContent>
|
|
113
|
+
<Text className="text-text-secondary">
|
|
114
|
+
This card has hover effects when isInteractive is true.
|
|
115
|
+
</Text>
|
|
116
|
+
</CardContent>
|
|
117
|
+
</Card>
|
|
118
|
+
),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const SimpleCard: Story = {
|
|
122
|
+
render: () => (
|
|
123
|
+
<Card style={{ width: 320 }}>
|
|
124
|
+
<CardContent>
|
|
125
|
+
<Text className="text-text-primary">Simple card with just content</Text>
|
|
126
|
+
</CardContent>
|
|
127
|
+
</Card>
|
|
128
|
+
),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const ElevationLevels: Story = {
|
|
132
|
+
render: () => (
|
|
133
|
+
<View style={{ flexDirection: 'row', gap: 16, flexWrap: 'wrap' }}>
|
|
134
|
+
<Card elevation={1} style={{ width: 140 }}>
|
|
135
|
+
<CardContent>
|
|
136
|
+
<Text className="text-text-primary font-semibold">Subtle (1)</Text>
|
|
137
|
+
</CardContent>
|
|
138
|
+
</Card>
|
|
139
|
+
<Card elevation={2} style={{ width: 140 }}>
|
|
140
|
+
<CardContent>
|
|
141
|
+
<Text className="text-text-primary font-semibold">Standard (2)</Text>
|
|
142
|
+
</CardContent>
|
|
143
|
+
</Card>
|
|
144
|
+
<Card elevation={3} style={{ width: 140 }}>
|
|
145
|
+
<CardContent>
|
|
146
|
+
<Text className="text-text-primary font-semibold">Prominent (3)</Text>
|
|
147
|
+
</CardContent>
|
|
148
|
+
</Card>
|
|
149
|
+
</View>
|
|
150
|
+
),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const Clickable: Story = {
|
|
154
|
+
render: () => (
|
|
155
|
+
<Card
|
|
156
|
+
isInteractive
|
|
157
|
+
onPress={() => console.log('Card clicked!')}
|
|
158
|
+
style={{ width: 320 }}
|
|
159
|
+
>
|
|
160
|
+
<CardHeader>
|
|
161
|
+
<CardTitle>Clickable Card</CardTitle>
|
|
162
|
+
<CardDescription>Click anywhere on this card</CardDescription>
|
|
163
|
+
</CardHeader>
|
|
164
|
+
<CardContent>
|
|
165
|
+
<Text className="text-text-secondary">
|
|
166
|
+
This card has onPress handler and shows hover/press feedback.
|
|
167
|
+
</Text>
|
|
168
|
+
</CardContent>
|
|
169
|
+
</Card>
|
|
170
|
+
),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const Loading: Story = {
|
|
174
|
+
args: {
|
|
175
|
+
isLoading: true,
|
|
176
|
+
},
|
|
177
|
+
render: (args) => (
|
|
178
|
+
<Card {...args} style={{ width: 320 }}>
|
|
179
|
+
<CardHeader>
|
|
180
|
+
<CardTitle>Loading Card</CardTitle>
|
|
181
|
+
<CardDescription>Content is loading...</CardDescription>
|
|
182
|
+
</CardHeader>
|
|
183
|
+
<CardContent>
|
|
184
|
+
<Text className="text-text-secondary">
|
|
185
|
+
This content is hidden behind a loading spinner.
|
|
186
|
+
</Text>
|
|
187
|
+
</CardContent>
|
|
188
|
+
</Card>
|
|
189
|
+
),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const Skeleton: Story = {
|
|
193
|
+
render: () => (
|
|
194
|
+
<View style={{ gap: 16 }}>
|
|
195
|
+
<CardSkeleton style={{ width: 320 }} />
|
|
196
|
+
<CardSkeleton hasHeader hasFooter style={{ width: 320 }} />
|
|
197
|
+
<CardSkeleton hasHeader={false} contentLines={2} style={{ width: 320 }} />
|
|
198
|
+
</View>
|
|
199
|
+
),
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const InsetElements: Story = {
|
|
203
|
+
render: () => (
|
|
204
|
+
<View style={{ gap: 16 }}>
|
|
205
|
+
<Card elevation={2} style={{ width: 320 }}>
|
|
206
|
+
<CardHeader>
|
|
207
|
+
<CardTitle>Card with Inset</CardTitle>
|
|
208
|
+
<CardDescription>Demonstrating inset elements within cards</CardDescription>
|
|
209
|
+
</CardHeader>
|
|
210
|
+
<CardContent>
|
|
211
|
+
<CardInset elevation={-1} className="p-4">
|
|
212
|
+
<Text className="text-text-secondary text-sm">
|
|
213
|
+
Shallow inset element (elevation -1)
|
|
214
|
+
</Text>
|
|
215
|
+
</CardInset>
|
|
216
|
+
</CardContent>
|
|
217
|
+
</Card>
|
|
218
|
+
|
|
219
|
+
<Card elevation={3} style={{ width: 320 }}>
|
|
220
|
+
<CardHeader>
|
|
221
|
+
<CardTitle>Deep Inset</CardTitle>
|
|
222
|
+
</CardHeader>
|
|
223
|
+
<CardContent>
|
|
224
|
+
<CardInset elevation={-2} className="p-4">
|
|
225
|
+
<Text className="text-text-secondary text-sm">
|
|
226
|
+
Deep inset element (elevation -2)
|
|
227
|
+
</Text>
|
|
228
|
+
</CardInset>
|
|
229
|
+
</CardContent>
|
|
230
|
+
</Card>
|
|
231
|
+
|
|
232
|
+
<Card elevation={2} style={{ width: 320 }}>
|
|
233
|
+
<CardHeader>
|
|
234
|
+
<CardTitle>Nested Insets</CardTitle>
|
|
235
|
+
</CardHeader>
|
|
236
|
+
<CardContent>
|
|
237
|
+
<CardInset elevation={-1} className="p-4 mb-4">
|
|
238
|
+
<Text className="text-text-secondary text-sm mb-2">
|
|
239
|
+
Outer inset (elevation -1)
|
|
240
|
+
</Text>
|
|
241
|
+
<CardInset elevation={-2} className="p-3">
|
|
242
|
+
<Text className="text-text-secondary text-xs">
|
|
243
|
+
Inner inset (elevation -2)
|
|
244
|
+
</Text>
|
|
245
|
+
</CardInset>
|
|
246
|
+
</CardInset>
|
|
247
|
+
</CardContent>
|
|
248
|
+
</Card>
|
|
249
|
+
</View>
|
|
250
|
+
),
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Status colors from the theme (using CSS custom properties for theme compliance)
|
|
254
|
+
const statusColors = {
|
|
255
|
+
success: 'var(--color-status-success)',
|
|
256
|
+
successSubtle: 'var(--color-status-success-subtle)',
|
|
257
|
+
error: 'var(--color-status-error)',
|
|
258
|
+
errorSubtle: 'var(--color-status-error-subtle)',
|
|
259
|
+
warning: 'var(--color-status-warning)',
|
|
260
|
+
warningSubtle: 'var(--color-status-warning-subtle)',
|
|
261
|
+
info: 'var(--color-status-info)',
|
|
262
|
+
infoSubtle: 'var(--color-status-info-subtle)',
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export const StatusOutlineCards: Story = {
|
|
266
|
+
render: () => (
|
|
267
|
+
<View style={{ gap: 16 }}>
|
|
268
|
+
<Text className="text-lg font-semibold text-text-primary mb-2">
|
|
269
|
+
Status Cards with Custom Border Colors
|
|
270
|
+
</Text>
|
|
271
|
+
|
|
272
|
+
<Card variant="outline" borderColor={statusColors.success} style={{ width: 320 }}>
|
|
273
|
+
<CardHeader>
|
|
274
|
+
<CardTitle>Success</CardTitle>
|
|
275
|
+
<CardDescription>Operation completed successfully</CardDescription>
|
|
276
|
+
</CardHeader>
|
|
277
|
+
<CardContent>
|
|
278
|
+
<Text className="text-text-secondary">
|
|
279
|
+
Your changes have been saved.
|
|
280
|
+
</Text>
|
|
281
|
+
</CardContent>
|
|
282
|
+
</Card>
|
|
283
|
+
|
|
284
|
+
<Card variant="outline" borderColor={statusColors.error} style={{ width: 320 }}>
|
|
285
|
+
<CardHeader>
|
|
286
|
+
<CardTitle>Error</CardTitle>
|
|
287
|
+
<CardDescription>Something went wrong</CardDescription>
|
|
288
|
+
</CardHeader>
|
|
289
|
+
<CardContent>
|
|
290
|
+
<Text className="text-text-secondary">
|
|
291
|
+
Please try again or contact support.
|
|
292
|
+
</Text>
|
|
293
|
+
</CardContent>
|
|
294
|
+
</Card>
|
|
295
|
+
|
|
296
|
+
<Card variant="outline" borderColor={statusColors.warning} style={{ width: 320 }}>
|
|
297
|
+
<CardHeader>
|
|
298
|
+
<CardTitle>Warning</CardTitle>
|
|
299
|
+
<CardDescription>Proceed with caution</CardDescription>
|
|
300
|
+
</CardHeader>
|
|
301
|
+
<CardContent>
|
|
302
|
+
<Text className="text-text-secondary">
|
|
303
|
+
This action cannot be undone.
|
|
304
|
+
</Text>
|
|
305
|
+
</CardContent>
|
|
306
|
+
</Card>
|
|
307
|
+
|
|
308
|
+
<Card variant="outline" borderColor={statusColors.info} style={{ width: 320 }}>
|
|
309
|
+
<CardHeader>
|
|
310
|
+
<CardTitle>Information</CardTitle>
|
|
311
|
+
<CardDescription>Good to know</CardDescription>
|
|
312
|
+
</CardHeader>
|
|
313
|
+
<CardContent>
|
|
314
|
+
<Text className="text-text-secondary">
|
|
315
|
+
New features are available in this release.
|
|
316
|
+
</Text>
|
|
317
|
+
</CardContent>
|
|
318
|
+
</Card>
|
|
319
|
+
</View>
|
|
320
|
+
),
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export const StatusFilledCards: Story = {
|
|
324
|
+
render: () => (
|
|
325
|
+
<View style={{ gap: 16 }}>
|
|
326
|
+
<Text className="text-lg font-semibold text-text-primary mb-2">
|
|
327
|
+
Status Cards with Custom Background Colors
|
|
328
|
+
</Text>
|
|
329
|
+
|
|
330
|
+
<Card
|
|
331
|
+
variant="outline"
|
|
332
|
+
borderColor={statusColors.success}
|
|
333
|
+
bgColor={statusColors.successSubtle}
|
|
334
|
+
style={{ width: 320 }}
|
|
335
|
+
>
|
|
336
|
+
<CardHeader>
|
|
337
|
+
<CardTitle>Success</CardTitle>
|
|
338
|
+
<CardDescription>Operation completed successfully</CardDescription>
|
|
339
|
+
</CardHeader>
|
|
340
|
+
<CardContent>
|
|
341
|
+
<Text className="text-text-secondary">
|
|
342
|
+
Your changes have been saved.
|
|
343
|
+
</Text>
|
|
344
|
+
</CardContent>
|
|
345
|
+
</Card>
|
|
346
|
+
|
|
347
|
+
<Card
|
|
348
|
+
variant="outline"
|
|
349
|
+
borderColor={statusColors.error}
|
|
350
|
+
bgColor={statusColors.errorSubtle}
|
|
351
|
+
style={{ width: 320 }}
|
|
352
|
+
>
|
|
353
|
+
<CardHeader>
|
|
354
|
+
<CardTitle>Error</CardTitle>
|
|
355
|
+
<CardDescription>Something went wrong</CardDescription>
|
|
356
|
+
</CardHeader>
|
|
357
|
+
<CardContent>
|
|
358
|
+
<Text className="text-text-secondary">
|
|
359
|
+
Please try again or contact support.
|
|
360
|
+
</Text>
|
|
361
|
+
</CardContent>
|
|
362
|
+
</Card>
|
|
363
|
+
|
|
364
|
+
<Card
|
|
365
|
+
variant="outline"
|
|
366
|
+
borderColor={statusColors.warning}
|
|
367
|
+
bgColor={statusColors.warningSubtle}
|
|
368
|
+
style={{ width: 320 }}
|
|
369
|
+
>
|
|
370
|
+
<CardHeader>
|
|
371
|
+
<CardTitle>Warning</CardTitle>
|
|
372
|
+
<CardDescription>Proceed with caution</CardDescription>
|
|
373
|
+
</CardHeader>
|
|
374
|
+
<CardContent>
|
|
375
|
+
<Text className="text-text-secondary">
|
|
376
|
+
This action cannot be undone.
|
|
377
|
+
</Text>
|
|
378
|
+
</CardContent>
|
|
379
|
+
</Card>
|
|
380
|
+
|
|
381
|
+
<Card
|
|
382
|
+
variant="outline"
|
|
383
|
+
borderColor={statusColors.info}
|
|
384
|
+
bgColor={statusColors.infoSubtle}
|
|
385
|
+
style={{ width: 320 }}
|
|
386
|
+
>
|
|
387
|
+
<CardHeader>
|
|
388
|
+
<CardTitle>Information</CardTitle>
|
|
389
|
+
<CardDescription>Good to know</CardDescription>
|
|
390
|
+
</CardHeader>
|
|
391
|
+
<CardContent>
|
|
392
|
+
<Text className="text-text-secondary">
|
|
393
|
+
New features are available in this release.
|
|
394
|
+
</Text>
|
|
395
|
+
</CardContent>
|
|
396
|
+
</Card>
|
|
397
|
+
</View>
|
|
398
|
+
),
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export const BrandColoredCards: Story = {
|
|
402
|
+
render: () => (
|
|
403
|
+
<View style={{ gap: 16 }}>
|
|
404
|
+
<Text className="text-lg font-semibold text-text-primary mb-2">
|
|
405
|
+
Brand Colored Cards
|
|
406
|
+
</Text>
|
|
407
|
+
|
|
408
|
+
<Card
|
|
409
|
+
variant="outline"
|
|
410
|
+
borderColor="var(--color-brand-primary)"
|
|
411
|
+
bgColor="var(--color-brand-primary-subtle)"
|
|
412
|
+
style={{ width: 320 }}
|
|
413
|
+
>
|
|
414
|
+
<CardHeader>
|
|
415
|
+
<CardTitle>Primary Brand</CardTitle>
|
|
416
|
+
<CardDescription>Orange brand color</CardDescription>
|
|
417
|
+
</CardHeader>
|
|
418
|
+
<CardContent>
|
|
419
|
+
<Text className="text-text-secondary">
|
|
420
|
+
Card with primary brand color styling.
|
|
421
|
+
</Text>
|
|
422
|
+
</CardContent>
|
|
423
|
+
</Card>
|
|
424
|
+
|
|
425
|
+
<Card
|
|
426
|
+
variant="outline"
|
|
427
|
+
borderColor="var(--color-brand-secondary)"
|
|
428
|
+
bgColor="var(--color-brand-secondary-subtle)"
|
|
429
|
+
style={{ width: 320 }}
|
|
430
|
+
>
|
|
431
|
+
<CardHeader>
|
|
432
|
+
<CardTitle>Secondary Brand</CardTitle>
|
|
433
|
+
<CardDescription>Steel brand color</CardDescription>
|
|
434
|
+
</CardHeader>
|
|
435
|
+
<CardContent>
|
|
436
|
+
<Text className="text-text-secondary">
|
|
437
|
+
Card with secondary brand color styling.
|
|
438
|
+
</Text>
|
|
439
|
+
</CardContent>
|
|
440
|
+
</Card>
|
|
441
|
+
</View>
|
|
442
|
+
),
|
|
443
|
+
}
|