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} + /> +