Enhance email components with dynamic accent colors for active tabs and improved styling. Updated CompactInboxCategoryTabs and EmailList to utilize new accent color logic, refined sidebar icons, and adjusted layout for better responsiveness in split view.

This commit is contained in:
R3D347HR4Y 2026-05-18 14:38:27 +02:00
parent 2eda0d4181
commit 18cb1257f4
6 changed files with 95 additions and 36 deletions

View File

@ -33,6 +33,7 @@ import {
DEFAULT_INBOX_TAB,
type MailRouteState,
} from "@/lib/mail-url"
import { cn } from "@/lib/utils"
function segmentsFromPathname(pathname: string | null): string[] | undefined {
if (!pathname?.startsWith("/mail")) return undefined
@ -120,7 +121,12 @@ function MailAppInner() {
/>
</div>
) : null}
<div className="relative flex min-h-0 flex-1 gap-0 overflow-hidden bg-app-canvas pl-0 pr-0 pb-1 pt-1 sm:gap-1 sm:pl-1">
<div
className={cn(
"relative flex min-h-0 flex-1 gap-0 overflow-hidden pl-0 pr-0",
splitView ? "bg-white p-0" : "bg-app-canvas pb-1 pt-1 sm:gap-1 sm:pl-1"
)}
>
{!sidebarCollapsed && touchNav && (
<button
type="button"
@ -145,7 +151,12 @@ function MailAppInner() {
folderUnreadCounts={folderUnreadCounts}
splitView={splitView}
/>
<main className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-none bg-white shadow-sm sm:rounded-2xl">
<main
className={cn(
"flex min-h-0 flex-1 flex-col overflow-hidden bg-white",
splitView ? "rounded-none shadow-none" : "rounded-none shadow-sm sm:rounded-2xl"
)}
>
<Suspense>
<EmailList
selectedFolder={route.folderId}
@ -160,7 +171,14 @@ function MailAppInner() {
/>
</Suspense>
</main>
<RightPanel />
<div
className={cn(
"flex shrink-0 flex-col",
splitView && "border-l border-gray-200"
)}
>
<RightPanel />
</div>
</div>
{!splitView ? (
<MobileBottomBar

View File

@ -2,6 +2,7 @@
import { memo, useEffect, useLayoutEffect, useRef, useState } from "react"
import { Icon } from "@iconify/react"
import { inboxTabActiveAccentColor } from "@/lib/inbox-category-tabs"
import { inboxTabShowsInactiveMeta } from "@/lib/mail-url"
import { cn } from "@/lib/utils"
@ -84,22 +85,31 @@ export const CompactInboxCategoryTabs = memo(function CompactInboxCategoryTabs({
}
}, [displayedTabId, tabs])
const activeTab = tabs.find((t) => t.id === displayedTabId)
const activeAccentColor = activeTab
? inboxTabActiveAccentColor(activeTab.id, activeTab.badgeColor)
: "#0b57d0"
return (
<div ref={barRef} className="relative flex w-full min-w-0">
<span
aria-hidden
className={cn(
"pointer-events-none absolute bottom-0 left-0 z-[2] h-[3px] origin-left rounded-t-sm bg-[#0b57d0]",
"will-change-[transform,width] transition-[transform,width] duration-300 ease-[cubic-bezier(0.2,0,0,1)] motion-reduce:transition-none",
"pointer-events-none absolute bottom-0 left-0 z-[2] h-[3px] origin-left rounded-t-sm",
"will-change-[transform,width] transition-[transform,width,background-color] duration-300 ease-[cubic-bezier(0.2,0,0,1)] motion-reduce:transition-none",
indicator.ready ? "opacity-100" : "opacity-0"
)}
style={{
width: indicator.width,
transform: `translate3d(${indicator.x}px, 0, 0)`,
backgroundColor: activeAccentColor,
}}
/>
{tabs.map((tab) => {
const isActive = displayedTabId === tab.id
const accentColor = isActive
? inboxTabActiveAccentColor(tab.id, tab.badgeColor)
: undefined
const unseen = unseenInTabById[tab.id] ?? 0
const showMeta =
inboxTabShowsInactiveMeta(tab.id) && !isActive && unseen > 0
@ -135,8 +145,9 @@ export const CompactInboxCategoryTabs = memo(function CompactInboxCategoryTabs({
className={cn(
TAB_ICON_CLASS,
"transition-colors duration-200 motion-reduce:transition-none",
isActive ? "text-[#0b57d0]" : "text-[#5f6368]"
!isActive && "text-[#5f6368]"
)}
style={accentColor ? { color: accentColor } : undefined}
aria-hidden
/>
{showMeta && unseen > 0 ? (
@ -147,7 +158,10 @@ export const CompactInboxCategoryTabs = memo(function CompactInboxCategoryTabs({
) : null}
</div>
{isActive ? (
<span className="shrink-0 whitespace-nowrap text-[13px] font-semibold leading-tight text-[#0b57d0]">
<span
className="shrink-0 whitespace-nowrap text-[13px] font-semibold leading-tight"
style={{ color: accentColor }}
>
{tab.label}
</span>
) : null}

View File

@ -129,6 +129,7 @@ import {
} from "@/lib/mail-folder-display"
import {
buildInboxCategoryTabIcons,
inboxTabActiveAccentColor,
resolveEmailInboxCategoryTabs,
} from "@/lib/inbox-category-tabs"
import {
@ -2981,6 +2982,9 @@ export function EmailList({
>
{inboxTabBarItems.map((tab) => {
const isActive = activeInboxTabId === tab.id
const accentColor = isActive
? inboxTabActiveAccentColor(tab.id, tab.badgeColor)
: undefined
const unseen = unseenInTabById[tab.id] ?? 0
const senderLine = tabUnseenSenderLineById[tab.id] ?? ""
const showMeta =
@ -2994,11 +2998,15 @@ export function EmailList({
aria-label={tab.label}
aria-current={isActive ? "true" : undefined}
onClick={() => handleCategoryInboxTabClick(tab.id)}
style={
accentColor
? { boxShadow: `inset 0 -3px 0 0 ${accentColor}` }
: undefined
}
className={cn(
"relative z-[1] flex cursor-pointer transition-colors",
"min-w-0 w-full overflow-hidden max-sm:min-h-10 max-sm:items-center max-sm:justify-center",
"sm:min-h-14 sm:items-center sm:py-2 sm:text-left",
isActive && "shadow-[inset_0_-3px_0_0_#0b57d0]",
!isActive && "hover:bg-[#f1f3f4]"
)}
>
@ -3009,8 +3017,9 @@ export function EmailList({
icon={tab.icon}
className={cn(
CATEGORY_TAB_ICON_CLASS,
isActive ? "text-[#0b57d0]" : "text-[#5f6368]"
!isActive && "text-[#5f6368]"
)}
style={accentColor ? { color: accentColor } : undefined}
aria-hidden
/>
{showMeta && unseen > 0 ? (
@ -3028,8 +3037,9 @@ export function EmailList({
className={cn(
CATEGORY_TAB_ICON_CLASS,
"self-center",
isActive ? "text-[#0b57d0]" : "text-[#5f6368]"
!isActive && "text-[#5f6368]"
)}
style={accentColor ? { color: accentColor } : undefined}
aria-hidden
/>
<div className="flex min-w-0 w-0 flex-1 flex-col gap-px">
@ -3042,8 +3052,9 @@ export function EmailList({
<span
className={cn(
"min-w-0 flex-1 truncate text-[13px] font-semibold leading-tight",
isActive ? "text-[#0b57d0]" : "text-[#3c4043]"
!isActive && "text-[#3c4043]"
)}
style={accentColor ? { color: accentColor } : undefined}
>
{tab.label}
</span>
@ -3080,8 +3091,7 @@ export function EmailList({
!splitView && isViewMode && openEmail
? "relative flex min-h-0 flex-1 flex-col overflow-hidden"
: mainScrollClass,
"relative min-h-0 flex-1 overscroll-y-none max-sm:pb-16",
listToolbarMode && "sm:pb-12"
"relative min-h-0 flex-1 overscroll-y-none max-sm:pb-16"
)}
>
{listToolbarMode && (
@ -3286,7 +3296,7 @@ export function EmailList({
!splitView &&
"md:flex md:items-start md:gap-2 md:px-2 md:py-1.5",
isSplitActiveRow
? "z-[1] bg-[#e8f0fe] shadow-[inset_3px_0_0_0_#0b57d0]"
? "z-[1] bg-[#e8f0fe] shadow-[inset_3px_0_0_0_#669df6]"
: isSelected
? "bg-[#e8f0fe]"
: isRead
@ -4527,16 +4537,14 @@ export function EmailList({
</div>
</div>
{listToolbarMode ? (
<div className="pointer-events-none absolute bottom-0 left-0 z-20 hidden max-w-full sm:block">
<div className="pointer-events-auto w-fit max-w-full">
<MailFolderStackIndicator
currentKey={mailNavVisitKey(selectedFolder, inboxTab)}
folderTree={sidebarNav.folderTree}
folderIdToLabel={sidebarNav.folderIdToLabel}
labelRows={sidebarNav.labelRows}
onNavigate={handleBreadcrumbNavigate}
/>
</div>
<div className="hidden w-fit max-w-full shrink-0 self-start sm:block">
<MailFolderStackIndicator
currentKey={mailNavVisitKey(selectedFolder, inboxTab)}
folderTree={sidebarNav.folderTree}
folderIdToLabel={sidebarNav.folderIdToLabel}
labelRows={sidebarNav.labelRows}
onNavigate={handleBreadcrumbNavigate}
/>
</div>
) : null}
</div>

View File

@ -7,7 +7,6 @@ import {
ClockArrowUp,
Send,
FileText,
Tag,
ChevronDown,
GripVertical,
Pencil,
@ -141,7 +140,7 @@ const mainItems = [
{ id: "inbox", label: "Boîte de réception", icon: Inbox },
{ id: "starred", label: "Messages suivis", icon: Star },
{ id: "snoozed", label: "En attente", icon: Clock },
{ id: "important", label: "Important", icon: Tag },
{ id: "important", label: "Important", icon: "mdi:label-variant-outline" },
{ id: "sent", label: "Messages envoyés", icon: Send },
{ id: "drafts", label: "Brouillons", icon: FileText },
{ id: "scheduled", label: "Planifié", icon: ClockArrowUp },
@ -1015,12 +1014,16 @@ export function Sidebar({
isSelected,
unreadCount,
}: {
item: { id: string; label: string; icon: React.ElementType }
item: { id: string; label: string; icon: React.ElementType | string }
isSelected: boolean
unreadCount: number
}) => {
const { isOver, dropHandlers } = useEmailDropTarget(item.id, item.label)
const hasUnread = unreadCount > 0
const iconClassName = cn(
"h-5 w-5 shrink-0",
hasUnread && !isSelected && "text-gray-900"
)
return (
<button
onClick={() => onSelectFolder(item.id)}
@ -1038,12 +1041,11 @@ export function Sidebar({
: "text-gray-700 hover:bg-gray-100"
)}
>
<item.icon
className={cn(
"h-5 w-5 shrink-0",
hasUnread && !isSelected && "text-gray-900"
)}
/>
{typeof item.icon === "string" ? (
<Icon icon={item.icon} className={iconClassName} aria-hidden />
) : (
<item.icon className={iconClassName} />
)}
{isExpanded && (
<div className="flex min-w-0 flex-1 items-baseline gap-4">
<span
@ -2450,6 +2452,7 @@ export function Sidebar({
className={cn(
"absolute left-0 top-0 bottom-0 flex flex-col overflow-hidden bg-app-canvas transition-[width,transform] duration-200 z-40 select-none",
isExpanded ? "w-60" : "w-[68px]",
splitView && "border-r border-gray-200",
!touchNav && hoverExpanded && "shadow-xl border-r border-gray-200",
isOverlayOpen && "z-50 shadow-xl border-r border-gray-200",
collapsed && isXs && "-translate-x-full pointer-events-none"

View File

@ -1,10 +1,15 @@
import type { Email } from "@/lib/email-data"
import { navFolderIconColorFromBgClass } from "@/lib/label-pill-contrast"
import {
emailMatchesFolder,
type MailFolderFilterCtx,
type MailNavFolderMaps,
} from "@/lib/mail-folder-filter"
import { DEFAULT_INBOX_TAB, INBOX_ALL_TAB } from "@/lib/mail-url"
import {
DEFAULT_INBOX_TAB,
INBOX_ALL_TAB,
normalizeInboxTabSegment,
} from "@/lib/mail-url"
import {
tabbedInboxLabelRows,
type LabelRowItem,
@ -17,6 +22,15 @@ export type InboxCategoryTabIcon = {
badgeColor: string
}
/** Couleur icône / libellé / soulignement de longlet boîte actif. */
export function inboxTabActiveAccentColor(
tabId: string,
badgeColor: string
): string {
if (normalizeInboxTabSegment(tabId) === INBOX_ALL_TAB) return "#202124"
return navFolderIconColorFromBgClass(badgeColor)
}
/** Onglets catégorie boîte (Principale + libellés tabbed), hors « Tous les messages ». */
export function buildInboxCategoryTabIcons(
labelRows: readonly LabelRowItem[]

View File

@ -3,7 +3,6 @@ import {
Inbox,
Star,
Clock,
Tag,
Send,
FileText,
ClockArrowUp,
@ -26,7 +25,6 @@ const SYSTEM_ICONS: Record<string, LucideIcon> = {
inbox: Inbox,
starred: Star,
snoozed: Clock,
important: Tag,
sent: Send,
drafts: FileText,
scheduled: ClockArrowUp,
@ -55,6 +53,10 @@ export function resolveMailNavIcon(
return { kind: "lucide", Icon: Inbox }
}
if (folderId === "important") {
return { kind: "iconify", icon: "mdi:label-variant-outline" }
}
const system = SYSTEM_ICONS[folderId]
if (system) return { kind: "lucide", Icon: system }