/*
[RFC-0103 + RFC-0134] Canonical SectionBodyContent discriminated union. One shape covers
every non-composite body kind: list / split-list / stats / cards / paragraphs /
comparison / rich. Body-level visual effects are authored through composable
`effects[]` assignments rather than legacy surface-specific props.
- Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.
- RFC-0133: backfilled MODULE_MAP and CHANGE_SUMMARY markers for compass.validate compliance.
- RFC-0134 replaces legacy surface config with composable effects assignments on supported body kinds.
*/
import { z } from "zod";
import { effectsSchema } from "./effects.ts";
import { horizontalAlignSchema } from "./horizontal-align.ts";
import { iconColorSchema } from "./icon-color.ts";
import { standardListItemSchema } from "./standard-list-item.ts";
import { standardCardSchema } from "./section-cards.ts";
import { statItemSchema } from "./section-stats.ts";
export const sectionBodyKindSchema = z.enum([
"list",
"split-list",
"stats",
"cards",
"paragraphs",
"comparison",
"rich",
"composite",
]);
export type SectionBodyKind = z.infer;
const listBodySchema = z
.object({
kind: z.literal("list"),
items: z.array(standardListItemSchema).min(1),
note: z.string().optional(),
iconColor: iconColorSchema.optional(),
align: horizontalAlignSchema.optional(),
itemAlign: horizontalAlignSchema.optional(),
hideMarkers: z.boolean().optional(),
effects: effectsSchema.optional(),
})
.strict();
const splitListBodySchema = z
.object({
kind: z.literal("split-list"),
primaryItems: z.array(standardListItemSchema).min(1),
secondaryItems: z.array(standardListItemSchema).optional(),
labels: z
.object({
primary: z.string().min(1),
secondary: z.string().min(1),
})
.optional(),
iconColors: z
.object({
primary: iconColorSchema.optional(),
secondary: iconColorSchema.optional(),
})
.optional(),
align: horizontalAlignSchema.optional(),
effects: effectsSchema.optional(),
})
.strict();
const statsBodySchema = z
.object({
kind: z.literal("stats"),
stats: z.array(statItemSchema).min(1),
animated: z.boolean().optional(),
align: horizontalAlignSchema.optional(),
})
.strict();
const cardsBodySchema = z
.object({
kind: z.literal("cards"),
cards: z.array(standardCardSchema).min(1),
layout: z.enum(["grid", "list"]).optional(),
columns: z.union([z.literal(2), z.literal(3), z.literal(4)]).optional(),
align: horizontalAlignSchema.optional(),
effects: effectsSchema.optional(),
})
.strict();
const paragraphsBodySchema = z
.object({
kind: z.literal("paragraphs"),
paragraphs: z.array(z.string().min(1)).min(1),
align: horizontalAlignSchema.optional(),
})
.strict();
const comparisonBodySchema = z
.object({
kind: z.literal("comparison"),
rows: z.array(z.object({ left: z.string().min(1), right: z.string().min(1) }).strict()).min(1),
labels: z.object({ left: z.string().min(1), right: z.string().min(1) }).optional(),
align: horizontalAlignSchema.optional(),
effects: effectsSchema.optional(),
})
.strict();
const richBodySchema = z
.object({
kind: z.literal("rich"),
contentRef: z.string().min(1),
animateNumbers: z.boolean().optional(),
align: horizontalAlignSchema.optional(),
})
.strict();
export const sectionBodyContentSchema = z.discriminatedUnion("kind", [
listBodySchema,
splitListBodySchema,
statsBodySchema,
cardsBodySchema,
paragraphsBodySchema,
comparisonBodySchema,
richBodySchema,
]);
export type SectionListBody = z.infer;
export type SectionSplitListBody = z.infer;
export type SectionStatsBody = z.infer;
export type SectionCardsBody = z.infer;
export type SectionParagraphsBody = z.infer;
export type SectionComparisonBody = z.infer;
export type SectionRichBody = z.infer;
export type SectionBodyContent = z.infer;