@umituz/react-native-design-system 2.1.0 → 2.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -105,4 +105,4 @@
105
105
  "README.md",
106
106
  "LICENSE"
107
107
  ]
108
- }
108
+ }
@@ -0,0 +1,142 @@
1
+ import React, { forwardRef } from 'react';
2
+ import {
3
+ View,
4
+ TextInput,
5
+ TouchableOpacity,
6
+ ActivityIndicator,
7
+ StyleSheet,
8
+ } from 'react-native';
9
+ import { useAppDesignTokens } from '../../theme';
10
+ import { AtomicIcon } from '../../atoms/AtomicIcon';
11
+ import type { SearchBarProps } from './types';
12
+
13
+ export const SearchBar = forwardRef<TextInput, SearchBarProps>(({
14
+ value,
15
+ onChangeText,
16
+ onSubmit,
17
+ onClear,
18
+ onFocus,
19
+ onBlur,
20
+ placeholder = 'Search...',
21
+ autoFocus = false,
22
+ loading = false,
23
+ disabled = false,
24
+ containerStyle,
25
+ inputStyle,
26
+ testID,
27
+ }, ref) => {
28
+ const tokens = useAppDesignTokens();
29
+
30
+ const handleClear = () => {
31
+ onChangeText('');
32
+ onClear?.();
33
+ };
34
+
35
+ const showClear = value.length > 0 && !loading;
36
+
37
+ return (
38
+ <View
39
+ style={[
40
+ styles.container,
41
+ {
42
+ backgroundColor: tokens.colors.surfaceVariant,
43
+ borderColor: tokens.colors.border,
44
+ },
45
+ containerStyle,
46
+ ]}
47
+ testID={testID}
48
+ >
49
+ <View style={styles.iconContainer}>
50
+ <AtomicIcon
51
+ name="search"
52
+ size="md"
53
+ customColor={tokens.colors.textSecondary}
54
+ />
55
+ </View>
56
+
57
+ <TextInput
58
+ ref={ref}
59
+ value={value}
60
+ onChangeText={onChangeText}
61
+ onSubmitEditing={onSubmit}
62
+ onFocus={onFocus}
63
+ onBlur={onBlur}
64
+ placeholder={placeholder}
65
+ placeholderTextColor={tokens.colors.textSecondary}
66
+ autoFocus={autoFocus}
67
+ editable={!disabled}
68
+ returnKeyType="search"
69
+ autoCapitalize="none"
70
+ autoCorrect={false}
71
+ style={[
72
+ styles.input,
73
+ {
74
+ color: tokens.colors.textPrimary,
75
+ fontSize: 16, // Body medium usually
76
+ },
77
+ inputStyle,
78
+ ]}
79
+ />
80
+
81
+ {(loading || showClear) && (
82
+ <View style={styles.rightActions}>
83
+ {loading && (
84
+ <ActivityIndicator
85
+ size="small"
86
+ color={tokens.colors.primary}
87
+ style={styles.loader}
88
+ />
89
+ )}
90
+
91
+ {showClear && (
92
+ <TouchableOpacity
93
+ onPress={handleClear}
94
+ style={styles.clearButton}
95
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
96
+ accessibilityRole="button"
97
+ accessibilityLabel="Clear search"
98
+ >
99
+ <AtomicIcon
100
+ name="close-circle"
101
+ size="md"
102
+ customColor={tokens.colors.textSecondary}
103
+ />
104
+ </TouchableOpacity>
105
+ )}
106
+ </View>
107
+ )}
108
+ </View>
109
+ );
110
+ });
111
+
112
+ SearchBar.displayName = 'SearchBar';
113
+
114
+ const styles = StyleSheet.create({
115
+ container: {
116
+ flexDirection: 'row',
117
+ alignItems: 'center',
118
+ paddingHorizontal: 12,
119
+ height: 48,
120
+ borderRadius: 24, // Pill shape
121
+ borderWidth: 1,
122
+ },
123
+ iconContainer: {
124
+ marginRight: 8,
125
+ },
126
+ input: {
127
+ flex: 1,
128
+ height: '100%',
129
+ paddingVertical: 0, // Reset default padding
130
+ },
131
+ rightActions: {
132
+ flexDirection: 'row',
133
+ alignItems: 'center',
134
+ marginLeft: 8,
135
+ },
136
+ loader: {
137
+ marginRight: 8,
138
+ },
139
+ clearButton: {
140
+ padding: 2,
141
+ },
142
+ });
@@ -0,0 +1,131 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ TouchableOpacity,
5
+ StyleSheet,
6
+ } from 'react-native';
7
+ import { AtomicText } from '../../atoms/AtomicText';
8
+ import { AtomicIcon } from '../../atoms/AtomicIcon';
9
+ import { useAppDesignTokens } from '../../theme';
10
+ import type { SearchHistoryProps } from './types';
11
+
12
+ export const SearchHistory: React.FC<SearchHistoryProps> = ({
13
+ history,
14
+ onSelectItem,
15
+ onRemoveItem,
16
+ onClearAll,
17
+ maxItems = 10,
18
+ style,
19
+ title = 'Recent Searches',
20
+ clearLabel = 'Clear All',
21
+ }) => {
22
+ const tokens = useAppDesignTokens();
23
+
24
+ if (!history || history.length === 0) {
25
+ return null;
26
+ }
27
+
28
+ const displayedHistory = history.slice(0, maxItems);
29
+
30
+ return (
31
+ <View style={[styles.container, style]}>
32
+ <View style={styles.header}>
33
+ <AtomicText
34
+ type="labelLarge"
35
+ style={{ color: tokens.colors.textSecondary }}
36
+ >
37
+ {title}
38
+ </AtomicText>
39
+ <TouchableOpacity
40
+ onPress={onClearAll}
41
+ style={styles.clearButton}
42
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
43
+ >
44
+ <AtomicText
45
+ type="labelMedium"
46
+ style={{ color: tokens.colors.primary }}
47
+ >
48
+ {clearLabel}
49
+ </AtomicText>
50
+ </TouchableOpacity>
51
+ </View>
52
+
53
+ {displayedHistory.map((item) => (
54
+ <TouchableOpacity
55
+ key={item.id}
56
+ onPress={() => onSelectItem(item.query)}
57
+ style={styles.item}
58
+ >
59
+ <View style={styles.itemLeft}>
60
+ <AtomicIcon
61
+ name="time-outline"
62
+ size="sm"
63
+ customColor={tokens.colors.textSecondary}
64
+ />
65
+ <AtomicText
66
+ type="bodyMedium"
67
+ style={[
68
+ styles.itemText,
69
+ { color: tokens.colors.textPrimary }
70
+ ]}
71
+ numberOfLines={1}
72
+ >
73
+ {item.query}
74
+ </AtomicText>
75
+ </View>
76
+
77
+ <TouchableOpacity
78
+ onPress={() => onRemoveItem(item.id)}
79
+ style={styles.removeButton}
80
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
81
+ >
82
+ <AtomicIcon
83
+ name="close"
84
+ size="sm"
85
+ customColor={tokens.colors.textSecondary}
86
+ />
87
+ </TouchableOpacity>
88
+ </TouchableOpacity>
89
+ ))}
90
+ </View>
91
+ );
92
+ };
93
+
94
+ const styles = StyleSheet.create({
95
+ container: {
96
+ paddingVertical: 8,
97
+ },
98
+ header: {
99
+ flexDirection: 'row',
100
+ justifyContent: 'space-between',
101
+ alignItems: 'center',
102
+ paddingHorizontal: 16,
103
+ paddingVertical: 8,
104
+ marginBottom: 4,
105
+ },
106
+ clearButton: {
107
+ paddingVertical: 4,
108
+ paddingHorizontal: 8,
109
+ },
110
+ item: {
111
+ flexDirection: 'row',
112
+ alignItems: 'center',
113
+ justifyContent: 'space-between',
114
+ paddingHorizontal: 16,
115
+ paddingVertical: 12,
116
+ minHeight: 48,
117
+ },
118
+ itemLeft: {
119
+ flexDirection: 'row',
120
+ alignItems: 'center',
121
+ flex: 1,
122
+ marginRight: 12,
123
+ },
124
+ itemText: {
125
+ marginLeft: 12,
126
+ flex: 1,
127
+ },
128
+ removeButton: {
129
+ padding: 4,
130
+ },
131
+ });
@@ -0,0 +1,113 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ TouchableOpacity,
5
+ StyleSheet,
6
+ } from 'react-native';
7
+ import { AtomicText } from '../../atoms/AtomicText';
8
+ import { useAppDesignTokens } from '../../theme';
9
+ import type { SearchSuggestionsProps } from './types';
10
+
11
+ export function SearchSuggestions<T>({
12
+ query,
13
+ suggestions,
14
+ renderItem,
15
+ onSelectSuggestion,
16
+ maxSuggestions = 5,
17
+ style,
18
+ emptyComponent,
19
+ }: SearchSuggestionsProps<T>) {
20
+ const tokens = useAppDesignTokens();
21
+
22
+ if (!query.trim() && !emptyComponent) {
23
+ return null;
24
+ }
25
+
26
+ if (suggestions.length === 0) {
27
+ return <>{emptyComponent}</>;
28
+ }
29
+
30
+ const displayedSuggestions = suggestions.slice(0, maxSuggestions);
31
+
32
+ return (
33
+ <View
34
+ style={[
35
+ styles.container,
36
+ {
37
+ backgroundColor: tokens.colors.surface,
38
+ },
39
+ style,
40
+ ]}
41
+ >
42
+ {displayedSuggestions.map((item, index) => (
43
+ <TouchableOpacity
44
+ key={index}
45
+ onPress={() => onSelectSuggestion(item)}
46
+ style={[
47
+ styles.item,
48
+ index > 0 ? {
49
+ borderTopWidth: 1,
50
+ borderTopColor: tokens.colors.border,
51
+ } : undefined,
52
+ ]}
53
+ >
54
+ {renderItem(item, query)}
55
+ </TouchableOpacity>
56
+ ))}
57
+ </View>
58
+ );
59
+ }
60
+
61
+ const styles = StyleSheet.create({
62
+ container: {
63
+ borderRadius: 12,
64
+ overflow: 'hidden',
65
+ },
66
+ item: {
67
+ paddingHorizontal: 16,
68
+ paddingVertical: 12,
69
+ minHeight: 48,
70
+ justifyContent: 'center',
71
+ },
72
+ });
73
+
74
+ /**
75
+ * Default suggestion renderer (simple text with highlighting)
76
+ */
77
+ export const DefaultSuggestionRenderer = (
78
+ text: string,
79
+ query: string,
80
+ tokens: ReturnType<typeof useAppDesignTokens>
81
+ ) => {
82
+ const lowerText = text.toLowerCase();
83
+ const lowerQuery = query.toLowerCase();
84
+ const index = lowerText.indexOf(lowerQuery);
85
+
86
+ if (index === -1) {
87
+ return (
88
+ <AtomicText
89
+ type="bodyMedium"
90
+ style={{ color: tokens.colors.textPrimary }}
91
+ >
92
+ {text}
93
+ </AtomicText>
94
+ );
95
+ }
96
+
97
+ const before = text.slice(0, index);
98
+ const match = text.slice(index, index + query.length);
99
+ const after = text.slice(index + query.length);
100
+
101
+ return (
102
+ <AtomicText
103
+ type="bodyMedium"
104
+ style={{ color: tokens.colors.textPrimary }}
105
+ >
106
+ {before}
107
+ <AtomicText type="bodyMedium" style={{ fontWeight: '700', color: tokens.colors.primary }}>
108
+ {match}
109
+ </AtomicText>
110
+ {after}
111
+ </AtomicText>
112
+ );
113
+ };
@@ -0,0 +1,4 @@
1
+ export * from './SearchBar';
2
+ export * from './SearchHistory';
3
+ export * from './SearchSuggestions';
4
+ export * from './types';
@@ -0,0 +1,46 @@
1
+ import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
2
+
3
+ export interface SearchHistoryItem {
4
+ id: string;
5
+ query: string;
6
+ timestamp: number;
7
+ }
8
+
9
+ export interface SearchBarProps {
10
+ value: string;
11
+ onChangeText: (text: string) => void;
12
+ onSubmit?: () => void;
13
+ onClear?: () => void;
14
+ onFocus?: () => void;
15
+ onBlur?: () => void;
16
+ placeholder?: string;
17
+ autoFocus?: boolean;
18
+ loading?: boolean;
19
+ disabled?: boolean;
20
+ showCancelButton?: boolean;
21
+ onCancel?: () => void;
22
+ containerStyle?: StyleProp<ViewStyle>;
23
+ inputStyle?: StyleProp<TextStyle>;
24
+ testID?: string;
25
+ }
26
+
27
+ export interface SearchHistoryProps {
28
+ history: SearchHistoryItem[];
29
+ onSelectItem: (query: string) => void;
30
+ onRemoveItem: (id: string) => void;
31
+ onClearAll: () => void;
32
+ maxItems?: number;
33
+ style?: StyleProp<ViewStyle>;
34
+ title?: string;
35
+ clearLabel?: string;
36
+ }
37
+
38
+ export interface SearchSuggestionsProps<T> {
39
+ query: string;
40
+ suggestions: T[];
41
+ renderItem: (item: T, query: string) => React.ReactNode;
42
+ onSelectSuggestion: (item: T) => void;
43
+ maxSuggestions?: number;
44
+ style?: StyleProp<ViewStyle>;
45
+ emptyComponent?: React.ReactNode;
46
+ }
@@ -1,198 +0,0 @@
1
- /**
2
- * SearchBar Molecule - Enhanced Search Input with Clear Button
3
- *
4
- * Universal search input with clear functionality and loading state
5
- * Fully configurable for general purpose use
6
- */
7
-
8
- import React from 'react';
9
- import {
10
- View,
11
- TextInput,
12
- TouchableOpacity,
13
- ActivityIndicator,
14
- StyleSheet,
15
- type StyleProp,
16
- type ViewStyle,
17
- } from 'react-native';
18
- import { useAppDesignTokens } from '../theme';
19
- import { AtomicIcon } from '../atoms';
20
-
21
- export interface SearchBarProps {
22
- value: string;
23
- onChangeText: (text: string) => void;
24
- onSubmit?: () => void;
25
- onClear?: () => void;
26
- placeholder?: string;
27
- autoFocus?: boolean;
28
- loading?: boolean;
29
- disabled?: boolean;
30
- style?: StyleProp<ViewStyle>;
31
- containerStyle?: ViewStyle;
32
- searchIconName?: string;
33
- clearIconName?: string;
34
- }
35
-
36
- const useSearchBarLogic = (
37
- value: string,
38
- loading: boolean,
39
- onChangeText: (text: string) => void,
40
- onClear?: () => void,
41
- onSubmit?: () => void
42
- ) => {
43
- const handleClear = React.useCallback(() => {
44
- onChangeText('');
45
- onClear?.();
46
- }, [onChangeText, onClear]);
47
-
48
- const handleSubmit = React.useCallback(() => {
49
- onSubmit?.();
50
- }, [onSubmit]);
51
-
52
- const showClear = React.useMemo(() =>
53
- value.length > 0 && !loading, [value, loading]
54
- );
55
-
56
- return { handleClear, handleSubmit, showClear };
57
- };
58
-
59
- const SearchBarInput: React.FC<{
60
- value: string;
61
- placeholder?: string;
62
- autoFocus?: boolean;
63
- disabled?: boolean;
64
- style?: StyleProp<ViewStyle>;
65
- onChangeText: (text: string) => void;
66
- onSubmitEditing?: () => void;
67
- tokens: ReturnType<typeof useAppDesignTokens>;
68
- }> = ({ value, placeholder, autoFocus, disabled, style, onChangeText, onSubmitEditing, tokens }) => {
69
- const styles = React.useMemo(() => getSearchBarStyles(), []);
70
-
71
- const inputStyle = React.useMemo(() => [
72
- styles.input,
73
- {
74
- color: tokens.colors.textPrimary,
75
- ...tokens.typography.bodyMedium,
76
- },
77
- ], [styles.input, tokens.colors.textPrimary, tokens.typography.bodyMedium]);
78
-
79
- return (
80
- <TextInput
81
- value={value}
82
- onChangeText={onChangeText}
83
- onSubmitEditing={onSubmitEditing}
84
- placeholder={placeholder}
85
- placeholderTextColor={tokens.colors.textSecondary}
86
- autoFocus={autoFocus}
87
- editable={!disabled}
88
- returnKeyType="search"
89
- autoCapitalize="none"
90
- autoCorrect={false}
91
- style={[inputStyle, style]}
92
- />
93
- );
94
- };
95
-
96
- const SearchBarIcons: React.FC<{
97
- searchIconName: string;
98
- clearIconName: string;
99
- loading: boolean;
100
- showClear: boolean;
101
- onClear: () => void;
102
- tokens: ReturnType<typeof useAppDesignTokens>;
103
- }> = ({ searchIconName, clearIconName, loading, showClear, onClear, tokens }) => {
104
- const styles = React.useMemo(() => getSearchBarStyles(), []);
105
-
106
- return (
107
- <>
108
- <AtomicIcon
109
- name={searchIconName}
110
- customSize={20}
111
- customColor={tokens.colors.textSecondary}
112
- style={styles.searchIcon}
113
- />
114
-
115
- {loading && (
116
- <ActivityIndicator
117
- size="small"
118
- color={tokens.colors.primary}
119
- style={styles.icon}
120
- />
121
- )}
122
-
123
- {showClear && (
124
- <TouchableOpacity onPress={onClear} style={styles.icon}>
125
- <AtomicIcon name={clearIconName} customSize={20} customColor={tokens.colors.textSecondary} />
126
- </TouchableOpacity>
127
- )}
128
- </>
129
- );
130
- };
131
-
132
- const SearchBarComponent: React.FC<SearchBarProps> = ({
133
- value,
134
- onChangeText,
135
- onSubmit,
136
- onClear,
137
- placeholder,
138
- autoFocus = false,
139
- loading = false,
140
- disabled = false,
141
- style,
142
- containerStyle,
143
- searchIconName = 'search',
144
- clearIconName = 'close-circle',
145
- }) => {
146
- const tokens = useAppDesignTokens();
147
- const { handleClear, handleSubmit, showClear } = useSearchBarLogic(
148
- value, loading, onChangeText, onClear, onSubmit
149
- );
150
-
151
- return (
152
- <View style={containerStyle}>
153
- <SearchBarIcons
154
- searchIconName={searchIconName}
155
- clearIconName={clearIconName}
156
- loading={loading}
157
- showClear={showClear}
158
- onClear={handleClear}
159
- tokens={tokens}
160
- />
161
-
162
- <SearchBarInput
163
- value={value}
164
- placeholder={placeholder}
165
- autoFocus={autoFocus}
166
- disabled={disabled}
167
- style={style}
168
- onChangeText={onChangeText}
169
- onSubmitEditing={handleSubmit}
170
- tokens={tokens}
171
- />
172
- </View>
173
- );
174
- };
175
-
176
- export const SearchBar = SearchBarComponent;
177
-
178
- const getSearchBarStyles = () => StyleSheet.create({
179
- container: {
180
- flexDirection: 'row',
181
- alignItems: 'center',
182
- paddingHorizontal: 12,
183
- paddingVertical: 8,
184
- borderRadius: 12,
185
- minHeight: 44,
186
- },
187
- searchIcon: {
188
- marginRight: 12,
189
- },
190
- input: {
191
- flex: 1,
192
- paddingVertical: 0,
193
- },
194
- icon: {
195
- marginLeft: 8,
196
- },
197
- });
198
-