@campxdev/react-native-blueprint 0.1.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 (178) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +358 -0
  3. package/lib/module/app/_layout.js +23 -0
  4. package/lib/module/app/_layout.js.map +1 -0
  5. package/lib/module/assets/icons/weather_icons/drizzle.png +0 -0
  6. package/lib/module/assets/icons/weather_icons/foggy.png +0 -0
  7. package/lib/module/assets/icons/weather_icons/freezing_rain.png +0 -0
  8. package/lib/module/assets/icons/weather_icons/partly_cloudy.png +0 -0
  9. package/lib/module/assets/icons/weather_icons/rainy.png +0 -0
  10. package/lib/module/assets/icons/weather_icons/showers.png +0 -0
  11. package/lib/module/assets/icons/weather_icons/sunny_weather.png +0 -0
  12. package/lib/module/assets/icons/weather_icons/thunderstorm.png +0 -0
  13. package/lib/module/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
  14. package/lib/module/components/theme-config.js +265 -0
  15. package/lib/module/components/theme-config.js.map +1 -0
  16. package/lib/module/components/ui/Accordion.js +228 -0
  17. package/lib/module/components/ui/Accordion.js.map +1 -0
  18. package/lib/module/components/ui/Alert-Dialog.js +266 -0
  19. package/lib/module/components/ui/Alert-Dialog.js.map +1 -0
  20. package/lib/module/components/ui/Alert.js +107 -0
  21. package/lib/module/components/ui/Alert.js.map +1 -0
  22. package/lib/module/components/ui/AppBar.js +403 -0
  23. package/lib/module/components/ui/AppBar.js.map +1 -0
  24. package/lib/module/components/ui/Aspect-Ratio.js +27 -0
  25. package/lib/module/components/ui/Aspect-Ratio.js.map +1 -0
  26. package/lib/module/components/ui/Avatar.js +97 -0
  27. package/lib/module/components/ui/Avatar.js.map +1 -0
  28. package/lib/module/components/ui/Badge.js +127 -0
  29. package/lib/module/components/ui/Badge.js.map +1 -0
  30. package/lib/module/components/ui/Bottom-Sheet.js +144 -0
  31. package/lib/module/components/ui/Bottom-Sheet.js.map +1 -0
  32. package/lib/module/components/ui/Button.js +88 -0
  33. package/lib/module/components/ui/Button.js.map +1 -0
  34. package/lib/module/components/ui/Card.js +176 -0
  35. package/lib/module/components/ui/Card.js.map +1 -0
  36. package/lib/module/components/ui/Checkbox.js +65 -0
  37. package/lib/module/components/ui/Checkbox.js.map +1 -0
  38. package/lib/module/components/ui/Collapsible.js +42 -0
  39. package/lib/module/components/ui/Collapsible.js.map +1 -0
  40. package/lib/module/components/ui/Context-Menu.js +287 -0
  41. package/lib/module/components/ui/Context-Menu.js.map +1 -0
  42. package/lib/module/components/ui/Custom-Card.js +202 -0
  43. package/lib/module/components/ui/Custom-Card.js.map +1 -0
  44. package/lib/module/components/ui/Dialog.js +202 -0
  45. package/lib/module/components/ui/Dialog.js.map +1 -0
  46. package/lib/module/components/ui/Dropdown-Menu.js +421 -0
  47. package/lib/module/components/ui/Dropdown-Menu.js.map +1 -0
  48. package/lib/module/components/ui/Floating-Action.js +50 -0
  49. package/lib/module/components/ui/Floating-Action.js.map +1 -0
  50. package/lib/module/components/ui/Greeting-Card.js +392 -0
  51. package/lib/module/components/ui/Greeting-Card.js.map +1 -0
  52. package/lib/module/components/ui/Hover-Card.js +96 -0
  53. package/lib/module/components/ui/Hover-Card.js.map +1 -0
  54. package/lib/module/components/ui/Icon.js +73 -0
  55. package/lib/module/components/ui/Icon.js.map +1 -0
  56. package/lib/module/components/ui/Input.js +74 -0
  57. package/lib/module/components/ui/Input.js.map +1 -0
  58. package/lib/module/components/ui/Label.js +44 -0
  59. package/lib/module/components/ui/Label.js.map +1 -0
  60. package/lib/module/components/ui/Menubar.js +375 -0
  61. package/lib/module/components/ui/Menubar.js.map +1 -0
  62. package/lib/module/components/ui/Native-Only-Animated-View.js +41 -0
  63. package/lib/module/components/ui/Native-Only-Animated-View.js.map +1 -0
  64. package/lib/module/components/ui/NavBar.js +352 -0
  65. package/lib/module/components/ui/NavBar.js.map +1 -0
  66. package/lib/module/components/ui/Popover.js +101 -0
  67. package/lib/module/components/ui/Popover.js.map +1 -0
  68. package/lib/module/components/ui/Progress.js +124 -0
  69. package/lib/module/components/ui/Progress.js.map +1 -0
  70. package/lib/module/components/ui/Radio-Group.js +75 -0
  71. package/lib/module/components/ui/Radio-Group.js.map +1 -0
  72. package/lib/module/components/ui/Select.js +269 -0
  73. package/lib/module/components/ui/Select.js.map +1 -0
  74. package/lib/module/components/ui/Separator.js +58 -0
  75. package/lib/module/components/ui/Separator.js.map +1 -0
  76. package/lib/module/components/ui/SizedBox.js +101 -0
  77. package/lib/module/components/ui/SizedBox.js.map +1 -0
  78. package/lib/module/components/ui/Skeleton.js +57 -0
  79. package/lib/module/components/ui/Skeleton.js.map +1 -0
  80. package/lib/module/components/ui/Slider.js +169 -0
  81. package/lib/module/components/ui/Slider.js.map +1 -0
  82. package/lib/module/components/ui/Switch.js +55 -0
  83. package/lib/module/components/ui/Switch.js.map +1 -0
  84. package/lib/module/components/ui/Table.js +150 -0
  85. package/lib/module/components/ui/Table.js.map +1 -0
  86. package/lib/module/components/ui/Tabs.js +106 -0
  87. package/lib/module/components/ui/Tabs.js.map +1 -0
  88. package/lib/module/components/ui/Text.js +69 -0
  89. package/lib/module/components/ui/Text.js.map +1 -0
  90. package/lib/module/components/ui/Textarea.js +88 -0
  91. package/lib/module/components/ui/Textarea.js.map +1 -0
  92. package/lib/module/components/ui/Theme-Toggle.js +156 -0
  93. package/lib/module/components/ui/Theme-Toggle.js.map +1 -0
  94. package/lib/module/components/ui/Toast.js +101 -0
  95. package/lib/module/components/ui/Toast.js.map +1 -0
  96. package/lib/module/components/ui/Toggle-Group.js +129 -0
  97. package/lib/module/components/ui/Toggle-Group.js.map +1 -0
  98. package/lib/module/components/ui/Toggle.js +106 -0
  99. package/lib/module/components/ui/Toggle.js.map +1 -0
  100. package/lib/module/components/ui/Tooltip.js +106 -0
  101. package/lib/module/components/ui/Tooltip.js.map +1 -0
  102. package/lib/module/components/ui/index.js +45 -0
  103. package/lib/module/components/ui/index.js.map +1 -0
  104. package/lib/module/index.js +19 -0
  105. package/lib/module/index.js.map +1 -0
  106. package/lib/module/lib/ThemeProvider.js +173 -0
  107. package/lib/module/lib/ThemeProvider.js.map +1 -0
  108. package/lib/module/lib/cornerRadius.js +164 -0
  109. package/lib/module/lib/cornerRadius.js.map +1 -0
  110. package/lib/module/lib/fonts.js +25 -0
  111. package/lib/module/lib/fonts.js.map +1 -0
  112. package/lib/module/lib/theme.js +212 -0
  113. package/lib/module/lib/theme.js.map +1 -0
  114. package/lib/module/lib/utils.js +137 -0
  115. package/lib/module/lib/utils.js.map +1 -0
  116. package/lib/module/package.json +1 -0
  117. package/package.json +208 -0
  118. package/src/app/_layout.tsx +25 -0
  119. package/src/assets/icons/weather_icons/drizzle.png +0 -0
  120. package/src/assets/icons/weather_icons/foggy.png +0 -0
  121. package/src/assets/icons/weather_icons/freezing_rain.png +0 -0
  122. package/src/assets/icons/weather_icons/partly_cloudy.png +0 -0
  123. package/src/assets/icons/weather_icons/rainy.png +0 -0
  124. package/src/assets/icons/weather_icons/showers.png +0 -0
  125. package/src/assets/icons/weather_icons/sunny_weather.png +0 -0
  126. package/src/assets/icons/weather_icons/thunderstorm.png +0 -0
  127. package/src/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
  128. package/src/components/theme-config.ts +331 -0
  129. package/src/components/ui/Accordion.tsx +253 -0
  130. package/src/components/ui/Alert-Dialog.tsx +295 -0
  131. package/src/components/ui/Alert.tsx +137 -0
  132. package/src/components/ui/AppBar.tsx +551 -0
  133. package/src/components/ui/Aspect-Ratio.tsx +25 -0
  134. package/src/components/ui/Avatar.tsx +103 -0
  135. package/src/components/ui/Badge.tsx +121 -0
  136. package/src/components/ui/Bottom-Sheet.tsx +224 -0
  137. package/src/components/ui/Button.tsx +100 -0
  138. package/src/components/ui/Card.tsx +185 -0
  139. package/src/components/ui/Checkbox.tsx +81 -0
  140. package/src/components/ui/Collapsible.tsx +40 -0
  141. package/src/components/ui/Context-Menu.tsx +407 -0
  142. package/src/components/ui/Custom-Card.tsx +226 -0
  143. package/src/components/ui/Dialog.tsx +240 -0
  144. package/src/components/ui/Dropdown-Menu.tsx +544 -0
  145. package/src/components/ui/Floating-Action.tsx +54 -0
  146. package/src/components/ui/Greeting-Card.tsx +471 -0
  147. package/src/components/ui/Hover-Card.tsx +101 -0
  148. package/src/components/ui/Icon.tsx +75 -0
  149. package/src/components/ui/Input.tsx +90 -0
  150. package/src/components/ui/Label.tsx +48 -0
  151. package/src/components/ui/Menubar.tsx +509 -0
  152. package/src/components/ui/Native-Only-Animated-View.tsx +37 -0
  153. package/src/components/ui/NavBar.tsx +397 -0
  154. package/src/components/ui/Popover.tsx +110 -0
  155. package/src/components/ui/Progress.tsx +138 -0
  156. package/src/components/ui/Radio-Group.tsx +79 -0
  157. package/src/components/ui/Select.tsx +344 -0
  158. package/src/components/ui/Separator.tsx +68 -0
  159. package/src/components/ui/SizedBox.tsx +116 -0
  160. package/src/components/ui/Skeleton.tsx +55 -0
  161. package/src/components/ui/Slider.tsx +222 -0
  162. package/src/components/ui/Switch.tsx +67 -0
  163. package/src/components/ui/Table.tsx +170 -0
  164. package/src/components/ui/Tabs.tsx +119 -0
  165. package/src/components/ui/Text.tsx +73 -0
  166. package/src/components/ui/Textarea.tsx +93 -0
  167. package/src/components/ui/Theme-Toggle.tsx +204 -0
  168. package/src/components/ui/Toast.tsx +127 -0
  169. package/src/components/ui/Toggle-Group.tsx +160 -0
  170. package/src/components/ui/Toggle.tsx +122 -0
  171. package/src/components/ui/Tooltip.tsx +117 -0
  172. package/src/components/ui/index.ts +42 -0
  173. package/src/index.tsx +24 -0
  174. package/src/lib/ThemeProvider.tsx +204 -0
  175. package/src/lib/cornerRadius.ts +160 -0
  176. package/src/lib/fonts.ts +28 -0
  177. package/src/lib/theme.ts +151 -0
  178. package/src/lib/utils.ts +146 -0
@@ -0,0 +1,471 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Image,
4
+ Pressable,
5
+ StyleSheet,
6
+ View,
7
+ useColorScheme,
8
+ } from 'react-native';
9
+ import { cssInterop } from 'nativewind';
10
+ import { cn } from '../../lib/utils';
11
+ import { CustomCard } from './Custom-Card';
12
+ import { Text } from './Text';
13
+
14
+ cssInterop(View, { className: 'style' });
15
+ cssInterop(Pressable, { className: 'style' });
16
+
17
+ /**
18
+ * Weather data structure for the greeting card
19
+ */
20
+ export interface Weather {
21
+ /** Temperature in Celsius */
22
+ temperature: number;
23
+ /** Human-readable weather description (e.g., "Clear Sky", "Rainy") */
24
+ weatherDescription: string;
25
+ /** Path to the weather icon image */
26
+ weatherImage: any;
27
+ /** Weather code from Open-Meteo API */
28
+ weatherCode: number;
29
+ /** Timestamp of last weather update */
30
+ lastUpdated: Date;
31
+ }
32
+
33
+ /**
34
+ * Weather icon mappings for different weather conditions
35
+ */
36
+ const WEATHER_ICONS = {
37
+ sunny: require('../../assets/icons/weather_icons/sunny_weather.png'),
38
+ partlyCloudy: require('../../assets/icons/weather_icons/partly_cloudy.png'),
39
+ foggy: require('../../assets/icons/weather_icons/foggy.png'),
40
+ drizzle: require('../../assets/icons/weather_icons/drizzle.png'),
41
+ rainy: require('../../assets/icons/weather_icons/rainy.png'),
42
+ freezingRain: require('../../assets/icons/weather_icons/freezing_rain.png'),
43
+ showers: require('../../assets/icons/weather_icons/showers.png'),
44
+ thunderstorm: require('../../assets/icons/weather_icons/thunderstorm.png'),
45
+ thunderstormHail: require('../../assets/icons/weather_icons/thunderstorm_hail.png'),
46
+ };
47
+
48
+ /**
49
+ * Converts a weather code from Open-Meteo API to a description and icon
50
+ *
51
+ * @param {number} code - Weather code from Open-Meteo API (0-99)
52
+ * @returns {Object} Object containing description and icon path
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * const weatherInfo = getWeatherDescription(0);
57
+ * // Returns: { description: 'Clear Sky (Sunny)', weatherImage: ... }
58
+ * ```
59
+ */
60
+ export function getWeatherDescription(code: number): {
61
+ description: string;
62
+ weatherImage: any;
63
+ } {
64
+ const weatherMap: {
65
+ [key: string]: { description: string; icon: keyof typeof WEATHER_ICONS };
66
+ } = {
67
+ '0': { description: 'Clear Sky (Sunny)', icon: 'sunny' },
68
+ '1,2,3': { description: 'Partly Cloudy', icon: 'partlyCloudy' },
69
+ '45,48': { description: 'Foggy', icon: 'foggy' },
70
+ '51,53,55': { description: 'Drizzle', icon: 'drizzle' },
71
+ '56,57': { description: 'Freezing Drizzle', icon: 'drizzle' },
72
+ '61,63,65': { description: 'Rainy', icon: 'rainy' },
73
+ '66,67': { description: 'Freezing Rain', icon: 'freezingRain' },
74
+ '80,81,82': { description: 'Showers', icon: 'showers' },
75
+ '95': { description: 'Thunderstorm', icon: 'thunderstorm' },
76
+ '96,99': {
77
+ description: 'Thunderstorm with Hail',
78
+ icon: 'thunderstormHail',
79
+ },
80
+ };
81
+
82
+ for (const [codes, info] of Object.entries(weatherMap)) {
83
+ if (codes.split(',').map(Number).includes(code)) {
84
+ return {
85
+ description: info.description,
86
+ weatherImage: WEATHER_ICONS[info.icon],
87
+ };
88
+ }
89
+ }
90
+
91
+ return {
92
+ description: 'Unknown Weather',
93
+ weatherImage: WEATHER_ICONS.sunny,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Returns a greeting based on the current time of day
99
+ *
100
+ * @returns {string} Time-appropriate greeting
101
+ *
102
+ * @example
103
+ * ```tsx
104
+ * const greeting = getTimeBasedGreeting();
105
+ * // Returns: "Good Morning" (if it's morning)
106
+ * ```
107
+ */
108
+ export function getTimeBasedGreeting(): string {
109
+ const hour = new Date().getHours();
110
+
111
+ if (hour >= 5 && hour < 12) {
112
+ return 'Good Morning';
113
+ } else if (hour >= 12 && hour < 17) {
114
+ return 'Good Afternoon';
115
+ } else if (hour >= 17 && hour < 21) {
116
+ return 'Good Evening';
117
+ } else {
118
+ return 'Good Night';
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Formats a date to a readable string format
124
+ *
125
+ * @param {Date} date - Date to format
126
+ * @returns {string} Formatted date string (e.g., "Monday, 16 October")
127
+ */
128
+ export function formatDate(date: Date): string {
129
+ const days = [
130
+ 'Sunday',
131
+ 'Monday',
132
+ 'Tuesday',
133
+ 'Wednesday',
134
+ 'Thursday',
135
+ 'Friday',
136
+ 'Saturday',
137
+ ];
138
+ const months = [
139
+ 'January',
140
+ 'February',
141
+ 'March',
142
+ 'April',
143
+ 'May',
144
+ 'June',
145
+ 'July',
146
+ 'August',
147
+ 'September',
148
+ 'October',
149
+ 'November',
150
+ 'December',
151
+ ];
152
+
153
+ const dayName = days[date.getDay()];
154
+ const day = date.getDate();
155
+ const monthName = months[date.getMonth()];
156
+
157
+ return `${dayName}, ${day} ${monthName}`;
158
+ }
159
+
160
+ /**
161
+ * Fetches weather data from Open-Meteo API
162
+ *
163
+ * @param {number} latitude - Latitude coordinate
164
+ * @param {number} longitude - Longitude coordinate
165
+ * @returns {Promise<Weather | null>} Weather data or null if fetch fails
166
+ *
167
+ * @example
168
+ * ```tsx
169
+ * const weather = await fetchWeather(37.7749, -122.4194);
170
+ * ```
171
+ */
172
+ export async function fetchWeather(
173
+ latitude: number,
174
+ longitude: number
175
+ ): Promise<Weather | null> {
176
+ const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,weathercode`;
177
+
178
+ try {
179
+ const response = await fetch(url);
180
+
181
+ if (response.ok) {
182
+ const data = await response.json();
183
+
184
+ const temperature: number = data.current?.temperature_2m ?? 0;
185
+ const weatherCode: number = data.current?.weathercode ?? 0;
186
+
187
+ const weatherInfo = getWeatherDescription(weatherCode);
188
+
189
+ return {
190
+ temperature,
191
+ weatherDescription: weatherInfo.description,
192
+ weatherImage: weatherInfo.weatherImage,
193
+ weatherCode,
194
+ lastUpdated: new Date(),
195
+ };
196
+ }
197
+
198
+ return null;
199
+ } catch (error) {
200
+ console.error('Failed to fetch weather:', error);
201
+ return null;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Props for the GreetingCard component
207
+ */
208
+ export interface GreetingCardProps {
209
+ /** User's full name to display in the greeting (required) */
210
+ userName: string;
211
+ /** Latitude coordinate for weather fetching (optional - weather shown only when both lat and lng provided) */
212
+ lat?: number;
213
+ /** Longitude coordinate for weather fetching (optional - weather shown only when both lat and lng provided) */
214
+ lng?: number;
215
+ /** Custom greeting message (overrides time-based greeting) */
216
+ customGreeting?: string;
217
+ /** Custom date to display (defaults to current date) */
218
+ date?: Date;
219
+ /** Action button text (e.g., "View Schedule") */
220
+ actionText?: string;
221
+ /** Callback when the card or action button is pressed */
222
+ onPress?: () => void;
223
+ /** Additional CSS classes for the card container */
224
+ className?: string;
225
+ /** Whether to show the action button section */
226
+ showAction?: boolean;
227
+ /** Cache duration in milliseconds (default: 15 minutes) */
228
+ cacheDuration?: number;
229
+ }
230
+
231
+ /**
232
+ * GreetingCard - A personalized greeting card with weather information
233
+ *
234
+ * Displays a personalized greeting with the user's name, current date, and optional
235
+ * weather information fetched from Open-Meteo API. Includes an optional action button
236
+ * (e.g., "View Schedule"). Weather data is automatically cached for 15 minutes.
237
+ *
238
+ * @component
239
+ * @example
240
+ * ```tsx
241
+ * // Basic usage without weather
242
+ * <GreetingCard
243
+ * userName="John Doe"
244
+ * actionText="View Schedule"
245
+ * onPress={() => navigation.navigate('Schedule')}
246
+ * />
247
+ *
248
+ * // With automatic weather fetching (provide both lat and lng)
249
+ * <GreetingCard
250
+ * userName="Jane Smith"
251
+ * lat={37.7749}
252
+ * lng={-122.4194}
253
+ * actionText="View Dashboard"
254
+ * onPress={() => console.log('Card pressed')}
255
+ * />
256
+ *
257
+ * // Custom greeting without action
258
+ * <GreetingCard
259
+ * userName="Alice"
260
+ * customGreeting="Welcome back"
261
+ * showAction={false}
262
+ * />
263
+ * ```
264
+ *
265
+ * @property {string} userName - User's full name to display in greeting (required)
266
+ * @property {number} [lat] - Latitude coordinate for weather fetching (optional)
267
+ * @property {number} [lng] - Longitude coordinate for weather fetching (optional)
268
+ * @property {string} [customGreeting] - Override the time-based greeting
269
+ * @property {Date} [date] - Custom date to display (defaults to current date)
270
+ * @property {string} [actionText="View Details"] - Text for the action button
271
+ * @property {Function} [onPress] - Handler called when card or action is pressed
272
+ * @property {string} [className] - Additional Tailwind classes for styling
273
+ * @property {boolean} [showAction=true] - Whether to show the action button section
274
+ * @property {number} [cacheDuration=900000] - Cache duration in ms (default: 15 min)
275
+ */
276
+ export function GreetingCard({
277
+ userName,
278
+ lat,
279
+ lng,
280
+ customGreeting,
281
+ date = new Date(),
282
+ actionText = 'View Details',
283
+ onPress,
284
+ className,
285
+ showAction = true,
286
+ cacheDuration = 15 * 60 * 1000, // 15 minutes
287
+ }: GreetingCardProps) {
288
+ const colorScheme = useColorScheme();
289
+ const isDark = colorScheme === 'dark';
290
+ const [weather, setWeather] = React.useState<Weather | null>(null);
291
+ const [isLoading, setIsLoading] = React.useState(false);
292
+ const cacheRef = React.useRef<{
293
+ data: Weather | null;
294
+ timestamp: number;
295
+ } | null>(null);
296
+
297
+ // Dynamic styles based on theme
298
+ const dynamicStyles = React.useMemo(
299
+ () => ({
300
+ separator: {
301
+ ...styles.separator,
302
+ backgroundColor: isDark
303
+ ? 'rgba(255, 255, 255, 0.15)'
304
+ : 'rgba(0, 0, 0, 0.1)',
305
+ },
306
+ }),
307
+ [isDark]
308
+ );
309
+
310
+ React.useEffect(() => {
311
+ const loadWeather = async () => {
312
+ if (lat === undefined || lng === undefined) {
313
+ return;
314
+ }
315
+
316
+ const now = Date.now();
317
+
318
+ // Check cache
319
+ if (
320
+ cacheRef.current &&
321
+ now - cacheRef.current.timestamp < cacheDuration
322
+ ) {
323
+ setWeather(cacheRef.current.data);
324
+ return;
325
+ }
326
+
327
+ setIsLoading(true);
328
+
329
+ try {
330
+ const weatherData = await fetchWeather(lat, lng);
331
+ setWeather(weatherData);
332
+ cacheRef.current = {
333
+ data: weatherData,
334
+ timestamp: now,
335
+ };
336
+ } catch (error) {
337
+ console.error('Error loading weather:', error);
338
+ } finally {
339
+ setIsLoading(false);
340
+ }
341
+ };
342
+
343
+ loadWeather();
344
+ }, [lat, lng, cacheDuration]);
345
+
346
+ const greeting = customGreeting || getTimeBasedGreeting();
347
+ const formattedDate = formatDate(date);
348
+
349
+ const CardWrapper = onPress ? Pressable : View;
350
+ const wrapperProps = onPress
351
+ ? {
352
+ onPress,
353
+ android_ripple: { color: 'rgba(0, 0, 0, 0.1)' },
354
+ }
355
+ : {};
356
+
357
+ return (
358
+ <CardWrapper {...wrapperProps}>
359
+ <CustomCard
360
+ cornerRadius={20}
361
+ cornerSmoothing={1}
362
+ className={cn('border-primary/20 dark:border-primary/30', className)}
363
+ >
364
+ {/* Wrapper with negative margin to counteract CustomCard's default padding */}
365
+ <View style={styles.contentWrapper}>
366
+ {/* Header Section with Greeting and Weather */}
367
+ <View style={styles.headerSection}>
368
+ {/* Greeting Text */}
369
+ <View style={styles.greetingText}>
370
+ <Text className="text-sm font-semibold text-card-foreground dark:text-card-foreground">
371
+ Hi {userName},
372
+ </Text>
373
+ <Text className="mt-1.5 text-xs tracking-wider text-muted-foreground dark:text-muted-foreground">
374
+ {greeting}, {formattedDate}
375
+ </Text>
376
+ </View>
377
+
378
+ {/* Weather Display */}
379
+ {(weather || isLoading) && (
380
+ <View style={styles.weatherContainer}>
381
+ {isLoading ? (
382
+ <Text className="text-xs text-muted-foreground dark:text-muted-foreground">
383
+ Loading...
384
+ </Text>
385
+ ) : (
386
+ weather && (
387
+ <>
388
+ <View style={styles.weatherRow}>
389
+ <Image
390
+ source={weather.weatherImage}
391
+ style={styles.weatherIcon}
392
+ resizeMode="contain"
393
+ />
394
+ <Text className="ml-1 text-xs text-card-foreground dark:text-card-foreground">
395
+ {Math.round(weather.temperature)}° C
396
+ </Text>
397
+ </View>
398
+ <Text className="mt-0.5 text-xs text-muted-foreground/70 dark:text-muted-foreground/70">
399
+ {weather.weatherDescription}
400
+ </Text>
401
+ </>
402
+ )
403
+ )}
404
+ </View>
405
+ )}
406
+ </View>
407
+
408
+ {/* Action Section */}
409
+ {showAction && (
410
+ <>
411
+ <View style={dynamicStyles.separator} />
412
+ <View style={styles.actionSection}>
413
+ <Text className="text-sm font-semibold text-card-foreground dark:text-card-foreground">
414
+ {actionText}
415
+ </Text>
416
+ <Text className="text-2xl text-primary dark:text-primary">
417
+
418
+ </Text>
419
+ </View>
420
+ </>
421
+ )}
422
+ </View>
423
+ </CustomCard>
424
+ </CardWrapper>
425
+ );
426
+ }
427
+
428
+ GreetingCard.displayName = 'GreetingCard';
429
+
430
+ const styles = StyleSheet.create({
431
+ weatherIcon: {
432
+ width: 40,
433
+ height: 35,
434
+ },
435
+ contentWrapper: {
436
+ margin: -24, // Counteract CustomCard's default padding of 24
437
+ },
438
+ headerSection: {
439
+ flexDirection: 'row',
440
+ alignItems: 'flex-start',
441
+ justifyContent: 'space-between',
442
+ paddingHorizontal: 20,
443
+ paddingTop: 20,
444
+ paddingBottom: 16,
445
+ },
446
+ greetingText: {
447
+ flex: 1,
448
+ },
449
+ weatherContainer: {
450
+ marginLeft: 12,
451
+ alignItems: 'flex-end',
452
+ },
453
+ weatherRow: {
454
+ flexDirection: 'row',
455
+ alignItems: 'center',
456
+ },
457
+ separator: {
458
+ height: 1,
459
+ marginVertical: 8,
460
+ },
461
+ actionSection: {
462
+ flexDirection: 'row',
463
+ alignItems: 'center',
464
+ justifyContent: 'space-between',
465
+ paddingHorizontal: 20,
466
+ paddingBottom: 12,
467
+ paddingTop: 4,
468
+ },
469
+ });
470
+
471
+ export { WEATHER_ICONS };
@@ -0,0 +1,101 @@
1
+ import { NativeOnlyAnimatedView } from './Native-Only-Animated-View';
2
+ import { TextClassContext } from './Text';
3
+ import { cn } from '../../lib/utils';
4
+ import * as HoverCardPrimitive from '@rn-primitives/hover-card';
5
+ import * as React from 'react';
6
+ import { Platform, StyleSheet } from 'react-native';
7
+ import { FadeIn, FadeOut } from 'react-native-reanimated';
8
+ import { FullWindowOverlay as RNFullWindowOverlay } from 'react-native-screens';
9
+
10
+ /**
11
+ * Root component for hover card - provides context for trigger and content
12
+ *
13
+ * @component
14
+ * @example
15
+ * ```tsx
16
+ * <HoverCard>
17
+ * <HoverCardTrigger>
18
+ * <Text>Hover over me</Text>
19
+ * </HoverCardTrigger>
20
+ * <HoverCardContent>
21
+ * <Text>Additional information appears here</Text>
22
+ * </HoverCardContent>
23
+ * </HoverCard>
24
+ * ```
25
+ */
26
+ const HoverCard = HoverCardPrimitive.Root;
27
+
28
+ /**
29
+ * Trigger component that shows hover card content on hover/press
30
+ *
31
+ * @component
32
+ */
33
+ const HoverCardTrigger = HoverCardPrimitive.Trigger;
34
+
35
+ /**
36
+ * Full window overlay wrapper for iOS, Fragment for other platforms
37
+ */
38
+ const FullWindowOverlay =
39
+ Platform.OS === 'ios' ? RNFullWindowOverlay : React.Fragment;
40
+
41
+ /**
42
+ * Content container for hover card
43
+ *
44
+ * Displays rich content in a popover when hovering over or pressing the trigger.
45
+ * Includes smooth fade animations and proper positioning.
46
+ *
47
+ * @component
48
+ * @example
49
+ * ```tsx
50
+ * <HoverCardContent align="center" sideOffset={8}>
51
+ * <View className="gap-2">
52
+ * <Text className="font-semibold">User Profile</Text>
53
+ * <Text className="text-sm">Additional details about the user</Text>
54
+ * </View>
55
+ * </HoverCardContent>
56
+ * ```
57
+ *
58
+ * @accessibility
59
+ * - Content is announced by screen readers
60
+ * - Proper focus management on web
61
+ */
62
+ function HoverCardContent({
63
+ className,
64
+ align = 'center',
65
+ sideOffset = 4,
66
+ ...props
67
+ }: HoverCardPrimitive.ContentProps &
68
+ React.RefAttributes<HoverCardPrimitive.ContentRef>) {
69
+ return (
70
+ <HoverCardPrimitive.Portal>
71
+ <FullWindowOverlay>
72
+ <HoverCardPrimitive.Overlay
73
+ style={Platform.select({ native: StyleSheet.absoluteFill })}
74
+ >
75
+ <NativeOnlyAnimatedView entering={FadeIn} exiting={FadeOut}>
76
+ <TextClassContext.Provider value="text-popover-foreground">
77
+ <HoverCardPrimitive.Content
78
+ align={align}
79
+ sideOffset={sideOffset}
80
+ className={cn(
81
+ 'bg-popover border-border outline-hidden z-50 w-64 rounded-md border p-4 shadow-md shadow-black/5',
82
+ Platform.select({
83
+ web: cn(
84
+ 'animate-in fade-in-0 zoom-in-95 origin-(--radix-hover-card-content-transform-origin) cursor-default [&>*]:cursor-auto',
85
+ props.side === 'bottom' && 'slide-in-from-top-2',
86
+ props.side === 'top' && 'slide-in-from-bottom-2'
87
+ ),
88
+ }),
89
+ className
90
+ )}
91
+ {...props}
92
+ />
93
+ </TextClassContext.Provider>
94
+ </NativeOnlyAnimatedView>
95
+ </HoverCardPrimitive.Overlay>
96
+ </FullWindowOverlay>
97
+ </HoverCardPrimitive.Portal>
98
+ );
99
+ }
100
+
101
+ export { HoverCard, HoverCardContent, HoverCardTrigger };
@@ -0,0 +1,75 @@
1
+ import { cn } from '../../lib/utils';
2
+ import type { LucideIcon, LucideProps } from 'lucide-react-native';
3
+ import { cssInterop } from 'nativewind';
4
+
5
+ /**
6
+ * Props for Icon component
7
+ *
8
+ * @extends LucideProps - All Lucide icon properties (size, color, strokeWidth, etc.)
9
+ * @property {LucideIcon} as - The Lucide icon component to render
10
+ * @property {string} [className] - Tailwind classes for styling (supports NativeWind)
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { Home } from 'lucide-react-native';
15
+ * <Icon as={Home} size={24} className="text-primary" />
16
+ * ```
17
+ */
18
+ type IconProps = LucideProps & {
19
+ as: LucideIcon;
20
+ className?: string;
21
+ };
22
+
23
+ function IconImpl({ as: IconComponent, ...props }: IconProps) {
24
+ return <IconComponent {...props} />;
25
+ }
26
+
27
+ cssInterop(IconImpl, {
28
+ className: {
29
+ target: 'style',
30
+ nativeStyleToProp: {
31
+ height: 'size',
32
+ width: 'size',
33
+ },
34
+ },
35
+ });
36
+
37
+ /**
38
+ * Universal icon wrapper for Lucide icons
39
+ *
40
+ * Provides consistent styling and sizing for Lucide React Native icons across the application.
41
+ * Automatically handles platform-specific sizing and color theming.
42
+ *
43
+ * @component
44
+ * @example
45
+ * ```tsx
46
+ * import { Settings, User, ChevronRight } from 'lucide-react-native';
47
+ *
48
+ * // Basic icon
49
+ * <Icon as={Settings} />
50
+ *
51
+ * // Sized icon with custom color
52
+ * <Icon as={User} size={32} className="text-primary" />
53
+ *
54
+ * // Icon with stroke width
55
+ * <Icon as={ChevronRight} size={20} strokeWidth={2.5} />
56
+ * ```
57
+ */
58
+ function Icon({
59
+ as: IconComponent,
60
+ className,
61
+ size = 14,
62
+ ...props
63
+ }: IconProps) {
64
+ return (
65
+ <IconImpl
66
+ as={IconComponent}
67
+ className={cn('text-foreground', className)}
68
+ size={size}
69
+ {...props}
70
+ />
71
+ );
72
+ }
73
+
74
+ export { Icon };
75
+ export type { IconProps };