react-native-auto-positioned-popup 1.0.2

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Stark
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,425 @@
1
+ # React Native Auto Positioned Popup
2
+
3
+ A highly customizable React Native auto-positioned popup component with search functionality and flexible styling options. Perfect for dropdowns, autocomplete inputs, and selection lists.
4
+
5
+ English | [δΈ­ζ–‡](./README_zh.md)
6
+
7
+ ## Features
8
+
9
+ πŸš€ **Auto-positioning**: Automatically adjusts popup position based on screen space
10
+ πŸ” **Search functionality**: Built-in search with debounced input
11
+ πŸ“± **Cross-platform**: Works on both iOS and Android
12
+ 🎨 **Customizable**: Extensive styling and theming options
13
+ ⚑ **Performance optimized**: Efficient rendering with AdvancedFlatList
14
+ 🎯 **TypeScript support**: Full TypeScript definitions included
15
+ πŸ”„ **Dynamic view management**: RootView-based popup system
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install react-native-auto-positioned-popup
21
+ ```
22
+
23
+ or
24
+
25
+ ```bash
26
+ yarn add react-native-auto-positioned-popup
27
+ ```
28
+
29
+ ## Basic Usage
30
+
31
+ First, wrap your app with the `RootViewProvider`:
32
+
33
+ ```tsx
34
+ import { RootViewProvider } from 'react-native-auto-positioned-popup';
35
+
36
+ const App = () => {
37
+ return (
38
+ <RootViewProvider>
39
+ {/* Your app content */}
40
+ </RootViewProvider>
41
+ );
42
+ };
43
+ ```
44
+
45
+ Then use the `AutoPositionedPopup` component:
46
+
47
+ ```tsx
48
+ import React, { useState } from 'react';
49
+ import { View } from 'react-native';
50
+ import AutoPositionedPopup, { SelectedItem, Data } from 'react-native-auto-positioned-popup';
51
+
52
+ const MyComponent = () => {
53
+ const [selectedItem, setSelectedItem] = useState<SelectedItem | undefined>();
54
+
55
+ const fetchData = async ({ pageIndex, pageSize, searchQuery }): Promise<Data | null> => {
56
+ // Your data fetching logic here
57
+ return {
58
+ items: [
59
+ { id: '1', title: 'Option 1' },
60
+ { id: '2', title: 'Option 2' },
61
+ { id: '3', title: 'Option 3' },
62
+ ],
63
+ pageIndex: 0,
64
+ needLoadMore: false,
65
+ };
66
+ };
67
+
68
+ return (
69
+ <View style={{ padding: 20 }}>
70
+ <AutoPositionedPopup
71
+ tag="example-popup"
72
+ placeholder="Select an option"
73
+ selectedItem={selectedItem}
74
+ fetchData={fetchData}
75
+ onItemSelected={(item) => setSelectedItem(item)}
76
+ useTextInput={true}
77
+ />
78
+ </View>
79
+ );
80
+ };
81
+
82
+ export default MyComponent;
83
+ ```
84
+
85
+ ## Advanced Usage
86
+
87
+ ### Custom Row Component
88
+
89
+ ```tsx
90
+ <AutoPositionedPopup
91
+ tag="custom-popup"
92
+ CustomRow={({ children }) => (
93
+ <View style={{ flexDirection: 'row', alignItems: 'center', padding: 10 }}>
94
+ <Text style={{ marginRight: 10 }}>Select:</Text>
95
+ {children}
96
+ </View>
97
+ )}
98
+ // ... other props
99
+ />
100
+ ```
101
+
102
+ ### Custom Item Rendering
103
+
104
+ ```tsx
105
+ <AutoPositionedPopup
106
+ tag="custom-items"
107
+ renderItem={({ item, index }) => (
108
+ <View style={{ padding: 15, borderBottomWidth: 1 }}>
109
+ <Text style={{ fontWeight: 'bold' }}>{item.title}</Text>
110
+ <Text style={{ color: '#666', fontSize: 12 }}>ID: {item.id}</Text>
111
+ </View>
112
+ )}
113
+ // ... other props
114
+ />
115
+ ```
116
+
117
+ ### With Custom Styling
118
+
119
+ ```tsx
120
+ <AutoPositionedPopup
121
+ tag="styled-popup"
122
+ style={{ backgroundColor: '#f5f5f5', borderRadius: 8 }}
123
+ AutoPositionedPopupBtnStyle={{
124
+ backgroundColor: '#e0e0e0',
125
+ padding: 15,
126
+ borderRadius: 8,
127
+ }}
128
+ inputStyle={{
129
+ fontSize: 16,
130
+ color: '#333',
131
+ }}
132
+ popUpViewStyle={{
133
+ left: '10%',
134
+ width: '80%',
135
+ }}
136
+ // ... other props
137
+ />
138
+ ```
139
+
140
+ ### Complete Dropdown Example (useTextInput=false)
141
+
142
+ This example shows a complete implementation without search input, suitable for dropdowns and selectors:
143
+
144
+ ```tsx
145
+ import React, { useState } from 'react';
146
+ import { View, Text, Image, StyleSheet } from 'react-native';
147
+ import AutoPositionedPopup, { SelectedItem, Data, RootViewProvider } from 'react-native-auto-positioned-popup';
148
+
149
+ // Sample data type with color support
150
+ interface ClinicItem extends SelectedItem {
151
+ code: string;
152
+ textColor: string;
153
+ address?: string;
154
+ }
155
+
156
+ const ClinicSelector = () => {
157
+ const [selectedClinic, setSelectedClinic] = useState<ClinicItem | null>(null);
158
+
159
+ const fetchClinics = async ({ pageIndex, pageSize }): Promise<Data | null> => {
160
+ // Simulate API call
161
+ const mockClinics = [
162
+ { id: '1', title: 'Main Clinic', code: 'MC001', textColor: '#4CAF50', address: '123 Main St' },
163
+ { id: '2', title: 'Downtown Clinic', code: 'DC002', textColor: '#2196F3', address: '456 Downtown Ave' },
164
+ { id: '3', title: 'Suburb Clinic', code: 'SC003', textColor: '#FF9800', address: '789 Suburb Rd' },
165
+ ];
166
+
167
+ return {
168
+ items: mockClinics.map(clinic => ({
169
+ title: clinic.code,
170
+ ...clinic,
171
+ })),
172
+ pageIndex,
173
+ needLoadMore: false,
174
+ };
175
+ };
176
+
177
+ return (
178
+ <RootViewProvider>
179
+ <View style={styles.container}>
180
+ <AutoPositionedPopup
181
+ tag="clinic-selector"
182
+ useTextInput={false}
183
+ localSearch={false}
184
+ forceRemoveAllRootViewOnItemSelected={true}
185
+ selectedItem={selectedClinic ? {
186
+ title: selectedClinic.code,
187
+ ...selectedClinic,
188
+ } : undefined}
189
+ CustomRow={({ children }) => (
190
+ <View style={styles.sectionRow}>
191
+ <Text style={styles.sectionRowLabel}>Clinic</Text>
192
+ {children}
193
+ <Image
194
+ source={require('./assets/arrow-down.png')}
195
+ style={styles.selectArrow}
196
+ />
197
+ </View>
198
+ )}
199
+ AutoPositionedPopupBtnStyle={styles.selectorButton}
200
+ btwChildren={() => (
201
+ <>
202
+ {!selectedClinic ? (
203
+ <Text style={styles.placeholderText} numberOfLines={1}>
204
+ Please Select
205
+ </Text>
206
+ ) : (
207
+ <View style={styles.selectedItemContainer}>
208
+ <View
209
+ style={[
210
+ styles.colorIndicator,
211
+ { backgroundColor: selectedClinic.textColor }
212
+ ]}
213
+ />
214
+ <Text style={styles.selectedText} numberOfLines={1}>
215
+ {selectedClinic.code}
216
+ </Text>
217
+ </View>
218
+ )}
219
+ </>
220
+ )}
221
+ fetchData={fetchClinics}
222
+ onItemSelected={(item: ClinicItem) => {
223
+ console.log('Selected clinic:', item);
224
+ setSelectedClinic(item);
225
+ }}
226
+ />
227
+ </View>
228
+ </RootViewProvider>
229
+ );
230
+ };
231
+
232
+ const styles = StyleSheet.create({
233
+ container: {
234
+ padding: 20,
235
+ },
236
+ sectionRow: {
237
+ flexDirection: 'row',
238
+ alignItems: 'center',
239
+ paddingVertical: 15,
240
+ paddingHorizontal: 16,
241
+ backgroundColor: '#fff',
242
+ borderRadius: 8,
243
+ shadowColor: '#000',
244
+ shadowOffset: { width: 0, height: 1 },
245
+ shadowOpacity: 0.1,
246
+ shadowRadius: 2,
247
+ elevation: 2,
248
+ },
249
+ sectionRowLabel: {
250
+ fontSize: 16,
251
+ fontWeight: '600',
252
+ color: '#333',
253
+ marginRight: 12,
254
+ minWidth: 60,
255
+ },
256
+ selectorButton: {
257
+ flex: 1,
258
+ alignItems: 'flex-start',
259
+ },
260
+ selectArrow: {
261
+ width: 12,
262
+ height: 12,
263
+ marginLeft: 8,
264
+ },
265
+ placeholderText: {
266
+ fontSize: 15,
267
+ color: '#999',
268
+ },
269
+ selectedItemContainer: {
270
+ flexDirection: 'row',
271
+ alignItems: 'center',
272
+ },
273
+ colorIndicator: {
274
+ width: 12,
275
+ height: 12,
276
+ borderRadius: 6,
277
+ marginRight: 8,
278
+ },
279
+ selectedText: {
280
+ fontSize: 15,
281
+ fontWeight: '500',
282
+ color: '#333',
283
+ },
284
+ });
285
+
286
+ export default ClinicSelector;
287
+ ```
288
+
289
+ ## API Reference
290
+
291
+ ### Props
292
+
293
+ | Prop | Type | Default | Description |
294
+ |------|------|---------|-------------|
295
+ | `tag` | `string` | **Required** | Unique identifier for the popup |
296
+ | `fetchData` | `function` | `undefined` | Function to fetch data for the popup list |
297
+ | `selectedItem` | `SelectedItem` | `undefined` | Currently selected item |
298
+ | `onItemSelected` | `function` | `undefined` | Callback when an item is selected |
299
+ | `placeholder` | `string` | `'Please Select'` | Placeholder text |
300
+ | `useTextInput` | `boolean` | `false` | Enable search input functionality |
301
+ | `localSearch` | `boolean` | `false` | Enable local filtering of data |
302
+ | `pageSize` | `number` | `20` | Number of items per page |
303
+ | `textAlign` | `'left' \| 'center' \| 'right'` | `'right'` | Text alignment |
304
+ | `AutoPositionedPopupBtnDisabled` | `boolean` | `false` | Disable the popup trigger button |
305
+ | `style` | `ViewStyle` | `undefined` | Container style |
306
+ | `AutoPositionedPopupBtnStyle` | `ViewStyle` | `undefined` | Button style |
307
+ | `inputStyle` | `TextStyle` | `undefined` | Input field style |
308
+ | `labelStyle` | `ViewStyle` | `undefined` | Label text style |
309
+ | `popUpViewStyle` | `ViewStyle` | `{ left: '5%', width: '90%' }` | Popup container positioning |
310
+
311
+ ### Data Structure
312
+
313
+ #### SelectedItem
314
+ ```typescript
315
+ interface SelectedItem {
316
+ id: string;
317
+ title: string;
318
+ }
319
+ ```
320
+
321
+ #### Data (for fetchData return)
322
+ ```typescript
323
+ interface Data {
324
+ items: SelectedItem[];
325
+ pageIndex: number;
326
+ needLoadMore: boolean;
327
+ }
328
+ ```
329
+
330
+ ### Methods (via ref)
331
+
332
+ ```typescript
333
+ const popupRef = useRef();
334
+
335
+ // Clear the selected item
336
+ popupRef.current?.clearSelectedItem();
337
+
338
+ // Programmatically show popup
339
+ popupRef.current?.showPopup();
340
+
341
+ // Programmatically hide popup
342
+ popupRef.current?.hidePopup();
343
+ ```
344
+
345
+ ## Customization Examples
346
+
347
+ ### Theming
348
+
349
+ The component supports custom theming by overriding the default styles:
350
+
351
+ ```tsx
352
+ const customTheme = {
353
+ colors: {
354
+ text: '#2c3e50',
355
+ placeholderText: '#95a5a6',
356
+ background: '#ecf0f1',
357
+ border: '#bdc3c7',
358
+ },
359
+ };
360
+ ```
361
+
362
+ ### Custom Search Logic
363
+
364
+ ```tsx
365
+ const fetchDataWithSearch = async ({ pageIndex, pageSize, searchQuery }) => {
366
+ const allItems = [
367
+ { id: '1', title: 'Apple' },
368
+ { id: '2', title: 'Banana' },
369
+ { id: '3', title: 'Cherry' },
370
+ // ... more items
371
+ ];
372
+
373
+ const filteredItems = searchQuery
374
+ ? allItems.filter(item =>
375
+ item.title.toLowerCase().includes(searchQuery.toLowerCase())
376
+ )
377
+ : allItems;
378
+
379
+ return {
380
+ items: filteredItems.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize),
381
+ pageIndex,
382
+ needLoadMore: (pageIndex + 1) * pageSize < filteredItems.length,
383
+ };
384
+ };
385
+ ```
386
+
387
+ ## Performance Tips
388
+
389
+ 1. **Use keyExtractor**: Provide a stable key for list items
390
+ ```tsx
391
+ keyExtractor={(item) => item.id}
392
+ ```
393
+
394
+ 2. **Optimize renderItem**: Use React.memo for custom item components
395
+ ```tsx
396
+ const CustomItem = React.memo(({ item }) => (
397
+ <View>{/* Your custom item */}</View>
398
+ ));
399
+ ```
400
+
401
+ 3. **Debounced Search**: The component includes built-in debounced search (300ms delay)
402
+
403
+ 4. **Local vs Remote Search**: Use `localSearch={true}` for small datasets, `false` for server-side filtering
404
+
405
+ ## Requirements
406
+
407
+ - React Native >= 0.60.0
408
+ - React >= 16.8.0 (Hooks support)
409
+
410
+ ## Contributing
411
+
412
+ Contributions are welcome! Please feel free to submit a Pull Request.
413
+
414
+ ## License
415
+
416
+ MIT Β© [Stark](https://github.com/your-username)
417
+
418
+ ## Changelog
419
+
420
+ ### 1.0.0
421
+ - Initial release
422
+ - Auto-positioning functionality
423
+ - Search support
424
+ - TypeScript definitions
425
+ - Cross-platform compatibility