@umituz/react-native-design-system 2.0.16 → 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.0.16",
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",
@@ -53,7 +53,6 @@
53
53
  "@react-native-async-storage/async-storage": ">=2.0.0",
54
54
  "@react-native-community/datetimepicker": ">=8.0.0",
55
55
  "@react-navigation/native": ">=6.0.0",
56
- "@umituz/react-native-icons": "latest",
57
56
  "expo-linear-gradient": ">=15.0.0",
58
57
  "react": ">=19.0.0",
59
58
  "react-native": ">=0.81.0",
@@ -85,7 +84,6 @@
85
84
  "@types/react": "~19.1.10",
86
85
  "@typescript-eslint/eslint-plugin": "^8.50.1",
87
86
  "@typescript-eslint/parser": "^8.50.1",
88
- "@umituz/react-native-icons": "latest",
89
87
  "eslint": "^9.39.2",
90
88
  "eslint-plugin-react": "^7.37.5",
91
89
  "eslint-plugin-react-hooks": "^7.0.1",
@@ -1,18 +1,25 @@
1
1
  /**
2
2
  * AtomicIcon - Theme-aware Icon Component
3
3
  *
4
- * Uses @umituz/react-native-icons internally
4
+ * Uses @expo/vector-icons/Ionicons internally
5
5
  * Adds theme-aware semantic colors and background support
6
6
  */
7
7
 
8
8
  import React from "react";
9
9
  import { View, StyleSheet, StyleProp, ViewStyle } from "react-native";
10
- import { Icon, type IconSize as BaseIconSize, ICON_SIZES } from "@umituz/react-native-icons";
10
+ import { Ionicons } from "@expo/vector-icons";
11
11
  import { useAppDesignTokens } from '../theme';
12
+ import {
13
+ getIconSize,
14
+ ICON_SIZES,
15
+ type IconSize as BaseIconSize
16
+ } from "./AtomicIcon.types";
12
17
 
13
- // Re-export IconSize from icons package for convenience
18
+ // Re-export IconSize for convenience
14
19
  export type IconSize = BaseIconSize;
15
20
 
21
+ const FALLBACK_ICON = "help-circle-outline";
22
+
16
23
  // Semantic color names that map to theme tokens
17
24
  export type IconColor =
18
25
  | "primary"
@@ -96,7 +103,7 @@ export const AtomicIcon: React.FC<AtomicIconProps> = React.memo(({
96
103
  const tokens = useAppDesignTokens();
97
104
 
98
105
  // Calculate size
99
- const iconSize = customSize ?? (typeof size === "number" ? size : ICON_SIZES[size]);
106
+ const sizeInPixels = customSize ?? getIconSize(size);
100
107
 
101
108
  // Calculate color
102
109
  const iconColor = customColor
@@ -105,9 +112,29 @@ export const AtomicIcon: React.FC<AtomicIconProps> = React.memo(({
105
112
  ? getSemanticColor(color, tokens)
106
113
  : tokens.colors.textPrimary;
107
114
 
115
+ // Validate icon
116
+ const isValidIcon = name in Ionicons.glyphMap;
117
+ const iconName = isValidIcon ? name : FALLBACK_ICON;
118
+
119
+ if (__DEV__ && !isValidIcon) {
120
+ console.warn(
121
+ `[AtomicIcon] Invalid icon name: "${name}". Using fallback icon "${FALLBACK_ICON}". ` +
122
+ `Available icons: https://icons.expo.fyi/`
123
+ );
124
+ }
125
+
126
+ const iconElement = (
127
+ <Ionicons
128
+ name={iconName as keyof typeof Ionicons.glyphMap}
129
+ size={sizeInPixels}
130
+ color={iconColor}
131
+ testID={testID ? `${testID}-icon` : undefined}
132
+ />
133
+ );
134
+
108
135
  if (withBackground) {
109
136
  const bgColor = backgroundColor || tokens.colors.surfaceVariant;
110
- const containerSize = iconSize + 16;
137
+ const containerSize = sizeInPixels + 16;
111
138
 
112
139
  return (
113
140
  <View
@@ -124,14 +151,14 @@ export const AtomicIcon: React.FC<AtomicIconProps> = React.memo(({
124
151
  testID={testID}
125
152
  accessibilityLabel={accessibilityLabel}
126
153
  >
127
- <Icon name={name} size={iconSize} color={iconColor} />
154
+ {iconElement}
128
155
  </View>
129
156
  );
130
157
  }
131
158
 
132
159
  return (
133
160
  <View accessibilityLabel={accessibilityLabel} testID={testID} style={style}>
134
- <Icon name={name} size={iconSize} color={iconColor} />
161
+ {iconElement}
135
162
  </View>
136
163
  );
137
164
  });
@@ -147,3 +174,4 @@ const styles = StyleSheet.create({
147
174
 
148
175
  // Legacy type alias for backward compatibility
149
176
  export type IconProps = AtomicIconProps;
177
+
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Icon Type Definitions
3
+ * Centralized icon types for @expo/vector-icons Ionicons
4
+ */
5
+
6
+ import type { Ionicons } from "@expo/vector-icons";
7
+
8
+ /**
9
+ * All available Ionicons names (type-safe)
10
+ */
11
+ export type IoniconsName = keyof typeof Ionicons.glyphMap;
12
+
13
+ /**
14
+ * Semantic icon size presets
15
+ */
16
+ export type IconSizePreset = "xs" | "sm" | "md" | "lg" | "xl" | "xxl";
17
+
18
+ /**
19
+ * Icon size - preset name or custom number in pixels
20
+ */
21
+ export type IconSize = IconSizePreset | number;
22
+
23
+ /**
24
+ * Icon size mapping to pixels
25
+ */
26
+ export const ICON_SIZES: Record<IconSizePreset, number> = {
27
+ xs: 12,
28
+ sm: 16,
29
+ md: 20,
30
+ lg: 24,
31
+ xl: 32,
32
+ xxl: 48,
33
+ } as const;
34
+
35
+ /**
36
+ * Get icon size in pixels
37
+ */
38
+ export function getIconSize(size: IconSize): number {
39
+ if (typeof size === "number") return size;
40
+ return ICON_SIZES[size];
41
+ }
42
+
43
+ /**
44
+ * Check if size is a preset
45
+ */
46
+ export function isIconSizePreset(size: IconSize): size is IconSizePreset {
47
+ return typeof size === "string" && size in ICON_SIZES;
48
+ }
@@ -40,6 +40,8 @@ export {
40
40
  type IconName,
41
41
  } from './AtomicIcon';
42
42
 
43
+ export * from './AtomicIcon.types';
44
+
43
45
  // Avatar
44
46
  export { AtomicAvatar, type AtomicAvatarProps } from './AtomicAvatar';
45
47
 
@@ -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
-