@sudobility/components-rn 1.0.37 → 1.0.39
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/dist/index.cjs.js +201 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +202 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/ui/PopupSelect/PopupSelect.d.ts +46 -0
- package/dist/ui/PopupSelect/PopupSelect.d.ts.map +1 -0
- package/dist/ui/PopupSelect/index.d.ts +3 -0
- package/dist/ui/PopupSelect/index.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/ui/PopupSelect/PopupSelect.tsx +247 -0
- package/src/ui/PopupSelect/index.ts +2 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export interface PopupSelectOption {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface PopupSelectProps {
|
|
8
|
+
/** Currently selected value */
|
|
9
|
+
value?: string;
|
|
10
|
+
/** Callback when value changes */
|
|
11
|
+
onValueChange?: (value: string) => void;
|
|
12
|
+
/** Options to display */
|
|
13
|
+
options: PopupSelectOption[];
|
|
14
|
+
/** Placeholder text when no value selected */
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
/** Title shown in the modal header */
|
|
17
|
+
title?: string;
|
|
18
|
+
/** Whether the select is disabled */
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/** Style overrides for the trigger button */
|
|
21
|
+
triggerStyle?: object;
|
|
22
|
+
/** Style overrides for the trigger text */
|
|
23
|
+
triggerTextStyle?: object;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* PopupSelect Component
|
|
27
|
+
*
|
|
28
|
+
* A select component that opens a pageSheet modal with a styled option list.
|
|
29
|
+
* Provides a native-feeling selection experience on iOS and Android.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <PopupSelect
|
|
34
|
+
* value={selected}
|
|
35
|
+
* onValueChange={setSelected}
|
|
36
|
+
* options={[
|
|
37
|
+
* { label: 'Option A', value: 'a' },
|
|
38
|
+
* { label: 'Option B', value: 'b' },
|
|
39
|
+
* ]}
|
|
40
|
+
* placeholder="Choose..."
|
|
41
|
+
* title="Select Option"
|
|
42
|
+
* />
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare const PopupSelect: React.FC<PopupSelectProps>;
|
|
46
|
+
//# sourceMappingURL=PopupSelect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PopupSelect.d.ts","sourceRoot":"","sources":["../../../src/ui/PopupSelect/PopupSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAYrD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,yBAAyB;IACzB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAkGlD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/PopupSelect/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudobility/components-rn",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.39",
|
|
4
4
|
"description": "React Native UI components and design system - Ported from @sudobility/components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.esm.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
],
|
|
42
42
|
"author": "Sudobility",
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@sudobility/design": "^1.1.
|
|
44
|
+
"@sudobility/design": "^1.1.25",
|
|
45
45
|
"@sudobility/types": "^1.9.61",
|
|
46
46
|
"nativewind": ">=4.0.0",
|
|
47
47
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@react-native/babel-preset": "^0.84.0",
|
|
65
65
|
"@react-native/js-polyfills": "^0.84.0",
|
|
66
66
|
"@react-native/normalize-colors": "^0.84.0",
|
|
67
|
-
"@sudobility/design": "^1.1.
|
|
67
|
+
"@sudobility/design": "^1.1.25",
|
|
68
68
|
"@sudobility/types": "^1.9.61",
|
|
69
69
|
"@testing-library/jest-native": "^5.4.3",
|
|
70
70
|
"@testing-library/react-native": "^13.3.3",
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
Modal,
|
|
7
|
+
FlatList,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
Pressable,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
12
|
+
|
|
13
|
+
export interface PopupSelectOption {
|
|
14
|
+
label: string;
|
|
15
|
+
value: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PopupSelectProps {
|
|
20
|
+
/** Currently selected value */
|
|
21
|
+
value?: string;
|
|
22
|
+
/** Callback when value changes */
|
|
23
|
+
onValueChange?: (value: string) => void;
|
|
24
|
+
/** Options to display */
|
|
25
|
+
options: PopupSelectOption[];
|
|
26
|
+
/** Placeholder text when no value selected */
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
/** Title shown in the modal header */
|
|
29
|
+
title?: string;
|
|
30
|
+
/** Whether the select is disabled */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** Style overrides for the trigger button */
|
|
33
|
+
triggerStyle?: object;
|
|
34
|
+
/** Style overrides for the trigger text */
|
|
35
|
+
triggerTextStyle?: object;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ItemSeparator = () => <View style={styles.separator} />;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* PopupSelect Component
|
|
42
|
+
*
|
|
43
|
+
* A select component that opens a pageSheet modal with a styled option list.
|
|
44
|
+
* Provides a native-feeling selection experience on iOS and Android.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* <PopupSelect
|
|
49
|
+
* value={selected}
|
|
50
|
+
* onValueChange={setSelected}
|
|
51
|
+
* options={[
|
|
52
|
+
* { label: 'Option A', value: 'a' },
|
|
53
|
+
* { label: 'Option B', value: 'b' },
|
|
54
|
+
* ]}
|
|
55
|
+
* placeholder="Choose..."
|
|
56
|
+
* title="Select Option"
|
|
57
|
+
* />
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export const PopupSelect: React.FC<PopupSelectProps> = ({
|
|
61
|
+
value,
|
|
62
|
+
onValueChange,
|
|
63
|
+
options,
|
|
64
|
+
placeholder = 'Select...',
|
|
65
|
+
title = 'Select',
|
|
66
|
+
disabled = false,
|
|
67
|
+
triggerStyle,
|
|
68
|
+
triggerTextStyle,
|
|
69
|
+
}) => {
|
|
70
|
+
const insets = useSafeAreaInsets();
|
|
71
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
72
|
+
|
|
73
|
+
const selectedOption = options.find(opt => opt.value === value);
|
|
74
|
+
|
|
75
|
+
const handleSelect = useCallback(
|
|
76
|
+
(optionValue: string) => {
|
|
77
|
+
onValueChange?.(optionValue);
|
|
78
|
+
setModalVisible(false);
|
|
79
|
+
},
|
|
80
|
+
[onValueChange]
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const renderOption = ({ item }: { item: PopupSelectOption }) => {
|
|
84
|
+
const isSelected = item.value === value;
|
|
85
|
+
return (
|
|
86
|
+
<TouchableOpacity
|
|
87
|
+
style={[styles.optionItem, isSelected && styles.optionItemSelected]}
|
|
88
|
+
onPress={() => !item.disabled && handleSelect(item.value)}
|
|
89
|
+
disabled={item.disabled}
|
|
90
|
+
activeOpacity={0.7}
|
|
91
|
+
>
|
|
92
|
+
<Text
|
|
93
|
+
style={[
|
|
94
|
+
styles.optionLabel,
|
|
95
|
+
isSelected && styles.optionLabelSelected,
|
|
96
|
+
item.disabled && styles.optionDisabled,
|
|
97
|
+
]}
|
|
98
|
+
>
|
|
99
|
+
{item.label}
|
|
100
|
+
</Text>
|
|
101
|
+
{isSelected && <Text style={styles.checkmark}>✓</Text>}
|
|
102
|
+
</TouchableOpacity>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<>
|
|
108
|
+
<TouchableOpacity
|
|
109
|
+
style={[
|
|
110
|
+
styles.trigger,
|
|
111
|
+
disabled && styles.triggerDisabled,
|
|
112
|
+
triggerStyle,
|
|
113
|
+
]}
|
|
114
|
+
onPress={() => !disabled && setModalVisible(true)}
|
|
115
|
+
activeOpacity={0.7}
|
|
116
|
+
>
|
|
117
|
+
<Text
|
|
118
|
+
style={[
|
|
119
|
+
styles.triggerText,
|
|
120
|
+
!selectedOption && styles.triggerPlaceholder,
|
|
121
|
+
triggerTextStyle,
|
|
122
|
+
]}
|
|
123
|
+
numberOfLines={1}
|
|
124
|
+
>
|
|
125
|
+
{selectedOption?.label ?? placeholder}
|
|
126
|
+
</Text>
|
|
127
|
+
<Text style={styles.triggerArrow}>▼</Text>
|
|
128
|
+
</TouchableOpacity>
|
|
129
|
+
|
|
130
|
+
<Modal
|
|
131
|
+
visible={modalVisible}
|
|
132
|
+
animationType='slide'
|
|
133
|
+
presentationStyle='pageSheet'
|
|
134
|
+
onRequestClose={() => setModalVisible(false)}
|
|
135
|
+
>
|
|
136
|
+
<View style={[styles.modalContainer, { paddingTop: insets.top }]}>
|
|
137
|
+
<View style={styles.modalHeader}>
|
|
138
|
+
<Text style={styles.modalTitle}>{title}</Text>
|
|
139
|
+
<Pressable
|
|
140
|
+
style={styles.closeButton}
|
|
141
|
+
onPress={() => setModalVisible(false)}
|
|
142
|
+
>
|
|
143
|
+
<Text style={styles.closeButtonText}>Done</Text>
|
|
144
|
+
</Pressable>
|
|
145
|
+
</View>
|
|
146
|
+
|
|
147
|
+
<FlatList
|
|
148
|
+
data={options}
|
|
149
|
+
keyExtractor={(item: PopupSelectOption) => item.value}
|
|
150
|
+
renderItem={renderOption}
|
|
151
|
+
contentContainerStyle={styles.listContent}
|
|
152
|
+
ItemSeparatorComponent={ItemSeparator}
|
|
153
|
+
/>
|
|
154
|
+
</View>
|
|
155
|
+
</Modal>
|
|
156
|
+
</>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const styles = StyleSheet.create({
|
|
161
|
+
trigger: {
|
|
162
|
+
flexDirection: 'row',
|
|
163
|
+
alignItems: 'center',
|
|
164
|
+
justifyContent: 'space-between',
|
|
165
|
+
borderWidth: 1,
|
|
166
|
+
borderColor: '#d1d5db',
|
|
167
|
+
borderRadius: 8,
|
|
168
|
+
paddingHorizontal: 12,
|
|
169
|
+
paddingVertical: 12,
|
|
170
|
+
backgroundColor: '#ffffff',
|
|
171
|
+
},
|
|
172
|
+
triggerDisabled: {
|
|
173
|
+
opacity: 0.5,
|
|
174
|
+
},
|
|
175
|
+
triggerText: {
|
|
176
|
+
flex: 1,
|
|
177
|
+
fontSize: 16,
|
|
178
|
+
color: '#1a1a1a',
|
|
179
|
+
},
|
|
180
|
+
triggerPlaceholder: {
|
|
181
|
+
color: '#9ca3af',
|
|
182
|
+
},
|
|
183
|
+
triggerArrow: {
|
|
184
|
+
fontSize: 10,
|
|
185
|
+
color: '#9ca3af',
|
|
186
|
+
marginLeft: 8,
|
|
187
|
+
},
|
|
188
|
+
modalContainer: {
|
|
189
|
+
flex: 1,
|
|
190
|
+
backgroundColor: '#ffffff',
|
|
191
|
+
},
|
|
192
|
+
modalHeader: {
|
|
193
|
+
flexDirection: 'row',
|
|
194
|
+
justifyContent: 'space-between',
|
|
195
|
+
alignItems: 'center',
|
|
196
|
+
paddingHorizontal: 16,
|
|
197
|
+
paddingVertical: 12,
|
|
198
|
+
borderBottomWidth: 1,
|
|
199
|
+
borderBottomColor: '#e9ecef',
|
|
200
|
+
},
|
|
201
|
+
modalTitle: {
|
|
202
|
+
fontSize: 18,
|
|
203
|
+
fontWeight: '600',
|
|
204
|
+
color: '#1a1a1a',
|
|
205
|
+
},
|
|
206
|
+
closeButton: {
|
|
207
|
+
padding: 8,
|
|
208
|
+
},
|
|
209
|
+
closeButtonText: {
|
|
210
|
+
fontSize: 16,
|
|
211
|
+
fontWeight: '600',
|
|
212
|
+
color: '#3b82f6',
|
|
213
|
+
},
|
|
214
|
+
listContent: {
|
|
215
|
+
padding: 16,
|
|
216
|
+
},
|
|
217
|
+
optionItem: {
|
|
218
|
+
flexDirection: 'row',
|
|
219
|
+
alignItems: 'center',
|
|
220
|
+
padding: 16,
|
|
221
|
+
backgroundColor: '#f8f9fa',
|
|
222
|
+
borderRadius: 12,
|
|
223
|
+
},
|
|
224
|
+
optionItemSelected: {
|
|
225
|
+
backgroundColor: '#e0f2fe',
|
|
226
|
+
},
|
|
227
|
+
optionLabel: {
|
|
228
|
+
flex: 1,
|
|
229
|
+
fontSize: 16,
|
|
230
|
+
color: '#1a1a1a',
|
|
231
|
+
},
|
|
232
|
+
optionLabelSelected: {
|
|
233
|
+
fontWeight: '600',
|
|
234
|
+
color: '#3b82f6',
|
|
235
|
+
},
|
|
236
|
+
optionDisabled: {
|
|
237
|
+
opacity: 0.5,
|
|
238
|
+
},
|
|
239
|
+
checkmark: {
|
|
240
|
+
fontSize: 18,
|
|
241
|
+
color: '#3b82f6',
|
|
242
|
+
fontWeight: 'bold',
|
|
243
|
+
},
|
|
244
|
+
separator: {
|
|
245
|
+
height: 8,
|
|
246
|
+
},
|
|
247
|
+
});
|