@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,397 @@
1
+ import * as React from 'react';
2
+ import { View as RNView, Pressable as RNPressable, Image } from 'react-native';
3
+ import type { ImageSourcePropType } from 'react-native';
4
+ import { SafeAreaView as RNSafeAreaView } from 'react-native-safe-area-context';
5
+ import { cssInterop } from 'nativewind';
6
+ import { cn } from '../../lib/utils';
7
+ import { Text, TextClassContext } from './Text';
8
+ import type { ViewRef } from '@rn-primitives/types';
9
+
10
+ // Enable NativeWind className support
11
+ const View = cssInterop(RNView, { className: 'style' });
12
+ const Pressable = cssInterop(RNPressable, { className: 'style' });
13
+ const SafeAreaView = cssInterop(RNSafeAreaView, { className: 'style' });
14
+
15
+ /**
16
+ * Props for individual navigation bar items
17
+ *
18
+ * Defines the structure for each item displayed in the NavBar component.
19
+ * Each item can have both active and inactive states with different icons.
20
+ *
21
+ * @interface NavBarItem
22
+ *
23
+ * @property {ImageSourcePropType | React.ReactElement} icon - Icon displayed when item is inactive
24
+ * - Can be an image source from require() or { uri: 'url' }
25
+ * - Can be a React element like lucide-react-native icons
26
+ * @property {ImageSourcePropType | React.ReactElement} activeIcon - Icon displayed when item is active
27
+ * - Same types as icon property
28
+ * - Typically a different color or style variant
29
+ * @property {string} label - Text label displayed below the icon
30
+ * @property {() => void} onPress - Callback function invoked when item is pressed
31
+ * @property {boolean} [disabled] - Optional flag to disable the item interaction
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * // Using image sources
36
+ * const homeItem: NavBarItem = {
37
+ * icon: require('./assets/home-inactive.png'),
38
+ * activeIcon: require('./assets/home-active.png'),
39
+ * label: 'Home',
40
+ * onPress: () => console.log('Home pressed'),
41
+ * };
42
+ *
43
+ * // Using React elements (lucide icons)
44
+ * import { Home } from 'lucide-react-native';
45
+ * const homeItem: NavBarItem = {
46
+ * icon: <Icon as={Home} color="#666" />,
47
+ * activeIcon: <Icon as={Home} color="#000" />,
48
+ * label: 'Home',
49
+ * onPress: () => navigation.navigate('Home'),
50
+ * };
51
+ * ```
52
+ */
53
+ export interface NavBarItem {
54
+ icon: ImageSourcePropType | React.ReactElement;
55
+ activeIcon: ImageSourcePropType | React.ReactElement;
56
+ label: string;
57
+ onPress: () => void;
58
+ disabled?: boolean;
59
+ }
60
+
61
+ /**
62
+ * Props for NavBar component
63
+ *
64
+ * Configuration options for customizing the NavBar appearance and behavior.
65
+ * Provides fine-grained control over styling for different states and elements.
66
+ *
67
+ * @interface NavBarProps
68
+ *
69
+ * @property {NavBarItem[]} items - Array of navigation items to display in the bar
70
+ * @property {number} activeIndex - Zero-based index of the currently active item
71
+ *
72
+ * @property {string} [className] - Additional Tailwind classes for the main container
73
+ * @property {string} [itemClassName] - Additional Tailwind classes applied to all items
74
+ * @property {string} [activeItemClassName] - Additional Tailwind classes for the active item only
75
+ * @property {string} [labelClassName] - Additional Tailwind classes for all labels
76
+ * @property {string} [activeLabelClassName] - Additional Tailwind classes for the active label only
77
+ * @property {string} [backgroundColor] - Custom background color classes (overrides default)
78
+ * @property {number} [iconSize] - Size of icons in pixels (default: 24)
79
+ *
80
+ * @example
81
+ * ```tsx
82
+ * const navBarProps: NavBarProps = {
83
+ * items: navigationItems,
84
+ * activeIndex: 0,
85
+ * iconSize: 28,
86
+ * className: 'px-4',
87
+ * activeItemClassName: 'scale-110',
88
+ * backgroundColor: 'bg-white dark:bg-gray-900',
89
+ * };
90
+ * ```
91
+ */
92
+ export interface NavBarProps {
93
+ items: NavBarItem[];
94
+ activeIndex: number;
95
+ className?: string;
96
+ itemClassName?: string;
97
+ activeItemClassName?: string;
98
+ labelClassName?: string;
99
+ activeLabelClassName?: string;
100
+ backgroundColor?: string;
101
+ iconSize?: number;
102
+ }
103
+
104
+ /**
105
+ * NavBar - Bottom navigation bar component
106
+ *
107
+ * A navigation component designed for mobile applications that displays at the bottom
108
+ * of the screen with proper safe area handling. Similar to Flutter's BottomNavigationBar,
109
+ * it provides an intuitive way to switch between different sections of your app.
110
+ *
111
+ * Features:
112
+ * - Automatic safe area handling for devices with notches/home indicators
113
+ * - Support for both image and React element icons
114
+ * - Active/inactive state management with visual feedback
115
+ * - Customizable styling through Tailwind classes
116
+ * - Full dark mode support
117
+ * - Accessibility features built-in
118
+ * - Disabled state support for items
119
+ *
120
+ * @component
121
+ *
122
+ * @example
123
+ * // Basic usage with state management
124
+ * ```tsx
125
+ * import { useState } from 'react';
126
+ * import { NavBar, NavBarItem } from 'react-native-blueprint';
127
+ *
128
+ * function App() {
129
+ * const [activeIndex, setActiveIndex] = useState(0);
130
+ *
131
+ * const navItems: NavBarItem[] = [
132
+ * {
133
+ * icon: require('./assets/home-inactive.png'),
134
+ * activeIcon: require('./assets/home-active.png'),
135
+ * label: 'Home',
136
+ * onPress: () => setActiveIndex(0),
137
+ * },
138
+ * {
139
+ * icon: require('./assets/browse-inactive.png'),
140
+ * activeIcon: require('./assets/browse-active.png'),
141
+ * label: 'Browse',
142
+ * onPress: () => setActiveIndex(1),
143
+ * },
144
+ * {
145
+ * icon: require('./assets/feed-inactive.png'),
146
+ * activeIcon: require('./assets/feed-active.png'),
147
+ * label: 'Feed',
148
+ * onPress: () => setActiveIndex(2),
149
+ * },
150
+ * {
151
+ * icon: require('./assets/chat-inactive.png'),
152
+ * activeIcon: require('./assets/chat-active.png'),
153
+ * label: 'Chat',
154
+ * onPress: () => setActiveIndex(3),
155
+ * },
156
+ * ];
157
+ *
158
+ * return (
159
+ * <View style={{ flex: 1 }}>
160
+ * <View style={{ flex: 1 }}>
161
+ * {renderScreen(activeIndex)}
162
+ * </View>
163
+ * <NavBar items={navItems} activeIndex={activeIndex} />
164
+ * </View>
165
+ * );
166
+ * }
167
+ * ```
168
+ *
169
+ * @example
170
+ * // With React Navigation
171
+ * ```tsx
172
+ * import { useNavigation, useRoute } from '@react-navigation/native';
173
+ * import { Home, Search, Bell, MessageCircle } from 'lucide-react-native';
174
+ * import { Icon } from 'react-native-blueprint';
175
+ *
176
+ * function BottomTabBar() {
177
+ * const navigation = useNavigation();
178
+ * const route = useRoute();
179
+ * const [activeIndex, setActiveIndex] = useState(0);
180
+ *
181
+ * const navItems: NavBarItem[] = [
182
+ * {
183
+ * icon: <Icon as={Home} size={24} color="#666" />,
184
+ * activeIcon: <Icon as={Home} size={24} color="#000" />,
185
+ * label: 'Home',
186
+ * onPress: () => {
187
+ * setActiveIndex(0);
188
+ * navigation.navigate('Home');
189
+ * },
190
+ * },
191
+ * {
192
+ * icon: <Icon as={Search} size={24} color="#666" />,
193
+ * activeIcon: <Icon as={Search} size={24} color="#000" />,
194
+ * label: 'Browse',
195
+ * onPress: () => {
196
+ * setActiveIndex(1);
197
+ * navigation.navigate('Browse');
198
+ * },
199
+ * },
200
+ * {
201
+ * icon: <Icon as={Bell} size={24} color="#666" />,
202
+ * activeIcon: <Icon as={Bell} size={24} color="#000" />,
203
+ * label: 'Feed',
204
+ * onPress: () => {
205
+ * setActiveIndex(2);
206
+ * navigation.navigate('Feed');
207
+ * },
208
+ * },
209
+ * {
210
+ * icon: <Icon as={MessageCircle} size={24} color="#666" />,
211
+ * activeIcon: <Icon as={MessageCircle} size={24} color="#000" />,
212
+ * label: 'Chat',
213
+ * onPress: () => {
214
+ * setActiveIndex(3);
215
+ * navigation.navigate('Chat');
216
+ * },
217
+ * },
218
+ * ];
219
+ *
220
+ * return <NavBar items={navItems} activeIndex={activeIndex} />;
221
+ * }
222
+ * ```
223
+ *
224
+ * @example
225
+ * // With custom styling and theming
226
+ * ```tsx
227
+ * import { useTheme } from 'react-native-blueprint';
228
+ *
229
+ * function ThemedNavBar() {
230
+ * const { isDark } = useTheme();
231
+ * const [activeIndex, setActiveIndex] = useState(0);
232
+ *
233
+ * return (
234
+ * <NavBar
235
+ * items={navItems}
236
+ * activeIndex={activeIndex}
237
+ * backgroundColor="bg-gray-100 dark:bg-gray-900"
238
+ * iconSize={28}
239
+ * className="py-2 px-4"
240
+ * activeItemClassName="scale-110 transition-transform"
241
+ * activeLabelClassName="text-blue-600 dark:text-blue-400 font-bold"
242
+ * labelClassName="text-gray-500 dark:text-gray-400"
243
+ * />
244
+ * );
245
+ * }
246
+ * ```
247
+ *
248
+ * @example
249
+ * // With disabled items
250
+ * ```tsx
251
+ * const navItems: NavBarItem[] = [
252
+ * {
253
+ * icon: require('./assets/home-inactive.png'),
254
+ * activeIcon: require('./assets/home-active.png'),
255
+ * label: 'Home',
256
+ * onPress: () => setActiveIndex(0),
257
+ * },
258
+ * {
259
+ * icon: require('./assets/premium-inactive.png'),
260
+ * activeIcon: require('./assets/premium-active.png'),
261
+ * label: 'Premium',
262
+ * onPress: () => showUpgradeModal(),
263
+ * disabled: !user.isPremium, // Disable if user is not premium
264
+ * },
265
+ * ];
266
+ * ```
267
+ *
268
+ * @param {NavBarProps} props - Component props
269
+ * @param {React.Ref<ViewRef>} ref - Forwarded ref to the container View
270
+ *
271
+ * @returns {React.ReactElement} The rendered NavBar component
272
+ *
273
+ * @accessibility
274
+ * - Sets role="navigation" on the container for proper semantic structure
275
+ * - Each item has role="button" with appropriate labels
276
+ * - Uses aria-label for screen reader support
277
+ * - Indicates selected state with aria-selected
278
+ * - Respects and communicates disabled state
279
+ * - Provides accessibilityRole and accessibilityLabel for React Native
280
+ * - Includes accessibilityState for selected and disabled states
281
+ *
282
+ * @see {@link NavBarItem} for item configuration
283
+ * @see {@link NavBarProps} for all available props
284
+ */
285
+ const NavBar = React.forwardRef<ViewRef, NavBarProps>(
286
+ (
287
+ {
288
+ items,
289
+ activeIndex,
290
+ className,
291
+ itemClassName,
292
+ activeItemClassName,
293
+ labelClassName,
294
+ activeLabelClassName,
295
+ backgroundColor,
296
+ iconSize = 24,
297
+ },
298
+ ref
299
+ ) => {
300
+ /**
301
+ * Renders an icon based on its type (image source or React element)
302
+ *
303
+ * @param {NavBarItem} item - The navigation item containing the icon
304
+ * @param {boolean} isActive - Whether this item is currently active
305
+ * @returns {React.ReactElement} The rendered icon
306
+ */
307
+ const renderIcon = (
308
+ item: NavBarItem,
309
+ isActive: boolean
310
+ ): React.ReactElement => {
311
+ const iconSource = isActive ? item.activeIcon : item.icon;
312
+
313
+ // Check if it's a React element (like lucide icon)
314
+ if (React.isValidElement(iconSource)) {
315
+ return iconSource;
316
+ }
317
+
318
+ // It's an image source
319
+ return (
320
+ <Image
321
+ source={iconSource as ImageSourcePropType}
322
+ style={{ width: iconSize, height: iconSize }}
323
+ resizeMode="contain"
324
+ accessibilityIgnoresInvertColors
325
+ />
326
+ );
327
+ };
328
+
329
+ // Default background color with dark mode support
330
+ const defaultBgColor = 'bg-background';
331
+
332
+ return (
333
+ <SafeAreaView
334
+ edges={['bottom']}
335
+ className={cn(
336
+ 'border-t border-border',
337
+ backgroundColor || defaultBgColor
338
+ )}
339
+ ref={ref as any}
340
+ >
341
+ <View
342
+ className={cn(
343
+ 'flex-row items-center justify-around px-2 py-1',
344
+ className
345
+ )}
346
+ role="navigation"
347
+ >
348
+ {items.map((item, index) => {
349
+ const isActive = index === activeIndex;
350
+ const isDisabled = item.disabled;
351
+
352
+ return (
353
+ <Pressable
354
+ key={`nav-item-${index}-${item.label}`}
355
+ onPress={item.onPress}
356
+ disabled={isDisabled}
357
+ className={cn(
358
+ 'flex-1 items-center justify-center py-2 px-1 active:opacity-70',
359
+ isDisabled && 'opacity-40',
360
+ itemClassName,
361
+ isActive && activeItemClassName
362
+ )}
363
+ role="button"
364
+ aria-label={item.label}
365
+ aria-selected={isActive}
366
+ accessibilityRole="button"
367
+ accessibilityLabel={item.label}
368
+ accessibilityState={{
369
+ selected: isActive,
370
+ disabled: isDisabled,
371
+ }}
372
+ >
373
+ <View className="mb-1.5 mt-2.5">
374
+ {renderIcon(item, isActive)}
375
+ </View>
376
+ <TextClassContext.Provider
377
+ value={cn(
378
+ 'text-xs font-medium text-center',
379
+ isActive
380
+ ? activeLabelClassName || 'text-foreground'
381
+ : labelClassName || 'text-muted-foreground'
382
+ )}
383
+ >
384
+ <Text>{item.label}</Text>
385
+ </TextClassContext.Provider>
386
+ </Pressable>
387
+ );
388
+ })}
389
+ </View>
390
+ </SafeAreaView>
391
+ );
392
+ }
393
+ );
394
+
395
+ NavBar.displayName = 'NavBar';
396
+
397
+ export { NavBar };
@@ -0,0 +1,110 @@
1
+ import { NativeOnlyAnimatedView } from './Native-Only-Animated-View';
2
+ import { TextClassContext } from './Text';
3
+ import { cn } from '../../lib/utils';
4
+ import * as PopoverPrimitive from '@rn-primitives/popover';
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 popover - provides context for trigger and content
12
+ *
13
+ * @component
14
+ * @example
15
+ * ```tsx
16
+ * <Popover>
17
+ * <PopoverTrigger>
18
+ * <Button variant="outline">
19
+ * <Text>Open Popover</Text>
20
+ * </Button>
21
+ * </PopoverTrigger>
22
+ * <PopoverContent>
23
+ * <Text>Popover content goes here</Text>
24
+ * </PopoverContent>
25
+ * </Popover>
26
+ * ```
27
+ */
28
+ const Popover = PopoverPrimitive.Root;
29
+
30
+ /**
31
+ * Trigger component that opens the popover when pressed
32
+ *
33
+ * @component
34
+ */
35
+ const PopoverTrigger = PopoverPrimitive.Trigger;
36
+
37
+ /**
38
+ * Full window overlay wrapper for iOS, Fragment for other platforms
39
+ */
40
+ const FullWindowOverlay =
41
+ Platform.OS === 'ios' ? RNFullWindowOverlay : React.Fragment;
42
+
43
+ /**
44
+ * Content container for popover
45
+ *
46
+ * Displays content in a floating panel anchored to the trigger.
47
+ * Includes smooth animations and proper positioning.
48
+ *
49
+ * @component
50
+ * @example
51
+ * ```tsx
52
+ * <PopoverContent align="start" sideOffset={8}>
53
+ * <View className="gap-2">
54
+ * <Text className="font-semibold">Dimensions</Text>
55
+ * <Text className="text-sm">Set the dimensions for the layer</Text>
56
+ * </View>
57
+ * </PopoverContent>
58
+ * ```
59
+ *
60
+ * @accessibility
61
+ * - Content is announced by screen readers
62
+ * - Dismissible with escape key on web
63
+ * - Proper focus management
64
+ */
65
+ function PopoverContent({
66
+ className,
67
+ align = 'center',
68
+ sideOffset = 4,
69
+ portalHost,
70
+ ...props
71
+ }: PopoverPrimitive.ContentProps &
72
+ React.RefAttributes<PopoverPrimitive.ContentRef> & {
73
+ portalHost?: string;
74
+ }) {
75
+ return (
76
+ <PopoverPrimitive.Portal hostName={portalHost}>
77
+ <FullWindowOverlay>
78
+ <PopoverPrimitive.Overlay
79
+ style={Platform.select({ native: StyleSheet.absoluteFill })}
80
+ >
81
+ <NativeOnlyAnimatedView
82
+ entering={FadeIn.duration(200)}
83
+ exiting={FadeOut}
84
+ >
85
+ <TextClassContext.Provider value="text-popover-foreground">
86
+ <PopoverPrimitive.Content
87
+ align={align}
88
+ sideOffset={sideOffset}
89
+ className={cn(
90
+ 'bg-popover border-border outline-hidden z-50 w-72 rounded-md border p-4 shadow-md shadow-black/5',
91
+ Platform.select({
92
+ web: cn(
93
+ 'animate-in fade-in-0 zoom-in-95 origin-(--radix-popover-content-transform-origin) cursor-auto',
94
+ props.side === 'bottom' && 'slide-in-from-top-2',
95
+ props.side === 'top' && 'slide-in-from-bottom-2'
96
+ ),
97
+ }),
98
+ className
99
+ )}
100
+ {...props}
101
+ />
102
+ </TextClassContext.Provider>
103
+ </NativeOnlyAnimatedView>
104
+ </PopoverPrimitive.Overlay>
105
+ </FullWindowOverlay>
106
+ </PopoverPrimitive.Portal>
107
+ );
108
+ }
109
+
110
+ export { Popover, PopoverContent, PopoverTrigger };
@@ -0,0 +1,138 @@
1
+ import { cn } from '../../lib/utils';
2
+ import * as ProgressPrimitive from '@rn-primitives/progress';
3
+ import { Platform, View } from 'react-native';
4
+ import { cssInterop } from 'nativewind';
5
+ import Animated, {
6
+ Extrapolation,
7
+ interpolate,
8
+ useAnimatedStyle,
9
+ useDerivedValue,
10
+ withSpring,
11
+ } from 'react-native-reanimated';
12
+
13
+ cssInterop(View, { className: 'style' });
14
+
15
+ /**
16
+ * Progress bar component for displaying task completion
17
+ *
18
+ * Displays an animated progress indicator with customizable styles.
19
+ * Uses spring animations on native platforms for smooth transitions.
20
+ *
21
+ * @component
22
+ * @example
23
+ * ```tsx
24
+ * <Progress value={60} className="w-full" />
25
+ *
26
+ * <Progress
27
+ * value={progress}
28
+ * indicatorClassName="bg-green-500"
29
+ * />
30
+ * ```
31
+ *
32
+ * @accessibility
33
+ * - Uses proper ARIA attributes for progress indication
34
+ * - Value changes are announced by screen readers
35
+ */
36
+ function Progress({
37
+ className,
38
+ value,
39
+ indicatorClassName,
40
+ ...props
41
+ }: ProgressPrimitive.RootProps &
42
+ React.RefAttributes<ProgressPrimitive.RootRef> & {
43
+ indicatorClassName?: string;
44
+ }) {
45
+ return (
46
+ <ProgressPrimitive.Root
47
+ className={cn(
48
+ 'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',
49
+ className
50
+ )}
51
+ {...props}
52
+ >
53
+ <Indicator value={value} className={indicatorClassName} />
54
+ </ProgressPrimitive.Root>
55
+ );
56
+ }
57
+
58
+ export { Progress };
59
+
60
+ /**
61
+ * Platform-specific indicator component selector
62
+ */
63
+ const Indicator = Platform.select({
64
+ web: WebIndicator,
65
+ native: NativeIndicator,
66
+ default: NullIndicator,
67
+ });
68
+
69
+ /**
70
+ * Props for progress indicator components
71
+ */
72
+ type IndicatorProps = {
73
+ value: number | undefined | null;
74
+ className?: string;
75
+ };
76
+
77
+ /**
78
+ * Web-specific progress indicator using CSS transforms
79
+ *
80
+ * @platform web
81
+ */
82
+ function WebIndicator({ value, className }: IndicatorProps) {
83
+ if (Platform.OS !== 'web') {
84
+ return null;
85
+ }
86
+
87
+ return (
88
+ <View
89
+ className={cn(
90
+ 'bg-primary h-full w-full flex-1 transition-all',
91
+ className
92
+ )}
93
+ style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
94
+ >
95
+ <ProgressPrimitive.Indicator className={cn('h-full w-full', className)} />
96
+ </View>
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Native-specific progress indicator using Reanimated
102
+ *
103
+ * Provides smooth spring animations for progress changes.
104
+ *
105
+ * @platform native
106
+ */
107
+ function NativeIndicator({ value, className }: IndicatorProps) {
108
+ const progress = useDerivedValue(() => value ?? 0);
109
+
110
+ const indicator = useAnimatedStyle(() => {
111
+ return {
112
+ width: withSpring(
113
+ `${interpolate(progress.value, [0, 100], [1, 100], Extrapolation.CLAMP)}%`,
114
+ { overshootClamping: true }
115
+ ),
116
+ };
117
+ }, [value]);
118
+
119
+ if (Platform.OS === 'web') {
120
+ return null;
121
+ }
122
+
123
+ return (
124
+ <ProgressPrimitive.Indicator asChild>
125
+ <Animated.View
126
+ style={indicator}
127
+ className={cn('bg-foreground h-full', className)}
128
+ />
129
+ </ProgressPrimitive.Indicator>
130
+ );
131
+ }
132
+
133
+ /**
134
+ * Null indicator fallback for unsupported platforms
135
+ */
136
+ function NullIndicator(_props: IndicatorProps) {
137
+ return null;
138
+ }