@remit/web-client 0.0.89 → 0.0.91
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 +1 -1
- package/src/components/mail/DailyBrief.selection.test.ts +19 -15
- package/src/components/mail/DailyBrief.tsx +70 -89
- package/src/components/mail/FlaggedList.tsx +58 -50
- package/src/components/mail/LabelApplyTrigger.tsx +11 -5
- package/src/components/mail/MailListHeader.tsx +165 -85
- package/src/components/mail/MailViewChrome.tsx +7 -6
- package/src/components/mail/MessageList.selection.test.ts +34 -36
- package/src/components/mail/MessageList.tsx +64 -96
- package/src/components/mail/MoveToTrigger.tsx +1 -1
- package/src/components/mail/ThreadListInteraction.test.ts +8 -2
- package/src/components/mail/ThreadListInteraction.tsx +47 -24
- package/src/hooks/useInitialSyncProgress.render.test.ts +11 -5
- package/src/hooks/useSelectedSubjects.ts +0 -0
- package/src/lib/list-header-chrome.ts +50 -0
- package/src/components/mail/SelectionToolbar.render.test.ts +0 -324
- package/src/components/mail/SelectionToolbar.stories.tsx +0 -199
- package/src/components/mail/SelectionToolbar.tsx +0 -274
- package/src/lib/selection-sheet-mode.test.ts +0 -105
- package/src/lib/selection-sheet-mode.ts +0 -49
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.91",
|
|
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": {
|
|
@@ -5,10 +5,9 @@ import { describe, it } from "node:test";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* The brief raises
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* desktop (#453).
|
|
8
|
+
* The brief raises one selection surface at every width, and select-all is on
|
|
9
|
+
* it — wired into the touch sheet alone, the same list could be selected whole
|
|
10
|
+
* on a phone and not on a desktop (#453).
|
|
12
11
|
*
|
|
13
12
|
* As with `MessageList.selection.test.ts`, `DailyBrief` weaves routing, the
|
|
14
13
|
* cursor and several data hooks together, so the rule is enforced by reading
|
|
@@ -27,23 +26,28 @@ const chromeBody = (): string => {
|
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
describe("the brief's select-all", () => {
|
|
30
|
-
it("reaches the
|
|
29
|
+
it("reaches the one selection bar from one derivation", () => {
|
|
31
30
|
const body = chromeBody();
|
|
32
31
|
const derivations = body.match(/const selectAll = useMemo\(/g) ?? [];
|
|
33
|
-
assert.equal(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"one select-all, so the two surfaces cannot drift",
|
|
37
|
-
);
|
|
32
|
+
assert.equal(derivations.length, 1, "one select-all");
|
|
33
|
+
const bars = body.match(/<SelectionTopBar\b/g) ?? [];
|
|
34
|
+
assert.equal(bars.length, 1, "one surface, at every width (#480)");
|
|
38
35
|
const wired = body.match(/selectAll=\{selectAll\}/g) ?? [];
|
|
39
|
-
assert.equal(
|
|
40
|
-
wired.length,
|
|
41
|
-
2,
|
|
42
|
-
"both SelectionToolbar and SelectionSheet take it",
|
|
43
|
-
);
|
|
36
|
+
assert.equal(wired.length, 1, "the bar takes it");
|
|
44
37
|
});
|
|
45
38
|
|
|
46
39
|
it("is offered only when there are rows to select", () => {
|
|
47
40
|
assert.match(chromeBody(), /orderedIds\.length > 0/);
|
|
48
41
|
});
|
|
42
|
+
|
|
43
|
+
it("mounts the bar unconditionally — it is the list header (#480)", () => {
|
|
44
|
+
assert.match(
|
|
45
|
+
chromeBody(),
|
|
46
|
+
/const selectionBar = \(chrome: ListHeaderChrome\) => \(\s*<SelectionTopBar/,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("keeps the free-text organize entry reachable, in the overflow", () => {
|
|
51
|
+
assert.match(chromeBody(), /onSomethingElse=\{/);
|
|
52
|
+
});
|
|
49
53
|
});
|
|
@@ -16,9 +16,8 @@
|
|
|
16
16
|
* the phone search takeover keeps its own copy of the filter sheet.
|
|
17
17
|
*
|
|
18
18
|
* Multi-select is the mailbox list's, not a copy of it: the same
|
|
19
|
-
* `ThreadListInteraction` cursor and `useSelection` state, the same
|
|
20
|
-
* `
|
|
21
|
-
* peeking `SelectionSheet` on touch. A shift-range spans the rendered rows in
|
|
19
|
+
* `ThreadListInteraction` cursor and `useSelection` state, and the same
|
|
20
|
+
* `SelectionTopBar` in place of the pane header. A shift-range spans the rendered rows in
|
|
22
21
|
* document order, so it crosses category sections exactly as the eye reads
|
|
23
22
|
* them and never picks up a row hidden behind "Show N more" or a collapsed
|
|
24
23
|
* header. Folder-scoped verbs (Move, Apply label, Organize) resolve their
|
|
@@ -43,9 +42,8 @@ import {
|
|
|
43
42
|
type FilterSheetProps,
|
|
44
43
|
type FilterSheetSource,
|
|
45
44
|
KeyboardHintBar,
|
|
46
|
-
SELECTION_SHEET_TEASER_HEIGHT,
|
|
47
45
|
type SearchResult,
|
|
48
|
-
|
|
46
|
+
SelectionTopBar,
|
|
49
47
|
type ThreadRowData,
|
|
50
48
|
type ThreadSection,
|
|
51
49
|
} from "@remit/ui";
|
|
@@ -65,6 +63,7 @@ import {
|
|
|
65
63
|
isSyncingPhase,
|
|
66
64
|
useInitialSyncProgress,
|
|
67
65
|
} from "@/hooks/useInitialSyncProgress";
|
|
66
|
+
import { useLabelList } from "@/hooks/useLabels";
|
|
68
67
|
import { useIsDesktop } from "@/hooks/useMediaQuery";
|
|
69
68
|
import { useMoveMessages } from "@/hooks/useMoveMessages";
|
|
70
69
|
import { useSearchTokenContext } from "@/hooks/useSearchTokenContext";
|
|
@@ -80,17 +79,17 @@ import {
|
|
|
80
79
|
toThreadRowData,
|
|
81
80
|
} from "@/lib/brief";
|
|
82
81
|
import { isServerError } from "@/lib/error-classifier";
|
|
82
|
+
import type { ListHeaderChrome } from "@/lib/list-header-chrome";
|
|
83
83
|
import { useMailContext } from "@/lib/mail-context";
|
|
84
84
|
import { relatedSearchResults, rowToSearchResult } from "@/lib/search-result";
|
|
85
85
|
import { parseSearchTokens } from "@/lib/search-tokens";
|
|
86
|
-
import {
|
|
86
|
+
import { LabelApplyTrigger } from "./LabelApplyTrigger";
|
|
87
87
|
import { MailListHeader, type MailListHeaderProps } from "./MailListHeader";
|
|
88
88
|
import type { MessageListCommands } from "./MessageList";
|
|
89
89
|
import { MessageRow } from "./MessageRow";
|
|
90
90
|
import { MoveToTrigger } from "./MoveToTrigger";
|
|
91
91
|
import { MobileOrganizeFlow } from "./organize/MobileOrganizeFlow";
|
|
92
92
|
import { OrganizeDialog } from "./organize/OrganizeDialog";
|
|
93
|
-
import { SelectionToolbar } from "./SelectionToolbar";
|
|
94
93
|
import {
|
|
95
94
|
type OpenMessageOptions,
|
|
96
95
|
ThreadListInteraction,
|
|
@@ -290,7 +289,7 @@ interface BriefSelectionChromeProps {
|
|
|
290
289
|
/** Everything the header needs when no selection is active. */
|
|
291
290
|
header: Omit<
|
|
292
291
|
MailListHeaderProps,
|
|
293
|
-
"children" | "selectionBar" | "
|
|
292
|
+
"children" | "selectionBar" | "paneOverlay"
|
|
294
293
|
>;
|
|
295
294
|
/** The rows the list is showing, for resolving the selection's scope. */
|
|
296
295
|
rows: readonly ThreadRowData[];
|
|
@@ -300,10 +299,9 @@ interface BriefSelectionChromeProps {
|
|
|
300
299
|
}
|
|
301
300
|
|
|
302
301
|
/**
|
|
303
|
-
* The brief's selection chrome: the same
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
* or more selected. The selection itself comes from the enclosing
|
|
302
|
+
* The brief's selection chrome: the same surface the mailbox list raises, in
|
|
303
|
+
* the same place. The pane header carries the count and the verbs from the
|
|
304
|
+
* first ticked row. The selection itself comes from the enclosing
|
|
307
305
|
* `ThreadListInteraction`, so the brief and the mailbox list run one model.
|
|
308
306
|
*/
|
|
309
307
|
function BriefSelectionChrome({
|
|
@@ -328,6 +326,7 @@ function BriefSelectionChrome({
|
|
|
328
326
|
[rows, selectedIds],
|
|
329
327
|
);
|
|
330
328
|
const { junkMailboxId } = useJunkMailbox(scope.accountId);
|
|
329
|
+
const { labels } = useLabelList(scope.accountId);
|
|
331
330
|
const { moveMessages, isPending: isMoving } = useMoveMessages({
|
|
332
331
|
mailboxId: scope.mailboxId ?? "",
|
|
333
332
|
accountId: scope.accountId,
|
|
@@ -395,33 +394,65 @@ function BriefSelectionChrome({
|
|
|
395
394
|
[orderedIds.length, allSelected, selectedCount, toggleAllLoaded],
|
|
396
395
|
);
|
|
397
396
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
<SelectionToolbar
|
|
401
|
-
selectedCount={selectedCount}
|
|
402
|
-
onDelete={requestDeleteSelection}
|
|
403
|
-
onClearSelection={exitSelection}
|
|
404
|
-
onMarkAsRead={onMarkMessagesRead ? handleMarkAsRead : undefined}
|
|
405
|
-
onMove={scoped ? handleMove : undefined}
|
|
406
|
-
onOrganize={() => setOrganizeOpen(true)}
|
|
407
|
-
isMoving={isMoving}
|
|
408
|
-
accountId={scope.accountId}
|
|
409
|
-
currentMailboxId={scope.mailboxId}
|
|
410
|
-
selectedMessageIds={selectedMessageIds}
|
|
411
|
-
moveDisabledHint={scope.moveDisabledHint}
|
|
412
|
-
selectAll={selectAll}
|
|
413
|
-
/>
|
|
414
|
-
) : undefined;
|
|
415
|
-
|
|
416
|
-
// The mobile sheet has no inline hint slot, so the scope restriction rides
|
|
417
|
-
// in as a notice — the same fold-in the mailbox list does.
|
|
397
|
+
// The bar shows one notice at a time, so the scope restriction rides in as
|
|
398
|
+
// a notice — the same fold-in the mailbox list does.
|
|
418
399
|
const notice = scope.moveDisabledHint
|
|
419
400
|
? { tone: "warning" as const, text: scope.moveDisabledHint }
|
|
420
401
|
: undefined;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
402
|
+
|
|
403
|
+
const selectionBar = (chrome: ListHeaderChrome) => (
|
|
404
|
+
<SelectionTopBar
|
|
405
|
+
title={chrome.title}
|
|
406
|
+
navSlot={chrome.navSlot}
|
|
407
|
+
titleMeta={chrome.titleMeta}
|
|
408
|
+
searchSlot={chrome.searchSlot}
|
|
409
|
+
searchField={chrome.searchField}
|
|
410
|
+
idleSlot={chrome.makeFilterSlot}
|
|
411
|
+
count={selectedCount}
|
|
412
|
+
onCancel={exitSelection}
|
|
413
|
+
onDelete={requestDeleteSelection}
|
|
414
|
+
onOrganize={
|
|
415
|
+
scoped
|
|
416
|
+
? () =>
|
|
417
|
+
isDesktop
|
|
418
|
+
? setOrganizeOpen(true)
|
|
419
|
+
: setMobileOrganizeEntry("select-similar")
|
|
420
|
+
: undefined
|
|
421
|
+
}
|
|
422
|
+
onJunk={canJunk ? handleJunk : undefined}
|
|
423
|
+
onMarkRead={onMarkMessagesRead ? handleMarkAsRead : undefined}
|
|
424
|
+
onSomethingElse={
|
|
425
|
+
scoped && !isDesktop
|
|
426
|
+
? () => setMobileOrganizeEntry("something-else")
|
|
427
|
+
: undefined
|
|
428
|
+
}
|
|
429
|
+
moveSlot={
|
|
430
|
+
scoped && scope.accountId && scope.mailboxId ? (
|
|
431
|
+
<MoveToTrigger
|
|
432
|
+
accountId={scope.accountId}
|
|
433
|
+
currentMailboxId={scope.mailboxId}
|
|
434
|
+
onMove={isMoving ? () => {} : handleMove}
|
|
435
|
+
label="Move selected messages"
|
|
436
|
+
/>
|
|
437
|
+
) : undefined
|
|
438
|
+
}
|
|
439
|
+
overflowSlot={
|
|
440
|
+
scope.accountId &&
|
|
441
|
+
scope.mailboxId &&
|
|
442
|
+
selectedCount > 0 &&
|
|
443
|
+
labels.length > 0 ? (
|
|
444
|
+
<LabelApplyTrigger
|
|
445
|
+
accountId={scope.accountId}
|
|
446
|
+
mailboxId={scope.mailboxId}
|
|
447
|
+
messageIds={selectedMessageIds}
|
|
448
|
+
/>
|
|
449
|
+
) : undefined
|
|
450
|
+
}
|
|
451
|
+
isBusy={isMoving}
|
|
452
|
+
selectAll={selectAll}
|
|
453
|
+
notice={notice}
|
|
454
|
+
/>
|
|
455
|
+
);
|
|
425
456
|
|
|
426
457
|
const organizeFlow =
|
|
427
458
|
mobileOrganizeEntry &&
|
|
@@ -441,64 +472,14 @@ function BriefSelectionChrome({
|
|
|
441
472
|
/>
|
|
442
473
|
) : undefined;
|
|
443
474
|
|
|
444
|
-
const selectionSheet =
|
|
445
|
-
showSheet || organizeFlow ? (
|
|
446
|
-
<>
|
|
447
|
-
{showSheet && (
|
|
448
|
-
<SelectionSheet
|
|
449
|
-
count={selectedCount}
|
|
450
|
-
onCancel={exitSelection}
|
|
451
|
-
onDelete={requestDeleteSelection}
|
|
452
|
-
onJunk={canJunk ? handleJunk : undefined}
|
|
453
|
-
onMarkRead={onMarkMessagesRead ? handleMarkAsRead : undefined}
|
|
454
|
-
onSelectSimilar={
|
|
455
|
-
scoped
|
|
456
|
-
? () => setMobileOrganizeEntry("select-similar")
|
|
457
|
-
: undefined
|
|
458
|
-
}
|
|
459
|
-
onSomethingElse={
|
|
460
|
-
scoped
|
|
461
|
-
? () => setMobileOrganizeEntry("something-else")
|
|
462
|
-
: undefined
|
|
463
|
-
}
|
|
464
|
-
moveSlot={
|
|
465
|
-
scoped && scope.accountId && scope.mailboxId ? (
|
|
466
|
-
<MoveToTrigger
|
|
467
|
-
accountId={scope.accountId}
|
|
468
|
-
currentMailboxId={scope.mailboxId}
|
|
469
|
-
onMove={isMoving ? () => {} : handleMove}
|
|
470
|
-
label="Move selected messages"
|
|
471
|
-
/>
|
|
472
|
-
) : undefined
|
|
473
|
-
}
|
|
474
|
-
isBusy={isMoving}
|
|
475
|
-
selectAll={selectAll}
|
|
476
|
-
notice={notice}
|
|
477
|
-
/>
|
|
478
|
-
)}
|
|
479
|
-
{organizeFlow}
|
|
480
|
-
</>
|
|
481
|
-
) : undefined;
|
|
482
|
-
|
|
483
475
|
return (
|
|
484
476
|
<>
|
|
485
477
|
<MailListHeader
|
|
486
478
|
{...header}
|
|
487
479
|
selectionBar={selectionBar}
|
|
488
|
-
|
|
480
|
+
paneOverlay={organizeFlow}
|
|
489
481
|
>
|
|
490
|
-
{
|
|
491
|
-
last rows hide behind it. */}
|
|
492
|
-
<div
|
|
493
|
-
className="h-full"
|
|
494
|
-
style={
|
|
495
|
-
showSheet
|
|
496
|
-
? { paddingBottom: SELECTION_SHEET_TEASER_HEIGHT }
|
|
497
|
-
: undefined
|
|
498
|
-
}
|
|
499
|
-
>
|
|
500
|
-
{children}
|
|
501
|
-
</div>
|
|
482
|
+
{children}
|
|
502
483
|
</MailListHeader>
|
|
503
484
|
{organizeOpen && scope.accountId && (
|
|
504
485
|
<OrganizeDialog
|
|
@@ -531,7 +512,7 @@ interface DailyBriefProps {
|
|
|
531
512
|
commandsRef?: RefObject<MessageListCommands | null>;
|
|
532
513
|
/** Cursor / selection / display order, reported up to the triage layer. */
|
|
533
514
|
onTriageContextChange?: (context: TriageContextUpdate) => void;
|
|
534
|
-
onDeleteMessages
|
|
515
|
+
onDeleteMessages: (messageIds: string[]) => void;
|
|
535
516
|
onMarkMessagesRead?: (messageIds: string[]) => void;
|
|
536
517
|
}
|
|
537
518
|
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
toThreadRowData,
|
|
31
31
|
} from "@/lib/brief";
|
|
32
32
|
import { buildBugReportContext, buildGitHubIssueUrl } from "@/lib/bug-report";
|
|
33
|
+
import { useListHeaderChrome } from "@/lib/list-header-chrome";
|
|
33
34
|
import { useMailContext } from "@/lib/mail-context";
|
|
34
35
|
import { rowToSearchResult } from "@/lib/search-result";
|
|
35
36
|
import { parseSearchTokens } from "@/lib/search-tokens";
|
|
@@ -55,7 +56,7 @@ interface FlaggedListProps {
|
|
|
55
56
|
commandsRef?: RefObject<MessageListCommands | null>;
|
|
56
57
|
/** Cursor / selection / display order, reported up to the triage layer. */
|
|
57
58
|
onTriageContextChange?: (context: TriageContextUpdate) => void;
|
|
58
|
-
onDeleteMessages
|
|
59
|
+
onDeleteMessages: (messageIds: string[]) => void;
|
|
59
60
|
onMarkMessagesRead?: (messageIds: string[]) => void;
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -146,42 +147,32 @@ export function FlaggedList({
|
|
|
146
147
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
147
148
|
}, []);
|
|
148
149
|
|
|
150
|
+
const chrome = useListHeaderChrome();
|
|
149
151
|
const listBody = (
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<div className="flex-1 overflow-y-auto">
|
|
162
|
-
<div className="divide-y divide-line">
|
|
163
|
-
{rows.map((thread) => (
|
|
164
|
-
<MessageRow
|
|
165
|
-
key={thread.id}
|
|
166
|
-
thread={thread}
|
|
167
|
-
active={thread.id === selectedMessageId}
|
|
168
|
-
onClick={() => onSelectMessage?.(thread.id)}
|
|
169
|
-
/>
|
|
170
|
-
))}
|
|
171
|
-
</div>
|
|
172
|
-
{hasNextPage ? (
|
|
173
|
-
<button
|
|
174
|
-
type="button"
|
|
175
|
-
className="w-full py-3 text-sm text-muted hover:text-fg disabled:opacity-50"
|
|
176
|
-
onClick={() => fetchNextPage()}
|
|
177
|
-
disabled={isFetchingNextPage}
|
|
178
|
-
>
|
|
179
|
-
{isFetchingNextPage ? "Loading…" : "Load more"}
|
|
180
|
-
</button>
|
|
181
|
-
) : null}
|
|
152
|
+
<div className="flex h-full min-h-0 flex-col">
|
|
153
|
+
<div className="flex-1 overflow-y-auto">
|
|
154
|
+
<div className="divide-y divide-line">
|
|
155
|
+
{rows.map((thread) => (
|
|
156
|
+
<MessageRow
|
|
157
|
+
key={thread.id}
|
|
158
|
+
thread={thread}
|
|
159
|
+
active={thread.id === selectedMessageId}
|
|
160
|
+
onClick={() => onSelectMessage?.(thread.id)}
|
|
161
|
+
/>
|
|
162
|
+
))}
|
|
182
163
|
</div>
|
|
164
|
+
{hasNextPage ? (
|
|
165
|
+
<button
|
|
166
|
+
type="button"
|
|
167
|
+
className="w-full py-3 text-sm text-muted hover:text-fg disabled:opacity-50"
|
|
168
|
+
onClick={() => fetchNextPage()}
|
|
169
|
+
disabled={isFetchingNextPage}
|
|
170
|
+
>
|
|
171
|
+
{isFetchingNextPage ? "Loading…" : "Load more"}
|
|
172
|
+
</button>
|
|
173
|
+
) : null}
|
|
183
174
|
</div>
|
|
184
|
-
</
|
|
175
|
+
</div>
|
|
185
176
|
);
|
|
186
177
|
|
|
187
178
|
return (
|
|
@@ -198,22 +189,39 @@ export function FlaggedList({
|
|
|
198
189
|
searchLoading={isLoading}
|
|
199
190
|
onSelectSearchResult={(result) => onSelectMessage?.(result.id)}
|
|
200
191
|
>
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
192
|
+
<ThreadListInteraction
|
|
193
|
+
selectedMessageId={selectedMessageId}
|
|
194
|
+
onOpen={(id, options) => onSelectMessage?.(id, options)}
|
|
195
|
+
onDeleteMessages={onDeleteMessages}
|
|
196
|
+
commandsRef={commandsRef}
|
|
197
|
+
onTriageContextChange={onTriageContextChange}
|
|
198
|
+
>
|
|
199
|
+
<MessageListPane
|
|
200
|
+
listTitle="Starred"
|
|
201
|
+
sections={[{ id: "flagged", threads: rows }]}
|
|
202
|
+
flatList
|
|
203
|
+
hideHeader
|
|
204
|
+
listState={chrome.searchResults ? "ready" : listState}
|
|
205
|
+
searchQuery={sq ? searchQuery : undefined}
|
|
206
|
+
errorMessage={isError ? formatErrorMessage(error) : undefined}
|
|
207
|
+
onRetry={() => refetch()}
|
|
208
|
+
onReportError={handleReportError}
|
|
209
|
+
selectedThreadId={selectedMessageId}
|
|
210
|
+
onSelectThread={onSelectMessage}
|
|
211
|
+
onSelectBriefCategory={() => undefined}
|
|
212
|
+
isDesktop={isDesktop}
|
|
213
|
+
selectionBar={
|
|
214
|
+
<ThreadListSelectionBar
|
|
215
|
+
title="Starred"
|
|
216
|
+
onMarkAsRead={onMarkMessagesRead}
|
|
217
|
+
/>
|
|
218
|
+
}
|
|
219
|
+
listBody={
|
|
220
|
+
chrome.searchResults ??
|
|
221
|
+
(listState === "ready" ? listBody : undefined)
|
|
222
|
+
}
|
|
223
|
+
/>
|
|
224
|
+
</ThreadListInteraction>
|
|
217
225
|
</MailViewChrome>
|
|
218
226
|
);
|
|
219
227
|
}
|
|
@@ -17,10 +17,11 @@ interface LabelApplyTriggerProps {
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* "Apply label" for a selection — the manual "just these" scope (issue #26,
|
|
20
|
-
* RFC 034 recap). A
|
|
21
|
-
*
|
|
22
|
-
* no labels yet — an empty menu
|
|
23
|
-
*
|
|
20
|
+
* RFC 034 recap). A worded row at the foot of the selection bar's overflow
|
|
21
|
+
* menu, opening the account's labels; picking one applies it to every selected
|
|
22
|
+
* message. Renders nothing when the account has no labels yet — an empty menu
|
|
23
|
+
* offering only "create one in Settings" is dead weight; Settings › Labels is
|
|
24
|
+
* where creation lives.
|
|
24
25
|
*/
|
|
25
26
|
export function LabelApplyTrigger({
|
|
26
27
|
accountId,
|
|
@@ -54,8 +55,13 @@ export function LabelApplyTrigger({
|
|
|
54
55
|
return (
|
|
55
56
|
<PopoverMenu
|
|
56
57
|
triggerLabel="Apply label to selected messages"
|
|
57
|
-
triggerIcon={<Tag className="size-4" />}
|
|
58
|
+
triggerIcon={<Tag className="size-4 text-fg-subtle" />}
|
|
59
|
+
triggerText="Apply label"
|
|
58
60
|
items={items}
|
|
61
|
+
align="start"
|
|
62
|
+
nested
|
|
63
|
+
touch={false}
|
|
64
|
+
triggerClassName="min-h-11 w-full justify-start gap-3 px-4 py-2.5 text-sm font-normal text-fg"
|
|
59
65
|
/>
|
|
60
66
|
);
|
|
61
67
|
}
|