ci / node (pull_request) Skipped
ci / version (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 3m4s
ci / docker-build (pull_request) Skipped
ci / publish (pull_request) Skipped
ci / announce (pull_request) Skipped
On a wide screen the switcher moves from the foot of the folder pane to a
rail down the left edge: Mail (with the inbox's unread badge), Calendar,
Contacts and Files, then Settings and the folder-list toggle at the
bottom. The top bar loses the menu button and the Settings gear there,
both now on the rail. Phones are unchanged: no rail, the tab bar, the
menu button for the drawer and the gear.
- Folder counts are pills: filled with the accent when there is unread
mail, neutral for totals (Drafts, Scheduled).
- Role folders get their own icon tint (Inbox, Drafts, Sent, Archive,
Junk, Trash, Scheduled); a color the reader picked still wins.
- Buttons, Compose and the current selection have a top-lit, raised look
with a pressed state; Compose and the selected rail item are filled
with the accent.
- Collapsing the sidebar keeps Mail's icon strip; Calendar, Contacts,
Files, Settings and Admin have no icon-only form, so their sidebar
hides instead of being crushed.
- The collapsed sidebar no longer draws a cut-off "FOLDERS": the rule
hiding section headings lost to a later one of the same weight (this
is on main today too).
Contrast: the accent behind small text is darkened in light themes and
lightened a touch in dark ones. Measured in a browser across all 12
palettes x 6 accents x both modes: text on pills, the rail badge and
Compose at least 4.83:1, tinted icons at least 3.14:1 on the sidebar.
New strings: 2 ("Show folder list", "Hide folder list"), in all ten
catalogs (1699 -> 1701).
50 lines
2.4 KiB
TypeScript
50 lines
2.4 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
// Read off disk: `?raw` comes back empty for a stylesheet under the test runner.
|
|
const css = readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), "../app.css"), "utf8");
|
|
|
|
/*
|
|
* The app rail, the collapsed sidebar and the raised surfaces are all CSS, and
|
|
* jsdom does no layout, so these pin the rules themselves. Contrast for the
|
|
* pill accent was measured in a browser across every palette, accent and
|
|
* mode; the shape of that rule is pinned here.
|
|
*/
|
|
|
|
const rule = (selector: string) => {
|
|
const i = css.indexOf(selector + " {");
|
|
expect(i, `no rule for ${selector}`).toBeGreaterThan(-1);
|
|
return css.slice(i, css.indexOf("}", i));
|
|
};
|
|
|
|
describe("app layout CSS", () => {
|
|
it("gives the rail its own column ahead of the sidebar, collapsed or not", () => {
|
|
expect(rule(".app-body.has-rail")).toMatch(/grid-template-columns:\s*56px var\(--sidebar-w\)/);
|
|
expect(rule(".app-body.has-rail.collapsed")).toMatch(/grid-template-columns:\s*56px var\(--sidebar-w-collapsed\)/);
|
|
});
|
|
|
|
it("hides a sidebar with no icon-only form instead of crushing it", () => {
|
|
expect(rule(".app-body.has-rail.sidebar-hidden")).toMatch(/grid-template-columns:\s*56px 0 /);
|
|
expect(rule(".app-body.sidebar-hidden > .sidebar")).toMatch(/visibility:\s*hidden/);
|
|
});
|
|
|
|
it("hides section headings when collapsed, over the sidebar's own display rule", () => {
|
|
// `.sidebar .nav-section { display: flex }` comes later at the same weight;
|
|
// the collapsed rule has to outweigh it or "FOLDERS" is drawn cut off.
|
|
expect(css).toMatch(/\.app-body\.collapsed \.sidebar \.nav-section[^{]*\{\s*display:\s*none/);
|
|
});
|
|
|
|
it("darkens the pill accent in light themes and lightens it in dark ones", () => {
|
|
expect(css).toMatch(/:root \{ --pill-accent: color-mix\(in srgb, var\(--accent\) \d+%, black\); \}/);
|
|
expect(css).toMatch(/:root\[data-theme="dark"\] \{ --pill-accent: color-mix\(in srgb, var\(--accent\) \d+%, white\); \}/);
|
|
});
|
|
|
|
it("lets a color the reader picked win over the role tint", () => {
|
|
for (const role of ["inbox", "drafts", "sent", "archive", "junk", "trash", "scheduled"]) {
|
|
expect(css).toContain(`.folder-icon[data-role="${role}"]:not([style*="--folder-color"]) svg`);
|
|
}
|
|
});
|
|
});
|