@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,551 @@
1
+ import type { LucideIcon } from 'lucide-react-native';
2
+ import { ArrowLeft, Menu, MoreVertical } from 'lucide-react-native';
3
+ import { cssInterop, useColorScheme } from 'nativewind';
4
+ import * as React from 'react';
5
+ import {
6
+ Platform,
7
+ Pressable as RNPressable,
8
+ View as RNView,
9
+ StatusBar,
10
+ } from 'react-native';
11
+ import { SafeAreaView } from 'react-native-safe-area-context';
12
+ import { cn } from '../../lib/utils';
13
+ import { Icon } from './Icon';
14
+ import { Text } from './Text';
15
+
16
+ const View = cssInterop(RNView, { className: 'style' });
17
+ const Pressable = cssInterop(RNPressable, { className: 'style' });
18
+
19
+ /**
20
+ * Configuration for an action item in the AppBar
21
+ *
22
+ * @property {LucideIcon} icon - Icon component from lucide-react-native
23
+ * @property {() => void} [onPress] - Callback when action is pressed
24
+ * @property {string} [label] - Accessibility label for the action
25
+ * @property {string} [className] - Additional classes for the action button container
26
+ * @property {string} [iconClassName] - Additional classes for the icon
27
+ * @property {boolean} [disabled] - Whether the action is disabled
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const actions: AppBarAction[] = [
32
+ * { icon: Search, onPress: handleSearch, label: 'Search' },
33
+ * { icon: MoreVertical, onPress: handleMore, label: 'More options' }
34
+ * ];
35
+ * ```
36
+ */
37
+ export type AppBarAction = {
38
+ icon: LucideIcon;
39
+ onPress?: () => void;
40
+ label?: string;
41
+ className?: string;
42
+ iconClassName?: string;
43
+ disabled?: boolean;
44
+ };
45
+
46
+ /**
47
+ * Props for AppBar component - A flexible top app bar following Material Design guidelines
48
+ *
49
+ * @property {string | React.ReactNode} [title] - AppBar title text or custom component
50
+ * @property {string} [subtitle] - Optional subtitle text displayed below title
51
+ *
52
+ * @property {React.ReactNode} [leading] - Custom leading widget (can include multiple icons or custom components, overrides default back button)
53
+ * @property {boolean} [showLeading=true] - Whether to show the leading widget
54
+ * @property {() => void} [onLeadingPress] - Callback when leading icon is pressed (only used if no custom leading provided)
55
+ * @property {LucideIcon} [leadingIcon=ArrowLeft] - Icon for the leading button (only used if no custom leading provided)
56
+ *
57
+ * @property {React.ReactNode} [center] - Custom center component (overrides title/subtitle, can include images, text, or complex layouts)
58
+ *
59
+ * @property {AppBarAction[]} [actions=[]] - Array of action items to display on the right (supports multiple icons)
60
+ *
61
+ * @property {string} [className] - Additional classes for the root container
62
+ * @property {string} [titleClassName] - Additional classes for the title text
63
+ * @property {string} [subtitleClassName] - Additional classes for the subtitle text
64
+ * @property {string} [contentClassName] - Additional classes for the content container
65
+ * @property {string} [leadingClassName] - Additional classes for the leading section container
66
+ * @property {string} [centerClassName] - Additional classes for the center section container
67
+ * @property {string} [actionsClassName] - Additional classes for the actions section container
68
+ *
69
+ * @property {boolean} [centerTitle=false] - Whether to center the title
70
+ * @property {number} [elevation=4] - Elevation level for shadow (0 = no shadow)
71
+ * @property {boolean} [transparent=false] - Whether the AppBar background is transparent
72
+ *
73
+ * @property {boolean} [useSafeArea=true] - Whether to respect safe area insets
74
+ *
75
+ * @property {string} [backgroundColor] - Custom background color class
76
+ * @property {string} [foregroundColor] - Custom foreground/text color class
77
+ *
78
+ * @property {'default' | 'light-content' | 'dark-content'} [statusBarStyle] - Status bar text color (auto-adjusts based on theme if not set)
79
+ * @property {string} [statusBarColor] - Status bar background color (Android)
80
+ *
81
+ * @property {number} [height] - Custom height in pixels (default: 56px per Material Design)
82
+ *
83
+ * @property {React.ReactNode} [bottom] - Additional content below main app bar (e.g., tabs, search)
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * // Simple app bar with title
88
+ * <AppBar title="Home" />
89
+ *
90
+ * // With custom center containing image and text
91
+ * <AppBar
92
+ * center={
93
+ * <View className="flex-row items-center gap-2">
94
+ * <Image source={logo} className="w-8 h-8" />
95
+ * <Text className="text-lg font-bold">MyApp</Text>
96
+ * </View>
97
+ * }
98
+ * />
99
+ *
100
+ * // With multiple leading icons
101
+ * <AppBar
102
+ * title="Settings"
103
+ * leading={
104
+ * <View className="flex-row items-center">
105
+ * <IconButton icon={Menu} onPress={openMenu} />
106
+ * <IconButton icon={ArrowLeft} onPress={goBack} />
107
+ * </View>
108
+ * }
109
+ * actions={[
110
+ * { icon: Search, onPress: handleSearch },
111
+ * { icon: Settings, onPress: openSettings },
112
+ * { icon: MoreVertical, onPress: handleMore }
113
+ * ]}
114
+ * />
115
+ * ```
116
+ */
117
+ export type AppBarProps = {
118
+ // Title and subtitle
119
+ title?: string | React.ReactNode;
120
+ subtitle?: string;
121
+
122
+ // Leading (supports custom components with multiple icons)
123
+ leading?: React.ReactNode;
124
+ showLeading?: boolean;
125
+ onLeadingPress?: () => void;
126
+ leadingIcon?: LucideIcon;
127
+
128
+ // Center (custom component that can include images or complex layouts)
129
+ center?: React.ReactNode;
130
+
131
+ // Actions (supports multiple icons)
132
+ actions?: AppBarAction[];
133
+
134
+ // Styling
135
+ className?: string;
136
+ titleClassName?: string;
137
+ subtitleClassName?: string;
138
+ contentClassName?: string;
139
+ leadingClassName?: string;
140
+ centerClassName?: string;
141
+ actionsClassName?: string;
142
+
143
+ // Layout
144
+ centerTitle?: boolean;
145
+ elevation?: number;
146
+ transparent?: boolean;
147
+
148
+ // Safe area
149
+ useSafeArea?: boolean;
150
+
151
+ // Colors
152
+ backgroundColor?: string;
153
+ foregroundColor?: string;
154
+
155
+ // Status bar (iOS/Android)
156
+ statusBarStyle?: 'default' | 'light-content' | 'dark-content';
157
+ statusBarColor?: string;
158
+
159
+ // Height
160
+ height?: number;
161
+
162
+ // Additional content
163
+ bottom?: React.ReactNode;
164
+ };
165
+
166
+ /**
167
+ * A flexible top app bar component following Material Design guidelines
168
+ *
169
+ * Provides consistent navigation and actions across your app with support for:
170
+ * - Customizable leading navigation (back button, menu, custom widget)
171
+ * - Title and subtitle with flexible positioning
172
+ * - Action buttons on the right side
173
+ * - Safe area insets handling
174
+ * - Platform-specific status bar configuration
175
+ * - Transparent overlay mode
176
+ * - Bottom content slot for tabs or search bars
177
+ *
178
+ * @component
179
+ * @example
180
+ * ```tsx
181
+ * // Basic with back button
182
+ * <AppBar
183
+ * title="Profile"
184
+ * onLeadingPress={() => navigation.goBack()}
185
+ * />
186
+ *
187
+ * // With menu and actions
188
+ * <AppBar
189
+ * title="Home"
190
+ * leadingIcon={Menu}
191
+ * onLeadingPress={openDrawer}
192
+ * actions={[
193
+ * { icon: Search, onPress: handleSearch },
194
+ * { icon: Settings, onPress: openSettings }
195
+ * ]}
196
+ * />
197
+ *
198
+ * // Transparent overlay (for use over content)
199
+ * <AppBar
200
+ * title="Photo"
201
+ * transparent
202
+ * foregroundColor="text-white"
203
+ * statusBarStyle="light-content"
204
+ * />
205
+ * ```
206
+ *
207
+ * @accessibility
208
+ * - Leading button includes "Navigate back" accessibility label
209
+ * - Action buttons support custom accessibility labels
210
+ * - Proper button role for all interactive elements
211
+ */
212
+ const AppBar = React.forwardRef<RNView, AppBarProps>(
213
+ (
214
+ {
215
+ title,
216
+ subtitle,
217
+ leading,
218
+ showLeading = true,
219
+ onLeadingPress,
220
+ leadingIcon = ArrowLeft,
221
+ center,
222
+ actions = [],
223
+ className,
224
+ titleClassName,
225
+ subtitleClassName,
226
+ contentClassName,
227
+ leadingClassName,
228
+ centerClassName,
229
+ actionsClassName,
230
+ centerTitle = false,
231
+ elevation = 4,
232
+ transparent = false,
233
+ useSafeArea = true,
234
+ backgroundColor,
235
+ foregroundColor,
236
+ statusBarStyle,
237
+ statusBarColor,
238
+ height,
239
+ bottom,
240
+ },
241
+ ref
242
+ ) => {
243
+ // Get the current color scheme for dark mode support
244
+ const { colorScheme } = useColorScheme();
245
+ const isDark = colorScheme === 'dark';
246
+
247
+ // Default height following Material Design guidelines
248
+ const defaultHeight = 56;
249
+ const appBarHeight = height ?? defaultHeight;
250
+
251
+ // Determine status bar style based on theme if not explicitly set
252
+ const defaultStatusBarStyle = transparent
253
+ ? 'light-content' // Transparent bars usually overlay content, so use light text
254
+ : isDark
255
+ ? 'light-content'
256
+ : 'dark-content';
257
+ const resolvedStatusBarStyle = statusBarStyle ?? defaultStatusBarStyle;
258
+
259
+ // Background color
260
+ const bgColor = transparent
261
+ ? 'bg-transparent'
262
+ : backgroundColor || 'bg-background';
263
+
264
+ // Shadow/elevation styles
265
+ const elevationStyle =
266
+ !transparent && elevation > 0 ? 'border-b border-border shadow-sm' : '';
267
+
268
+ // Render center/title content
269
+ const renderCenter = () => {
270
+ // If custom center is provided, use it
271
+ if (center) {
272
+ return (
273
+ <View className={cn('flex-1 justify-center', centerClassName)}>
274
+ {center}
275
+ </View>
276
+ );
277
+ }
278
+
279
+ // Otherwise fall back to title/subtitle
280
+ if (!title) return null;
281
+
282
+ if (typeof title === 'string') {
283
+ return (
284
+ <View
285
+ className={cn(
286
+ 'flex-1 justify-center',
287
+ subtitle && 'gap-0.5',
288
+ centerClassName
289
+ )}
290
+ >
291
+ <Text
292
+ className={cn(
293
+ 'text-xl font-semibold',
294
+ foregroundColor || 'text-foreground',
295
+ titleClassName
296
+ )}
297
+ numberOfLines={1}
298
+ ellipsizeMode="tail"
299
+ >
300
+ {title}
301
+ </Text>
302
+ {subtitle && (
303
+ <Text
304
+ className={cn(
305
+ 'text-xs',
306
+ foregroundColor ? 'opacity-70' : 'text-muted-foreground',
307
+ subtitleClassName
308
+ )}
309
+ numberOfLines={1}
310
+ ellipsizeMode="tail"
311
+ >
312
+ {subtitle}
313
+ </Text>
314
+ )}
315
+ </View>
316
+ );
317
+ }
318
+
319
+ return (
320
+ <View className={cn('flex-1 justify-center', centerClassName)}>
321
+ {title}
322
+ </View>
323
+ );
324
+ };
325
+
326
+ // Render leading icon/widget
327
+ const renderLeading = () => {
328
+ if (!showLeading) return null;
329
+
330
+ if (leading) {
331
+ return <View className={cn('mr-2', leadingClassName)}>{leading}</View>;
332
+ }
333
+
334
+ if (onLeadingPress) {
335
+ const LeadingIcon = leadingIcon;
336
+ return (
337
+ <Pressable
338
+ onPress={onLeadingPress}
339
+ className={cn(
340
+ 'mr-2 -ml-2 h-10 w-10 items-center justify-center rounded-full active:bg-accent/50',
341
+ leadingClassName
342
+ )}
343
+ accessibilityRole="button"
344
+ accessibilityLabel="Navigate back"
345
+ >
346
+ <Icon
347
+ as={LeadingIcon}
348
+ size={24}
349
+ className={cn(foregroundColor || 'text-foreground')}
350
+ />
351
+ </Pressable>
352
+ );
353
+ }
354
+
355
+ return null;
356
+ };
357
+
358
+ // Render action items
359
+ const renderActions = () => {
360
+ if (!actions || actions.length === 0) return null;
361
+
362
+ return (
363
+ <View className={cn('flex-row items-center gap-1', actionsClassName)}>
364
+ {actions.map((action, index) => {
365
+ const ActionIcon = action.icon;
366
+ return (
367
+ <Pressable
368
+ key={index}
369
+ onPress={action.onPress}
370
+ disabled={action.disabled}
371
+ className={cn(
372
+ 'h-10 w-10 items-center justify-center rounded-full active:bg-accent/50',
373
+ action.disabled && 'opacity-50',
374
+ action.className
375
+ )}
376
+ accessibilityRole="button"
377
+ accessibilityLabel={action.label || `Action ${index + 1}`}
378
+ >
379
+ <Icon
380
+ as={ActionIcon}
381
+ size={24}
382
+ className={cn(
383
+ foregroundColor || 'text-foreground',
384
+ action.iconClassName
385
+ )}
386
+ />
387
+ </Pressable>
388
+ );
389
+ })}
390
+ </View>
391
+ );
392
+ };
393
+
394
+ return (
395
+ <>
396
+ {/* Status Bar */}
397
+ {Platform.OS !== 'web' && (
398
+ <StatusBar
399
+ barStyle={resolvedStatusBarStyle}
400
+ backgroundColor={statusBarColor || 'transparent'}
401
+ translucent={useSafeArea}
402
+ />
403
+ )}
404
+
405
+ {/* Main AppBar Container with SafeAreaView */}
406
+ {useSafeArea ? (
407
+ <SafeAreaView edges={['top']}>
408
+ <View
409
+ ref={ref as any}
410
+ className={cn(bgColor, elevationStyle, className)}
411
+ >
412
+ {/* AppBar Content */}
413
+ <View
414
+ className={cn(
415
+ 'flex-row items-center justify-between px-4',
416
+ contentClassName
417
+ )}
418
+ style={{ height: appBarHeight }}
419
+ >
420
+ {/* Leading Section */}
421
+ {renderLeading()}
422
+
423
+ {/* Center/Title Section */}
424
+ <View
425
+ className={cn(
426
+ 'flex-1',
427
+ centerTitle && 'items-center',
428
+ !centerTitle && 'items-start'
429
+ )}
430
+ >
431
+ {renderCenter()}
432
+ </View>
433
+
434
+ {/* Actions Section */}
435
+ {renderActions()}
436
+ </View>
437
+
438
+ {/* Bottom Content (for tabs, search bar, etc.) */}
439
+ {bottom && <View className="pb-2">{bottom}</View>}
440
+ </View>
441
+ </SafeAreaView>
442
+ ) : (
443
+ <View
444
+ ref={ref as any}
445
+ className={cn(bgColor, elevationStyle, className)}
446
+ >
447
+ {/* AppBar Content */}
448
+ <View
449
+ className={cn(
450
+ 'flex-row items-center justify-between px-4',
451
+ contentClassName
452
+ )}
453
+ style={{ height: appBarHeight }}
454
+ >
455
+ {/* Leading Section */}
456
+ {renderLeading()}
457
+
458
+ {/* Center/Title Section */}
459
+ <View
460
+ className={cn(
461
+ 'flex-1',
462
+ centerTitle && 'items-center',
463
+ !centerTitle && 'items-start'
464
+ )}
465
+ >
466
+ {renderCenter()}
467
+ </View>
468
+
469
+ {/* Actions Section */}
470
+ {renderActions()}
471
+ </View>
472
+
473
+ {/* Bottom Content (for tabs, search bar, etc.) */}
474
+ {bottom && <View className="pb-2">{bottom}</View>}
475
+ </View>
476
+ )}
477
+ </>
478
+ );
479
+ }
480
+ );
481
+
482
+ AppBar.displayName = 'AppBar';
483
+
484
+ /**
485
+ * Pre-configured AppBar variants for common use cases
486
+ *
487
+ * @example
488
+ * ```tsx
489
+ * // Simple app bar without navigation
490
+ * <AppBarVariants.Simple title="Home" />
491
+ *
492
+ * // With back button
493
+ * <AppBarVariants.WithBack title="Details" onLeadingPress={goBack} />
494
+ *
495
+ * // With menu drawer
496
+ * <AppBarVariants.WithMenu title="Main" onLeadingPress={openDrawer} />
497
+ *
498
+ * // Transparent overlay
499
+ * <AppBarVariants.Transparent title="Gallery" />
500
+ *
501
+ * // Centered title
502
+ * <AppBarVariants.Centered title="About" />
503
+ * ```
504
+ */
505
+ export const AppBarVariants = {
506
+ // Simple app bar with just a title
507
+ Simple: (props: Omit<AppBarProps, 'showLeading'>) => (
508
+ <AppBar {...props} showLeading={false} />
509
+ ),
510
+
511
+ // App bar with back button
512
+ WithBack: (props: AppBarProps) => <AppBar {...props} showLeading={true} />,
513
+
514
+ // App bar with menu icon
515
+ WithMenu: (props: Omit<AppBarProps, 'leadingIcon'>) => (
516
+ <AppBar {...props} leadingIcon={Menu} showLeading={true} />
517
+ ),
518
+
519
+ // Transparent app bar (for use over content)
520
+ Transparent: (props: Omit<AppBarProps, 'transparent'>) => (
521
+ <AppBar {...props} transparent={true} elevation={0} />
522
+ ),
523
+
524
+ // Centered title app bar
525
+ Centered: (props: Omit<AppBarProps, 'centerTitle'>) => (
526
+ <AppBar {...props} centerTitle={true} />
527
+ ),
528
+ };
529
+
530
+ /**
531
+ * Common icons exported for convenience when configuring AppBar
532
+ *
533
+ * @example
534
+ * ```tsx
535
+ * import { AppBar, AppBarIcons } from './AppBar';
536
+ *
537
+ * <AppBar
538
+ * leadingIcon={AppBarIcons.Menu}
539
+ * actions={[
540
+ * { icon: AppBarIcons.MoreVertical, onPress: handleMore }
541
+ * ]}
542
+ * />
543
+ * ```
544
+ */
545
+ export const AppBarIcons = {
546
+ Menu,
547
+ ArrowLeft,
548
+ MoreVertical,
549
+ };
550
+
551
+ export { AppBar };
@@ -0,0 +1,25 @@
1
+ import * as AspectRatioPrimitive from '@rn-primitives/aspect-ratio';
2
+
3
+ /**
4
+ * Maintains a consistent aspect ratio for its child content
5
+ *
6
+ * Displays content within a desired ratio (e.g., 16/9, 4/3, 1/1) regardless of available space.
7
+ * Commonly used for images, videos, and embedded content that require specific proportions.
8
+ *
9
+ * @component
10
+ * @example
11
+ * ```tsx
12
+ * // 16:9 aspect ratio for video container
13
+ * <AspectRatio ratio={16 / 9} className="bg-muted">
14
+ * <Image source={{uri: 'video-thumbnail.jpg'}} className="h-full w-full rounded-md object-cover" />
15
+ * </AspectRatio>
16
+ *
17
+ * // Square aspect ratio for avatars
18
+ * <AspectRatio ratio={1} className="w-full">
19
+ * <Image source={{uri: 'avatar.jpg'}} className="h-full w-full" />
20
+ * </AspectRatio>
21
+ * ```
22
+ */
23
+ const AspectRatio = AspectRatioPrimitive.Root;
24
+
25
+ export { AspectRatio };
@@ -0,0 +1,103 @@
1
+ import * as React from 'react';
2
+ import { Image, View } from 'react-native';
3
+ import { cssInterop } from 'nativewind';
4
+ import { cn } from '../../lib/utils';
5
+ import type { ViewRef, SlottableViewProps } from '@rn-primitives/types';
6
+ import * as Slot from '@rn-primitives/slot';
7
+
8
+ cssInterop(View, { className: 'style' });
9
+ cssInterop(Slot.View, { className: 'style' });
10
+ cssInterop(Image, { className: 'style' });
11
+
12
+ /**
13
+ * Avatar container component
14
+ *
15
+ * Circular container for displaying user avatars with images or fallback initials.
16
+ * Use with AvatarImage and AvatarFallback for complete functionality.
17
+ *
18
+ * @component
19
+ * @example
20
+ * ```tsx
21
+ * <Avatar>
22
+ * <AvatarImage source={{ uri: userPhoto }} alt="User avatar" />
23
+ * <AvatarFallback>
24
+ * <Text>JD</Text>
25
+ * </AvatarFallback>
26
+ * </Avatar>
27
+ * ```
28
+ *
29
+ * @property {boolean} [asChild] - When true, merges props into immediate child
30
+ * @property {string} [className] - Additional Tailwind classes
31
+ */
32
+ const Avatar = React.forwardRef<
33
+ ViewRef,
34
+ SlottableViewProps & { className?: string }
35
+ >(({ className, asChild = false, ...props }, ref) => {
36
+ const Component = asChild ? Slot.View : View;
37
+ return (
38
+ <Component
39
+ className={cn(
40
+ 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
41
+ className
42
+ )}
43
+ ref={ref as any}
44
+ {...props}
45
+ />
46
+ );
47
+ });
48
+ Avatar.displayName = 'Avatar';
49
+
50
+ /**
51
+ * AvatarImage - Image component for avatar
52
+ *
53
+ * Displays the user's profile image. Falls back to AvatarFallback if image fails to load.
54
+ *
55
+ * @property {ImageSource} source - Image source (local or remote URI)
56
+ * @property {string} [alt] - Alternative text for accessibility
57
+ * @property {string} [className] - Additional Tailwind classes
58
+ */
59
+ const AvatarImage = React.forwardRef<
60
+ React.ElementRef<typeof Image>,
61
+ React.ComponentPropsWithoutRef<typeof Image> & {
62
+ className?: string;
63
+ alt?: string;
64
+ }
65
+ >(({ className, ...props }, ref) => {
66
+ return (
67
+ <Image
68
+ className={cn('aspect-square h-full w-full', className)}
69
+ ref={ref}
70
+ {...props}
71
+ />
72
+ );
73
+ });
74
+ AvatarImage.displayName = 'AvatarImage';
75
+
76
+ /**
77
+ * AvatarFallback - Fallback content when image is unavailable
78
+ *
79
+ * Typically displays user initials or an icon. Shows when AvatarImage fails to load
80
+ * or is not provided.
81
+ *
82
+ * @property {boolean} [asChild] - When true, merges props into immediate child
83
+ * @property {string} [className] - Additional Tailwind classes
84
+ */
85
+ const AvatarFallback = React.forwardRef<
86
+ ViewRef,
87
+ SlottableViewProps & { className?: string }
88
+ >(({ className, asChild = false, ...props }, ref) => {
89
+ const Component = asChild ? Slot.View : View;
90
+ return (
91
+ <Component
92
+ className={cn(
93
+ 'flex h-full w-full items-center justify-center rounded-full bg-muted',
94
+ className
95
+ )}
96
+ ref={ref}
97
+ {...props}
98
+ />
99
+ );
100
+ });
101
+ AvatarFallback.displayName = 'AvatarFallback';
102
+
103
+ export { Avatar, AvatarImage, AvatarFallback };