w-funnel/src/components/funnel/templates/ListTemplate/ListTemplate.tsx
gofnnp 1b13a6b0ab edits
add payment buttons loader state
fix click active answer
fix load state
2025-10-08 23:19:36 +04:00

143 lines
4.3 KiB
TypeScript

"use client";
import { useMemo } from "react";
import { RadioAnswersList } from "@/components/widgets/RadioAnswersList/RadioAnswersList";
import { SelectAnswersList } from "@/components/widgets/SelectAnswersList/SelectAnswersList";
import type { ActionButtonProps } from "@/components/ui/ActionButton/ActionButton";
import type { MainButtonProps } from "@/components/ui/MainButton/MainButton";
import type { RadioAnswersListProps } from "@/components/widgets/RadioAnswersList/RadioAnswersList";
import type { SelectAnswersListProps } from "@/components/widgets/SelectAnswersList/SelectAnswersList";
import { mapListOptionsToButtons } from "@/lib/funnel/mappers";
import type { ListScreenDefinition } from "@/lib/funnel/types";
import { TemplateLayout } from "../layouts/TemplateLayout";
import { createTemplateLayoutProps } from "@/lib/funnel/templateHelpers";
export interface ListTemplateProps {
screen: ListScreenDefinition;
selectedOptionIds: string[];
onSelectionChange: (
selectedIds: string[],
skipCheckChanges?: boolean
) => void;
actionButtonProps?: ActionButtonProps;
canGoBack: boolean;
onBack: () => void;
screenProgress?: { current: number; total: number };
}
function stringId(value: MainButtonProps["id"]): string | null {
if (value === undefined || value === null) {
return null;
}
return String(value);
}
export function ListTemplate({
screen,
selectedOptionIds,
onSelectionChange,
actionButtonProps,
canGoBack,
onBack,
screenProgress,
}: ListTemplateProps) {
const buttons = useMemo(
() =>
mapListOptionsToButtons(screen.list.options, screen.list.selectionType),
[screen.list.options, screen.list.selectionType]
);
const selectionSet = useMemo(
() => new Set(selectedOptionIds.map((id) => String(id))),
[selectedOptionIds]
);
const contentType: "radio-answers-list" | "select-answers-list" =
screen.list.selectionType === "multi"
? "select-answers-list"
: "radio-answers-list";
const activeAnswer: MainButtonProps | null =
contentType === "radio-answers-list"
? buttons.find((button) => selectionSet.has(String(button.id))) ?? null
: null;
const activeAnswers: MainButtonProps[] | null =
contentType === "select-answers-list"
? buttons.filter((button) => selectionSet.has(String(button.id)))
: null;
const handleRadioChange: RadioAnswersListProps["onChangeSelectedAnswer"] = (
answer
) => {
const id = stringId(answer?.id);
onSelectionChange(id ? [id] : []);
};
const handleRadioAnswerClick: RadioAnswersListProps["onAnswerClick"] = (
answer
) => {
const id = stringId(answer?.id);
onSelectionChange(id ? [id] : [], true);
};
const handleSelectChange: SelectAnswersListProps["onChangeSelectedAnswers"] =
(answers) => {
const ids = answers
?.map((answer) => stringId(answer.id))
.filter((value): value is string => Boolean(value));
onSelectionChange(ids ?? []);
};
const radioContent: RadioAnswersListProps = {
answers: buttons,
activeAnswer,
onChangeSelectedAnswer: handleRadioChange,
onAnswerClick: handleRadioAnswerClick,
};
const selectContent: SelectAnswersListProps = {
answers: buttons,
activeAnswers,
onChangeSelectedAnswers: handleSelectChange,
};
const actionButtonOptions = actionButtonProps
? {
defaultText: (actionButtonProps.children as string) || "Next",
// Кнопка неактивна если: 1) disabled из props ИЛИ 2) ничего не выбрано
disabled: actionButtonProps.disabled || selectedOptionIds.length === 0,
onClick: () => {
if (actionButtonProps.onClick) {
actionButtonProps.onClick(
{} as React.MouseEvent<HTMLButtonElement>
);
}
},
}
: undefined;
const layoutProps = createTemplateLayoutProps(
screen,
{ canGoBack, onBack },
screenProgress,
{
preset: "left",
actionButton: actionButtonOptions,
}
);
return (
<TemplateLayout {...layoutProps}>
<div className="w-full mt-[22px]">
{contentType === "radio-answers-list" ? (
<RadioAnswersList {...radioContent} />
) : (
<SelectAnswersList {...selectContent} />
)}
</div>
</TemplateLayout>
);
}