@rapidmx/web-client 0.14.1 → 0.16.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 +373 -373
- package/apps/admin/branding/index.tsx +39 -39
- package/apps/admin/data-requests/index.tsx +482 -480
- package/apps/admin/domains/[uid].tsx +164 -163
- package/apps/admin/escrow-scopes/[uid].tsx +348 -348
- package/apps/admin/index.tsx +127 -124
- package/apps/admin/mailboxes/[uid].tsx +269 -224
- package/apps/admin/mailboxes/new/index.tsx +28 -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/escrow/audit-log/index.tsx +196 -196
- package/apps/escrow/matters/[uid].tsx +621 -621
- package/apps/shared/appearance/AppearanceForm.tsx +9 -1
- package/apps/shared/auth/adminAccess.ts +99 -99
- package/apps/shared/calendar/bookingPlugin.ts +100 -0
- package/apps/shared/calendar/calendarLiveUpdates.ts +27 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +378 -346
- 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 +53 -4
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
- package/apps/shared/components/admin/settings/PluginsManager.tsx +64 -10
- 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 +444 -444
- package/apps/shared/components/admin/usePagedList.tsx +129 -129
- package/apps/shared/components/calendar/AppointmentQuickForm.tsx +327 -0
- package/apps/shared/components/calendar/DescriptionEditor.tsx +274 -0
- package/apps/shared/components/calendar/EventDescriptionView.tsx +70 -0
- package/apps/shared/components/calendar/EventDetails.tsx +401 -0
- package/apps/shared/components/calendar/EventEditor.tsx +635 -0
- package/apps/shared/components/calendar/EventExpandedForm.tsx +210 -0
- package/apps/shared/components/calendar/EventFormParts.tsx +591 -0
- package/apps/shared/components/calendar/EventModal.tsx +206 -946
- package/apps/shared/components/calendar/EventQuickForm.tsx +116 -0
- package/apps/shared/components/calendar/EventShell.tsx +202 -0
- package/apps/shared/components/calendar/FindATime.tsx +379 -0
- package/apps/shared/components/calendar/GuestInput.tsx +100 -0
- package/apps/shared/components/calendar/LazyDescriptionEditor.tsx +70 -0
- package/apps/shared/components/calendar/MonthView.tsx +185 -173
- package/apps/shared/components/calendar/QuickCreateFaces.tsx +87 -0
- package/apps/shared/components/calendar/QuickCreateTabs.tsx +81 -0
- package/apps/shared/components/calendar/QuickFaceFrame.tsx +81 -0
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -222
- package/apps/shared/components/calendar/RequestChangeForm.tsx +240 -0
- package/apps/shared/components/calendar/SplitDayView.tsx +144 -143
- package/apps/shared/components/calendar/TaskQuickForm.tsx +289 -0
- package/apps/shared/components/calendar/TimeGridView.tsx +246 -244
- package/apps/shared/components/calendar/allDay.ts +124 -124
- package/apps/shared/components/calendar/eventDialogFields.ts +86 -0
- package/apps/shared/components/calendar/eventForm.ts +108 -0
- package/apps/shared/components/calendar/eventFormat.ts +210 -0
- package/apps/shared/components/calendar/findTimeGrid.ts +90 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +201 -200
- package/apps/shared/components/calendar/swipeNavigation.ts +14 -0
- 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 +217 -216
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
- package/apps/shared/components/layout/AppShell.tsx +480 -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 -372
- package/apps/shared/components/mail/ConversationList.tsx +323 -271
- package/apps/shared/components/mail/ConversationThreadPane.tsx +130 -5
- package/apps/shared/components/mail/InviteCard.tsx +387 -0
- package/apps/shared/components/mail/MailSelectionBar.tsx +236 -227
- package/apps/shared/components/mail/MessageDetailPane.tsx +1559 -1537
- package/apps/shared/components/mail/SwipeRow.tsx +120 -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 -1673
- 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/invite/InviteRowChip.tsx +104 -0
- package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +271 -0
- package/apps/shared/components/mail/invite/InviteTimeline.tsx +200 -0
- package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +109 -0
- package/apps/shared/components/mail/invite/inviteFormat.ts +110 -0
- package/apps/shared/components/mail/invite/inviteStore.ts +114 -0
- package/apps/shared/components/mail/layout/FolderBadgeChip.tsx +21 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +546 -490
- package/apps/shared/components/mail/layout/SidebarSection.tsx +66 -0
- package/apps/shared/components/mail/reading/PendingMessageCard.tsx +68 -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 +255 -253
- package/apps/shared/components/tasks/TasksSidebar.tsx +1 -1
- package/apps/shared/components/tasks/layout/TasksShell.tsx +218 -217
- package/apps/shared/gestures/useEnterSlide.ts +54 -0
- package/apps/shared/gestures/useSwipe.ts +114 -0
- package/apps/shared/gestures/useSwipeSlide.ts +113 -0
- package/apps/shared/gestures/useWheelPaging.ts +107 -0
- 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/folderTree.ts +143 -143
- package/apps/shared/mail/listAllPages.ts +39 -39
- package/apps/shared/mail/newMailNotifications.ts +3 -15
- 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/primaryMailbox.ts +71 -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/useMarkMessageRead.ts +47 -47
- package/apps/shared/mail/useNewMailNotifications.ts +2 -2
- package/apps/shared/navigation/AppRouter.tsx +300 -300
- package/apps/shared/navigation/routerContext.tsx +83 -83
- package/apps/shared/notifications/preferences.ts +36 -0
- package/apps/shared/notifications/store.ts +9 -0
- 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/_routes.ts +1 -0
- package/apps/www/calendar/index.tsx +578 -469
- package/apps/www/contacts/[uid].tsx +109 -109
- package/apps/www/contacts/index.tsx +6 -0
- package/apps/www/index.tsx +2847 -2332
- package/apps/www/messages/[uid].tsx +135 -108
- package/apps/www/settings/auto-reply/index.tsx +133 -133
- package/apps/www/settings/encryption/index.tsx +1287 -1287
- package/apps/www/settings/filters/[uid].tsx +176 -176
- package/apps/www/settings/filters/index.tsx +102 -102
- package/apps/www/settings/filters/new/index.tsx +140 -140
- package/apps/www/settings/labels/index.tsx +204 -204
- package/apps/www/settings/privacy/index.tsx +492 -492
- package/apps/www/settings/profile/index.tsx +248 -0
- package/apps/www/settings/read-receipts/index.tsx +147 -147
- package/apps/www/settings/sharing/index.tsx +256 -256
- package/apps/www/settings/signatures/[uid].tsx +172 -172
- package/apps/www/settings/signatures/index.tsx +88 -88
- package/apps/www/settings/signatures/new/index.tsx +135 -135
- package/apps/www/tasks/index.tsx +649 -649
- package/dist/apps/admin/data-requests/index.js +1 -1
- package/dist/apps/admin/domains/[uid].js +4 -2
- package/dist/apps/admin/index.js +2 -1
- package/dist/apps/admin/mailboxes/[uid].js +12 -2
- package/dist/apps/shared/appearance/AppearanceForm.js +9 -1
- package/dist/apps/shared/calendar/bookingPlugin.d.ts +50 -0
- package/dist/apps/shared/calendar/bookingPlugin.js +57 -0
- package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -0
- package/dist/apps/shared/calendar/calendarLiveUpdates.js +23 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +7 -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 +28 -4
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +40 -6
- package/dist/apps/shared/components/calendar/AppointmentQuickForm.d.ts +47 -0
- package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +135 -0
- package/dist/apps/shared/components/calendar/DescriptionEditor.d.ts +26 -0
- package/dist/apps/shared/components/calendar/DescriptionEditor.js +192 -0
- package/dist/apps/shared/components/calendar/EventDescriptionView.d.ts +14 -0
- package/dist/apps/shared/components/calendar/EventDescriptionView.js +39 -0
- package/dist/apps/shared/components/calendar/EventDetails.d.ts +33 -0
- package/dist/apps/shared/components/calendar/EventDetails.js +137 -0
- package/dist/apps/shared/components/calendar/EventEditor.d.ts +59 -0
- package/dist/apps/shared/components/calendar/EventEditor.js +544 -0
- package/dist/apps/shared/components/calendar/EventExpandedForm.d.ts +14 -0
- package/dist/apps/shared/components/calendar/EventExpandedForm.js +44 -0
- package/dist/apps/shared/components/calendar/EventFormParts.d.ts +70 -0
- package/dist/apps/shared/components/calendar/EventFormParts.js +191 -0
- package/dist/apps/shared/components/calendar/EventModal.d.ts +26 -13
- package/dist/apps/shared/components/calendar/EventModal.js +57 -459
- package/dist/apps/shared/components/calendar/EventQuickForm.d.ts +18 -0
- package/dist/apps/shared/components/calendar/EventQuickForm.js +25 -0
- package/dist/apps/shared/components/calendar/EventShell.d.ts +64 -0
- package/dist/apps/shared/components/calendar/EventShell.js +134 -0
- package/dist/apps/shared/components/calendar/FindATime.d.ts +18 -0
- package/dist/apps/shared/components/calendar/FindATime.js +190 -0
- 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/LazyDescriptionEditor.d.ts +8 -0
- package/dist/apps/shared/components/calendar/LazyDescriptionEditor.js +44 -0
- package/dist/apps/shared/components/calendar/MonthView.d.ts +5 -1
- package/dist/apps/shared/components/calendar/MonthView.js +11 -5
- package/dist/apps/shared/components/calendar/QuickCreateFaces.d.ts +30 -0
- package/dist/apps/shared/components/calendar/QuickCreateFaces.js +44 -0
- package/dist/apps/shared/components/calendar/QuickCreateTabs.d.ts +24 -0
- package/dist/apps/shared/components/calendar/QuickCreateTabs.js +46 -0
- package/dist/apps/shared/components/calendar/QuickFaceFrame.d.ts +25 -0
- package/dist/apps/shared/components/calendar/QuickFaceFrame.js +19 -0
- package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +2 -2
- package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +36 -0
- package/dist/apps/shared/components/calendar/RequestChangeForm.js +127 -0
- package/dist/apps/shared/components/calendar/SplitDayView.d.ts +3 -2
- package/dist/apps/shared/components/calendar/SplitDayView.js +3 -2
- package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +40 -0
- package/dist/apps/shared/components/calendar/TaskQuickForm.js +101 -0
- package/dist/apps/shared/components/calendar/TimeGridView.d.ts +4 -2
- package/dist/apps/shared/components/calendar/TimeGridView.js +3 -2
- package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +28 -0
- package/dist/apps/shared/components/calendar/eventDialogFields.js +59 -0
- package/dist/apps/shared/components/calendar/eventForm.d.ts +98 -0
- package/dist/apps/shared/components/calendar/eventForm.js +1 -0
- package/dist/apps/shared/components/calendar/eventFormat.d.ts +80 -0
- package/dist/apps/shared/components/calendar/eventFormat.js +190 -0
- package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +30 -0
- package/dist/apps/shared/components/calendar/findTimeGrid.js +83 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -2
- package/dist/apps/shared/components/calendar/swipeNavigation.d.ts +9 -0
- package/dist/apps/shared/components/calendar/swipeNavigation.js +8 -0
- 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 +4 -3
- 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 +14 -9
- package/dist/apps/shared/components/layout/UserMenu.js +74 -26
- package/dist/apps/shared/components/mail/ConversationList.d.ts +12 -1
- package/dist/apps/shared/components/mail/ConversationList.js +21 -4
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +127 -22
- package/dist/apps/shared/components/mail/InviteCard.d.ts +20 -0
- package/dist/apps/shared/components/mail/InviteCard.js +150 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -1
- package/dist/apps/shared/components/mail/MailSelectionBar.js +3 -3
- package/dist/apps/shared/components/mail/MessageDetailPane.js +13 -2
- package/dist/apps/shared/components/mail/SwipeRow.d.ts +22 -0
- package/dist/apps/shared/components/mail/SwipeRow.js +71 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +16 -7
- 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/invite/InviteRowChip.d.ts +23 -0
- package/dist/apps/shared/components/mail/invite/InviteRowChip.js +46 -0
- package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +20 -0
- package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +141 -0
- package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +27 -0
- package/dist/apps/shared/components/mail/invite/InviteTimeline.js +120 -0
- package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +17 -0
- package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +45 -0
- package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +28 -0
- package/dist/apps/shared/components/mail/invite/inviteFormat.js +94 -0
- package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +28 -0
- package/dist/apps/shared/components/mail/invite/inviteStore.js +95 -0
- 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 +10 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +76 -46
- 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/PendingMessageCard.d.ts +14 -0
- package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +16 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +7 -5
- package/dist/apps/shared/components/tasks/TasksSidebar.js +1 -1
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +4 -3
- package/dist/apps/shared/gestures/useEnterSlide.d.ts +14 -0
- package/dist/apps/shared/gestures/useEnterSlide.js +47 -0
- package/dist/apps/shared/gestures/useSwipe.d.ts +45 -0
- package/dist/apps/shared/gestures/useSwipe.js +90 -0
- package/dist/apps/shared/gestures/useSwipeSlide.d.ts +40 -0
- package/dist/apps/shared/gestures/useSwipeSlide.js +95 -0
- package/dist/apps/shared/gestures/useWheelPaging.d.ts +32 -0
- package/dist/apps/shared/gestures/useWheelPaging.js +85 -0
- package/dist/apps/shared/mail/folderCounts.d.ts +10 -0
- package/dist/apps/shared/mail/folderCounts.js +12 -0
- package/dist/apps/shared/mail/newMailNotifications.d.ts +3 -8
- package/dist/apps/shared/mail/newMailNotifications.js +3 -12
- 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/primaryMailbox.d.ts +29 -0
- package/dist/apps/shared/mail/primaryMailbox.js +61 -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/useNewMailNotifications.js +3 -2
- package/dist/apps/shared/notifications/preferences.d.ts +13 -0
- package/dist/apps/shared/notifications/preferences.js +36 -0
- package/dist/apps/shared/notifications/store.d.ts +1 -0
- package/dist/apps/shared/notifications/store.js +7 -23
- 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/_routes.js +1 -0
- package/dist/apps/www/calendar/index.js +78 -7
- package/dist/apps/www/contacts/index.js +3 -1
- package/dist/apps/www/index.js +548 -210
- package/dist/apps/www/messages/[uid].js +13 -5
- package/dist/apps/www/settings/profile/index.d.ts +4 -0
- package/dist/apps/www/settings/profile/index.js +124 -0
- package/package.json +2 -2
|
@@ -1,173 +1,185 @@
|
|
|
1
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import React from "react";
|
|
6
|
-
import { useDraggable, useDroppable } from "@dnd-kit/core";
|
|
7
|
-
import { eachDayOfInterval, endOfMonth, endOfWeek, format, isSameMonth, isToday, startOfMonth, startOfWeek } from "date-fns";
|
|
8
|
-
import { dayDropId, eventDragId } from "@rapidmx/react-shared/calendar/calendarDragIds.js";
|
|
9
|
-
import { CalendarOccurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
|
|
10
|
-
import { occursOnDay, startsOnDay } from "./allDay.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
);
|
|
173
|
-
}
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { useDraggable, useDroppable } from "@dnd-kit/core";
|
|
7
|
+
import { addDays, eachDayOfInterval, endOfMonth, endOfWeek, format, isSameMonth, isToday, startOfMonth, startOfWeek } from "date-fns";
|
|
8
|
+
import { dayDropId, eventDragId } from "@rapidmx/react-shared/calendar/calendarDragIds.js";
|
|
9
|
+
import { CalendarOccurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
|
|
10
|
+
import { occursOnDay, startsOnDay } from "./allDay.js";
|
|
11
|
+
import { EventAnchor, anchorOf } from "./EventShell.js";
|
|
12
|
+
|
|
13
|
+
const MAX_CHIPS_PER_DAY = 3;
|
|
14
|
+
|
|
15
|
+
export interface MonthViewProps {
|
|
16
|
+
/** Any date within the month to display. */
|
|
17
|
+
viewDate: Date;
|
|
18
|
+
/** Already expanded for the visible grid range — see `recurrence.ts`'s `expandAllOccurrences`. */
|
|
19
|
+
occurrences: CalendarOccurrence[];
|
|
20
|
+
/** Each occurrence's own calendar color, keyed by `folderUid` — see `calendarColors.ts`. */
|
|
21
|
+
folderColors: Record<string, string>;
|
|
22
|
+
onSelectDay: (date: Date) => void;
|
|
23
|
+
onSelectEvent: (occurrence: CalendarOccurrence) => void;
|
|
24
|
+
/** Click on the empty part of a day: the day (from midnight to the next midnight) and the cell, for a new all-day event's quick-create
|
|
25
|
+
* popover to open beside. */
|
|
26
|
+
onSelectSlot?: (start: Date, end: Date, anchor: EventAnchor) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A real 6-week month grid (Mon-start), matching Outlook/Gmail's month view. Multi-day and all-day
|
|
30
|
+
* events are shown on every day they cover (see `allDay.ts`'s `occursOnDay`). */
|
|
31
|
+
export default function MonthView({ viewDate, occurrences, folderColors, onSelectDay, onSelectEvent, onSelectSlot }: MonthViewProps) {
|
|
32
|
+
const gridStart = startOfWeek(startOfMonth(viewDate), { weekStartsOn: 1 });
|
|
33
|
+
const gridEnd = endOfWeek(endOfMonth(viewDate), { weekStartsOn: 1 });
|
|
34
|
+
const days = eachDayOfInterval({ start: gridStart, end: gridEnd });
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex-1 grid grid-cols-7 grid-rows-6 min-h-0" role="grid" aria-label="Month">
|
|
38
|
+
{days.map((day) => (
|
|
39
|
+
<DayCell
|
|
40
|
+
key={day.toISOString()}
|
|
41
|
+
day={day}
|
|
42
|
+
inCurrentMonth={isSameMonth(day, viewDate)}
|
|
43
|
+
occurrences={occurrences.filter((occ) => occursOnDay(occ, day))}
|
|
44
|
+
folderColors={folderColors}
|
|
45
|
+
onSelectDay={onSelectDay}
|
|
46
|
+
onSelectEvent={onSelectEvent}
|
|
47
|
+
onSelectSlot={onSelectSlot}
|
|
48
|
+
/>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface DayCellProps {
|
|
55
|
+
day: Date;
|
|
56
|
+
inCurrentMonth: boolean;
|
|
57
|
+
occurrences: CalendarOccurrence[];
|
|
58
|
+
folderColors: Record<string, string>;
|
|
59
|
+
onSelectDay: (date: Date) => void;
|
|
60
|
+
onSelectEvent: (occurrence: CalendarOccurrence) => void;
|
|
61
|
+
onSelectSlot?: (start: Date, end: Date, anchor: EventAnchor) => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function DayCell({ day, inCurrentMonth, occurrences, folderColors, onSelectDay, onSelectEvent, onSelectSlot }: DayCellProps) {
|
|
65
|
+
const { setNodeRef, isOver } = useDroppable({ id: dayDropId(day) });
|
|
66
|
+
const visible = occurrences.slice(0, MAX_CHIPS_PER_DAY);
|
|
67
|
+
const overflowCount = occurrences.length - visible.length;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
ref={setNodeRef}
|
|
72
|
+
onClick={(e) => {
|
|
73
|
+
// Only the empty part of the cell: the day number, the chips and "+N more" have their own clicks.
|
|
74
|
+
if (onSelectSlot && !(e.target as Element).closest("button")) {
|
|
75
|
+
onSelectSlot(day, addDays(day, 1), anchorOf(e.currentTarget));
|
|
76
|
+
}
|
|
77
|
+
}}
|
|
78
|
+
className={[
|
|
79
|
+
"border-b border-r border-border p-1 flex flex-col gap-0.5 min-h-0 overflow-hidden",
|
|
80
|
+
isOver ? "bg-primary/5" : "",
|
|
81
|
+
inCurrentMonth ? "bg-surface" : "bg-surface-alt",
|
|
82
|
+
].join(" ")}
|
|
83
|
+
>
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
onClick={() => onSelectDay(day)}
|
|
87
|
+
className={[
|
|
88
|
+
"self-start text-xs font-semibold w-6 h-6 rounded-full flex items-center justify-center shrink-0",
|
|
89
|
+
isToday(day) ? "bg-primary text-white" : inCurrentMonth ? "text-text" : "text-text-muted",
|
|
90
|
+
].join(" ")}
|
|
91
|
+
>
|
|
92
|
+
{format(day, "d")}
|
|
93
|
+
</button>
|
|
94
|
+
<div className="flex-1 flex flex-col gap-0.5 min-h-0 overflow-hidden">
|
|
95
|
+
{visible.map((occurrence) =>
|
|
96
|
+
startsOnDay(occurrence, day) ? (
|
|
97
|
+
<EventChip
|
|
98
|
+
key={occurrence.occurrenceKey}
|
|
99
|
+
occurrence={occurrence}
|
|
100
|
+
color={folderColors[occurrence.folderUid]}
|
|
101
|
+
onSelect={onSelectEvent}
|
|
102
|
+
/>
|
|
103
|
+
) : (
|
|
104
|
+
<ContinuationChip
|
|
105
|
+
key={occurrence.occurrenceKey}
|
|
106
|
+
occurrence={occurrence}
|
|
107
|
+
color={folderColors[occurrence.folderUid]}
|
|
108
|
+
onSelect={onSelectEvent}
|
|
109
|
+
/>
|
|
110
|
+
),
|
|
111
|
+
)}
|
|
112
|
+
{overflowCount > 0 && (
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
onClick={() => onSelectDay(day)}
|
|
116
|
+
className="text-xs text-text-muted text-left hover:underline shrink-0"
|
|
117
|
+
>
|
|
118
|
+
+{overflowCount} more
|
|
119
|
+
</button>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function EventChip({
|
|
127
|
+
occurrence,
|
|
128
|
+
color,
|
|
129
|
+
onSelect,
|
|
130
|
+
}: {
|
|
131
|
+
occurrence: CalendarOccurrence;
|
|
132
|
+
/** This occurrence's calendar color — see `calendarColors.ts`. Only applied for a "busy" chip; a
|
|
133
|
+
* "free" one keeps Outlook's own muted/outline treatment regardless of which calendar it's on. */
|
|
134
|
+
color: string;
|
|
135
|
+
onSelect: (occurrence: CalendarOccurrence) => void;
|
|
136
|
+
}) {
|
|
137
|
+
const { setNodeRef, listeners, attributes, transform, isDragging } = useDraggable({ id: eventDragId(occurrence) });
|
|
138
|
+
const isFree = occurrence.busyStatus === "free";
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<button
|
|
142
|
+
ref={setNodeRef}
|
|
143
|
+
type="button"
|
|
144
|
+
onClick={() => onSelect(occurrence)}
|
|
145
|
+
style={{
|
|
146
|
+
...(transform ? { transform: `translate(${transform.x}px, ${transform.y}px)`, zIndex: 10 } : undefined),
|
|
147
|
+
...(isFree ? undefined : { backgroundColor: color, color: "#fff" }),
|
|
148
|
+
}}
|
|
149
|
+
className={[
|
|
150
|
+
"text-xs text-left truncate rounded-sm px-1.5 py-0.5 shrink-0",
|
|
151
|
+
isFree ? "bg-surface-alt text-text-muted" : "",
|
|
152
|
+
isDragging ? "opacity-50" : "",
|
|
153
|
+
].join(" ")}
|
|
154
|
+
{...listeners}
|
|
155
|
+
{...attributes}
|
|
156
|
+
>
|
|
157
|
+
{occurrence.allDay ? "" : `${format(new Date(occurrence.startDate), "h:mma")} `}
|
|
158
|
+
{occurrence.title}
|
|
159
|
+
</button>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A multi-day event's chip on a day after its first: clickable, but not draggable (its drag id is
|
|
164
|
+
* already taken by the start day's chip) and without a start-time prefix. */
|
|
165
|
+
function ContinuationChip({
|
|
166
|
+
occurrence,
|
|
167
|
+
color,
|
|
168
|
+
onSelect,
|
|
169
|
+
}: {
|
|
170
|
+
occurrence: CalendarOccurrence;
|
|
171
|
+
color: string;
|
|
172
|
+
onSelect: (occurrence: CalendarOccurrence) => void;
|
|
173
|
+
}) {
|
|
174
|
+
const isFree = occurrence.busyStatus === "free";
|
|
175
|
+
return (
|
|
176
|
+
<button
|
|
177
|
+
type="button"
|
|
178
|
+
onClick={() => onSelect(occurrence)}
|
|
179
|
+
style={isFree ? undefined : { backgroundColor: color, color: "#fff" }}
|
|
180
|
+
className={["text-xs text-left truncate rounded-sm px-1.5 py-0.5 shrink-0", isFree ? "bg-surface-alt text-text-muted" : ""].join(" ")}
|
|
181
|
+
>
|
|
182
|
+
{occurrence.title}
|
|
183
|
+
</button>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
|
6
|
+
import { deviceTimeZone } from "@rapidmx/react-shared/util/timeZone.js";
|
|
7
|
+
import AppointmentQuickForm, { initialAppointmentDraft } from "./AppointmentQuickForm.js";
|
|
8
|
+
import EventExpandedForm from "./EventExpandedForm.js";
|
|
9
|
+
import EventQuickForm from "./EventQuickForm.js";
|
|
10
|
+
import { EventFormController } from "./eventForm.js";
|
|
11
|
+
import QuickCreateTabs, { QuickTab, quickPanelProps, quickTabId } from "./QuickCreateTabs.js";
|
|
12
|
+
import TaskQuickForm, { initialTaskDraft } from "./TaskQuickForm.js";
|
|
13
|
+
|
|
14
|
+
/** What `EventModal` tells the editor about the tabs of a new event's popover. */
|
|
15
|
+
export interface QuickCreateConfig {
|
|
16
|
+
tab: QuickTab;
|
|
17
|
+
onTabChange: (tab: QuickTab) => void;
|
|
18
|
+
/** Where the booking plugin's Settings pages are, or `undefined` when the plugin is not running (and there is no Appointment schedule tab). */
|
|
19
|
+
bookingHref?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface QuickCreateFacesProps {
|
|
23
|
+
/** The Event tab's form, which owns the title and the mailbox and calendar the other tabs share. */
|
|
24
|
+
c: EventFormController;
|
|
25
|
+
layout: "quick" | "expanded";
|
|
26
|
+
onExpand: () => void;
|
|
27
|
+
config: QuickCreateConfig;
|
|
28
|
+
dirtyRef: MutableRefObject<boolean>;
|
|
29
|
+
/** The signed-in mailbox's address: the host's name on booking pages when its display name is not known. */
|
|
30
|
+
organizerAddress: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The faces of a new event, one for each tab: Event (the quick popover or, after More options, the full card - unchanged), Task and
|
|
35
|
+
* Appointment schedule. Stays mounted while the tab changes, so it is what keeps the other tabs' fields (the Event tab's live in
|
|
36
|
+
* `EventEditor`) when the user looks at another one and comes back. The title, the mailbox and the calendar are the Event tab's, so they
|
|
37
|
+
* are shared by all three.
|
|
38
|
+
*
|
|
39
|
+
* The tab strip is redrawn by whichever face is showing, so a tab change moves keyboard focus back to the selected tab here - what an
|
|
40
|
+
* arrow-key walk along the strip needs.
|
|
41
|
+
*/
|
|
42
|
+
export default function QuickCreateFaces({ c, layout, onExpand, config, dirtyRef, organizerAddress }: QuickCreateFacesProps) {
|
|
43
|
+
const { tab, onTabChange, bookingHref } = config;
|
|
44
|
+
const [task, setTask] = useState(() => initialTaskDraft(c.values.start, c.values.allDay));
|
|
45
|
+
const [appointment, setAppointment] = useState(initialAppointmentDraft);
|
|
46
|
+
|
|
47
|
+
const shownTab = useRef(tab);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (shownTab.current !== tab) {
|
|
50
|
+
shownTab.current = tab;
|
|
51
|
+
document.getElementById(quickTabId(tab))?.focus();
|
|
52
|
+
}
|
|
53
|
+
}, [tab]);
|
|
54
|
+
|
|
55
|
+
// Notes typed on the Task tab are worth a second thought before a stray click closes the popover, like a title.
|
|
56
|
+
dirtyRef.current = dirtyRef.current || task.body.trim() !== "";
|
|
57
|
+
|
|
58
|
+
const tabs = <QuickCreateTabs tab={tab} onChange={onTabChange} showAppointment={!!bookingHref} />;
|
|
59
|
+
|
|
60
|
+
if (tab === "task") {
|
|
61
|
+
return (
|
|
62
|
+
<TaskQuickForm
|
|
63
|
+
c={c}
|
|
64
|
+
draft={task}
|
|
65
|
+
onDraftChange={(patch) => setTask((prev) => ({ ...prev, ...patch }))}
|
|
66
|
+
tabs={tabs}
|
|
67
|
+
expanded={layout === "expanded"}
|
|
68
|
+
onExpand={onExpand}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (tab === "appointment") {
|
|
73
|
+
const hostMailbox = c.mailboxOptions?.find((option) => option.mailbox.uid === c.values.targetMailboxUid)?.mailbox;
|
|
74
|
+
return (
|
|
75
|
+
<AppointmentQuickForm
|
|
76
|
+
c={c}
|
|
77
|
+
draft={appointment}
|
|
78
|
+
onDraftChange={(patch) => setAppointment((prev) => ({ ...prev, ...patch }))}
|
|
79
|
+
tabs={tabs}
|
|
80
|
+
settingsHref={bookingHref!}
|
|
81
|
+
hostName={hostMailbox?.displayName ?? organizerAddress}
|
|
82
|
+
timeZone={hostMailbox?.timezone ?? deviceTimeZone()}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return layout === "quick" ? <EventQuickForm c={c} onExpand={onExpand} tabs={tabs} tabPanelProps={quickPanelProps("event")} /> : <EventExpandedForm c={c} />;
|
|
87
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React, { KeyboardEvent } from "react";
|
|
6
|
+
|
|
7
|
+
/** What the New event popover can create. */
|
|
8
|
+
export type QuickTab = "event" | "task" | "appointment";
|
|
9
|
+
|
|
10
|
+
const TAB_LABELS: Record<QuickTab, string> = { event: "Event", task: "Task", appointment: "Appointment schedule" };
|
|
11
|
+
|
|
12
|
+
/** The DOM ids that tie each tab to its panel (`aria-controls` / `aria-labelledby`). */
|
|
13
|
+
export function quickTabId(tab: QuickTab): string {
|
|
14
|
+
return `quick-create-tab-${tab}`;
|
|
15
|
+
}
|
|
16
|
+
export function quickPanelId(tab: QuickTab): string {
|
|
17
|
+
return `quick-create-panel-${tab}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The attributes of the element holding the selected tab's content. */
|
|
21
|
+
export function quickPanelProps(tab: QuickTab) {
|
|
22
|
+
return { role: "tabpanel" as const, id: quickPanelId(tab), "aria-labelledby": quickTabId(tab) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface QuickCreateTabsProps {
|
|
26
|
+
tab: QuickTab;
|
|
27
|
+
onChange: (tab: QuickTab) => void;
|
|
28
|
+
/** Whether the booking plugin is running: without it there is no Appointment schedule tab. */
|
|
29
|
+
showAppointment: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The segmented control across the top of the New event popover: Event, Task and (when the booking plugin is running) Appointment schedule.
|
|
34
|
+
* A WAI-ARIA tab list with automatic activation: only the selected tab is in the Tab order, and the arrow keys (wrapping), Home and End
|
|
35
|
+
* move to and select another.
|
|
36
|
+
*/
|
|
37
|
+
export default function QuickCreateTabs({ tab, onChange, showAppointment }: QuickCreateTabsProps) {
|
|
38
|
+
const tabs: QuickTab[] = showAppointment ? ["event", "task", "appointment"] : ["event", "task"];
|
|
39
|
+
|
|
40
|
+
function handleKeyDown(event: KeyboardEvent<HTMLButtonElement>) {
|
|
41
|
+
const index = tabs.indexOf(tab);
|
|
42
|
+
let next: QuickTab;
|
|
43
|
+
if (event.key === "ArrowRight") {
|
|
44
|
+
next = tabs[(index + 1) % tabs.length];
|
|
45
|
+
} else if (event.key === "ArrowLeft") {
|
|
46
|
+
next = tabs[(index + tabs.length - 1) % tabs.length];
|
|
47
|
+
} else if (event.key === "Home") {
|
|
48
|
+
next = tabs[0];
|
|
49
|
+
} else if (event.key === "End") {
|
|
50
|
+
next = tabs[tabs.length - 1];
|
|
51
|
+
} else {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
onChange(next);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div role="tablist" aria-label="What to create" className="mx-5 mb-3 flex gap-1 rounded-lg bg-surface-alt p-1">
|
|
60
|
+
{tabs.map((id) => (
|
|
61
|
+
<button
|
|
62
|
+
key={id}
|
|
63
|
+
type="button"
|
|
64
|
+
role="tab"
|
|
65
|
+
id={quickTabId(id)}
|
|
66
|
+
aria-selected={id === tab}
|
|
67
|
+
aria-controls={quickPanelId(id)}
|
|
68
|
+
tabIndex={id === tab ? 0 : -1}
|
|
69
|
+
onClick={() => onChange(id)}
|
|
70
|
+
onKeyDown={handleKeyDown}
|
|
71
|
+
className={[
|
|
72
|
+
"flex-1 min-w-0 rounded-md px-2 py-1.5 text-sm font-medium truncate focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
73
|
+
id === tab ? "bg-surface text-text shadow-sm" : "text-text-muted hover:text-text",
|
|
74
|
+
].join(" ")}
|
|
75
|
+
>
|
|
76
|
+
{TAB_LABELS[id]}
|
|
77
|
+
</button>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React, { FormEvent, ReactNode, useContext } from "react";
|
|
6
|
+
import { HiOutlineBars2, HiOutlineXMark } from "react-icons/hi2";
|
|
7
|
+
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
8
|
+
import { EventShellContext } from "./EventShell.js";
|
|
9
|
+
import { EventFormController } from "./eventForm.js";
|
|
10
|
+
import { QuickTab, quickPanelProps } from "./QuickCreateTabs.js";
|
|
11
|
+
|
|
12
|
+
export interface QuickFaceFrameProps {
|
|
13
|
+
c: EventFormController;
|
|
14
|
+
/** The `QuickCreateTabs` strip. */
|
|
15
|
+
tabs: ReactNode;
|
|
16
|
+
/** The tab this face is the panel of. */
|
|
17
|
+
tab: QuickTab;
|
|
18
|
+
error: string | null;
|
|
19
|
+
/** Leave the title out (the Appointment schedule tab once it has saved). */
|
|
20
|
+
hideTitle?: boolean;
|
|
21
|
+
/** Enter in a field, or the Save button. */
|
|
22
|
+
onSubmit: (event: FormEvent) => void;
|
|
23
|
+
/** What sits in the tab's panel under the title. */
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
/** The buttons along the bottom (More options, Save). */
|
|
26
|
+
footer: ReactNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The frame the Task and Appointment schedule faces share, drawn the way `EventQuickForm` draws the Event face: a grip to drag the popover
|
|
31
|
+
* and a close button, the tab strip, then the panel - an error, the title (the one the Event tab holds, so switching tabs keeps it) and the
|
|
32
|
+
* face's own rows - and the buttons.
|
|
33
|
+
*/
|
|
34
|
+
export default function QuickFaceFrame({ c, tabs, tab, error, hideTitle, onSubmit, children, footer }: QuickFaceFrameProps) {
|
|
35
|
+
const { dragHandleProps } = useContext(EventShellContext);
|
|
36
|
+
return (
|
|
37
|
+
<form onSubmit={onSubmit} className="flex flex-col">
|
|
38
|
+
<div className="flex items-center justify-between px-2 pt-2">
|
|
39
|
+
{dragHandleProps ? (
|
|
40
|
+
<div {...dragHandleProps} aria-hidden="true" className="flex-1 h-7 flex items-center cursor-move text-text-muted select-none touch-none">
|
|
41
|
+
<HiOutlineBars2 size={20} />
|
|
42
|
+
</div>
|
|
43
|
+
) : (
|
|
44
|
+
<div className="flex-1" />
|
|
45
|
+
)}
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
aria-label="Close"
|
|
49
|
+
onClick={c.onCancel}
|
|
50
|
+
className="w-8 h-8 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text"
|
|
51
|
+
>
|
|
52
|
+
<HiOutlineXMark size={20} aria-hidden="true" />
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{tabs}
|
|
57
|
+
|
|
58
|
+
<div {...quickPanelProps(tab)} className="flex flex-col gap-3 px-5 pb-3">
|
|
59
|
+
{error && <Alert>{error}</Alert>}
|
|
60
|
+
|
|
61
|
+
{!hideTitle && (
|
|
62
|
+
<div className="pl-8">
|
|
63
|
+
<input
|
|
64
|
+
type="text"
|
|
65
|
+
aria-label="Title"
|
|
66
|
+
placeholder="Add title"
|
|
67
|
+
data-autofocus
|
|
68
|
+
className="w-full text-xl bg-transparent text-text border-0 border-b-2 border-primary/50 focus:border-primary focus:outline-none pb-1 placeholder:text-text-muted"
|
|
69
|
+
value={c.values.title}
|
|
70
|
+
onChange={(e) => c.update({ title: e.target.value })}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{children}
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div className="flex items-center justify-end gap-2 px-4 py-3">{footer}</div>
|
|
79
|
+
</form>
|
|
80
|
+
);
|
|
81
|
+
}
|