diff --git a/src/__tests__/pwa-mobile-shell-guard.test.ts b/src/__tests__/pwa-mobile-shell-guard.test.ts index 0daf812ef..964f7df3e 100644 --- a/src/__tests__/pwa-mobile-shell-guard.test.ts +++ b/src/__tests__/pwa-mobile-shell-guard.test.ts @@ -78,6 +78,20 @@ describe("the shell owns the safe area", () => { ); }); + it("a bottom sheet on the keyboard drops its home-indicator padding", () => { + // Standing on the keyboard, the safe-area inset left a ~34 px gap + // between the sheet's last row and the keys. + expect(code(read("src/app/globals.css"))).toMatch( + /@custom-variant keyboard-open \(html\[data-keyboard="open"\] &\);/, + ); + const sheet = code(read("src/components/ui/responsive-sheet.tsx")); + const padded = (sheet.match( + /"[^"]*pb-\[calc\(env\(safe-area-inset-bottom[^"]*"/g, + ) ?? []) as string[]; + expect(padded.length).toBe(2); + for (const cls of padded) expect(cls).toMatch(/\bkeyboard-open:pb-4\b/); + }); + it("the sheet primitive names its side for those rules", () => { expect(read("src/components/ui/sheet.tsx")).toMatch( /data-sheet-side=\{side\}/, diff --git a/src/app/globals.css b/src/app/globals.css index 6d230f1fe..fcb04514a 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -76,6 +76,13 @@ } } +/* + * `keyboard-open:` — while the on-screen keyboard is up (see the keyboard + * rules further down). A bottom sheet then stands on the keyboard, not on + * the home indicator, so its safe-area padding has nothing left to clear. + */ +@custom-variant keyboard-open (html[data-keyboard="open"] &); + @custom-variant shell-mobile { @media (max-width: 47.98rem), (pointer: coarse) and (max-height: 520px) { @slot; diff --git a/src/components/ui/responsive-sheet.tsx b/src/components/ui/responsive-sheet.tsx index 3f9f67edc..17fa6f950 100644 --- a/src/components/ui/responsive-sheet.tsx +++ b/src/components/ui/responsive-sheet.tsx @@ -257,7 +257,10 @@ export function ResponsiveSheet({ // Without a footer the body is the sheet's bottom edge, so it // keeps its last row above the home indicator of an // installed app; the footer does that when there is one. - !footer && "pb-[calc(env(safe-area-inset-bottom,0px)+1rem)]", + // Standing on the keyboard, the sheet is clear of the + // indicator already; the inset would leave a gap above it. + !footer && + "keyboard-open:pb-4 pb-[calc(env(safe-area-inset-bottom,0px)+1rem)]", bodyClassName, )} > @@ -266,7 +269,7 @@ export function ResponsiveSheet({ {footer ? ( {footer}