@rapidmx/web-client 0.15.1 → 0.17.0
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 +381 -373
- package/apps/admin/branding/index.tsx +39 -39
- package/apps/admin/data-requests/index.tsx +482 -480
- package/apps/admin/distribution-lists/new/index.tsx +3 -1
- package/apps/admin/domains/[uid].tsx +166 -164
- package/apps/admin/domains/new/index.tsx +3 -1
- package/apps/admin/escrow-scopes/[uid].tsx +350 -348
- package/apps/admin/escrow-scopes/new/index.tsx +3 -1
- package/apps/admin/index.tsx +129 -124
- package/apps/admin/mailboxes/[uid].tsx +271 -224
- package/apps/admin/mailboxes/new/index.tsx +30 -28
- package/apps/admin/plugins/index.tsx +15 -15
- package/apps/admin/retention-policy/index.tsx +39 -39
- package/apps/admin/signing-certificates/index.tsx +343 -343
- package/apps/admin/transport-rules/new/index.tsx +3 -1
- package/apps/escrow/audit-log/index.tsx +196 -196
- package/apps/escrow/matters/[uid].tsx +621 -621
- package/apps/escrow/matters/new/index.tsx +3 -1
- package/apps/shared/auth/adminAccess.ts +99 -99
- package/apps/shared/components/admin/layout/AdminShell.tsx +381 -378
- package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -0
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -152
- package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -0
- package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +50 -3
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
- package/apps/shared/components/admin/settings/PluginsManager.tsx +1545 -1545
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
- package/apps/shared/components/admin/setup/SetupWizard.tsx +446 -444
- package/apps/shared/components/admin/usePagedList.tsx +129 -129
- package/apps/shared/components/calendar/EventEditor.tsx +5 -10
- package/apps/shared/components/calendar/EventFormParts.tsx +16 -47
- package/apps/shared/components/calendar/EventModal.tsx +206 -206
- package/apps/shared/components/calendar/GuestInput.tsx +100 -0
- package/apps/shared/components/calendar/MonthView.tsx +185 -185
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -227
- package/apps/shared/components/calendar/RequestChangeForm.tsx +23 -54
- package/apps/shared/components/calendar/SplitDayView.tsx +144 -144
- package/apps/shared/components/calendar/TimeGridView.tsx +246 -246
- package/apps/shared/components/calendar/allDay.ts +124 -124
- package/apps/shared/components/calendar/eventForm.ts +2 -2
- package/apps/shared/components/calendar/eventFormat.ts +49 -22
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +201 -200
- package/apps/shared/components/contacts/ContactForm.tsx +383 -383
- package/apps/shared/components/contacts/ContactsSidebar.tsx +1 -1
- package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -98
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +218 -216
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
- package/apps/shared/components/layout/AppShell.tsx +461 -480
- package/apps/shared/components/layout/FloatingActionButton.tsx +31 -0
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
- package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -168
- package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
- package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
- package/apps/shared/components/layout/UserMenu.tsx +439 -424
- package/apps/shared/components/mail/ConversationList.tsx +323 -323
- package/apps/shared/components/mail/ConversationThreadPane.tsx +613 -491
- package/apps/shared/components/mail/EmptyFolderBar.tsx +41 -0
- package/apps/shared/components/mail/MailSelectionBar.tsx +240 -227
- package/apps/shared/components/mail/MenuButton.tsx +31 -11
- package/apps/shared/components/mail/MessageDetailPane.tsx +1757 -1559
- package/apps/shared/components/mail/PermanentDeleteDialog.tsx +47 -0
- package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -312
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +1681 -1677
- package/apps/shared/components/mail/compose/RecipientInput.tsx +54 -11
- package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
- package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
- package/apps/shared/components/mail/layout/FolderBadgeChip.tsx +21 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +161 -134
- package/apps/shared/components/mail/layout/SidebarSection.tsx +66 -0
- package/apps/shared/components/mail/reading/MessageMoreMenu.tsx +258 -0
- package/apps/shared/components/mail/reading/MessageSourceDialog.tsx +79 -0
- package/apps/shared/components/mail/reading/PendingMessageCard.tsx +68 -0
- package/apps/shared/components/mail/reading/messageExport.ts +59 -0
- package/apps/shared/components/mail/reading/printMessage.ts +99 -0
- package/apps/shared/components/mail/reading/useMessageActions.ts +443 -0
- package/apps/shared/components/mail/verificationSeals.ts +125 -125
- package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
- package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
- package/apps/shared/components/settings/layout/SettingsShell.tsx +257 -254
- package/apps/shared/components/tasks/TasksSidebar.tsx +1 -1
- package/apps/shared/components/tasks/layout/TasksShell.tsx +219 -217
- package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
- package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
- package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
- package/apps/shared/keyboard/dispatch.ts +124 -124
- package/apps/shared/keyboard/format.ts +89 -89
- package/apps/shared/keyboard/keymap.ts +114 -114
- package/apps/shared/keyboard/registry.ts +65 -65
- package/apps/shared/keyboard/targets.ts +79 -79
- package/apps/shared/mail/folderCounts.ts +17 -0
- package/apps/shared/mail/folderOfType.ts +49 -0
- package/apps/shared/mail/folderTree.ts +143 -143
- package/apps/shared/mail/listAllPages.ts +39 -39
- package/apps/shared/mail/newMailNotifications.ts +171 -171
- package/apps/shared/mail/outbox/outgoingReplies.ts +192 -0
- package/apps/shared/mail/outbox/sendJob.ts +19 -1
- package/apps/shared/mail/outbox/sendOutcomes.ts +13 -4
- package/apps/shared/mail/permanentDelete.ts +160 -0
- package/apps/shared/mail/primaryMailbox.ts +71 -0
- package/apps/shared/mail/reportNotices.ts +66 -0
- package/apps/shared/mail/senderBlocking.ts +141 -0
- package/apps/shared/mail/useCollapsedSections.ts +146 -0
- package/apps/shared/mail/useMailConnection.ts +205 -203
- package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
- package/apps/shared/mail/useMailboxUpdateAccess.ts +60 -0
- package/apps/shared/mail/useMarkMessageRead.ts +47 -47
- package/apps/shared/mail/useNewMailNotifications.ts +178 -178
- package/apps/shared/mail/usePermanentDelete.tsx +160 -0
- package/apps/shared/navigation/frameContext.tsx +1 -1
- package/apps/shared/navigation/index.ts +43 -0
- package/apps/shared/navigation/pageTitle.ts +19 -0
- package/apps/shared/notifications/store.ts +560 -560
- package/apps/shared/search/LocalIndexLifecycle.tsx +114 -114
- package/apps/shared/search/crossMailboxSearch.ts +169 -0
- package/apps/shared/search/localIndexBuilder.ts +481 -481
- package/apps/shared/signing/enrollmentStorage.ts +33 -33
- package/apps/shared/signing/enrollmentTracker.ts +385 -385
- package/apps/shared/signing/enrollmentView.ts +251 -251
- package/apps/shared/signing/useNow.ts +19 -19
- package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -90
- package/apps/shared/styles/app.css +396 -386
- package/apps/www/_shell.tsx +82 -0
- package/apps/www/calendar/index.tsx +581 -571
- package/apps/www/contacts/[uid].tsx +112 -109
- package/apps/www/contacts/index.tsx +14 -3
- package/apps/www/index.tsx +2962 -2479
- package/apps/www/messages/[uid].tsx +139 -135
- package/apps/www/settings/appearance/index.tsx +5 -2
- package/apps/www/settings/auto-reply/index.tsx +136 -133
- package/apps/www/settings/blocked-senders/index.tsx +303 -0
- package/apps/www/settings/encryption/index.tsx +1290 -1287
- package/apps/www/settings/filters/[uid].tsx +179 -176
- package/apps/www/settings/filters/index.tsx +105 -102
- package/apps/www/settings/filters/new/index.tsx +165 -140
- package/apps/www/settings/labels/index.tsx +207 -204
- package/apps/www/settings/privacy/index.tsx +495 -492
- package/apps/www/settings/profile/index.tsx +251 -248
- package/apps/www/settings/read-receipts/index.tsx +150 -147
- package/apps/www/settings/sharing/index.tsx +259 -256
- package/apps/www/settings/signatures/[uid].tsx +175 -172
- package/apps/www/settings/signatures/index.tsx +91 -88
- package/apps/www/settings/signatures/new/index.tsx +138 -135
- package/apps/www/tasks/index.tsx +654 -649
- package/dist/apps/admin/data-requests/index.js +1 -1
- package/dist/apps/admin/distribution-lists/new/index.js +3 -1
- package/dist/apps/admin/domains/[uid].js +3 -1
- package/dist/apps/admin/domains/new/index.js +3 -1
- package/dist/apps/admin/escrow-scopes/[uid].js +3 -1
- package/dist/apps/admin/escrow-scopes/new/index.js +3 -1
- package/dist/apps/admin/index.js +5 -2
- package/dist/apps/admin/mailboxes/[uid].js +15 -3
- package/dist/apps/admin/mailboxes/new/index.js +3 -1
- package/dist/apps/admin/transport-rules/new/index.js +3 -1
- package/dist/apps/escrow/matters/new/index.js +3 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +6 -3
- package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.d.ts +35 -0
- package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +115 -0
- package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.d.ts +16 -0
- package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +92 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +3 -3
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +25 -3
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +3 -1
- package/dist/apps/shared/components/calendar/EventEditor.js +5 -10
- package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -2
- package/dist/apps/shared/components/calendar/EventFormParts.js +5 -16
- package/dist/apps/shared/components/calendar/GuestInput.d.ts +45 -0
- package/dist/apps/shared/components/calendar/GuestInput.js +30 -0
- package/dist/apps/shared/components/calendar/RequestChangeForm.js +11 -22
- package/dist/apps/shared/components/calendar/eventForm.d.ts +2 -2
- package/dist/apps/shared/components/calendar/eventFormat.d.ts +21 -8
- package/dist/apps/shared/components/calendar/eventFormat.js +50 -23
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +5 -4
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +1 -1
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +4 -2
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +7 -5
- package/dist/apps/shared/components/layout/AppShell.d.ts +8 -11
- package/dist/apps/shared/components/layout/AppShell.js +24 -33
- package/dist/apps/shared/components/layout/FloatingActionButton.d.ts +13 -0
- package/dist/apps/shared/components/layout/FloatingActionButton.js +9 -0
- package/dist/apps/shared/components/layout/UserMenu.d.ts +4 -1
- package/dist/apps/shared/components/layout/UserMenu.js +6 -5
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +127 -22
- package/dist/apps/shared/components/mail/EmptyFolderBar.d.ts +18 -0
- package/dist/apps/shared/components/mail/EmptyFolderBar.js +10 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +14 -4
- package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -13
- package/dist/apps/shared/components/mail/MenuButton.d.ts +9 -2
- package/dist/apps/shared/components/mail/MenuButton.js +15 -11
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +5 -0
- package/dist/apps/shared/components/mail/MessageDetailPane.js +147 -7
- package/dist/apps/shared/components/mail/PermanentDeleteDialog.d.ts +19 -0
- package/dist/apps/shared/components/mail/PermanentDeleteDialog.js +11 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +8 -4
- package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +21 -1
- package/dist/apps/shared/components/mail/compose/RecipientInput.js +18 -10
- package/dist/apps/shared/components/mail/layout/FolderBadgeChip.d.ts +6 -0
- package/dist/apps/shared/components/mail/layout/FolderBadgeChip.js +9 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +16 -7
- package/dist/apps/shared/components/mail/layout/MailShell.js +78 -65
- package/dist/apps/shared/components/mail/layout/SidebarSection.d.ts +23 -0
- package/dist/apps/shared/components/mail/layout/SidebarSection.js +16 -0
- package/dist/apps/shared/components/mail/reading/MessageMoreMenu.d.ts +56 -0
- package/dist/apps/shared/components/mail/reading/MessageMoreMenu.js +153 -0
- package/dist/apps/shared/components/mail/reading/MessageSourceDialog.d.ts +27 -0
- package/dist/apps/shared/components/mail/reading/MessageSourceDialog.js +27 -0
- package/dist/apps/shared/components/mail/reading/PendingMessageCard.d.ts +14 -0
- package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +16 -0
- package/dist/apps/shared/components/mail/reading/messageExport.d.ts +19 -0
- package/dist/apps/shared/components/mail/reading/messageExport.js +51 -0
- package/dist/apps/shared/components/mail/reading/printMessage.d.ts +30 -0
- package/dist/apps/shared/components/mail/reading/printMessage.js +70 -0
- package/dist/apps/shared/components/mail/reading/useMessageActions.d.ts +57 -0
- package/dist/apps/shared/components/mail/reading/useMessageActions.js +366 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +10 -7
- package/dist/apps/shared/components/tasks/TasksSidebar.js +1 -1
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +7 -5
- package/dist/apps/shared/keyboard/GlobalShortcuts.js +1 -1
- package/dist/apps/shared/mail/folderCounts.d.ts +10 -0
- package/dist/apps/shared/mail/folderCounts.js +12 -0
- package/dist/apps/shared/mail/folderOfType.d.ts +13 -0
- package/dist/apps/shared/mail/folderOfType.js +40 -0
- package/dist/apps/shared/mail/outbox/outgoingReplies.d.ts +80 -0
- package/dist/apps/shared/mail/outbox/outgoingReplies.js +131 -0
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +5 -1
- package/dist/apps/shared/mail/outbox/sendJob.js +14 -1
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +13 -4
- package/dist/apps/shared/mail/permanentDelete.d.ts +62 -0
- package/dist/apps/shared/mail/permanentDelete.js +127 -0
- package/dist/apps/shared/mail/primaryMailbox.d.ts +29 -0
- package/dist/apps/shared/mail/primaryMailbox.js +61 -0
- package/dist/apps/shared/mail/reportNotices.d.ts +21 -0
- package/dist/apps/shared/mail/reportNotices.js +47 -0
- package/dist/apps/shared/mail/senderBlocking.d.ts +26 -0
- package/dist/apps/shared/mail/senderBlocking.js +92 -0
- package/dist/apps/shared/mail/useCollapsedSections.d.ts +32 -0
- package/dist/apps/shared/mail/useCollapsedSections.js +110 -0
- package/dist/apps/shared/mail/useMailConnection.js +3 -1
- package/dist/apps/shared/mail/useMailboxUpdateAccess.d.ts +11 -0
- package/dist/apps/shared/mail/useMailboxUpdateAccess.js +52 -0
- package/dist/apps/shared/mail/usePermanentDelete.d.ts +35 -0
- package/dist/apps/shared/mail/usePermanentDelete.js +96 -0
- package/dist/apps/shared/navigation/frameContext.d.ts +1 -1
- package/dist/apps/shared/navigation/index.d.ts +16 -0
- package/dist/apps/shared/navigation/index.js +34 -0
- package/dist/apps/shared/navigation/pageTitle.d.ts +11 -0
- package/dist/apps/shared/navigation/pageTitle.js +12 -0
- package/dist/apps/shared/search/crossMailboxSearch.d.ts +72 -0
- package/dist/apps/shared/search/crossMailboxSearch.js +136 -0
- package/dist/apps/shared/styles/app.css +396 -386
- package/dist/apps/www/_shell.d.ts +26 -0
- package/dist/apps/www/_shell.js +31 -0
- package/dist/apps/www/calendar/index.d.ts +7 -2
- package/dist/apps/www/calendar/index.js +7 -4
- package/dist/apps/www/contacts/[uid].d.ts +14 -3
- package/dist/apps/www/contacts/[uid].js +5 -3
- package/dist/apps/www/contacts/index.d.ts +7 -2
- package/dist/apps/www/contacts/index.js +8 -4
- package/dist/apps/www/index.d.ts +7 -2
- package/dist/apps/www/index.js +542 -207
- package/dist/apps/www/messages/[uid].d.ts +14 -3
- package/dist/apps/www/messages/[uid].js +7 -5
- package/dist/apps/www/settings/appearance/index.d.ts +8 -2
- package/dist/apps/www/settings/appearance/index.js +4 -2
- package/dist/apps/www/settings/auto-reply/index.d.ts +7 -2
- package/dist/apps/www/settings/auto-reply/index.js +4 -2
- package/dist/apps/www/settings/blocked-senders/index.d.ts +9 -0
- package/dist/apps/www/settings/blocked-senders/index.js +134 -0
- package/dist/apps/www/settings/encryption/index.d.ts +7 -2
- package/dist/apps/www/settings/encryption/index.js +4 -2
- package/dist/apps/www/settings/filters/[uid].d.ts +7 -2
- package/dist/apps/www/settings/filters/[uid].js +4 -2
- package/dist/apps/www/settings/filters/index.d.ts +7 -2
- package/dist/apps/www/settings/filters/index.js +4 -2
- package/dist/apps/www/settings/filters/new/index.d.ts +7 -2
- package/dist/apps/www/settings/filters/new/index.js +28 -5
- package/dist/apps/www/settings/labels/index.d.ts +7 -2
- package/dist/apps/www/settings/labels/index.js +4 -2
- package/dist/apps/www/settings/privacy/index.d.ts +7 -2
- package/dist/apps/www/settings/privacy/index.js +4 -2
- package/dist/apps/www/settings/profile/index.d.ts +7 -2
- package/dist/apps/www/settings/profile/index.js +4 -2
- package/dist/apps/www/settings/read-receipts/index.d.ts +7 -2
- package/dist/apps/www/settings/read-receipts/index.js +4 -2
- package/dist/apps/www/settings/sharing/index.d.ts +7 -2
- package/dist/apps/www/settings/sharing/index.js +4 -2
- package/dist/apps/www/settings/signatures/[uid].d.ts +7 -2
- package/dist/apps/www/settings/signatures/[uid].js +4 -2
- package/dist/apps/www/settings/signatures/index.d.ts +7 -2
- package/dist/apps/www/settings/signatures/index.js +4 -2
- package/dist/apps/www/settings/signatures/new/index.d.ts +7 -2
- package/dist/apps/www/settings/signatures/new/index.js +5 -3
- package/dist/apps/www/tasks/index.d.ts +7 -2
- package/dist/apps/www/tasks/index.js +5 -3
- package/package.json +4 -2
- package/apps/shared/navigation/AppRouter.tsx +0 -300
- package/apps/shared/navigation/idle.ts +0 -45
- package/apps/shared/navigation/routerContext.tsx +0 -83
- package/apps/shared/navigation/routes.ts +0 -72
- package/apps/www/_routedPage.tsx +0 -24
- package/apps/www/_routes.ts +0 -37
- package/dist/apps/shared/navigation/AppRouter.d.ts +0 -52
- package/dist/apps/shared/navigation/AppRouter.js +0 -242
- package/dist/apps/shared/navigation/idle.d.ts +0 -14
- package/dist/apps/shared/navigation/idle.js +0 -43
- package/dist/apps/shared/navigation/routerContext.d.ts +0 -37
- package/dist/apps/shared/navigation/routerContext.js +0 -56
- package/dist/apps/shared/navigation/routes.d.ts +0 -32
- package/dist/apps/shared/navigation/routes.js +0 -37
- package/dist/apps/www/_routedPage.d.ts +0 -12
- package/dist/apps/www/_routedPage.js +0 -19
- package/dist/apps/www/_routes.d.ts +0 -11
- package/dist/apps/www/_routes.js +0 -31
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { HiOutlineTrash } from "react-icons/hi2";
|
|
7
|
+
|
|
8
|
+
export interface EmptyFolderBarProps {
|
|
9
|
+
/** The folder the list is showing ("Deleted Items", "Junk Email"). */
|
|
10
|
+
folderName: string;
|
|
11
|
+
/** How many messages it holds, when that is known. */
|
|
12
|
+
count?: number;
|
|
13
|
+
/** Nothing to empty, or not allowed to. */
|
|
14
|
+
disabled: boolean;
|
|
15
|
+
/** Why, shown as the button's tooltip while it is disabled. */
|
|
16
|
+
disabledReason?: string;
|
|
17
|
+
onEmpty: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Outlook's "Empty folder", at the top of the list of Deleted Items and of Junk Email: the whole folder is permanently deleted, after the
|
|
22
|
+
* page has asked (this only reports the click). A row of its own under the toolbar rather than a toolbar button, so the toolbar keeps the
|
|
23
|
+
* width a phone gives it and the action is never taken for one of the list's arrangement controls.
|
|
24
|
+
*/
|
|
25
|
+
export default function EmptyFolderBar({ folderName, count, disabled, disabledReason, onEmpty }: EmptyFolderBarProps) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="flex items-center justify-between gap-2 px-3 py-1 border-b border-border text-xs text-text-muted">
|
|
28
|
+
<span>{count === undefined ? "" : `${count} ${count === 1 ? "item" : "items"}`}</span>
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
onClick={onEmpty}
|
|
32
|
+
disabled={disabled}
|
|
33
|
+
title={disabled ? disabledReason : `Permanently delete everything in ${folderName}`}
|
|
34
|
+
className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-sm text-danger hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent"
|
|
35
|
+
>
|
|
36
|
+
<HiOutlineTrash size={16} aria-hidden="true" className="shrink-0" />
|
|
37
|
+
{`Empty ${folderName}`}
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -1,227 +1,240 @@
|
|
|
1
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import React, { useState } from "react";
|
|
6
|
-
import { Folder, Message } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
7
|
-
import { Label } from "@rapidmx/react-shared/mail/labelsApi.js";
|
|
8
|
-
import LabelMenuButton from "./labelMenu.js";
|
|
9
|
-
import MoveToFolderDialog, { MOVE_TARGET_TYPES } from "./MoveToFolderDialog.js";
|
|
10
|
-
import { ariaKeyShortcuts, withHint } from "../../keyboard/format.js";
|
|
11
|
-
import { SHORTCUTS, ShortcutDef } from "../../keyboard/keymap.js";
|
|
12
|
-
import { useKeyEnvironment } from "../../keyboard/ShortcutProvider.js";
|
|
13
|
-
|
|
14
|
-
export interface MailSelectionBarProps {
|
|
15
|
-
selected: Message[];
|
|
16
|
-
/** Every message currently listed - what "Select all" selects and what `allSelected` is measured against. */
|
|
17
|
-
listed: Message[];
|
|
18
|
-
/** What the bar *counts*, when the rows being ticked aren't messages themselves: the conversation list
|
|
19
|
-
* ticks conversations, each standing for several messages, so "3 conversations selected" is what the
|
|
20
|
-
* reader picked while `selected` stays the messages every action below acts on. Left out by the flat
|
|
21
|
-
* message list, which counts `selected`/`listed` directly. */
|
|
22
|
-
totals?: { selected: number; listed: number; noun: string };
|
|
23
|
-
onSelectAll: () => void;
|
|
24
|
-
onClearSelection: () => void;
|
|
25
|
-
/** Leaves select mode entirely (and clears the selection). */
|
|
26
|
-
onCancel: () => void;
|
|
27
|
-
folders: Folder[];
|
|
28
|
-
currentFolderUid?: string;
|
|
29
|
-
/** Every label this mailbox has, for the Apply label action. */
|
|
30
|
-
labels: Label[];
|
|
31
|
-
/** The mailbox a label created from Apply label belongs to. */
|
|
32
|
-
mailboxUid: string;
|
|
33
|
-
onLabelCreated: (label: Label) => void;
|
|
34
|
-
/** A folder created from the Move to prompt, for the caller's own folder list and the sidebar. */
|
|
35
|
-
onFolderCreated: (folder: Folder) => void;
|
|
36
|
-
/** Sets the selection's labels: every message ends up with `labelUids`, plus whichever of
|
|
37
|
-
* `keepPartial` it already had (those rows were left partially applied, so each message keeps what
|
|
38
|
-
* it has). */
|
|
39
|
-
onApplyLabels: (labelUids: string[], keepPartial: string[]) => void;
|
|
40
|
-
onSetRead: (read: boolean) => void;
|
|
41
|
-
onSetFlagged: (flagged: boolean) => void;
|
|
42
|
-
onArchive: () => void;
|
|
43
|
-
/** Moves the selection into `folderUid`, resolving once the bulk update has settled. It resolves
|
|
44
|
-
* whether or not that update was rejected: a bulk update is applied element by element, so a failure
|
|
45
|
-
* is explained by a pop-up (see `notifyApiError()`), raised by the page along with the reload it triggers, rather than in the
|
|
46
|
-
* prompt, which would be claiming the move simply didn't happen. */
|
|
47
|
-
onMoveTo: (folderUid: string) => Promise<void>;
|
|
48
|
-
onReportJunk: () => void;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
<button type="button" onClick={
|
|
157
|
-
|
|
158
|
-
</button>
|
|
159
|
-
<button type="button" onClick={
|
|
160
|
-
|
|
161
|
-
</button>
|
|
162
|
-
<button type="button" onClick={
|
|
163
|
-
|
|
164
|
-
</button>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React, { useState } from "react";
|
|
6
|
+
import { Folder, Message } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
7
|
+
import { Label } from "@rapidmx/react-shared/mail/labelsApi.js";
|
|
8
|
+
import LabelMenuButton from "./labelMenu.js";
|
|
9
|
+
import MoveToFolderDialog, { MOVE_TARGET_TYPES } from "./MoveToFolderDialog.js";
|
|
10
|
+
import { ariaKeyShortcuts, withHint } from "../../keyboard/format.js";
|
|
11
|
+
import { SHORTCUTS, ShortcutDef } from "../../keyboard/keymap.js";
|
|
12
|
+
import { useKeyEnvironment } from "../../keyboard/ShortcutProvider.js";
|
|
13
|
+
|
|
14
|
+
export interface MailSelectionBarProps {
|
|
15
|
+
selected: Message[];
|
|
16
|
+
/** Every message currently listed - what "Select all" selects and what `allSelected` is measured against. */
|
|
17
|
+
listed: Message[];
|
|
18
|
+
/** What the bar *counts*, when the rows being ticked aren't messages themselves: the conversation list
|
|
19
|
+
* ticks conversations, each standing for several messages, so "3 conversations selected" is what the
|
|
20
|
+
* reader picked while `selected` stays the messages every action below acts on. Left out by the flat
|
|
21
|
+
* message list, which counts `selected`/`listed` directly. */
|
|
22
|
+
totals?: { selected: number; listed: number; noun: string };
|
|
23
|
+
onSelectAll: () => void;
|
|
24
|
+
onClearSelection: () => void;
|
|
25
|
+
/** Leaves select mode entirely (and clears the selection). */
|
|
26
|
+
onCancel: () => void;
|
|
27
|
+
folders: Folder[];
|
|
28
|
+
currentFolderUid?: string;
|
|
29
|
+
/** Every label this mailbox has, for the Apply label action. */
|
|
30
|
+
labels: Label[];
|
|
31
|
+
/** The mailbox a label created from Apply label belongs to. */
|
|
32
|
+
mailboxUid: string;
|
|
33
|
+
onLabelCreated: (label: Label) => void;
|
|
34
|
+
/** A folder created from the Move to prompt, for the caller's own folder list and the sidebar. */
|
|
35
|
+
onFolderCreated: (folder: Folder) => void;
|
|
36
|
+
/** Sets the selection's labels: every message ends up with `labelUids`, plus whichever of
|
|
37
|
+
* `keepPartial` it already had (those rows were left partially applied, so each message keeps what
|
|
38
|
+
* it has). */
|
|
39
|
+
onApplyLabels: (labelUids: string[], keepPartial: string[]) => void;
|
|
40
|
+
onSetRead: (read: boolean) => void;
|
|
41
|
+
onSetFlagged: (flagged: boolean) => void;
|
|
42
|
+
onArchive: () => void;
|
|
43
|
+
/** Moves the selection into `folderUid`, resolving once the bulk update has settled. It resolves
|
|
44
|
+
* whether or not that update was rejected: a bulk update is applied element by element, so a failure
|
|
45
|
+
* is explained by a pop-up (see `notifyApiError()`), raised by the page along with the reload it triggers, rather than in the
|
|
46
|
+
* prompt, which would be claiming the move simply didn't happen. */
|
|
47
|
+
onMoveTo: (folderUid: string) => Promise<void>;
|
|
48
|
+
onReportJunk: () => void;
|
|
49
|
+
/** Delete: moves the selection to Deleted Items - or, when `deletesPermanently`, permanently deletes it (the page asks first). */
|
|
50
|
+
onDelete: () => void;
|
|
51
|
+
/** The selection is in Deleted Items, so Delete is the permanent one: the button says "Delete permanently" and is styled as the destructive
|
|
52
|
+
* action it is. The page decides (a search over several mailboxes lists messages from more than one folder, so the folder being viewed is not
|
|
53
|
+
* always the answer). */
|
|
54
|
+
deletesPermanently?: boolean;
|
|
55
|
+
/** A bulk action is in flight - every action is held until it settles, since the next one would send
|
|
56
|
+
* `version`s the first has already superseded. */
|
|
57
|
+
busy: boolean;
|
|
58
|
+
/** The keyboard acts on this selection (Delete, Ctrl+Q, Ctrl+U, Insert - registered by the page): Mark read, Mark unread, Flag and
|
|
59
|
+
* Delete name their shortcut in the tooltip and `aria-keyshortcuts`. */
|
|
60
|
+
shortcuts?: boolean;
|
|
61
|
+
/** Why Move to is unavailable, when the selection is spread over mailboxes and a folder can only belong to one (search results over several
|
|
62
|
+
* mailboxes). Shown as the button's tooltip. */
|
|
63
|
+
moveDisabledReason?: string;
|
|
64
|
+
/** The same for Apply label: a label belongs to one mailbox, and `labels` are one mailbox's. */
|
|
65
|
+
labelsDisabledReason?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function actionClassName(destructive = false): string {
|
|
69
|
+
return `px-2 py-1 rounded-md text-sm ${destructive ? "text-danger font-semibold" : "text-text"} hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The header that replaces the list's own toolbar while select mode is on: how many rows are selected,
|
|
74
|
+
* select-all/clear, and the bulk actions Outlook offers for a multi-selection - mark read/unread, flag,
|
|
75
|
+
* archive, move, report junk and delete.
|
|
76
|
+
*
|
|
77
|
+
* "Delete" means *move to Deleted Items* - except on messages that are already in Deleted Items (`deletesPermanently`),
|
|
78
|
+
* where it is "Delete permanently": the page confirms, then erases them for good, as Outlook's Delete does there. An
|
|
79
|
+
* action whose target folder is the one already being viewed is disabled with a reason rather than hidden, so the set
|
|
80
|
+
* of actions doesn't shift around between folders.
|
|
81
|
+
*/
|
|
82
|
+
export default function MailSelectionBar({
|
|
83
|
+
selected,
|
|
84
|
+
listed,
|
|
85
|
+
totals,
|
|
86
|
+
onSelectAll,
|
|
87
|
+
onClearSelection,
|
|
88
|
+
onCancel,
|
|
89
|
+
folders,
|
|
90
|
+
currentFolderUid,
|
|
91
|
+
labels,
|
|
92
|
+
mailboxUid,
|
|
93
|
+
onLabelCreated,
|
|
94
|
+
onFolderCreated,
|
|
95
|
+
onApplyLabels,
|
|
96
|
+
onSetRead,
|
|
97
|
+
onSetFlagged,
|
|
98
|
+
onArchive,
|
|
99
|
+
onMoveTo,
|
|
100
|
+
onReportJunk,
|
|
101
|
+
onDelete,
|
|
102
|
+
deletesPermanently = false,
|
|
103
|
+
busy,
|
|
104
|
+
shortcuts,
|
|
105
|
+
moveDisabledReason,
|
|
106
|
+
labelsDisabledReason,
|
|
107
|
+
}: MailSelectionBarProps) {
|
|
108
|
+
const env = useKeyEnvironment();
|
|
109
|
+
/** `title` and `aria-keyshortcuts` for an action the keyboard also does. */
|
|
110
|
+
const hint = (label: string, shortcut: ShortcutDef) =>
|
|
111
|
+
shortcuts ? { title: withHint(label, shortcut, env), "aria-keyshortcuts": ariaKeyShortcuts(shortcut, env) } : {};
|
|
112
|
+
const selectedCount = totals?.selected ?? selected.length;
|
|
113
|
+
const listedCount = totals?.listed ?? listed.length;
|
|
114
|
+
// Counted on the rows that were ticked, but *emptied* on the messages: a ticked conversation whose
|
|
115
|
+
// messages are still being fetched has nothing for an action to act on yet.
|
|
116
|
+
const none = selectedCount === 0 || selected.length === 0;
|
|
117
|
+
const allSelected = listedCount > 0 && selectedCount === listedCount;
|
|
118
|
+
const currentType = folders.find((folder) => folder.uid === currentFolderUid)?.type;
|
|
119
|
+
const moveTargets = folders.filter((folder) => MOVE_TARGET_TYPES.has(folder.type) && folder.uid !== currentFolderUid);
|
|
120
|
+
// The same prompt the reading pane's own Move to opens, so one list of destinations and one way to
|
|
121
|
+
// create a folder serve both.
|
|
122
|
+
const [movePrompt, setMovePrompt] = useState(false);
|
|
123
|
+
|
|
124
|
+
// None of the three needs its folder to exist first - the caller creates or lazily provisions it (see
|
|
125
|
+
// `resolveFolderOfType()` and `bulkArchive()` in `apps/www/index.tsx`) - so the only thing that disables
|
|
126
|
+
// them is already being in the folder they would move to.
|
|
127
|
+
const archiveReason = currentType === "archive" ? "These messages are already in Archive" : undefined;
|
|
128
|
+
const junkReason = currentType === "junk" ? "These messages are already in Junk" : undefined;
|
|
129
|
+
const deleteLabel = deletesPermanently ? "Delete permanently" : "Delete";
|
|
130
|
+
|
|
131
|
+
// A label every selected message already carries starts ticked; one only some of them carry starts
|
|
132
|
+
// partially applied, and is left exactly as it is unless the reader touches that row.
|
|
133
|
+
const appliedToAll = labels.filter((label) => !none && selected.every((m) => m.labelUids?.includes(label.uid)));
|
|
134
|
+
const appliedToSome = labels.filter(
|
|
135
|
+
(label) => selected.some((m) => m.labelUids?.includes(label.uid)) && !appliedToAll.includes(label),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="border-b border-border">
|
|
141
|
+
<MoveToFolderDialog
|
|
142
|
+
open={movePrompt}
|
|
143
|
+
onClose={() => setMovePrompt(false)}
|
|
144
|
+
mailboxUid={mailboxUid}
|
|
145
|
+
folders={folders}
|
|
146
|
+
currentFolderUid={currentFolderUid}
|
|
147
|
+
count={selected.length}
|
|
148
|
+
onMove={onMoveTo}
|
|
149
|
+
onFolderCreated={onFolderCreated}
|
|
150
|
+
/>
|
|
151
|
+
<div className="flex items-center gap-2 px-3 py-1.5 bg-surface-alt">
|
|
152
|
+
<span aria-live="polite" className="text-sm font-semibold text-text">
|
|
153
|
+
{selectedCount}
|
|
154
|
+
{totals ? ` ${totals.noun}${selectedCount === 1 ? "" : "s"}` : ""} selected
|
|
155
|
+
</span>
|
|
156
|
+
<button type="button" onClick={onSelectAll} disabled={allSelected || listedCount === 0} className={actionClassName()}>
|
|
157
|
+
Select all
|
|
158
|
+
</button>
|
|
159
|
+
<button type="button" onClick={onClearSelection} disabled={selectedCount === 0} className={actionClassName()}>
|
|
160
|
+
Clear
|
|
161
|
+
</button>
|
|
162
|
+
<button type="button" onClick={onCancel} className={`${actionClassName()} ml-auto`}>
|
|
163
|
+
Cancel
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="flex flex-wrap items-center gap-0.5 px-1.5 py-1">
|
|
167
|
+
<button type="button" onClick={() => onSetRead(true)} disabled={none || busy} className={actionClassName()} {...hint("Mark read", SHORTCUTS.mail.markRead)}>
|
|
168
|
+
Mark read
|
|
169
|
+
</button>
|
|
170
|
+
<button type="button" onClick={() => onSetRead(false)} disabled={none || busy} className={actionClassName()} {...hint("Mark unread", SHORTCUTS.mail.markUnread)}>
|
|
171
|
+
Mark unread
|
|
172
|
+
</button>
|
|
173
|
+
<button type="button" onClick={() => onSetFlagged(true)} disabled={none || busy} className={actionClassName()} {...hint("Flag", SHORTCUTS.mail.flag)}>
|
|
174
|
+
Flag
|
|
175
|
+
</button>
|
|
176
|
+
<button type="button" onClick={() => onSetFlagged(false)} disabled={none || busy} className={actionClassName()}>
|
|
177
|
+
Unflag
|
|
178
|
+
</button>
|
|
179
|
+
<button
|
|
180
|
+
type="button"
|
|
181
|
+
onClick={onArchive}
|
|
182
|
+
disabled={none || busy || !!archiveReason}
|
|
183
|
+
title={archiveReason}
|
|
184
|
+
className={actionClassName()}
|
|
185
|
+
>
|
|
186
|
+
Archive
|
|
187
|
+
</button>
|
|
188
|
+
<LabelMenuButton
|
|
189
|
+
aria-label="Apply label"
|
|
190
|
+
label="Apply label"
|
|
191
|
+
className="text-sm"
|
|
192
|
+
labels={labels}
|
|
193
|
+
mailboxUid={mailboxUid}
|
|
194
|
+
onLabelCreated={onLabelCreated}
|
|
195
|
+
applied={appliedToAll.map((label) => label.uid)}
|
|
196
|
+
partial={appliedToSome.map((label) => label.uid)}
|
|
197
|
+
onCommit={onApplyLabels}
|
|
198
|
+
busy={busy}
|
|
199
|
+
disabled={none || busy || !!labelsDisabledReason}
|
|
200
|
+
title={labelsDisabledReason}
|
|
201
|
+
note={
|
|
202
|
+
selected.length === 1
|
|
203
|
+
? "Ticked labels are applied, unticked ones removed."
|
|
204
|
+
: "Ticked labels are applied to every selected message and unticked ones removed from all of them; a dash means only some have that label, and leaving it alone keeps it that way."
|
|
205
|
+
}
|
|
206
|
+
emptyNote="This mailbox has no labels yet."
|
|
207
|
+
commit={{ label: "Apply" }}
|
|
208
|
+
clear={{ label: "Remove all labels" }}
|
|
209
|
+
/>
|
|
210
|
+
<button
|
|
211
|
+
type="button"
|
|
212
|
+
onClick={() => setMovePrompt(true)}
|
|
213
|
+
disabled={none || busy || !!moveDisabledReason}
|
|
214
|
+
title={moveDisabledReason}
|
|
215
|
+
className={actionClassName()}
|
|
216
|
+
>
|
|
217
|
+
Move to
|
|
218
|
+
</button>
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
onClick={onReportJunk}
|
|
222
|
+
disabled={none || busy || !!junkReason}
|
|
223
|
+
title={junkReason}
|
|
224
|
+
className={actionClassName()}
|
|
225
|
+
>
|
|
226
|
+
Report junk
|
|
227
|
+
</button>
|
|
228
|
+
<button
|
|
229
|
+
type="button"
|
|
230
|
+
onClick={onDelete}
|
|
231
|
+
disabled={none || busy}
|
|
232
|
+
className={actionClassName(deletesPermanently)}
|
|
233
|
+
{...hint(deleteLabel, SHORTCUTS.mail.delete)}
|
|
234
|
+
>
|
|
235
|
+
{deleteLabel}
|
|
236
|
+
</button>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
@@ -19,6 +19,10 @@ interface MenuItemBase {
|
|
|
19
19
|
checked?: boolean | "mixed";
|
|
20
20
|
/** A colour swatch before the label - a label's own colour. */
|
|
21
21
|
swatchColor?: string;
|
|
22
|
+
/** An icon before the label, for a menu whose rows are commands (the reading pane's "More actions"). */
|
|
23
|
+
icon?: ReactNode;
|
|
24
|
+
/** A tooltip for the row - the full text of a label the row truncates. */
|
|
25
|
+
title?: string;
|
|
22
26
|
disabled?: boolean;
|
|
23
27
|
/** Leaves the menu open after choosing this row, for a multi-select list where several rows are ticked
|
|
24
28
|
* before one command commits them all. */
|
|
@@ -97,8 +101,11 @@ export function menuHeight(sections: MenuSectionSpec[], width: number = DEFAULT_
|
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
export interface MenuButtonProps {
|
|
100
|
-
/** The trigger's visible label. */
|
|
104
|
+
/** The trigger's visible label. Not drawn (nor is the chevron) with `iconOnly`, where `aria-label` is all the trigger says. */
|
|
101
105
|
label: ReactNode;
|
|
106
|
+
/** The trigger is just its `icon` - a round icon button like the reading pane's own command row, whose `className` it takes over
|
|
107
|
+
* completely. `aria-label` (and `title`) name it. */
|
|
108
|
+
iconOnly?: boolean;
|
|
102
109
|
/** The trigger's accessible name, which also names the menu itself - includes the current selection
|
|
103
110
|
* where there is one ("Filter: Unread"), so the button says what it's set to, not just what it does. */
|
|
104
111
|
"aria-label": string;
|
|
@@ -127,6 +134,7 @@ export interface MenuButtonProps {
|
|
|
127
134
|
*/
|
|
128
135
|
export default function MenuButton({
|
|
129
136
|
label,
|
|
137
|
+
iconOnly = false,
|
|
130
138
|
icon,
|
|
131
139
|
disabled,
|
|
132
140
|
title,
|
|
@@ -190,8 +198,10 @@ export default function MenuButton({
|
|
|
190
198
|
|
|
191
199
|
function enterSubmenu(item: MenuSubmenuSpec) {
|
|
192
200
|
setSubmenuKey(item.key);
|
|
193
|
-
|
|
194
|
-
|
|
201
|
+
const rows = item.submenu.flatMap((section) => section.items);
|
|
202
|
+
// Past the Back row, onto the first row of the submenu itself - or, when none of them can take focus (every row is unavailable right now), onto
|
|
203
|
+
// the Back row, so the keyboard still has a place to be.
|
|
204
|
+
setActiveIndex(rows.some((row) => !row.disabled) ? 1 + initialActiveIndex(rows) : 0);
|
|
195
205
|
}
|
|
196
206
|
|
|
197
207
|
function leaveSubmenu() {
|
|
@@ -285,16 +295,20 @@ export default function MenuButton({
|
|
|
285
295
|
disabled={disabled}
|
|
286
296
|
onClick={() => (open ? closeMenu(false) : openMenu())}
|
|
287
297
|
onKeyDown={handleTriggerKeyDown}
|
|
288
|
-
className={
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
298
|
+
className={
|
|
299
|
+
iconOnly
|
|
300
|
+
? className
|
|
301
|
+
: [
|
|
302
|
+
// `min-w-0` so a long label ("Filter: Has attachments") truncates instead of pushing
|
|
303
|
+
// whatever sits beside it in the toolbar off the edge of a 384px list column.
|
|
304
|
+
"inline-flex min-w-0 items-center gap-1 px-2 py-1 rounded-md text-sm text-text hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent",
|
|
305
|
+
className,
|
|
306
|
+
].join(" ")
|
|
307
|
+
}
|
|
294
308
|
>
|
|
295
309
|
{icon}
|
|
296
|
-
<span className="truncate">{label}</span>
|
|
297
|
-
<HiChevronDown size={14} aria-hidden="true" className="shrink-0 text-text-muted" />
|
|
310
|
+
{!iconOnly && <span className="truncate">{label}</span>}
|
|
311
|
+
{!iconOnly && <HiChevronDown size={14} aria-hidden="true" className="shrink-0 text-text-muted" />}
|
|
298
312
|
</button>
|
|
299
313
|
{open && (
|
|
300
314
|
<PopoverPortal
|
|
@@ -332,6 +346,7 @@ export default function MenuButton({
|
|
|
332
346
|
type="button"
|
|
333
347
|
role={role}
|
|
334
348
|
disabled={item.disabled}
|
|
349
|
+
title={item.title}
|
|
335
350
|
tabIndex={index === activeIndex ? 0 : -1}
|
|
336
351
|
{...(role === "menuitem"
|
|
337
352
|
? {}
|
|
@@ -352,6 +367,11 @@ export default function MenuButton({
|
|
|
352
367
|
</span>
|
|
353
368
|
<span className="min-w-0 flex-1">
|
|
354
369
|
<span className="flex h-5 items-center gap-1.5">
|
|
370
|
+
{item.icon && (
|
|
371
|
+
<span aria-hidden="true" className="shrink-0 text-text-muted">
|
|
372
|
+
{item.icon}
|
|
373
|
+
</span>
|
|
374
|
+
)}
|
|
355
375
|
{item.swatchColor && (
|
|
356
376
|
<span
|
|
357
377
|
aria-hidden="true"
|