feat: mobile-responsive mail plugin UI

This commit is contained in:
Agent Zero
2026-07-15 17:53:56 +02:00
parent 57b6df5357
commit de74429f4e
7 changed files with 127 additions and 38 deletions
@@ -151,7 +151,7 @@ export function ComposeModal({
const title = mode === 'reply' ? t('mail.reply') : mode === 'forward' ? t('mail.forward') : t('mail.compose');
return (
<Modal open={open} onClose={onClose} title={title} size="xl" closeOnBackdrop={false}>
<Modal open={open} onClose={onClose} title={title} size="xl" closeOnBackdrop={false} fullScreenMobile>
<div className="space-y-4" data-testid="compose-modal">
{/* Toolbar */}
<div className="flex items-center gap-1 border-b border-secondary-200 pb-2" data-testid="compose-toolbar">
@@ -225,7 +225,7 @@ export function ComposeModal({
{t('mail.showCcBcc')}
</button>
) : (
<div className="w-full grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="w-full grid grid-cols-1 sm:grid-cols-2 gap-2">
<Input
label={t('mail.cc')}
value={cc}
@@ -278,7 +278,7 @@ export function ComposeModal({
/>
{/* Actions */}
<div className="flex justify-end gap-2 pt-2 border-t border-secondary-200">
<div className="flex justify-end gap-2 pt-2 border-t border-secondary-200 sticky bottom-0 bg-white py-3">
<Button variant="secondary" onClick={onClose} type="button">
{t('common.cancel')}
</Button>
+19 -19
View File
@@ -91,7 +91,7 @@ export function MailDetail({
return (
<div className="flex flex-col h-full" data-testid="mail-detail">
{/* Toolbar */}
<div className="flex items-center gap-2 px-4 py-3 border-b border-secondary-200" data-testid="mail-detail-toolbar">
<div className="flex items-center gap-1 sm:gap-2 px-3 py-2 md:px-4 md:py-3 border-b border-secondary-200 overflow-x-auto" data-testid="mail-detail-toolbar">
<Button variant="secondary" size="sm" onClick={() => onReply(mail)} icon={
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
@@ -135,9 +135,9 @@ export function MailDetail({
</div>
{/* Headers */}
<div className="px-4 py-3 border-b border-secondary-200" data-testid="mail-detail-headers">
<div className="flex items-start justify-between gap-4">
<h2 className="text-lg font-semibold text-secondary-900 flex-1">{mail.subject || t('mail.noSubject')}</h2>
<div className="px-3 py-2 md:px-4 md:py-3 border-b border-secondary-200" data-testid="mail-detail-headers">
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-2 sm:gap-4">
<h2 className="text-base md:text-lg font-semibold text-secondary-900 flex-1">{mail.subject || t('mail.noSubject')}</h2>
{mail.labels && mail.labels.length > 0 && (
<div className="flex gap-1 flex-shrink-0">
{mail.labels.map((label) => (
@@ -148,30 +148,30 @@ export function MailDetail({
</div>
)}
</div>
<div className="mt-2 space-y-1 text-sm text-secondary-600">
<div className="flex gap-2">
<span className="font-medium text-secondary-500">{t('mail.from')}:</span>
<span>{mail.from_name ? `${mail.from_name} <${mail.from_address}>` : mail.from_address}</span>
<div className="mt-2 space-y-1 text-xs md:text-sm text-secondary-600">
<div className="flex flex-col sm:flex-row sm:gap-2">
<span className="font-medium text-secondary-500 flex-shrink-0">{t('mail.from')}:</span>
<span className="break-words">{mail.from_name ? `${mail.from_name} <${mail.from_address}>` : mail.from_address}</span>
</div>
<div className="flex gap-2">
<span className="font-medium text-secondary-500">{t('mail.to')}:</span>
<span>{mail.to_addresses.join(', ')}</span>
<div className="flex flex-col sm:flex-row sm:gap-2">
<span className="font-medium text-secondary-500 flex-shrink-0">{t('mail.to')}:</span>
<span className="break-words">{mail.to_addresses.join(', ')}</span>
</div>
{mail.cc_addresses.length > 0 && (
<div className="flex gap-2">
<span className="font-medium text-secondary-500">{t('mail.cc')}:</span>
<span>{mail.cc_addresses.join(', ')}</span>
<div className="flex flex-col sm:flex-row sm:gap-2">
<span className="font-medium text-secondary-500 flex-shrink-0">{t('mail.cc')}:</span>
<span className="break-words">{mail.cc_addresses.join(', ')}</span>
</div>
)}
<div className="flex gap-2">
<span className="font-medium text-secondary-500">{t('mail.date')}:</span>
<div className="flex flex-col sm:flex-row sm:gap-2">
<span className="font-medium text-secondary-500 flex-shrink-0">{t('mail.date')}:</span>
<span>{formatFullDate(mail.date)}</span>
</div>
</div>
</div>
{/* Body */}
<div className="flex-1 overflow-y-auto px-4 py-4" data-testid="mail-detail-body">
<div className="flex-1 overflow-y-auto px-3 py-3 md:px-4 md:py-4" data-testid="mail-detail-body">
{safeHtml && isSafe ? (
<div
className="prose prose-sm max-w-none text-secondary-800"
@@ -190,9 +190,9 @@ export function MailDetail({
{/* Attachments */}
{mail.attachments && mail.attachments.length > 0 && (
<div className="px-4 py-3 border-t border-secondary-200" data-testid="mail-detail-attachments">
<div className="px-3 py-2 md:px-4 md:py-3 border-t border-secondary-200" data-testid="mail-detail-attachments">
<h3 className="text-sm font-semibold text-secondary-700 mb-2">{t('mail.attachments')}</h3>
<ul className="space-y-2">
<ul className="space-y-1 md:space-y-2">
{mail.attachments.map((att) => (
<li key={att.id} className="flex items-center gap-3 p-2 rounded-md hover:bg-secondary-50 min-h-touch">
<svg className="w-5 h-5 text-secondary-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
@@ -147,7 +147,7 @@ function FolderNode({
<button
onClick={() => onSelect(folder.id)}
className={clsx(
'flex-1 flex items-center gap-2 px-2 py-1.5 rounded-md text-sm min-h-touch',
'flex-1 flex items-center gap-2 px-2 py-2 md:py-1.5 rounded-md text-sm min-h-touch',
'motion-safe:transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500',
isSelected
? 'bg-primary-50 text-primary-700 font-medium'
@@ -203,7 +203,7 @@ function AccountNode({
<div role="treeitem" aria-label={account.email}>
<button
onClick={() => setExpanded(!expanded)}
className="w-full flex items-center gap-1 px-1 py-1.5 rounded-md text-sm font-semibold text-secondary-900 hover:bg-secondary-50"
className="w-full flex items-center gap-1 px-1 py-2 md:py-1.5 rounded-md text-sm font-semibold text-secondary-900 hover:bg-secondary-50 min-h-touch"
data-testid={`account-${account.id}`}
>
<ChevronIcon expanded={expanded} />
+2 -2
View File
@@ -73,7 +73,7 @@ export function MailList({
<button
onClick={() => onSelectMail(mail)}
className={clsx(
'w-full text-left px-4 py-3 hover:bg-secondary-50 min-h-touch motion-safe:transition-colors',
'w-full text-left px-3 py-2 md:px-4 md:py-3 hover:bg-secondary-50 min-h-touch motion-safe:transition-colors',
'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500',
mail.id === selectedMailId && 'bg-primary-50',
!mail.is_seen && 'font-semibold',
@@ -102,7 +102,7 @@ export function MailList({
</span>
<span className="text-xs text-secondary-400 flex-shrink-0">{formatDate(mail.date)}</span>
</div>
<p className={clsx('text-sm truncate mt-0.5', !mail.is_seen ? 'text-secondary-800' : 'text-secondary-600')}>
<p className={clsx('text-xs md:text-sm truncate mt-0.5', !mail.is_seen ? 'text-secondary-800' : 'text-secondary-600')}>
{mail.subject || t('mail.noSubject')}
</p>
{mail.labels && mail.labels.length > 0 && (