story
This commit is contained in:
parent
e98b1bfc05
commit
b28d22967f
@ -0,0 +1,109 @@
|
||||
import { Meta, StoryObj } from "@storybook/nextjs-vite";
|
||||
import { CouponTemplate } from "./CouponTemplate";
|
||||
import { fn } from "storybook/test";
|
||||
import { buildCouponDefaults } from "@/lib/admin/builder/state/defaults/coupon";
|
||||
import type { CouponScreenDefinition } from "@/lib/funnel/types";
|
||||
|
||||
// Получаем дефолтные значения из builder
|
||||
const defaultScreen = buildCouponDefaults("coupon-screen-story") as CouponScreenDefinition;
|
||||
|
||||
/** CouponTemplate - экраны с купонами и промокодами */
|
||||
const meta: Meta<typeof CouponTemplate> = {
|
||||
title: "Funnel Templates/CouponTemplate",
|
||||
component: CouponTemplate,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
args: {
|
||||
screen: defaultScreen,
|
||||
onContinue: fn(),
|
||||
canGoBack: true,
|
||||
onBack: fn(),
|
||||
screenProgress: { current: 8, total: 10 },
|
||||
defaultTexts: {
|
||||
nextButton: "Next",
|
||||
continueButton: "Continue"
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
screen: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
screenProgress: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
onContinue: { action: "continue" },
|
||||
onBack: { action: "back" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/** Дефолтный купон экран */
|
||||
export const Default: Story = {};
|
||||
|
||||
/** Купон с показом прогресса */
|
||||
export const WithProgress: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
header: {
|
||||
show: true,
|
||||
showBackButton: true,
|
||||
showProgress: true, // Показываем прогресс
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран без header */
|
||||
export const WithoutHeader: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
header: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Купон с другой скидкой */
|
||||
export const CustomDiscount: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
coupon: {
|
||||
...defaultScreen.coupon,
|
||||
offer: {
|
||||
title: {
|
||||
text: "50% OFF",
|
||||
font: "manrope",
|
||||
weight: "bold",
|
||||
align: "center",
|
||||
size: "3xl",
|
||||
color: "primary",
|
||||
},
|
||||
description: {
|
||||
text: "Скидка на первую покупку",
|
||||
font: "inter",
|
||||
weight: "medium",
|
||||
color: "muted",
|
||||
align: "center",
|
||||
size: "md",
|
||||
},
|
||||
},
|
||||
promoCode: {
|
||||
text: "FIRST50",
|
||||
font: "geistMono",
|
||||
weight: "bold",
|
||||
align: "center",
|
||||
size: "lg",
|
||||
color: "accent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
1
src/components/funnel/templates/CouponTemplate/index.ts
Normal file
1
src/components/funnel/templates/CouponTemplate/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { CouponTemplate } from "./CouponTemplate";
|
||||
@ -0,0 +1,133 @@
|
||||
import { Meta, StoryObj } from "@storybook/nextjs-vite";
|
||||
import { DateTemplate } from "./DateTemplate";
|
||||
import { fn } from "storybook/test";
|
||||
import { buildDateDefaults } from "@/lib/admin/builder/state/defaults/date";
|
||||
import type { DateScreenDefinition } from "@/lib/funnel/types";
|
||||
|
||||
// Получаем дефолтные значения из builder
|
||||
const defaultScreen = buildDateDefaults("date-screen-story") as DateScreenDefinition;
|
||||
|
||||
/** DateTemplate - экраны с выбором даты рождения */
|
||||
const meta: Meta<typeof DateTemplate> = {
|
||||
title: "Funnel Templates/DateTemplate",
|
||||
component: DateTemplate,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
args: {
|
||||
screen: defaultScreen,
|
||||
selectedDate: {},
|
||||
onDateChange: fn(),
|
||||
onContinue: fn(),
|
||||
canGoBack: true,
|
||||
onBack: fn(),
|
||||
screenProgress: { current: 4, total: 10 },
|
||||
defaultTexts: {
|
||||
nextButton: "Next",
|
||||
continueButton: "Continue"
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
screen: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
selectedDate: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
screenProgress: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
onDateChange: { action: "date changed" },
|
||||
onContinue: { action: "continue" },
|
||||
onBack: { action: "back" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/** Дефолтный экран выбора даты */
|
||||
export const Default: Story = {};
|
||||
|
||||
/** Экран с предзаполненной датой */
|
||||
export const WithPrefilledDate: Story = {
|
||||
args: {
|
||||
selectedDate: {
|
||||
month: "4",
|
||||
day: "8",
|
||||
year: "1987",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран без показа выбранной даты */
|
||||
export const WithoutSelectedDate: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
dateInput: {
|
||||
...defaultScreen.dateInput,
|
||||
showSelectedDate: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран с кастомными лейблами */
|
||||
export const CustomLabels: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
dateInput: {
|
||||
...defaultScreen.dateInput,
|
||||
monthLabel: "Month",
|
||||
dayLabel: "Day",
|
||||
yearLabel: "Year",
|
||||
monthPlaceholder: "MM",
|
||||
dayPlaceholder: "DD",
|
||||
yearPlaceholder: "YYYY",
|
||||
selectedDateLabel: "Selected date:",
|
||||
selectedDateFormat: "MMMM d, yyyy",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран без информационного сообщения */
|
||||
export const WithoutInfoMessage: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
infoMessage: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран с включенным зодиаком */
|
||||
export const WithZodiac: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
dateInput: {
|
||||
...defaultScreen.dateInput,
|
||||
zodiac: {
|
||||
enabled: true,
|
||||
storageKey: "zodiac_sign",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран без header */
|
||||
export const WithoutHeader: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
header: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -9,6 +9,21 @@ import type { DateScreenDefinition } from "@/lib/funnel/types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TemplateLayout } from "../layouts/TemplateLayout";
|
||||
|
||||
// Утилита для форматирования даты на основе паттерна
|
||||
function formatDateByPattern(date: Date, pattern: string): string {
|
||||
const monthNames = [
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
|
||||
return pattern
|
||||
.replace("MMMM", monthNames[date.getMonth()])
|
||||
.replace("MMM", monthNames[date.getMonth()].substring(0, 3))
|
||||
.replace("yyyy", date.getFullYear().toString())
|
||||
.replace("dd", date.getDate().toString().padStart(2, '0'))
|
||||
.replace("d", date.getDate().toString());
|
||||
}
|
||||
|
||||
interface DateTemplateProps {
|
||||
screen: DateScreenDefinition;
|
||||
selectedDate: { month?: string; day?: string; year?: string };
|
||||
@ -67,6 +82,38 @@ export function DateTemplate({
|
||||
|
||||
const isFormValid = Boolean(isoDate);
|
||||
|
||||
// Форматированная дата для отображения
|
||||
const formattedDate = useMemo(() => {
|
||||
if (!isoDate) return null;
|
||||
|
||||
const date = new Date(isoDate);
|
||||
const pattern = screen.dateInput?.selectedDateFormat || "MMMM d, yyyy";
|
||||
return formatDateByPattern(date, pattern);
|
||||
}, [isoDate, screen.dateInput?.selectedDateFormat]);
|
||||
|
||||
// Компонент отображения выбранной даты над кнопкой
|
||||
const selectedDateDisplay = formattedDate && screen.dateInput?.showSelectedDate !== false ? (
|
||||
<div className="text-center space-y-1 mb-4">
|
||||
<Typography
|
||||
as="p"
|
||||
size="sm"
|
||||
color="muted"
|
||||
className="font-medium"
|
||||
>
|
||||
{screen.dateInput?.selectedDateLabel || "Выбранная дата:"}
|
||||
</Typography>
|
||||
<Typography
|
||||
as="p"
|
||||
size="xl"
|
||||
weight="bold"
|
||||
color="default"
|
||||
className="font-semibold"
|
||||
>
|
||||
{formattedDate}
|
||||
</Typography>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<TemplateLayout
|
||||
screen={screen}
|
||||
@ -80,6 +127,7 @@ export function DateTemplate({
|
||||
disabled: !isFormValid,
|
||||
onClick: onContinue,
|
||||
}}
|
||||
childrenUnderButton={selectedDateDisplay}
|
||||
>
|
||||
<div className="w-full mt-[22px] space-y-6">
|
||||
<DateInput
|
||||
1
src/components/funnel/templates/DateTemplate/index.ts
Normal file
1
src/components/funnel/templates/DateTemplate/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { DateTemplate } from "./DateTemplate";
|
||||
@ -0,0 +1,93 @@
|
||||
import { Meta, StoryObj } from "@storybook/nextjs-vite";
|
||||
import { EmailTemplate } from "./EmailTemplate";
|
||||
import { fn } from "storybook/test";
|
||||
import { buildEmailDefaults } from "@/lib/admin/builder/state/defaults/email";
|
||||
import type { EmailScreenDefinition } from "@/lib/funnel/types";
|
||||
|
||||
// Получаем дефолтные значения из builder
|
||||
const defaultScreen = buildEmailDefaults("email-screen-story") as EmailScreenDefinition;
|
||||
|
||||
/** EmailTemplate - экраны сбора email адреса */
|
||||
const meta: Meta<typeof EmailTemplate> = {
|
||||
title: "Funnel Templates/EmailTemplate",
|
||||
component: EmailTemplate,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
args: {
|
||||
screen: defaultScreen,
|
||||
onContinue: fn(),
|
||||
canGoBack: true,
|
||||
onBack: fn(),
|
||||
screenProgress: { current: 9, total: 10 },
|
||||
defaultTexts: {
|
||||
nextButton: "Next",
|
||||
continueButton: "Continue"
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
screen: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
screenProgress: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
onContinue: { action: "continue" },
|
||||
onBack: { action: "back" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/** Дефолтный email экран */
|
||||
export const Default: Story = {};
|
||||
|
||||
/** Экран без изображения */
|
||||
export const WithoutImage: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
image: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран с consent */
|
||||
export const WithConsent: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
bottomActionButton: {
|
||||
...defaultScreen.bottomActionButton,
|
||||
showPrivacyTermsConsent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран без header */
|
||||
export const WithoutHeader: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
header: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран с кастомными лейблами */
|
||||
export const CustomLabels: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
emailInput: {
|
||||
label: "Your Email Address",
|
||||
placeholder: "Enter your email here...",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
1
src/components/funnel/templates/EmailTemplate/index.ts
Normal file
1
src/components/funnel/templates/EmailTemplate/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { EmailTemplate } from "./EmailTemplate";
|
||||
@ -0,0 +1,152 @@
|
||||
import { Meta, StoryObj } from "@storybook/nextjs-vite";
|
||||
import { FormTemplate } from "./FormTemplate";
|
||||
import { fn } from "storybook/test";
|
||||
import { buildFormDefaults } from "@/lib/admin/builder/state/defaults/form";
|
||||
import type { FormScreenDefinition } from "@/lib/funnel/types";
|
||||
|
||||
// Получаем дефолтные значения из builder
|
||||
const defaultScreen = buildFormDefaults("form-screen-story") as FormScreenDefinition;
|
||||
|
||||
// Создаем более богатую форму для демонстрации
|
||||
const richFormScreen: FormScreenDefinition = {
|
||||
...defaultScreen,
|
||||
title: {
|
||||
...defaultScreen.title,
|
||||
text: "Расскажите о себе",
|
||||
},
|
||||
subtitle: {
|
||||
...defaultScreen.subtitle,
|
||||
text: "Заполните форму для персонализированного анализа",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
id: "name",
|
||||
label: "Полное имя",
|
||||
placeholder: "Введите ваше имя",
|
||||
type: "text",
|
||||
required: true,
|
||||
maxLength: 50,
|
||||
},
|
||||
{
|
||||
id: "email",
|
||||
label: "Email адрес",
|
||||
placeholder: "example@email.com",
|
||||
type: "email",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "phone",
|
||||
label: "Телефон",
|
||||
placeholder: "+7 (999) 123-45-67",
|
||||
type: "tel",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: "website",
|
||||
label: "Веб-сайт",
|
||||
placeholder: "https://example.com",
|
||||
type: "url",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/** FormTemplate - экраны с формами ввода */
|
||||
const meta: Meta<typeof FormTemplate> = {
|
||||
title: "Funnel Templates/FormTemplate",
|
||||
component: FormTemplate,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
args: {
|
||||
screen: richFormScreen,
|
||||
formData: {},
|
||||
onFormDataChange: fn(),
|
||||
onContinue: fn(),
|
||||
canGoBack: true,
|
||||
onBack: fn(),
|
||||
screenProgress: { current: 6, total: 10 },
|
||||
defaultTexts: {
|
||||
nextButton: "Next",
|
||||
continueButton: "Continue"
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
screen: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
formData: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
screenProgress: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
onFormDataChange: { action: "form data changed" },
|
||||
onContinue: { action: "continue" },
|
||||
onBack: { action: "back" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/** Дефолтная форма */
|
||||
export const Default: Story = {};
|
||||
|
||||
/** Простая форма с одним полем */
|
||||
export const SimpleForm: Story = {
|
||||
args: {
|
||||
screen: defaultScreen, // Используем базовые дефолты
|
||||
},
|
||||
};
|
||||
|
||||
/** Форма только с обязательными полями */
|
||||
export const RequiredFieldsOnly: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...richFormScreen,
|
||||
fields: richFormScreen.fields.filter(field => field.required),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Форма с кастомными сообщениями валидации */
|
||||
export const CustomValidation: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...richFormScreen,
|
||||
validationMessages: {
|
||||
required: "Пожалуйста, заполните это поле",
|
||||
maxLength: "Слишком длинное значение",
|
||||
invalidFormat: "Неправильный формат данных",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Форма без header */
|
||||
export const WithoutHeader: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...richFormScreen,
|
||||
header: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Форма без subtitle */
|
||||
export const WithoutSubtitle: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...richFormScreen,
|
||||
subtitle: {
|
||||
...richFormScreen.subtitle,
|
||||
show: false,
|
||||
text: richFormScreen.subtitle?.text || "",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
1
src/components/funnel/templates/FormTemplate/index.ts
Normal file
1
src/components/funnel/templates/FormTemplate/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { FormTemplate } from "./FormTemplate";
|
||||
@ -0,0 +1,95 @@
|
||||
import { Meta, StoryObj } from "@storybook/nextjs-vite";
|
||||
import { InfoTemplate } from "./InfoTemplate";
|
||||
import { fn } from "storybook/test";
|
||||
import { buildInfoDefaults } from "@/lib/admin/builder/state/defaults/info";
|
||||
import type { InfoScreenDefinition } from "@/lib/funnel/types";
|
||||
|
||||
// Получаем дефолтные значения из builder
|
||||
const defaultScreen = buildInfoDefaults("info-screen-story") as InfoScreenDefinition;
|
||||
|
||||
/** InfoTemplate - информационные экраны с иконкой и описанием */
|
||||
const meta: Meta<typeof InfoTemplate> = {
|
||||
title: "Funnel Templates/InfoTemplate",
|
||||
component: InfoTemplate,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
args: {
|
||||
screen: defaultScreen,
|
||||
onContinue: fn(),
|
||||
canGoBack: true,
|
||||
onBack: fn(),
|
||||
screenProgress: { current: 3, total: 10 },
|
||||
defaultTexts: {
|
||||
nextButton: "Next",
|
||||
continueButton: "Continue"
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
screen: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
screenProgress: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
onContinue: { action: "continue" },
|
||||
onBack: { action: "back" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/** Дефолтный информационный экран */
|
||||
export const Default: Story = {};
|
||||
|
||||
/** Экран без иконки */
|
||||
export const WithoutIcon: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
icon: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран с кастомной иконкой */
|
||||
export const WithCustomIcon: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
icon: {
|
||||
type: "emoji",
|
||||
value: "🎯",
|
||||
size: "lg",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран без header */
|
||||
export const WithoutHeader: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
header: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран с скрытым прогрессом */
|
||||
export const WithoutProgress: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
header: {
|
||||
show: true,
|
||||
showBackButton: true,
|
||||
showProgress: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
1
src/components/funnel/templates/InfoTemplate/index.ts
Normal file
1
src/components/funnel/templates/InfoTemplate/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { InfoTemplate } from "./InfoTemplate";
|
||||
@ -0,0 +1,144 @@
|
||||
import { Meta, StoryObj } from "@storybook/nextjs-vite";
|
||||
import { ListTemplate } from "./ListTemplate";
|
||||
import { fn } from "storybook/test";
|
||||
import { buildListDefaults } from "@/lib/admin/builder/state/defaults/list";
|
||||
import type { ListScreenDefinition } from "@/lib/funnel/types";
|
||||
|
||||
// Получаем дефолтные значения из builder
|
||||
const defaultScreen = buildListDefaults("list-screen-story") as ListScreenDefinition;
|
||||
|
||||
// Более богатый список опций для демонстрации
|
||||
const richOptionsScreen: ListScreenDefinition = {
|
||||
...defaultScreen,
|
||||
title: {
|
||||
...defaultScreen.title,
|
||||
text: "Выберите ваш знак зодиака",
|
||||
},
|
||||
list: {
|
||||
...defaultScreen.list,
|
||||
options: [
|
||||
{ id: "aries", label: "Овен", emoji: "♈" },
|
||||
{ id: "taurus", label: "Телец", emoji: "♉" },
|
||||
{ id: "gemini", label: "Близнецы", emoji: "♊" },
|
||||
{ id: "cancer", label: "Рак", emoji: "♋" },
|
||||
{ id: "leo", label: "Лев", emoji: "♌" },
|
||||
{ id: "virgo", label: "Дева", emoji: "♍" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
/** ListTemplate - экраны с выбором опций (single/multi selection) */
|
||||
const meta: Meta<typeof ListTemplate> = {
|
||||
title: "Funnel Templates/ListTemplate",
|
||||
component: ListTemplate,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
args: {
|
||||
screen: richOptionsScreen,
|
||||
selectedOptionIds: [],
|
||||
onSelectionChange: fn(),
|
||||
actionButtonProps: {
|
||||
children: "Continue",
|
||||
onClick: fn(),
|
||||
},
|
||||
canGoBack: true,
|
||||
onBack: fn(),
|
||||
screenProgress: { current: 5, total: 10 },
|
||||
},
|
||||
argTypes: {
|
||||
screen: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
selectedOptionIds: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
actionButtonProps: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
screenProgress: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
onSelectionChange: { action: "selection changed" },
|
||||
onBack: { action: "back" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/** Дефолтный single selection список (кнопка disabled пока не выбрано) */
|
||||
export const SingleSelection: Story = {};
|
||||
|
||||
/** Multi selection список (кнопка disabled пока не выбрано) */
|
||||
export const MultiSelection: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...richOptionsScreen,
|
||||
title: {
|
||||
...richOptionsScreen.title,
|
||||
text: "Выберите несколько вариантов",
|
||||
},
|
||||
list: {
|
||||
...richOptionsScreen.list,
|
||||
selectionType: "multi",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Single selection с предвыбранным значением (кнопка активна) */
|
||||
export const WithPreselection: Story = {
|
||||
args: {
|
||||
selectedOptionIds: ["leo"],
|
||||
},
|
||||
};
|
||||
|
||||
/** Multi selection с несколькими выбранными (кнопка активна) */
|
||||
export const MultiWithPreselection: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...richOptionsScreen,
|
||||
list: {
|
||||
...richOptionsScreen.list,
|
||||
selectionType: "multi",
|
||||
},
|
||||
},
|
||||
selectedOptionIds: ["aries", "leo", "virgo"],
|
||||
},
|
||||
};
|
||||
|
||||
/** Single selection с автопереходом (без кнопки) */
|
||||
export const SingleAutoAdvance: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...richOptionsScreen,
|
||||
bottomActionButton: {
|
||||
show: false, // Автопереход при выборе
|
||||
},
|
||||
},
|
||||
actionButtonProps: undefined, // Нет кнопки
|
||||
},
|
||||
};
|
||||
|
||||
/** Список без header */
|
||||
export const WithoutHeader: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...richOptionsScreen,
|
||||
header: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Простой список без эмодзи */
|
||||
export const SimpleList: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen, // Используем базовые дефолты без эмодзи
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -93,7 +93,8 @@ export function ListTemplate({
|
||||
|
||||
const actionButtonOptions = actionButtonProps ? {
|
||||
defaultText: actionButtonProps.children as string || "Next",
|
||||
disabled: actionButtonProps.disabled || false,
|
||||
// Кнопка неактивна если: 1) disabled из props ИЛИ 2) ничего не выбрано
|
||||
disabled: actionButtonProps.disabled || selectedOptionIds.length === 0,
|
||||
onClick: () => {
|
||||
if (actionButtonProps.onClick) {
|
||||
actionButtonProps.onClick({} as React.MouseEvent<HTMLButtonElement>);
|
||||
1
src/components/funnel/templates/ListTemplate/index.ts
Normal file
1
src/components/funnel/templates/ListTemplate/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { ListTemplate } from "./ListTemplate";
|
||||
@ -0,0 +1,112 @@
|
||||
import { Meta, StoryObj } from "@storybook/nextjs-vite";
|
||||
import { LoadersTemplate } from "./LoadersTemplate";
|
||||
import { fn } from "storybook/test";
|
||||
import { buildLoadersDefaults } from "@/lib/admin/builder/state/defaults/loaders";
|
||||
import type { LoadersScreenDefinition } from "@/lib/funnel/types";
|
||||
|
||||
// Получаем дефолтные значения из builder
|
||||
const defaultScreen = buildLoadersDefaults("loaders-screen-story") as LoadersScreenDefinition;
|
||||
|
||||
/** LoadersTemplate - экраны с анимированными загрузчиками */
|
||||
const meta: Meta<typeof LoadersTemplate> = {
|
||||
title: "Funnel Templates/LoadersTemplate",
|
||||
component: LoadersTemplate,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
args: {
|
||||
screen: defaultScreen,
|
||||
onContinue: fn(),
|
||||
canGoBack: false, // Обычно на лоадерах нет кнопки назад
|
||||
onBack: fn(),
|
||||
screenProgress: undefined, // У лоадеров обычно нет прогресса
|
||||
defaultTexts: {
|
||||
nextButton: "Next",
|
||||
continueButton: "Continue"
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
screen: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
screenProgress: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
onContinue: { action: "continue" },
|
||||
onBack: { action: "back" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/** Дефолтный loaders экран */
|
||||
export const Default: Story = {};
|
||||
|
||||
/** Лоадеры с быстрой анимацией */
|
||||
export const FastAnimation: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
progressbars: {
|
||||
...defaultScreen.progressbars,
|
||||
transitionDuration: 1000, // Быстрее
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Лоадеры с медленной анимацией */
|
||||
export const SlowAnimation: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
progressbars: {
|
||||
...defaultScreen.progressbars,
|
||||
transitionDuration: 5000, // Медленнее
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Лоадеры с кастомными сообщениями */
|
||||
export const CustomMessages: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
progressbars: {
|
||||
...defaultScreen.progressbars,
|
||||
items: [
|
||||
{
|
||||
processingTitle: "Анализируем ваши ответы...",
|
||||
processingSubtitle: "Обработка данных",
|
||||
completedTitle: "Анализ завершен",
|
||||
completedSubtitle: "Готово!",
|
||||
},
|
||||
{
|
||||
processingTitle: "Создаем персональный портрет...",
|
||||
processingSubtitle: "Генерация",
|
||||
completedTitle: "Портрет готов",
|
||||
completedSubtitle: "Завершено",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Лоадеры с header (необычно, но возможно) */
|
||||
export const WithHeader: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
header: {
|
||||
show: true,
|
||||
showBackButton: false,
|
||||
showProgress: true,
|
||||
},
|
||||
},
|
||||
screenProgress: { current: 7, total: 10 },
|
||||
},
|
||||
};
|
||||
1
src/components/funnel/templates/LoadersTemplate/index.ts
Normal file
1
src/components/funnel/templates/LoadersTemplate/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { LoadersTemplate } from "./LoadersTemplate";
|
||||
@ -0,0 +1,114 @@
|
||||
import { Meta, StoryObj } from "@storybook/nextjs-vite";
|
||||
import { SoulmatePortraitTemplate } from "./SoulmatePortraitTemplate";
|
||||
import { fn } from "storybook/test";
|
||||
import { buildSoulmateDefaults } from "@/lib/admin/builder/state/defaults/soulmate";
|
||||
import type { SoulmatePortraitScreenDefinition } from "@/lib/funnel/types";
|
||||
|
||||
// Получаем дефолтные значения из builder
|
||||
const defaultScreen = buildSoulmateDefaults("soulmate-screen-story") as SoulmatePortraitScreenDefinition;
|
||||
|
||||
/** SoulmatePortraitTemplate - результирующие экраны с портретом партнера */
|
||||
const meta: Meta<typeof SoulmatePortraitTemplate> = {
|
||||
title: "Funnel Templates/SoulmatePortraitTemplate",
|
||||
component: SoulmatePortraitTemplate,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
args: {
|
||||
screen: defaultScreen,
|
||||
onContinue: fn(),
|
||||
canGoBack: true,
|
||||
onBack: fn(),
|
||||
screenProgress: { current: 10, total: 10 }, // Обычно финальный экран
|
||||
defaultTexts: {
|
||||
nextButton: "Next",
|
||||
continueButton: "Continue"
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
screen: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
screenProgress: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
onContinue: { action: "continue" },
|
||||
onBack: { action: "back" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/** Дефолтный soulmate portrait экран */
|
||||
export const Default: Story = {};
|
||||
|
||||
/** Экран без описания */
|
||||
export const WithoutDescription: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
description: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран с кастомным описанием */
|
||||
export const CustomDescription: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
description: {
|
||||
text: "На основе ваших ответов мы создали уникальный **портрет вашей второй половинки**. Этот анализ поможет вам лучше понять, кто может стать идеальным партнером.",
|
||||
font: "inter",
|
||||
weight: "regular",
|
||||
align: "center",
|
||||
size: "md",
|
||||
color: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран без header */
|
||||
export const WithoutHeader: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
header: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Экран без subtitle */
|
||||
export const WithoutSubtitle: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
subtitle: {
|
||||
...defaultScreen.subtitle,
|
||||
show: false,
|
||||
text: defaultScreen.subtitle?.text || "",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Финальный экран (без прогресса) */
|
||||
export const FinalScreen: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...defaultScreen,
|
||||
header: {
|
||||
show: true,
|
||||
showBackButton: false, // На финальном экране обычно нет кнопки назад
|
||||
showProgress: false, // И нет прогресса
|
||||
},
|
||||
},
|
||||
screenProgress: undefined,
|
||||
canGoBack: false,
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export { SoulmatePortraitTemplate } from "./SoulmatePortraitTemplate";
|
||||
@ -1,4 +0,0 @@
|
||||
// Content templates - informational and display screens
|
||||
export { InfoTemplate } from "./InfoTemplate";
|
||||
export { LoadersTemplate } from "./LoadersTemplate";
|
||||
export { SoulmatePortraitTemplate } from "./SoulmatePortraitTemplate";
|
||||
@ -1,4 +0,0 @@
|
||||
// Form templates - input and data collection screens
|
||||
export { FormTemplate } from "./FormTemplate";
|
||||
export { DateTemplate } from "./DateTemplate";
|
||||
export { EmailTemplate } from "./EmailTemplate";
|
||||
@ -1,13 +1,12 @@
|
||||
// Funnel templates organized by category
|
||||
// Funnel Templates - каждый в своей папке с stories
|
||||
export { InfoTemplate } from "./InfoTemplate";
|
||||
export { ListTemplate } from "./ListTemplate";
|
||||
export { DateTemplate } from "./DateTemplate";
|
||||
export { FormTemplate } from "./FormTemplate";
|
||||
export { EmailTemplate } from "./EmailTemplate";
|
||||
export { CouponTemplate } from "./CouponTemplate";
|
||||
export { LoadersTemplate } from "./LoadersTemplate";
|
||||
export { SoulmatePortraitTemplate } from "./SoulmatePortraitTemplate";
|
||||
|
||||
// Content templates (informational and display)
|
||||
export * from "./content";
|
||||
|
||||
// Form templates (input and data collection)
|
||||
export * from "./forms";
|
||||
|
||||
// Interactive templates (user choice and engagement)
|
||||
export * from "./interactive";
|
||||
|
||||
// Layout components (base layouts and structural)
|
||||
export * from "./layouts";
|
||||
// Layout Templates
|
||||
export { TemplateLayout } from "./layouts/TemplateLayout";
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
// Interactive templates - user choice and engagement screens
|
||||
export { ListTemplate } from "./ListTemplate";
|
||||
export { CouponTemplate } from "./CouponTemplate";
|
||||
@ -0,0 +1,241 @@
|
||||
import { Meta, StoryObj } from "@storybook/nextjs-vite";
|
||||
import { TemplateLayout } from "./TemplateLayout";
|
||||
import { fn } from "storybook/test";
|
||||
import type { InfoScreenDefinition } from "@/lib/funnel/types";
|
||||
|
||||
// Создаем mock экран для демонстрации TemplateLayout
|
||||
const mockScreen: InfoScreenDefinition = {
|
||||
id: "template-layout-demo",
|
||||
template: "info",
|
||||
header: {
|
||||
show: true,
|
||||
showBackButton: true,
|
||||
showProgress: true,
|
||||
},
|
||||
title: {
|
||||
text: "TemplateLayout Demo",
|
||||
font: "manrope",
|
||||
weight: "bold",
|
||||
align: "center",
|
||||
size: "2xl",
|
||||
color: "default",
|
||||
},
|
||||
description: {
|
||||
text: "Это демонстрация **TemplateLayout** - централизованного layout wrapper для всех funnel templates. Он управляет header, progress bar, кнопкой назад и нижней кнопкой.",
|
||||
font: "inter",
|
||||
weight: "regular",
|
||||
align: "center",
|
||||
size: "md",
|
||||
color: "default",
|
||||
},
|
||||
bottomActionButton: {
|
||||
show: true,
|
||||
text: "Continue",
|
||||
showGradientBlur: true,
|
||||
},
|
||||
navigation: {
|
||||
defaultNextScreenId: undefined,
|
||||
rules: [],
|
||||
},
|
||||
};
|
||||
|
||||
/** TemplateLayout - централизованный wrapper для всех funnel templates */
|
||||
const meta: Meta<typeof TemplateLayout> = {
|
||||
title: "Funnel Templates/TemplateLayout",
|
||||
component: TemplateLayout,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
TemplateLayout - это централизованный layout wrapper, который используется всеми funnel templates.
|
||||
|
||||
**Основные возможности:**
|
||||
- Управление header (показать/скрыть, progress bar, кнопка назад)
|
||||
- Bottom action button с gradient blur
|
||||
- Privacy terms consent
|
||||
- Динамическая высота для фиксированного нижнего контента
|
||||
- Единообразные настройки типографики
|
||||
|
||||
**Архитектурные принципы:**
|
||||
- Устраняет дублирование кода между templates
|
||||
- Централизованная логика UI элементов
|
||||
- Использует только существующие UI компоненты (LayoutQuestion, BottomActionButton)
|
||||
`
|
||||
}
|
||||
}
|
||||
},
|
||||
args: {
|
||||
screen: mockScreen,
|
||||
canGoBack: true,
|
||||
onBack: fn(),
|
||||
screenProgress: { current: 5, total: 10 },
|
||||
titleDefaults: { font: "manrope", weight: "bold", align: "center", size: "2xl", color: "default" },
|
||||
subtitleDefaults: { font: "inter", weight: "medium", color: "default", align: "center", size: "lg" },
|
||||
actionButtonOptions: {
|
||||
defaultText: "Continue",
|
||||
disabled: false,
|
||||
onClick: fn(),
|
||||
},
|
||||
children: (
|
||||
<div className="w-full flex flex-col items-center gap-4 py-8">
|
||||
<div className="w-full max-w-sm text-center">
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Это контент, который передается в TemplateLayout через children prop.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="p-3 bg-muted/20 rounded-lg">
|
||||
<strong>Header:</strong> управляется TemplateLayout
|
||||
</div>
|
||||
<div className="p-3 bg-muted/20 rounded-lg">
|
||||
<strong>Content:</strong> передается через children
|
||||
</div>
|
||||
<div className="p-3 bg-muted/20 rounded-lg">
|
||||
<strong>Button:</strong> управляется TemplateLayout
|
||||
</div>
|
||||
<div className="p-3 bg-muted/20 rounded-lg">
|
||||
<strong>Blur:</strong> управляется TemplateLayout
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
argTypes: {
|
||||
screen: {
|
||||
control: { type: "object" },
|
||||
description: "Screen definition с настройками header и bottomActionButton"
|
||||
},
|
||||
screenProgress: {
|
||||
control: { type: "object" },
|
||||
description: "Прогресс для отображения в header"
|
||||
},
|
||||
actionButtonOptions: {
|
||||
control: { type: "object" },
|
||||
description: "Настройки нижней кнопки (если передается, кнопка показывается)"
|
||||
},
|
||||
children: {
|
||||
control: false,
|
||||
description: "Контент template, который рендерится внутри layout"
|
||||
},
|
||||
onBack: { action: "back" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/** Дефолтный TemplateLayout со всеми элементами */
|
||||
export const Default: Story = {};
|
||||
|
||||
/** Layout без header */
|
||||
export const WithoutHeader: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...mockScreen,
|
||||
header: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Layout без progress bar */
|
||||
export const WithoutProgressBar: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...mockScreen,
|
||||
header: {
|
||||
show: true,
|
||||
showBackButton: true,
|
||||
showProgress: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Layout без кнопки назад */
|
||||
export const WithoutBackButton: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...mockScreen,
|
||||
header: {
|
||||
show: true,
|
||||
showBackButton: false,
|
||||
showProgress: true,
|
||||
},
|
||||
},
|
||||
canGoBack: false,
|
||||
},
|
||||
};
|
||||
|
||||
/** Layout без нижней кнопки */
|
||||
export const WithoutActionButton: Story = {
|
||||
args: {
|
||||
actionButtonOptions: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
/** Layout только с blur gradient (без кнопки) */
|
||||
export const OnlyBlurGradient: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...mockScreen,
|
||||
bottomActionButton: {
|
||||
show: false,
|
||||
showGradientBlur: true,
|
||||
},
|
||||
},
|
||||
actionButtonOptions: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
/** Layout с Privacy Terms Consent */
|
||||
export const WithPrivacyConsent: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...mockScreen,
|
||||
bottomActionButton: {
|
||||
...mockScreen.bottomActionButton,
|
||||
showPrivacyTermsConsent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Layout с disabled кнопкой */
|
||||
export const WithDisabledButton: Story = {
|
||||
args: {
|
||||
actionButtonOptions: {
|
||||
defaultText: "Continue",
|
||||
disabled: true,
|
||||
onClick: fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Минимальный layout (только контент) */
|
||||
export const Minimal: Story = {
|
||||
args: {
|
||||
screen: {
|
||||
...mockScreen,
|
||||
header: {
|
||||
show: false,
|
||||
},
|
||||
bottomActionButton: {
|
||||
show: false,
|
||||
showGradientBlur: false,
|
||||
},
|
||||
},
|
||||
actionButtonOptions: undefined,
|
||||
children: (
|
||||
<div className="w-full py-16 text-center">
|
||||
<h1 className="text-2xl font-bold mb-4">Минимальный Layout</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Только контент, без header и нижней кнопки
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
};
|
||||
@ -62,7 +62,6 @@ export function TemplateLayout({
|
||||
}: TemplateLayoutProps) {
|
||||
// 🎛️ ЦЕНТРАЛИЗОВАННАЯ ЛОГИКА BOTTOM BUTTON
|
||||
const {
|
||||
height: bottomActionButtonHeight,
|
||||
elementRef: bottomActionButtonRef,
|
||||
} = useDynamicSize<HTMLDivElement>({
|
||||
defaultHeight: 132,
|
||||
@ -115,10 +114,7 @@ export function TemplateLayout({
|
||||
|
||||
// 🎨 ЦЕНТРАЛИЗОВАННЫЙ РЕНДЕРИНГ
|
||||
return (
|
||||
<div
|
||||
className="w-full"
|
||||
style={{ paddingBottom: `${bottomActionButtonHeight}px` }}
|
||||
>
|
||||
<div className="w-full">
|
||||
<LayoutQuestion {...layoutQuestionProps}>
|
||||
{children}
|
||||
</LayoutQuestion>
|
||||
|
||||
@ -24,6 +24,7 @@ export function buildDefaultHeader(overrides?: Partial<HeaderDefinition>): Heade
|
||||
return {
|
||||
show: true,
|
||||
showBackButton: true,
|
||||
showProgress: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@ -13,7 +13,9 @@ export function buildCouponDefaults(id: string): BuilderScreen {
|
||||
return {
|
||||
id,
|
||||
template: "coupon",
|
||||
header: buildDefaultHeader(),
|
||||
header: buildDefaultHeader({
|
||||
showProgress: false
|
||||
}),
|
||||
title: buildDefaultTitle({
|
||||
text: "Тебе повезло!",
|
||||
align: "center",
|
||||
|
||||
@ -110,6 +110,10 @@ export function shouldShowBackButton(header?: HeaderDefinition, canGoBack?: bool
|
||||
return Boolean(canGoBack);
|
||||
}
|
||||
|
||||
export function shouldShowProgress(header?: HeaderDefinition) {
|
||||
return header?.showProgress !== false;
|
||||
}
|
||||
|
||||
export function shouldShowHeader(header?: HeaderDefinition) {
|
||||
return header?.show !== false;
|
||||
}
|
||||
@ -175,14 +179,17 @@ export function buildLayoutQuestionProps(
|
||||
|
||||
const showBackButton = shouldShowBackButton(screen.header, canGoBack);
|
||||
const showHeader = shouldShowHeader(screen.header);
|
||||
const showProgress = shouldShowProgress(screen.header);
|
||||
|
||||
return {
|
||||
headerProps: showHeader ? {
|
||||
progressProps: screenProgress ? buildHeaderProgress({
|
||||
current: screenProgress.current,
|
||||
total: screenProgress.total,
|
||||
label: `${screenProgress.current} of ${screenProgress.total}`
|
||||
}) : buildHeaderProgress(screen.header?.progress),
|
||||
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,
|
||||
|
||||
@ -39,6 +39,8 @@ export interface HeaderDefinition {
|
||||
progress?: HeaderProgressDefinition;
|
||||
/** Controls whether back button should be displayed. Defaults to true. */
|
||||
showBackButton?: boolean;
|
||||
/** Controls whether progress bar and label should be displayed. Defaults to true. */
|
||||
showProgress?: boolean;
|
||||
/** Controls whether header should be displayed at all. Defaults to true. */
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user