Merge pull request #358 from Coffey-Labs/fix/contacts-resizable-list

Let the contact list be resized, and give the contact the rest of the page
This commit is contained in:
jcoffey
2026-09-15 07:28:06 -07:00
committed by GitHub
13 changed files with 43 additions and 6 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ describe("which settings follow the account", () => {
const synced = syncedPart(DEFAULT_SETTINGS); const synced = syncedPart(DEFAULT_SETTINGS);
// A pane width picked on a monitor is wrong on a laptop, and the // A pane width picked on a monitor is wrong on a laptop, and the
// notification toggles track a per-browser permission grant. // 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); expect(synced, key).not.toHaveProperty(key);
} }
}); });
+1
View File
@@ -1215,6 +1215,7 @@ export const catalog: Catalog = {
"Full screen": "Vollbild", "Full screen": "Vollbild",
"Resize panes": "Bereichsgröße ändern", "Resize panes": "Bereichsgröße ändern",
"Resize message list": "Größe der Nachrichtenliste ändern", "Resize message list": "Größe der Nachrichtenliste ändern",
"Resize contact list": "Größe der Kontaktliste ändern",
"Resize sidebar": "Größe der Seitenleiste ändern", "Resize sidebar": "Größe der Seitenleiste ändern",
"Hide from list": "Aus der Liste ausblenden", "Hide from list": "Aus der Liste ausblenden",
"Show in list": "In der Liste einblenden", "Show in list": "In der Liste einblenden",
+1
View File
@@ -1188,6 +1188,7 @@ export const catalog: Catalog = {
"Full screen": "Pantalla completa", "Full screen": "Pantalla completa",
"Resize panes": "Cambiar el tamaño de los paneles", "Resize panes": "Cambiar el tamaño de los paneles",
"Resize message list": "Cambiar el tamaño de la lista de mensajes", "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", "Resize sidebar": "Cambiar el tamaño de la barra lateral",
"Hide from list": "Ocultar de la lista", "Hide from list": "Ocultar de la lista",
"Show in list": "Mostrar en la lista", "Show in list": "Mostrar en la lista",
+1
View File
@@ -1193,6 +1193,7 @@ export const catalog: Catalog = {
"Full screen": "Plein écran", "Full screen": "Plein écran",
"Resize panes": "Redimensionner les volets", "Resize panes": "Redimensionner les volets",
"Resize message list": "Redimensionner la liste des messages", "Resize message list": "Redimensionner la liste des messages",
"Resize contact list": "Redimensionner la liste des contacts",
"Resize sidebar": "Redimensionner la barre latérale", "Resize sidebar": "Redimensionner la barre latérale",
"Hide from list": "Masquer de la liste", "Hide from list": "Masquer de la liste",
"Show in list": "Afficher dans la liste", "Show in list": "Afficher dans la liste",
+1
View File
@@ -1196,6 +1196,7 @@ export const catalog: Catalog = {
"Full screen": "全画面", "Full screen": "全画面",
"Resize panes": "ペインの大きさを変更", "Resize panes": "ペインの大きさを変更",
"Resize message list": "メール一覧の大きさを変更", "Resize message list": "メール一覧の大きさを変更",
"Resize contact list": "連絡先一覧の大きさを変更",
"Resize sidebar": "サイドバーの大きさを変更", "Resize sidebar": "サイドバーの大きさを変更",
"Hide from list": "一覧で非表示にする", "Hide from list": "一覧で非表示にする",
"Show in list": "一覧に表示する", "Show in list": "一覧に表示する",
+1
View File
@@ -1184,6 +1184,7 @@ export const catalog: Catalog = {
"Full screen": "Volledig scherm", "Full screen": "Volledig scherm",
"Resize panes": "Vensterdelen verslepen", "Resize panes": "Vensterdelen verslepen",
"Resize message list": "Grootte van de berichtenlijst wijzigen", "Resize message list": "Grootte van de berichtenlijst wijzigen",
"Resize contact list": "Grootte van de contactenlijst wijzigen",
"Resize sidebar": "Grootte van de zijbalk wijzigen", "Resize sidebar": "Grootte van de zijbalk wijzigen",
"Hide from list": "Verbergen in de lijst", "Hide from list": "Verbergen in de lijst",
"Show in list": "Tonen in de lijst", "Show in list": "Tonen in de lijst",
+1
View File
@@ -1191,6 +1191,7 @@ export const catalog: Catalog = {
"Full screen": "Tela cheia", "Full screen": "Tela cheia",
"Resize panes": "Redimensionar os painéis", "Resize panes": "Redimensionar os painéis",
"Resize message list": "Redimensionar a lista de mensagens", "Resize message list": "Redimensionar a lista de mensagens",
"Resize contact list": "Redimensionar a lista de contatos",
"Resize sidebar": "Redimensionar a barra lateral", "Resize sidebar": "Redimensionar a barra lateral",
"Hide from list": "Ocultar da lista", "Hide from list": "Ocultar da lista",
"Show in list": "Mostrar na lista", "Show in list": "Mostrar na lista",
+1
View File
@@ -1190,6 +1190,7 @@ export const catalog: Catalog = {
"Full screen": "Во весь экран", "Full screen": "Во весь экран",
"Resize panes": "Изменить размер панелей", "Resize panes": "Изменить размер панелей",
"Resize message list": "Изменить размер списка писем", "Resize message list": "Изменить размер списка писем",
"Resize contact list": "Изменить размер списка контактов",
"Resize sidebar": "Изменить размер боковой панели", "Resize sidebar": "Изменить размер боковой панели",
"Hide from list": "Скрыть из списка", "Hide from list": "Скрыть из списка",
"Show in list": "Показать в списке", "Show in list": "Показать в списке",
+1
View File
@@ -1184,6 +1184,7 @@ export const catalog: Catalog = {
"Full screen": "На весь екран", "Full screen": "На весь екран",
"Resize panes": "Змінити розмір панелей", "Resize panes": "Змінити розмір панелей",
"Resize message list": "Змінити розмір списку листів", "Resize message list": "Змінити розмір списку листів",
"Resize contact list": "Змінити розмір списку контактів",
"Resize sidebar": "Змінити розмір бічної панелі", "Resize sidebar": "Змінити розмір бічної панелі",
"Hide from list": "Сховати зі списку", "Hide from list": "Сховати зі списку",
"Show in list": "Показати в списку", "Show in list": "Показати в списку",
+1
View File
@@ -1195,6 +1195,7 @@ export const catalog: Catalog = {
"Full screen": "全屏", "Full screen": "全屏",
"Resize panes": "调整窗格大小", "Resize panes": "调整窗格大小",
"Resize message list": "调整邮件列表大小", "Resize message list": "调整邮件列表大小",
"Resize contact list": "调整联系人列表大小",
"Resize sidebar": "调整侧边栏大小", "Resize sidebar": "调整侧边栏大小",
"Hide from list": "从列表中隐藏", "Hide from list": "从列表中隐藏",
"Show in list": "在列表中显示", "Show in list": "在列表中显示",
+4
View File
@@ -256,6 +256,8 @@ export interface Settings {
listPaneWidth: number; listPaneWidth: number;
/** Height (px) of the message list when the reading pane is below. */ /** Height (px) of the message list when the reading pane is below. */
listPaneHeight: number; 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 * 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 * 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, sendAndArchive: false,
listPaneWidth: 520, listPaneWidth: 520,
listPaneHeight: 340, listPaneHeight: 340,
contactsListWidth: 320,
sidebarWidth: null, sidebarWidth: null,
eventCategories: [ eventCategories: [
{ name: "Important", color: "#dc2626" }, { name: "Important", color: "#dc2626" },
@@ -408,6 +411,7 @@ export const DEVICE_KEYS: ReadonlySet<keyof Settings> = new Set<keyof Settings>(
"notificationSound", "notificationSound",
"listPaneWidth", "listPaneWidth",
"listPaneHeight", "listPaneHeight",
"contactsListWidth",
"sidebarWidth", "sidebarWidth",
]); ]);
+2 -4
View File
@@ -1753,8 +1753,7 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); }
/* ========================================================================== /* ==========================================================================
Contacts Contacts
========================================================================== */ ========================================================================== */
.contacts-layout { display: grid; grid-template-columns: 220px minmax(280px, 360px) 1fr; height: 100%; min-height: 0; flex: 1; } .contacts-layout { display: grid; grid-template-columns: var(--list-size, 320px) auto minmax(0, 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-list { border-right: 1px solid var(--border); display: flex; flex-direction: column; min-height: 0; } .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-list .list-search { padding: 10px 12px; border-bottom: 1px solid var(--border); }
.contacts-scroll { flex: 1; overflow-y: auto; } .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) { @media (max-width: 1100px) {
:root { --list-w: 44%; } :root { --list-w: 44%; }
.msg-row .msg-from { width: 140px; } .msg-row .msg-from { width: 140px; }
.contacts-layout { grid-template-columns: 200px 300px 1fr; }
} }
@media (max-width: 900px) { @media (max-width: 900px) {
.mail-layout:not(.pane-off):not(.pane-bottom) { grid-template-columns: 1fr !important; } .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-table .hide-mobile { display: none; }
.admin-sheet { width: 100%; border-left: 0; box-shadow: none; } .admin-sheet { width: 100%; border-left: 0; box-shadow: none; }
.contacts-layout { grid-template-columns: 1fr; } .contacts-layout { grid-template-columns: 1fr; }
.contacts-books { display: none; } .contacts-layout .splitter { display: none; }
.contacts-layout.detail .contacts-list { display: none; } .contacts-layout.detail .contacts-list { display: none; }
.contacts-layout:not(.detail) .contact-detail { display: none; } .contacts-layout:not(.detail) .contact-detail { display: none; }
.cal-layout { grid-template-columns: 1fr; } .cal-layout { grid-template-columns: 1fr; }
+27 -1
View File
@@ -10,6 +10,8 @@ import { formatDate, formatDateLong } from "@/lib/datetime";
import { Avatar, Empty, Spinner, useIsNarrow } from "@/ui/misc"; import { Avatar, Empty, Spinner, useIsNarrow } from "@/ui/misc";
import { confirmDialog } from "@/ui/dialog"; import { confirmDialog } from "@/ui/dialog";
import { toast } from "@/ui/toast"; import { toast } from "@/ui/toast";
import { Splitter } from "@/ui/Splitter";
import { useSettings } from "@/store/settings";
import { ContactEditor } from "./ContactEditor"; import { ContactEditor } from "./ContactEditor";
import { avatarColor } from "@/lib/address"; import { avatarColor } from "@/lib/address";
import { plural, t as translate } from "@/lib/i18n"; import { plural, t as translate } from "@/lib/i18n";
@@ -18,6 +20,14 @@ export function ContactsView({ id }: { id?: string }) {
const [, navigate] = useLocation(); const [, navigate] = useLocation();
const contacts = useContacts(); const contacts = useContacts();
const narrow = useIsNarrow(); const narrow = useIsNarrow();
const listWidth = useSettings((s) => s.settings.contactsListWidth);
const updateSettings = useSettings((s) => s.update);
const layoutRef = useRef<HTMLDivElement>(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<number | null>(null);
const liveWidthRef = useRef<number | null>(null);
const [q, setQ] = useState(""); const [q, setQ] = useState("");
/* The book being shown lives in the store, because the list that chooses it /* 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. */ 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 ( return (
<div className={`contacts-layout ${selected || editing ? "detail" : ""}`}> <div ref={layoutRef} className={`contacts-layout ${selected || editing ? "detail" : ""}`} style={{ "--list-size": `${shownListWidth}px` } as React.CSSProperties}>
<section className="contacts-list"> <section className="contacts-list">
{pickedIds.length ? ( {pickedIds.length ? (
@@ -303,6 +328,7 @@ export function ContactsView({ id }: { id?: string }) {
))} ))}
</div> </div>
</section> </section>
{!narrow && <Splitter direction="vertical" onResize={onSplit} onEnd={onSplitEnd} onReset={() => updateSettings({ contactsListWidth: 320 })} ariaLabel={translate("Resize contact list")} />}
<section className="contact-detail"> <section className="contact-detail">
{selected ? ( {selected ? (