diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 55e685a..dfbe818 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -54,3 +54,55 @@ h3, ::selection { background: color-mix(in srgb, var(--color-bronze) 22%, transparent); } + +/* Pedigree bracket connectors (ancestors grow rightward). Each leaf draws its + own half of the vertical spine + a horizontal stub, so lines stay correct + regardless of box heights: focus → 2 parents, each parent → 2 grandparents. */ +.ped-person { + display: flex; + align-items: center; +} +.ped-self { + flex-shrink: 0; +} +.ped-branch { + position: relative; + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-left: 2.5rem; +} +.ped-branch::before { + content: ""; + position: absolute; + left: -2.5rem; + top: 50%; + width: 2.5rem; + border-top: 1px solid var(--border); +} +.ped-leaf { + position: relative; + padding-left: 1.5rem; +} +.ped-leaf::before { + content: ""; + position: absolute; + left: 0; + top: 50%; + width: 1.5rem; + border-top: 1px solid var(--border); +} +.ped-leaf::after { + content: ""; + position: absolute; + left: 0; + top: 0; + bottom: 0; + border-left: 1px solid var(--border); +} +.ped-leaf:first-child::after { + top: 50%; +} +.ped-leaf:last-child::after { + bottom: 50%; +} diff --git a/frontend/app/trees/[id]/page.tsx b/frontend/app/trees/[id]/page.tsx index 32a4ad6..e533e59 100644 --- a/frontend/app/trees/[id]/page.tsx +++ b/frontend/app/trees/[id]/page.tsx @@ -36,7 +36,9 @@ export default function FamilyViewPage() { const [search, setSearch] = useState(""); const [firstName, setFirstName] = useState(""); // Inline add-relative form: which anchor + kind is open, and the typed name. - const [adding, setAdding] = useState<{ kind: AddKind; anchor: string } | null>(null); + // `key` keeps each empty slot's inline form independent (a person has 2 + // parents, 4 grandparents — many same-kind/anchor slots can coexist). + const [adding, setAdding] = useState<{ key: string; kind: AddKind; anchor: string } | null>(null); const [addName, setAddName] = useState(""); const load = useCallback(async () => { @@ -179,8 +181,18 @@ export default function FamilyViewPage() { ); }; - const AddSlot = ({ kind, anchor, label }: { kind: AddKind; anchor: string; label: string }) => - adding && adding.kind === kind && adding.anchor === anchor ? ( + const AddSlot = ({ + formKey, + kind, + anchor, + label, + }: { + formKey: string; + kind: AddKind; + anchor: string; + label: string; + }) => + adding?.key === formKey ? (
{ - setAdding({ kind, anchor }); + setAdding({ key: formKey, kind, anchor }); setAddName(""); }} className="w-44 rounded-lg border border-dashed border-[var(--border)] px-3 py-2 text-left text-sm text-[var(--muted)] hover:border-bronze hover:text-bronze" @@ -214,7 +226,39 @@ export default function FamilyViewPage() { ); - const parents = parentsOf(focus.id); + // Recursive ancestor chart (grows rightward): a node is its box plus a + // two-leaf "branch" of its parents, with CSS bracket connectors. Depth 0 = + // focus, capped at grandparents (depth 2). + const renderNode = ( + slotPersonId: string | null, + childId: string, + keyPrefix: string, + depth: number, + ): React.ReactNode => { + const box = slotPersonId ? ( + 0} /> + ) : ( + + ); + if (!slotPersonId || depth >= 2) { + return
{box}
; + } + const ps = parentsOf(slotPersonId); + return ( +
+
{box}
+
+
+ {renderNode(ps[0] ?? null, slotPersonId, `${keyPrefix}-a`, depth + 1)} +
+
+ {renderNode(ps[1] ?? null, slotPersonId, `${keyPrefix}-b`, depth + 1)} +
+
+
+ ); + }; + const partners = partnersOf(focus.id); const children = childrenOf(focus.id); @@ -237,46 +281,10 @@ export default function FamilyViewPage() { - {/* Pedigree: focus → parents → grandparents */} + {/* Pedigree: focus → parents → grandparents, with bracket connectors */} -
-
-
- Focus -
- -
- -
-
- Parents -
- {parents.map((pid) => ( - - ))} - {parents.length < 2 && } -
- -
-
- Grandparents -
- {parents.length === 0 && ( -
Add parents first.
- )} - {parents.map((pid) => ( -
- {parentsOf(pid).map((gp) => ( - - ))} - {parentsOf(pid).length < 2 && ( - - )} -
- ))} -
-
+
{renderNode(focus.id, focus.id, "ped", 0)}
@@ -289,7 +297,12 @@ export default function FamilyViewPage() { {partners.map((id) => ( ))} - + @@ -301,7 +314,12 @@ export default function FamilyViewPage() { {children.map((id) => ( ))} - +