@timmbr/native-ui 0.0.0-stage → 1.0.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 (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 +1019 -0
  212. package/README.md +0 -3
@@ -0,0 +1,1019 @@
1
+ import React, { createContext, useCallback, 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 = useCallback((newMode: ThemeMode) => {
69
+ setUncontrolledMode(newMode);
70
+ }, []);
71
+
72
+ const toggleTheme = useCallback(() => {
73
+ setUncontrolledMode((prev) => {
74
+ const active = prev === 'system' ? (systemScheme === 'dark' ? 'dark' : 'light') : prev;
75
+ return active === 'dark' ? 'light' : 'dark';
76
+ });
77
+ }, [systemScheme]);
78
+
79
+ const contextValue = useMemo(
80
+ () => ({
81
+ mode: currentMode,
82
+ resolvedTheme,
83
+ setMode,
84
+ toggleTheme,
85
+ }),
86
+ [currentMode, resolvedTheme, setMode, toggleTheme]
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' as NonNullable<HeadingProps['size']>,
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
+ const NON_COLOR_TEXT_UTILITY =
212
+ /^(?:dark:)?text-(xs|sm|base|lg|xl|2xl|3xl|4xl|left|center|right|justify|auto|\[\d+(px|rem|em)?\])$/;
213
+
214
+ function hasExplicitTextColorClass(className: string): boolean {
215
+ return className
216
+ .split(/\s+/)
217
+ .filter(Boolean)
218
+ .some((token) => /^(?:dark:)?text-/.test(token) && !NON_COLOR_TEXT_UTILITY.test(token));
219
+ }
220
+
221
+ export type ButtonVariant = 'solid' | 'outline' | 'link';
222
+ export type ButtonAction = 'primary' | 'secondary' | 'positive' | 'negative' | 'default';
223
+ export type ButtonSize = 'sm' | 'md' | 'lg';
224
+
225
+ interface ButtonStyleContextValue {
226
+ variant: ButtonVariant;
227
+ action: ButtonAction;
228
+ }
229
+
230
+ const ButtonStyleContext = createContext<ButtonStyleContextValue>({
231
+ variant: 'solid',
232
+ action: 'primary',
233
+ });
234
+
235
+ export interface ButtonProps extends Omit<PressableProps, 'children'> {
236
+ className?: string;
237
+ variant?: ButtonVariant;
238
+ action?: ButtonAction;
239
+ size?: ButtonSize;
240
+ isDisabled?: boolean;
241
+ isLoading?: boolean;
242
+ children?: React.ReactNode;
243
+ }
244
+
245
+ const buttonVariantStyles: Record<string, Record<string, string>> = {
246
+ solid: {
247
+ primary: 'bg-primary border-primary',
248
+ secondary: 'bg-secondary border-border',
249
+ positive: 'bg-success border-success',
250
+ negative: 'bg-destructive border-destructive',
251
+ default: 'bg-secondary border-border',
252
+ },
253
+ outline: {
254
+ primary: 'bg-transparent border-primary',
255
+ secondary: 'bg-transparent border-border',
256
+ positive: 'bg-transparent border-success',
257
+ negative: 'bg-transparent border-destructive',
258
+ default: 'bg-transparent border-border',
259
+ },
260
+ link: {
261
+ primary: 'bg-transparent border-transparent',
262
+ secondary: 'bg-transparent border-transparent',
263
+ positive: 'bg-transparent border-transparent',
264
+ negative: 'bg-transparent border-transparent',
265
+ default: 'bg-transparent border-transparent',
266
+ },
267
+ };
268
+
269
+ const buttonTextColorStyles: Record<string, Record<string, string>> = {
270
+ solid: {
271
+ primary: 'text-primary-foreground',
272
+ secondary: 'text-secondary-foreground',
273
+ positive: 'text-primary-foreground',
274
+ negative: 'text-primary-foreground',
275
+ default: 'text-secondary-foreground',
276
+ },
277
+ outline: {
278
+ primary: 'text-primary',
279
+ secondary: 'text-foreground',
280
+ positive: 'text-emerald-600 dark:text-emerald-400',
281
+ negative: 'text-destructive',
282
+ default: 'text-foreground',
283
+ },
284
+ link: {
285
+ primary: 'text-primary',
286
+ secondary: 'text-foreground',
287
+ positive: 'text-emerald-600 dark:text-emerald-400',
288
+ negative: 'text-destructive',
289
+ default: 'text-primary',
290
+ },
291
+ };
292
+
293
+ function resolveButtonSpinnerColor(
294
+ variant: ButtonVariant,
295
+ action: ButtonAction,
296
+ resolvedTheme: ResolvedTheme
297
+ ): string {
298
+ if (variant === 'solid' && action !== 'secondary' && action !== 'default') {
299
+ return '#FFFFFF';
300
+ }
301
+ if (variant === 'solid') {
302
+ return resolvedTheme === 'dark' ? '#F5EDE0' : '#60321D';
303
+ }
304
+ if (action === 'primary') return '#C0643A';
305
+ if (action === 'positive') return '#059669';
306
+ if (action === 'negative') return resolvedTheme === 'dark' ? '#EF4444' : '#DC2626';
307
+ return resolvedTheme === 'dark' ? '#F7F1E6' : '#0B0B0B';
308
+ }
309
+
310
+ const buttonSizeStyles: Record<string, string> = {
311
+ sm: 'px-3 py-1.5 rounded-md min-h-[36px]',
312
+ md: 'px-4 py-2.5 rounded-lg min-h-[44px]',
313
+ lg: 'px-6 py-3.5 rounded-xl min-h-[52px]',
314
+ };
315
+
316
+ export const Button: React.FC<ButtonProps> = ({
317
+ variant = 'solid' as ButtonVariant,
318
+ action = 'primary' as ButtonAction,
319
+ size = 'md' as ButtonSize,
320
+ isDisabled = false,
321
+ isLoading = false,
322
+ disabled,
323
+ className = '',
324
+ children,
325
+ ...props
326
+ }) => {
327
+ const { resolvedTheme } = useTimmbrNative();
328
+ const isActuallyDisabled = isDisabled || isLoading || disabled;
329
+ const variantClass = buttonVariantStyles[variant]?.[action] || buttonVariantStyles.solid.primary;
330
+ const sizeClass = buttonSizeStyles[size] || buttonSizeStyles.md;
331
+ const disabledClass = isActuallyDisabled ? 'opacity-50' : 'active:opacity-85';
332
+ const spinnerColor = resolveButtonSpinnerColor(variant, action, resolvedTheme);
333
+ const styleValue = useMemo(() => ({ variant, action }), [variant, action]);
334
+
335
+ return (
336
+ <ButtonStyleContext.Provider value={styleValue}>
337
+ <RNPressable
338
+ disabled={isActuallyDisabled}
339
+ className={`flex-row items-center justify-center border ${variantClass} ${sizeClass} ${disabledClass} ${className}`}
340
+ {...props}
341
+ >
342
+ {isLoading && <ActivityIndicator color={spinnerColor} className="mr-2" size="small" />}
343
+ {children}
344
+ </RNPressable>
345
+ </ButtonStyleContext.Provider>
346
+ );
347
+ };
348
+
349
+ export interface ButtonTextProps extends TextPropsWithClass {
350
+ action?: 'primary' | 'secondary' | 'positive' | 'negative' | 'default';
351
+ variant?: 'solid' | 'outline' | 'link';
352
+ }
353
+
354
+ export const ButtonText: React.FC<ButtonTextProps> = ({
355
+ variant: variantProp,
356
+ action: actionProp,
357
+ className = '',
358
+ children,
359
+ ...props
360
+ }) => {
361
+ const parentStyle = useContext(ButtonStyleContext);
362
+ const variant = variantProp ?? parentStyle.variant;
363
+ const action = actionProp ?? parentStyle.action;
364
+ const defaultColorClass = hasExplicitTextColorClass(className)
365
+ ? ''
366
+ : buttonTextColorStyles[variant]?.[action] || 'text-primary-foreground';
367
+
368
+ return (
369
+ <RNText
370
+ className={`text-center font-semibold font-sans text-sm ${defaultColorClass} ${className}`}
371
+ {...props}
372
+ >
373
+ {children}
374
+ </RNText>
375
+ );
376
+ };
377
+
378
+ export interface ButtonSpinnerProps extends ActivityIndicatorProps {
379
+ className?: string;
380
+ }
381
+
382
+ export const ButtonSpinner: React.FC<ButtonSpinnerProps> = ({
383
+ color,
384
+ size = 'small',
385
+ className = 'mr-2',
386
+ ...props
387
+ }) => {
388
+ const { resolvedTheme } = useTimmbrNative();
389
+ const parentStyle = useContext(ButtonStyleContext);
390
+ const spinnerColor =
391
+ color ?? resolveButtonSpinnerColor(parentStyle.variant, parentStyle.action, resolvedTheme);
392
+
393
+ return <ActivityIndicator color={spinnerColor} size={size} className={className} {...props} />;
394
+ };
395
+
396
+ export type BadgeVariant = 'solid' | 'outline';
397
+ export type BadgeAction = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'muted';
398
+
399
+ interface BadgeStyleContextValue {
400
+ variant: BadgeVariant;
401
+ action: BadgeAction;
402
+ }
403
+
404
+ const BadgeStyleContext = createContext<BadgeStyleContextValue>({
405
+ variant: 'solid',
406
+ action: 'primary',
407
+ });
408
+
409
+ export interface BadgeProps extends ViewProps {
410
+ className?: string;
411
+ variant?: BadgeVariant;
412
+ action?: BadgeAction;
413
+ }
414
+
415
+ const badgeActionStyles: Record<string, Record<string, string>> = {
416
+ solid: {
417
+ primary: 'bg-primary border-primary',
418
+ secondary: 'bg-secondary border-border',
419
+ success: 'bg-emerald-600 border-emerald-600',
420
+ warning: 'bg-amber-600 border-amber-600',
421
+ error: 'bg-destructive border-destructive',
422
+ info: 'bg-blue-600 border-blue-600',
423
+ muted: 'bg-muted border-border',
424
+ },
425
+ outline: {
426
+ primary: 'bg-transparent border-primary',
427
+ secondary: 'bg-transparent border-border',
428
+ success: 'bg-transparent border-emerald-600',
429
+ warning: 'bg-transparent border-amber-600',
430
+ error: 'bg-transparent border-destructive',
431
+ info: 'bg-transparent border-blue-600',
432
+ muted: 'bg-transparent border-border',
433
+ },
434
+ };
435
+
436
+ const badgeTextColorStyles: Record<string, Record<string, string>> = {
437
+ solid: {
438
+ primary: 'text-primary-foreground',
439
+ secondary: 'text-secondary-foreground',
440
+ success: 'text-primary-foreground',
441
+ warning: 'text-primary-foreground',
442
+ error: 'text-primary-foreground',
443
+ info: 'text-primary-foreground',
444
+ muted: 'text-foreground',
445
+ },
446
+ outline: {
447
+ primary: 'text-primary',
448
+ secondary: 'text-foreground',
449
+ success: 'text-emerald-600 dark:text-emerald-400',
450
+ warning: 'text-amber-600 dark:text-amber-400',
451
+ error: 'text-destructive',
452
+ info: 'text-blue-600 dark:text-blue-400',
453
+ muted: 'text-muted-foreground',
454
+ },
455
+ };
456
+
457
+ export const Badge: React.FC<BadgeProps> = ({
458
+ variant = 'solid' as BadgeVariant,
459
+ action = 'primary' as BadgeAction,
460
+ className = '',
461
+ children,
462
+ ...props
463
+ }) => {
464
+ const badgeClass = badgeActionStyles[variant]?.[action] || badgeActionStyles.solid.primary;
465
+ const styleValue = useMemo(() => ({ variant, action }), [variant, action]);
466
+
467
+ return (
468
+ <BadgeStyleContext.Provider value={styleValue}>
469
+ <View
470
+ className={`px-2.5 py-0.5 rounded-full border items-center justify-center flex-row ${badgeClass} ${className}`}
471
+ {...props}
472
+ >
473
+ {children}
474
+ </View>
475
+ </BadgeStyleContext.Provider>
476
+ );
477
+ };
478
+
479
+ export const BadgeText: React.FC<TextPropsWithClass> = ({
480
+ className = '',
481
+ children,
482
+ ...props
483
+ }) => {
484
+ const parentStyle = useContext(BadgeStyleContext);
485
+ const defaultColorClass = hasExplicitTextColorClass(className)
486
+ ? ''
487
+ : badgeTextColorStyles[parentStyle.variant]?.[parentStyle.action] || 'text-primary-foreground';
488
+
489
+ return (
490
+ <RNText
491
+ className={`text-xs font-semibold font-sans ${defaultColorClass} ${className}`}
492
+ {...props}
493
+ >
494
+ {children}
495
+ </RNText>
496
+ );
497
+ };
498
+
499
+ export const Card: React.FC<BoxProps> = ({ className = '', children, ...props }) => (
500
+ <View
501
+ className={`rounded-xl p-4 bg-card border border-border shadow-sm ${className}`}
502
+ {...props}
503
+ >
504
+ {children}
505
+ </View>
506
+ );
507
+
508
+ export interface InputProps extends ViewProps {
509
+ className?: string;
510
+ isInvalid?: boolean;
511
+ isDisabled?: boolean;
512
+ }
513
+
514
+ export const Input: React.FC<InputProps> = ({
515
+ isInvalid = false,
516
+ isDisabled = false,
517
+ className = '',
518
+ children,
519
+ ...props
520
+ }) => {
521
+ const stateClass = isInvalid
522
+ ? 'border-destructive'
523
+ : isDisabled
524
+ ? 'border-border opacity-50'
525
+ : 'border-border';
526
+ return (
527
+ <View
528
+ className={`flex-row items-center rounded-lg border bg-background px-3 py-2 min-h-[44px] ${stateClass} ${className}`}
529
+ {...props}
530
+ >
531
+ {children}
532
+ </View>
533
+ );
534
+ };
535
+
536
+ export interface InputFieldProps extends TextInputProps {
537
+ className?: string;
538
+ }
539
+
540
+ export const InputField: React.FC<InputFieldProps> = ({
541
+ className = '',
542
+ placeholderTextColor,
543
+ ...props
544
+ }) => {
545
+ const { resolvedTheme } = useTimmbrNative();
546
+ const defaultPlaceholderColor = resolvedTheme === 'dark' ? '#9D9D9D' : '#6D6D6D';
547
+ return (
548
+ <TextInput
549
+ className={`flex-1 text-foreground placeholder:text-muted-foreground font-sans text-base py-1 ${className}`}
550
+ placeholderTextColor={placeholderTextColor ?? defaultPlaceholderColor}
551
+ {...props}
552
+ />
553
+ );
554
+ };
555
+
556
+ export const Textarea: React.FC<InputProps> = ({
557
+ isInvalid = false,
558
+ isDisabled = false,
559
+ className = '',
560
+ children,
561
+ ...props
562
+ }) => {
563
+ const stateClass = isInvalid
564
+ ? 'border-destructive'
565
+ : isDisabled
566
+ ? 'border-border opacity-50'
567
+ : 'border-border';
568
+ return (
569
+ <View
570
+ className={`rounded-lg border bg-background p-3 min-h-[100px] ${stateClass} ${className}`}
571
+ {...props}
572
+ >
573
+ {children}
574
+ </View>
575
+ );
576
+ };
577
+
578
+ export const TextareaInput: React.FC<InputFieldProps> = ({
579
+ className = '',
580
+ placeholderTextColor,
581
+ ...props
582
+ }) => {
583
+ const { resolvedTheme } = useTimmbrNative();
584
+ const defaultPlaceholderColor = resolvedTheme === 'dark' ? '#9D9D9D' : '#6D6D6D';
585
+ return (
586
+ <TextInput
587
+ multiline
588
+ textAlignVertical="top"
589
+ className={`flex-1 text-foreground placeholder:text-muted-foreground font-sans text-base ${className}`}
590
+ placeholderTextColor={placeholderTextColor ?? defaultPlaceholderColor}
591
+ {...props}
592
+ />
593
+ );
594
+ };
595
+
596
+ interface TabsContextValue {
597
+ activeTab: string;
598
+ setActiveTab: (tab: string) => void;
599
+ }
600
+
601
+ const TabsContext = createContext<TabsContextValue>({
602
+ activeTab: '',
603
+ setActiveTab: () => {},
604
+ });
605
+
606
+ export interface TabsProps extends ViewProps {
607
+ defaultValue?: string;
608
+ value?: string;
609
+ onValueChange?: (value: string) => void;
610
+ className?: string;
611
+ }
612
+
613
+ export const Tabs: React.FC<TabsProps> = ({
614
+ defaultValue = '',
615
+ value: controlledValue,
616
+ onValueChange,
617
+ className = '',
618
+ children,
619
+ ...props
620
+ }) => {
621
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
622
+ const activeTab = controlledValue ?? uncontrolledValue;
623
+
624
+ const setActiveTab = (newTab: string) => {
625
+ setUncontrolledValue(newTab);
626
+ onValueChange?.(newTab);
627
+ };
628
+
629
+ return (
630
+ <TabsContext.Provider value={{ activeTab, setActiveTab }}>
631
+ <View className={`w-full ${className}`} {...props}>
632
+ {children}
633
+ </View>
634
+ </TabsContext.Provider>
635
+ );
636
+ };
637
+
638
+ export const TabsList: React.FC<BoxProps> = ({ className = '', children, ...props }) => (
639
+ <View
640
+ className={`flex-row rounded-lg bg-secondary p-1 border border-border ${className}`}
641
+ {...props}
642
+ >
643
+ {children}
644
+ </View>
645
+ );
646
+
647
+ export interface TabsTriggerProps extends PressableProps {
648
+ value: string;
649
+ className?: string;
650
+ }
651
+
652
+ export const TabsTrigger: React.FC<TabsTriggerProps> = ({
653
+ value,
654
+ className = '',
655
+ children,
656
+ ...props
657
+ }) => {
658
+ const { activeTab, setActiveTab } = useContext(TabsContext);
659
+ const isActive = activeTab === value;
660
+
661
+ return (
662
+ <RNPressable
663
+ onPress={() => setActiveTab(value)}
664
+ className={`flex-1 py-2 px-3 rounded-md items-center justify-center ${
665
+ isActive ? 'bg-card shadow-sm border border-border' : 'opacity-70'
666
+ } ${className}`}
667
+ {...props}
668
+ >
669
+ {children}
670
+ </RNPressable>
671
+ );
672
+ };
673
+
674
+ export const TabsTriggerText: React.FC<TextPropsWithClass> = ({
675
+ className = '',
676
+ children,
677
+ ...props
678
+ }) => (
679
+ <RNText
680
+ className={`text-sm font-semibold text-foreground font-sans ${className}`}
681
+ {...props}
682
+ >
683
+ {children}
684
+ </RNText>
685
+ );
686
+
687
+ export interface TabsContentProps extends ViewProps {
688
+ value: string;
689
+ className?: string;
690
+ }
691
+
692
+ export const TabsContent: React.FC<TabsContentProps> = ({
693
+ value,
694
+ className = '',
695
+ children,
696
+ ...props
697
+ }) => {
698
+ const { activeTab } = useContext(TabsContext);
699
+ if (activeTab !== value) return null;
700
+
701
+ return (
702
+ <View className={`mt-3 ${className}`} {...props}>
703
+ {children}
704
+ </View>
705
+ );
706
+ };
707
+
708
+ export interface ActionsheetProps {
709
+ isOpen: boolean;
710
+ onClose: () => void;
711
+ children: React.ReactNode;
712
+ }
713
+
714
+ export const Actionsheet: React.FC<ActionsheetProps> = ({ isOpen, onClose, children }) => {
715
+ const { resolvedTheme } = useTimmbrNative();
716
+ return (
717
+ <RNModal
718
+ visible={isOpen}
719
+ transparent
720
+ animationType="fade"
721
+ onRequestClose={onClose}
722
+ >
723
+ <View className={`flex-1 justify-end ${resolvedTheme === 'dark' ? 'dark' : ''}`}>
724
+ {children}
725
+ </View>
726
+ </RNModal>
727
+ );
728
+ };
729
+
730
+ export const ActionsheetBackdrop: React.FC<PressableProps & { className?: string }> = ({
731
+ onPress,
732
+ className = '',
733
+ }) => (
734
+ <RNPressable
735
+ onPress={onPress}
736
+ className={`absolute inset-0 bg-black/60 ${className}`}
737
+ />
738
+ );
739
+
740
+ export const ActionsheetContent: React.FC<BoxProps> = ({
741
+ className = '',
742
+ children,
743
+ ...props
744
+ }) => (
745
+ <View
746
+ className={`bg-card rounded-t-3xl p-5 border-t border-border shadow-lg max-h-[85%] ${className}`}
747
+ {...props}
748
+ >
749
+ {children}
750
+ </View>
751
+ );
752
+
753
+ export const ActionsheetDragIndicator: React.FC<BoxProps> = ({
754
+ className = '',
755
+ ...props
756
+ }) => (
757
+ <View className="items-center pb-3">
758
+ <View className={`w-12 h-1.5 rounded-full bg-muted ${className}`} {...props} />
759
+ </View>
760
+ );
761
+
762
+ export interface ActionsheetItemProps extends PressableProps {
763
+ className?: string;
764
+ }
765
+
766
+ export const ActionsheetItem: React.FC<ActionsheetItemProps> = ({
767
+ className = '',
768
+ children,
769
+ ...props
770
+ }) => (
771
+ <RNPressable
772
+ className={`py-3.5 px-4 rounded-xl active:bg-secondary flex-row items-center ${className}`}
773
+ {...props}
774
+ >
775
+ {children}
776
+ </RNPressable>
777
+ );
778
+
779
+ export const ActionsheetItemText: React.FC<TextPropsWithClass> = ({
780
+ className = '',
781
+ children,
782
+ ...props
783
+ }) => (
784
+ <RNText className={`text-base font-sans text-foreground ${className}`} {...props}>
785
+ {children}
786
+ </RNText>
787
+ );
788
+
789
+ export interface CustomModalProps extends ModalProps {
790
+ isOpen?: boolean;
791
+ onClose?: () => void;
792
+ className?: string;
793
+ }
794
+
795
+ export const Modal: React.FC<CustomModalProps> = ({
796
+ isOpen = false,
797
+ visible,
798
+ onClose,
799
+ className = '',
800
+ children,
801
+ ...props
802
+ }) => {
803
+ const { resolvedTheme } = useTimmbrNative();
804
+ return (
805
+ <RNModal
806
+ visible={isOpen || visible}
807
+ transparent
808
+ animationType="fade"
809
+ onRequestClose={onClose}
810
+ {...props}
811
+ >
812
+ <View
813
+ className={`flex-1 items-center justify-center bg-black/60 p-4 ${
814
+ resolvedTheme === 'dark' ? 'dark' : ''
815
+ } ${className}`}
816
+ >
817
+ {children}
818
+ </View>
819
+ </RNModal>
820
+ );
821
+ };
822
+
823
+ export interface CustomSwitchProps extends SwitchProps {
824
+ className?: string;
825
+ }
826
+
827
+ export const Switch: React.FC<CustomSwitchProps> = ({
828
+ trackColor,
829
+ thumbColor = '#FFFFFF',
830
+ className = '',
831
+ ...props
832
+ }) => {
833
+ const { resolvedTheme } = useTimmbrNative();
834
+ const defaultTrackColor = {
835
+ false: resolvedTheme === 'dark' ? '#3C3C3C' : '#DDD5CA',
836
+ true: '#C0643A',
837
+ };
838
+ return (
839
+ <RNSwitch
840
+ trackColor={trackColor ?? defaultTrackColor}
841
+ thumbColor={thumbColor}
842
+ className={className}
843
+ {...props}
844
+ />
845
+ );
846
+ };
847
+
848
+ export const Spinner: React.FC<ButtonSpinnerProps> = ({
849
+ color = '#C0643A',
850
+ size = 'small',
851
+ className = '',
852
+ ...props
853
+ }) => <ActivityIndicator color={color} size={size} className={className} {...props} />;
854
+
855
+ export interface AvatarProps extends ViewProps {
856
+ size?: 'sm' | 'md' | 'lg' | 'xl';
857
+ className?: string;
858
+ }
859
+
860
+ const avatarSizeMap: Record<string, string> = {
861
+ sm: 'w-8 h-8 rounded-full',
862
+ md: 'w-10 h-10 rounded-full',
863
+ lg: 'w-14 h-14 rounded-full',
864
+ xl: 'w-20 h-20 rounded-full',
865
+ };
866
+
867
+ export const Avatar: React.FC<AvatarProps> = ({
868
+ size = 'md',
869
+ className = '',
870
+ children,
871
+ ...props
872
+ }) => (
873
+ <View
874
+ className={`items-center justify-center bg-secondary border border-border overflow-hidden ${avatarSizeMap[size]} ${className}`}
875
+ {...props}
876
+ >
877
+ {children}
878
+ </View>
879
+ );
880
+
881
+ export const AvatarFallbackText: React.FC<TextPropsWithClass> = ({
882
+ className = '',
883
+ children,
884
+ ...props
885
+ }) => (
886
+ <RNText
887
+ className={`font-bold font-sans text-secondary-foreground text-sm ${className}`}
888
+ {...props}
889
+ >
890
+ {children}
891
+ </RNText>
892
+ );
893
+
894
+ export const Divider: React.FC<BoxProps> = ({ className = '', ...props }) => (
895
+ <View className={`h-px bg-border my-2 w-full ${className}`} {...props} />
896
+ );
897
+
898
+ export const Pressable: React.FC<PressableProps & { className?: string }> = ({
899
+ className = '',
900
+ children,
901
+ ...props
902
+ }) => (
903
+ <RNPressable className={className} {...props}>
904
+ {children}
905
+ </RNPressable>
906
+ );
907
+
908
+ export interface ToastProps extends ViewProps {
909
+ action?: 'success' | 'warning' | 'error' | 'info';
910
+ title?: string;
911
+ description?: string;
912
+ className?: string;
913
+ }
914
+
915
+ export const Toast: React.FC<ToastProps> = ({
916
+ action = 'info',
917
+ title,
918
+ description,
919
+ className = '',
920
+ children,
921
+ ...props
922
+ }) => {
923
+ const borderColors: Record<string, string> = {
924
+ success: 'border-emerald-600',
925
+ warning: 'border-amber-600',
926
+ error: 'border-destructive',
927
+ info: 'border-blue-600',
928
+ };
929
+ return (
930
+ <View
931
+ className={`p-4 rounded-xl bg-card border-l-4 shadow-md ${borderColors[action]} ${className}`}
932
+ {...props}
933
+ >
934
+ {title && <RNText className="font-bold text-foreground">{title}</RNText>}
935
+ {description && <RNText className="text-sm text-muted-foreground mt-1">{description}</RNText>}
936
+ {children}
937
+ </View>
938
+ );
939
+ };
940
+
941
+ export interface IconProps {
942
+ as?: React.ElementType;
943
+ size?: number | string;
944
+ color?: string;
945
+ className?: string;
946
+ }
947
+
948
+ function getSemanticIconColorMap(resolvedTheme: ResolvedTheme): Record<string, string> {
949
+ const isDark = resolvedTheme === 'dark';
950
+ return {
951
+ 'text-foreground': isDark ? '#F7F1E6' : '#0B0B0B',
952
+ 'text-card-foreground': isDark ? '#F7F1E6' : '#0B0B0B',
953
+ 'text-muted-foreground': isDark ? '#9D9D9D' : '#6D6D6D',
954
+ 'text-secondary-foreground': isDark ? '#F5EDE0' : '#60321D',
955
+ 'text-accent-foreground': isDark ? '#F5EDE0' : '#60321D',
956
+ 'text-primary': '#C0643A',
957
+ 'text-primary-foreground': '#FFFFFF',
958
+ 'text-white': '#FFFFFF',
959
+ 'text-destructive': isDark ? '#EF4444' : '#DC2626',
960
+ 'text-destructive-foreground': isDark ? '#F7F1E6' : '#FFFFFF',
961
+ 'text-success': '#059669',
962
+ 'text-emerald-500': '#10B981',
963
+ 'text-emerald-600': '#059669',
964
+ 'text-warning': '#D97706',
965
+ 'text-amber-400': '#FBBF24',
966
+ 'text-amber-500': '#F59E0B',
967
+ 'text-amber-600': '#D97706',
968
+ 'text-info': '#2563EB',
969
+ 'text-blue-500': '#3B82F6',
970
+ 'text-blue-600': '#2563EB',
971
+ };
972
+ }
973
+
974
+ function resolveIconColor(
975
+ explicitColor: string | undefined,
976
+ className: string,
977
+ resolvedTheme: ResolvedTheme
978
+ ): string {
979
+ if (explicitColor && explicitColor !== 'currentColor') {
980
+ return explicitColor;
981
+ }
982
+
983
+ const tokens = className.split(/\s+/).filter(Boolean);
984
+ const colorMap = getSemanticIconColorMap(resolvedTheme);
985
+
986
+ if (resolvedTheme === 'dark') {
987
+ for (const token of tokens) {
988
+ if (token.startsWith('dark:')) {
989
+ const unprefixed = token.slice('dark:'.length);
990
+ const hexMatch = /^text-\[(#[0-9a-fA-F]{3,8})\]$/.exec(unprefixed);
991
+ if (hexMatch?.[1]) return hexMatch[1];
992
+ if (colorMap[unprefixed]) return colorMap[unprefixed];
993
+ }
994
+ }
995
+ }
996
+
997
+ for (const token of tokens) {
998
+ if (token.startsWith('dark:')) continue;
999
+ const hexMatch = /^text-\[(#[0-9a-fA-F]{3,8})\]$/.exec(token);
1000
+ if (hexMatch?.[1]) return hexMatch[1];
1001
+ if (colorMap[token]) return colorMap[token];
1002
+ }
1003
+
1004
+ return resolvedTheme === 'dark' ? '#F7F1E6' : '#0B0B0B';
1005
+ }
1006
+
1007
+ export const Icon: React.FC<IconProps> = ({
1008
+ as: Component,
1009
+ size = 20,
1010
+ color,
1011
+ className = '',
1012
+ }) => {
1013
+ const { resolvedTheme } = useTimmbrNative();
1014
+ if (!Component) return null;
1015
+ const numSize = typeof size === 'number' ? size : parseInt(size, 10) || 20;
1016
+ const resolvedColor = resolveIconColor(color, className, resolvedTheme);
1017
+ return <Component size={numSize} color={resolvedColor} className={className} />;
1018
+ };
1019
+