@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.
Files changed (192) hide show
  1. package/package.json +10 -5
  2. package/src/components/custom/DateTime/DateTime.stories.tsx +180 -0
  3. package/src/components/custom/DateTime/DateTime.test.tsx +152 -0
  4. package/src/components/custom/DateTime/DateTime.tsx +235 -0
  5. package/src/components/custom/DateTime/index.ts +2 -0
  6. package/src/components/custom/EmptyState/EmptyState.stories.tsx +99 -0
  7. package/src/components/custom/EmptyState/EmptyState.test.tsx +106 -0
  8. package/src/components/custom/EmptyState/EmptyState.tsx +68 -0
  9. package/src/components/custom/EmptyState/index.ts +2 -0
  10. package/src/components/custom/Metric/Metric.stories.tsx +133 -0
  11. package/src/components/custom/Metric/Metric.test.tsx +120 -0
  12. package/src/components/custom/Metric/Metric.tsx +92 -0
  13. package/src/components/custom/Metric/index.ts +2 -0
  14. package/src/components/custom/Sidebar/Sidebar.stories.tsx +227 -0
  15. package/src/components/custom/Sidebar/Sidebar.test.tsx +185 -0
  16. package/src/components/custom/Sidebar/Sidebar.tsx +257 -0
  17. package/src/components/custom/Sidebar/index.ts +18 -0
  18. package/src/components/custom/Table/Table.stories.tsx +481 -0
  19. package/src/components/custom/Table/Table.test.tsx +531 -0
  20. package/src/components/custom/Table/Table.tsx +696 -0
  21. package/src/components/custom/Table/index.ts +22 -0
  22. package/src/components/custom/Typography/Typography.stories.tsx +153 -0
  23. package/src/components/custom/Typography/Typography.test.tsx +107 -0
  24. package/src/components/custom/Typography/Typography.tsx +218 -0
  25. package/src/components/custom/Typography/index.ts +19 -0
  26. package/src/components/custom/index.ts +8 -0
  27. package/src/components/custom/stepper/Stepper.stories.tsx +245 -0
  28. package/src/components/custom/stepper/Stepper.test.tsx +230 -0
  29. package/src/components/custom/stepper/Stepper.tsx +250 -0
  30. package/src/components/custom/stepper/index.ts +10 -0
  31. package/src/components/index.ts +5 -0
  32. package/src/components/ui/alert/Alert.stories.tsx +154 -0
  33. package/src/components/ui/alert/Alert.test.tsx +202 -0
  34. package/src/components/ui/alert/Alert.tsx +170 -0
  35. package/src/components/ui/alert/index.ts +8 -0
  36. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +299 -0
  37. package/src/components/ui/autocomplete/Autocomplete.test.tsx +247 -0
  38. package/src/components/ui/autocomplete/Autocomplete.tsx +332 -0
  39. package/src/components/ui/autocomplete/index.ts +2 -0
  40. package/src/components/ui/avatar/Avatar.stories.tsx +105 -0
  41. package/src/components/ui/avatar/Avatar.test.tsx +135 -0
  42. package/src/components/ui/avatar/Avatar.tsx +168 -0
  43. package/src/components/ui/avatar/index.ts +2 -0
  44. package/src/components/ui/badge/Badge.stories.tsx +100 -0
  45. package/src/components/ui/badge/Badge.test.tsx +86 -0
  46. package/src/components/ui/badge/Badge.tsx +105 -0
  47. package/src/components/ui/badge/index.ts +2 -0
  48. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +70 -0
  49. package/src/components/ui/breadcrumbs/Breadcrumbs.test.tsx +156 -0
  50. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +127 -0
  51. package/src/components/ui/breadcrumbs/index.ts +2 -0
  52. package/src/components/ui/button/Button.stories.tsx +216 -0
  53. package/src/components/ui/button/Button.test.tsx +132 -0
  54. package/src/components/ui/button/Button.tsx +242 -0
  55. package/src/components/ui/button/index.ts +10 -0
  56. package/src/components/ui/card/Card.stories.tsx +443 -0
  57. package/src/components/ui/card/Card.test.tsx +265 -0
  58. package/src/components/ui/card/Card.tsx +380 -0
  59. package/src/components/ui/card/index.ts +20 -0
  60. package/src/components/ui/checkbox/Checkbox.stories.tsx +126 -0
  61. package/src/components/ui/checkbox/Checkbox.test.tsx +152 -0
  62. package/src/components/ui/checkbox/Checkbox.tsx +163 -0
  63. package/src/components/ui/checkbox/index.ts +2 -0
  64. package/src/components/ui/chip/Chip.stories.tsx +112 -0
  65. package/src/components/ui/chip/Chip.test.tsx +119 -0
  66. package/src/components/ui/chip/Chip.tsx +151 -0
  67. package/src/components/ui/chip/index.ts +2 -0
  68. package/src/components/ui/collapse/Collapse.stories.tsx +313 -0
  69. package/src/components/ui/collapse/Collapse.test.tsx +298 -0
  70. package/src/components/ui/collapse/Collapse.tsx +275 -0
  71. package/src/components/ui/collapse/index.ts +18 -0
  72. package/src/components/ui/data-row/DataRow.stories.tsx +78 -0
  73. package/src/components/ui/data-row/DataRow.test.tsx +78 -0
  74. package/src/components/ui/data-row/DataRow.tsx +29 -0
  75. package/src/components/ui/data-row/index.ts +2 -0
  76. package/src/components/ui/divider/Divider.stories.tsx +71 -0
  77. package/src/components/ui/divider/Divider.test.tsx +55 -0
  78. package/src/components/ui/divider/Divider.tsx +37 -0
  79. package/src/components/ui/divider/index.ts +2 -0
  80. package/src/components/ui/drawer/Drawer.stories.tsx +228 -0
  81. package/src/components/ui/drawer/Drawer.test.tsx +142 -0
  82. package/src/components/ui/drawer/Drawer.tsx +228 -0
  83. package/src/components/ui/drawer/index.ts +9 -0
  84. package/src/components/ui/form-field/FormField.stories.tsx +260 -0
  85. package/src/components/ui/form-field/FormField.test.tsx +280 -0
  86. package/src/components/ui/form-field/FormField.tsx +308 -0
  87. package/src/components/ui/form-field/index.ts +2 -0
  88. package/src/components/ui/help-tip/HelpTip.stories.tsx +250 -0
  89. package/src/components/ui/help-tip/HelpTip.test.tsx +198 -0
  90. package/src/components/ui/help-tip/HelpTip.tsx +229 -0
  91. package/src/components/ui/help-tip/index.ts +2 -0
  92. package/src/components/ui/icon-box/IconBox.stories.tsx +139 -0
  93. package/src/components/ui/icon-box/IconBox.test.tsx +120 -0
  94. package/src/components/ui/icon-box/IconBox.tsx +57 -0
  95. package/src/components/ui/icon-box/index.ts +1 -0
  96. package/src/components/ui/index.ts +36 -0
  97. package/src/components/ui/input/Input.stories.tsx +192 -0
  98. package/src/components/ui/input/Input.test.tsx +86 -0
  99. package/src/components/ui/input/Input.tsx +273 -0
  100. package/src/components/ui/input/index.ts +2 -0
  101. package/src/components/ui/link/Link.stories.tsx +78 -0
  102. package/src/components/ui/link/Link.test.tsx +94 -0
  103. package/src/components/ui/link/Link.tsx +86 -0
  104. package/src/components/ui/link/index.ts +2 -0
  105. package/src/components/ui/list-item/ListItem.stories.tsx +146 -0
  106. package/src/components/ui/list-item/ListItem.test.tsx +128 -0
  107. package/src/components/ui/list-item/ListItem.tsx +89 -0
  108. package/src/components/ui/list-item/index.ts +14 -0
  109. package/src/components/ui/menu/Menu.stories.tsx +180 -0
  110. package/src/components/ui/menu/Menu.test.tsx +353 -0
  111. package/src/components/ui/menu/Menu.tsx +222 -0
  112. package/src/components/ui/menu/index.ts +2 -0
  113. package/src/components/ui/modal/Modal.stories.tsx +350 -0
  114. package/src/components/ui/modal/Modal.test.tsx +159 -0
  115. package/src/components/ui/modal/Modal.tsx +254 -0
  116. package/src/components/ui/modal/index.ts +19 -0
  117. package/src/components/ui/popover/Popover.stories.tsx +190 -0
  118. package/src/components/ui/popover/Popover.test.tsx +277 -0
  119. package/src/components/ui/popover/Popover.tsx +162 -0
  120. package/src/components/ui/popover/index.ts +2 -0
  121. package/src/components/ui/progress/Progress.stories.tsx +190 -0
  122. package/src/components/ui/progress/Progress.test.tsx +181 -0
  123. package/src/components/ui/progress/Progress.tsx +320 -0
  124. package/src/components/ui/progress/index.ts +9 -0
  125. package/src/components/ui/radio/Radio.stories.tsx +199 -0
  126. package/src/components/ui/radio/Radio.test.tsx +166 -0
  127. package/src/components/ui/radio/Radio.tsx +200 -0
  128. package/src/components/ui/radio/index.ts +2 -0
  129. package/src/components/ui/section/Section.stories.tsx +111 -0
  130. package/src/components/ui/section/Section.test.tsx +121 -0
  131. package/src/components/ui/section/Section.tsx +52 -0
  132. package/src/components/ui/section/index.ts +6 -0
  133. package/src/components/ui/select/Select.stories.tsx +286 -0
  134. package/src/components/ui/select/Select.test.tsx +180 -0
  135. package/src/components/ui/select/Select.tsx +236 -0
  136. package/src/components/ui/select/index.ts +2 -0
  137. package/src/components/ui/skeleton/Skeleton.stories.tsx +153 -0
  138. package/src/components/ui/skeleton/Skeleton.test.tsx +162 -0
  139. package/src/components/ui/skeleton/Skeleton.tsx +229 -0
  140. package/src/components/ui/skeleton/index.ts +16 -0
  141. package/src/components/ui/spinner/Spinner.stories.tsx +67 -0
  142. package/src/components/ui/spinner/Spinner.test.tsx +67 -0
  143. package/src/components/ui/spinner/Spinner.tsx +72 -0
  144. package/src/components/ui/spinner/index.ts +2 -0
  145. package/src/components/ui/stack/Stack.stories.tsx +127 -0
  146. package/src/components/ui/stack/Stack.test.tsx +184 -0
  147. package/src/components/ui/stack/Stack.tsx +76 -0
  148. package/src/components/ui/stack/index.ts +2 -0
  149. package/src/components/ui/surface/Surface.stories.tsx +91 -0
  150. package/src/components/ui/surface/Surface.test.tsx +63 -0
  151. package/src/components/ui/surface/Surface.tsx +51 -0
  152. package/src/components/ui/surface/index.ts +1 -0
  153. package/src/components/ui/switch/Switch.stories.tsx +111 -0
  154. package/src/components/ui/switch/Switch.test.tsx +112 -0
  155. package/src/components/ui/switch/Switch.tsx +103 -0
  156. package/src/components/ui/switch/index.ts +2 -0
  157. package/src/components/ui/tabs/Tabs.stories.tsx +213 -0
  158. package/src/components/ui/tabs/Tabs.test.tsx +192 -0
  159. package/src/components/ui/tabs/Tabs.tsx +253 -0
  160. package/src/components/ui/tabs/index.ts +16 -0
  161. package/src/components/ui/toast/Toast.stories.tsx +255 -0
  162. package/src/components/ui/toast/Toast.test.tsx +207 -0
  163. package/src/components/ui/toast/Toast.tsx +327 -0
  164. package/src/components/ui/toast/index.ts +8 -0
  165. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +415 -0
  166. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +224 -0
  167. package/src/components/ui/toolbar-button/ToolbarButton.tsx +307 -0
  168. package/src/components/ui/toolbar-button/index.ts +7 -0
  169. package/src/components/ui/tooltip/Tooltip.stories.tsx +126 -0
  170. package/src/components/ui/tooltip/Tooltip.test.tsx +179 -0
  171. package/src/components/ui/tooltip/Tooltip.tsx +143 -0
  172. package/src/components/ui/tooltip/index.ts +2 -0
  173. package/src/index.ts +10 -0
  174. package/src/test/setup.ts +19 -0
  175. package/src/theme/Colors.stories.tsx +258 -0
  176. package/src/theme/config.ts +193 -0
  177. package/src/theme/elevation.stories.tsx +346 -0
  178. package/src/theme/elevation.test.ts +27 -0
  179. package/src/theme/elevation.ts +567 -0
  180. package/src/theme/index.ts +39 -0
  181. package/src/theme/shadows.stories.tsx +523 -0
  182. package/src/theme/shadows.ts +439 -0
  183. package/src/theme/tokens/index.ts +2 -0
  184. package/src/theme/tokens/primitives.ts +354 -0
  185. package/src/theme/tokens/semantic.ts +381 -0
  186. package/src/types/jest-axe.d.ts +16 -0
  187. package/src/types/nativewind.d.ts +54 -0
  188. package/src/utils/cn.ts +14 -0
  189. package/src/utils/colors.ts +140 -0
  190. package/src/utils/form.ts +188 -0
  191. package/src/utils/index.ts +29 -0
  192. 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,10 @@
1
+ export { Button, ButtonText, ButtonIcon, ButtonSpinner } from './Button'
2
+ export type {
3
+ ButtonProps,
4
+ ButtonTextProps,
5
+ ButtonIconProps,
6
+ ButtonSpinnerProps,
7
+ ButtonVariant,
8
+ ButtonSize,
9
+ ButtonColor,
10
+ } from './Button'
@@ -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
+ }