feat: add extra-compact density option and fix font size scaling

- Add 'extra-compact' density level that hides avatars and preview
  text, showing only sender and subject for maximum information density
- Add visual density preview in appearance settings showing a 3-row
  email list mockup that reflects each density option
- Fix global font size setting only changing line height by moving
  font-size from body to :root so rem-based Tailwind classes scale
- Apply extra-compact behavior to email list items, thread list items,
  thread email items, and thread conversation view (EmailCard)
- Update email list virtualizer size estimates for extra-compact
- Add extra-compact translations for all 8 locales
This commit is contained in:
Linus Rath
2026-03-14 18:59:00 +01:00
parent 838de8e5da
commit d5404de224
16 changed files with 128 additions and 40 deletions
+20 -14
View File
@@ -42,6 +42,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
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<HTMLDivElement, SingleEmailItemProps>(
</div>
)}
<Avatar
name={sender?.name}
email={sender?.email}
size="md"
className="flex-shrink-0 shadow-sm"
/>
{density !== 'extra-compact' && (
<Avatar
name={sender?.name}
email={sender?.email}
size="md"
className="flex-shrink-0 shadow-sm"
/>
)}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2 mb-1">
@@ -185,7 +188,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
{email.subject || "(no subject)"}
</div>
{showPreview && (
{showPreview && density !== 'extra-compact' && (
<p className={cn(
"text-sm leading-relaxed line-clamp-2",
isUnread
@@ -216,6 +219,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
}, ref) {
const t = useTranslations('threads');
const showPreview = useSettingsStore((state) => 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<HTMLDivElement, ThreadListItemPro
</div>
)}
<Avatar
name={latestEmail.from?.[0]?.name}
email={latestEmail.from?.[0]?.email}
size="md"
className="flex-shrink-0 shadow-sm"
/>
{density !== 'extra-compact' && (
<Avatar
name={latestEmail.from?.[0]?.name}
email={latestEmail.from?.[0]?.email}
size="md"
className="flex-shrink-0 shadow-sm"
/>
)}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2 mb-1">
@@ -446,7 +452,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
{latestEmail.subject || "(no subject)"}
</div>
{showPreview && (
{showPreview && density !== 'extra-compact' && (
<p className={cn(
"text-sm leading-relaxed line-clamp-2",
hasUnread