diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts index 5c4129c..e38ec4a 100644 --- a/web/src/locales/de.ts +++ b/web/src/locales/de.ts @@ -681,6 +681,7 @@ export const catalog: Catalog = { "Shortcuts": "Kürzel", "Go to": "Gehe zu", "Menu": "Menü", + "Close menu": "Menü schließen", "Options": "Optionen", "Send options": "Sendeoptionen", "Name": "Name", diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts index db0b982..e9603f4 100644 --- a/web/src/locales/es.ts +++ b/web/src/locales/es.ts @@ -692,6 +692,7 @@ export const catalog: Catalog = { "Shortcuts": "Atajos", "Go to": "Ir a", "Menu": "Menú", + "Close menu": "Cerrar el menú", "Options": "Opciones", "Send options": "Opciones de envío", "Name": "Nombre", diff --git a/web/src/locales/fr.ts b/web/src/locales/fr.ts index 7f34517..4dda9f8 100644 --- a/web/src/locales/fr.ts +++ b/web/src/locales/fr.ts @@ -697,6 +697,7 @@ export const catalog: Catalog = { "Shortcuts": "Raccourcis", "Go to": "Aller à", "Menu": "Menu", + "Close menu": "Fermer le menu", "Options": "Options", "Send options": "Options d'envoi", "Name": "Nom", diff --git a/web/src/locales/ja.ts b/web/src/locales/ja.ts index 3f09ead..9ff773e 100644 --- a/web/src/locales/ja.ts +++ b/web/src/locales/ja.ts @@ -694,6 +694,7 @@ export const catalog: Catalog = { "Shortcuts": "ショートカット", "Go to": "移動", "Menu": "メニュー", + "Close menu": "メニューを閉じる", "Options": "オプション", "Send options": "送信オプション", "Name": "名前", diff --git a/web/src/locales/nl.ts b/web/src/locales/nl.ts index 6478b80..c292297 100644 --- a/web/src/locales/nl.ts +++ b/web/src/locales/nl.ts @@ -688,6 +688,7 @@ export const catalog: Catalog = { "Shortcuts": "Sneltoetsen", "Go to": "Ga naar", "Menu": "Menu", + "Close menu": "Menu sluiten", "Options": "Opties", "Send options": "Verzendopties", "Name": "Naam", diff --git a/web/src/locales/pt-BR.ts b/web/src/locales/pt-BR.ts index b281538..657ee93 100644 --- a/web/src/locales/pt-BR.ts +++ b/web/src/locales/pt-BR.ts @@ -695,6 +695,7 @@ export const catalog: Catalog = { "Shortcuts": "Atalhos", "Go to": "Ir para", "Menu": "Menu", + "Close menu": "Fechar o menu", "Options": "Opções", "Send options": "Opções de envio", "Name": "Nome", diff --git a/web/src/locales/ru.ts b/web/src/locales/ru.ts index 09daeb3..c344a71 100644 --- a/web/src/locales/ru.ts +++ b/web/src/locales/ru.ts @@ -694,6 +694,7 @@ export const catalog: Catalog = { "Shortcuts": "Сочетания", "Go to": "Перейти", "Menu": "Меню", + "Close menu": "Закрыть меню", "Options": "Параметры", "Send options": "Параметры отправки", "Name": "Имя", diff --git a/web/src/locales/uk.ts b/web/src/locales/uk.ts index a7d43c8..87a72bc 100644 --- a/web/src/locales/uk.ts +++ b/web/src/locales/uk.ts @@ -688,6 +688,7 @@ export const catalog: Catalog = { "Shortcuts": "Сполучення", "Go to": "Перейти", "Menu": "Меню", + "Close menu": "Закрити меню", "Options": "Параметри", "Send options": "Параметри надсилання", "Name": "Ім'я", diff --git a/web/src/locales/zh-Hans.ts b/web/src/locales/zh-Hans.ts index 26c4e2b..ff87e11 100644 --- a/web/src/locales/zh-Hans.ts +++ b/web/src/locales/zh-Hans.ts @@ -693,6 +693,7 @@ export const catalog: Catalog = { "Shortcuts": "快捷键", "Go to": "转到", "Menu": "菜单", + "Close menu": "关闭菜单", "Options": "选项", "Send options": "发送选项", "Name": "名称", diff --git a/web/src/styles/app.css b/web/src/styles/app.css index 1c08869..e7f5fee 100644 --- a/web/src/styles/app.css +++ b/web/src/styles/app.css @@ -449,15 +449,24 @@ a.menu-item:hover { color: var(--fg); } .nav-item.folder-row .nav-twisty { position: absolute; left: calc(8px + var(--folder-indent)); top: 50%; transform: translateY(-50%); margin: 0; } /* Labels sit in the same column: a 20px slot matching the folder icons. */ /* ---- Drill-down (phones; see mail/MailboxTree.tsx) ---- */ -/* No indent to pay for, so the name gets the width the tree was spending on - depth. The twisty's 30px gutter goes too -- nothing is drawn in it here. */ -.nav-item.folder-row.has-drill { padding-left: 12px; } +/* + * No indent to pay for, so the name gets the width the tree was spending on + * depth. The twisty's 30px gutter goes with it -- nothing is ever drawn in it + * here, whether or not the folder has children. + * + * Scoped to the list rather than to the rows that offer a drill, which is the + * distinction the first cut got wrong: only folders WITH children carried the + * class, so they sat 18px left of every folder without any and the column of + * icons came apart. Whether a folder has children is not a reason to hang it + * somewhere else. + */ +.folder-drill .nav-item.folder-row { padding-left: 12px; } .nav-item.folder-row .drill-into { width: 44px; height: 100%; border-radius: 0; margin-right: -12px; color: var(--fg-faint); } .nav-item.folder-row .drill-into:hover { background: var(--bg-hover); color: var(--fg); } /* The row it belongs to is a link and paints its own active state; a second filled rectangle inside it reads as a separate selected thing. */ .nav-item.folder-row.active .drill-into { color: inherit; } -.drill-back { width: 100%; border: 0; background: none; font: inherit; text-align: left; cursor: pointer; color: var(--fg-muted); padding-left: 8px; } +.drill-back { width: 100%; border: 0; background: none; font: inherit; text-align: left; cursor: pointer; color: var(--fg-muted); padding-left: 12px; } .drill-back:hover { background: var(--bg-hover); color: var(--fg); } .drill-back svg { flex: 0 0 auto; } .nav-item.folder-row .nav-label-color { flex: 0 0 20px; width: 20px; height: 20px; border-radius: 0; display: inline-flex; align-items: center; justify-content: center; } @@ -1058,6 +1067,11 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); } .mobile-tabbar a.active { color: var(--accent); } .main { padding-bottom: calc(60px + env(safe-area-inset-bottom)); } .sidebar { padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); } + /* Lines the close up with the hamburger underneath it: same height as the + top bar, same 12px in from the edge, so the second press lands where the + first one did. The negative margin cancels the drawer's own padding-top, + which would otherwise push it a row lower than the thing it replaces. */ + .drawer-head { display: flex; align-items: center; height: var(--topbar-h); margin-top: -12px; padding-left: max(6px, env(safe-area-inset-left)); } .thread-subject { padding: 14px 16px 8px; } .message { margin: 0 8px 8px; } .message-head { padding: 10px 12px; } @@ -1108,7 +1122,9 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); } .sidebar .nav-item, .cal-list-item { height: 44px; } /* The hamburger is the one control in the corner a thumb is worst at, so it gets real distance from the bezel rather than just a wider hit area. */ - .topbar { padding-left: max(12px, env(safe-area-inset-left)); } + /* Both, together: the close in the drawer stands exactly on the hamburger it + covers, and it only does that if the two are moved by the same rule. */ + .topbar, .drawer-head { padding-left: max(12px, env(safe-area-inset-left)); } } @media (hover: none) { .msg-row .msg-check { opacity: 1; } diff --git a/web/src/views/AppShell.tsx b/web/src/views/AppShell.tsx index dccb1ec..69b1b23 100644 --- a/web/src/views/AppShell.tsx +++ b/web/src/views/AppShell.tsx @@ -1,6 +1,6 @@ import { useEffect, useState, type ReactNode } from "react"; import { Link, useLocation } from "wouter"; -import { BookOpen, Calendar, ChevronsUpDown, FolderOpen, Globe, HelpCircle, LogOut, Mail, Menu as MenuIcon, Moon, PenSquare, Plus, RefreshCw, Settings, Sun, Upload, Users } from "lucide-react"; +import { BookOpen, Calendar, ChevronsUpDown, FolderOpen, Globe, HelpCircle, LogOut, Mail, Menu as MenuIcon, Moon, PenSquare, Plus, RefreshCw, Settings, Sun, Upload, Users, X } from "lucide-react"; import { useSession } from "@/store/session"; import { toggleTarget, useEffectiveTheme, useSettings } from "@/store/settings"; import { useMail } from "@/store/mail"; @@ -40,6 +40,14 @@ export function AppShell({ children }: { children: ReactNode }) { useGlobalShortcuts({ onHelp: () => setHelpOpen(true) }); useEffect(() => setDrawer(false), [location]); + // Escape closes it too, for the tablet with a keyboard attached. + useEffect(() => { + if (!drawer) return; + const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setDrawer(false); }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [drawer]); + // Deep link: /mail?compose=new (PWA shortcut) / mailto handler useEffect(() => { const params = new URLSearchParams(window.location.search); @@ -68,7 +76,7 @@ export function AppShell({ children }: { children: ReactNode }) { return (
- @@ -120,6 +128,26 @@ export function AppShell({ children }: { children: ReactNode }) {
setDrawer(false)} />