Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
00b787b
feat: add hand-drawn animation to FamilyTreePlaceholder
jthrilly Mar 19, 2026
75c9dac
fix(e2e): resolve SILOS WebKit failures with announcement-based DnD
jthrilly Mar 19, 2026
b92b77d
refactor: update QuickStart wizard types for parent relationship rede…
jthrilly Mar 19, 2026
5a19a1c
refactor: replace biological toggle with raised/bio/auxiliary fields …
jthrilly Mar 19, 2026
7b4858a
refactor: add donor/surrogate selection to BioParentsStep
jthrilly Mar 19, 2026
1048c12
feat: add SiblingParentMappingStep for per-sibling parent assignment
jthrilly Mar 19, 2026
79a0fce
feat: add ParentGroupingStep for zero-siblings parent grouping
jthrilly Mar 19, 2026
e02906d
feat: wire SiblingParentMappingStep and ParentGroupingStep into wizard
jthrilly Mar 19, 2026
9cbd0b2
use node context menu; animate pedigree census placeholder
jthrilly Mar 19, 2026
de418c3
refactor: update generateQuickStartNetwork for flexible parent relati…
jthrilly Mar 19, 2026
135a1f7
fix: resolve lint errors in new wizard steps
jthrilly Mar 19, 2026
cc06454
feat: add pre-populated family stories for FamilyTreeCensus
jthrilly Mar 19, 2026
b983720
refactor: remove filled/unfilled distinction and label from FamilyTre…
jthrilly Mar 19, 2026
061398c
feat: load FamilyTree store from stageMetadata when available
jthrilly Mar 19, 2026
7554d1a
fix: let DropdownMenu handle its own open state for NodeContextMenu
jthrilly Mar 19, 2026
1de109b
fix: use onPointerUp for context menu to bypass drag source click sup…
jthrilly Mar 19, 2026
4f4a29c
feat: add bio-parent edge type, parent partnership step, and flatten …
jthrilly Mar 19, 2026
649cccc
refactor: rebuild FamilyTreeNode with labeled node pattern and ego icon
jthrilly Mar 19, 2026
571bce4
feat: show relationship to ego as fallback label on FamilyTreeNode
jthrilly Mar 19, 2026
ef8bd36
refactor: only show label below node when name is unknown
jthrilly Mar 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
340 changes: 265 additions & 75 deletions lib/interviewer/Interfaces/FamilyTreeCensus/FamilyTreeCensus.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,18 @@ function createFamilyTreeInterview(seed: number) {

function FamilyTreeStoryWrapper({
buildFn,
stageMetadata,
}: {
buildFn: () => SyntheticInterview;
stageMetadata?: Record<number, unknown>;
}) {
const interview = useMemo(() => buildFn(), [buildFn]);
const rawPayload = useMemo(
() =>
SuperJSON.stringify(interview.getInterviewPayload({ currentStep: 1 })),
[interview],
SuperJSON.stringify(
interview.getInterviewPayload({ currentStep: 1, stageMetadata }),
),
[interview, stageMetadata],
);

return (
Expand Down Expand Up @@ -142,93 +146,279 @@ const meta: Meta<StoryArgs> = {
export default meta;
type Story = StoryObj<StoryArgs>;

function addFamilyTreeStage(
interview: ReturnType<typeof createFamilyTreeInterview>,
opts: {
initialNodes: number;
showQuickStartModal: boolean;
scaffoldingText: string;
diseaseStepCount: 0 | 1 | 2;
},
) {
const {
si,
nodeType,
nameVar,
ageVar,
sexVar,
edgeType,
relationshipVar,
egoSexVar,
diseaseVar,
isEgoVar,
relationshipToEgoVar,
} = interview;

const disease2Var = nodeType.addVariable({
name: 'Has Diabetes',
type: 'boolean',
component: 'Boolean',
});

si.addInformationStage({
title: 'Welcome',
text: 'Before the main stage.',
});

const stage = si.addStage('FamilyTreeCensus', {
label: 'Family Tree',
subject: { entity: 'node', type: nodeType.id },
initialNodes: opts.initialNodes,
edgeType: { entity: 'edge', type: edgeType.id },
relationshipTypeVariable: relationshipVar.id,
nodeSexVariable: sexVar.id,
egoSexVariable: egoSexVar.id,
relationshipToEgoVariable: relationshipToEgoVar.id,
nodeIsEgoVariable: isEgoVar.id,
scaffoldingStep: {
text: opts.scaffoldingText,
showQuickStartModal: opts.showQuickStartModal,
},
nameGenerationStep: {
text: 'Please provide information for each family member.',
form: {
title: 'Family Member Information',
fields: [
{ variable: nameVar.id, prompt: 'Name', component: 'Text' },
{ variable: ageVar.id, prompt: 'Age', component: 'Number' },
],
},
},
});

if (opts.diseaseStepCount >= 1) {
stage.addDiseaseNominationStep({
text: 'Which family members have the disease?',
variable: diseaseVar.id,
});
}
if (opts.diseaseStepCount >= 2) {
stage.addDiseaseNominationStep({
text: 'Which family members have diabetes?',
variable: disease2Var.id,
});
}

si.addInformationStage({
title: 'Complete',
text: 'After the main stage.',
});

return { stage, nameVar, sexVar, relationshipVar, isEgoVar };
}

// --- Stories ---

/**
* Single configurable story for FamilyTreeCensus interface.
*
* Use the Storybook controls to configure:
* - showQuickStartModal: Show the quick start dialog
* - diseaseStepCount: Number of disease nomination steps (0, 1, or 2)
* - scaffoldingText: Text displayed in the scaffolding step
*/
export const Default: Story = {
render: ({ showQuickStartModal, diseaseStepCount, scaffoldingText }) => {
const buildFn = () => {
const {
si,
nodeType,
nameVar,
ageVar,
sexVar,
edgeType,
relationshipVar,
egoSexVar,
diseaseVar,
isEgoVar,
relationshipToEgoVar,
} = createFamilyTreeInterview(1);

// Add additional disease variable for multiple disease steps
const disease2Var = nodeType.addVariable({
name: 'Has Diabetes',
type: 'boolean',
component: 'Boolean',
const interview = createFamilyTreeInterview(1);
addFamilyTreeStage(interview, {
initialNodes: 0,
showQuickStartModal,
scaffoldingText,
diseaseStepCount,
});
return interview.si;
};

si.addInformationStage({
title: 'Welcome',
text: 'Before the main stage.',
});
return <FamilyTreeStoryWrapper buildFn={buildFn} />;
},
};

const stage = si.addStage('FamilyTreeCensus', {
label: 'Family Tree',
subject: { entity: 'node', type: nodeType.id },
/**
* Pre-populated traditional family: 2 parents, 3 children.
* Uses stageMetadata to provide named nodes — skips the QuickStart wizard.
*/
export const TraditionalFamily: Story = {
args: {
showQuickStartModal: false,
diseaseStepCount: 1,
scaffoldingText: 'Please create your family tree by adding family members.',
},
render: ({ diseaseStepCount, scaffoldingText }) => {
const buildFn = () => {
const interview = createFamilyTreeInterview(10);
addFamilyTreeStage(interview, {
initialNodes: 0,
edgeType: { entity: 'edge', type: edgeType.id },
relationshipTypeVariable: relationshipVar.id,
nodeSexVariable: sexVar.id,
egoSexVariable: egoSexVar.id,
relationshipToEgoVariable: relationshipToEgoVar.id,
nodeIsEgoVariable: isEgoVar.id,
scaffoldingStep: {
text: scaffoldingText,
showQuickStartModal,
},
nameGenerationStep: {
text: 'Please provide information for each family member.',
form: {
title: 'Family Member Information',
fields: [
{ variable: nameVar.id, prompt: 'Name', component: 'Text' },
{ variable: ageVar.id, prompt: 'Age', component: 'Number' },
],
},
},
showQuickStartModal: false,
scaffoldingText,
diseaseStepCount,
});
return interview.si;
};

const stageMetadata: Record<number, unknown> = {
1: {
hasCompletedQuickStart: true,
nodes: [
{ id: 'ego', label: '', sex: 'male', isEgo: true },
{ id: 'dad', label: 'Robert', sex: 'male', isEgo: false },
{ id: 'mom', label: 'Susan', sex: 'female', isEgo: false },
{ id: 'child1', label: 'James', sex: 'male', isEgo: false },
{ id: 'child2', label: 'Emily', sex: 'female', isEgo: false },
{ id: 'child3', label: 'Tom', sex: 'male', isEgo: false },
],
edges: [
{
id: 'e1',
source: 'dad',
target: 'ego',
type: 'parent',
edgeType: 'parent',
},
{
id: 'e2',
source: 'mom',
target: 'ego',
type: 'parent',
edgeType: 'parent',
},
{
id: 'e3',
source: 'dad',
target: 'child1',
type: 'parent',
edgeType: 'parent',
},
{
id: 'e4',
source: 'mom',
target: 'child1',
type: 'parent',
edgeType: 'parent',
},
{
id: 'e5',
source: 'dad',
target: 'child2',
type: 'parent',
edgeType: 'parent',
},
{
id: 'e6',
source: 'mom',
target: 'child2',
type: 'parent',
edgeType: 'parent',
},
{
id: 'e7',
source: 'dad',
target: 'child3',
type: 'parent',
edgeType: 'parent',
},
{
id: 'e8',
source: 'mom',
target: 'child3',
type: 'parent',
edgeType: 'parent',
},
{
id: 'e9',
source: 'dad',
target: 'mom',
type: 'partner',
active: true,
},
],
},
};

return (
<FamilyTreeStoryWrapper buildFn={buildFn} stageMetadata={stageMetadata} />
);
},
};

// Add disease nomination steps based on diseaseStepCount
if (diseaseStepCount >= 1) {
stage.addDiseaseNominationStep({
text: 'Which family members have the disease?',
variable: diseaseVar.id,
});
}
if (diseaseStepCount >= 2) {
stage.addDiseaseNominationStep({
text: 'Which family members have diabetes?',
variable: disease2Var.id,
});
}

si.addInformationStage({
title: 'Complete',
text: 'After the main stage.',
/**
* Same-sex parents with a sperm donor.
* Uses stageMetadata to provide named nodes — skips the QuickStart wizard.
*/
export const InclusiveFamily: Story = {
args: {
showQuickStartModal: false,
diseaseStepCount: 0,
scaffoldingText: 'Please create your family tree by adding family members.',
},
render: ({ diseaseStepCount, scaffoldingText }) => {
const buildFn = () => {
const interview = createFamilyTreeInterview(20);
addFamilyTreeStage(interview, {
initialNodes: 0,
showQuickStartModal: false,
scaffoldingText,
diseaseStepCount,
});
return interview.si;
};

return si;
const stageMetadata: Record<number, unknown> = {
1: {
hasCompletedQuickStart: true,
nodes: [
{ id: 'ego', label: '', sex: 'female', isEgo: true },
{ id: 'parentA', label: 'Sarah', sex: 'female', isEgo: false },
{ id: 'parentB', label: 'Lisa', sex: 'female', isEgo: false },
{ id: 'donor', label: '', sex: 'male', isEgo: false },
],
edges: [
{
id: 'e1',
source: 'parentA',
target: 'ego',
type: 'parent',
edgeType: 'social-parent',
},
{
id: 'e2',
source: 'parentB',
target: 'ego',
type: 'parent',
edgeType: 'social-parent',
},
{
id: 'e3',
source: 'donor',
target: 'ego',
type: 'parent',
edgeType: 'donor',
},
{
id: 'e4',
source: 'parentA',
target: 'parentB',
type: 'partner',
active: true,
},
],
},
};

return <FamilyTreeStoryWrapper buildFn={buildFn} />;
return (
<FamilyTreeStoryWrapper buildFn={buildFn} stageMetadata={stageMetadata} />
);
},
};
12 changes: 7 additions & 5 deletions lib/interviewer/Interfaces/FamilyTreeCensus/FamilyTreeCensus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,13 @@ const FamilyTreeCensus = (props: FamilyTreeCensusProps) => {
return (
<>
<div className="interface">
<Prompts
prompts={allPrompts}
currentPromptId={allPrompts[currentStepIndex]?.id}
className="shrink-0"
/>
{!showQuickStart && (
<Prompts
prompts={allPrompts}
currentPromptId={allPrompts[currentStepIndex]?.id}
className="shrink-0"
/>
)}
<div className="relative flex grow items-center justify-center">
{showQuickStart ? (
<QuickStartForm
Expand Down
Loading
Loading