@remit/web-client 0.0.310 → 0.0.312

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": "@remit/web-client",
3
- "version": "0.0.310",
3
+ "version": "0.0.312",
4
4
  "type": "module",
5
5
  "description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
6
6
  "exports": {
@@ -30,7 +30,7 @@ import {
30
30
  sanitizeQuotedHtml,
31
31
  unwrapLanguage,
32
32
  } from "@remit/ui";
33
- import type { ComposeBodyMode } from "@remit/ui/rich-text";
33
+ import type { ComposeBodyMode, ComposeInsertion } from "@remit/ui/rich-text";
34
34
  import { useMutation, useQuery } from "@tanstack/react-query";
35
35
  import type { RefObject } from "react";
36
36
  import {
@@ -50,6 +50,7 @@ import { useMessageBodyContent } from "../../hooks/useMessageBodyContent";
50
50
  import { useSaveDraft } from "../../hooks/useSaveDraft";
51
51
  import { useSignature } from "../../hooks/useSignature.js";
52
52
  import { isNotFound, softErrorMeta } from "../../lib/error-classifier";
53
+ import type { ComposeSeed } from "../../lib/mail-search";
53
54
  import { accountIsMissingSmtp } from "../settings/account-form-helpers.js";
54
55
  import { useErrorBanners } from "../ui/ErrorBannerProvider.js";
55
56
  import {
@@ -94,6 +95,8 @@ interface ComposeFormProps {
94
95
  * shared would open on the last draft any other composer touched.
95
96
  */
96
97
  outboxMessageId?: string;
98
+ seed?: ComposeSeed;
99
+ insertion?: ComposeInsertion;
97
100
  /** The draft the first autosave created, for the owner to record. */
98
101
  onDraftCreated: (outboxMessageId: string) => void;
99
102
  onClose: () => void;
@@ -118,10 +121,19 @@ const buildInitialHtml = (signaturePlainText: string): string => {
118
121
  */
119
122
  const freshDocument = (
120
123
  signaturePlainText: string,
121
- ): { html: string; text: string } => ({
122
- html: buildInitialHtml(signaturePlainText),
123
- text: signaturePlainText,
124
- });
124
+ seed?: ComposeSeed,
125
+ ): { html: string; text: string } => {
126
+ if (!seed) {
127
+ return {
128
+ html: buildInitialHtml(signaturePlainText),
129
+ text: signaturePlainText,
130
+ };
131
+ }
132
+ return {
133
+ html: `${textToHtml(seed.body)}${buildInitialHtml(signaturePlainText)}`,
134
+ text: [seed.body, signaturePlainText].filter(Boolean).join("\n\n"),
135
+ };
136
+ };
125
137
 
126
138
  const buildReplySubject = (subject?: string): string => {
127
139
  if (!subject) return "Re: ";
@@ -396,6 +408,8 @@ export const ComposeForm = ({
396
408
  account,
397
409
  sourceMessage,
398
410
  outboxMessageId,
411
+ seed,
412
+ insertion,
399
413
  onDraftCreated,
400
414
  onClose,
401
415
  onAccountChange,
@@ -546,7 +560,7 @@ export const ComposeForm = ({
546
560
  // the signature, the same document a fresh mount would have started on.
547
561
  const opening = outboxMessageId
548
562
  ? { html: "", text: "" }
549
- : freshDocument(signatureRef.current);
563
+ : freshDocument(signatureRef.current, seedRef.current);
550
564
  setToAddresses([]);
551
565
  setCcAddresses([]);
552
566
  setBccAddresses([]);
@@ -571,15 +585,19 @@ export const ComposeForm = ({
571
585
  // not a reason to reopen the document somebody is typing in.
572
586
  const signatureRef = useRef(signature.plainText);
573
587
  signatureRef.current = signature.plainText;
588
+ const seedRef = useRef(seed);
589
+ seedRef.current = seed;
574
590
  // The editor reads its document once, so this is the document it opens on,
575
591
  // not the live value, and it is remounted when the generation changes. Only
576
592
  // loading a different document bumps that — remounting mid-compose would take
577
593
  // the caret, the focus and the undo history with it.
578
594
  const [documentGeneration, setDocumentGeneration] = useState(0);
579
595
  const [initialHtml, setInitialHtml] = useState(
580
- () => freshDocument(signature.plainText).html,
596
+ () => freshDocument(signature.plainText, seed).html,
597
+ );
598
+ const [initialText, setInitialText] = useState(
599
+ () => freshDocument(signature.plainText, seed).text,
581
600
  );
582
- const [initialText, setInitialText] = useState(signature.plainText);
583
601
  const [bodyMode, setBodyMode] = useState<ComposeBodyMode>("rich");
584
602
  // What the body is tagged with on the way out. The composer owns the value —
585
603
  // it is the surface that has the text detection reads — and reports it here,
@@ -587,9 +605,18 @@ export const ComposeForm = ({
587
605
  const [composeLanguage, setComposeLanguage] = useState("en");
588
606
  const [draftLanguage, setDraftLanguage] = useState<string | undefined>();
589
607
  const [body, setBody] = useState<RichTextValue>(() => ({
590
- ...freshDocument(signature.plainText),
608
+ ...freshDocument(signature.plainText, seed),
591
609
  formatting: [],
592
610
  }));
611
+ const [insertedVersion, setInsertedVersion] = useState(
612
+ () => insertion?.version ?? 0,
613
+ );
614
+ const pendingInsertion =
615
+ insertion &&
616
+ insertion.version > insertedVersion &&
617
+ (outboxMessageId === undefined || draftLoaded)
618
+ ? insertion
619
+ : undefined;
593
620
 
594
621
  const { data: draftData, error: draftError } = useQuery({
595
622
  ...outboxDetailOperationsGetOutboxMessageOptions({
@@ -1352,6 +1379,8 @@ export const ComposeForm = ({
1352
1379
  initialLanguage={draftLanguage}
1353
1380
  onLanguageChange={setComposeLanguage}
1354
1381
  spellcheck={spellcheck}
1382
+ insertion={pendingInsertion}
1383
+ onInserted={setInsertedVersion}
1355
1384
  />
1356
1385
  </Suspense>
1357
1386
  </ComposeFormShell>
@@ -9,6 +9,7 @@ import {
9
9
  useContext,
10
10
  useEffect,
11
11
  useMemo,
12
+ useRef,
12
13
  useState,
13
14
  } from "react";
14
15
  import { isNotFound, softErrorStatuses } from "@/lib/error-classifier";
@@ -30,8 +31,15 @@ export type ComposeMode = ReplyMode | "new";
30
31
  * the address's answers — `useComposeDraftId` and `useIsComposing`. A second
31
32
  * copy here is what let a reply's draft turn up in the next new message.
32
33
  */
34
+ type InsertIntoReply = (text: string) => void;
35
+
33
36
  interface ComposeContextValue {
34
37
  startSendPolling: (outboxMessageId: string) => void;
38
+ insertIntoOpenReply: (sourceMessageId: string, text: string) => boolean;
39
+ registerOpenReply: (
40
+ sourceMessageId: string,
41
+ insert: InsertIntoReply,
42
+ ) => () => void;
35
43
  }
36
44
 
37
45
  const ComposeContext = createContext<ComposeContextValue | undefined>(
@@ -124,7 +132,33 @@ export const ComposeProvider = ({
124
132
  setPollingMessageId(outboxMessageId);
125
133
  }, []);
126
134
 
127
- const value = useMemo(() => ({ startSendPolling }), [startSendPolling]);
135
+ const openReplies = useRef(new Map<string, InsertIntoReply>());
136
+
137
+ const insertIntoOpenReply = useCallback(
138
+ (sourceMessageId: string, text: string): boolean => {
139
+ const insert = openReplies.current.get(sourceMessageId);
140
+ if (!insert) return false;
141
+ insert(text);
142
+ return true;
143
+ },
144
+ [],
145
+ );
146
+
147
+ const registerOpenReply = useCallback(
148
+ (sourceMessageId: string, insert: InsertIntoReply) => {
149
+ openReplies.current.set(sourceMessageId, insert);
150
+ return () => {
151
+ if (openReplies.current.get(sourceMessageId) === insert)
152
+ openReplies.current.delete(sourceMessageId);
153
+ };
154
+ },
155
+ [],
156
+ );
157
+
158
+ const value = useMemo(
159
+ () => ({ startSendPolling, insertIntoOpenReply, registerOpenReply }),
160
+ [startSendPolling, insertIntoOpenReply, registerOpenReply],
161
+ );
128
162
 
129
163
  return (
130
164
  <ComposeContext.Provider value={value}>{children}</ComposeContext.Provider>
@@ -2,7 +2,9 @@ import {
2
2
  configOperationsGetConfigOptions,
3
3
  messageOperationsDescribeMessageOptions,
4
4
  } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
5
+ import type { ComposeInsertion } from "@remit/ui/rich-text";
5
6
  import { useQuery } from "@tanstack/react-query";
7
+ import { useCallback, useEffect, useState } from "react";
6
8
  import { useMailboxAccount } from "@/hooks/useMailboxAccount";
7
9
  import {
8
10
  type ReplyAddress,
@@ -10,6 +12,7 @@ import {
10
12
  useCloseReply,
11
13
  } from "@/routing";
12
14
  import { ComposeForm } from "./ComposeForm";
15
+ import { useCompose } from "./ComposeProvider";
13
16
 
14
17
  /**
15
18
  * The answer to a message, as the top block of the conversation it answers.
@@ -27,6 +30,19 @@ import { ComposeForm } from "./ComposeForm";
27
30
  export const ConversationCompose = ({ surface }: { surface: ReplyAddress }) => {
28
31
  const adoptCreatedDraft = useAdoptReplyDraft();
29
32
  const closeReply = useCloseReply();
33
+ const { registerOpenReply } = useCompose();
34
+ const [insertion, setInsertion] = useState<ComposeInsertion>();
35
+ const insert = useCallback(
36
+ (text: string) =>
37
+ setInsertion((prev) => ({ version: (prev?.version ?? 0) + 1, text })),
38
+ [],
39
+ );
40
+ const answering = surface.sourceMessageId;
41
+
42
+ useEffect(
43
+ () => registerOpenReply(answering, insert),
44
+ [answering, insert, registerOpenReply],
45
+ );
30
46
 
31
47
  const { data: sourceMessage } = useQuery({
32
48
  ...messageOperationsDescribeMessageOptions({
@@ -56,11 +72,14 @@ export const ConversationCompose = ({ surface }: { surface: ReplyAddress }) => {
56
72
  the address, and remounting the form on that would take the caret out
57
73
  of the sentence being typed. */}
58
74
  <ComposeForm
75
+ key={surface.sourceMessageId}
59
76
  layout="flow"
60
77
  mode={surface.mode}
61
78
  account={account}
62
79
  sourceMessage={sourceMessage}
63
80
  outboxMessageId={surface.outboxMessageId}
81
+ seed={surface.seed}
82
+ insertion={insertion}
64
83
  onDraftCreated={adoptCreatedDraft}
65
84
  onClose={closeReply}
66
85
  />
@@ -174,6 +174,19 @@ export const Reply: Story = {
174
174
  },
175
175
  };
176
176
 
177
+ const OFFERED_TIMES = "Thursday 11 June: 12:30 – 13:00, 15:00 – 15:30";
178
+
179
+ export const ReplyWithOfferedTimes: Story = {
180
+ args: {
181
+ url: `${messageUrl("lunch")}/reply?body=${encodeURIComponent(OFFERED_TIMES)}`,
182
+ },
183
+ play: async () => {
184
+ const subject = await page().findByPlaceholderText("Subject");
185
+ await waitFor(() => expect(subject).toHaveValue(`Re: ${LUNCH_SUBJECT}`));
186
+ await expect(await writingSurface(OFFERED_TIMES)).toBeVisible();
187
+ },
188
+ };
189
+
177
190
  export const Forward: Story = {
178
191
  args: { url: `${messageUrl("lunch")}/forward` },
179
192
  play: async () => {
@@ -0,0 +1,22 @@
1
+ import { useCallback } from "react";
2
+ import { useOpenReply } from "@/routing";
3
+ import { useCompose } from "./ComposeProvider";
4
+
5
+ export interface AnsweredMessage {
6
+ threadId: string;
7
+ messageId: string;
8
+ }
9
+
10
+ export type ReplyWithText = (message: AnsweredMessage, text: string) => void;
11
+
12
+ export function useReplyWithText(): ReplyWithText {
13
+ const { insertIntoOpenReply } = useCompose();
14
+ const openReply = useOpenReply();
15
+ return useCallback(
16
+ (message: AnsweredMessage, text: string) => {
17
+ if (insertIntoOpenReply(message.messageId, text)) return;
18
+ openReply({ ...message, mode: "reply" }, { body: text });
19
+ },
20
+ [insertIntoOpenReply, openReply],
21
+ );
22
+ }
@@ -23,6 +23,7 @@ import {
23
23
  useContext,
24
24
  useMemo,
25
25
  } from "react";
26
+ import { useReplyWithText } from "@/components/compose/reply-with-times";
26
27
  import { ConversationView } from "@/components/mail/ConversationView";
27
28
  import { DailyBrief } from "@/components/mail/DailyBrief";
28
29
  import { IntelligenceDrawer } from "@/components/mail/IntelligenceDrawer";
@@ -317,6 +318,7 @@ function BriefList() {
317
318
  * Mount in the `reading` slot of `AppShellSlotted`. Only rendered ≥ 1024px.
318
319
  */
319
320
  function BriefReading() {
321
+ const replyWithText = useReplyWithText();
320
322
  const {
321
323
  conversation,
322
324
  selectedThread,
@@ -374,6 +376,7 @@ function BriefReading() {
374
376
  mailboxId={selectedThread?.mailboxId}
375
377
  accountId={selectedThread?.accountId}
376
378
  onAfterOptimisticRemove={handleDeselectIfRemoved}
379
+ onReplyWithText={replyWithText}
377
380
  />
378
381
  </>
379
382
  );
@@ -384,6 +387,7 @@ function BriefReading() {
384
387
  * Mount in the `intelligence` slot of `AppShellSlotted`. Only rendered ≥ 1280px.
385
388
  */
386
389
  function BriefIntelligence() {
390
+ const replyWithText = useReplyWithText();
387
391
  const { selectedThread, handleDeselectIfRemoved } = useBriefPane();
388
392
  const { onToggleIntelligence } = useMailContext();
389
393
 
@@ -394,6 +398,7 @@ function BriefIntelligence() {
394
398
  mailboxId={selectedThread?.mailboxId}
395
399
  accountId={selectedThread?.accountId}
396
400
  onAfterOptimisticRemove={handleDeselectIfRemoved}
401
+ onReplyWithText={replyWithText}
397
402
  />
398
403
  );
399
404
  }
@@ -402,6 +407,7 @@ function BriefIntelligence() {
402
407
  * Phone view: ConversationView when thread is open, or the DailyBrief list.
403
408
  */
404
409
  function BriefPhone() {
410
+ const replyWithText = useReplyWithText();
405
411
  const {
406
412
  selectedThread,
407
413
  conversation,
@@ -437,6 +443,7 @@ function BriefPhone() {
437
443
  mailboxId={selectedThread?.mailboxId}
438
444
  accountId={selectedThread?.accountId}
439
445
  onAfterOptimisticRemove={handleDeselectIfRemoved}
446
+ onReplyWithText={replyWithText}
440
447
  />
441
448
  </>
442
449
  );
@@ -33,6 +33,7 @@ import {
33
33
  useContext,
34
34
  useMemo,
35
35
  } from "react";
36
+ import { useReplyWithText } from "@/components/compose/reply-with-times";
36
37
  import { ConversationView } from "@/components/mail/ConversationView";
37
38
  import { FlaggedList } from "@/components/mail/FlaggedList";
38
39
  import { IntelligenceDrawer } from "@/components/mail/IntelligenceDrawer";
@@ -320,6 +321,7 @@ function FlaggedListSlot() {
320
321
  * Mount in the `reading` slot of `AppShellSlotted`. Only rendered ≥ 1024px.
321
322
  */
322
323
  function FlaggedReading() {
324
+ const replyWithText = useReplyWithText();
323
325
  const {
324
326
  conversation,
325
327
  selectedThread,
@@ -377,6 +379,7 @@ function FlaggedReading() {
377
379
  mailboxId={selectedThread?.mailboxId}
378
380
  accountId={selectedThread?.accountId}
379
381
  onAfterOptimisticRemove={handleDeselectIfRemoved}
382
+ onReplyWithText={replyWithText}
380
383
  />
381
384
  </>
382
385
  );
@@ -387,6 +390,7 @@ function FlaggedReading() {
387
390
  * Mount in the `intelligence` slot of `AppShellSlotted`. Only rendered ≥ 1280px.
388
391
  */
389
392
  function FlaggedIntelligence() {
393
+ const replyWithText = useReplyWithText();
390
394
  const { selectedThread, handleDeselectIfRemoved } = useFlaggedPane();
391
395
  const { onToggleIntelligence } = useMailContext();
392
396
 
@@ -397,12 +401,14 @@ function FlaggedIntelligence() {
397
401
  mailboxId={selectedThread?.mailboxId}
398
402
  accountId={selectedThread?.accountId}
399
403
  onAfterOptimisticRemove={handleDeselectIfRemoved}
404
+ onReplyWithText={replyWithText}
400
405
  />
401
406
  );
402
407
  }
403
408
 
404
409
  /** Phone view: ConversationView when a thread is open, else the flat list. */
405
410
  function FlaggedPhone() {
411
+ const replyWithText = useReplyWithText();
406
412
  const {
407
413
  selectedThread,
408
414
  conversation,
@@ -438,6 +444,7 @@ function FlaggedPhone() {
438
444
  mailboxId={selectedThread?.mailboxId}
439
445
  accountId={selectedThread?.accountId}
440
446
  onAfterOptimisticRemove={handleDeselectIfRemoved}
447
+ onReplyWithText={replyWithText}
441
448
  />
442
449
  </>
443
450
  );
@@ -1,4 +1,5 @@
1
1
  import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
2
+ import type { ReplyWithText } from "@/components/compose/reply-with-times";
2
3
  import { Drawer } from "@/components/layout/Drawer";
3
4
  import { IntelligencePane } from "@/components/mail/IntelligencePane";
4
5
 
@@ -16,6 +17,7 @@ export function IntelligenceDrawer({
16
17
  mailboxId,
17
18
  accountId,
18
19
  onAfterOptimisticRemove,
20
+ onReplyWithText,
19
21
  }: {
20
22
  isOpen: boolean;
21
23
  onClose: () => void;
@@ -23,6 +25,7 @@ export function IntelligenceDrawer({
23
25
  mailboxId?: string;
24
26
  accountId?: string;
25
27
  onAfterOptimisticRemove?: (messageIds: string[]) => void;
28
+ onReplyWithText?: ReplyWithText;
26
29
  }) {
27
30
  return (
28
31
  <Drawer
@@ -41,6 +44,7 @@ export function IntelligenceDrawer({
41
44
  accountId={accountId}
42
45
  hideCloseButton
43
46
  onAfterOptimisticRemove={onAfterOptimisticRemove}
47
+ onReplyWithText={onReplyWithText}
44
48
  />
45
49
  </Drawer>
46
50
  );
@@ -20,6 +20,7 @@ import { isRescueCandidate } from "@/lib/rescue-candidates";
20
20
  import { recordRescueSentToJunk } from "@/lib/rescue-telemetry";
21
21
  import { useTelemetry } from "@/lib/telemetry-context";
22
22
  import { NavLink } from "@/routing";
23
+ import type { ReplyWithText } from "../compose/reply-with-times";
23
24
 
24
25
  export interface IntelligencePaneProps {
25
26
  onClose: () => void;
@@ -53,6 +54,7 @@ export interface IntelligencePaneProps {
53
54
  * occasional unneeded pane-close — not a bug; see `useReportSpam`.
54
55
  */
55
56
  onAfterOptimisticRemove?: (messageIds: string[]) => void;
57
+ onReplyWithText?: ReplyWithText;
56
58
  }
57
59
 
58
60
  /**
@@ -251,6 +253,7 @@ interface WiredPanelProps {
251
253
  accountId?: string;
252
254
  hideCloseButton?: boolean;
253
255
  onAfterOptimisticRemove?: (messageIds: string[]) => void;
256
+ onReplyWithText?: ReplyWithText;
254
257
  }
255
258
 
256
259
  /**
@@ -263,6 +266,7 @@ function WiredPanel({
263
266
  accountId,
264
267
  hideCloseButton,
265
268
  onAfterOptimisticRemove,
269
+ onReplyWithText,
266
270
  }: WiredPanelProps) {
267
271
  const {
268
272
  data,
@@ -273,7 +277,7 @@ function WiredPanel({
273
277
  similarErrorIsFatal,
274
278
  semanticEnabled,
275
279
  } = useIntelligenceData(thread, mailboxId);
276
- const calendar = useIntelligenceCalendar(thread);
280
+ const calendar = useIntelligenceCalendar(thread, onReplyWithText);
277
281
  const [reclassifyOpen, setReclassifyOpen] = useState(false);
278
282
  const senderEmail = thread.fromEmail ?? undefined;
279
283
 
@@ -432,6 +436,7 @@ export const IntelligencePane = ({
432
436
  accountId,
433
437
  hideCloseButton,
434
438
  onAfterOptimisticRemove,
439
+ onReplyWithText,
435
440
  }: IntelligencePaneProps) => {
436
441
  if (!thread) {
437
442
  return (
@@ -461,6 +466,7 @@ export const IntelligencePane = ({
461
466
  accountId={accountId}
462
467
  hideCloseButton={hideCloseButton}
463
468
  onAfterOptimisticRemove={onAfterOptimisticRemove}
469
+ onReplyWithText={onReplyWithText}
464
470
  />
465
471
  );
466
472
  };
@@ -45,6 +45,7 @@ import {
45
45
  useRef,
46
46
  useState,
47
47
  } from "react";
48
+ import { useReplyWithText } from "@/components/compose/reply-with-times";
48
49
  import { ConversationView } from "@/components/mail/ConversationView";
49
50
  import { DraftsView } from "@/components/mail/DraftsView";
50
51
  import { EmptyTrashBar } from "@/components/mail/EmptyTrashBar";
@@ -1115,6 +1116,7 @@ function MailboxList() {
1115
1116
  * Mount in the `reading` slot of `AppShellSlotted`. Only rendered ≥ 1024px.
1116
1117
  */
1117
1118
  function MailboxReading() {
1119
+ const replyWithText = useReplyWithText();
1118
1120
  const {
1119
1121
  mailboxId,
1120
1122
  mailboxAccountId,
@@ -1204,6 +1206,7 @@ function MailboxReading() {
1204
1206
  mailboxId={mailboxId}
1205
1207
  accountId={mailboxAccountId}
1206
1208
  onAfterOptimisticRemove={handleDeselectIfRemoved}
1209
+ onReplyWithText={replyWithText}
1207
1210
  />
1208
1211
  </>
1209
1212
  );
@@ -1214,6 +1217,7 @@ function MailboxReading() {
1214
1217
  * Mount in the `intelligence` slot of `AppShellSlotted`. Only rendered ≥ 1280px.
1215
1218
  */
1216
1219
  function MailboxIntelligence() {
1220
+ const replyWithText = useReplyWithText();
1217
1221
  const {
1218
1222
  mailboxId,
1219
1223
  mailboxAccountId,
@@ -1229,6 +1233,7 @@ function MailboxIntelligence() {
1229
1233
  mailboxId={mailboxId}
1230
1234
  accountId={mailboxAccountId}
1231
1235
  onAfterOptimisticRemove={handleDeselectIfRemoved}
1236
+ onReplyWithText={replyWithText}
1232
1237
  />
1233
1238
  );
1234
1239
  }
@@ -1238,6 +1243,7 @@ function MailboxIntelligence() {
1238
1243
  * Use this on phones instead of the slot sub-views.
1239
1244
  */
1240
1245
  function MailboxPhone() {
1246
+ const replyWithText = useReplyWithText();
1241
1247
  const {
1242
1248
  mailboxId,
1243
1249
  mailboxAccountId,
@@ -1281,6 +1287,7 @@ function MailboxPhone() {
1281
1287
  mailboxId={mailboxId}
1282
1288
  accountId={mailboxAccountId}
1283
1289
  onAfterOptimisticRemove={handleDeselectIfRemoved}
1290
+ onReplyWithText={replyWithText}
1284
1291
  />
1285
1292
  </>
1286
1293
  );