mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 16:16:59 +00:00
Add an "Interface layout" setting at the top of Settings > Layout with two options, Default and Mountain View. Default is the existing layout and stays the default, so nothing changes unless a user opts in. Mountain View rearranges the chrome while keeping the theme's colours, icons and type: - desktop: a top bar with a wide search field (filters drop from the field), an apps grid and the account avatar instead of the left app rail; flat navigation with Starred under Inbox and the rest behind "More"; a Compose pill; one list toolbar with select-all, refresh, overflow menu, a 1-N of total range and the batch actions; rows led by a checkbox and a star; an icon-only message toolbar with Reply / Reply all / Forward under the message; compose in a docked window; an account popover with per-account unread counts; j/k cursor, x to select and g-sequences - phone: rounded search header, compact rows with the star on the preview line, and Reply / Reply all / Forward at the end of a message instead of the bottom action bar It is one setting (`interfaceLayout`), written to a data attribute on <html> that keys a block of CSS tokens in globals.css, plus a few components gated on the flag. The mail layout in force is derived (effectiveMailLayout), so the user's saved mail layout is never rewritten. The setting is listed in the admin policy tab, and the new strings are translated in all locales. Includes unit tests and a Playwright smoke test against a running instance (scripts/mountain-view-smoke/).
44 lines
2.1 KiB
TypeScript
44 lines
2.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { densityVarsFor, type Density, type InterfaceLayout } from '@/stores/settings-store';
|
|
|
|
// Mountain View has three row rhythms of its own and the app has four
|
|
// densities. The four are laid over its scale rather than left meaning
|
|
// whatever they meant in the default layout.
|
|
|
|
const px = (value: string) => Number.parseInt(value, 10);
|
|
const ROW = (density: Density, layout: InterfaceLayout) =>
|
|
px(densityVarsFor(density, layout)['--list-item-height']);
|
|
|
|
describe('densityVarsFor', () => {
|
|
it('lands the three shared names on the 32 / 40 / 48 pixel rows', () => {
|
|
expect(ROW('compact', 'mountain-view')).toBe(32);
|
|
expect(ROW('regular', 'mountain-view')).toBe(40);
|
|
expect(ROW('comfortable', 'mountain-view')).toBe(48);
|
|
});
|
|
|
|
it('continues the series below compact rather than dropping a choice', () => {
|
|
expect(ROW('extra-compact', 'mountain-view')).toBeLessThan(ROW('compact', 'mountain-view'));
|
|
});
|
|
|
|
it('keeps every step distinct, so all four settings still do something', () => {
|
|
const heights = (['extra-compact', 'compact', 'regular', 'comfortable'] as Density[])
|
|
.map((density) => ROW(density, 'mountain-view'));
|
|
expect(new Set(heights).size).toBe(4);
|
|
expect([...heights]).toEqual([...heights].sort((a, b) => a - b));
|
|
});
|
|
|
|
it('leaves the default layout exactly as it was', () => {
|
|
expect(densityVarsFor('regular', 'default')['--list-item-height']).toBe('48px');
|
|
expect(densityVarsFor('compact', 'default')['--list-item-height']).toBe('auto');
|
|
expect(densityVarsFor('extra-compact', 'default')['--density-item-py']).toBe('2px');
|
|
});
|
|
|
|
it('restates only the vertical rhythm, not the rest of the spacing', () => {
|
|
const mountainView = densityVarsFor('regular', 'mountain-view');
|
|
const standard = densityVarsFor('regular', 'default');
|
|
expect(mountainView['--density-card-p']).toBe(standard['--density-card-p']);
|
|
expect(mountainView['--density-sidebar-py']).toBe(standard['--density-sidebar-py']);
|
|
expect(mountainView['--density-item-py']).not.toBe(standard['--density-item-py']);
|
|
});
|
|
});
|