w-funnel/src/lib/funnel/mappers.tsx
dev.daminik00 e98b1bfc05 fix
2025-09-28 15:59:45 +02:00

222 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { TypographyProps } from "@/components/ui/Typography/Typography";
import type { MainButtonProps } from "@/components/ui/MainButton/MainButton";
import { hasTextMarkup } from "@/lib/text-markup";
import type {
HeaderDefinition,
HeaderProgressDefinition,
ListOptionDefinition,
SelectionType,
TypographyVariant,
BottomActionButtonDefinition,
ScreenDefinition,
} from "./types";
import type { LayoutQuestionProps } from "@/components/layout/LayoutQuestion/LayoutQuestion";
import type { ActionButtonProps } from "@/components/ui/ActionButton/ActionButton";
import type { BottomActionButtonProps } from "@/components/widgets/BottomActionButton/BottomActionButton";
type TypographyAs = "span" | "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "div";
interface TypographyDefaults {
font?: TypographyVariant["font"];
weight?: TypographyVariant["weight"];
size?: TypographyVariant["size"];
align?: TypographyVariant["align"];
color?: TypographyVariant["color"];
}
interface BuildTypographyOptions<T extends TypographyAs> {
as: T;
defaults?: TypographyDefaults;
}
export function buildTypographyProps<T extends TypographyAs>(
variant: TypographyVariant | undefined,
options: BuildTypographyOptions<T>
): TypographyProps<T> | undefined {
if (!variant) {
return undefined;
}
const { as, defaults } = options;
return {
as,
children: variant.text,
font: variant.font ?? defaults?.font,
weight: variant.weight ?? defaults?.weight,
size: variant.size ?? defaults?.size,
align: variant.align ?? defaults?.align,
color: variant.color ?? defaults?.color,
className: variant.className,
enableMarkup: hasTextMarkup(variant.text || ''), // 🎨 АВТОМАТИЧЕСКИ включаем разметку если обнаружена
} as TypographyProps<T>;
}
export function buildHeaderProgress(progress?: HeaderProgressDefinition) {
if (!progress) {
return undefined;
}
const { current, total, value, label, className } = progress;
const computedValue =
value ?? (current !== undefined && total ? (current / total) * 100 : undefined);
return {
value: computedValue,
label,
className,
};
}
export function buildAutoHeaderProgress(
currentScreenId: string,
totalScreens: number,
currentPosition: number,
explicitProgress?: HeaderProgressDefinition
) {
// If explicit progress is provided, use it
if (explicitProgress) {
return buildHeaderProgress(explicitProgress);
}
// Otherwise, auto-calculate
const autoProgress: HeaderProgressDefinition = {
current: currentPosition,
total: totalScreens,
label: `${currentPosition} of ${totalScreens}`,
};
return buildHeaderProgress(autoProgress);
}
export function mapListOptionsToButtons(
options: ListOptionDefinition[],
selectionType: SelectionType
): MainButtonProps[] {
return options.map((option) => ({
id: option.id,
children: option.label,
emoji: option.emoji,
isCheckbox: selectionType === "multi",
disabled: option.disabled,
}));
}
export function shouldShowBackButton(header?: HeaderDefinition, canGoBack?: boolean) {
if (header?.showBackButton === false) {
return false;
}
return Boolean(canGoBack);
}
export function shouldShowHeader(header?: HeaderDefinition) {
return header?.show !== false;
}
interface BuildActionButtonOptions {
defaultText?: string;
disabled?: boolean;
onClick: () => void;
}
export function buildActionButtonProps(
options: BuildActionButtonOptions,
buttonDef?: BottomActionButtonDefinition
): ActionButtonProps {
const { defaultText = "Continue", disabled = false, onClick } = options;
return {
children: buttonDef?.text ?? defaultText,
cornerRadius: buttonDef?.cornerRadius,
disabled: disabled, // disabled управляется только логикой экрана, не админкой
onClick: disabled ? undefined : onClick,
};
}
export function buildBottomActionButtonProps(
options: BuildActionButtonOptions,
buttonDef?: BottomActionButtonDefinition
): BottomActionButtonProps | undefined {
// Если кнопка отключена (show: false) - не показывать ничего
if (buttonDef?.show === false) {
return undefined;
}
// В остальных случаях показать кнопку с градиентом
const actionButtonProps = buildActionButtonProps(options, buttonDef);
return {
actionButtonProps,
showGradientBlur: true, // Градиент всегда включен (как требовалось)
};
}
interface BuildLayoutQuestionOptions {
screen: ScreenDefinition;
titleDefaults?: TypographyDefaults;
subtitleDefaults?: TypographyDefaults;
canGoBack: boolean;
onBack: () => void;
screenProgress?: { current: number; total: number };
}
export function buildLayoutQuestionProps(
options: BuildLayoutQuestionOptions
): Omit<LayoutQuestionProps, "children"> {
const {
screen,
titleDefaults = { font: "manrope", weight: "bold", align: "left" },
subtitleDefaults = { font: "inter", weight: "medium", color: "muted", align: "left" },
canGoBack,
onBack,
screenProgress
} = options;
const showBackButton = shouldShowBackButton(screen.header, canGoBack);
const showHeader = shouldShowHeader(screen.header);
return {
headerProps: showHeader ? {
progressProps: screenProgress ? buildHeaderProgress({
current: screenProgress.current,
total: screenProgress.total,
label: `${screenProgress.current} of ${screenProgress.total}`
}) : buildHeaderProgress(screen.header?.progress),
onBack: showBackButton ? onBack : undefined,
showBackButton,
} : undefined,
title: buildTypographyProps(screen.title, {
as: "h2",
defaults: titleDefaults,
}) ?? {
as: "h2",
children: screen.title.text,
},
subtitle: 'subtitle' in screen ? buildTypographyProps(screen.subtitle, {
as: "p",
defaults: subtitleDefaults,
}) : undefined,
};
}
// Отдельная функция для получения bottomActionButtonProps
export function buildTemplateBottomActionButtonProps(options: {
screen: ScreenDefinition;
actionButtonOptions: BuildActionButtonOptions;
}) {
const { screen, actionButtonOptions } = options;
// Наличие actionButtonOptions — явный сигнал показать кнопку.
// Принудительно включаем кнопку независимо от screen.bottomActionButton.show
return buildBottomActionButtonProps(
actionButtonOptions,
'bottomActionButton' in screen
? (screen.bottomActionButton?.show === false
? { ...screen.bottomActionButton, show: true }
: screen.bottomActionButton)
: undefined
);
}