/*
Shared types and reusable JSON Schema building blocks for section prop fragments: standard list items, cards, stats, enums, and effect stack items.
- Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.
- RFC-0303 Phase 3: extracted from shared-section-props/index.ts as part of the domain split.
- RFC-1289
*/
export type JsonSchemaFragment = {
properties: Record;
required?: string[];
};
export const STANDARD_LIST_ITEM_SCHEMA = {
type: "object",
additionalProperties: false,
required: ["text"],
properties: {
text: { type: "string", minLength: 1 },
icon: {
type: "object",
additionalProperties: false,
required: ["vendor", "collection", "name"],
properties: {
vendor: { type: "string" },
collection: { type: "string" },
name: { type: "string" },
size: { type: "number" },
},
},
},
};
/**
* RFC-1289 x-text contract: `body-list` consumers render items through
* section-list.astro (or an equivalent resolving loop), which resolves
* {price:…} markers — item text is marker-capable. `body-split-list` renders
* item text plain and keeps the unannotated STANDARD_LIST_ITEM_SCHEMA.
*/
export const MARKER_CAPABLE_LIST_ITEM_SCHEMA = {
...STANDARD_LIST_ITEM_SCHEMA,
properties: {
...STANDARD_LIST_ITEM_SCHEMA.properties,
text: { type: "string", minLength: 1, "x-text": { markers: ["price"] } },
},
};
export const STANDARD_CARD_SCHEMA = {
type: "object",
additionalProperties: false,
required: ["title"],
properties: {
title: { type: "string", minLength: 1 },
// RFC-1289: the card-grid body renderer resolves {price:…} markers in
// description and badge; title stays plain.
description: { type: "string", "x-text": { markers: ["price"] } },
image: { type: "string" },
imageAlt: { type: "string" },
icon: {
type: "object",
additionalProperties: false,
required: ["vendor", "collection", "name"],
properties: {
vendor: { type: "string" },
collection: { type: "string" },
name: { type: "string" },
size: { type: "number" },
},
},
href: { type: "string" },
badge: { type: "string", "x-text": { markers: ["price"] } },
number: { type: "string" },
},
};
export const STAT_ITEM_SCHEMA = {
type: "object",
additionalProperties: false,
required: ["value", "label"],
properties: {
value: { type: "string", minLength: 1 },
label: { type: "string", minLength: 1 },
numericValue: { type: "number" },
prefix: { type: "string" },
suffix: { type: "string" },
decimals: { type: "number", minimum: 0 },
duration: { type: "number", minimum: 0.1 },
},
};
export const ALIGN_ENUM = { type: "string", enum: ["left", "center", "right"] };
export const ICON_COLOR_ENUM = {
type: "string",
enum: ["primary", "accent", "success", "warning", "error", "muted"],
};
export const EFFECT_COLOR_SCHEMA = { type: "string", minLength: 1 };
/**
* RFC-0134 + RFC-0151 — one effect stack item is a discriminated union over
* every supported effect kind. Target × kind admissibility (e.g. glass only on
* surfaces, text kinds only on `heading`) is enforced by effects.contract.validate,
* not by this shape schema. Mirrors packages/share/src/schemas/effects.ts.
*/
export const EFFECT_STACK_ITEM_SCHEMA = {
oneOf: [
{
type: "object",
additionalProperties: false,
required: ["kind", "enabled"],
properties: {
kind: { const: "glass" },
enabled: { type: "boolean" },
blur: { type: "number", minimum: 0, maximum: 64 },
saturate: { type: "number", minimum: 0, maximum: 400 },
tint: { type: "string", minLength: 1 },
tintOpacity: { type: "number", minimum: 0, maximum: 1 },
border: { enum: ["hairline", "none"] },
},
},
{
type: "object",
additionalProperties: false,
required: ["kind", "enabled"],
properties: {
kind: { const: "shadow" },
enabled: { type: "boolean" },
offsetX: { type: "number", minimum: -64, maximum: 64 },
offsetY: { type: "number", minimum: -64, maximum: 64 },
blur: { type: "number", minimum: 0, maximum: 64 },
spread: { type: "number", minimum: -64, maximum: 64 },
color: EFFECT_COLOR_SCHEMA,
opacity: { type: "number", minimum: 0, maximum: 1 },
},
},
{
type: "object",
additionalProperties: false,
required: ["kind", "enabled"],
properties: {
kind: { const: "glow" },
enabled: { type: "boolean" },
blur: { type: "number", minimum: 0, maximum: 96 },
color: EFFECT_COLOR_SCHEMA,
opacity: { type: "number", minimum: 0, maximum: 1 },
},
},
{
type: "object",
additionalProperties: false,
required: ["kind", "enabled"],
properties: {
kind: { const: "bulge" },
enabled: { type: "boolean" },
depth: { type: "number", minimum: 0, maximum: 16 },
highlight: { type: "number", minimum: 0, maximum: 1 },
shade: { type: "number", minimum: 0, maximum: 1 },
},
},
{
type: "object",
additionalProperties: false,
required: ["kind", "enabled"],
properties: {
kind: { const: "tilt" },
enabled: { type: "boolean" },
rotate: { type: "number", minimum: -15, maximum: 15 },
skewX: { type: "number", minimum: -15, maximum: 15 },
},
},
{
type: "object",
additionalProperties: false,
required: ["kind", "enabled"],
properties: {
kind: { const: "lighten" },
enabled: { type: "boolean" },
opacity: { type: "number", minimum: 0, maximum: 1 },
color: EFFECT_COLOR_SCHEMA,
},
},
{
type: "object",
additionalProperties: false,
required: ["kind", "enabled"],
properties: {
kind: { const: "fade" },
enabled: { type: "boolean" },
edge: { enum: ["top", "bottom", "left", "right"] },
color: EFFECT_COLOR_SCHEMA,
opacity: { type: "number", minimum: 0, maximum: 1 },
extent: { type: "number", minimum: 0, maximum: 0.5 },
},
},
],
};
/** Shared effects array schema used by multiple body fragments. */
export const EFFECTS_ARRAY_SCHEMA = {
type: "array",
minItems: 1,
items: {
type: "object",
additionalProperties: false,
required: ["target", "stack"],
properties: {
target: { type: "string", minLength: 1 },
stack: {
type: "array",
minItems: 1,
items: EFFECT_STACK_ITEM_SCHEMA,
},
},
},
};