diff --git a/frontend/app/trees/[id]/tree/page.tsx b/frontend/app/trees/[id]/tree/page.tsx
index ee9ad00..d37dc16 100644
--- a/frontend/app/trees/[id]/tree/page.tsx
+++ b/frontend/app/trees/[id]/tree/page.tsx
@@ -12,6 +12,7 @@ import type { components } from "@/lib/api/schema";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { FanChart } from "@/components/fan-chart";
+import { DepthControl } from "@/components/depth-control";
type Person = components["schemas"]["PersonRead"];
type Relationship = components["schemas"]["RelationshipRead"];
@@ -280,67 +281,6 @@ export default function TreePage() {
);
- // Slider + number stepper + "All" for one generation direction.
- const DepthControl = ({
- label,
- icon,
- value,
- all,
- onValue,
- onAll,
- disabled,
- }: {
- label: string;
- icon: string;
- value: number;
- all: boolean;
- onValue: (v: number) => void;
- onAll: (b: boolean) => void;
- disabled?: boolean;
- }) => (
-
-
- {icon} {label}
-
- onValue(Number(e.target.value))}
- className="w-28 accent-bronze"
- aria-label={`${label} generations`}
- />
- {all ? (
- All
- ) : (
- onValue(Math.max(0, Math.min(99, Number(e.target.value) || 0)))}
- className="h-7 w-12 rounded-md border border-[var(--border)] bg-[var(--surface)] px-1 text-center text-sm"
- />
- )}
-
-
- );
return (
diff --git a/frontend/components/depth-control.tsx b/frontend/components/depth-control.tsx
new file mode 100644
index 0000000..4bec1da
--- /dev/null
+++ b/frontend/components/depth-control.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+// Slider + number stepper + "All" toggle for one generation direction
+// (ancestors or descendants). Shared by the member and public tree views.
+export function DepthControl({
+ label,
+ icon,
+ value,
+ all,
+ onValue,
+ onAll,
+ disabled,
+}: {
+ label: string;
+ icon: string;
+ value: number;
+ all: boolean;
+ onValue: (v: number) => void;
+ onAll: (b: boolean) => void;
+ disabled?: boolean;
+}) {
+ return (
+
+
+ {icon} {label}
+
+ onValue(Number(e.target.value))}
+ className="w-28 accent-bronze"
+ aria-label={`${label} generations`}
+ />
+ {all ? (
+ All
+ ) : (
+ onValue(Math.max(0, Math.min(99, Number(e.target.value) || 0)))}
+ className="h-7 w-12 rounded-md border border-[var(--border)] bg-[var(--surface)] px-1 text-center text-sm"
+ />
+ )}
+
+
+ );
+}
diff --git a/frontend/components/public-tree-chart.tsx b/frontend/components/public-tree-chart.tsx
index 3ee121d..d7533f6 100644
--- a/frontend/components/public-tree-chart.tsx
+++ b/frontend/components/public-tree-chart.tsx
@@ -3,9 +3,10 @@
// Vendored family-chart styles (the package blocks the CSS subpath export).
import "../app/trees/[id]/tree/chart.css";
-import { useCallback, useEffect, useMemo, useRef } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { components } from "@/lib/api/schema";
+import { DepthControl } from "@/components/depth-control";
type Person = components["schemas"]["PersonRead"];
type Relationship = components["schemas"]["RelationshipRead"];
@@ -42,6 +43,16 @@ export function PublicTreeChart({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const chartRef = useRef
(null);
+ // Generations to show around the focus, each settable (or "all"). ALL_DEPTH is
+ // a number bigger than any real lineage; the chart only renders real people.
+ const [ancDepth, setAncDepth] = useState(3);
+ const [progDepth, setProgDepth] = useState(2);
+ const [ancAll, setAncAll] = useState(false);
+ const [progAll, setProgAll] = useState(false);
+ const ALL_DEPTH = 100;
+ const effAnc = ancAll ? ALL_DEPTH : ancDepth;
+ const effProg = progAll ? ALL_DEPTH : progDepth;
+
const parentsOf = useCallback(
(id: string) =>
rels.filter((x) => x.type === "parent_child" && x.person_to_id === id).map((x) => x.person_from_id),
@@ -126,8 +137,8 @@ export function PublicTreeChart({
const chart = f3.createChart(containerRef.current, data);
chart.setCardHtml().setCardDisplay([["first name", "last name"], ["birthday"]]);
chart.setOrientationHorizontal();
- chart.setAncestryDepth?.(3);
- chart.setProgenyDepth?.(2);
+ chart.setAncestryDepth?.(effAnc);
+ chart.setProgenyDepth?.(effProg);
chart.setAfterUpdate?.(() => {
const md = chart.getMainDatum?.();
const id = md?.id ?? md?.data?.id;
@@ -155,8 +166,41 @@ export function PublicTreeChart({
}
}, [focusId]);
+ // Apply depth changes to the live chart without a full rebuild.
+ useEffect(() => {
+ if (!chartRef.current) return;
+ chartRef.current.setAncestryDepth?.(effAnc);
+ chartRef.current.setProgenyDepth?.(effProg);
+ chartRef.current.updateTree?.();
+ }, [effAnc, effProg]);
+
return (
+
+ Generations
+ {
+ setAncAll(false);
+ setAncDepth(v);
+ }}
+ onAll={setAncAll}
+ />
+ {
+ setProgAll(false);
+ setProgDepth(v);
+ }}
+ onAll={setProgAll}
+ />
+