fix(web): mobile layout — Tailwind content missed components/, truncate min-w-0 overflow, drawer portal
- tailwind.config content now scans components/ and lib/ (classes unique to those files were never generated: drawer fixed/inset/animation, search dropdown styling) - min-w-0 on truncate flex children + ranking grid children: page no longer overflows horizontally, mobile browsers no longer zoom out - drawer rendered via portal to <body> (blurred sticky header is a containing block for fixed descendants) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 5 changed files with +27 and −17
modified
apps/web/app/occupations/[code]/page.tsx
+1 −1
@@ -373,7 +373,7 @@ export default async function OccupationPage({ | ||
| 373 | 373 | href={`/occupations/${row.occupationCode}`} |
| 374 | 374 | className="flex items-baseline gap-3 px-5 py-3 hover:bg-[var(--wash)]" |
| 375 | 375 | > |
| 376 | − <span className="truncate text-sm">{row.occupation.title}</span> | |
| 376 | + <span className="min-w-0 truncate text-sm">{row.occupation.title}</span> | |
| 377 | 377 | <span className="ml-auto text-sm font-semibold tabular-nums"> |
| 378 | 378 | {row.substitution.toFixed(0)} |
| 379 | 379 | </span> |
modified
apps/web/app/occupations/page.tsx
+1 −1
@@ -68,7 +68,7 @@ export default async function OccupationsPage(): Promise<JSX.Element> { | ||
| 68 | 68 | href={`/occupations/${occupation.code}`} |
| 69 | 69 | className="flex items-baseline gap-2 py-2 hover:bg-[var(--wash)]" |
| 70 | 70 | > |
| 71 | − <span className="truncate text-sm">{occupation.title}</span> | |
| 71 | + <span className="min-w-0 truncate text-sm">{occupation.title}</span> | |
| 72 | 72 | {score !== undefined && ( |
| 73 | 73 | <span className="ml-auto shrink-0 rounded-full bg-[var(--seq-track)] px-2 py-0.5 text-xs font-semibold tabular-nums"> |
| 74 | 74 | {score.toFixed(0)} |
modified
apps/web/app/page.tsx
+4 −4
@@ -89,7 +89,7 @@ function RankList({ rows, startRank }: { rows: RankRow[]; startRank?: number }): | ||
| 89 | 89 | {startRank + index} |
| 90 | 90 | </span> |
| 91 | 91 | )} |
| 92 | − <span className="truncate font-medium">{row.occupation.title}</span> | |
| 92 | + <span className="min-w-0 truncate font-medium">{row.occupation.title}</span> | |
| 93 | 93 | <span className="ml-auto pl-3 text-sm font-semibold tabular-nums"> |
| 94 | 94 | {row.substitution.toFixed(0)} |
| 95 | 95 | </span> |
@@ -171,11 +171,11 @@ export default async function HomePage(): Promise<JSX.Element> { | ||
| 171 | 171 | from rater disagreement — a wide band is a claim we hold loosely. |
| 172 | 172 | </p> |
| 173 | 173 | <div className="mt-6 grid gap-6 lg:grid-cols-2"> |
| 174 | − <div> | |
| 174 | + <div className="min-w-0"> | |
| 175 | 175 | <h3 className="mb-2 text-sm font-medium text-[var(--ink-2)]">Most exposed</h3> |
| 176 | 176 | <RankList rows={top as RankRow[]} startRank={1} /> |
| 177 | 177 | </div> |
| 178 | − <div> | |
| 178 | + <div className="min-w-0"> | |
| 179 | 179 | <h3 className="mb-2 text-sm font-medium text-[var(--ink-2)]">Least exposed</h3> |
| 180 | 180 | <RankList rows={bottom as RankRow[]} /> |
| 181 | 181 | </div> |
@@ -196,7 +196,7 @@ export default async function HomePage(): Promise<JSX.Element> { | ||
| 196 | 196 | className="block border-b border-[var(--grid)] px-5 py-3 last:border-b-0 hover:bg-[var(--wash)]" |
| 197 | 197 | > |
| 198 | 198 | <div className="flex items-baseline gap-3"> |
| 199 | − <span className="truncate text-sm"> | |
| 199 | + <span className="min-w-0 truncate text-sm"> | |
| 200 | 200 | {SOC_MAJOR_GROUPS[group.prefix] ?? group.prefix} |
| 201 | 201 | </span> |
| 202 | 202 | <span className="ml-auto text-xs text-[var(--muted)]"> |
modified
apps/web/components/mobile-menu.tsx
+15 −10
@@ -2,6 +2,7 @@ | ||
| 2 | 2 | |
| 3 | 3 | import Link from "next/link"; |
| 4 | 4 | import { useEffect, useState } from "react"; |
| 5 | +import { createPortal } from "react-dom"; | |
| 5 | 6 | |
| 6 | 7 | export function MobileMenu({ items }: { items: ReadonlyArray<readonly [string, string]> }): JSX.Element { |
| 7 | 8 | const [open, setOpen] = useState(false); |
@@ -40,14 +41,17 @@ export function MobileMenu({ items }: { items: ReadonlyArray<readonly [string, s | ||
| 40 | 41 | )} |
| 41 | 42 | </button> |
| 42 | 43 | |
| 43 | − {open && ( | |
| 44 | − <> | |
| 45 | − <button | |
| 46 | − type="button" | |
| 47 | − aria-label="Close menu" | |
| 48 | − onClick={() => setOpen(false)} | |
| 49 | − className="fixed inset-0 z-40 cursor-default bg-black/25 backdrop-blur-[2px]" | |
| 50 | − /> | |
| 44 | + {/* Portal to <body>: the blurred sticky header is a containing block for | |
| 45 | + fixed descendants, which would trap and stretch the drawer. */} | |
| 46 | + {open && | |
| 47 | + createPortal( | |
| 48 | + <> | |
| 49 | + <button | |
| 50 | + type="button" | |
| 51 | + aria-label="Close menu" | |
| 52 | + onClick={() => setOpen(false)} | |
| 53 | + className="fixed inset-0 z-40 cursor-default bg-black/25 backdrop-blur-[2px]" | |
| 54 | + /> | |
| 51 | 55 | {/* Drawer sliding in from the right */} |
| 52 | 56 | <nav className="fixed right-0 top-0 z-50 flex h-dvh w-72 max-w-[85vw] animate-[drawer-in_180ms_ease-out] flex-col border-l border-[var(--border)] bg-[var(--surface-1)] shadow-2xl"> |
| 53 | 57 | <div className="flex items-center justify-between border-b border-[var(--grid)] px-5 py-3.5"> |
@@ -85,8 +89,9 @@ export function MobileMenu({ items }: { items: ReadonlyArray<readonly [string, s | ||
| 85 | 89 | </a> |
| 86 | 90 | </p> |
| 87 | 91 | </nav> |
| 88 | − </> | |
| 89 | − )} | |
| 92 | + </>, | |
| 93 | + document.body, | |
| 94 | + )} | |
| 90 | 95 | </div> |
| 91 | 96 | ); |
| 92 | 97 | } |
modified
apps/web/tailwind.config.ts
+6 −1
@@ -1,7 +1,12 @@ | ||
| 1 | 1 | import type { Config } from "tailwindcss"; |
| 2 | 2 | |
| 3 | 3 | export default { |
| 4 | − content: ["./app/**/*.{ts,tsx}", "../../packages/ui/src/**/*.{ts,tsx}"], | |
| 4 | + content: [ | |
| 5 | + "./app/**/*.{ts,tsx}", | |
| 6 | + "./components/**/*.{ts,tsx}", | |
| 7 | + "./lib/**/*.{ts,tsx}", | |
| 8 | + "../../packages/ui/src/**/*.{ts,tsx}", | |
| 9 | + ], | |
| 5 | 10 | theme: { |
| 6 | 11 | extend: {}, |
| 7 | 12 | }, |
| 8 | 13 | |