@terreno/ui 0.7.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/dist/AiSuggestionBox.d.ts +6 -0
- package/dist/AiSuggestionBox.js +87 -0
- package/dist/AiSuggestionBox.js.map +1 -0
- package/dist/Common.d.ts +12 -0
- package/dist/Common.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/package.json +1 -1
- package/src/AiSuggestionBox.test.tsx +373 -0
- package/src/AiSuggestionBox.tsx +233 -0
- package/src/Common.ts +14 -0
- package/src/TextField.tsx +87 -70
- package/src/__snapshots__/AddressField.test.tsx.snap +208 -156
- package/src/__snapshots__/AiSuggestionBox.test.tsx.snap +1031 -0
- package/src/__snapshots__/CustomSelectField.test.tsx.snap +51 -38
- package/src/__snapshots__/EmailField.test.tsx.snap +111 -85
- package/src/__snapshots__/Field.test.tsx.snap +616 -460
- package/src/__snapshots__/MobileAddressAutoComplete.test.tsx.snap +51 -38
- package/src/__snapshots__/NumberField.test.tsx.snap +51 -38
- package/src/__snapshots__/PhoneNumberField.test.tsx.snap +264 -199
- package/src/__snapshots__/TapToEdit.test.tsx.snap +51 -38
- package/src/__snapshots__/TextArea.test.tsx.snap +255 -190
- package/src/__snapshots__/TextField.test.tsx.snap +264 -199
- package/src/__snapshots__/UnifiedAddressAutoComplete.test.tsx.snap +204 -152
- package/src/__snapshots__/WebAddressAutocomplete.test.tsx.snap +153 -114
- package/src/index.tsx +1 -0
- package/src/login/__snapshots__/LoginScreen.test.tsx.snap +104 -78
- package/src/signUp/__snapshots__/SignUpScreen.test.tsx.snap +156 -117
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import {describe, expect, it, mock} from "bun:test";
|
|
2
|
+
import {fireEvent} from "@testing-library/react-native";
|
|
3
|
+
|
|
4
|
+
import {TextArea} from "./TextArea";
|
|
5
|
+
import {renderWithTheme} from "./test-utils";
|
|
6
|
+
|
|
7
|
+
describe("AiSuggestionBox in TextArea", () => {
|
|
8
|
+
describe("not-started state", () => {
|
|
9
|
+
it("should render default not-started text", () => {
|
|
10
|
+
const {getByText} = renderWithTheme(
|
|
11
|
+
<TextArea
|
|
12
|
+
aiSuggestion={{onAdd: () => {}, status: "not-started"}}
|
|
13
|
+
onChange={() => {}}
|
|
14
|
+
value=""
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(getByText("AI note will be generated once the session ends.")).toBeTruthy();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should render custom not-started text", () => {
|
|
22
|
+
const {getByText} = renderWithTheme(
|
|
23
|
+
<TextArea
|
|
24
|
+
aiSuggestion={{
|
|
25
|
+
notStartedText: "Custom pending message",
|
|
26
|
+
onAdd: () => {},
|
|
27
|
+
status: "not-started",
|
|
28
|
+
}}
|
|
29
|
+
onChange={() => {}}
|
|
30
|
+
value=""
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(getByText("Custom pending message")).toBeTruthy();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("generating state", () => {
|
|
39
|
+
it("should render default generating text", () => {
|
|
40
|
+
const {getByText} = renderWithTheme(
|
|
41
|
+
<TextArea
|
|
42
|
+
aiSuggestion={{onAdd: () => {}, status: "generating"}}
|
|
43
|
+
onChange={() => {}}
|
|
44
|
+
value=""
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(getByText("AI note generation in progress...")).toBeTruthy();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should render custom generating text", () => {
|
|
52
|
+
const {getByText} = renderWithTheme(
|
|
53
|
+
<TextArea
|
|
54
|
+
aiSuggestion={{
|
|
55
|
+
generatingText: "Thinking...",
|
|
56
|
+
onAdd: () => {},
|
|
57
|
+
status: "generating",
|
|
58
|
+
}}
|
|
59
|
+
onChange={() => {}}
|
|
60
|
+
value=""
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(getByText("Thinking...")).toBeTruthy();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("ready state", () => {
|
|
69
|
+
it("should render suggestion text", () => {
|
|
70
|
+
const {getByText} = renderWithTheme(
|
|
71
|
+
<TextArea
|
|
72
|
+
aiSuggestion={{
|
|
73
|
+
onAdd: () => {},
|
|
74
|
+
status: "ready",
|
|
75
|
+
text: "This is a suggestion.",
|
|
76
|
+
}}
|
|
77
|
+
onChange={() => {}}
|
|
78
|
+
value=""
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(getByText("AI-generated note")).toBeTruthy();
|
|
83
|
+
expect(getByText("This is a suggestion.")).toBeTruthy();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should render Hide and Add to note buttons", () => {
|
|
87
|
+
const {getByText} = renderWithTheme(
|
|
88
|
+
<TextArea
|
|
89
|
+
aiSuggestion={{
|
|
90
|
+
onAdd: () => {},
|
|
91
|
+
status: "ready",
|
|
92
|
+
text: "Suggestion text",
|
|
93
|
+
}}
|
|
94
|
+
onChange={() => {}}
|
|
95
|
+
value=""
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(getByText("Hide")).toBeTruthy();
|
|
100
|
+
expect(getByText("Add to note")).toBeTruthy();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should call onAdd when Add to note is pressed", () => {
|
|
104
|
+
const mockOnAdd = mock(() => {});
|
|
105
|
+
const {getByLabelText} = renderWithTheme(
|
|
106
|
+
<TextArea
|
|
107
|
+
aiSuggestion={{
|
|
108
|
+
onAdd: mockOnAdd,
|
|
109
|
+
status: "ready",
|
|
110
|
+
text: "Suggestion text",
|
|
111
|
+
}}
|
|
112
|
+
onChange={() => {}}
|
|
113
|
+
testID="test"
|
|
114
|
+
value=""
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
fireEvent.press(getByLabelText("Add to note"));
|
|
119
|
+
expect(mockOnAdd).toHaveBeenCalledTimes(1);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should collapse when Hide is pressed", () => {
|
|
123
|
+
const {getByLabelText, getByText, queryByText} = renderWithTheme(
|
|
124
|
+
<TextArea
|
|
125
|
+
aiSuggestion={{
|
|
126
|
+
onAdd: () => {},
|
|
127
|
+
status: "ready",
|
|
128
|
+
text: "Suggestion text",
|
|
129
|
+
}}
|
|
130
|
+
onChange={() => {}}
|
|
131
|
+
testID="test"
|
|
132
|
+
value=""
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(getByText("Suggestion text")).toBeTruthy();
|
|
137
|
+
|
|
138
|
+
fireEvent.press(getByLabelText("Hide suggestion"));
|
|
139
|
+
|
|
140
|
+
expect(queryByText("Suggestion text")).toBeNull();
|
|
141
|
+
expect(getByText("AI-generated note (hidden)")).toBeTruthy();
|
|
142
|
+
expect(getByText("Show")).toBeTruthy();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should expand when Show is pressed after collapsing", () => {
|
|
146
|
+
const {getByLabelText, getByText} = renderWithTheme(
|
|
147
|
+
<TextArea
|
|
148
|
+
aiSuggestion={{
|
|
149
|
+
onAdd: () => {},
|
|
150
|
+
status: "ready",
|
|
151
|
+
text: "Suggestion text",
|
|
152
|
+
}}
|
|
153
|
+
onChange={() => {}}
|
|
154
|
+
testID="test"
|
|
155
|
+
value=""
|
|
156
|
+
/>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
fireEvent.press(getByLabelText("Hide suggestion"));
|
|
160
|
+
expect(getByText("AI-generated note (hidden)")).toBeTruthy();
|
|
161
|
+
|
|
162
|
+
fireEvent.press(getByLabelText("Show suggestion"));
|
|
163
|
+
expect(getByText("Suggestion text")).toBeTruthy();
|
|
164
|
+
expect(getByText("AI-generated note")).toBeTruthy();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should call onHide when Hide is pressed", () => {
|
|
168
|
+
const mockOnHide = mock(() => {});
|
|
169
|
+
const {getByLabelText} = renderWithTheme(
|
|
170
|
+
<TextArea
|
|
171
|
+
aiSuggestion={{
|
|
172
|
+
onAdd: () => {},
|
|
173
|
+
onHide: mockOnHide,
|
|
174
|
+
status: "ready",
|
|
175
|
+
text: "Suggestion text",
|
|
176
|
+
}}
|
|
177
|
+
onChange={() => {}}
|
|
178
|
+
testID="test"
|
|
179
|
+
value=""
|
|
180
|
+
/>
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
fireEvent.press(getByLabelText("Hide suggestion"));
|
|
184
|
+
expect(mockOnHide).toHaveBeenCalledTimes(1);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should call onShow when Show is pressed", () => {
|
|
188
|
+
const mockOnShow = mock(() => {});
|
|
189
|
+
const {getByLabelText} = renderWithTheme(
|
|
190
|
+
<TextArea
|
|
191
|
+
aiSuggestion={{
|
|
192
|
+
onAdd: () => {},
|
|
193
|
+
onShow: mockOnShow,
|
|
194
|
+
status: "ready",
|
|
195
|
+
text: "Suggestion text",
|
|
196
|
+
}}
|
|
197
|
+
onChange={() => {}}
|
|
198
|
+
testID="test"
|
|
199
|
+
value=""
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
fireEvent.press(getByLabelText("Hide suggestion"));
|
|
204
|
+
fireEvent.press(getByLabelText("Show suggestion"));
|
|
205
|
+
expect(mockOnShow).toHaveBeenCalledTimes(1);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("added state", () => {
|
|
210
|
+
it("should render added heading", () => {
|
|
211
|
+
const {getByText} = renderWithTheme(
|
|
212
|
+
<TextArea
|
|
213
|
+
aiSuggestion={{
|
|
214
|
+
onAdd: () => {},
|
|
215
|
+
status: "added",
|
|
216
|
+
text: "Added suggestion",
|
|
217
|
+
}}
|
|
218
|
+
onChange={() => {}}
|
|
219
|
+
value=""
|
|
220
|
+
/>
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
expect(getByText("AI-generated note added!")).toBeTruthy();
|
|
224
|
+
expect(getByText("Added suggestion")).toBeTruthy();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe("feedback", () => {
|
|
229
|
+
it("should call onFeedback with 'like' when thumbs up is pressed", () => {
|
|
230
|
+
const mockOnFeedback = mock(() => {});
|
|
231
|
+
const {getByLabelText} = renderWithTheme(
|
|
232
|
+
<TextArea
|
|
233
|
+
aiSuggestion={{
|
|
234
|
+
feedback: null,
|
|
235
|
+
onAdd: () => {},
|
|
236
|
+
onFeedback: mockOnFeedback,
|
|
237
|
+
status: "ready",
|
|
238
|
+
text: "Suggestion",
|
|
239
|
+
}}
|
|
240
|
+
onChange={() => {}}
|
|
241
|
+
testID="test"
|
|
242
|
+
value=""
|
|
243
|
+
/>
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
fireEvent.press(getByLabelText("Thumbs up"));
|
|
247
|
+
expect(mockOnFeedback).toHaveBeenCalledWith("like");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should call onFeedback with null when thumbs up is pressed while already liked", () => {
|
|
251
|
+
const mockOnFeedback = mock(() => {});
|
|
252
|
+
const {getByLabelText} = renderWithTheme(
|
|
253
|
+
<TextArea
|
|
254
|
+
aiSuggestion={{
|
|
255
|
+
feedback: "like",
|
|
256
|
+
onAdd: () => {},
|
|
257
|
+
onFeedback: mockOnFeedback,
|
|
258
|
+
status: "ready",
|
|
259
|
+
text: "Suggestion",
|
|
260
|
+
}}
|
|
261
|
+
onChange={() => {}}
|
|
262
|
+
testID="test"
|
|
263
|
+
value=""
|
|
264
|
+
/>
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
fireEvent.press(getByLabelText("Thumbs up"));
|
|
268
|
+
expect(mockOnFeedback).toHaveBeenCalledWith(null);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("should call onFeedback with 'dislike' when thumbs down is pressed", () => {
|
|
272
|
+
const mockOnFeedback = mock(() => {});
|
|
273
|
+
const {getByLabelText} = renderWithTheme(
|
|
274
|
+
<TextArea
|
|
275
|
+
aiSuggestion={{
|
|
276
|
+
feedback: null,
|
|
277
|
+
onAdd: () => {},
|
|
278
|
+
onFeedback: mockOnFeedback,
|
|
279
|
+
status: "ready",
|
|
280
|
+
text: "Suggestion",
|
|
281
|
+
}}
|
|
282
|
+
onChange={() => {}}
|
|
283
|
+
testID="test"
|
|
284
|
+
value=""
|
|
285
|
+
/>
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
fireEvent.press(getByLabelText("Thumbs down"));
|
|
289
|
+
expect(mockOnFeedback).toHaveBeenCalledWith("dislike");
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe("testID propagation", () => {
|
|
294
|
+
it("should apply testIDs to interactive elements", () => {
|
|
295
|
+
const {getByTestId} = renderWithTheme(
|
|
296
|
+
<TextArea
|
|
297
|
+
aiSuggestion={{
|
|
298
|
+
onAdd: () => {},
|
|
299
|
+
status: "ready",
|
|
300
|
+
text: "Suggestion",
|
|
301
|
+
}}
|
|
302
|
+
onChange={() => {}}
|
|
303
|
+
testID="notes"
|
|
304
|
+
value=""
|
|
305
|
+
/>
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
expect(getByTestId("notes-ai-suggestion")).toBeTruthy();
|
|
309
|
+
expect(getByTestId("notes-ai-suggestion-thumbs-up")).toBeTruthy();
|
|
310
|
+
expect(getByTestId("notes-ai-suggestion-thumbs-down")).toBeTruthy();
|
|
311
|
+
expect(getByTestId("notes-ai-suggestion-hide")).toBeTruthy();
|
|
312
|
+
expect(getByTestId("notes-ai-suggestion-add")).toBeTruthy();
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("snapshots", () => {
|
|
317
|
+
it("should match snapshot for not-started state", () => {
|
|
318
|
+
const component = renderWithTheme(
|
|
319
|
+
<TextArea
|
|
320
|
+
aiSuggestion={{onAdd: () => {}, status: "not-started"}}
|
|
321
|
+
onChange={() => {}}
|
|
322
|
+
value=""
|
|
323
|
+
/>
|
|
324
|
+
);
|
|
325
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("should match snapshot for generating state", () => {
|
|
329
|
+
const component = renderWithTheme(
|
|
330
|
+
<TextArea
|
|
331
|
+
aiSuggestion={{onAdd: () => {}, status: "generating"}}
|
|
332
|
+
onChange={() => {}}
|
|
333
|
+
value=""
|
|
334
|
+
/>
|
|
335
|
+
);
|
|
336
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("should match snapshot for ready state", () => {
|
|
340
|
+
const component = renderWithTheme(
|
|
341
|
+
<TextArea
|
|
342
|
+
aiSuggestion={{
|
|
343
|
+
feedback: null,
|
|
344
|
+
onAdd: () => {},
|
|
345
|
+
status: "ready",
|
|
346
|
+
text: "AI-generated suggestion text.",
|
|
347
|
+
}}
|
|
348
|
+
onChange={() => {}}
|
|
349
|
+
testID="test"
|
|
350
|
+
value=""
|
|
351
|
+
/>
|
|
352
|
+
);
|
|
353
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("should match snapshot for added state", () => {
|
|
357
|
+
const component = renderWithTheme(
|
|
358
|
+
<TextArea
|
|
359
|
+
aiSuggestion={{
|
|
360
|
+
feedback: "like",
|
|
361
|
+
onAdd: () => {},
|
|
362
|
+
status: "added",
|
|
363
|
+
text: "AI-generated suggestion text.",
|
|
364
|
+
}}
|
|
365
|
+
onChange={() => {}}
|
|
366
|
+
testID="test"
|
|
367
|
+
value=""
|
|
368
|
+
/>
|
|
369
|
+
);
|
|
370
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
});
|
|
@@ -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"> {}
|