From 95395200a3ff4b130628de3de151f1b0a04550c6 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Tue, 15 Sep 2026 07:24:56 -0700 Subject: [PATCH] Let the contact list be resized, and give the contact the rest of the page The contacts grid still had three columns from when the address books sat inside the view: 220px for them, 280-360px for the list, the rest for the contact. The books moved to the app's left pane in 350f4f4 and the grid never followed, so the list was squeezed into the books' 220px, truncating names, the contact was held to 360px, and the remaining width sat empty. It is two columns now, with the same splitter as the message list between them: drag between 240px and the width that leaves 360px for the contact, arrow keys move it, double-click puts it back to 320px. The width is a device setting beside the mail list's, so it is kept by a device marked as your own and never synced. The splitter is hidden where contacts show one pane at a time. The unused .contacts-books rules are gone. "Resize contact list" is in all nine catalogues. --- web/src/lib/__tests__/settingsSync.test.ts | 2 +- web/src/locales/de.ts | 1 + web/src/locales/es.ts | 1 + web/src/locales/fr.ts | 1 + web/src/locales/ja.ts | 1 + web/src/locales/nl.ts | 1 + web/src/locales/pt-BR.ts | 1 + web/src/locales/ru.ts | 1 + web/src/locales/uk.ts | 1 + web/src/locales/zh-Hans.ts | 1 + web/src/store/settings.ts | 4 ++++ web/src/styles/app.css | 6 ++--- web/src/views/contacts/ContactsView.tsx | 28 +++++++++++++++++++++- 13 files changed, 43 insertions(+), 6 deletions(-) diff --git a/web/src/lib/__tests__/settingsSync.test.ts b/web/src/lib/__tests__/settingsSync.test.ts index 64721d5..0fb78d5 100644 --- a/web/src/lib/__tests__/settingsSync.test.ts +++ b/web/src/lib/__tests__/settingsSync.test.ts @@ -25,7 +25,7 @@ describe("which settings follow the account", () => { const synced = syncedPart(DEFAULT_SETTINGS); // A pane width picked on a monitor is wrong on a laptop, and the // notification toggles track a per-browser permission grant. - for (const key of ["listPaneWidth", "listPaneHeight", "sidebarWidth", "density", "fontSize", "sidebarCollapsed", "desktopNotifications", "notificationSound"]) { + for (const key of ["listPaneWidth", "listPaneHeight", "contactsListWidth", "sidebarWidth", "density", "fontSize", "sidebarCollapsed", "desktopNotifications", "notificationSound"]) { expect(synced, key).not.toHaveProperty(key); } }); diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts index 717a38e..2131e50 100644 --- a/web/src/locales/de.ts +++ b/web/src/locales/de.ts @@ -1215,6 +1215,7 @@ export const catalog: Catalog = { "Full screen": "Vollbild", "Resize panes": "Bereichsgröße ändern", "Resize message list": "Größe der Nachrichtenliste ändern", + "Resize contact list": "Größe der Kontaktliste ändern", "Resize sidebar": "Größe der Seitenleiste ändern", "Hide from list": "Aus der Liste ausblenden", "Show in list": "In der Liste einblenden", diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts index 7c810ae..b6d8dfd 100644 --- a/web/src/locales/es.ts +++ b/web/src/locales/es.ts @@ -1188,6 +1188,7 @@ export const catalog: Catalog = { "Full screen": "Pantalla completa", "Resize panes": "Cambiar el tamaño de los paneles", "Resize message list": "Cambiar el tamaño de la lista de mensajes", + "Resize contact list": "Cambiar el tamaño de la lista de contactos", "Resize sidebar": "Cambiar el tamaño de la barra lateral", "Hide from list": "Ocultar de la lista", "Show in list": "Mostrar en la lista", diff --git a/web/src/locales/fr.ts b/web/src/locales/fr.ts index e1649d3..676cf33 100644 --- a/web/src/locales/fr.ts +++ b/web/src/locales/fr.ts @@ -1193,6 +1193,7 @@ export const catalog: Catalog = { "Full screen": "Plein écran", "Resize panes": "Redimensionner les volets", "Resize message list": "Redimensionner la liste des messages", + "Resize contact list": "Redimensionner la liste des contacts", "Resize sidebar": "Redimensionner la barre latérale", "Hide from list": "Masquer de la liste", "Show in list": "Afficher dans la liste", diff --git a/web/src/locales/ja.ts b/web/src/locales/ja.ts index cc0e599..c597628 100644 --- a/web/src/locales/ja.ts +++ b/web/src/locales/ja.ts @@ -1196,6 +1196,7 @@ export const catalog: Catalog = { "Full screen": "全画面", "Resize panes": "ペインの大きさを変更", "Resize message list": "メール一覧の大きさを変更", + "Resize contact list": "連絡先一覧の大きさを変更", "Resize sidebar": "サイドバーの大きさを変更", "Hide from list": "一覧で非表示にする", "Show in list": "一覧に表示する", diff --git a/web/src/locales/nl.ts b/web/src/locales/nl.ts index 3437f22..6b6883b 100644 --- a/web/src/locales/nl.ts +++ b/web/src/locales/nl.ts @@ -1184,6 +1184,7 @@ export const catalog: Catalog = { "Full screen": "Volledig scherm", "Resize panes": "Vensterdelen verslepen", "Resize message list": "Grootte van de berichtenlijst wijzigen", + "Resize contact list": "Grootte van de contactenlijst wijzigen", "Resize sidebar": "Grootte van de zijbalk wijzigen", "Hide from list": "Verbergen in de lijst", "Show in list": "Tonen in de lijst", diff --git a/web/src/locales/pt-BR.ts b/web/src/locales/pt-BR.ts index 934f09e..1767f1f 100644 --- a/web/src/locales/pt-BR.ts +++ b/web/src/locales/pt-BR.ts @@ -1191,6 +1191,7 @@ export const catalog: Catalog = { "Full screen": "Tela cheia", "Resize panes": "Redimensionar os painéis", "Resize message list": "Redimensionar a lista de mensagens", + "Resize contact list": "Redimensionar a lista de contatos", "Resize sidebar": "Redimensionar a barra lateral", "Hide from list": "Ocultar da lista", "Show in list": "Mostrar na lista", diff --git a/web/src/locales/ru.ts b/web/src/locales/ru.ts index a5915da..8bdf1ae 100644 --- a/web/src/locales/ru.ts +++ b/web/src/locales/ru.ts @@ -1190,6 +1190,7 @@ export const catalog: Catalog = { "Full screen": "Во весь экран", "Resize panes": "Изменить размер панелей", "Resize message list": "Изменить размер списка писем", + "Resize contact list": "Изменить размер списка контактов", "Resize sidebar": "Изменить размер боковой панели", "Hide from list": "Скрыть из списка", "Show in list": "Показать в списке", diff --git a/web/src/locales/uk.ts b/web/src/locales/uk.ts index e5669ef..0aa6e7e 100644 --- a/web/src/locales/uk.ts +++ b/web/src/locales/uk.ts @@ -1184,6 +1184,7 @@ export const catalog: Catalog = { "Full screen": "На весь екран", "Resize panes": "Змінити розмір панелей", "Resize message list": "Змінити розмір списку листів", + "Resize contact list": "Змінити розмір списку контактів", "Resize sidebar": "Змінити розмір бічної панелі", "Hide from list": "Сховати зі списку", "Show in list": "Показати в списку", diff --git a/web/src/locales/zh-Hans.ts b/web/src/locales/zh-Hans.ts index ec1d87f..e4035fd 100644 --- a/web/src/locales/zh-Hans.ts +++ b/web/src/locales/zh-Hans.ts @@ -1195,6 +1195,7 @@ export const catalog: Catalog = { "Full screen": "全屏", "Resize panes": "调整窗格大小", "Resize message list": "调整邮件列表大小", + "Resize contact list": "调整联系人列表大小", "Resize sidebar": "调整侧边栏大小", "Hide from list": "从列表中隐藏", "Show in list": "在列表中显示", diff --git a/web/src/store/settings.ts b/web/src/store/settings.ts index 4ee9a27..91d8c09 100644 --- a/web/src/store/settings.ts +++ b/web/src/store/settings.ts @@ -256,6 +256,8 @@ export interface Settings { listPaneWidth: number; /** Height (px) of the message list when the reading pane is below. */ listPaneHeight: number; + /** Width (px) of the contact list, beside the contact on show. */ + contactsListWidth: number; /** * Width (px) of the sidebar, dragged by its edge (#345). Null until someone * drags it, and null again after a double-click resets it -- which leaves the @@ -377,6 +379,7 @@ export const DEFAULT_SETTINGS: Settings = { sendAndArchive: false, listPaneWidth: 520, listPaneHeight: 340, + contactsListWidth: 320, sidebarWidth: null, eventCategories: [ { name: "Important", color: "#dc2626" }, @@ -408,6 +411,7 @@ export const DEVICE_KEYS: ReadonlySet = new Set( "notificationSound", "listPaneWidth", "listPaneHeight", + "contactsListWidth", "sidebarWidth", ]); diff --git a/web/src/styles/app.css b/web/src/styles/app.css index f8bbc62..c6f7216 100644 --- a/web/src/styles/app.css +++ b/web/src/styles/app.css @@ -1753,8 +1753,7 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); } /* ========================================================================== Contacts ========================================================================== */ -.contacts-layout { display: grid; grid-template-columns: 220px minmax(280px, 360px) 1fr; height: 100%; min-height: 0; flex: 1; } -.contacts-books { border-right: 1px solid var(--border); padding: 12px 8px; overflow-y: auto; overflow-x: hidden; } +.contacts-layout { display: grid; grid-template-columns: var(--list-size, 320px) auto minmax(0, 1fr); height: 100%; min-height: 0; flex: 1; } .contacts-list { border-right: 1px solid var(--border); display: flex; flex-direction: column; min-height: 0; } .contacts-list .list-search { padding: 10px 12px; border-bottom: 1px solid var(--border); } .contacts-scroll { flex: 1; overflow-y: auto; } @@ -2015,7 +2014,6 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); } @media (max-width: 1100px) { :root { --list-w: 44%; } .msg-row .msg-from { width: 140px; } - .contacts-layout { grid-template-columns: 200px 300px 1fr; } } @media (max-width: 900px) { .mail-layout:not(.pane-off):not(.pane-bottom) { grid-template-columns: 1fr !important; } @@ -2036,7 +2034,7 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); } .admin-table .hide-mobile { display: none; } .admin-sheet { width: 100%; border-left: 0; box-shadow: none; } .contacts-layout { grid-template-columns: 1fr; } - .contacts-books { display: none; } + .contacts-layout .splitter { display: none; } .contacts-layout.detail .contacts-list { display: none; } .contacts-layout:not(.detail) .contact-detail { display: none; } .cal-layout { grid-template-columns: 1fr; } diff --git a/web/src/views/contacts/ContactsView.tsx b/web/src/views/contacts/ContactsView.tsx index dc64d12..1b40658 100644 --- a/web/src/views/contacts/ContactsView.tsx +++ b/web/src/views/contacts/ContactsView.tsx @@ -10,6 +10,8 @@ import { formatDate, formatDateLong } from "@/lib/datetime"; import { Avatar, Empty, Spinner, useIsNarrow } from "@/ui/misc"; import { confirmDialog } from "@/ui/dialog"; import { toast } from "@/ui/toast"; +import { Splitter } from "@/ui/Splitter"; +import { useSettings } from "@/store/settings"; import { ContactEditor } from "./ContactEditor"; import { avatarColor } from "@/lib/address"; import { plural, t as translate } from "@/lib/i18n"; @@ -18,6 +20,14 @@ export function ContactsView({ id }: { id?: string }) { const [, navigate] = useLocation(); const contacts = useContacts(); const narrow = useIsNarrow(); + const listWidth = useSettings((s) => s.settings.contactsListWidth); + const updateSettings = useSettings((s) => s.update); + const layoutRef = useRef(null); + /* The width mid-drag, and a ref mirroring it for the end of a key press, + which follows the resize in the same tick -- the same pair as the mail + list's splitter, for the same reason. */ + const [liveWidth, setLiveWidth] = useState(null); + const liveWidthRef = useRef(null); const [q, setQ] = useState(""); /* The book being shown lives in the store, because the list that chooses it is the app's own sidebar rather than anything this view owns. */ @@ -240,8 +250,23 @@ export function ContactsView({ id }: { id?: string }) { } }; + const shownListWidth = liveWidth ?? listWidth; + const onSplit = (delta: number) => { + const total = layoutRef.current?.clientWidth ?? 1200; + const max = Math.max(240, total - 360); + const next = Math.min(max, Math.max(240, (liveWidthRef.current ?? shownListWidth) + delta)); + liveWidthRef.current = next; + setLiveWidth(next); + }; + const onSplitEnd = () => { + const width = liveWidthRef.current; + liveWidthRef.current = null; + setLiveWidth(null); + if (width != null) updateSettings({ contactsListWidth: width }); + }; + return ( -
+
{pickedIds.length ? ( @@ -303,6 +328,7 @@ export function ContactsView({ id }: { id?: string }) { ))}
+ {!narrow && updateSettings({ contactsListWidth: 320 })} ariaLabel={translate("Resize contact list")} />}
{selected ? (