diff --git a/web/src/components/community/Community.module.css b/web/src/components/community/Community.module.css index 834280bb0..33d24c313 100644 --- a/web/src/components/community/Community.module.css +++ b/web/src/components/community/Community.module.css @@ -4167,11 +4167,31 @@ } @media (max-width: 768px), (hover: none) { + /* 8px, not 14px: the wordmark below is 48px tall now and the links 44px, + and the header stays the 65px it was. */ .pageHeader { flex-wrap: wrap; align-items: center; gap: 8px 12px; - padding: 14px 16px; + padding: 8px 16px; + } + + /* Two links stacked 18.4px and 11.5px tall, the one pair in the community + that failed WCAG 2.5.8 at 390px: a 24px circle on each overlapped the + other. A 24px line each, the words pulled together at the seam, so the + wordmark reads as it did. */ + .brand, + .pageTitle { + display: flex; + min-height: 24px; + } + + .brand { + align-items: flex-end; + } + + .pageTitle { + align-items: flex-start; } /* The section nav moves to the tab bar at the bottom of the screen, and the @@ -4200,6 +4220,24 @@ margin-left: auto; } + /* 44px of thumb for each. At 390px Alerts was 35.8×18.8, the handle + 28.8×17.4, Pattern Lab ↗ 86.1×18.8 and the deck chip, a phone's only way + to the deck, 64.9×29.2. */ + .headerNav > a, + .headerNav > button { + display: inline-flex; + align-items: center; + min-height: 44px; + } + + /* The two narrowest take half the 12px gap on each side as well. The + negative margin gives it back, so the row is as long as it was. */ + .notifyLink, + .headerNav > .userChip { + padding: 0 6px; + margin: 0 -6px; + } + .makeCta { font-size: 12px; padding: 8px 12px; @@ -4211,7 +4249,8 @@ display: none; } - /* A handle can be any length; a header row cannot. */ + /* A handle can be any length; a header row cannot. The ellipsis needs a + block box, which a flex item already is, so the 44px comes from the line. */ .userChip { max-width: 96px; overflow: hidden; @@ -4219,6 +4258,28 @@ white-space: nowrap; } + /* 108px is the 96px above with the 6px either side that the row gets back. */ + .headerNav > .userChip { + display: block; + max-width: 108px; + line-height: 44px; + } + + /* The notice's ✕ was 18.9×21. Both reach into the strip's 9px padding, so + the strip grows 5px, not 23. */ + .fwNoticeLink { + display: inline-flex; + align-items: center; + min-height: 44px; + margin: -9px 0; + } + + .fwNoticeClose { + min-width: 44px; + min-height: 44px; + margin: -9px -12px -9px auto; + } + /* The fifth item is what tipped the row onto a second line, and it is the one the "You" tab already leads to. It moves to your own profile. */ .headerNav .signOutLink { @@ -4294,8 +4355,9 @@ right: 6px; } + /* No gap between the rows: each one is 44px of thumb with air of its own. */ .feedControls { - gap: 12px; + gap: 0 12px; } /* Six tabs with Oldest and Liked do not fit a phone's width on one line; @@ -4303,7 +4365,21 @@ .sortTabs { flex-wrap: wrap; column-gap: 16px; - row-gap: 8px; + row-gap: 0; + } + + /* The tabs were 25.6px tall and Flashable now 18.1px. A tab keeps its + word on its underline and takes the extra height above it. The buttons + are the featured editor's copy of the same tabs. */ + .sortTabs a, + .sortTabs button { + display: inline-flex; + align-items: flex-end; + min-height: 44px; + } + + .filterChip { + min-height: 44px; } .feedSearchInput { diff --git a/web/src/components/community/communityCss.test.ts b/web/src/components/community/communityCss.test.ts index 6876a6700..579d1164f 100644 --- a/web/src/components/community/communityCss.test.ts +++ b/web/src/components/community/communityCss.test.ts @@ -219,6 +219,81 @@ describe("text fields on a phone", () => { }); }); +// On 26 October, at 390px, the header's links were 17.4 to 18.8px tall, the +// deck chip 29.2px, the notice's ✕ 18.9×21, the wall's sort tabs 25.6px and +// Flashable now 18.1px. "Patternflow" (18.4px) and "COMMUNITY" (11.5px) sat +// one on the other, and a 24px circle on each overlapped the other: WCAG 2.5.8. +describe("touch targets on a phone", () => { + const community = css("Community.module.css"); + const phone = (selector: string) => + mediaRules(community, MOBILE_MEDIA_QUERY) + .filter(({ selectors }) => selectors.includes(selector)) + .flatMap(({ body }) => declarations(body)); + const px = (lines: string[], property: string) => { + const line = lines.findLast((l) => l.startsWith(`${property}:`)); + expect(line, `${property} is missing`).toBeDefined(); + return line!.slice(property.length + 1).trim(); + }; + + it.each([".headerNav > a", ".headerNav > button", ".sortTabs a", ".sortTabs button", ".filterChip"])( + "gives %s 44px of height", + (selector) => { + expect(phone(selector)).toContain("min-height: 44px"); + }, + ); + + // The handle's ellipsis needs a block box, so it cannot be the flex box the + // other links are. + it("gives the handle its 44px from the line and keeps the ellipsis", () => { + const chip = phone(".headerNav > .userChip"); + expect(chip).toContain("display: block"); + expect(chip).toContain("line-height: 44px"); + expect(phone(".userChip")).toContain("text-overflow: ellipsis"); + }); + + it("widens Alerts and the handle without lengthening the row", () => { + for (const selector of [".notifyLink", ".headerNav > .userChip"]) { + const lines = phone(selector); + expect(lines, selector).toContain("padding: 0 6px"); + expect(lines, selector).toContain("margin: 0 -6px"); + } + // Every box is border-box, so the padding comes out of the handle's + // 96px unless the cap grows with it. + const caps = phone(".headerNav > .userChip").concat(phone(".userChip")).filter((l) => l.startsWith("max-width:")); + expect(caps).toEqual(["max-width: 108px", "max-width: 96px"]); + }); + + it("gives the wordmark's two links a 24px line each", () => { + for (const selector of [".brand", ".pageTitle"]) { + expect(phone(selector), selector).toContain("min-height: 24px"); + } + }); + + // 8px of padding on each side of a 48px wordmark is 64px and a border, the + // 65px the header was with 14px around a 36.8px Sign in. + it("keeps the header as tall as it was", () => { + const [vertical] = px(phone(".pageHeader"), "padding").split(" "); + expect(parseFloat(vertical) * 2 + 2 * 24).toBeLessThanOrEqual(66); + }); + + // The ✕ and the link reach into the strip's own padding, so a 44px row in + // a strip with 9px above it does not push the header down 23px. + it("gives the notice's ✕ a 44px square inside the strip's padding", () => { + const close = phone(".fwNoticeClose"); + expect(close).toContain("min-width: 44px"); + expect(close).toContain("min-height: 44px"); + const [top, right] = px(declarations(rule(community, ".fwNotice")), "padding").split(" "); + expect(px(close, "margin")).toBe(`-${top} -${right} -${top} auto`); + expect(phone(".fwNoticeLink")).toContain("min-height: 44px"); + expect(px(phone(".fwNoticeLink"), "margin")).toBe(`-${top} 0`); + }); + + it("drops the gaps between the wall's control rows", () => { + expect(phone(".sortTabs")).toContain("row-gap: 0"); + expect(phone(".feedControls")).toContain("gap: 0 12px"); + }); +}); + /** A JSX opening tag from its `<` to its `>`, past the arrows in its handlers. */ function openingTag(source: string, from: number): string { let depth = 0;