Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.1.30

- Keep compact right-side sheet headers and close controls below the top device safe area.
- Place full-screen new-message, reply, and forward composer headers below the notch or Dynamic
Island.
- Add device-aware bottom spacing around new-message actions so they clear the home indicator.

## 0.1.29

- Replace the available-update banner with an animated progress status after an update starts, then
Expand Down
9 changes: 7 additions & 2 deletions app/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@ export const SheetContent = React.forwardRef<
"fixed inset-y-0 z-50 w-[min(92vw,480px)] bg-background p-5 shadow-lg motion-reduce:animate-none",
side === "left"
? "left-0 border-r data-[state=closed]:animate-sheet-out-left data-[state=open]:animate-sheet-in-left"
: "right-0 border-l data-[state=closed]:animate-sheet-out-right data-[state=open]:animate-sheet-in-right",
: "right-0 border-l max-md:pb-[max(1.25rem,env(safe-area-inset-bottom))] max-md:pt-[max(1.25rem,env(safe-area-inset-top))] data-[state=closed]:animate-sheet-out-right data-[state=open]:animate-sheet-in-right",
className
)}
ref={ref}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-3 top-3 inline-flex size-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
<DialogPrimitive.Close
className={cn(
"absolute right-3 top-3 inline-flex size-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
side === "right" && "max-md:top-[max(0.75rem,env(safe-area-inset-top))]"
)}
>
<X />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
Expand Down
8 changes: 7 additions & 1 deletion app/features/compose/compose-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,13 @@ export function ComposeForm(props: ComposeFormProps): React.ReactElement {
onChange={props.onEditorChange}
/>
<AttachmentList attachments={props.attachments} onRemove={props.onRemoveAttachment} />
<footer className="flex items-center justify-between gap-2 border-t bg-background/50 px-5 py-3">
<footer
className={cn(
"flex items-center justify-between gap-2 border-t bg-background/50 px-5 py-3",
props.presentation === "window" &&
"pb-[max(1rem,env(safe-area-inset-bottom))] md:pb-3"
)}
>
<div className="flex gap-2">
<Button
className={cn(props.presentation === "thread" && "hidden lg:inline-flex")}
Expand Down
2 changes: 1 addition & 1 deletion app/features/compose/compose-window.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function ComposeWindow({
aria-labelledby={titleId}
aria-modal="false"
className={cn(
"fixed inset-0 z-50 flex h-[100dvh] w-full flex-col overflow-hidden bg-card shadow-2xl outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring md:inset-auto md:bottom-0 md:right-4 md:z-40 md:h-[min(42rem,calc(100vh-5rem))] md:w-[min(42rem,calc(100vw-2rem))] md:rounded-t-lg md:border",
"fixed inset-0 z-50 flex h-[100dvh] w-full flex-col overflow-hidden bg-card pt-[env(safe-area-inset-top)] shadow-2xl outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring md:inset-auto md:bottom-0 md:right-4 md:z-40 md:h-[min(42rem,calc(100vh-5rem))] md:w-[min(42rem,calc(100vw-2rem))] md:rounded-t-lg md:border md:pt-0",
expanded &&
"md:bottom-6 md:right-1/2 md:h-[min(48rem,calc(100vh-3rem))] md:w-[min(64rem,calc(100vw-3rem))] md:translate-x-1/2 md:rounded-lg",
minimized &&
Expand Down
2 changes: 1 addition & 1 deletion app/features/compose/thread-compose-surface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function ThreadComposeSurface({
<section
aria-describedby={statusId}
aria-labelledby={titleId}
className="fixed inset-0 z-50 flex h-[100dvh] flex-col overflow-hidden bg-background outline-none lg:static lg:z-auto lg:mt-6 lg:h-auto lg:rounded-lg lg:border lg:bg-card lg:shadow-sm"
className="fixed inset-0 z-50 flex h-[100dvh] flex-col overflow-hidden bg-background pt-[env(safe-area-inset-top)] outline-none lg:static lg:z-auto lg:mt-6 lg:h-auto lg:rounded-lg lg:border lg:bg-card lg:pt-0 lg:shadow-sm"
ref={surfaceRef}
tabIndex={-1}
onKeyDown={(event) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hqbase",
"version": "0.1.29",
"version": "0.1.30",
"private": true,
"type": "module",
"packageManager": "pnpm@11.7.0",
Expand Down
25 changes: 25 additions & 0 deletions test/unit/app/layout/mobile-shell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ const sidebar = readFileSync(
new URL("../../../../app/components/layout/sidebar.tsx", import.meta.url),
"utf8"
);
const sheet = readFileSync(
new URL("../../../../app/components/ui/sheet.tsx", import.meta.url),
"utf8"
);
const composeWindow = readFileSync(
new URL("../../../../app/features/compose/compose-window.tsx", import.meta.url),
"utf8"
);
const composeForm = readFileSync(
new URL("../../../../app/features/compose/compose-form.tsx", import.meta.url),
"utf8"
);
const threadComposeSurface = readFileSync(
new URL("../../../../app/features/compose/thread-compose-surface.tsx", import.meta.url),
"utf8"
);
const styles = readFileSync(new URL("../../../../app/styles.css", import.meta.url), "utf8");

describe("mobile application shell", () => {
Expand All @@ -25,6 +41,15 @@ describe("mobile application shell", () => {
expect(sidebar).toContain("safe-area-inset-bottom");
});

it("keeps compact right sheets and composer controls clear of device safe areas", () => {
expect(sheet).toContain("max-md:pt-[max(1.25rem,env(safe-area-inset-top))]");
expect(sheet).toContain("max-md:pb-[max(1.25rem,env(safe-area-inset-bottom))]");
expect(sheet).toContain("max-md:top-[max(0.75rem,env(safe-area-inset-top))]");
expect(composeWindow).toContain("pt-[env(safe-area-inset-top)]");
expect(threadComposeSurface).toContain("pt-[env(safe-area-inset-top)]");
expect(composeForm).toContain("pb-[max(1rem,env(safe-area-inset-bottom))]");
});

it("keeps editable field text large enough to avoid iOS focus zoom", () => {
expect(styles).toContain("@media (max-width: 767px)");
expect(styles).toContain('[contenteditable="true"][class]');
Expand Down