diff --git a/app/globals.css b/app/globals.css index 4386b06e..59b6d2db 100644 --- a/app/globals.css +++ b/app/globals.css @@ -3,6 +3,8 @@ @custom-variant dark (&:where(.dark, .dark *)); :root { + font-size: var(--font-size-base); + --color-border: #e2e8f0; --color-input: #e2e8f0; --color-ring: #94a3b8; @@ -79,7 +81,6 @@ body { system-ui, -apple-system, sans-serif; - font-size: var(--font-size-base); font-feature-settings: "rlig" 1, "calt" 1; diff --git a/components/email/email-list-item.tsx b/components/email/email-list-item.tsx index eadf3f2a..d7ce04a1 100644 --- a/components/email/email-list-item.tsx +++ b/components/email/email-list-item.tsx @@ -24,6 +24,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu }: Email const t = useTranslations('email_viewer'); const { selectedEmailIds, toggleEmailSelection, selectRangeEmails, selectedMailbox, clearSelection } = useEmailStore(); const showPreview = useSettingsStore((state) => state.showPreview); + const density = useSettingsStore((state) => state.density); const emailKeywords = useSettingsStore((state) => state.emailKeywords); const { identities } = useAuthStore(); const isChecked = selectedEmailIds.has(email.id); @@ -116,12 +117,14 @@ export function EmailListItem({ email, selected, onClick, onContextMenu }: Email )} {/* Avatar */} - + {density !== 'extra-compact' && ( + + )} {/* Content */}
@@ -183,7 +186,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu }: Email
{/* Third Line: Preview (controlled by showPreview setting) */} - {showPreview && ( + {showPreview && density !== 'extra-compact' && (

state.showPreview); const estimateSize = useCallback(() => { - const base = { compact: 60, regular: 84, comfortable: 104 }[density]; - return showPreview ? base + 36 : base; + const base = { 'extra-compact': 32, compact: 60, regular: 84, comfortable: 104 }[density]; + return (showPreview && density !== 'extra-compact') ? base + 36 : base; }, [density, showPreview]); const virtualizer = useVirtualizer({ diff --git a/components/email/thread-conversation-view.tsx b/components/email/thread-conversation-view.tsx index 3ea1867b..11248e0a 100644 --- a/components/email/thread-conversation-view.tsx +++ b/components/email/thread-conversation-view.tsx @@ -221,6 +221,7 @@ function EmailCard({ }: EmailCardProps) { const t = useTranslations(); const resolvedTheme = useThemeStore((state) => state.resolvedTheme); + const density = useSettingsStore((state) => state.density); const sender = email.from?.[0]; const isUnread = !email.keywords?.$seen; const isStarred = email.keywords?.$flagged; @@ -428,12 +429,14 @@ function EmailCard({ )} style={{ gap: 'var(--density-item-gap)', padding: 'var(--density-card-p)' }} > - + {density !== 'extra-compact' && ( + + )}

{formatDate(email.receivedAt)}
- {!isExpanded && ( + {!isExpanded && density !== 'extra-compact' && (

{email.preview || "No preview available"}

diff --git a/components/email/thread-email-item.tsx b/components/email/thread-email-item.tsx index cce9e20e..0355eb6f 100644 --- a/components/email/thread-email-item.tsx +++ b/components/email/thread-email-item.tsx @@ -7,6 +7,7 @@ import { Avatar } from "@/components/ui/avatar"; import { Paperclip, Star, Circle, CheckSquare, Square } from "lucide-react"; import { useEmailDrag } from "@/hooks/use-email-drag"; import { useEmailStore } from "@/stores/email-store"; +import { useSettingsStore } from "@/stores/settings-store"; interface ThreadEmailItemProps { email: Email; @@ -27,6 +28,7 @@ export function ThreadEmailItem({ const isStarred = email.keywords?.$flagged; const sender = email.from?.[0]; const { selectedMailbox, selectedEmailIds, toggleEmailSelection, selectRangeEmails, clearSelection } = useEmailStore(); + const density = useSettingsStore((state) => state.density); const isChecked = selectedEmailIds.has(email.id); const { dragHandlers, isDragging } = useEmailDrag({ @@ -104,12 +106,14 @@ export function ThreadEmailItem({ )} {/* Small Avatar */} - + {density !== 'extra-compact' && ( + + )} {/* Content */}
diff --git a/components/email/thread-list-item.tsx b/components/email/thread-list-item.tsx index 707ece1e..cc5acd47 100644 --- a/components/email/thread-list-item.tsx +++ b/components/email/thread-list-item.tsx @@ -42,6 +42,7 @@ const SingleEmailItem = React.forwardRef( const sender = email.from?.[0]; const { selectedMailbox, selectedEmailIds, toggleEmailSelection, selectRangeEmails, clearSelection } = useEmailStore(); const emailKeywords = useSettingsStore((state) => state.emailKeywords); + const density = useSettingsStore((state) => state.density); const isChecked = selectedEmailIds.has(email.id); // Resolve color and keyword definition from keyword definitions if not passed directly @@ -128,12 +129,14 @@ const SingleEmailItem = React.forwardRef(
)} - + {density !== 'extra-compact' && ( + + )}
@@ -185,7 +188,7 @@ const SingleEmailItem = React.forwardRef( {email.subject || "(no subject)"}
- {showPreview && ( + {showPreview && density !== 'extra-compact' && (

state.showPreview); + const density = useSettingsStore((state) => state.density); const isMobile = useUIStore((state) => state.isMobile); const { latestEmail, participantNames, hasUnread, hasStarred, hasAttachment, emailCount } = thread; @@ -377,12 +381,14 @@ export const ThreadListItem = React.forwardRef )} - + {density !== 'extra-compact' && ( + + )}

@@ -446,7 +452,7 @@ export const ThreadListItem = React.forwardRef - {showPreview && ( + {showPreview && density !== 'extra-compact' && (

= { + 'extra-compact': { py: 'py-0.5', gap: 'gap-1.5', showAvatar: false, showPreview: false }, + compact: { py: 'py-1', gap: 'gap-2', showAvatar: true, showPreview: false }, + regular: { py: 'py-2.5', gap: 'gap-3', showAvatar: true, showPreview: true }, + comfortable: { py: 'py-4', gap: 'gap-4', showAvatar: true, showPreview: true }, +}; + +function DensityPreview({ density }: { density: Density }) { + const cfg = DENSITY_PREVIEW[density]; + const rows = [ + { unread: true, sender: 'Alice Johnson', subject: 'Project update — Q1 roadmap', preview: 'Here are the latest numbers from…' }, + { unread: false, sender: 'Bob Smith', subject: 'Re: Meeting notes', preview: 'Thanks, will review and get back...' }, + { unread: true, sender: 'Carol Lee', subject: 'Invoice #4092', preview: 'Please find attached the invoice…' }, + ]; + + return ( +

+ {rows.map((row, i) => ( +
+ {cfg.showAvatar && ( +
+ )} +
+
+ + {row.sender} + + 12:00 +
+
+ {row.subject} +
+ {cfg.showPreview && ( +
{row.preview}
+ )} +
+
+ ))} +
+ ); +} export function AppearanceSettings() { const t = useTranslations('settings.appearance'); @@ -49,14 +102,16 @@ export function AppearanceSettings() { - updateSetting('density', value as 'compact' | 'regular' | 'comfortable') + updateSetting('density', value as Density) } options={[ + { value: 'extra-compact', label: t('list_density.extra_compact') }, { value: 'compact', label: t('list_density.compact') }, { value: 'regular', label: t('list_density.regular') }, { value: 'comfortable', label: t('list_density.comfortable') }, ]} /> + {/* Toolbar Position */} diff --git a/locales/de/common.json b/locales/de/common.json index 44c03db4..bac553fe 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -512,6 +512,7 @@ "list_density": { "label": "Dichte", "description": "Abstände in der Benutzeroberfläche steuern", + "extra_compact": "Sehr kompakt", "compact": "Kompakt", "regular": "Normal", "comfortable": "Komfortabel" diff --git a/locales/en/common.json b/locales/en/common.json index 0dd03897..77a2aca5 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -512,6 +512,7 @@ "list_density": { "label": "Density", "description": "Control spacing and padding across the UI", + "extra_compact": "Extra Compact", "compact": "Compact", "regular": "Regular", "comfortable": "Comfortable" diff --git a/locales/es/common.json b/locales/es/common.json index 192ff548..5ff45f8e 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -512,6 +512,7 @@ "list_density": { "label": "Densidad", "description": "Controle el espaciado en toda la interfaz", + "extra_compact": "Extra compacto", "compact": "Compacto", "regular": "Regular", "comfortable": "Cómodo" diff --git a/locales/fr/common.json b/locales/fr/common.json index c0806d7b..92201d49 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -512,6 +512,7 @@ "list_density": { "label": "Densité", "description": "Contrôlez l'espacement dans toute l'interface", + "extra_compact": "Très compacte", "compact": "Compacte", "regular": "Normale", "comfortable": "Confortable" diff --git a/locales/it/common.json b/locales/it/common.json index 6b0a85de..dea325ef 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -512,6 +512,7 @@ "list_density": { "label": "Densità", "description": "Controlla la spaziatura in tutta l'interfaccia", + "extra_compact": "Molto compatto", "compact": "Compatto", "regular": "Normale", "comfortable": "Comodo" diff --git a/locales/ja/common.json b/locales/ja/common.json index a989e717..cddaf5c3 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -512,6 +512,7 @@ "list_density": { "label": "密度", "description": "UI全体の間隔を調整", + "extra_compact": "極小", "compact": "コンパクト", "regular": "標準", "comfortable": "ゆったり" diff --git a/locales/nl/common.json b/locales/nl/common.json index 9aac4f48..645137da 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -512,6 +512,7 @@ "list_density": { "label": "Dichtheid", "description": "Regeleer de ruimte in de gehele interface", + "extra_compact": "Extra compact", "compact": "Compact", "regular": "Normaal", "comfortable": "Comfortabel" diff --git a/locales/pt/common.json b/locales/pt/common.json index ee55b304..4befd9fd 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -512,6 +512,7 @@ "list_density": { "label": "Densidade", "description": "Controle o espaçamento em toda a interface", + "extra_compact": "Extra compacto", "compact": "Compacto", "regular": "Regular", "comfortable": "Confortável" diff --git a/stores/settings-store.ts b/stores/settings-store.ts index f858163a..3ba2435f 100644 --- a/stores/settings-store.ts +++ b/stores/settings-store.ts @@ -19,7 +19,7 @@ let isLoadingFromServer = false; const SYNC_DEBOUNCE_MS = 2000; export type FontSize = 'small' | 'medium' | 'large'; -export type Density = 'compact' | 'regular' | 'comfortable'; +export type Density = 'extra-compact' | 'compact' | 'regular' | 'comfortable'; /** @deprecated Use Density instead */ export type ListDensity = Density; export type DeleteAction = 'trash' | 'permanent'; @@ -438,6 +438,14 @@ function applyDensity(density: Density) { const root = document.documentElement; const densityValues = { + 'extra-compact': { + '--list-item-height': 'auto', + '--density-item-py': '2px', + '--density-item-gap': '6px', + '--density-header-py': '4px', + '--density-card-p': '8px', + '--density-sidebar-py': '0px', + }, compact: { '--list-item-height': 'auto', '--density-item-py': '4px',