mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Scale typography without resizing layout
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -52,7 +52,7 @@
|
||||
}
|
||||
|
||||
.buzz-diff-theme .diff {
|
||||
font-size: 0.75rem;
|
||||
font-size: calc(var(--buzz-type-rem) * 0.75);
|
||||
}
|
||||
|
||||
.buzz-diff-theme .diff td {
|
||||
@@ -68,7 +68,7 @@
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-right: 1px solid hsl(var(--border) / 0.65);
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.6875rem;
|
||||
font-size: calc(var(--buzz-type-rem) * 0.6875);
|
||||
}
|
||||
|
||||
.buzz-diff-theme .buzz-diff-code {
|
||||
@@ -86,7 +86,7 @@
|
||||
padding: 0.2rem 0.75rem;
|
||||
background: hsl(var(--muted) / 0.35);
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.6875rem;
|
||||
font-size: calc(var(--buzz-type-rem) * 0.6875);
|
||||
}
|
||||
|
||||
.buzz-diff-theme .diff-gutter-omit::before {
|
||||
|
||||
@@ -3,7 +3,10 @@ import test from "node:test";
|
||||
|
||||
const values = new Map();
|
||||
const attributes = new Map();
|
||||
const style = { fontSize: "" };
|
||||
const styleValues = new Map();
|
||||
const style = {
|
||||
setProperty: (name, value) => styleValues.set(name, value),
|
||||
};
|
||||
|
||||
globalThis.localStorage = {
|
||||
getItem: (key) => values.get(key) ?? null,
|
||||
@@ -32,7 +35,7 @@ test("persists and applies the selected font size across the app", () => {
|
||||
assert.equal(preference.getFontSize(), "smaller");
|
||||
assert.equal(values.get(preference.FONT_SIZE_STORAGE_KEY), "smaller");
|
||||
assert.equal(attributes.get("data-font-size"), "smaller");
|
||||
assert.equal(style.fontSize, "16px");
|
||||
assert.equal(styleValues.get("--buzz-type-rem"), "16px");
|
||||
});
|
||||
|
||||
test("previews a font size without changing the saved preference", () => {
|
||||
@@ -42,11 +45,11 @@ test("previews a font size without changing the saved preference", () => {
|
||||
assert.equal(preference.getFontSize(), "smaller");
|
||||
assert.equal(values.get(preference.FONT_SIZE_STORAGE_KEY), "smaller");
|
||||
assert.equal(attributes.get("data-font-size"), "larger");
|
||||
assert.equal(style.fontSize, "20.114286px");
|
||||
assert.equal(styleValues.get("--buzz-type-rem"), "20.114286px");
|
||||
|
||||
preference.previewFontSize(null);
|
||||
assert.equal(attributes.get("data-font-size"), "smaller");
|
||||
assert.equal(style.fontSize, "17.6px");
|
||||
assert.equal(styleValues.get("--buzz-type-rem"), "17.6px");
|
||||
});
|
||||
|
||||
test("initializes from the stored font size", () => {
|
||||
@@ -55,5 +58,5 @@ test("initializes from the stored font size", () => {
|
||||
preference.initializeFontSizePreference();
|
||||
assert.equal(preference.getFontSize(), "larger");
|
||||
assert.equal(attributes.get("data-font-size"), "larger");
|
||||
assert.equal(style.fontSize, "18.285714px");
|
||||
assert.equal(styleValues.get("--buzz-type-rem"), "18.285714px");
|
||||
});
|
||||
|
||||
@@ -7,15 +7,17 @@ export const FONT_SIZE_STORAGE_KEY = "buzz.appearance.fontSize";
|
||||
export const DEFAULT_FONT_SIZE: FontSize = "default";
|
||||
|
||||
/**
|
||||
* Root sizes that map Tailwind's production `text-sm` size (0.875rem) to the
|
||||
* three app-wide 14px, 15px, and 16px steps.
|
||||
* Virtual rem sizes used by typography tokens. Keeping the real root at 16px
|
||||
* prevents a text preference from also resizing rem-based layout geometry.
|
||||
*/
|
||||
const BASE_FONT_SIZE_PX: Record<FontSize, number> = {
|
||||
const TYPE_REM_SIZE_PX: Record<FontSize, number> = {
|
||||
smaller: 14 / 0.875,
|
||||
default: 15 / 0.875,
|
||||
larger: 16 / 0.875,
|
||||
};
|
||||
|
||||
const TYPE_REM_PROPERTY = "--buzz-type-rem";
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
let fontSize: FontSize = DEFAULT_FONT_SIZE;
|
||||
let textZoomFactor = 1;
|
||||
@@ -36,16 +38,16 @@ function readStoredFontSize(): FontSize {
|
||||
}
|
||||
}
|
||||
|
||||
function rootFontSizePx(size: FontSize): number {
|
||||
function typeRemSizePx(size: FontSize): number {
|
||||
return (
|
||||
Math.round(BASE_FONT_SIZE_PX[size] * textZoomFactor * 1_000_000) / 1_000_000
|
||||
Math.round(TYPE_REM_SIZE_PX[size] * textZoomFactor * 1_000_000) / 1_000_000
|
||||
);
|
||||
}
|
||||
|
||||
function applyFontSize(size: FontSize): void {
|
||||
const root = globalThis.document?.documentElement;
|
||||
root?.setAttribute("data-font-size", size);
|
||||
if (root) root.style.fontSize = `${rootFontSizePx(size)}px`;
|
||||
root?.style.setProperty(TYPE_REM_PROPERTY, `${typeRemSizePx(size)}px`);
|
||||
}
|
||||
|
||||
function notifyListeners(): void {
|
||||
|
||||
@@ -132,8 +132,8 @@
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
color: hsl(var(--muted-foreground) / 0.72);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
font-size: calc(var(--buzz-type-rem) * 0.875);
|
||||
line-height: calc(var(--buzz-type-rem) * 1.25);
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -548,7 +548,7 @@
|
||||
.buzz-onboarding-runtime-pill {
|
||||
animation: buzz-onboarding-runtime-pill-in 180ms
|
||||
cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
font-size: 0.625rem;
|
||||
font-size: calc(var(--buzz-type-rem) * 0.625);
|
||||
letter-spacing: 0;
|
||||
line-height: 1;
|
||||
text-transform: uppercase;
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
color: hsl(var(--muted-foreground));
|
||||
display: flex;
|
||||
font: inherit;
|
||||
font-size: 0.75rem;
|
||||
font-size: calc(var(--buzz-type-rem) * 0.75);
|
||||
font-weight: 500;
|
||||
gap: 6px;
|
||||
height: 30px;
|
||||
@@ -130,7 +130,7 @@
|
||||
.buzz-terminal-designator {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
font-size: 0.75rem;
|
||||
font-size: calc(var(--buzz-type-rem) * 0.75);
|
||||
gap: 2px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
@@ -3,19 +3,35 @@
|
||||
/* Catppuccin Latte (mauve accent) */
|
||||
--radius: 0.625rem;
|
||||
/*
|
||||
* Default conversation type and comfy spacing for channels, DMs, threads,
|
||||
* Inbox, and the composer. The app-wide Font size preference and Cmd +/-
|
||||
* scale these rem values; Conversation density overrides only spacing.
|
||||
* A virtual typography rem. Font preferences and Cmd +/- change this
|
||||
* token while the browser root remains 16px, so text scales without also
|
||||
* resizing rem-based widths, gaps, radii, and controls.
|
||||
*/
|
||||
--conversation-message-font-size: 0.875rem;
|
||||
--conversation-message-line-height: 1.25rem;
|
||||
--conversation-author-line-height: 1rem;
|
||||
--buzz-type-rem: 1rem;
|
||||
--text-xs: calc(var(--buzz-type-rem) * 0.75);
|
||||
--text-sm: calc(var(--buzz-type-rem) * 0.875);
|
||||
--text-base: var(--buzz-type-rem);
|
||||
--text-lg: calc(var(--buzz-type-rem) * 1.125);
|
||||
--text-xl: calc(var(--buzz-type-rem) * 1.25);
|
||||
--text-2xl: calc(var(--buzz-type-rem) * 1.5);
|
||||
--text-3xl: calc(var(--buzz-type-rem) * 1.875);
|
||||
--text-4xl: calc(var(--buzz-type-rem) * 2.25);
|
||||
--text-5xl: calc(var(--buzz-type-rem) * 3);
|
||||
--text-6xl: calc(var(--buzz-type-rem) * 3.75);
|
||||
/*
|
||||
* Default conversation type and comfy spacing for channels, DMs, threads,
|
||||
* Inbox, and the composer. Font size changes the type tokens only;
|
||||
* Conversation density overrides only spacing.
|
||||
*/
|
||||
--conversation-message-font-size: calc(var(--buzz-type-rem) * 0.875);
|
||||
--conversation-message-line-height: calc(var(--buzz-type-rem) * 1.25);
|
||||
--conversation-author-line-height: var(--buzz-type-rem);
|
||||
--conversation-body-gap: 0.125rem;
|
||||
--conversation-row-padding-block: 0.375rem;
|
||||
--conversation-paragraph-gap: 0.5rem;
|
||||
--conversation-list-item-gap: 0.375rem;
|
||||
--conversation-timestamp-font-size: 0.75rem;
|
||||
--conversation-timestamp-line-height: 1rem;
|
||||
--conversation-timestamp-font-size: calc(var(--buzz-type-rem) * 0.75);
|
||||
--conversation-timestamp-line-height: var(--buzz-type-rem);
|
||||
--background: 220 23.08% 94.9%;
|
||||
--foreground: 234 16.02% 35.49%;
|
||||
--card: 220 23.08% 94.9%;
|
||||
|
||||
+19
-14
@@ -3,17 +3,16 @@ export default {
|
||||
theme: {
|
||||
extend: {
|
||||
// Sub-`text-xs` ramp for meta text (timestamps, count badges, tracking
|
||||
// labels) and tiny glyphs. Defined in rem so Cmd +/- zoom — which scales
|
||||
// the root <html> font-size — keeps scaling them. Do NOT reintroduce
|
||||
// arbitrary `text-[…rem]` / `text-[…px]` literals; the px-text guard
|
||||
// rejects them. Stock scale picks up from here: xs (12px), sm (14px)…
|
||||
// labels) and tiny glyphs. These follow the virtual typography rem so
|
||||
// preferences and Cmd +/- scale text without changing layout geometry.
|
||||
// Do NOT reintroduce arbitrary `text-[…rem]` / `text-[…px]` literals;
|
||||
// the px-text guard rejects them. Stock scale picks up from xs.
|
||||
fontSize: {
|
||||
"2xs": "0.6875rem", // 11px — meta-text workhorse (timestamps, badges)
|
||||
"3xs": "0.5rem", // 8px — tiny glyphs / micro labels
|
||||
badge: "0.625rem", // 10px — compact status badges
|
||||
// Shared channel, DM, thread, and composer type. Rem-backed variables
|
||||
// keep app-wide font size and keyboard zoom consistent without
|
||||
// branching individual components.
|
||||
"2xs": "calc(var(--buzz-type-rem) * 0.6875)", // 11px at 16px type rem
|
||||
"3xs": "calc(var(--buzz-type-rem) * 0.5)", // 8px at 16px type rem
|
||||
badge: "calc(var(--buzz-type-rem) * 0.625)", // 10px at 16px type rem
|
||||
// Shared channel, DM, thread, and composer type. Variables keep app-wide
|
||||
// font size and keyboard zoom consistent without branching components.
|
||||
message: [
|
||||
"var(--conversation-message-font-size)",
|
||||
{ lineHeight: "var(--conversation-message-line-height)" },
|
||||
@@ -22,10 +21,16 @@ export default {
|
||||
"var(--conversation-timestamp-font-size)",
|
||||
{ lineHeight: "var(--conversation-timestamp-line-height)" },
|
||||
],
|
||||
// 40px — onboarding page titles (tightened tracking for large display type)
|
||||
title: ["2.5rem", { lineHeight: "1.15", letterSpacing: "-0.02em" }],
|
||||
// 36px — the backup-step private key, shown large in monospace
|
||||
"nsec-key": ["2.25rem", { lineHeight: "1.3" }],
|
||||
// 40px at the 16px type rem — onboarding page titles.
|
||||
title: [
|
||||
"calc(var(--buzz-type-rem) * 2.5)",
|
||||
{ lineHeight: "1.15", letterSpacing: "-0.02em" },
|
||||
],
|
||||
// 36px at the 16px type rem — backup-step private key.
|
||||
"nsec-key": [
|
||||
"calc(var(--buzz-type-rem) * 2.25)",
|
||||
{ lineHeight: "1.3" },
|
||||
],
|
||||
},
|
||||
lineHeight: {
|
||||
"message-author": "var(--conversation-author-line-height)",
|
||||
|
||||
@@ -663,11 +663,9 @@ test("app font size and conversation density apply independently", async ({
|
||||
bodyGap: Number.parseFloat(
|
||||
style.getPropertyValue("--conversation-body-gap"),
|
||||
),
|
||||
fontSize: Number.parseFloat(
|
||||
style.getPropertyValue("--conversation-message-font-size"),
|
||||
),
|
||||
lineHeight: Number.parseFloat(
|
||||
style.getPropertyValue("--conversation-message-line-height"),
|
||||
fontSize: style.getPropertyValue("--conversation-message-font-size"),
|
||||
lineHeight: style.getPropertyValue(
|
||||
"--conversation-message-line-height",
|
||||
),
|
||||
paragraphGap: Number.parseFloat(
|
||||
style.getPropertyValue("--conversation-paragraph-gap"),
|
||||
@@ -675,8 +673,8 @@ test("app font size and conversation density apply independently", async ({
|
||||
rowPadding: Number.parseFloat(
|
||||
style.getPropertyValue("--conversation-row-padding-block"),
|
||||
),
|
||||
timestampFontSize: Number.parseFloat(
|
||||
style.getPropertyValue("--conversation-timestamp-font-size"),
|
||||
timestampFontSize: style.getPropertyValue(
|
||||
"--conversation-timestamp-font-size",
|
||||
),
|
||||
timestampLineHeight: Number.parseFloat(
|
||||
style.getPropertyValue("--conversation-timestamp-line-height"),
|
||||
@@ -731,14 +729,14 @@ test("app font size and conversation density apply independently", async ({
|
||||
);
|
||||
await expect(fontSizeDescription).toHaveText("Adjust text throughout Buzz.");
|
||||
await expect.poll(readScale).toEqual({
|
||||
authorLineHeight: 1,
|
||||
authorLineHeight: 17.142857,
|
||||
bodyGap: 0.125,
|
||||
fontSize: 0.875,
|
||||
lineHeight: 1.25,
|
||||
fontSize: "calc(17.142857px * .875)",
|
||||
lineHeight: "calc(17.142857px * 1.25)",
|
||||
paragraphGap: 0.5,
|
||||
rowPadding: 0.375,
|
||||
timestampFontSize: 0.75,
|
||||
timestampLineHeight: 1,
|
||||
timestampFontSize: "calc(17.142857px * .75)",
|
||||
timestampLineHeight: 17.142857,
|
||||
});
|
||||
await expect
|
||||
.poll(() =>
|
||||
@@ -754,7 +752,7 @@ test("app font size and conversation density apply independently", async ({
|
||||
.evaluate((element) => element.getBoundingClientRect().width),
|
||||
]),
|
||||
)
|
||||
.toEqual([308.5625, 308.5625, 257.140625]);
|
||||
.toEqual([288, 288, 240]);
|
||||
await expect
|
||||
.poll(() =>
|
||||
previewMessage.evaluate((element) => {
|
||||
@@ -766,8 +764,8 @@ test("app font size and conversation density apply independently", async ({
|
||||
await expect.poll(readSettingsScale).toEqual({
|
||||
fontSize: "15px",
|
||||
lineHeight: "21.4286px",
|
||||
minHeight: "68.5714px",
|
||||
paddingBlock: "12.8571px",
|
||||
minHeight: "64px",
|
||||
paddingBlock: "12px",
|
||||
});
|
||||
await expect
|
||||
.poll(readPreviewTimestampScale)
|
||||
@@ -820,24 +818,24 @@ test("app font size and conversation density apply independently", async ({
|
||||
const previewChipTopInset = previewChipBox.y - previewSurfaceBox.y;
|
||||
expect(previewChipTopInset).toBeGreaterThanOrEqual(13);
|
||||
expect(previewChipTopInset).toBeLessThanOrEqual(15);
|
||||
await expect(previewContent).toHaveCSS("padding-top", "17.1429px");
|
||||
await expect(previewContent).toHaveCSS("padding-right", "17.1429px");
|
||||
await expect(previewContent).toHaveCSS("padding-bottom", "17.1429px");
|
||||
await expect(previewContent).toHaveCSS("padding-left", "17.1429px");
|
||||
await expect(previewContent).toHaveCSS("padding-top", "16px");
|
||||
await expect(previewContent).toHaveCSS("padding-right", "16px");
|
||||
await expect(previewContent).toHaveCSS("padding-bottom", "16px");
|
||||
await expect(previewContent).toHaveCSS("padding-left", "16px");
|
||||
expect(firstPreviewMessageBox.x - previewSurfaceBox.x).toBeGreaterThanOrEqual(
|
||||
17,
|
||||
15,
|
||||
);
|
||||
expect(firstPreviewMessageBox.x - previewSurfaceBox.x).toBeLessThanOrEqual(
|
||||
19,
|
||||
);
|
||||
expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeGreaterThanOrEqual(
|
||||
17,
|
||||
);
|
||||
expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeLessThanOrEqual(
|
||||
19,
|
||||
expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeGreaterThanOrEqual(
|
||||
15,
|
||||
);
|
||||
await expect(previewChip).toHaveCSS("padding-top", "4.28571px");
|
||||
await expect(previewChip).toHaveCSS("padding-bottom", "4.28571px");
|
||||
expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeLessThanOrEqual(
|
||||
17,
|
||||
);
|
||||
await expect(previewChip).toHaveCSS("padding-top", "4px");
|
||||
await expect(previewChip).toHaveCSS("padding-bottom", "4px");
|
||||
|
||||
await densityIndicator.evaluate((element) => {
|
||||
element.addEventListener(
|
||||
@@ -861,20 +859,20 @@ test("app font size and conversation density apply independently", async ({
|
||||
)
|
||||
.toBe("compact");
|
||||
await expect.poll(readScale).toEqual({
|
||||
authorLineHeight: 1,
|
||||
authorLineHeight: 17.142857,
|
||||
bodyGap: 0,
|
||||
fontSize: 0.875,
|
||||
lineHeight: 1.25,
|
||||
fontSize: "calc(17.142857px * .875)",
|
||||
lineHeight: "calc(17.142857px * 1.25)",
|
||||
paragraphGap: 0.375,
|
||||
rowPadding: 0.25,
|
||||
timestampFontSize: 0.75,
|
||||
timestampLineHeight: 1,
|
||||
timestampFontSize: "calc(17.142857px * .75)",
|
||||
timestampLineHeight: 17.142857,
|
||||
});
|
||||
await expect.poll(readSettingsScale).toEqual({
|
||||
fontSize: "15px",
|
||||
lineHeight: "21.4286px",
|
||||
minHeight: "68.5714px",
|
||||
paddingBlock: "12.8571px",
|
||||
minHeight: "64px",
|
||||
paddingBlock: "12px",
|
||||
});
|
||||
await expect
|
||||
.poll(readPreviewTimestampScale)
|
||||
@@ -896,14 +894,14 @@ test("app font size and conversation density apply independently", async ({
|
||||
)
|
||||
.toBe("larger");
|
||||
await expect.poll(readScale).toEqual({
|
||||
authorLineHeight: 1,
|
||||
authorLineHeight: 18.285714,
|
||||
bodyGap: 0,
|
||||
fontSize: 0.875,
|
||||
lineHeight: 1.25,
|
||||
fontSize: "calc(18.285714px * .875)",
|
||||
lineHeight: "calc(18.285714px * 1.25)",
|
||||
paragraphGap: 0.375,
|
||||
rowPadding: 0.25,
|
||||
timestampFontSize: 0.75,
|
||||
timestampLineHeight: 1,
|
||||
timestampFontSize: "calc(18.285714px * .75)",
|
||||
timestampLineHeight: 18.285714,
|
||||
});
|
||||
await expect
|
||||
.poll(() =>
|
||||
@@ -916,8 +914,8 @@ test("app font size and conversation density apply independently", async ({
|
||||
await expect.poll(readSettingsScale).toEqual({
|
||||
fontSize: "16px",
|
||||
lineHeight: "22.8571px",
|
||||
minHeight: "73.1429px",
|
||||
paddingBlock: "13.7143px",
|
||||
minHeight: "64px",
|
||||
paddingBlock: "12px",
|
||||
});
|
||||
await expect
|
||||
.poll(readPreviewTimestampScale)
|
||||
@@ -935,20 +933,20 @@ test("app font size and conversation density apply independently", async ({
|
||||
await expect(root).toHaveAttribute("data-font-size", "larger");
|
||||
await expect(spacious).toHaveAttribute("aria-pressed", "true");
|
||||
await expect.poll(readScale).toEqual({
|
||||
authorLineHeight: 1,
|
||||
authorLineHeight: 18.285714,
|
||||
bodyGap: 0.25,
|
||||
fontSize: 0.875,
|
||||
lineHeight: 1.25,
|
||||
fontSize: "calc(18.285714px * .875)",
|
||||
lineHeight: "calc(18.285714px * 1.25)",
|
||||
paragraphGap: 0.625,
|
||||
rowPadding: 0.5,
|
||||
timestampFontSize: 0.75,
|
||||
timestampLineHeight: 1,
|
||||
timestampFontSize: "calc(18.285714px * .75)",
|
||||
timestampLineHeight: 18.285714,
|
||||
});
|
||||
await expect.poll(readSettingsScale).toEqual({
|
||||
fontSize: "16px",
|
||||
lineHeight: "22.8571px",
|
||||
minHeight: "73.1429px",
|
||||
paddingBlock: "13.7143px",
|
||||
minHeight: "64px",
|
||||
paddingBlock: "12px",
|
||||
});
|
||||
await expect
|
||||
.poll(readPreviewTimestampScale)
|
||||
@@ -962,14 +960,14 @@ test("app font size and conversation density apply independently", async ({
|
||||
await expect(root).toHaveAttribute("data-font-size", "smaller");
|
||||
await expect(smaller).toHaveAttribute("aria-pressed", "true");
|
||||
await expect.poll(readScale).toEqual({
|
||||
authorLineHeight: 1,
|
||||
authorLineHeight: 16,
|
||||
bodyGap: 0.25,
|
||||
fontSize: 0.875,
|
||||
lineHeight: 1.25,
|
||||
fontSize: "calc(16px * .875)",
|
||||
lineHeight: "calc(16px * 1.25)",
|
||||
paragraphGap: 0.625,
|
||||
rowPadding: 0.5,
|
||||
timestampFontSize: 0.75,
|
||||
timestampLineHeight: 1,
|
||||
timestampFontSize: "calc(16px * .75)",
|
||||
timestampLineHeight: 16,
|
||||
});
|
||||
await expect
|
||||
.poll(() =>
|
||||
@@ -1029,20 +1027,20 @@ test("app font size and conversation density apply independently", async ({
|
||||
)
|
||||
.toBe("comfortable");
|
||||
await expect.poll(readScale).toEqual({
|
||||
authorLineHeight: 1,
|
||||
authorLineHeight: 17.142857,
|
||||
bodyGap: 0.25,
|
||||
fontSize: 0.875,
|
||||
lineHeight: 1.25,
|
||||
fontSize: "calc(17.142857px * .875)",
|
||||
lineHeight: "calc(17.142857px * 1.25)",
|
||||
paragraphGap: 0.625,
|
||||
rowPadding: 0.5,
|
||||
timestampFontSize: 0.75,
|
||||
timestampLineHeight: 1,
|
||||
timestampFontSize: "calc(17.142857px * .75)",
|
||||
timestampLineHeight: 17.142857,
|
||||
});
|
||||
await expect.poll(readSettingsScale).toEqual({
|
||||
fontSize: "15px",
|
||||
lineHeight: "21.4286px",
|
||||
minHeight: "68.5714px",
|
||||
paddingBlock: "12.8571px",
|
||||
minHeight: "64px",
|
||||
paddingBlock: "12px",
|
||||
});
|
||||
await page.mouse.up();
|
||||
await expect(spacious).toHaveAttribute("aria-pressed", "true");
|
||||
|
||||
@@ -414,7 +414,7 @@ test.describe("inbox refactor screenshots", () => {
|
||||
]);
|
||||
await expect.poll(readConversationMetrics).toEqual([
|
||||
{ fontSize: "15px", lineHeight: "21.4286px" },
|
||||
{ paddingBottom: "6.42857px", paddingTop: "6.42857px" },
|
||||
{ paddingBottom: "6px", paddingTop: "6px" },
|
||||
{ fontSize: "15px", lineHeight: "17.1429px" },
|
||||
{ fontSize: "15px", lineHeight: "21.4286px" },
|
||||
{ fontSize: "12.8571px", lineHeight: "17.1429px" },
|
||||
@@ -429,7 +429,7 @@ test.describe("inbox refactor screenshots", () => {
|
||||
});
|
||||
await expect.poll(readConversationMetrics).toEqual([
|
||||
{ fontSize: "15px", lineHeight: "21.4286px" },
|
||||
{ paddingBottom: "4.28571px", paddingTop: "4.28571px" },
|
||||
{ paddingBottom: "4px", paddingTop: "4px" },
|
||||
{ fontSize: "15px", lineHeight: "17.1429px" },
|
||||
{ fontSize: "15px", lineHeight: "21.4286px" },
|
||||
{ fontSize: "12.8571px", lineHeight: "17.1429px" },
|
||||
@@ -438,7 +438,7 @@ test.describe("inbox refactor screenshots", () => {
|
||||
|
||||
await page.locator("html").evaluate((root) => {
|
||||
root.setAttribute("data-font-size", "smaller");
|
||||
root.style.fontSize = "16px";
|
||||
root.style.setProperty("--buzz-type-rem", "16px");
|
||||
});
|
||||
await expect.poll(readConversationMetrics).toEqual([
|
||||
{ fontSize: "14px", lineHeight: "20px" },
|
||||
@@ -454,11 +454,11 @@ test.describe("inbox refactor screenshots", () => {
|
||||
await page.locator("html").evaluate((root) => {
|
||||
root.setAttribute("data-conversation-density", "spacious");
|
||||
root.setAttribute("data-font-size", "larger");
|
||||
root.style.fontSize = "18.285714px";
|
||||
root.style.setProperty("--buzz-type-rem", "18.285714px");
|
||||
});
|
||||
await expect.poll(readConversationMetrics).toEqual([
|
||||
{ fontSize: "16px", lineHeight: "22.8571px" },
|
||||
{ paddingBottom: "9.14286px", paddingTop: "9.14286px" },
|
||||
{ paddingBottom: "8px", paddingTop: "8px" },
|
||||
{ fontSize: "16px", lineHeight: "18.2857px" },
|
||||
{ fontSize: "16px", lineHeight: "22.8571px" },
|
||||
{ fontSize: "13.7143px", lineHeight: "18.2857px" },
|
||||
@@ -470,7 +470,7 @@ test.describe("inbox refactor screenshots", () => {
|
||||
await page.locator("html").evaluate((root) => {
|
||||
root.setAttribute("data-conversation-density", "comfortable");
|
||||
root.setAttribute("data-font-size", "default");
|
||||
root.style.fontSize = "17.142857px";
|
||||
root.style.setProperty("--buzz-type-rem", "17.142857px");
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
@@ -490,15 +490,18 @@ test.describe("inbox refactor screenshots", () => {
|
||||
|
||||
await expect
|
||||
.poll(async () => [
|
||||
await page.evaluate(
|
||||
() => window.getComputedStyle(document.documentElement).fontSize,
|
||||
await page.evaluate(() =>
|
||||
window
|
||||
.getComputedStyle(document.documentElement)
|
||||
.getPropertyValue("--buzz-type-rem")
|
||||
.trim(),
|
||||
),
|
||||
...(await readConversationMetrics()),
|
||||
])
|
||||
.toEqual([
|
||||
"18.8571px",
|
||||
"18.857143px",
|
||||
{ fontSize: "16.5px", lineHeight: "23.5714px" },
|
||||
{ paddingBottom: "7.07143px", paddingTop: "7.07143px" },
|
||||
{ paddingBottom: "6px", paddingTop: "6px" },
|
||||
{ fontSize: "16.5px", lineHeight: "18.8571px" },
|
||||
{ fontSize: "16.5px", lineHeight: "23.5714px" },
|
||||
{ fontSize: "14.1429px", lineHeight: "18.8571px" },
|
||||
|
||||
@@ -1556,7 +1556,10 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
|
||||
|
||||
const getTextScaleState = () =>
|
||||
page.evaluate(() => ({
|
||||
fontSize: getComputedStyle(document.documentElement).fontSize,
|
||||
rootFontSize: getComputedStyle(document.documentElement).fontSize,
|
||||
textRemSize: getComputedStyle(document.documentElement)
|
||||
.getPropertyValue("--buzz-type-rem")
|
||||
.trim(),
|
||||
storedScale: localStorage.getItem("buzz:text-scale"),
|
||||
webviewZoom: (window as Window & { __BUZZ_E2E_WEBVIEW_ZOOM__?: number })
|
||||
.__BUZZ_E2E_WEBVIEW_ZOOM__,
|
||||
@@ -1587,7 +1590,8 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
|
||||
await dispatchPrimaryShortcut("+", "Equal", true);
|
||||
|
||||
await expect.poll(getTextScaleState).toEqual({
|
||||
fontSize: "17.6px",
|
||||
rootFontSize: "16px",
|
||||
textRemSize: "18.857143px",
|
||||
storedScale: "1.1",
|
||||
webviewZoom: 1,
|
||||
});
|
||||
@@ -1595,7 +1599,8 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
|
||||
await dispatchPrimaryShortcut("-", "Minus");
|
||||
|
||||
await expect.poll(getTextScaleState).toEqual({
|
||||
fontSize: "16px",
|
||||
rootFontSize: "16px",
|
||||
textRemSize: "17.142857px",
|
||||
storedScale: null,
|
||||
webviewZoom: 1,
|
||||
});
|
||||
@@ -1604,7 +1609,8 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
|
||||
await dispatchPrimaryShortcut("+", "Equal", true);
|
||||
|
||||
await expect.poll(getTextScaleState).toEqual({
|
||||
fontSize: "19.2px",
|
||||
rootFontSize: "16px",
|
||||
textRemSize: "20.571429px",
|
||||
storedScale: "1.2",
|
||||
webviewZoom: 1,
|
||||
});
|
||||
@@ -1612,7 +1618,8 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
|
||||
await dispatchPrimaryShortcut("0", "Digit0");
|
||||
|
||||
await expect.poll(getTextScaleState).toEqual({
|
||||
fontSize: "16px",
|
||||
rootFontSize: "16px",
|
||||
textRemSize: "17.142857px",
|
||||
storedScale: null,
|
||||
webviewZoom: 1,
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ const EXPECTED_NAV_CENTER_Y = 23;
|
||||
// The macOS traffic lights are native chrome: with `trafficLightPosition`
|
||||
// x:16 they occupy roughly x 16–68 regardless of the app's Cmd +/- text
|
||||
// zoom. The top-chrome nav row must clear that band in fixed px, so the
|
||||
// clearance cannot shrink when the root font size scales down.
|
||||
// clearance cannot change when text scales.
|
||||
const TRAFFIC_LIGHT_RIGHT_EDGE = 72;
|
||||
|
||||
async function spoofMacPlatform(page: import("@playwright/test").Page) {
|
||||
@@ -90,15 +90,24 @@ async function seedTextScale(
|
||||
}, scale);
|
||||
}
|
||||
|
||||
async function expectRootFontSize(
|
||||
async function expectTextRemSize(
|
||||
page: import("@playwright/test").Page,
|
||||
fontSize: string,
|
||||
) {
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(() => getComputedStyle(document.documentElement).fontSize),
|
||||
page.evaluate(() =>
|
||||
getComputedStyle(document.documentElement)
|
||||
.getPropertyValue("--buzz-type-rem")
|
||||
.trim(),
|
||||
),
|
||||
)
|
||||
.toBe(fontSize);
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(() => getComputedStyle(document.documentElement).fontSize),
|
||||
)
|
||||
.toBe("16px");
|
||||
}
|
||||
|
||||
test.describe("top chrome macOS traffic-light clearance under text zoom", () => {
|
||||
@@ -143,8 +152,8 @@ test.describe("top chrome macOS traffic-light clearance under text zoom", () =>
|
||||
await installMockBridge(page);
|
||||
await page.goto("/");
|
||||
|
||||
// Confirm the zoomed-out scale actually applied to the root font size.
|
||||
await expectRootFontSize(page, "12px");
|
||||
// Confirm the zoomed-out text scale applied without changing the root.
|
||||
await expectTextRemSize(page, "12.857143px");
|
||||
|
||||
expect(await firstNavButtonX(page)).toBeGreaterThanOrEqual(
|
||||
TRAFFIC_LIGHT_RIGHT_EDGE,
|
||||
@@ -161,7 +170,7 @@ test.describe("top chrome macOS traffic-light clearance under text zoom", () =>
|
||||
await installMockBridge(page);
|
||||
await page.goto("/");
|
||||
|
||||
await expectRootFontSize(page, "24px");
|
||||
await expectTextRemSize(page, "25.714286px");
|
||||
|
||||
expect(await firstNavButtonX(page)).toBeGreaterThanOrEqual(
|
||||
TRAFFIC_LIGHT_RIGHT_EDGE,
|
||||
|
||||
Reference in New Issue
Block a user