ultisuite-client/components/gmail/email-list/email-list-body.tsx
R3D347HR4Y 5304790ed5
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat(auth): enhance session management and identity provider settings
- Added SessionGuard component to manage session expiration and online status.
- Updated AuthProvider to streamline session fetching and handling.
- Introduced IdentityProvidersSection for managing OAuth, SAML, and LDAP identity providers.
- Implemented identity provider guides for easier configuration.
- Enhanced mail settings with infinite scroll option for improved user experience.
- Updated global styles and layout components for better consistency across the application.
2026-06-09 09:36:46 +02:00

284 lines
11 KiB
TypeScript

"use client"
import { ChevronLeft, ChevronUp, ChevronDown, RefreshCw } from "lucide-react"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { MailFolderStackIndicator } from "@/components/gmail/mail-folder-stack-indicator"
import { mailNavVisitKey } from "@/lib/mail-folder-display"
import { MAIL_LIST_ROW_DIVIDER_CLASS } from "@/lib/mail-chrome-classes"
import {
PULL_HOLD_HEIGHT,
REFRESH_SPIN_CLASS,
} from "@/components/gmail/email-list/email-list-helpers"
import { EmailListRow } from "@/components/gmail/email-list/email-list-row"
import {
EmailListEmpty,
EmailListScheduledBanner,
} from "@/components/gmail/email-list/email-list-empty"
import { EmailListEmailViewPane } from "@/components/gmail/email-list/email-list-email-view-pane"
import type { EmailListData } from "@/components/gmail/email-list/hooks/use-email-list-data"
import type { EmailListLabels } from "@/components/gmail/email-list/hooks/use-email-list-labels"
import type { EmailListSelection } from "@/components/gmail/email-list/hooks/use-email-list-selection"
import type { EmailListReading } from "@/components/gmail/email-list/hooks/use-email-list-reading"
const MAIN_SCROLL_CLASS =
"min-h-0 flex-1 overflow-y-auto overflow-x-hidden border-0 bg-mail-surface shadow-none outline-none sm:rounded-b-2xl " +
"[scrollbar-color:#9aa0a6_#ffffff] [scrollbar-width:auto] " +
"[&::-webkit-scrollbar]:w-2.5 [&::-webkit-scrollbar]:border-0 [&::-webkit-scrollbar]:bg-white " +
"[&::-webkit-scrollbar-track]:border-0 [&::-webkit-scrollbar-track]:bg-white [&::-webkit-scrollbar-track]:shadow-none " +
"[&::-webkit-scrollbar-thumb]:rounded-none [&::-webkit-scrollbar-thumb]:border-0 [&::-webkit-scrollbar-thumb]:shadow-none " +
"[&::-webkit-scrollbar-thumb]:bg-[#9aa0a6] hover:[&::-webkit-scrollbar-thumb]:bg-[#5f6368] " +
"[&::-webkit-scrollbar-corner]:border-0 [&::-webkit-scrollbar-corner]:bg-white"
type EmailListBodyProps = {
data: EmailListData
labels: EmailListLabels
selection: EmailListSelection
reading: EmailListReading
onSelectFolder?: (folder: string) => void
}
export function EmailListBody({
data,
labels,
selection,
reading,
onSelectFolder,
}: EmailListBodyProps) {
const {
splitView,
isViewMode,
isSearchMode,
selectedFolder,
listToolbarMode,
isRefreshing,
listViewportRef,
pullContentRef,
pullIconRef,
displayListEmails,
listEmails,
inboxCategoryTabLabel,
sidebarNav,
searchParams,
} = data
const {
openEmail,
openMailIndex,
goBack,
goToPrev,
goToNext,
handleBreadcrumbNavigate,
handleCategoryInboxTabClick,
} = reading
const rowPropsBase = {
allEmails: data.allEmails,
emailById: data.emailById,
listMailIndex: data.listMailIndex,
listRowExtras: data.listRowExtras,
starredEmails: data.starredEmails,
importantEmails: data.importantEmails,
readOverrides: data.readOverrides,
conversationMode: data.conversationMode,
savedThreadReplyDrafts: data.savedThreadReplyDrafts,
selectedEmails: selection.selectedEmails,
selectedFolder: data.selectedFolder,
splitView: data.splitView,
openMailId: data.openMailId,
isXs: data.isXs,
isMd: data.isMd,
density: data.density,
mobileSelectionMode: selection.mobileSelectionMode,
touchListSwipeEnabled: selection.touchListSwipeEnabled,
openSwipeRowId: selection.openSwipeRowId,
setOpenSwipeRowId: selection.setOpenSwipeRowId,
listRowLabelBgByTextLower: data.listRowLabelBgByTextLower,
sidebarNav: data.sidebarNav,
rescheduleTarget: data.rescheduleTarget,
setRescheduleTarget: data.setRescheduleTarget,
rescheduleDismissTimeoutsRef: data.rescheduleDismissTimeoutsRef,
scheduleReschedulePopoverDismiss: data.scheduleReschedulePopoverDismiss,
rowContextMenuOpenedAtRef: selection.rowContextMenuOpenedAtRef,
contextMenuTargetIdsRef: selection.contextMenuTargetIdsRef,
lastSelectionAnchorIdRef: selection.lastSelectionAnchorIdRef,
setSelectedEmails: selection.setSelectedEmails,
setLabelPickerQuery: data.setLabelPickerQuery,
labelPickerQuery: data.labelPickerQuery,
catalogLabels: labels.catalogLabels,
resolveLabelVisual: labels.resolveLabelVisual,
getCatalogLabelPresence: labels.getCatalogLabelPresence,
toggleLabelOnEmails: labels.toggleLabelOnEmails,
addLabelToEmails: labels.addLabelToEmails,
moveTargets: data.moveTargets,
moveEmailsToTarget: labels.moveEmailsToTarget,
cmScheduledRescheduleValue: data.cmScheduledRescheduleValue,
setCmScheduledRescheduleValue: data.setCmScheduledRescheduleValue,
mailActions: data.mailActions,
setReadOverrides: data.setReadOverrides,
onSelectFolder,
toggleSelect: selection.toggleSelect,
handleRowCheckboxClickCapture: selection.handleRowCheckboxClickCapture,
handleRowActivate: reading.handleRowActivate,
startRowDrag: selection.startRowDrag,
archiveListRow: reading.archiveListRow,
deleteListRow: reading.deleteListRow,
toggleStar: selection.toggleStar,
toggleImportant: selection.toggleImportant,
openSwipeRowLabelSheet: selection.openSwipeRowLabelSheet,
handleNavigateToLabel: reading.handleNavigateToLabel,
handleCategoryInboxTabClick,
closeViewIfShowingEmail: reading.closeViewIfShowingEmail,
restoreSnoozedRowToMailbox: reading.restoreSnoozedRowToMailbox,
handleEditScheduledMail: data.handleEditScheduledMail,
requestArchiveScheduled: data.requestArchiveScheduled,
requestDeleteScheduled: data.requestDeleteScheduled,
requestToggleReadScheduled: data.requestToggleReadScheduled,
requestSnoozeScheduled: data.requestSnoozeScheduled,
requestRescheduleScheduled: data.requestRescheduleScheduled,
requestSendScheduledNow: data.requestSendScheduledNow,
requestSnoozeMailboxEmail: data.requestSnoozeMailboxEmail,
}
return (
<div className={cn("relative flex min-h-0 flex-1 flex-col")}>
<div
ref={listViewportRef}
className={cn(
"max-sm:pb-16",
!splitView && isViewMode && openEmail
? "relative flex min-h-0 flex-1 flex-col overflow-hidden"
: MAIN_SCROLL_CLASS,
"relative min-h-0 flex-1 overscroll-y-none"
)}
>
{listToolbarMode && (
<div
className="pointer-events-none absolute inset-x-0 top-0 z-10 flex items-center justify-center pt-2 sm:hidden"
style={{ height: PULL_HOLD_HEIGHT }}
aria-hidden
>
<RefreshCw
ref={pullIconRef}
className={cn(
"h-5 w-5 text-[#5f6368]",
isRefreshing && REFRESH_SPIN_CLASS
)}
style={{ opacity: 0 }}
/>
</div>
)}
<div
ref={pullContentRef}
className={cn(
!splitView && isViewMode && openEmail && "relative flex min-h-0 flex-1 flex-col ",
listToolbarMode && "max-sm:[transform:translateZ(0)]"
)}
>
{!splitView && isViewMode && openEmail ? (
<>
<div className="pointer-events-none absolute inset-x-0 top-0 z-30 flex items-center justify-between gap-2 px-3 py-2 sm:hidden">
<Button
type="button"
variant="ghost"
size="icon"
className="pointer-events-auto size-9 shrink-0 rounded-full border border-gray-200 bg-white/80 text-[#444746] shadow-md backdrop-blur hover:bg-white"
aria-label="Retour à la boîte de réception"
onClick={goBack}
>
<ChevronLeft className="size-5" strokeWidth={1.5} />
</Button>
<div className="pointer-events-auto flex shrink-0 overflow-hidden rounded-full border border-gray-200 bg-white/80 shadow-md backdrop-blur">
<Button
type="button"
variant="ghost"
size="icon"
className="size-9 rounded-none text-[#444746] hover:bg-[#f1f3f4] disabled:opacity-40"
disabled={openMailIndex <= 0}
onClick={goToPrev}
aria-label="Message plus récent"
>
<ChevronUp className="size-5" strokeWidth={1.5} />
</Button>
<span className="w-px shrink-0 self-stretch bg-border" aria-hidden />
<Button
type="button"
variant="ghost"
size="icon"
className="size-9 rounded-none text-[#444746] hover:bg-[#f1f3f4] disabled:opacity-40"
disabled={openMailIndex >= displayListEmails.length - 1}
onClick={goToNext}
aria-label="Message plus ancien"
>
<ChevronDown className="size-5" strokeWidth={1.5} />
</Button>
</div>
</div>
<EmailListEmailViewPane
data={data}
reading={reading}
selection={selection}
/>
</>
) : (
<>
{selectedFolder === "scheduled" && <EmailListScheduledBanner />}
{displayListEmails.length === 0 ? (
selectedFolder === "scheduled" ? (
<EmailListEmpty variant="scheduled" />
) : isSearchMode && searchParams ? (
<EmailListEmpty variant="search" searchParams={searchParams} />
) : (
<EmailListEmpty
variant="folder"
selectedFolder={selectedFolder}
inboxCategoryTabLabel={inboxCategoryTabLabel}
folderIdToLabel={sidebarNav.folderIdToLabel}
/>
)
) : (
<div
className={cn(
MAIL_LIST_ROW_DIVIDER_CLASS,
listToolbarMode && "sm:pb-14"
)}
>
{listEmails.map((email) => (
<EmailListRow key={email.id} email={email} {...rowPropsBase} />
))}
{data.scrollInfiniteList && data.hasMoreInfinite ? (
<div
ref={data.loadMoreSentinelRef}
className="h-px w-full shrink-0"
aria-hidden
/>
) : null}
{data.isFetchingNextInfinitePage ? (
<div className="flex justify-center py-3 text-xs text-muted-foreground">
Chargement
</div>
) : null}
</div>
)}
</>
)}
</div>
</div>
{listToolbarMode ? (
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-20 hidden sm:flex sm:justify-start">
<MailFolderStackIndicator
currentKey={mailNavVisitKey(selectedFolder, data.inboxTab)}
folderTree={sidebarNav.folderTree}
folderIdToLabel={sidebarNav.folderIdToLabel}
labelRows={sidebarNav.labelRows}
onNavigate={handleBreadcrumbNavigate}
className="pointer-events-auto"
/>
</div>
) : null}
</div>
)
}
export type { EmailListBodyProps }