@terreno/ui 0.6.0 → 0.7.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.
Files changed (52) hide show
  1. package/README.md +5 -5
  2. package/dist/AiSuggestionBox.d.ts +6 -0
  3. package/dist/AiSuggestionBox.js +87 -0
  4. package/dist/AiSuggestionBox.js.map +1 -0
  5. package/dist/Common.d.ts +13 -0
  6. package/dist/Common.js.map +1 -1
  7. package/dist/CommonIconTypes.d.ts +1 -1
  8. package/dist/FlatList.js.map +1 -1
  9. package/dist/GPTChat.d.ts +2 -1
  10. package/dist/GPTChat.js +14 -4
  11. package/dist/GPTChat.js.map +1 -1
  12. package/dist/Page.js +2 -1
  13. package/dist/Page.js.map +1 -1
  14. package/dist/ScrollView.js.map +1 -1
  15. package/dist/TextField.js +46 -41
  16. package/dist/TextField.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/login/LoginScreen.js +1 -1
  21. package/dist/login/LoginScreen.js.map +1 -1
  22. package/dist/signUp/SignUpScreen.js +1 -1
  23. package/dist/signUp/SignUpScreen.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/AiSuggestionBox.test.tsx +373 -0
  26. package/src/AiSuggestionBox.tsx +233 -0
  27. package/src/Common.ts +15 -0
  28. package/src/CommonIconTypes.ts +1 -1
  29. package/src/FlatList.tsx +1 -0
  30. package/src/GPTChat.tsx +40 -2
  31. package/src/Page.tsx +20 -9
  32. package/src/ScrollView.tsx +1 -0
  33. package/src/TextField.tsx +88 -69
  34. package/src/__snapshots__/AddressField.test.tsx.snap +208 -152
  35. package/src/__snapshots__/AiSuggestionBox.test.tsx.snap +1031 -0
  36. package/src/__snapshots__/CustomSelectField.test.tsx.snap +51 -37
  37. package/src/__snapshots__/EmailField.test.tsx.snap +111 -83
  38. package/src/__snapshots__/Field.test.tsx.snap +616 -448
  39. package/src/__snapshots__/MobileAddressAutoComplete.test.tsx.snap +51 -37
  40. package/src/__snapshots__/NumberField.test.tsx.snap +51 -37
  41. package/src/__snapshots__/Page.test.tsx.snap +15 -20
  42. package/src/__snapshots__/PhoneNumberField.test.tsx.snap +264 -194
  43. package/src/__snapshots__/TapToEdit.test.tsx.snap +51 -37
  44. package/src/__snapshots__/TextArea.test.tsx.snap +255 -185
  45. package/src/__snapshots__/TextField.test.tsx.snap +264 -194
  46. package/src/__snapshots__/UnifiedAddressAutoComplete.test.tsx.snap +204 -148
  47. package/src/__snapshots__/WebAddressAutocomplete.test.tsx.snap +153 -111
  48. package/src/index.tsx +1 -0
  49. package/src/login/LoginScreen.tsx +1 -0
  50. package/src/login/__snapshots__/LoginScreen.test.tsx.snap +104 -76
  51. package/src/signUp/SignUpScreen.tsx +1 -0
  52. package/src/signUp/__snapshots__/SignUpScreen.test.tsx.snap +156 -114
@@ -0,0 +1,233 @@
1
+ import {type FC, useCallback, useEffect, useState} from "react";
2
+ import {Pressable, View} from "react-native";
3
+
4
+ import type {AiSuggestionProps} from "./Common";
5
+ import {Icon} from "./Icon";
6
+ import {Text} from "./Text";
7
+ import {useTheme} from "./Theme";
8
+
9
+ export interface AiSuggestionBoxProps extends AiSuggestionProps {
10
+ testID?: string;
11
+ }
12
+
13
+ export const AiSuggestionBox: FC<AiSuggestionBoxProps> = ({
14
+ status,
15
+ text,
16
+ onAdd,
17
+ onHide,
18
+ onShow,
19
+ onFeedback,
20
+ feedback,
21
+ notStartedText = "AI note will be generated once the session ends.",
22
+ generatingText = "AI note generation in progress...",
23
+ testID,
24
+ }) => {
25
+ const {theme} = useTheme();
26
+ const [expanded, setExpanded] = useState(true);
27
+
28
+ // Re-expand when a new suggestion arrives or is added
29
+ useEffect(() => {
30
+ if (status === "ready" || status === "added") {
31
+ setExpanded(true);
32
+ }
33
+ }, [status]);
34
+
35
+ const isAdded = status === "added";
36
+
37
+ const backgroundColor = isAdded
38
+ ? theme.surface.successLight
39
+ : status === "not-started"
40
+ ? theme.primitives.neutral050
41
+ : theme.primitives.primary000;
42
+
43
+ const borderColor = isAdded
44
+ ? "#9BE7B2"
45
+ : status === "not-started"
46
+ ? theme.surface.secondaryLight
47
+ : theme.primitives.primary100;
48
+
49
+ const containerStyle = {
50
+ backgroundColor,
51
+ borderColor,
52
+ borderRadius: 8,
53
+ borderWidth: 1,
54
+ gap: 8,
55
+ padding: 8,
56
+ width: "100%" as const,
57
+ };
58
+
59
+ const headingText =
60
+ status === "not-started"
61
+ ? notStartedText
62
+ : status === "generating"
63
+ ? generatingText
64
+ : isAdded
65
+ ? "AI-generated note added!"
66
+ : expanded
67
+ ? "AI-generated note"
68
+ : "AI-generated note (hidden)";
69
+
70
+ const handleHide = useCallback(() => {
71
+ setExpanded(false);
72
+ onHide?.();
73
+ }, [onHide]);
74
+
75
+ const handleShow = useCallback(() => {
76
+ setExpanded(true);
77
+ onShow?.();
78
+ }, [onShow]);
79
+
80
+ const handleThumbsUp = useCallback(() => {
81
+ if (!onFeedback) {
82
+ return;
83
+ }
84
+ onFeedback(feedback === "like" ? null : "like");
85
+ }, [onFeedback, feedback]);
86
+
87
+ const handleThumbsDown = useCallback(() => {
88
+ if (!onFeedback) {
89
+ return;
90
+ }
91
+ onFeedback(feedback === "dislike" ? null : "dislike");
92
+ }, [onFeedback, feedback]);
93
+
94
+ const renderFeedback = () => (
95
+ <View
96
+ style={{alignItems: "center", flexDirection: "row"}}
97
+ testID={testID ? `${testID}-feedback` : undefined}
98
+ >
99
+ <Pressable
100
+ accessibilityLabel="Thumbs up"
101
+ accessibilityRole="button"
102
+ onPress={handleThumbsUp}
103
+ style={{alignItems: "center", height: 24, justifyContent: "center", width: 24}}
104
+ testID={testID ? `${testID}-thumbs-up` : undefined}
105
+ >
106
+ <Icon
107
+ color={feedback === "like" ? "secondaryDark" : "secondaryLight"}
108
+ iconName="thumbs-up"
109
+ size="xs"
110
+ type={feedback === "like" ? "solid" : "regular"}
111
+ />
112
+ </Pressable>
113
+ <Pressable
114
+ accessibilityLabel="Thumbs down"
115
+ accessibilityRole="button"
116
+ onPress={handleThumbsDown}
117
+ style={{alignItems: "center", height: 24, justifyContent: "center", width: 24}}
118
+ testID={testID ? `${testID}-thumbs-down` : undefined}
119
+ >
120
+ <Icon
121
+ color={feedback === "dislike" ? "secondaryDark" : "secondaryLight"}
122
+ iconName="thumbs-down"
123
+ size="xs"
124
+ type={feedback === "dislike" ? "solid" : "regular"}
125
+ />
126
+ </Pressable>
127
+ </View>
128
+ );
129
+
130
+ if (status === "not-started" || status === "generating") {
131
+ return (
132
+ <View style={containerStyle} testID={testID}>
133
+ <View style={{alignItems: "center", flexDirection: "row", gap: 4, width: "100%"}}>
134
+ <Icon color="secondaryDark" iconName="wand-magic-sparkles" size="xs" />
135
+ <View style={{flex: 1}}>
136
+ <Text color="secondaryDark" size="sm">
137
+ {headingText}
138
+ </Text>
139
+ </View>
140
+ </View>
141
+ </View>
142
+ );
143
+ }
144
+
145
+ if (!expanded) {
146
+ return (
147
+ <View style={{...containerStyle, flexDirection: "column"}} testID={testID}>
148
+ <View style={{alignItems: "center", flexDirection: "row", gap: 4, width: "100%"}}>
149
+ <Icon color="secondaryDark" iconName="wand-magic-sparkles" size="xs" />
150
+ <View style={{flex: 1}}>
151
+ <Text color="secondaryDark" size="sm">
152
+ {headingText}
153
+ </Text>
154
+ </View>
155
+ <View style={{alignItems: "center", flexDirection: "row", gap: 4}}>
156
+ <Pressable
157
+ accessibilityLabel="Show suggestion"
158
+ accessibilityRole="button"
159
+ onPress={handleShow}
160
+ style={{height: 28, justifyContent: "center", paddingHorizontal: 16}}
161
+ testID={testID ? `${testID}-show` : undefined}
162
+ >
163
+ <Text bold color="secondaryDark" size="sm">
164
+ Show
165
+ </Text>
166
+ </Pressable>
167
+ {renderFeedback()}
168
+ </View>
169
+ </View>
170
+ </View>
171
+ );
172
+ }
173
+
174
+ return (
175
+ <View style={{...containerStyle, alignItems: "flex-end"}} testID={testID}>
176
+ <View style={{alignItems: "center", flexDirection: "row", gap: 4, width: "100%"}}>
177
+ <Icon color="secondaryDark" iconName="wand-magic-sparkles" size="xs" />
178
+ <View style={{flex: 1}}>
179
+ <Text color="secondaryDark" size="sm">
180
+ {headingText}
181
+ </Text>
182
+ </View>
183
+ {renderFeedback()}
184
+ </View>
185
+
186
+ {Boolean(text) && (
187
+ <View style={{paddingBottom: 4, width: "100%"}}>
188
+ <Text size="md">{text}</Text>
189
+ </View>
190
+ )}
191
+
192
+ <View
193
+ style={{
194
+ alignItems: "center",
195
+ flexDirection: "row",
196
+ gap: 8,
197
+ justifyContent: "flex-end",
198
+ width: "100%",
199
+ }}
200
+ >
201
+ <Pressable
202
+ accessibilityLabel="Hide suggestion"
203
+ accessibilityRole="button"
204
+ onPress={handleHide}
205
+ style={{height: 28, justifyContent: "center", paddingHorizontal: 16}}
206
+ testID={testID ? `${testID}-hide` : undefined}
207
+ >
208
+ <Text bold color="secondaryDark" size="sm">
209
+ Hide
210
+ </Text>
211
+ </Pressable>
212
+ <Pressable
213
+ accessibilityLabel="Add to note"
214
+ accessibilityRole="button"
215
+ onPress={onAdd}
216
+ style={{
217
+ alignItems: "center",
218
+ backgroundColor: theme.surface.secondaryDark,
219
+ borderRadius: 360,
220
+ height: 28,
221
+ justifyContent: "center",
222
+ paddingHorizontal: 16,
223
+ }}
224
+ testID={testID ? `${testID}-add` : undefined}
225
+ >
226
+ <Text bold color="inverted" size="sm">
227
+ Add to note
228
+ </Text>
229
+ </Pressable>
230
+ </View>
231
+ </View>
232
+ );
233
+ };
package/src/Common.ts CHANGED
@@ -630,6 +630,18 @@ export interface ErrorTextProps {
630
630
  errorText?: string;
631
631
  }
632
632
 
633
+ export interface AiSuggestionProps {
634
+ status: "not-started" | "generating" | "ready" | "added";
635
+ text?: string;
636
+ onAdd?: () => void;
637
+ onHide?: () => void;
638
+ onShow?: () => void;
639
+ onFeedback?: (feedback: "like" | "dislike" | null) => void;
640
+ feedback?: "like" | "dislike" | null;
641
+ notStartedText?: string;
642
+ generatingText?: string;
643
+ }
644
+
633
645
  export interface TextFieldProps extends BaseFieldProps, HelperTextProps, ErrorTextProps {
634
646
  type?: "email" | "password" | "phoneNumber" | "search" | "text" | "url";
635
647
 
@@ -642,6 +654,8 @@ export interface TextFieldProps extends BaseFieldProps, HelperTextProps, ErrorTe
642
654
 
643
655
  inputRef?: any;
644
656
  trimOnBlur?: boolean;
657
+
658
+ aiSuggestion?: AiSuggestionProps;
645
659
  }
646
660
 
647
661
  export interface TextAreaProps extends Omit<TextFieldProps, "multiline" | "type"> {}
@@ -1904,6 +1918,7 @@ export interface PageProps {
1904
1918
  navigation?: any;
1905
1919
  scroll?: boolean;
1906
1920
  loading?: boolean;
1921
+ loadingText?: string;
1907
1922
  display?: "flex" | "none" | "block" | "inlineBlock";
1908
1923
  title?: string;
1909
1924
  backButton?: boolean;
@@ -466,7 +466,7 @@ export type FontAwesome6BrandNames =
466
466
  | "yammer"
467
467
  | "yandex-international"
468
468
  | "yandex"
469
- | "yarn"
469
+ | "bun"
470
470
  | "yelp"
471
471
  | "yoast"
472
472
  | "youtube"
package/src/FlatList.tsx CHANGED
@@ -1,2 +1,3 @@
1
1
  import {FlatList} from "react-native";
2
+
2
3
  export {FlatList};
package/src/GPTChat.tsx CHANGED
@@ -103,6 +103,7 @@ export interface GPTChatProps {
103
103
  onSubmit: (prompt: string) => void;
104
104
  onUpdateTitle?: (id: string, title: string) => void;
105
105
  selectedModel?: string;
106
+ suggestedPrompts?: string[];
106
107
  systemMemory?: string;
107
108
  testID?: string;
108
109
  }
@@ -706,6 +707,7 @@ export const GPTChat = ({
706
707
  onSubmit,
707
708
  onUpdateTitle,
708
709
  selectedModel,
710
+ suggestedPrompts,
709
711
  systemMemory,
710
712
  testID,
711
713
  }: GPTChatProps): React.ReactElement => {
@@ -730,7 +732,7 @@ export const GPTChat = ({
730
732
  setInputValue("");
731
733
  }, [inputValue, isStreaming, onSubmit]);
732
734
 
733
- // On web, intercept Enter key in the chat input to submit (Shift+Enter for newline)
735
+ // On web: Enter sends, Cmd+Enter inserts a new line
734
736
  const handleSubmitRef = useRef(handleSubmit);
735
737
  handleSubmitRef.current = handleSubmit;
736
738
  useEffect(() => {
@@ -738,7 +740,7 @@ export const GPTChat = ({
738
740
  return;
739
741
  }
740
742
  const handler = (e: KeyboardEvent) => {
741
- if (e.key !== "Enter" || e.shiftKey) {
743
+ if (e.key !== "Enter") {
742
744
  return;
743
745
  }
744
746
  const target = e.target as HTMLElement | null;
@@ -746,6 +748,10 @@ export const GPTChat = ({
746
748
  if (testId !== "gpt-input") {
747
749
  return;
748
750
  }
751
+ if (e.metaKey || e.shiftKey) {
752
+ // Cmd+Enter or Shift+Enter: allow default (new line)
753
+ return;
754
+ }
749
755
  e.preventDefault();
750
756
  handleSubmitRef.current();
751
757
  };
@@ -830,6 +836,16 @@ export const GPTChat = ({
830
836
  setEditingTitle("");
831
837
  }, [editingHistoryId, editingTitle, onUpdateTitle]);
832
838
 
839
+ const handleSuggestedPrompt = useCallback(
840
+ (prompt: string) => {
841
+ if (isStreaming) {
842
+ return;
843
+ }
844
+ onSubmit(prompt);
845
+ },
846
+ [isStreaming, onSubmit]
847
+ );
848
+
833
849
  const handleOpenApiKeyModal = useCallback(() => {
834
850
  setApiKeyDraft(geminiApiKey ?? "");
835
851
  setIsApiKeyModalVisible(true);
@@ -917,6 +933,28 @@ export const GPTChat = ({
917
933
  <Box flex="grow" marginBottom={3} onLayout={handleViewportLayout}>
918
934
  <Box flex="grow" gap={3} onScroll={handleScroll} scroll={true} scrollRef={scrollViewRef}>
919
935
  <Box gap={3} onLayout={handleContentLayout}>
936
+ {currentMessages.length === 0 && suggestedPrompts && suggestedPrompts.length > 0 && (
937
+ <Box alignItems="center" gap={2} paddingY={4}>
938
+ <Text color="secondaryDark" size="sm">
939
+ Try asking...
940
+ </Text>
941
+ <Box direction="row" gap={2} wrap={true}>
942
+ {suggestedPrompts.map((prompt) => (
943
+ <Box
944
+ accessibilityHint="Send this suggested prompt"
945
+ accessibilityLabel={prompt}
946
+ border="default"
947
+ key={prompt}
948
+ onClick={() => handleSuggestedPrompt(prompt)}
949
+ padding={2}
950
+ rounding="lg"
951
+ >
952
+ <Text size="sm">{prompt}</Text>
953
+ </Box>
954
+ ))}
955
+ </Box>
956
+ </Box>
957
+ )}
920
958
  {currentMessages.map((message, index) => {
921
959
  // Tool call/result messages
922
960
  if (message.role === "tool-call" && message.toolCall) {
package/src/Page.tsx CHANGED
@@ -8,6 +8,7 @@ import {ErrorBoundary} from "./ErrorBoundary";
8
8
  import {Heading} from "./Heading";
9
9
  import {IconButton} from "./IconButton";
10
10
  import {Spinner} from "./Spinner";
11
+ import {Text} from "./Text";
11
12
 
12
13
  export class Page extends React.Component<PageProps, {}> {
13
14
  actionSheetRef: React.RefObject<any> = React.createRef();
@@ -74,15 +75,25 @@ export class Page extends React.Component<PageProps, {}> {
74
75
  width="100%"
75
76
  >
76
77
  {this.renderHeader()}
77
- {this.props.loading === true && <Spinner />}
78
- {/* <KeyboardAccessoryNavigation
79
- avoidKeyboard
80
- doneButton={true}
81
- nextButton={true}
82
- previousButton={true}
83
- /> */}
84
-
85
- {this.props.children}
78
+ {this.props.loading ? (
79
+ <Box
80
+ alignItems="center"
81
+ direction="column"
82
+ display="flex"
83
+ flex="grow"
84
+ justifyContent="center"
85
+ marginTop={8}
86
+ >
87
+ <Spinner />
88
+ {Boolean(this.props.loadingText) && (
89
+ <Box marginTop={2}>
90
+ <Text color="secondaryDark">{this.props.loadingText}</Text>
91
+ </Box>
92
+ )}
93
+ </Box>
94
+ ) : (
95
+ this.props.children
96
+ )}
86
97
  </Box>
87
98
  {Boolean(this.props.footer) && (
88
99
  <Box
@@ -1,2 +1,3 @@
1
1
  import {ScrollView} from "react-native";
2
+
2
3
  export {ScrollView};
package/src/TextField.tsx CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  View,
11
11
  } from "react-native";
12
12
 
13
+ import {AiSuggestionBox} from "./AiSuggestionBox";
13
14
  import type {TextFieldProps, TextStyleWithOutline} from "./Common";
14
15
  import {FieldError, FieldHelperText, FieldTitle} from "./fieldElements";
15
16
  import {Icon} from "./Icon";
@@ -78,6 +79,8 @@ export const TextField: FC<TextFieldProps> = ({
78
79
  onEnter,
79
80
  onSubmitEditing,
80
81
  testID,
82
+ id,
83
+ aiSuggestion,
81
84
  }) => {
82
85
  const {theme} = useTheme();
83
86
 
@@ -146,86 +149,102 @@ export const TextField: FC<TextFieldProps> = ({
146
149
  {Boolean(errorText) && <FieldError text={errorText!} />}
147
150
  <View
148
151
  style={{
149
- alignItems: "center",
150
152
  backgroundColor: disabled ? theme.surface.neutralLight : theme.surface.base,
151
153
  borderColor,
152
154
  borderRadius: 4,
153
155
  borderWidth: focused ? 3 : 1,
154
- flexDirection: "row",
156
+ flexDirection: "column",
157
+ gap: aiSuggestion ? 10 : 0,
155
158
  overflow: "hidden",
156
159
  paddingHorizontal: focused ? 10 : 12,
157
160
  paddingVertical: focused ? 6 : 8,
158
161
  }}
159
162
  >
160
- <TextInput
161
- accessibilityHint="Enter text here"
162
- accessibilityState={{disabled}}
163
- aria-label="Text input field"
164
- autoCapitalize={type === "text" ? "sentences" : "none"}
165
- autoCorrect={shouldAutocorrect}
166
- blurOnSubmit={blurOnSubmit}
167
- enterKeyHint={returnKeyType}
168
- keyboardType={keyboardType as KeyboardTypeOptions}
169
- multiline={multiline}
170
- numberOfLines={rows || 4}
171
- onBlur={() => {
172
- if (disabled) return;
173
- let finalValue = value ?? "";
163
+ {Boolean(aiSuggestion) && (
164
+ <AiSuggestionBox
165
+ testID={testID ? `${testID}-ai-suggestion` : undefined}
166
+ {...aiSuggestion!}
167
+ />
168
+ )}
169
+ <View
170
+ style={{
171
+ alignItems: "center",
172
+ flexDirection: "row",
173
+ }}
174
+ >
175
+ <TextInput
176
+ accessibilityHint="Enter text here"
177
+ accessibilityState={{disabled}}
178
+ aria-label="Text input field"
179
+ autoCapitalize={type === "text" ? "sentences" : "none"}
180
+ autoCorrect={shouldAutocorrect}
181
+ blurOnSubmit={blurOnSubmit}
182
+ enterKeyHint={returnKeyType}
183
+ keyboardType={keyboardType as KeyboardTypeOptions}
184
+ multiline={multiline}
185
+ nativeID={id}
186
+ numberOfLines={rows || 4}
187
+ onBlur={() => {
188
+ if (disabled) {
189
+ return;
190
+ }
191
+ let finalValue = value ?? "";
174
192
 
175
- if (trimOnBlur && value) {
176
- finalValue = finalValue.trim();
177
- if (finalValue !== value) {
178
- onChange(finalValue);
193
+ if (trimOnBlur && value) {
194
+ finalValue = finalValue.trim();
195
+ if (finalValue !== value) {
196
+ onChange(finalValue);
197
+ }
179
198
  }
180
- }
181
- if (onBlur) {
182
- onBlur(finalValue);
183
- }
184
- setFocused(false);
185
- }}
186
- onChangeText={onChange}
187
- onContentSizeChange={(event) => {
188
- if (!grow) {
189
- return;
190
- }
191
- setHeight(event.nativeEvent.contentSize.height);
192
- }}
193
- onFocus={() => {
194
- if (!disabled) {
195
- setFocused(true);
196
- }
197
- if (onFocus) {
198
- onFocus();
199
- }
200
- }}
201
- onSubmitEditing={() => {
202
- if (onEnter) {
203
- onEnter();
204
- }
205
- if (onSubmitEditing) {
206
- onSubmitEditing();
207
- }
208
- }}
209
- placeholder={placeholder}
210
- placeholderTextColor={theme.text.secondaryLight}
211
- readOnly={disabled}
212
- ref={(ref) => {
213
- if (inputRef) {
214
- inputRef(ref);
215
- }
216
- }}
217
- secureTextEntry={type === "password"}
218
- style={defaultTextInputStyles}
219
- testID={testID}
220
- textContentType={textContentType}
221
- underlineColorAndroid="transparent"
222
- value={value}
223
- />
224
- {Boolean(iconName) && (
225
- <Pressable aria-role="button" onPress={onIconClick}>
226
- <Icon iconName={iconName!} size="md" />
227
- </Pressable>
228
- )}
199
+ if (onBlur) {
200
+ onBlur(finalValue);
201
+ }
202
+ setFocused(false);
203
+ }}
204
+ onChangeText={onChange}
205
+ onContentSizeChange={(event) => {
206
+ if (!grow) {
207
+ return;
208
+ }
209
+ setHeight(event.nativeEvent.contentSize.height);
210
+ }}
211
+ onFocus={() => {
212
+ if (!disabled) {
213
+ setFocused(true);
214
+ }
215
+ if (onFocus) {
216
+ onFocus();
217
+ }
218
+ }}
219
+ onSubmitEditing={() => {
220
+ if (onEnter) {
221
+ onEnter();
222
+ }
223
+ if (onSubmitEditing) {
224
+ onSubmitEditing();
225
+ }
226
+ }}
227
+ placeholder={placeholder}
228
+ placeholderTextColor={theme.text.secondaryLight}
229
+ readOnly={disabled}
230
+ ref={(ref) => {
231
+ if (inputRef) {
232
+ inputRef(ref);
233
+ }
234
+ }}
235
+ secureTextEntry={type === "password"}
236
+ style={defaultTextInputStyles}
237
+ testID={testID}
238
+ textContentType={textContentType}
239
+ underlineColorAndroid="transparent"
240
+ value={value}
241
+ />
242
+ {Boolean(iconName) && (
243
+ <Pressable aria-role="button" onPress={onIconClick}>
244
+ <Icon iconName={iconName!} size="md" />
245
+ </Pressable>
246
+ )}
247
+ </View>
229
248
  </View>
230
249
  {Boolean(helperText) && <FieldHelperText text={helperText!} />}
231
250
  {/* {type === "numberRange" && value && (