Rework safe area insets with CSS variables and per-element spacers
Replace container-level env(safe-area-inset-*) inline padding with
--safe-top/--safe-bottom CSS variables applied per-element: drawer,
task list panel, detail panels, FAB, settings overlay, error banner,
and setup screen. Wrap task list in {#key app.activeListId} to force
re-render on list switch (co-located with FAB safe-bottom fix).
This commit is contained in:
parent
501f991f2c
commit
ca52ed9fee
|
|
@ -19,18 +19,27 @@
|
||||||
class="relative h-full w-full overflow-hidden bg-surface-light text-text-light dark:bg-surface-dark dark:text-text-dark"
|
class="relative h-full w-full overflow-hidden bg-surface-light text-text-light dark:bg-surface-dark dark:text-text-dark"
|
||||||
class:rounded-xl={isLinux}
|
class:rounded-xl={isLinux}
|
||||||
class:linux-window-border={isLinux}
|
class:linux-window-border={isLinux}
|
||||||
style="container-type: inline-size{isMobile ? '; padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom)' : ''}"
|
style="container-type: inline-size"
|
||||||
>
|
>
|
||||||
{#if app.error}
|
{#if app.error}
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 left-0 right-0 z-50 flex items-center justify-between bg-danger px-4 py-2 text-sm text-white"
|
class="absolute top-0 left-0 right-0 z-50 flex items-center justify-between bg-danger px-4 py-2 text-sm text-white"
|
||||||
|
style="top: env(safe-area-inset-top)"
|
||||||
>
|
>
|
||||||
<span>{app.error}</span>
|
<span>{app.error}</span>
|
||||||
<button onclick={() => app.clearError()} class="ml-2 font-bold">✕</button>
|
<button onclick={() => app.clearError()} class="ml-2 font-bold">✕</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if app.screen === "missing"}
|
{#if app.initialSync}
|
||||||
|
<div class="flex h-full flex-col items-center justify-center gap-4">
|
||||||
|
<svg class="h-8 w-8 animate-spin text-primary" viewBox="0 0 24 24" fill="none">
|
||||||
|
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.25" />
|
||||||
|
<path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
<p class="text-sm text-text-secondary-light dark:text-text-secondary-dark">Syncing workspace…</p>
|
||||||
|
</div>
|
||||||
|
{:else if app.screen === "missing"}
|
||||||
<div class="flex h-full items-center justify-center p-6">
|
<div class="flex h-full items-center justify-center p-6">
|
||||||
<div class="w-full max-w-sm rounded-2xl bg-card-light p-8 shadow-lg dark:bg-card-dark">
|
<div class="w-full max-w-sm rounded-2xl bg-card-light p-8 shadow-lg dark:bg-card-dark">
|
||||||
<h1 class="mb-1 text-2xl font-bold">Workspace Not Found</h1>
|
<h1 class="mb-1 text-2xl font-bold">Workspace Not Found</h1>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,12 @@ body {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Safe area CSS variable — content elements opt into this, overlays don't */
|
||||||
|
:root {
|
||||||
|
--safe-top: env(safe-area-inset-top);
|
||||||
|
--safe-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
.linux-window-border {
|
.linux-window-border {
|
||||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25), 0 0 2px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25), 0 0 2px rgba(0, 0, 0, 0.1);
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,7 @@
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div class="flex h-full flex-col" onmousedown={handleDrag}>
|
<div class="flex h-full flex-col" onmousedown={handleDrag}>
|
||||||
|
<div class="shrink-0" style="height: var(--safe-top)"></div>
|
||||||
<!-- Title bar area with window controls -->
|
<!-- Title bar area with window controls -->
|
||||||
<header class="flex h-11 shrink-0 items-center justify-between px-2">
|
<header class="flex h-11 shrink-0 items-center justify-between px-2">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@
|
||||||
>
|
>
|
||||||
<!-- Drawer panel -->
|
<!-- Drawer panel -->
|
||||||
<div class="flex h-full shrink-0 flex-col bg-surface-light dark:bg-surface-dark" style="width: 80cqi">
|
<div class="flex h-full shrink-0 flex-col bg-surface-light dark:bg-surface-dark" style="width: 80cqi">
|
||||||
|
<div class="shrink-0" style="height: var(--safe-top)"></div>
|
||||||
<!-- Drawer header: workspace switcher + settings -->
|
<!-- Drawer header: workspace switcher + settings -->
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
|
|
@ -327,7 +328,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Drawer footer: sync status -->
|
<!-- Drawer footer: sync status -->
|
||||||
<div class="shrink-0 px-4 py-2.5">
|
<div class="shrink-0 px-4 py-2.5" style="padding-bottom: max(0.625rem, var(--safe-bottom))">
|
||||||
{#if app.isWebdav}
|
{#if app.isWebdav}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<!-- Status dot -->
|
<!-- Status dot -->
|
||||||
|
|
@ -374,6 +375,7 @@
|
||||||
>
|
>
|
||||||
<!-- Sub-panel: Task list -->
|
<!-- Sub-panel: Task list -->
|
||||||
<div class="relative flex h-full w-1/3 flex-col">
|
<div class="relative flex h-full w-1/3 flex-col">
|
||||||
|
<div class="shrink-0" style="height: var(--safe-top)"></div>
|
||||||
<!-- Header / drag region -->
|
<!-- Header / drag region -->
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<header
|
<header
|
||||||
|
|
@ -495,6 +497,7 @@
|
||||||
|
|
||||||
<!-- Task list -->
|
<!-- Task list -->
|
||||||
<main class="flex-1 overflow-y-auto">
|
<main class="flex-1 overflow-y-auto">
|
||||||
|
{#key app.activeListId}
|
||||||
{#if app.lists.length === 0}
|
{#if app.lists.length === 0}
|
||||||
<div class="flex h-full flex-col items-center justify-center p-8 text-center">
|
<div class="flex h-full flex-col items-center justify-center p-8 text-center">
|
||||||
<p class="text-lg font-medium opacity-60">No lists yet</p>
|
<p class="text-lg font-medium opacity-60">No lists yet</p>
|
||||||
|
|
@ -558,11 +561,13 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
{/key}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- FAB button -->
|
<!-- FAB button -->
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute bottom-6 left-0 right-0 z-20 flex justify-center transition-all duration-250 ease-out {newTaskState.open ? 'opacity-0 scale-75' : ''} {showDrawer || taskStack.length > 0 ? 'translate-y-24 opacity-0' : 'translate-y-0 opacity-100'}"
|
class="pointer-events-none absolute left-0 right-0 z-20 flex justify-center transition-all duration-250 ease-out {newTaskState.open ? 'opacity-0 scale-75' : ''} {showDrawer || taskStack.length > 0 ? 'translate-y-24 opacity-0' : 'translate-y-0 opacity-100'}"
|
||||||
|
style="bottom: max(1.5rem, var(--safe-bottom))"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onclick={() => { if (app.activeListId) newTaskState.open = true; }}
|
onclick={() => { if (app.activeListId) newTaskState.open = true; }}
|
||||||
|
|
@ -578,6 +583,7 @@
|
||||||
|
|
||||||
<!-- Sub-panel: Task detail -->
|
<!-- Sub-panel: Task detail -->
|
||||||
<div class="relative flex h-full w-1/3 flex-col bg-surface-light dark:bg-surface-dark">
|
<div class="relative flex h-full w-1/3 flex-col bg-surface-light dark:bg-surface-dark">
|
||||||
|
<div class="shrink-0" style="height: var(--safe-top)"></div>
|
||||||
{#if parentTask}
|
{#if parentTask}
|
||||||
{#key parentTask.id}
|
{#key parentTask.id}
|
||||||
<TaskDetailView task={parentTask} onback={closeDetail} onopen={pushTask} />
|
<TaskDetailView task={parentTask} onback={closeDetail} onopen={pushTask} />
|
||||||
|
|
@ -587,6 +593,7 @@
|
||||||
|
|
||||||
<!-- Sub-panel: Subtask detail -->
|
<!-- Sub-panel: Subtask detail -->
|
||||||
<div class="relative flex h-full w-1/3 flex-col bg-surface-light dark:bg-surface-dark">
|
<div class="relative flex h-full w-1/3 flex-col bg-surface-light dark:bg-surface-dark">
|
||||||
|
<div class="shrink-0" style="height: var(--safe-top)"></div>
|
||||||
{#if subtaskDetail}
|
{#if subtaskDetail}
|
||||||
{#key subtaskDetail.id}
|
{#key subtaskDetail.id}
|
||||||
<TaskDetailView task={subtaskDetail} onback={closeDetail} />
|
<TaskDetailView task={subtaskDetail} onback={closeDetail} />
|
||||||
|
|
@ -603,7 +610,7 @@
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
class="absolute inset-0 z-50 flex transition-opacity duration-200 {showSettings ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}"
|
class="absolute inset-0 z-50 flex transition-opacity duration-200 {showSettings ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}"
|
||||||
style="padding: 4%"
|
style="padding: 4%; padding-top: max(4%, env(safe-area-inset-top)); padding-bottom: max(4%, env(safe-area-inset-bottom))"
|
||||||
>
|
>
|
||||||
<!-- Backdrop -->
|
<!-- Backdrop -->
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue