feat(stammbaum): bottom-right zoom + fit-to-screen control cluster (#692)
Move zoom controls out of the page header into a docked bottom-right cluster inside the canvas (one-handed phone reach, Leonie) and add a fit-to-screen button (data-testid=fit-to-screen). Add the 5 new i18n keys to de/en/es. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1106,6 +1106,11 @@
|
||||
"stammbaum_relationships_heading": "Stammbaum & Beziehungen",
|
||||
"stammbaum_zoom_in": "Vergrößern",
|
||||
"stammbaum_zoom_out": "Verkleinern",
|
||||
"stammbaum_fit_to_screen": "An Bildschirm anpassen",
|
||||
"stammbaum_affordance_hint": "Ziehen zum Erkunden · Zusammendrücken zum Zoomen",
|
||||
"stammbaum_affordance_dismiss": "Hinweis schließen",
|
||||
"stammbaum_close_panel": "Schließen",
|
||||
"stammbaum_centre_on_person": "Auf diese Person zentrieren",
|
||||
"relation_error_duplicate": "Diese Beziehung gibt es bereits.",
|
||||
"relation_error_circular": "Diese Beziehung würde einen Kreis erzeugen.",
|
||||
"relation_error_self": "Eine Person kann nicht mit sich selbst verbunden werden.",
|
||||
|
||||
@@ -1106,6 +1106,11 @@
|
||||
"stammbaum_relationships_heading": "Family tree & relationships",
|
||||
"stammbaum_zoom_in": "Zoom in",
|
||||
"stammbaum_zoom_out": "Zoom out",
|
||||
"stammbaum_fit_to_screen": "Fit to screen",
|
||||
"stammbaum_affordance_hint": "Drag to explore · pinch to zoom",
|
||||
"stammbaum_affordance_dismiss": "Dismiss hint",
|
||||
"stammbaum_close_panel": "Close",
|
||||
"stammbaum_centre_on_person": "Centre on this person",
|
||||
"relation_error_duplicate": "This relationship already exists.",
|
||||
"relation_error_circular": "This relationship would form a cycle.",
|
||||
"relation_error_self": "A person cannot be related to themselves.",
|
||||
|
||||
@@ -1106,6 +1106,11 @@
|
||||
"stammbaum_relationships_heading": "Árbol genealógico & relaciones",
|
||||
"stammbaum_zoom_in": "Acercar",
|
||||
"stammbaum_zoom_out": "Alejar",
|
||||
"stammbaum_fit_to_screen": "Ajustar a la pantalla",
|
||||
"stammbaum_affordance_hint": "Arrastra para explorar · pellizca para ampliar",
|
||||
"stammbaum_affordance_dismiss": "Cerrar aviso",
|
||||
"stammbaum_close_panel": "Cerrar",
|
||||
"stammbaum_centre_on_person": "Centrar en esta persona",
|
||||
"relation_error_duplicate": "Esta relación ya existe.",
|
||||
"relation_error_circular": "Esta relación crearía un ciclo.",
|
||||
"relation_error_self": "Una persona no puede estar relacionada consigo misma.",
|
||||
|
||||
38
frontend/src/lib/person/genealogy/StammbaumControls.svelte
Normal file
38
frontend/src/lib/person/genealogy/StammbaumControls.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
|
||||
interface Props {
|
||||
onZoomIn: () => void;
|
||||
onZoomOut: () => void;
|
||||
onFit: () => void;
|
||||
}
|
||||
|
||||
let { onZoomIn, onZoomOut, onFit }: Props = $props();
|
||||
|
||||
// Docked bottom-right inside the canvas — the primary one-handed reach zone on a
|
||||
// phone (Leonie). The container ignores pointer events so canvas gestures pass
|
||||
// through the gaps; only the buttons capture taps.
|
||||
const buttonClass =
|
||||
'pointer-events-auto inline-flex h-11 w-11 items-center justify-center rounded-sm border border-line bg-surface text-lg text-ink-2 shadow-sm transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:outline-none';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="pointer-events-none absolute right-4 z-30 flex flex-col gap-1"
|
||||
style="bottom: max(calc(env(safe-area-inset-bottom, 0px) + 1rem), 1rem);"
|
||||
>
|
||||
<button type="button" onclick={onZoomIn} aria-label={m.stammbaum_zoom_in()} class={buttonClass}>
|
||||
+
|
||||
</button>
|
||||
<button type="button" onclick={onZoomOut} aria-label={m.stammbaum_zoom_out()} class={buttonClass}>
|
||||
−
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="fit-to-screen"
|
||||
onclick={onFit}
|
||||
aria-label={m.stammbaum_fit_to_screen()}
|
||||
class={buttonClass}
|
||||
>
|
||||
⤢
|
||||
</button>
|
||||
</div>
|
||||
@@ -3,6 +3,7 @@ import { m } from '$lib/paraglide/messages.js';
|
||||
import { page } from '$app/state';
|
||||
import StammbaumTree from '$lib/person/genealogy/StammbaumTree.svelte';
|
||||
import StammbaumSidePanel from '$lib/person/genealogy/StammbaumSidePanel.svelte';
|
||||
import StammbaumControls from '$lib/person/genealogy/StammbaumControls.svelte';
|
||||
import {
|
||||
type PanZoomState,
|
||||
DEFAULT_VIEW,
|
||||
@@ -36,6 +37,9 @@ function zoomIn() {
|
||||
function zoomOut() {
|
||||
view = { ...view, z: clampZoom(view.z - ZOOM_STEP_KB) };
|
||||
}
|
||||
function fitToScreen() {
|
||||
view = DEFAULT_VIEW;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 4.25rem = 4rem navbar (h-16) + 0.25rem accent strip (h-1).
|
||||
@@ -46,26 +50,6 @@ function zoomOut() {
|
||||
class="flex shrink-0 items-center justify-between border-b border-line bg-surface px-6 py-4"
|
||||
>
|
||||
<h1 class="font-serif text-2xl text-ink">{m.nav_stammbaum()}</h1>
|
||||
{#if data.nodes.length > 0}
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={zoomOut}
|
||||
aria-label={m.stammbaum_zoom_out()}
|
||||
class="inline-flex h-11 w-11 items-center justify-center rounded-sm border border-line bg-surface text-ink-2 transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:outline-none"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={zoomIn}
|
||||
aria-label={m.stammbaum_zoom_in()}
|
||||
class="inline-flex h-11 w-11 items-center justify-center rounded-sm border border-line bg-surface text-ink-2 transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:outline-none"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
{#if data.nodes.length === 0}
|
||||
@@ -98,7 +82,7 @@ function zoomOut() {
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<div class="flex-1 overflow-hidden bg-muted/20">
|
||||
<div class="relative flex-1 overflow-hidden bg-muted/20">
|
||||
<StammbaumTree
|
||||
nodes={data.nodes}
|
||||
edges={data.edges}
|
||||
@@ -107,6 +91,7 @@ function zoomOut() {
|
||||
onPanZoom={(v) => (view = v)}
|
||||
onSelect={(id) => (selectedId = id)}
|
||||
/>
|
||||
<StammbaumControls onZoomIn={zoomIn} onZoomOut={zoomOut} onFit={fitToScreen} />
|
||||
</div>
|
||||
{#if selectedNode}
|
||||
<!-- Desktop: side panel on the right -->
|
||||
|
||||
Reference in New Issue
Block a user