feat: mobile-responsive mail plugin UI
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user