@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.
- package/README.md +5 -5
- package/dist/AiSuggestionBox.d.ts +6 -0
- package/dist/AiSuggestionBox.js +87 -0
- package/dist/AiSuggestionBox.js.map +1 -0
- package/dist/Common.d.ts +13 -0
- package/dist/Common.js.map +1 -1
- package/dist/CommonIconTypes.d.ts +1 -1
- package/dist/FlatList.js.map +1 -1
- package/dist/GPTChat.d.ts +2 -1
- package/dist/GPTChat.js +14 -4
- package/dist/GPTChat.js.map +1 -1
- package/dist/Page.js +2 -1
- package/dist/Page.js.map +1 -1
- package/dist/ScrollView.js.map +1 -1
- package/dist/TextField.js +46 -41
- package/dist/TextField.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/login/LoginScreen.js +1 -1
- package/dist/login/LoginScreen.js.map +1 -1
- package/dist/signUp/SignUpScreen.js +1 -1
- package/dist/signUp/SignUpScreen.js.map +1 -1
- package/package.json +1 -1
- package/src/AiSuggestionBox.test.tsx +373 -0
- package/src/AiSuggestionBox.tsx +233 -0
- package/src/Common.ts +15 -0
- package/src/CommonIconTypes.ts +1 -1
- package/src/FlatList.tsx +1 -0
- package/src/GPTChat.tsx +40 -2
- package/src/Page.tsx +20 -9
- package/src/ScrollView.tsx +1 -0
- package/src/TextField.tsx +88 -69
- package/src/__snapshots__/AddressField.test.tsx.snap +208 -152
- package/src/__snapshots__/AiSuggestionBox.test.tsx.snap +1031 -0
- package/src/__snapshots__/CustomSelectField.test.tsx.snap +51 -37
- package/src/__snapshots__/EmailField.test.tsx.snap +111 -83
- package/src/__snapshots__/Field.test.tsx.snap +616 -448
- package/src/__snapshots__/MobileAddressAutoComplete.test.tsx.snap +51 -37
- package/src/__snapshots__/NumberField.test.tsx.snap +51 -37
- package/src/__snapshots__/Page.test.tsx.snap +15 -20
- package/src/__snapshots__/PhoneNumberField.test.tsx.snap +264 -194
- package/src/__snapshots__/TapToEdit.test.tsx.snap +51 -37
- package/src/__snapshots__/TextArea.test.tsx.snap +255 -185
- package/src/__snapshots__/TextField.test.tsx.snap +264 -194
- package/src/__snapshots__/UnifiedAddressAutoComplete.test.tsx.snap +204 -148
- package/src/__snapshots__/WebAddressAutocomplete.test.tsx.snap +153 -111
- package/src/index.tsx +1 -0
- package/src/login/LoginScreen.tsx +1 -0
- package/src/login/__snapshots__/LoginScreen.test.tsx.snap +104 -76
- package/src/signUp/SignUpScreen.tsx +1 -0
- 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;
|
package/src/CommonIconTypes.ts
CHANGED
package/src/FlatList.tsx
CHANGED
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
|
|
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"
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
package/src/ScrollView.tsx
CHANGED
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: "
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
if (trimOnBlur && value) {
|
|
194
|
+
finalValue = finalValue.trim();
|
|
195
|
+
if (finalValue !== value) {
|
|
196
|
+
onChange(finalValue);
|
|
197
|
+
}
|
|
179
198
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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 && (
|