refactor: optimize safe area handling in web components

Apply safe area insets to sticky/fixed headers (using top inset) and bottom-fixed elements (using bottom inset), rather than to scroll containers. This ensures proper spacing on devices with notches and home indicators (e.g., iPhone).

- Sessions List page: Move header outside scroll container with sticky positioning and pt-[env(safe-area-inset-top)]
- SessionList component: Add renderHeader prop to optionally hide internal header when parent renders it
- HappyComposer: Change bottom padding to pb-[calc(0.75rem+env(safe-area-inset-bottom))] for proper iPhone home indicator spacing
This commit is contained in:
weishu
2025-12-25 17:44:44 +08:00
parent 0be023b23b
commit 8e6ab5394b
3 changed files with 72 additions and 25 deletions
@@ -436,7 +436,7 @@ export function HappyComposer(props: {
])
return (
<div className="px-3 pb-3 pt-2 bg-[var(--app-bg)]">
<div className="px-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))] pt-2 bg-[var(--app-bg)]">
<div className="mx-auto w-full max-w-content">
<ComposerPrimitive.Root className="relative">
{overlays}
+17 -12
View File
@@ -98,22 +98,27 @@ export function SessionList(props: {
onNewSession: () => void
onRefresh: () => void
isLoading: boolean
renderHeader?: boolean
}) {
const { renderHeader = true } = props
return (
<div className="mx-auto w-full max-w-content flex flex-col">
<div className="flex items-center justify-between px-3 py-1">
<div className="text-xs text-[var(--app-hint)]">
{props.sessions.length} sessions
{renderHeader ? (
<div className="flex items-center justify-between px-3 py-1">
<div className="text-xs text-[var(--app-hint)]">
{props.sessions.length} sessions
</div>
<button
type="button"
onClick={props.onNewSession}
className="session-list-new-button p-1.5 rounded-full text-[var(--app-link)] transition-colors"
title="New Session"
>
<PlusIcon className="h-5 w-5" />
</button>
</div>
<button
type="button"
onClick={props.onNewSession}
className="session-list-new-button p-1.5 rounded-full text-[var(--app-link)] transition-colors"
title="New Session"
>
<PlusIcon className="h-5 w-5" />
</button>
</div>
) : null}
<div className="flex flex-col divide-y divide-[var(--app-divider)]">
{props.sessions.map((s) => (
+54 -12
View File
@@ -44,6 +44,26 @@ function BackIcon(props: { className?: string }) {
)
}
function PlusIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={props.className}
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
)
}
function SessionsPage() {
const { api } = useAppContext()
const navigate = useNavigate()
@@ -54,18 +74,40 @@ function SessionsPage() {
}, [refetch])
return (
<div className="flex-1 overflow-y-auto p-4 pt-[calc(1rem+env(safe-area-inset-top))] space-y-4">
{error ? <div className="text-sm text-red-600">{error}</div> : null}
<SessionList
sessions={sessions}
onSelect={(sessionId) => navigate({
to: '/sessions/$sessionId',
params: { sessionId },
})}
onNewSession={() => navigate({ to: '/sessions/new' })}
onRefresh={handleRefresh}
isLoading={isLoading}
/>
<div className="flex h-full flex-col">
<div className="bg-[var(--app-bg)] pt-[env(safe-area-inset-top)]">
<div className="mx-auto w-full max-w-content flex items-center justify-between px-3 py-2">
<div className="text-xs text-[var(--app-hint)]">
{sessions.length} sessions
</div>
<button
type="button"
onClick={() => navigate({ to: '/sessions/new' })}
className="session-list-new-button p-1.5 rounded-full text-[var(--app-link)] transition-colors"
title="New Session"
>
<PlusIcon className="h-5 w-5" />
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto">
{error ? (
<div className="mx-auto w-full max-w-content px-3 py-2">
<div className="text-sm text-red-600">{error}</div>
</div>
) : null}
<SessionList
sessions={sessions}
onSelect={(sessionId) => navigate({
to: '/sessions/$sessionId',
params: { sessionId },
})}
onNewSession={() => navigate({ to: '/sessions/new' })}
onRefresh={handleRefresh}
isLoading={isLoading}
renderHeader={false}
/>
</div>
</div>
)
}