This commit is contained in:
freedakgmail
2025-11-22 19:52:55 +08:00
commit 43c8389705
28950 changed files with 3640981 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
"use client"
import type { FamilyMember } from "@/types/family"
import { cn } from "@/lib/utils"
interface FamilyNodeProps {
member: FamilyMember
spouse?: FamilyMember
isRoot?: boolean
onSelect?: (member: FamilyMember) => void
}
export function FamilyNode({ member, spouse, isRoot, onSelect }: FamilyNodeProps) {
return (
<div
className={cn(
"relative flex flex-col items-center gap-2 p-3 rounded-lg border-2 transition-all cursor-pointer hover:scale-105 shadow-sm w-32",
isRoot ? "border-primary bg-primary/5" : "border-border bg-card",
member.gender === "female" ? "rounded-full w-28 h-28 justify-center" : "rounded-lg", // Traditional round sky/square earth or just variety
)}
onClick={() => onSelect?.(member)}
>
<div className="absolute -top-3 bg-background border border-border px-2 py-0.5 rounded-full text-[10px] text-muted-foreground font-mono">
{member.generation}
</div>
<div className="h-10 w-10 rounded-full bg-muted flex items-center justify-center text-muted-foreground overflow-hidden border border-border/50">
{member.avatarUrl ? (
<img
src={member.avatarUrl || "/placeholder.svg"}
alt={member.fullName}
className="h-full w-full object-cover"
/>
) : (
<span className="font-serif font-bold text-lg text-foreground/70">{member.surname}</span>
)}
</div>
<div className="text-center">
<div className="font-serif font-bold text-foreground leading-tight">{member.givenName}</div>
{member.courtesyName && (
<div className="text-[10px] text-muted-foreground mt-0.5"> {member.courtesyName}</div>
)}
</div>
{member.lifeStatus === "deceased" && (
<div className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-neutral-400 ring-2 ring-background" />
)}
</div>
)
}
+81
View File
@@ -0,0 +1,81 @@
"use client"
import type { FamilyMember } from "@/types/family"
import { FamilyNode } from "./family-node"
import { useFamily } from "@/context/family-context"
interface TreeLayoutProps {
rootId: string
onSelectMember?: (member: FamilyMember) => void
}
export function TreeLayout({ rootId, onSelectMember }: TreeLayoutProps) {
const { getMember } = useFamily()
const root = getMember(rootId)
if (!root) return null
// Recursive component to render the tree
const TreeNode = ({ memberId }: { memberId: string }) => {
const member = getMember(memberId)
if (!member) return null
const hasChildren = member.childrenIds && member.childrenIds.length > 0
return (
<div className="flex flex-col items-center">
<div className="relative z-10">
<FamilyNode member={member} onSelect={onSelectMember} />
{/* Connector to children */}
{hasChildren && <div className="absolute top-full left-1/2 -translate-x-1/2 h-8 w-px bg-border" />}
</div>
{hasChildren && (
<div className="flex items-start pt-8 relative">
{/* Horizontal connecting line */}
{member.childrenIds.length > 1 && (
<div className="absolute top-0 left-[50%] -translate-x-1/2 h-px bg-border w-[calc(100%-8rem)]" /> // Rough approx for connector width
)}
<div className="flex gap-8 relative">
{/* Top connecting lines for children */}
{member.childrenIds.length > 1 && (
<div className="absolute top-0 left-0 right-0 h-px bg-transparent">
{/* This needs precise calculation or just use CSS pseudo elements on children */}
</div>
)}
{member.childrenIds.map((childId, index) => (
<div key={childId} className="flex flex-col items-center relative">
{/* Vertical line from parent's horizontal line to child */}
<div className="h-8 w-px bg-border absolute -top-8 left-1/2 -translate-x-1/2"></div>
{/* Horizontal connector fix for first/last child */}
{member.childrenIds.length > 1 && (
<>
{index === 0 && <div className="absolute -top-8 right-0 w-1/2 h-px bg-border"></div>}
{index === member.childrenIds.length - 1 && (
<div className="absolute -top-8 left-0 w-1/2 h-px bg-border"></div>
)}
{index > 0 && index < member.childrenIds.length - 1 && (
<div className="absolute -top-8 left-0 w-full h-px bg-border"></div>
)}
</>
)}
<TreeNode memberId={childId} />
</div>
))}
</div>
</div>
)}
</div>
)
}
return (
<div className="p-12 min-w-fit">
<TreeNode memberId={rootId} />
</div>
)
}