w-funnel/src/lib/funnel/mappers.tsx
dev.daminik00 0ceb254f4e hh
2025-09-29 06:10:56 +02:00

231 lines
7.0 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;
}
// Проверяем поле show - если false, не показываем
if ('show' in variant && variant.show === false) {
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 shouldShowProgress(header?: HeaderDefinition) {
return header?.showProgress !== false;
}
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);
const showProgress = shouldShowProgress(screen.header);
return {
headerProps: showHeader ? {
progressProps: showProgress ? (
screenProgress ? buildHeaderProgress({
current: screenProgress.current,
total: screenProgress.total,
label: `${screenProgress.current} of ${screenProgress.total}`
}) : buildHeaderProgress(screen.header?.progress)
) : undefined,
onBack: showBackButton ? onBack : undefined,
showBackButton,
} : undefined,
title: screen.title ? buildTypographyProps(screen.title, {
as: "h2",
defaults: titleDefaults,
}) : undefined,
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
);
}