@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.
- package/LICENSE +20 -0
- package/README.md +358 -0
- package/lib/module/app/_layout.js +23 -0
- package/lib/module/app/_layout.js.map +1 -0
- package/lib/module/assets/icons/weather_icons/drizzle.png +0 -0
- package/lib/module/assets/icons/weather_icons/foggy.png +0 -0
- package/lib/module/assets/icons/weather_icons/freezing_rain.png +0 -0
- package/lib/module/assets/icons/weather_icons/partly_cloudy.png +0 -0
- package/lib/module/assets/icons/weather_icons/rainy.png +0 -0
- package/lib/module/assets/icons/weather_icons/showers.png +0 -0
- package/lib/module/assets/icons/weather_icons/sunny_weather.png +0 -0
- package/lib/module/assets/icons/weather_icons/thunderstorm.png +0 -0
- package/lib/module/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
- package/lib/module/components/theme-config.js +265 -0
- package/lib/module/components/theme-config.js.map +1 -0
- package/lib/module/components/ui/Accordion.js +228 -0
- package/lib/module/components/ui/Accordion.js.map +1 -0
- package/lib/module/components/ui/Alert-Dialog.js +266 -0
- package/lib/module/components/ui/Alert-Dialog.js.map +1 -0
- package/lib/module/components/ui/Alert.js +107 -0
- package/lib/module/components/ui/Alert.js.map +1 -0
- package/lib/module/components/ui/AppBar.js +403 -0
- package/lib/module/components/ui/AppBar.js.map +1 -0
- package/lib/module/components/ui/Aspect-Ratio.js +27 -0
- package/lib/module/components/ui/Aspect-Ratio.js.map +1 -0
- package/lib/module/components/ui/Avatar.js +97 -0
- package/lib/module/components/ui/Avatar.js.map +1 -0
- package/lib/module/components/ui/Badge.js +127 -0
- package/lib/module/components/ui/Badge.js.map +1 -0
- package/lib/module/components/ui/Bottom-Sheet.js +144 -0
- package/lib/module/components/ui/Bottom-Sheet.js.map +1 -0
- package/lib/module/components/ui/Button.js +88 -0
- package/lib/module/components/ui/Button.js.map +1 -0
- package/lib/module/components/ui/Card.js +176 -0
- package/lib/module/components/ui/Card.js.map +1 -0
- package/lib/module/components/ui/Checkbox.js +65 -0
- package/lib/module/components/ui/Checkbox.js.map +1 -0
- package/lib/module/components/ui/Collapsible.js +42 -0
- package/lib/module/components/ui/Collapsible.js.map +1 -0
- package/lib/module/components/ui/Context-Menu.js +287 -0
- package/lib/module/components/ui/Context-Menu.js.map +1 -0
- package/lib/module/components/ui/Custom-Card.js +202 -0
- package/lib/module/components/ui/Custom-Card.js.map +1 -0
- package/lib/module/components/ui/Dialog.js +202 -0
- package/lib/module/components/ui/Dialog.js.map +1 -0
- package/lib/module/components/ui/Dropdown-Menu.js +421 -0
- package/lib/module/components/ui/Dropdown-Menu.js.map +1 -0
- package/lib/module/components/ui/Floating-Action.js +50 -0
- package/lib/module/components/ui/Floating-Action.js.map +1 -0
- package/lib/module/components/ui/Greeting-Card.js +392 -0
- package/lib/module/components/ui/Greeting-Card.js.map +1 -0
- package/lib/module/components/ui/Hover-Card.js +96 -0
- package/lib/module/components/ui/Hover-Card.js.map +1 -0
- package/lib/module/components/ui/Icon.js +73 -0
- package/lib/module/components/ui/Icon.js.map +1 -0
- package/lib/module/components/ui/Input.js +74 -0
- package/lib/module/components/ui/Input.js.map +1 -0
- package/lib/module/components/ui/Label.js +44 -0
- package/lib/module/components/ui/Label.js.map +1 -0
- package/lib/module/components/ui/Menubar.js +375 -0
- package/lib/module/components/ui/Menubar.js.map +1 -0
- package/lib/module/components/ui/Native-Only-Animated-View.js +41 -0
- package/lib/module/components/ui/Native-Only-Animated-View.js.map +1 -0
- package/lib/module/components/ui/NavBar.js +352 -0
- package/lib/module/components/ui/NavBar.js.map +1 -0
- package/lib/module/components/ui/Popover.js +101 -0
- package/lib/module/components/ui/Popover.js.map +1 -0
- package/lib/module/components/ui/Progress.js +124 -0
- package/lib/module/components/ui/Progress.js.map +1 -0
- package/lib/module/components/ui/Radio-Group.js +75 -0
- package/lib/module/components/ui/Radio-Group.js.map +1 -0
- package/lib/module/components/ui/Select.js +269 -0
- package/lib/module/components/ui/Select.js.map +1 -0
- package/lib/module/components/ui/Separator.js +58 -0
- package/lib/module/components/ui/Separator.js.map +1 -0
- package/lib/module/components/ui/SizedBox.js +101 -0
- package/lib/module/components/ui/SizedBox.js.map +1 -0
- package/lib/module/components/ui/Skeleton.js +57 -0
- package/lib/module/components/ui/Skeleton.js.map +1 -0
- package/lib/module/components/ui/Slider.js +169 -0
- package/lib/module/components/ui/Slider.js.map +1 -0
- package/lib/module/components/ui/Switch.js +55 -0
- package/lib/module/components/ui/Switch.js.map +1 -0
- package/lib/module/components/ui/Table.js +150 -0
- package/lib/module/components/ui/Table.js.map +1 -0
- package/lib/module/components/ui/Tabs.js +106 -0
- package/lib/module/components/ui/Tabs.js.map +1 -0
- package/lib/module/components/ui/Text.js +69 -0
- package/lib/module/components/ui/Text.js.map +1 -0
- package/lib/module/components/ui/Textarea.js +88 -0
- package/lib/module/components/ui/Textarea.js.map +1 -0
- package/lib/module/components/ui/Theme-Toggle.js +156 -0
- package/lib/module/components/ui/Theme-Toggle.js.map +1 -0
- package/lib/module/components/ui/Toast.js +101 -0
- package/lib/module/components/ui/Toast.js.map +1 -0
- package/lib/module/components/ui/Toggle-Group.js +129 -0
- package/lib/module/components/ui/Toggle-Group.js.map +1 -0
- package/lib/module/components/ui/Toggle.js +106 -0
- package/lib/module/components/ui/Toggle.js.map +1 -0
- package/lib/module/components/ui/Tooltip.js +106 -0
- package/lib/module/components/ui/Tooltip.js.map +1 -0
- package/lib/module/components/ui/index.js +45 -0
- package/lib/module/components/ui/index.js.map +1 -0
- package/lib/module/index.js +19 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/lib/ThemeProvider.js +173 -0
- package/lib/module/lib/ThemeProvider.js.map +1 -0
- package/lib/module/lib/cornerRadius.js +164 -0
- package/lib/module/lib/cornerRadius.js.map +1 -0
- package/lib/module/lib/fonts.js +25 -0
- package/lib/module/lib/fonts.js.map +1 -0
- package/lib/module/lib/theme.js +212 -0
- package/lib/module/lib/theme.js.map +1 -0
- package/lib/module/lib/utils.js +137 -0
- package/lib/module/lib/utils.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/package.json +208 -0
- package/src/app/_layout.tsx +25 -0
- package/src/assets/icons/weather_icons/drizzle.png +0 -0
- package/src/assets/icons/weather_icons/foggy.png +0 -0
- package/src/assets/icons/weather_icons/freezing_rain.png +0 -0
- package/src/assets/icons/weather_icons/partly_cloudy.png +0 -0
- package/src/assets/icons/weather_icons/rainy.png +0 -0
- package/src/assets/icons/weather_icons/showers.png +0 -0
- package/src/assets/icons/weather_icons/sunny_weather.png +0 -0
- package/src/assets/icons/weather_icons/thunderstorm.png +0 -0
- package/src/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
- package/src/components/theme-config.ts +331 -0
- package/src/components/ui/Accordion.tsx +253 -0
- package/src/components/ui/Alert-Dialog.tsx +295 -0
- package/src/components/ui/Alert.tsx +137 -0
- package/src/components/ui/AppBar.tsx +551 -0
- package/src/components/ui/Aspect-Ratio.tsx +25 -0
- package/src/components/ui/Avatar.tsx +103 -0
- package/src/components/ui/Badge.tsx +121 -0
- package/src/components/ui/Bottom-Sheet.tsx +224 -0
- package/src/components/ui/Button.tsx +100 -0
- package/src/components/ui/Card.tsx +185 -0
- package/src/components/ui/Checkbox.tsx +81 -0
- package/src/components/ui/Collapsible.tsx +40 -0
- package/src/components/ui/Context-Menu.tsx +407 -0
- package/src/components/ui/Custom-Card.tsx +226 -0
- package/src/components/ui/Dialog.tsx +240 -0
- package/src/components/ui/Dropdown-Menu.tsx +544 -0
- package/src/components/ui/Floating-Action.tsx +54 -0
- package/src/components/ui/Greeting-Card.tsx +471 -0
- package/src/components/ui/Hover-Card.tsx +101 -0
- package/src/components/ui/Icon.tsx +75 -0
- package/src/components/ui/Input.tsx +90 -0
- package/src/components/ui/Label.tsx +48 -0
- package/src/components/ui/Menubar.tsx +509 -0
- package/src/components/ui/Native-Only-Animated-View.tsx +37 -0
- package/src/components/ui/NavBar.tsx +397 -0
- package/src/components/ui/Popover.tsx +110 -0
- package/src/components/ui/Progress.tsx +138 -0
- package/src/components/ui/Radio-Group.tsx +79 -0
- package/src/components/ui/Select.tsx +344 -0
- package/src/components/ui/Separator.tsx +68 -0
- package/src/components/ui/SizedBox.tsx +116 -0
- package/src/components/ui/Skeleton.tsx +55 -0
- package/src/components/ui/Slider.tsx +222 -0
- package/src/components/ui/Switch.tsx +67 -0
- package/src/components/ui/Table.tsx +170 -0
- package/src/components/ui/Tabs.tsx +119 -0
- package/src/components/ui/Text.tsx +73 -0
- package/src/components/ui/Textarea.tsx +93 -0
- package/src/components/ui/Theme-Toggle.tsx +204 -0
- package/src/components/ui/Toast.tsx +127 -0
- package/src/components/ui/Toggle-Group.tsx +160 -0
- package/src/components/ui/Toggle.tsx +122 -0
- package/src/components/ui/Tooltip.tsx +117 -0
- package/src/components/ui/index.ts +42 -0
- package/src/index.tsx +24 -0
- package/src/lib/ThemeProvider.tsx +204 -0
- package/src/lib/cornerRadius.ts +160 -0
- package/src/lib/fonts.ts +28 -0
- package/src/lib/theme.ts +151 -0
- 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 };
|