@timmbr/native-ui 0.0.0-stage → 1.0.0

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 (212) hide show
  1. package/dist/components/Actionsheet/Actionsheet.d.ts +12 -0
  2. package/dist/components/Actionsheet/Actionsheet.d.ts.map +1 -0
  3. package/dist/components/Actionsheet/Actionsheet.js +116 -0
  4. package/dist/components/Actionsheet/Actionsheet.styles.d.ts +43 -0
  5. package/dist/components/Actionsheet/Actionsheet.styles.d.ts.map +1 -0
  6. package/dist/components/Actionsheet/Actionsheet.styles.js +25 -0
  7. package/dist/components/Actionsheet/Actionsheet.types.d.ts +39 -0
  8. package/dist/components/Actionsheet/Actionsheet.types.d.ts.map +1 -0
  9. package/dist/components/Actionsheet/index.d.ts +4 -0
  10. package/dist/components/Actionsheet/index.d.ts.map +1 -0
  11. package/dist/components/Avatar/Avatar.d.ts +8 -0
  12. package/dist/components/Avatar/Avatar.d.ts.map +1 -0
  13. package/dist/components/Avatar/Avatar.js +66 -0
  14. package/dist/components/Avatar/Avatar.styles.d.ts +25 -0
  15. package/dist/components/Avatar/Avatar.styles.d.ts.map +1 -0
  16. package/dist/components/Avatar/Avatar.styles.js +29 -0
  17. package/dist/components/Avatar/Avatar.types.d.ts +21 -0
  18. package/dist/components/Avatar/Avatar.types.d.ts.map +1 -0
  19. package/dist/components/Avatar/index.d.ts +4 -0
  20. package/dist/components/Avatar/index.d.ts.map +1 -0
  21. package/dist/components/Badge/Badge.d.ts +7 -0
  22. package/dist/components/Badge/Badge.d.ts.map +1 -0
  23. package/dist/components/Badge/Badge.js +73 -0
  24. package/dist/components/Badge/Badge.styles.d.ts +15 -0
  25. package/dist/components/Badge/Badge.styles.d.ts.map +1 -0
  26. package/dist/components/Badge/Badge.styles.js +71 -0
  27. package/dist/components/Badge/Badge.types.d.ts +25 -0
  28. package/dist/components/Badge/Badge.types.d.ts.map +1 -0
  29. package/dist/components/Badge/index.d.ts +4 -0
  30. package/dist/components/Badge/index.d.ts.map +1 -0
  31. package/dist/components/Box/Box.d.ts +4 -0
  32. package/dist/components/Box/Box.d.ts.map +1 -0
  33. package/dist/components/Box/Box.js +26 -0
  34. package/dist/components/Box/Box.styles.d.ts +7 -0
  35. package/dist/components/Box/Box.styles.d.ts.map +1 -0
  36. package/dist/components/Box/Box.styles.js +7 -0
  37. package/dist/components/Box/Box.types.d.ts +8 -0
  38. package/dist/components/Box/Box.types.d.ts.map +1 -0
  39. package/dist/components/Box/index.d.ts +4 -0
  40. package/dist/components/Box/index.d.ts.map +1 -0
  41. package/dist/components/Button/Button.d.ts +9 -0
  42. package/dist/components/Button/Button.d.ts.map +1 -0
  43. package/dist/components/Button/Button.js +169 -0
  44. package/dist/components/Button/Button.styles.d.ts +30 -0
  45. package/dist/components/Button/Button.styles.d.ts.map +1 -0
  46. package/dist/components/Button/Button.styles.js +106 -0
  47. package/dist/components/Button/Button.types.d.ts +41 -0
  48. package/dist/components/Button/Button.types.d.ts.map +1 -0
  49. package/dist/components/Button/index.d.ts +4 -0
  50. package/dist/components/Button/index.d.ts.map +1 -0
  51. package/dist/components/Card/Card.d.ts +8 -0
  52. package/dist/components/Card/Card.d.ts.map +1 -0
  53. package/dist/components/Card/Card.js +50 -0
  54. package/dist/components/Card/Card.styles.d.ts +26 -0
  55. package/dist/components/Card/Card.styles.d.ts.map +1 -0
  56. package/dist/components/Card/Card.styles.js +32 -0
  57. package/dist/components/Card/Card.types.d.ts +26 -0
  58. package/dist/components/Card/Card.types.d.ts.map +1 -0
  59. package/dist/components/Card/index.d.ts +4 -0
  60. package/dist/components/Card/index.d.ts.map +1 -0
  61. package/dist/components/Divider/Divider.d.ts +4 -0
  62. package/dist/components/Divider/Divider.d.ts.map +1 -0
  63. package/dist/components/Divider/Divider.js +22 -0
  64. package/dist/components/Divider/Divider.styles.d.ts +7 -0
  65. package/dist/components/Divider/Divider.styles.d.ts.map +1 -0
  66. package/dist/components/Divider/Divider.styles.js +16 -0
  67. package/dist/components/Divider/Divider.types.d.ts +7 -0
  68. package/dist/components/Divider/Divider.types.d.ts.map +1 -0
  69. package/dist/components/Divider/index.d.ts +4 -0
  70. package/dist/components/Divider/index.d.ts.map +1 -0
  71. package/dist/components/HStack/HStack.d.ts +4 -0
  72. package/dist/components/HStack/HStack.d.ts.map +1 -0
  73. package/dist/components/HStack/HStack.js +21 -0
  74. package/dist/components/HStack/HStack.styles.d.ts +8 -0
  75. package/dist/components/HStack/HStack.styles.d.ts.map +1 -0
  76. package/dist/components/HStack/HStack.styles.js +28 -0
  77. package/dist/components/HStack/HStack.types.d.ts +9 -0
  78. package/dist/components/HStack/HStack.types.d.ts.map +1 -0
  79. package/dist/components/HStack/index.d.ts +4 -0
  80. package/dist/components/HStack/index.d.ts.map +1 -0
  81. package/dist/components/Heading/Heading.d.ts +4 -0
  82. package/dist/components/Heading/Heading.d.ts.map +1 -0
  83. package/dist/components/Heading/Heading.js +22 -0
  84. package/dist/components/Heading/Heading.styles.d.ts +8 -0
  85. package/dist/components/Heading/Heading.styles.d.ts.map +1 -0
  86. package/dist/components/Heading/Heading.styles.js +28 -0
  87. package/dist/components/Heading/Heading.types.d.ts +10 -0
  88. package/dist/components/Heading/Heading.types.d.ts.map +1 -0
  89. package/dist/components/Heading/index.d.ts +4 -0
  90. package/dist/components/Heading/index.d.ts.map +1 -0
  91. package/dist/components/Icon/Icon.d.ts +4 -0
  92. package/dist/components/Icon/Icon.d.ts.map +1 -0
  93. package/dist/components/Icon/Icon.js +37 -0
  94. package/dist/components/Icon/Icon.styles.d.ts +7 -0
  95. package/dist/components/Icon/Icon.styles.d.ts.map +1 -0
  96. package/dist/components/Icon/Icon.styles.js +20 -0
  97. package/dist/components/Icon/Icon.types.d.ts +9 -0
  98. package/dist/components/Icon/Icon.types.d.ts.map +1 -0
  99. package/dist/components/Icon/index.d.ts +4 -0
  100. package/dist/components/Icon/index.d.ts.map +1 -0
  101. package/dist/components/Input/Input.d.ts +8 -0
  102. package/dist/components/Input/Input.d.ts.map +1 -0
  103. package/dist/components/Input/Input.js +105 -0
  104. package/dist/components/Input/Input.styles.d.ts +23 -0
  105. package/dist/components/Input/Input.styles.d.ts.map +1 -0
  106. package/dist/components/Input/Input.styles.js +56 -0
  107. package/dist/components/Input/Input.types.d.ts +32 -0
  108. package/dist/components/Input/Input.types.d.ts.map +1 -0
  109. package/dist/components/Input/index.d.ts +4 -0
  110. package/dist/components/Input/index.d.ts.map +1 -0
  111. package/dist/components/Modal/Modal.d.ts +11 -0
  112. package/dist/components/Modal/Modal.d.ts.map +1 -0
  113. package/dist/components/Modal/Modal.js +102 -0
  114. package/dist/components/Modal/Modal.styles.d.ts +43 -0
  115. package/dist/components/Modal/Modal.styles.d.ts.map +1 -0
  116. package/dist/components/Modal/Modal.styles.js +37 -0
  117. package/dist/components/Modal/Modal.types.d.ts +38 -0
  118. package/dist/components/Modal/Modal.types.d.ts.map +1 -0
  119. package/dist/components/Modal/index.d.ts +4 -0
  120. package/dist/components/Modal/index.d.ts.map +1 -0
  121. package/dist/components/Pressable/Pressable.d.ts +4 -0
  122. package/dist/components/Pressable/Pressable.d.ts.map +1 -0
  123. package/dist/components/Pressable/Pressable.js +43 -0
  124. package/dist/components/Pressable/Pressable.styles.d.ts +7 -0
  125. package/dist/components/Pressable/Pressable.styles.d.ts.map +1 -0
  126. package/dist/components/Pressable/Pressable.styles.js +16 -0
  127. package/dist/components/Pressable/Pressable.types.d.ts +9 -0
  128. package/dist/components/Pressable/Pressable.types.d.ts.map +1 -0
  129. package/dist/components/Pressable/index.d.ts +4 -0
  130. package/dist/components/Pressable/index.d.ts.map +1 -0
  131. package/dist/components/Spinner/Spinner.d.ts +4 -0
  132. package/dist/components/Spinner/Spinner.d.ts.map +1 -0
  133. package/dist/components/Spinner/Spinner.js +52 -0
  134. package/dist/components/Spinner/Spinner.styles.d.ts +7 -0
  135. package/dist/components/Spinner/Spinner.styles.d.ts.map +1 -0
  136. package/dist/components/Spinner/Spinner.styles.js +20 -0
  137. package/dist/components/Spinner/Spinner.types.d.ts +8 -0
  138. package/dist/components/Spinner/Spinner.types.d.ts.map +1 -0
  139. package/dist/components/Spinner/index.d.ts +4 -0
  140. package/dist/components/Spinner/index.d.ts.map +1 -0
  141. package/dist/components/Switch/Switch.d.ts +4 -0
  142. package/dist/components/Switch/Switch.d.ts.map +1 -0
  143. package/dist/components/Switch/Switch.js +56 -0
  144. package/dist/components/Switch/Switch.styles.d.ts +16 -0
  145. package/dist/components/Switch/Switch.styles.d.ts.map +1 -0
  146. package/dist/components/Switch/Switch.styles.js +50 -0
  147. package/dist/components/Switch/Switch.types.d.ts +11 -0
  148. package/dist/components/Switch/Switch.types.d.ts.map +1 -0
  149. package/dist/components/Switch/index.d.ts +4 -0
  150. package/dist/components/Switch/index.d.ts.map +1 -0
  151. package/dist/components/Tabs/Tabs.d.ts +9 -0
  152. package/dist/components/Tabs/Tabs.d.ts.map +1 -0
  153. package/dist/components/Tabs/Tabs.js +109 -0
  154. package/dist/components/Tabs/Tabs.styles.d.ts +31 -0
  155. package/dist/components/Tabs/Tabs.styles.d.ts.map +1 -0
  156. package/dist/components/Tabs/Tabs.styles.js +37 -0
  157. package/dist/components/Tabs/Tabs.types.d.ts +31 -0
  158. package/dist/components/Tabs/Tabs.types.d.ts.map +1 -0
  159. package/dist/components/Tabs/index.d.ts +4 -0
  160. package/dist/components/Tabs/index.d.ts.map +1 -0
  161. package/dist/components/Text/Text.d.ts +4 -0
  162. package/dist/components/Text/Text.d.ts.map +1 -0
  163. package/dist/components/Text/Text.js +29 -0
  164. package/dist/components/Text/Text.styles.d.ts +9 -0
  165. package/dist/components/Text/Text.styles.d.ts.map +1 -0
  166. package/dist/components/Text/Text.styles.js +35 -0
  167. package/dist/components/Text/Text.types.d.ts +11 -0
  168. package/dist/components/Text/Text.types.d.ts.map +1 -0
  169. package/dist/components/Text/index.d.ts +4 -0
  170. package/dist/components/Text/index.d.ts.map +1 -0
  171. package/dist/components/Textarea/Textarea.d.ts +6 -0
  172. package/dist/components/Textarea/Textarea.d.ts.map +1 -0
  173. package/dist/components/Textarea/Textarea.js +66 -0
  174. package/dist/components/Textarea/Textarea.styles.d.ts +16 -0
  175. package/dist/components/Textarea/Textarea.styles.d.ts.map +1 -0
  176. package/dist/components/Textarea/Textarea.styles.js +47 -0
  177. package/dist/components/Textarea/Textarea.types.d.ts +20 -0
  178. package/dist/components/Textarea/Textarea.types.d.ts.map +1 -0
  179. package/dist/components/Textarea/index.d.ts +4 -0
  180. package/dist/components/Textarea/index.d.ts.map +1 -0
  181. package/dist/components/Toast/Toast.d.ts +7 -0
  182. package/dist/components/Toast/Toast.d.ts.map +1 -0
  183. package/dist/components/Toast/Toast.js +39 -0
  184. package/dist/components/Toast/Toast.styles.d.ts +20 -0
  185. package/dist/components/Toast/Toast.styles.d.ts.map +1 -0
  186. package/dist/components/Toast/Toast.styles.js +42 -0
  187. package/dist/components/Toast/Toast.types.d.ts +22 -0
  188. package/dist/components/Toast/Toast.types.d.ts.map +1 -0
  189. package/dist/components/Toast/index.d.ts +4 -0
  190. package/dist/components/Toast/index.d.ts.map +1 -0
  191. package/dist/components/VStack/VStack.d.ts +4 -0
  192. package/dist/components/VStack/VStack.d.ts.map +1 -0
  193. package/dist/components/VStack/VStack.js +21 -0
  194. package/dist/components/VStack/VStack.styles.d.ts +8 -0
  195. package/dist/components/VStack/VStack.styles.d.ts.map +1 -0
  196. package/dist/components/VStack/VStack.styles.js +28 -0
  197. package/dist/components/VStack/VStack.types.d.ts +9 -0
  198. package/dist/components/VStack/VStack.types.d.ts.map +1 -0
  199. package/dist/components/VStack/index.d.ts +4 -0
  200. package/dist/components/VStack/index.d.ts.map +1 -0
  201. package/dist/index.d.ts +23 -0
  202. package/dist/index.d.ts.map +1 -0
  203. package/dist/index.js +42 -0
  204. package/dist/provider/TimmbrNativeProvider.d.ts +21 -0
  205. package/dist/provider/TimmbrNativeProvider.d.ts.map +1 -0
  206. package/dist/provider/TimmbrNativeProvider.js +110 -0
  207. package/dist/theme/config.d.ts +360 -0
  208. package/dist/theme/config.d.ts.map +1 -0
  209. package/dist/theme/config.js +129 -0
  210. package/package.json +70 -3
  211. package/src/native/index.tsx +799 -0
  212. package/README.md +0 -3
@@ -0,0 +1,799 @@
1
+ import React, { createContext, useContext, useState, useMemo } from 'react';
2
+ import {
3
+ View,
4
+ Text as RNText,
5
+ Pressable as RNPressable,
6
+ TextInput,
7
+ Modal as RNModal,
8
+ Switch as RNSwitch,
9
+ ActivityIndicator,
10
+ ViewProps,
11
+ TextProps,
12
+ PressableProps,
13
+ TextInputProps,
14
+ SwitchProps,
15
+ ActivityIndicatorProps,
16
+ ModalProps,
17
+ useColorScheme,
18
+ } from 'react-native';
19
+
20
+ export type ThemeMode = 'light' | 'dark' | 'system';
21
+ export type ResolvedTheme = 'light' | 'dark';
22
+
23
+ export interface TimmbrNativeContextValue {
24
+ mode: ThemeMode;
25
+ resolvedTheme: ResolvedTheme;
26
+ setMode: (mode: ThemeMode) => void;
27
+ toggleTheme: () => void;
28
+ }
29
+
30
+ const TimmbrNativeContext = createContext<TimmbrNativeContextValue>({
31
+ mode: 'system',
32
+ resolvedTheme: 'light',
33
+ setMode: () => {},
34
+ toggleTheme: () => {},
35
+ });
36
+
37
+ export const useTimmbrNative = (): TimmbrNativeContextValue => useContext(TimmbrNativeContext);
38
+ export const useTimmbrTheme = useTimmbrNative;
39
+
40
+ export interface TimmbrNativeProviderProps {
41
+ mode?: ThemeMode;
42
+ defaultMode?: ThemeMode;
43
+ children: React.ReactNode;
44
+ className?: string;
45
+ colorMode?: ThemeMode;
46
+ }
47
+
48
+ export const TimmbrNativeProvider: React.FC<TimmbrNativeProviderProps> = ({
49
+ mode: controlledMode,
50
+ defaultMode = 'system',
51
+ colorMode,
52
+ children,
53
+ className = '',
54
+ }) => {
55
+ const systemScheme = useColorScheme();
56
+ const [uncontrolledMode, setUncontrolledMode] = useState<ThemeMode>(
57
+ colorMode ?? defaultMode
58
+ );
59
+ const currentMode = controlledMode ?? colorMode ?? uncontrolledMode;
60
+
61
+ const resolvedTheme: ResolvedTheme = useMemo(() => {
62
+ if (currentMode === 'system') {
63
+ return systemScheme === 'dark' ? 'dark' : 'light';
64
+ }
65
+ return currentMode;
66
+ }, [currentMode, systemScheme]);
67
+
68
+ const setMode = (newMode: ThemeMode) => {
69
+ setUncontrolledMode(newMode);
70
+ };
71
+
72
+ const toggleTheme = () => {
73
+ setUncontrolledMode((prev) => {
74
+ const active = prev === 'system' ? (systemScheme === 'dark' ? 'dark' : 'light') : prev;
75
+ return active === 'dark' ? 'light' : 'dark';
76
+ });
77
+ };
78
+
79
+ const contextValue = useMemo(
80
+ () => ({
81
+ mode: currentMode,
82
+ resolvedTheme,
83
+ setMode,
84
+ toggleTheme,
85
+ }),
86
+ [currentMode, resolvedTheme]
87
+ );
88
+
89
+ return (
90
+ <TimmbrNativeContext.Provider value={contextValue}>
91
+ <View
92
+ className={`flex-1 ${resolvedTheme === 'dark' ? 'dark bg-background' : 'bg-background'} ${className}`}
93
+ >
94
+ {children}
95
+ </View>
96
+ </TimmbrNativeContext.Provider>
97
+ );
98
+ };
99
+
100
+ export interface BoxProps extends ViewProps {
101
+ className?: string;
102
+ }
103
+ export const Box: React.FC<BoxProps> = ({ className = '', style, children, ...props }) => (
104
+ <View className={className} style={style} {...props}>
105
+ {children}
106
+ </View>
107
+ );
108
+
109
+ export interface StackProps extends ViewProps {
110
+ className?: string;
111
+ space?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
112
+ reversed?: boolean;
113
+ }
114
+
115
+ const spaceGapMap: Record<string, string> = {
116
+ xs: 'gap-1',
117
+ sm: 'gap-2',
118
+ md: 'gap-3',
119
+ lg: 'gap-4',
120
+ xl: 'gap-6',
121
+ };
122
+
123
+ export const VStack: React.FC<StackProps> = ({
124
+ space,
125
+ className = '',
126
+ reversed,
127
+ children,
128
+ ...props
129
+ }) => {
130
+ const gapClass = space ? spaceGapMap[space] : '';
131
+ const dirClass = reversed ? 'flex-col-reverse' : 'flex-col';
132
+ return (
133
+ <View className={`${dirClass} ${gapClass} ${className}`} {...props}>
134
+ {children}
135
+ </View>
136
+ );
137
+ };
138
+
139
+ export const HStack: React.FC<StackProps> = ({
140
+ space,
141
+ className = '',
142
+ reversed,
143
+ children,
144
+ ...props
145
+ }) => {
146
+ const gapClass = space ? spaceGapMap[space] : '';
147
+ const dirClass = reversed ? 'flex-row-reverse' : 'flex-row items-center';
148
+ return (
149
+ <View className={`${dirClass} ${gapClass} ${className}`} {...props}>
150
+ {children}
151
+ </View>
152
+ );
153
+ };
154
+
155
+ export interface TextPropsWithClass extends TextProps {
156
+ className?: string;
157
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
158
+ }
159
+
160
+ const textSizeMap: Record<string, string> = {
161
+ xs: 'text-xs',
162
+ sm: 'text-sm',
163
+ md: 'text-base',
164
+ lg: 'text-lg',
165
+ xl: 'text-xl',
166
+ };
167
+
168
+ export const Text: React.FC<TextPropsWithClass> = ({
169
+ size,
170
+ className = '',
171
+ children,
172
+ ...props
173
+ }) => {
174
+ const sizeClass = size ? textSizeMap[size] : '';
175
+ return (
176
+ <RNText className={`text-foreground font-sans ${sizeClass} ${className}`} {...props}>
177
+ {children}
178
+ </RNText>
179
+ );
180
+ };
181
+
182
+ export interface HeadingProps extends TextProps {
183
+ className?: string;
184
+ size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
185
+ }
186
+
187
+ const headingSizeMap: Record<string, string> = {
188
+ sm: 'text-lg',
189
+ md: 'text-xl',
190
+ lg: 'text-2xl',
191
+ xl: 'text-3xl',
192
+ '2xl': 'text-4xl',
193
+ };
194
+
195
+ export const Heading: React.FC<HeadingProps> = ({
196
+ size = 'lg',
197
+ className = '',
198
+ children,
199
+ ...props
200
+ }) => {
201
+ return (
202
+ <RNText
203
+ className={`font-title font-bold text-foreground ${headingSizeMap[size]} ${className}`}
204
+ {...props}
205
+ >
206
+ {children}
207
+ </RNText>
208
+ );
209
+ };
210
+
211
+ export interface ButtonProps extends Omit<PressableProps, 'children'> {
212
+ className?: string;
213
+ variant?: 'solid' | 'outline' | 'link';
214
+ action?: 'primary' | 'secondary' | 'positive' | 'negative' | 'default';
215
+ size?: 'sm' | 'md' | 'lg';
216
+ isDisabled?: boolean;
217
+ isLoading?: boolean;
218
+ children?: React.ReactNode;
219
+ }
220
+
221
+ const buttonVariantStyles: Record<string, Record<string, string>> = {
222
+ solid: {
223
+ primary: 'bg-primary border-primary',
224
+ secondary: 'bg-secondary border-border',
225
+ positive: 'bg-success border-success',
226
+ negative: 'bg-destructive border-destructive',
227
+ default: 'bg-secondary border-border',
228
+ },
229
+ outline: {
230
+ primary: 'bg-transparent border-primary',
231
+ secondary: 'bg-transparent border-border',
232
+ positive: 'bg-transparent border-success',
233
+ negative: 'bg-transparent border-destructive',
234
+ default: 'bg-transparent border-border',
235
+ },
236
+ link: {
237
+ primary: 'bg-transparent border-transparent',
238
+ secondary: 'bg-transparent border-transparent',
239
+ positive: 'bg-transparent border-transparent',
240
+ negative: 'bg-transparent border-transparent',
241
+ default: 'bg-transparent border-transparent',
242
+ },
243
+ };
244
+
245
+ const buttonSizeStyles: Record<string, string> = {
246
+ sm: 'px-3 py-1.5 rounded-md min-h-[36px]',
247
+ md: 'px-4 py-2.5 rounded-lg min-h-[44px]',
248
+ lg: 'px-6 py-3.5 rounded-xl min-h-[52px]',
249
+ };
250
+
251
+ export const Button: React.FC<ButtonProps> = ({
252
+ variant = 'solid',
253
+ action = 'primary',
254
+ size = 'md',
255
+ isDisabled = false,
256
+ isLoading = false,
257
+ disabled,
258
+ className = '',
259
+ children,
260
+ ...props
261
+ }) => {
262
+ const isActuallyDisabled = isDisabled || isLoading || disabled;
263
+ const variantClass = buttonVariantStyles[variant]?.[action] || buttonVariantStyles.solid.primary;
264
+ const sizeClass = buttonSizeStyles[size] || buttonSizeStyles.md;
265
+ const disabledClass = isActuallyDisabled ? 'opacity-50' : 'active:opacity-85';
266
+
267
+ return (
268
+ <RNPressable
269
+ disabled={isActuallyDisabled}
270
+ className={`flex-row items-center justify-center border ${variantClass} ${sizeClass} ${disabledClass} ${className}`}
271
+ {...props}
272
+ >
273
+ {isLoading && <ActivityIndicator color="#FFFFFF" className="mr-2" size="small" />}
274
+ {children}
275
+ </RNPressable>
276
+ );
277
+ };
278
+
279
+ export interface ButtonTextProps extends TextPropsWithClass {
280
+ action?: 'primary' | 'secondary' | 'positive' | 'negative' | 'default';
281
+ variant?: 'solid' | 'outline' | 'link';
282
+ }
283
+
284
+ export const ButtonText: React.FC<ButtonTextProps> = ({
285
+ className = '',
286
+ children,
287
+ ...props
288
+ }) => (
289
+ <RNText
290
+ className={`text-center font-semibold font-sans text-sm text-primary-foreground ${className}`}
291
+ {...props}
292
+ >
293
+ {children}
294
+ </RNText>
295
+ );
296
+
297
+ export interface ButtonSpinnerProps extends ActivityIndicatorProps {
298
+ className?: string;
299
+ }
300
+
301
+ export const ButtonSpinner: React.FC<ButtonSpinnerProps> = ({
302
+ color = '#FFFFFF',
303
+ size = 'small',
304
+ className = 'mr-2',
305
+ ...props
306
+ }) => <ActivityIndicator color={color} size={size} className={className} {...props} />;
307
+
308
+ export interface BadgeProps extends ViewProps {
309
+ className?: string;
310
+ variant?: 'solid' | 'outline';
311
+ action?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'muted';
312
+ }
313
+
314
+ const badgeActionStyles: Record<string, Record<string, string>> = {
315
+ solid: {
316
+ primary: 'bg-primary border-primary',
317
+ secondary: 'bg-secondary border-border',
318
+ success: 'bg-emerald-600 border-emerald-600',
319
+ warning: 'bg-amber-600 border-amber-600',
320
+ error: 'bg-destructive border-destructive',
321
+ info: 'bg-blue-600 border-blue-600',
322
+ muted: 'bg-muted border-border',
323
+ },
324
+ outline: {
325
+ primary: 'bg-transparent border-primary',
326
+ secondary: 'bg-transparent border-border',
327
+ success: 'bg-transparent border-emerald-600',
328
+ warning: 'bg-transparent border-amber-600',
329
+ error: 'bg-transparent border-destructive',
330
+ info: 'bg-transparent border-blue-600',
331
+ muted: 'bg-transparent border-border',
332
+ },
333
+ };
334
+
335
+ export const Badge: React.FC<BadgeProps> = ({
336
+ variant = 'solid',
337
+ action = 'primary',
338
+ className = '',
339
+ children,
340
+ ...props
341
+ }) => {
342
+ const badgeClass = badgeActionStyles[variant]?.[action] || badgeActionStyles.solid.primary;
343
+ return (
344
+ <View
345
+ className={`px-2.5 py-0.5 rounded-full border items-center justify-center flex-row ${badgeClass} ${className}`}
346
+ {...props}
347
+ >
348
+ {children}
349
+ </View>
350
+ );
351
+ };
352
+
353
+ export const BadgeText: React.FC<TextPropsWithClass> = ({
354
+ className = '',
355
+ children,
356
+ ...props
357
+ }) => (
358
+ <RNText
359
+ className={`text-xs font-semibold font-sans text-primary-foreground ${className}`}
360
+ {...props}
361
+ >
362
+ {children}
363
+ </RNText>
364
+ );
365
+
366
+ export const Card: React.FC<BoxProps> = ({ className = '', children, ...props }) => (
367
+ <View
368
+ className={`rounded-xl p-4 bg-card border border-border shadow-sm ${className}`}
369
+ {...props}
370
+ >
371
+ {children}
372
+ </View>
373
+ );
374
+
375
+ export interface InputProps extends ViewProps {
376
+ className?: string;
377
+ isInvalid?: boolean;
378
+ isDisabled?: boolean;
379
+ }
380
+
381
+ export const Input: React.FC<InputProps> = ({
382
+ isInvalid = false,
383
+ isDisabled = false,
384
+ className = '',
385
+ children,
386
+ ...props
387
+ }) => {
388
+ const stateClass = isInvalid
389
+ ? 'border-destructive'
390
+ : isDisabled
391
+ ? 'border-border opacity-50'
392
+ : 'border-border';
393
+ return (
394
+ <View
395
+ className={`flex-row items-center rounded-lg border bg-background px-3 py-2 min-h-[44px] ${stateClass} ${className}`}
396
+ {...props}
397
+ >
398
+ {children}
399
+ </View>
400
+ );
401
+ };
402
+
403
+ export interface InputFieldProps extends TextInputProps {
404
+ className?: string;
405
+ }
406
+
407
+ export const InputField: React.FC<InputFieldProps> = ({
408
+ className = '',
409
+ placeholderTextColor = '#6D6D6D',
410
+ ...props
411
+ }) => (
412
+ <TextInput
413
+ className={`flex-1 text-foreground placeholder:text-muted-foreground font-sans text-base py-1 ${className}`}
414
+ placeholderTextColor={placeholderTextColor}
415
+ {...props}
416
+ />
417
+ );
418
+
419
+ export const Textarea: React.FC<InputProps> = ({
420
+ isInvalid = false,
421
+ isDisabled = false,
422
+ className = '',
423
+ children,
424
+ ...props
425
+ }) => {
426
+ const stateClass = isInvalid
427
+ ? 'border-destructive'
428
+ : isDisabled
429
+ ? 'border-border opacity-50'
430
+ : 'border-border';
431
+ return (
432
+ <View
433
+ className={`rounded-lg border bg-background p-3 min-h-[100px] ${stateClass} ${className}`}
434
+ {...props}
435
+ >
436
+ {children}
437
+ </View>
438
+ );
439
+ };
440
+
441
+ export const TextareaInput: React.FC<InputFieldProps> = ({
442
+ className = '',
443
+ placeholderTextColor = '#6D6D6D',
444
+ ...props
445
+ }) => (
446
+ <TextInput
447
+ multiline
448
+ textAlignVertical="top"
449
+ className={`flex-1 text-foreground placeholder:text-muted-foreground font-sans text-base ${className}`}
450
+ placeholderTextColor={placeholderTextColor}
451
+ {...props}
452
+ />
453
+ );
454
+
455
+ interface TabsContextValue {
456
+ activeTab: string;
457
+ setActiveTab: (tab: string) => void;
458
+ }
459
+
460
+ const TabsContext = createContext<TabsContextValue>({
461
+ activeTab: '',
462
+ setActiveTab: () => {},
463
+ });
464
+
465
+ export interface TabsProps extends ViewProps {
466
+ defaultValue?: string;
467
+ value?: string;
468
+ onValueChange?: (value: string) => void;
469
+ className?: string;
470
+ }
471
+
472
+ export const Tabs: React.FC<TabsProps> = ({
473
+ defaultValue = '',
474
+ value: controlledValue,
475
+ onValueChange,
476
+ className = '',
477
+ children,
478
+ ...props
479
+ }) => {
480
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
481
+ const activeTab = controlledValue ?? uncontrolledValue;
482
+
483
+ const setActiveTab = (newTab: string) => {
484
+ setUncontrolledValue(newTab);
485
+ onValueChange?.(newTab);
486
+ };
487
+
488
+ return (
489
+ <TabsContext.Provider value={{ activeTab, setActiveTab }}>
490
+ <View className={`w-full ${className}`} {...props}>
491
+ {children}
492
+ </View>
493
+ </TabsContext.Provider>
494
+ );
495
+ };
496
+
497
+ export const TabsList: React.FC<BoxProps> = ({ className = '', children, ...props }) => (
498
+ <View
499
+ className={`flex-row rounded-lg bg-secondary p-1 border border-border ${className}`}
500
+ {...props}
501
+ >
502
+ {children}
503
+ </View>
504
+ );
505
+
506
+ export interface TabsTriggerProps extends PressableProps {
507
+ value: string;
508
+ className?: string;
509
+ }
510
+
511
+ export const TabsTrigger: React.FC<TabsTriggerProps> = ({
512
+ value,
513
+ className = '',
514
+ children,
515
+ ...props
516
+ }) => {
517
+ const { activeTab, setActiveTab } = useContext(TabsContext);
518
+ const isActive = activeTab === value;
519
+
520
+ return (
521
+ <RNPressable
522
+ onPress={() => setActiveTab(value)}
523
+ className={`flex-1 py-2 px-3 rounded-md items-center justify-center ${
524
+ isActive ? 'bg-card shadow-sm border border-border' : 'opacity-70'
525
+ } ${className}`}
526
+ {...props}
527
+ >
528
+ {children}
529
+ </RNPressable>
530
+ );
531
+ };
532
+
533
+ export const TabsTriggerText: React.FC<TextPropsWithClass> = ({
534
+ className = '',
535
+ children,
536
+ ...props
537
+ }) => (
538
+ <RNText
539
+ className={`text-sm font-semibold text-foreground font-sans ${className}`}
540
+ {...props}
541
+ >
542
+ {children}
543
+ </RNText>
544
+ );
545
+
546
+ export interface TabsContentProps extends ViewProps {
547
+ value: string;
548
+ className?: string;
549
+ }
550
+
551
+ export const TabsContent: React.FC<TabsContentProps> = ({
552
+ value,
553
+ className = '',
554
+ children,
555
+ ...props
556
+ }) => {
557
+ const { activeTab } = useContext(TabsContext);
558
+ if (activeTab !== value) return null;
559
+
560
+ return (
561
+ <View className={`mt-3 ${className}`} {...props}>
562
+ {children}
563
+ </View>
564
+ );
565
+ };
566
+
567
+ export interface ActionsheetProps {
568
+ isOpen: boolean;
569
+ onClose: () => void;
570
+ children: React.ReactNode;
571
+ }
572
+
573
+ export const Actionsheet: React.FC<ActionsheetProps> = ({ isOpen, onClose, children }) => (
574
+ <RNModal
575
+ visible={isOpen}
576
+ transparent
577
+ animationType="fade"
578
+ onRequestClose={onClose}
579
+ >
580
+ <View className="flex-1 justify-end">
581
+ {children}
582
+ </View>
583
+ </RNModal>
584
+ );
585
+
586
+ export const ActionsheetBackdrop: React.FC<PressableProps & { className?: string }> = ({
587
+ onPress,
588
+ className = '',
589
+ }) => (
590
+ <RNPressable
591
+ onPress={onPress}
592
+ className={`absolute inset-0 bg-black/60 ${className}`}
593
+ />
594
+ );
595
+
596
+ export const ActionsheetContent: React.FC<BoxProps> = ({
597
+ className = '',
598
+ children,
599
+ ...props
600
+ }) => (
601
+ <View
602
+ className={`bg-card rounded-t-3xl p-5 border-t border-border shadow-lg max-h-[85%] ${className}`}
603
+ {...props}
604
+ >
605
+ {children}
606
+ </View>
607
+ );
608
+
609
+ export const ActionsheetDragIndicator: React.FC<BoxProps> = ({
610
+ className = '',
611
+ ...props
612
+ }) => (
613
+ <View className="items-center pb-3">
614
+ <View className={`w-12 h-1.5 rounded-full bg-muted ${className}`} {...props} />
615
+ </View>
616
+ );
617
+
618
+ export interface ActionsheetItemProps extends PressableProps {
619
+ className?: string;
620
+ }
621
+
622
+ export const ActionsheetItem: React.FC<ActionsheetItemProps> = ({
623
+ className = '',
624
+ children,
625
+ ...props
626
+ }) => (
627
+ <RNPressable
628
+ className={`py-3.5 px-4 rounded-xl active:bg-secondary flex-row items-center ${className}`}
629
+ {...props}
630
+ >
631
+ {children}
632
+ </RNPressable>
633
+ );
634
+
635
+ export const ActionsheetItemText: React.FC<TextPropsWithClass> = ({
636
+ className = '',
637
+ children,
638
+ ...props
639
+ }) => (
640
+ <RNText className={`text-base font-sans text-foreground ${className}`} {...props}>
641
+ {children}
642
+ </RNText>
643
+ );
644
+
645
+ export interface CustomModalProps extends ModalProps {
646
+ isOpen?: boolean;
647
+ onClose?: () => void;
648
+ className?: string;
649
+ }
650
+
651
+ export const Modal: React.FC<CustomModalProps> = ({
652
+ isOpen = false,
653
+ visible,
654
+ onClose,
655
+ children,
656
+ ...props
657
+ }) => (
658
+ <RNModal
659
+ visible={isOpen || visible}
660
+ transparent
661
+ animationType="fade"
662
+ onRequestClose={onClose}
663
+ {...props}
664
+ >
665
+ <View className="flex-1 items-center justify-center bg-black/60 p-4">
666
+ {children}
667
+ </View>
668
+ </RNModal>
669
+ );
670
+
671
+ export interface CustomSwitchProps extends SwitchProps {
672
+ className?: string;
673
+ }
674
+
675
+ export const Switch: React.FC<CustomSwitchProps> = ({
676
+ trackColor = { false: '#DDD5CA', true: '#C0643A' },
677
+ thumbColor = '#FFFFFF',
678
+ className = '',
679
+ ...props
680
+ }) => (
681
+ <RNSwitch
682
+ trackColor={trackColor}
683
+ thumbColor={thumbColor}
684
+ className={className}
685
+ {...props}
686
+ />
687
+ );
688
+
689
+ export const Spinner: React.FC<ButtonSpinnerProps> = ({
690
+ color = '#C0643A',
691
+ size = 'small',
692
+ className = '',
693
+ ...props
694
+ }) => <ActivityIndicator color={color} size={size} className={className} {...props} />;
695
+
696
+ export interface AvatarProps extends ViewProps {
697
+ size?: 'sm' | 'md' | 'lg' | 'xl';
698
+ className?: string;
699
+ }
700
+
701
+ const avatarSizeMap: Record<string, string> = {
702
+ sm: 'w-8 h-8 rounded-full',
703
+ md: 'w-10 h-10 rounded-full',
704
+ lg: 'w-14 h-14 rounded-full',
705
+ xl: 'w-20 h-20 rounded-full',
706
+ };
707
+
708
+ export const Avatar: React.FC<AvatarProps> = ({
709
+ size = 'md',
710
+ className = '',
711
+ children,
712
+ ...props
713
+ }) => (
714
+ <View
715
+ className={`items-center justify-center bg-secondary border border-border overflow-hidden ${avatarSizeMap[size]} ${className}`}
716
+ {...props}
717
+ >
718
+ {children}
719
+ </View>
720
+ );
721
+
722
+ export const AvatarFallbackText: React.FC<TextPropsWithClass> = ({
723
+ className = '',
724
+ children,
725
+ ...props
726
+ }) => (
727
+ <RNText
728
+ className={`font-bold font-sans text-secondary-foreground text-sm ${className}`}
729
+ {...props}
730
+ >
731
+ {children}
732
+ </RNText>
733
+ );
734
+
735
+ export const Divider: React.FC<BoxProps> = ({ className = '', ...props }) => (
736
+ <View className={`h-px bg-border my-2 w-full ${className}`} {...props} />
737
+ );
738
+
739
+ export const Pressable: React.FC<PressableProps & { className?: string }> = ({
740
+ className = '',
741
+ children,
742
+ ...props
743
+ }) => (
744
+ <RNPressable className={className} {...props}>
745
+ {children}
746
+ </RNPressable>
747
+ );
748
+
749
+ export interface ToastProps extends ViewProps {
750
+ action?: 'success' | 'warning' | 'error' | 'info';
751
+ title?: string;
752
+ description?: string;
753
+ className?: string;
754
+ }
755
+
756
+ export const Toast: React.FC<ToastProps> = ({
757
+ action = 'info',
758
+ title,
759
+ description,
760
+ className = '',
761
+ children,
762
+ ...props
763
+ }) => {
764
+ const borderColors: Record<string, string> = {
765
+ success: 'border-emerald-600',
766
+ warning: 'border-amber-600',
767
+ error: 'border-destructive',
768
+ info: 'border-blue-600',
769
+ };
770
+ return (
771
+ <View
772
+ className={`p-4 rounded-xl bg-card border-l-4 shadow-md ${borderColors[action]} ${className}`}
773
+ {...props}
774
+ >
775
+ {title && <RNText className="font-bold text-foreground">{title}</RNText>}
776
+ {description && <RNText className="text-sm text-muted-foreground mt-1">{description}</RNText>}
777
+ {children}
778
+ </View>
779
+ );
780
+ };
781
+
782
+ export interface IconProps {
783
+ as?: React.ComponentType<{ size?: number; color?: string; className?: string }>;
784
+ size?: number | string;
785
+ color?: string;
786
+ className?: string;
787
+ }
788
+
789
+ export const Icon: React.FC<IconProps> = ({
790
+ as: Component,
791
+ size = 20,
792
+ color = 'currentColor',
793
+ className = '',
794
+ }) => {
795
+ if (!Component) return null;
796
+ const numSize = typeof size === 'number' ? size : parseInt(size, 10) || 20;
797
+ return <Component size={numSize} color={color} className={className} />;
798
+ };
799
+