@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,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
|
+
}
|