@umituz/react-native-design-system 1.5.36 → 1.5.38
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/README.md +2 -2
- package/package.json +7 -5
- package/src/index.ts +29 -221
- package/src/presentation/organisms/AppHeader.tsx +3 -5
- package/src/presentation/tokens/commonStyles.ts +1 -1
- package/src/presentation/atoms/AtomicAvatar.tsx +0 -157
- package/src/presentation/atoms/AtomicAvatarGroup.tsx +0 -169
- package/src/presentation/atoms/AtomicBadge.tsx +0 -232
- package/src/presentation/atoms/AtomicButton.tsx +0 -236
- package/src/presentation/atoms/AtomicCard.tsx +0 -107
- package/src/presentation/atoms/AtomicChip.tsx +0 -223
- package/src/presentation/atoms/AtomicDatePicker.tsx +0 -347
- package/src/presentation/atoms/AtomicDivider.tsx +0 -114
- package/src/presentation/atoms/AtomicFab.tsx +0 -98
- package/src/presentation/atoms/AtomicFilter.tsx +0 -154
- package/src/presentation/atoms/AtomicFormError.tsx +0 -105
- package/src/presentation/atoms/AtomicIcon.tsx +0 -40
- package/src/presentation/atoms/AtomicImage.tsx +0 -149
- package/src/presentation/atoms/AtomicInput.tsx +0 -363
- package/src/presentation/atoms/AtomicNumberInput.tsx +0 -182
- package/src/presentation/atoms/AtomicPicker.tsx +0 -458
- package/src/presentation/atoms/AtomicProgress.tsx +0 -139
- package/src/presentation/atoms/AtomicSearchBar.tsx +0 -114
- package/src/presentation/atoms/AtomicSort.tsx +0 -145
- package/src/presentation/atoms/AtomicSwitch.tsx +0 -166
- package/src/presentation/atoms/AtomicText.tsx +0 -55
- package/src/presentation/atoms/AtomicTextArea.tsx +0 -313
- package/src/presentation/atoms/AtomicTouchable.tsx +0 -209
- package/src/presentation/atoms/fab/styles/fabStyles.ts +0 -69
- package/src/presentation/atoms/fab/types/index.ts +0 -82
- package/src/presentation/atoms/filter/styles/filterStyles.ts +0 -32
- package/src/presentation/atoms/filter/types/index.ts +0 -89
- package/src/presentation/atoms/index.ts +0 -366
- package/src/presentation/atoms/input/hooks/useInputState.ts +0 -15
- package/src/presentation/atoms/input/styles/inputStyles.ts +0 -66
- package/src/presentation/atoms/input/types/index.ts +0 -25
- package/src/presentation/atoms/picker/styles/pickerStyles.ts +0 -207
- package/src/presentation/atoms/picker/types/index.ts +0 -40
- package/src/presentation/atoms/touchable/styles/touchableStyles.ts +0 -62
- package/src/presentation/atoms/touchable/types/index.ts +0 -155
- package/src/presentation/hooks/useResponsive.ts +0 -180
- package/src/presentation/molecules/AtomicConfirmationModal.tsx +0 -243
- package/src/presentation/molecules/EmptyState.tsx +0 -130
- package/src/presentation/molecules/FormField.tsx +0 -128
- package/src/presentation/molecules/GridContainer.tsx +0 -124
- package/src/presentation/molecules/IconContainer.tsx +0 -94
- package/src/presentation/molecules/ListItem.tsx +0 -36
- package/src/presentation/molecules/ScreenHeader.tsx +0 -140
- package/src/presentation/molecules/SearchBar.tsx +0 -85
- package/src/presentation/molecules/SectionCard.tsx +0 -74
- package/src/presentation/molecules/SectionContainer.tsx +0 -106
- package/src/presentation/molecules/SectionHeader.tsx +0 -125
- package/src/presentation/molecules/confirmation-modal/styles/confirmationModalStyles.ts +0 -133
- package/src/presentation/molecules/confirmation-modal/types/index.ts +0 -105
- package/src/presentation/molecules/index.ts +0 -41
- package/src/presentation/molecules/listitem/styles/listItemStyles.ts +0 -19
- package/src/presentation/molecules/listitem/types/index.ts +0 -17
- package/src/presentation/organisms/FormContainer.tsx +0 -180
- package/src/presentation/organisms/ScreenLayout.tsx +0 -171
- package/src/presentation/organisms/index.ts +0 -25
- package/src/presentation/utils/platformConstants.ts +0 -124
- package/src/presentation/utils/responsive.ts +0 -516
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AtomicPicker Component
|
|
3
|
-
*
|
|
4
|
-
* A reusable option picker/dropdown component for selecting from a list of options.
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Single and multi-select support
|
|
8
|
-
* - Modal display mode (full-screen on mobile)
|
|
9
|
-
* - Optional search/filter capability
|
|
10
|
-
* - Error and disabled states
|
|
11
|
-
* - Theme-aware styling
|
|
12
|
-
* - Icons for options
|
|
13
|
-
* - Clearable selection
|
|
14
|
-
* - react-hook-form integration ready
|
|
15
|
-
*
|
|
16
|
-
* Architecture:
|
|
17
|
-
* - Follows AtomicButton pattern with separated types and styles
|
|
18
|
-
* - Uses helper functions from picker/styles/pickerStyles.ts
|
|
19
|
-
* - Types defined in picker/types/index.ts
|
|
20
|
-
* - Zero inline StyleSheet.create()
|
|
21
|
-
*
|
|
22
|
-
* Usage:
|
|
23
|
-
* ```tsx
|
|
24
|
-
* const [partyType, setPartyType] = useState('birthday');
|
|
25
|
-
*
|
|
26
|
-
* <AtomicPicker
|
|
27
|
-
* value={partyType}
|
|
28
|
-
* onChange={setPartyType}
|
|
29
|
-
* options={[
|
|
30
|
-
* { label: 'Birthday Party', value: 'birthday', icon: 'cake' },
|
|
31
|
-
* { label: 'Wedding', value: 'wedding', icon: 'heart' },
|
|
32
|
-
* { label: 'Corporate Event', value: 'corporate', icon: 'briefcase' },
|
|
33
|
-
* ]}
|
|
34
|
-
* label="Party Type"
|
|
35
|
-
* placeholder="Select party type"
|
|
36
|
-
* searchable
|
|
37
|
-
* />
|
|
38
|
-
* ```
|
|
39
|
-
*
|
|
40
|
-
* @module AtomicPicker
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
import React, { useState, useMemo } from 'react';
|
|
44
|
-
import {
|
|
45
|
-
View,
|
|
46
|
-
TouchableOpacity,
|
|
47
|
-
Modal,
|
|
48
|
-
FlatList,
|
|
49
|
-
TextInput,
|
|
50
|
-
useWindowDimensions,
|
|
51
|
-
StyleSheet,
|
|
52
|
-
} from 'react-native';
|
|
53
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
54
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
55
|
-
import { AtomicPickerProps, PickerOption } from './picker/types';
|
|
56
|
-
import { AtomicIcon } from './AtomicIcon';
|
|
57
|
-
import { AtomicText } from './AtomicText';
|
|
58
|
-
import {
|
|
59
|
-
getPickerContainerStyles,
|
|
60
|
-
getPickerLabelStyles,
|
|
61
|
-
getPickerPlaceholderStyles,
|
|
62
|
-
getPickerValueStyles,
|
|
63
|
-
getPickerErrorStyles,
|
|
64
|
-
getModalOverlayStyles,
|
|
65
|
-
getModalContainerStyles,
|
|
66
|
-
getModalHeaderStyles,
|
|
67
|
-
getModalTitleStyles,
|
|
68
|
-
getSearchContainerStyles,
|
|
69
|
-
getSearchInputStyles,
|
|
70
|
-
getOptionContainerStyles,
|
|
71
|
-
getOptionTextStyles,
|
|
72
|
-
getOptionDescriptionStyles,
|
|
73
|
-
getEmptyStateStyles,
|
|
74
|
-
getEmptyStateTextStyles,
|
|
75
|
-
getChipContainerStyles,
|
|
76
|
-
getChipStyles,
|
|
77
|
-
getChipTextStyles,
|
|
78
|
-
} from './picker/styles/pickerStyles';
|
|
79
|
-
|
|
80
|
-
export type { AtomicPickerProps, PickerOption, PickerSize } from './picker/types';
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* AtomicPicker - Universal option picker component
|
|
84
|
-
*
|
|
85
|
-
* Displays a button that opens a modal for selection.
|
|
86
|
-
* Supports single/multi-select, search, and custom rendering.
|
|
87
|
-
*/
|
|
88
|
-
export const AtomicPicker: React.FC<AtomicPickerProps> = ({
|
|
89
|
-
value,
|
|
90
|
-
onChange,
|
|
91
|
-
options,
|
|
92
|
-
label,
|
|
93
|
-
placeholder = 'Select...',
|
|
94
|
-
error,
|
|
95
|
-
disabled = false,
|
|
96
|
-
multiple = false,
|
|
97
|
-
searchable = false,
|
|
98
|
-
clearable = false,
|
|
99
|
-
autoClose = true,
|
|
100
|
-
color = 'primary',
|
|
101
|
-
size = 'md',
|
|
102
|
-
modalTitle,
|
|
103
|
-
emptyMessage = 'No options available',
|
|
104
|
-
style,
|
|
105
|
-
labelStyle,
|
|
106
|
-
testID,
|
|
107
|
-
}) => {
|
|
108
|
-
const tokens = useAppDesignTokens();
|
|
109
|
-
const { height } = useWindowDimensions();
|
|
110
|
-
const insets = useSafeAreaInsets();
|
|
111
|
-
|
|
112
|
-
const [modalVisible, setModalVisible] = useState(false);
|
|
113
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
114
|
-
|
|
115
|
-
// Get style helpers with design tokens
|
|
116
|
-
const containerStyles = getPickerContainerStyles(tokens);
|
|
117
|
-
const labelStyles = getPickerLabelStyles(tokens);
|
|
118
|
-
const placeholderStyles = getPickerPlaceholderStyles(tokens);
|
|
119
|
-
const valueStyles = getPickerValueStyles(tokens);
|
|
120
|
-
const errorStyles = getPickerErrorStyles(tokens);
|
|
121
|
-
const modalOverlayStyles = getModalOverlayStyles(tokens);
|
|
122
|
-
const modalContainerStyles = getModalContainerStyles(tokens, height * 0.85);
|
|
123
|
-
const modalHeaderStyles = getModalHeaderStyles(tokens);
|
|
124
|
-
const modalTitleStyles = getModalTitleStyles(tokens);
|
|
125
|
-
const searchContainerStyles = getSearchContainerStyles(tokens);
|
|
126
|
-
const searchInputStyles = getSearchInputStyles(tokens);
|
|
127
|
-
const emptyStateStyles = getEmptyStateStyles(tokens);
|
|
128
|
-
const emptyStateTextStyles = getEmptyStateTextStyles(tokens);
|
|
129
|
-
const chipContainerStyles = getChipContainerStyles(tokens);
|
|
130
|
-
const chipStyles = getChipStyles(tokens);
|
|
131
|
-
const chipTextStyles = getChipTextStyles(tokens);
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Normalize value to array for consistent handling
|
|
135
|
-
*/
|
|
136
|
-
const selectedValues = useMemo(() => {
|
|
137
|
-
if (multiple) {
|
|
138
|
-
return Array.isArray(value) ? value : [];
|
|
139
|
-
}
|
|
140
|
-
return value ? [value as string] : [];
|
|
141
|
-
}, [value, multiple]);
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Get selected option objects
|
|
145
|
-
*/
|
|
146
|
-
const selectedOptions = useMemo(() => {
|
|
147
|
-
return options.filter((opt) => selectedValues.includes(opt.value));
|
|
148
|
-
}, [options, selectedValues]);
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Filter options based on search query
|
|
152
|
-
*/
|
|
153
|
-
const filteredOptions = useMemo(() => {
|
|
154
|
-
if (!searchQuery.trim()) return options;
|
|
155
|
-
|
|
156
|
-
const query = searchQuery.toLowerCase();
|
|
157
|
-
return options.filter(
|
|
158
|
-
(opt) =>
|
|
159
|
-
opt.label.toLowerCase().includes(query) ||
|
|
160
|
-
opt.description?.toLowerCase().includes(query)
|
|
161
|
-
);
|
|
162
|
-
}, [options, searchQuery]);
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Format display text for selected value(s)
|
|
166
|
-
*/
|
|
167
|
-
const displayText = useMemo(() => {
|
|
168
|
-
if (selectedOptions.length === 0) {
|
|
169
|
-
return placeholder;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (multiple) {
|
|
173
|
-
return selectedOptions.length === 1
|
|
174
|
-
? selectedOptions[0].label
|
|
175
|
-
: `${selectedOptions.length} selected`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return selectedOptions[0]?.label || placeholder;
|
|
179
|
-
}, [selectedOptions, placeholder, multiple]);
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Handle modal open
|
|
183
|
-
*/
|
|
184
|
-
const openModal = () => {
|
|
185
|
-
if (disabled) return;
|
|
186
|
-
setModalVisible(true);
|
|
187
|
-
setSearchQuery('');
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Handle modal close
|
|
192
|
-
*/
|
|
193
|
-
const closeModal = () => {
|
|
194
|
-
setModalVisible(false);
|
|
195
|
-
setSearchQuery('');
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Handle option selection
|
|
200
|
-
*/
|
|
201
|
-
const handleSelect = (optionValue: string) => {
|
|
202
|
-
if (multiple) {
|
|
203
|
-
const newValues = selectedValues.includes(optionValue)
|
|
204
|
-
? selectedValues.filter((v) => v !== optionValue)
|
|
205
|
-
: [...selectedValues, optionValue];
|
|
206
|
-
onChange(newValues);
|
|
207
|
-
} else {
|
|
208
|
-
onChange(optionValue);
|
|
209
|
-
if (autoClose) {
|
|
210
|
-
closeModal();
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Handle clear selection
|
|
217
|
-
*/
|
|
218
|
-
const handleClear = () => {
|
|
219
|
-
onChange(multiple ? [] : '');
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Handle search query change
|
|
224
|
-
*/
|
|
225
|
-
const handleSearch = (query: string) => {
|
|
226
|
-
setSearchQuery(query);
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Check if option is selected
|
|
231
|
-
*/
|
|
232
|
-
const isSelected = (optionValue: string): boolean => {
|
|
233
|
-
return selectedValues.includes(optionValue);
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Render single option
|
|
238
|
-
*/
|
|
239
|
-
const renderOption = ({ item }: { item: PickerOption }) => {
|
|
240
|
-
const selected = isSelected(item.value);
|
|
241
|
-
const itemDisabled = item.disabled || false;
|
|
242
|
-
|
|
243
|
-
const optionContainerStyle = getOptionContainerStyles(
|
|
244
|
-
tokens,
|
|
245
|
-
selected,
|
|
246
|
-
itemDisabled
|
|
247
|
-
);
|
|
248
|
-
const optionTextStyle = getOptionTextStyles(tokens, selected);
|
|
249
|
-
const optionDescriptionStyle = getOptionDescriptionStyles(tokens);
|
|
250
|
-
|
|
251
|
-
return (
|
|
252
|
-
<TouchableOpacity
|
|
253
|
-
onPress={() => !itemDisabled && handleSelect(item.value)}
|
|
254
|
-
disabled={itemDisabled}
|
|
255
|
-
testID={item.testID || `${testID}-option-${item.value}`}
|
|
256
|
-
style={optionContainerStyle}
|
|
257
|
-
>
|
|
258
|
-
{/* Option Icon */}
|
|
259
|
-
{item.icon && (
|
|
260
|
-
<AtomicIcon
|
|
261
|
-
name={item.icon}
|
|
262
|
-
size="md"
|
|
263
|
-
color={selected ? 'primary' : 'secondary'}
|
|
264
|
-
/>
|
|
265
|
-
)}
|
|
266
|
-
|
|
267
|
-
{/* Option Content */}
|
|
268
|
-
<View style={{ flex: 1 }}>
|
|
269
|
-
<AtomicText style={optionTextStyle}>{item.label}</AtomicText>
|
|
270
|
-
{item.description && (
|
|
271
|
-
<AtomicText style={optionDescriptionStyle}>
|
|
272
|
-
{item.description}
|
|
273
|
-
</AtomicText>
|
|
274
|
-
)}
|
|
275
|
-
</View>
|
|
276
|
-
|
|
277
|
-
{/* Selected Indicator */}
|
|
278
|
-
{selected && (
|
|
279
|
-
<AtomicIcon name="CircleCheck" size="md" color="primary" />
|
|
280
|
-
)}
|
|
281
|
-
</TouchableOpacity>
|
|
282
|
-
);
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Render selected chips for multi-select
|
|
287
|
-
*/
|
|
288
|
-
const renderSelectedChips = () => {
|
|
289
|
-
if (!multiple || selectedOptions.length === 0) return null;
|
|
290
|
-
|
|
291
|
-
return (
|
|
292
|
-
<View style={chipContainerStyles}>
|
|
293
|
-
{selectedOptions.map((opt) => (
|
|
294
|
-
<View key={opt.value} style={chipStyles}>
|
|
295
|
-
<AtomicText style={chipTextStyles}>{opt.label}</AtomicText>
|
|
296
|
-
<TouchableOpacity
|
|
297
|
-
onPress={(e) => {
|
|
298
|
-
e.stopPropagation();
|
|
299
|
-
handleSelect(opt.value);
|
|
300
|
-
}}
|
|
301
|
-
hitSlop={{ top: 4, bottom: 4, left: 4, right: 4 }}
|
|
302
|
-
>
|
|
303
|
-
<AtomicIcon name="X" size="sm" color="primary" />
|
|
304
|
-
</TouchableOpacity>
|
|
305
|
-
</View>
|
|
306
|
-
))}
|
|
307
|
-
</View>
|
|
308
|
-
);
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
const pickerContainerStyle = StyleSheet.flatten([
|
|
312
|
-
containerStyles.base,
|
|
313
|
-
containerStyles.size[size],
|
|
314
|
-
error ? containerStyles.state.error : undefined,
|
|
315
|
-
disabled ? containerStyles.state.disabled : undefined,
|
|
316
|
-
style,
|
|
317
|
-
]);
|
|
318
|
-
|
|
319
|
-
const pickerLabelStyle = StyleSheet.flatten([
|
|
320
|
-
labelStyles.base,
|
|
321
|
-
labelStyles.size[size],
|
|
322
|
-
labelStyle,
|
|
323
|
-
]);
|
|
324
|
-
|
|
325
|
-
const pickerValueStyle = StyleSheet.flatten([
|
|
326
|
-
selectedOptions.length > 0 ? valueStyles.base : placeholderStyles.base,
|
|
327
|
-
selectedOptions.length > 0
|
|
328
|
-
? valueStyles.size[size]
|
|
329
|
-
: placeholderStyles.size[size],
|
|
330
|
-
]);
|
|
331
|
-
|
|
332
|
-
return (
|
|
333
|
-
<View>
|
|
334
|
-
{/* Label */}
|
|
335
|
-
{label && <AtomicText style={pickerLabelStyle}>{label}</AtomicText>}
|
|
336
|
-
|
|
337
|
-
{/* Picker Button */}
|
|
338
|
-
<TouchableOpacity
|
|
339
|
-
onPress={openModal}
|
|
340
|
-
disabled={disabled}
|
|
341
|
-
accessibilityRole="button"
|
|
342
|
-
accessibilityLabel={label || placeholder}
|
|
343
|
-
accessibilityState={{ disabled }}
|
|
344
|
-
testID={testID}
|
|
345
|
-
style={pickerContainerStyle}
|
|
346
|
-
>
|
|
347
|
-
{/* Display Text */}
|
|
348
|
-
<AtomicText style={pickerValueStyle} numberOfLines={1}>
|
|
349
|
-
{displayText}
|
|
350
|
-
</AtomicText>
|
|
351
|
-
|
|
352
|
-
{/* Icons */}
|
|
353
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap: tokens.spacing.xs }}>
|
|
354
|
-
{/* Clear Button */}
|
|
355
|
-
{clearable && selectedOptions.length > 0 && !disabled && (
|
|
356
|
-
<TouchableOpacity
|
|
357
|
-
onPress={handleClear}
|
|
358
|
-
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
359
|
-
accessibilityRole="button"
|
|
360
|
-
accessibilityLabel="Clear selection"
|
|
361
|
-
testID={`${testID}-clear`}
|
|
362
|
-
>
|
|
363
|
-
<AtomicIcon name="X" size="sm" color="secondary" />
|
|
364
|
-
</TouchableOpacity>
|
|
365
|
-
)}
|
|
366
|
-
|
|
367
|
-
{/* Dropdown Icon */}
|
|
368
|
-
<AtomicIcon
|
|
369
|
-
name={modalVisible ? 'ChevronUp' : 'ChevronDown'}
|
|
370
|
-
size="sm"
|
|
371
|
-
color={disabled ? 'surfaceVariant' : 'secondary'}
|
|
372
|
-
/>
|
|
373
|
-
</View>
|
|
374
|
-
</TouchableOpacity>
|
|
375
|
-
|
|
376
|
-
{/* Selected Chips (Multi-select) */}
|
|
377
|
-
{renderSelectedChips()}
|
|
378
|
-
|
|
379
|
-
{/* Error Message */}
|
|
380
|
-
{error && <AtomicText style={errorStyles}>{error}</AtomicText>}
|
|
381
|
-
|
|
382
|
-
{/* Selection Modal */}
|
|
383
|
-
<Modal
|
|
384
|
-
visible={modalVisible}
|
|
385
|
-
animationType="slide"
|
|
386
|
-
transparent
|
|
387
|
-
onRequestClose={closeModal}
|
|
388
|
-
testID={`${testID}-modal`}
|
|
389
|
-
>
|
|
390
|
-
<View style={modalOverlayStyles}>
|
|
391
|
-
<View
|
|
392
|
-
style={[
|
|
393
|
-
modalContainerStyles,
|
|
394
|
-
{ paddingBottom: insets.bottom + tokens.spacing.md },
|
|
395
|
-
]}
|
|
396
|
-
>
|
|
397
|
-
{/* Modal Header */}
|
|
398
|
-
<View style={modalHeaderStyles}>
|
|
399
|
-
{/* Title */}
|
|
400
|
-
<AtomicText style={modalTitleStyles}>
|
|
401
|
-
{modalTitle || label || 'Select'}
|
|
402
|
-
</AtomicText>
|
|
403
|
-
|
|
404
|
-
{/* Close Button */}
|
|
405
|
-
<TouchableOpacity
|
|
406
|
-
onPress={closeModal}
|
|
407
|
-
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
408
|
-
accessibilityRole="button"
|
|
409
|
-
accessibilityLabel="Close picker"
|
|
410
|
-
testID={`${testID}-close`}
|
|
411
|
-
>
|
|
412
|
-
<AtomicIcon name="X" size="md" color="primary" />
|
|
413
|
-
</TouchableOpacity>
|
|
414
|
-
</View>
|
|
415
|
-
|
|
416
|
-
{/* Search Bar */}
|
|
417
|
-
{searchable && (
|
|
418
|
-
<View style={searchContainerStyles}>
|
|
419
|
-
<AtomicIcon name="Search" size="sm" color="secondary" />
|
|
420
|
-
<TextInput
|
|
421
|
-
value={searchQuery}
|
|
422
|
-
onChangeText={handleSearch}
|
|
423
|
-
placeholder="Search..."
|
|
424
|
-
placeholderTextColor={tokens.colors.textSecondary}
|
|
425
|
-
style={searchInputStyles}
|
|
426
|
-
testID={`${testID}-search`}
|
|
427
|
-
/>
|
|
428
|
-
{searchQuery.length > 0 && (
|
|
429
|
-
<TouchableOpacity onPress={() => handleSearch('')}>
|
|
430
|
-
<AtomicIcon name="X" size="sm" color="secondary" />
|
|
431
|
-
</TouchableOpacity>
|
|
432
|
-
)}
|
|
433
|
-
</View>
|
|
434
|
-
)}
|
|
435
|
-
|
|
436
|
-
{/* Options List */}
|
|
437
|
-
{filteredOptions.length > 0 ? (
|
|
438
|
-
<FlatList
|
|
439
|
-
data={filteredOptions}
|
|
440
|
-
keyExtractor={(item) => item.value}
|
|
441
|
-
renderItem={renderOption}
|
|
442
|
-
showsVerticalScrollIndicator
|
|
443
|
-
testID={`${testID}-list`}
|
|
444
|
-
/>
|
|
445
|
-
) : (
|
|
446
|
-
<View style={emptyStateStyles}>
|
|
447
|
-
<AtomicIcon name="Info" size="xl" color="secondary" />
|
|
448
|
-
<AtomicText style={emptyStateTextStyles}>
|
|
449
|
-
{emptyMessage}
|
|
450
|
-
</AtomicText>
|
|
451
|
-
</View>
|
|
452
|
-
)}
|
|
453
|
-
</View>
|
|
454
|
-
</View>
|
|
455
|
-
</Modal>
|
|
456
|
-
</View>
|
|
457
|
-
);
|
|
458
|
-
};
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AtomicProgress - Universal Progress Bar Component
|
|
3
|
-
*
|
|
4
|
-
* Displays progress bars for completion tracking
|
|
5
|
-
* Theme: {{THEME_NAME}} ({{CATEGORY}} category)
|
|
6
|
-
*
|
|
7
|
-
* Atomic Design Level: ATOM
|
|
8
|
-
* Purpose: Progress indication and completion tracking
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* - File upload progress
|
|
12
|
-
* - Task completion progress
|
|
13
|
-
* - Achievement progress
|
|
14
|
-
* - Form completion
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import React from 'react';
|
|
18
|
-
import { View, StyleSheet, ViewStyle, DimensionValue, Text } from 'react-native';
|
|
19
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
20
|
-
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// TYPE DEFINITIONS
|
|
23
|
-
// =============================================================================
|
|
24
|
-
|
|
25
|
-
export interface AtomicProgressProps {
|
|
26
|
-
/** Progress value (0-100) */
|
|
27
|
-
value: number;
|
|
28
|
-
/** Progress bar height */
|
|
29
|
-
height?: number;
|
|
30
|
-
/** Progress bar width */
|
|
31
|
-
width?: number | string;
|
|
32
|
-
/** Progress bar color */
|
|
33
|
-
color?: string;
|
|
34
|
-
/** Background color */
|
|
35
|
-
backgroundColor?: string;
|
|
36
|
-
/** Progress bar shape */
|
|
37
|
-
shape?: 'rounded' | 'square';
|
|
38
|
-
/** Whether to show percentage text */
|
|
39
|
-
showPercentage?: boolean;
|
|
40
|
-
/** Whether to show value text */
|
|
41
|
-
showValue?: boolean;
|
|
42
|
-
/** Custom text color */
|
|
43
|
-
textColor?: string;
|
|
44
|
-
/** Style overrides */
|
|
45
|
-
style?: ViewStyle | ViewStyle[];
|
|
46
|
-
/** Test ID for testing */
|
|
47
|
-
testID?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// =============================================================================
|
|
51
|
-
// COMPONENT IMPLEMENTATION
|
|
52
|
-
// =============================================================================
|
|
53
|
-
|
|
54
|
-
export const AtomicProgress: React.FC<AtomicProgressProps> = ({
|
|
55
|
-
value,
|
|
56
|
-
height = 8,
|
|
57
|
-
width = '100%',
|
|
58
|
-
color,
|
|
59
|
-
backgroundColor,
|
|
60
|
-
shape = 'rounded',
|
|
61
|
-
showPercentage = false,
|
|
62
|
-
showValue = false,
|
|
63
|
-
textColor,
|
|
64
|
-
style,
|
|
65
|
-
testID,
|
|
66
|
-
}) => {
|
|
67
|
-
const tokens = useAppDesignTokens();
|
|
68
|
-
|
|
69
|
-
// Clamp value between 0 and 100
|
|
70
|
-
const clampedValue = Math.max(0, Math.min(100, value));
|
|
71
|
-
|
|
72
|
-
// Default colors
|
|
73
|
-
const progressColor = color || tokens.colors.primary;
|
|
74
|
-
const progressBackground = backgroundColor || tokens.colors.surfaceVariant;
|
|
75
|
-
const progressTextColor = textColor || tokens.colors.textPrimary;
|
|
76
|
-
|
|
77
|
-
// Calculate progress width
|
|
78
|
-
const progressWidth = `${clampedValue}%`;
|
|
79
|
-
|
|
80
|
-
// Border radius based on shape
|
|
81
|
-
const borderRadius = shape === 'rounded' ? height / 2 : 0;
|
|
82
|
-
|
|
83
|
-
const containerStyle: ViewStyle = {
|
|
84
|
-
width: width as DimensionValue,
|
|
85
|
-
height,
|
|
86
|
-
backgroundColor: progressBackground,
|
|
87
|
-
borderRadius,
|
|
88
|
-
overflow: 'hidden',
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const progressStyle: ViewStyle = {
|
|
92
|
-
width: progressWidth as DimensionValue,
|
|
93
|
-
height: '100%' as DimensionValue,
|
|
94
|
-
backgroundColor: progressColor,
|
|
95
|
-
borderRadius,
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const textStyle = {
|
|
99
|
-
fontSize: tokens.typography.bodySmall.fontSize,
|
|
100
|
-
fontWeight: tokens.typography.labelMedium.fontWeight,
|
|
101
|
-
color: progressTextColor,
|
|
102
|
-
textAlign: 'center' as const,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<View style={[containerStyle, style]} testID={testID}>
|
|
107
|
-
<View style={progressStyle} />
|
|
108
|
-
{(showPercentage || showValue) && (
|
|
109
|
-
<View style={styles.textContainer}>
|
|
110
|
-
<Text style={textStyle}>
|
|
111
|
-
{showPercentage ? `${Math.round(clampedValue)}%` : `${Math.round(clampedValue)}`}
|
|
112
|
-
</Text>
|
|
113
|
-
</View>
|
|
114
|
-
)}
|
|
115
|
-
</View>
|
|
116
|
-
);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// =============================================================================
|
|
120
|
-
// STYLES
|
|
121
|
-
// =============================================================================
|
|
122
|
-
|
|
123
|
-
const styles = StyleSheet.create({
|
|
124
|
-
textContainer: {
|
|
125
|
-
position: 'absolute',
|
|
126
|
-
top: 0,
|
|
127
|
-
left: 0,
|
|
128
|
-
right: 0,
|
|
129
|
-
bottom: 0,
|
|
130
|
-
justifyContent: 'center',
|
|
131
|
-
alignItems: 'center',
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// =============================================================================
|
|
136
|
-
// EXPORTS
|
|
137
|
-
// =============================================================================
|
|
138
|
-
|
|
139
|
-
export default AtomicProgress;
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, TextInput, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
|
-
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
4
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
5
|
-
import { AtomicIcon } from './AtomicIcon';
|
|
6
|
-
|
|
7
|
-
export interface AtomicSearchBarProps {
|
|
8
|
-
value: string;
|
|
9
|
-
onChangeText: (text: string) => void;
|
|
10
|
-
placeholder?: string;
|
|
11
|
-
autoFocus?: boolean;
|
|
12
|
-
editable?: boolean;
|
|
13
|
-
onClear?: () => void;
|
|
14
|
-
onFocus?: () => void;
|
|
15
|
-
onBlur?: () => void;
|
|
16
|
-
onSubmitEditing?: () => void;
|
|
17
|
-
style?: StyleProp<ViewStyle>;
|
|
18
|
-
inputStyle?: StyleProp<TextStyle>;
|
|
19
|
-
accessibilityLabel?: string;
|
|
20
|
-
accessibilityHint?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const AtomicSearchBar: React.FC<AtomicSearchBarProps> = ({
|
|
24
|
-
value,
|
|
25
|
-
onChangeText,
|
|
26
|
-
placeholder = 'Search...',
|
|
27
|
-
autoFocus = false,
|
|
28
|
-
editable = true,
|
|
29
|
-
onClear,
|
|
30
|
-
onFocus,
|
|
31
|
-
onBlur,
|
|
32
|
-
onSubmitEditing,
|
|
33
|
-
style,
|
|
34
|
-
inputStyle,
|
|
35
|
-
accessibilityLabel = 'Search input',
|
|
36
|
-
accessibilityHint,
|
|
37
|
-
}) => {
|
|
38
|
-
const tokens = useAppDesignTokens();
|
|
39
|
-
|
|
40
|
-
const handleClear = () => {
|
|
41
|
-
onChangeText('');
|
|
42
|
-
onClear?.();
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<View
|
|
47
|
-
style={[
|
|
48
|
-
{
|
|
49
|
-
flexDirection: 'row',
|
|
50
|
-
alignItems: 'center',
|
|
51
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
52
|
-
borderRadius: tokens.borders.radius.lg,
|
|
53
|
-
borderWidth: 1,
|
|
54
|
-
borderColor: tokens.colors.border,
|
|
55
|
-
paddingHorizontal: tokens.spacing.md,
|
|
56
|
-
paddingVertical: tokens.spacing.sm,
|
|
57
|
-
gap: tokens.spacing.sm,
|
|
58
|
-
minHeight: 48,
|
|
59
|
-
},
|
|
60
|
-
style,
|
|
61
|
-
]}
|
|
62
|
-
accessibilityRole="search"
|
|
63
|
-
>
|
|
64
|
-
<AtomicIcon
|
|
65
|
-
name="Search"
|
|
66
|
-
size="sm"
|
|
67
|
-
color="secondary"
|
|
68
|
-
/>
|
|
69
|
-
|
|
70
|
-
<TextInput
|
|
71
|
-
value={value}
|
|
72
|
-
onChangeText={onChangeText}
|
|
73
|
-
placeholder={placeholder}
|
|
74
|
-
placeholderTextColor={tokens.colors.textSecondary}
|
|
75
|
-
autoFocus={autoFocus}
|
|
76
|
-
editable={editable}
|
|
77
|
-
onFocus={onFocus}
|
|
78
|
-
onBlur={onBlur}
|
|
79
|
-
onSubmitEditing={onSubmitEditing}
|
|
80
|
-
style={[
|
|
81
|
-
{
|
|
82
|
-
flex: 1,
|
|
83
|
-
...tokens.typography.bodyMedium,
|
|
84
|
-
color: tokens.colors.textPrimary,
|
|
85
|
-
padding: 0,
|
|
86
|
-
margin: 0,
|
|
87
|
-
},
|
|
88
|
-
inputStyle,
|
|
89
|
-
]}
|
|
90
|
-
accessibilityLabel={accessibilityLabel}
|
|
91
|
-
accessibilityHint={accessibilityHint}
|
|
92
|
-
returnKeyType="search"
|
|
93
|
-
underlineColorAndroid="transparent"
|
|
94
|
-
/>
|
|
95
|
-
|
|
96
|
-
{value.length > 0 && (
|
|
97
|
-
<TouchableOpacity
|
|
98
|
-
onPress={handleClear}
|
|
99
|
-
style={{
|
|
100
|
-
padding: tokens.spacing.xs,
|
|
101
|
-
}}
|
|
102
|
-
accessibilityLabel="Clear search"
|
|
103
|
-
accessibilityRole="button"
|
|
104
|
-
>
|
|
105
|
-
<AtomicIcon
|
|
106
|
-
name="X"
|
|
107
|
-
size="sm"
|
|
108
|
-
color="secondary"
|
|
109
|
-
/>
|
|
110
|
-
</TouchableOpacity>
|
|
111
|
-
)}
|
|
112
|
-
</View>
|
|
113
|
-
);
|
|
114
|
-
};
|