use super::Component;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct List {
#[serde(default)]
pub title: Option<String>,
pub items: Vec<ListItem>,
#[serde(default = "default_list_layout")]
pub layout: String,
#[serde(default = "default_columns")]
pub columns: usize,
#[serde(default)]
pub numbered: bool,
}
fn default_list_layout() -> String {
"vertical".into()
}
fn default_columns() -> usize {
1
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListItem {
pub content: String,
#[serde(default)]
pub icon: Option<String>,
#[serde(default)]
pub children: Vec<ListItem>,
}
impl Default for List {
fn default() -> Self {
Self::new()
}
}
impl List {
pub fn new() -> Self {
Self {
title: None,
items: Vec::new(),
layout: "vertical".into(),
columns: 1,
numbered: false,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn add_item(mut self, content: impl Into<String>) -> Self {
self.items.push(ListItem {
content: content.into(),
icon: None,
children: Vec::new(),
});
self
}
pub fn add_item_with_icon(
mut self,
content: impl Into<String>,
icon: impl Into<String>,
) -> Self {
self.items.push(ListItem {
content: content.into(),
icon: Some(icon.into()),
children: Vec::new(),
});
self
}
pub fn grid_layout(mut self, columns: usize) -> Self {
self.layout = "grid".into();
self.columns = columns;
self
}
pub fn numbered(mut self) -> Self {
self.numbered = true;
self
}
}
impl Component for List {
fn component_id(&self) -> &'static str {
"list"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Divider {
#[serde(default = "default_divider_style")]
pub style: String,
#[serde(default = "default_divider_thickness")]
pub thickness: String,
#[serde(default)]
pub color: Option<String>,
#[serde(default = "default_spacing")]
pub spacing_above: String,
#[serde(default = "default_spacing")]
pub spacing_below: String,
}
fn default_divider_style() -> String {
"solid".into()
}
fn default_divider_thickness() -> String {
"0.5pt".into()
}
fn default_spacing() -> String {
"12pt".into()
}
impl Default for Divider {
fn default() -> Self {
Self::new()
}
}
impl Divider {
pub fn new() -> Self {
Self {
style: "solid".into(),
thickness: "0.5pt".into(),
color: None,
spacing_above: "12pt".into(),
spacing_below: "12pt".into(),
}
}
pub fn dashed() -> Self {
Self {
style: "dashed".into(),
..Self::new()
}
}
pub fn dotted() -> Self {
Self {
style: "dotted".into(),
..Self::new()
}
}
pub fn thick() -> Self {
Self {
thickness: "2pt".into(),
..Self::new()
}
}
pub fn with_color(mut self, color: impl Into<String>) -> Self {
self.color = Some(color.into());
self
}
}
impl Component for Divider {
fn component_id(&self) -> &'static str {
"divider"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Grid {
#[serde(default)]
pub title: Option<String>,
pub columns: usize,
pub items: Vec<GridItem>,
#[serde(default = "default_grid_gap")]
pub column_gap: String,
#[serde(default = "default_grid_gap")]
pub row_gap: String,
#[serde(default)]
pub item_min_height: Option<String>,
}
fn default_grid_gap() -> String {
"16pt".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GridItem {
pub content: serde_json::Value,
#[serde(default = "default_span")]
pub colspan: usize,
}
fn default_span() -> usize {
1
}
impl Grid {
pub fn new(columns: usize) -> Self {
Self {
title: None,
columns,
items: Vec::new(),
column_gap: "16pt".into(),
row_gap: "16pt".into(),
item_min_height: None,
}
}
pub fn with_item_min_height(mut self, min_height: impl Into<String>) -> Self {
self.item_min_height = Some(min_height.into());
self
}
pub fn add_item(mut self, content: serde_json::Value) -> Self {
self.items.push(GridItem {
content,
colspan: 1,
});
self
}
pub fn add_item_with_span(mut self, content: serde_json::Value, colspan: usize) -> Self {
self.items.push(GridItem { content, colspan });
self
}
}
impl Component for Grid {
fn component_id(&self) -> &'static str {
"grid-component"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlowGroup {
pub items: Vec<serde_json::Value>,
#[serde(default)]
pub spacing: Option<String>,
#[serde(default)]
pub keep_together_if_under: Option<String>,
}
impl Default for FlowGroup {
fn default() -> Self {
Self::new()
}
}
impl FlowGroup {
pub fn new() -> Self {
Self {
items: Vec::new(),
spacing: None,
keep_together_if_under: None,
}
}
pub fn add_item(mut self, content: serde_json::Value) -> Self {
self.items.push(content);
self
}
pub fn with_spacing(mut self, spacing: impl Into<String>) -> Self {
self.spacing = Some(spacing.into());
self
}
pub fn with_keep_together_if_under(mut self, threshold: impl Into<String>) -> Self {
self.keep_together_if_under = Some(threshold.into());
self
}
}
impl Component for FlowGroup {
fn component_id(&self) -> &'static str {
"flow-group"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageBreak;
impl Default for PageBreak {
fn default() -> Self {
Self::new()
}
}
impl PageBreak {
pub fn new() -> Self {
Self
}
}
impl Component for PageBreak {
fn component_id(&self) -> &'static str {
"page-break"
}
fn to_data(&self) -> serde_json::Value {
serde_json::json!({})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Watermark {
pub text: String,
#[serde(default = "default_rotation")]
pub rotation: f64,
#[serde(default = "default_opacity")]
pub opacity: f64,
#[serde(default = "default_watermark_size")]
pub size: String,
}
fn default_rotation() -> f64 {
-45.0
}
fn default_opacity() -> f64 {
0.1
}
fn default_watermark_size() -> String {
"48pt".into()
}
impl Watermark {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
rotation: -45.0,
opacity: 0.1,
size: "48pt".into(),
}
}
pub fn confidential() -> Self {
Self::new("CONFIDENTIAL")
}
pub fn draft() -> Self {
Self::new("DRAFT")
}
pub fn with_rotation(mut self, degrees: f64) -> Self {
self.rotation = degrees;
self
}
pub fn with_opacity(mut self, opacity: f64) -> Self {
self.opacity = opacity.clamp(0.0, 1.0);
self
}
}
impl Component for Watermark {
fn component_id(&self) -> &'static str {
"watermark"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressBar {
pub label: String,
pub value: f64,
#[serde(default = "default_max")]
pub max: f64,
#[serde(default = "default_true")]
pub show_percentage: bool,
#[serde(default)]
pub color: Option<String>,
}
fn default_max() -> f64 {
100.0
}
fn default_true() -> bool {
true
}
impl ProgressBar {
pub fn new(label: impl Into<String>, value: f64) -> Self {
Self {
label: label.into(),
value,
max: 100.0,
show_percentage: true,
color: None,
}
}
pub fn with_max(mut self, max: f64) -> Self {
self.max = max;
self
}
pub fn with_color(mut self, color: impl Into<String>) -> Self {
self.color = Some(color.into());
self
}
pub fn percentage(&self) -> f64 {
(self.value / self.max * 100.0).min(100.0)
}
}
impl Component for ProgressBar {
fn component_id(&self) -> &'static str {
"progress-bar"
}
fn to_data(&self) -> serde_json::Value {
let mut data = serde_json::to_value(self).unwrap_or_default();
if let serde_json::Value::Object(ref mut map) = data {
map.insert("percentage".into(), serde_json::json!(self.percentage()));
}
data
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyValueList {
#[serde(default)]
pub title: Option<String>,
pub items: Vec<KeyValuePair>,
#[serde(default = "default_kv_layout")]
pub layout: String,
}
fn default_kv_layout() -> String {
"vertical".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyValuePair {
pub key: String,
pub value: String,
#[serde(default)]
pub highlight: bool,
}
impl Default for KeyValueList {
fn default() -> Self {
Self::new()
}
}
impl KeyValueList {
pub fn new() -> Self {
Self {
title: None,
items: Vec::new(),
layout: "vertical".into(),
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.items.push(KeyValuePair {
key: key.into(),
value: value.into(),
highlight: false,
});
self
}
pub fn add_highlighted(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.items.push(KeyValuePair {
key: key.into(),
value: value.into(),
highlight: true,
});
self
}
}
impl Component for KeyValueList {
fn component_id(&self) -> &'static str {
"key-value-list"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TableOfContents {
pub title: String,
pub depth: u8,
#[serde(default)]
pub font_size: Option<String>,
}
impl TableOfContents {
pub fn new() -> Self {
Self {
title: "Inhaltsverzeichnis".to_string(),
depth: 3,
font_size: None,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn with_depth(mut self, depth: u8) -> Self {
self.depth = depth;
self
}
pub fn with_font_size(mut self, size: impl Into<String>) -> Self {
self.font_size = Some(size.into());
self
}
}
impl Default for TableOfContents {
fn default() -> Self {
Self::new()
}
}
impl Component for TableOfContents {
fn component_id(&self) -> &'static str {
"table-of-contents"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
fn default_level_two() -> u8 { 2 }
fn default_outlined() -> bool { true }
fn default_divider_below() -> bool { true }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SectionHeaderSplit {
pub title: String,
pub body: String,
#[serde(default)]
pub eyebrow: Option<String>,
#[serde(default = "default_level_two")]
pub level: u8,
#[serde(default = "default_outlined")]
pub outlined: bool,
#[serde(default = "default_divider_below")]
pub divider_below: bool,
}
impl SectionHeaderSplit {
pub fn new(title: impl Into<String>, body: impl Into<String>) -> Self {
Self {
title: title.into(),
body: body.into(),
eyebrow: None,
level: 2,
outlined: true,
divider_below: true,
}
}
pub fn with_eyebrow(mut self, eyebrow: impl Into<String>) -> Self {
self.eyebrow = Some(eyebrow.into());
self
}
pub fn with_level(mut self, level: u8) -> Self {
self.level = level.clamp(1, 6);
self
}
pub fn no_divider(mut self) -> Self {
self.divider_below = false;
self
}
}
impl Component for SectionHeaderSplit {
fn component_id(&self) -> &'static str {
"section-header-split"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhaseBlock {
pub phase_number: u8,
pub phase_label: String,
pub description: String,
pub items: Vec<String>,
#[serde(default)]
pub accent_color: Option<String>,
#[serde(default)]
pub item_count_total: Option<usize>,
}
impl PhaseBlock {
pub fn new(
number: u8,
label: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
phase_number: number,
phase_label: label.into(),
description: description.into(),
items: Vec::new(),
accent_color: None,
item_count_total: None,
}
}
pub fn with_items(mut self, items: Vec<String>) -> Self {
self.items = items;
self
}
pub fn with_total(mut self, total: usize) -> Self {
self.item_count_total = Some(total);
self
}
pub fn with_color(mut self, color: impl Into<String>) -> Self {
self.accent_color = Some(color.into());
self
}
}
impl Component for PhaseBlock {
fn component_id(&self) -> &'static str {
"phase-block"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChecklistRow {
pub label: String,
pub diagnosis: String,
#[serde(default)]
pub status: Option<String>,
}
impl ChecklistRow {
pub fn new(label: impl Into<String>, diagnosis: impl Into<String>) -> Self {
Self {
label: label.into(),
diagnosis: diagnosis.into(),
status: None,
}
}
pub fn with_status(mut self, status: impl Into<String>) -> Self {
self.status = Some(status.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChecklistPanel {
#[serde(default)]
pub title: Option<String>,
pub rows: Vec<ChecklistRow>,
}
impl ChecklistPanel {
pub fn new(rows: Vec<ChecklistRow>) -> Self {
Self { title: None, rows }
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
}
impl Component for ChecklistPanel {
fn component_id(&self) -> &'static str {
"checklist-panel"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricStripItem {
pub label: String,
pub value: String,
#[serde(default)]
pub unit: Option<String>,
#[serde(default)]
pub accent_color: Option<String>,
#[serde(default)]
pub status: Option<String>,
}
impl MetricStripItem {
pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
Self {
label: label.into(),
value: value.into(),
unit: None,
accent_color: None,
status: None,
}
}
pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
self.unit = Some(unit.into());
self
}
pub fn with_accent(mut self, color: impl Into<String>) -> Self {
self.accent_color = Some(color.into());
self
}
pub fn with_status(mut self, status: impl Into<String>) -> Self {
self.status = Some(status.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricStrip {
pub items: Vec<MetricStripItem>,
#[serde(default)]
pub compact: bool,
}
impl MetricStrip {
pub fn new(items: Vec<MetricStripItem>) -> Self {
Self { items, compact: false }
}
pub fn compact(mut self) -> Self {
self.compact = true;
self
}
}
impl Component for MetricStrip {
fn component_id(&self) -> &'static str {
"metric-strip"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImpactGridCard {
pub label: String,
pub headline: String,
pub body: String,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub icon: Option<String>,
}
impl ImpactGridCard {
pub fn new(
label: impl Into<String>,
headline: impl Into<String>,
body: impl Into<String>,
) -> Self {
Self {
label: label.into(),
headline: headline.into(),
body: body.into(),
status: None,
icon: None,
}
}
pub fn with_status(mut self, status: impl Into<String>) -> Self {
self.status = Some(status.into());
self
}
pub fn with_icon(mut self, icon: impl Into<String>) -> Self {
self.icon = Some(icon.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImpactGrid {
pub user: ImpactGridCard,
pub risk: ImpactGridCard,
pub conversion: ImpactGridCard,
#[serde(default)]
pub title: Option<String>,
}
impl ImpactGrid {
pub fn new(user: ImpactGridCard, risk: ImpactGridCard, conversion: ImpactGridCard) -> Self {
Self { user, risk, conversion, title: None }
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
}
impl Component for ImpactGrid {
fn component_id(&self) -> &'static str {
"impact-grid"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpotlightCard {
pub title: String,
pub body: String,
#[serde(default)]
pub variant: Option<String>,
#[serde(default)]
pub eyebrow: Option<String>,
#[serde(default)]
pub metric: Option<String>,
#[serde(default)]
pub detail: Option<String>,
#[serde(default)]
pub action: Option<String>,
}
impl SpotlightCard {
pub fn new(title: impl Into<String>, body: impl Into<String>) -> Self {
Self {
title: title.into(),
body: body.into(),
variant: None,
eyebrow: None,
metric: None,
detail: None,
action: None,
}
}
pub fn with_variant(mut self, variant: impl Into<String>) -> Self {
self.variant = Some(variant.into());
self
}
pub fn with_eyebrow(mut self, eyebrow: impl Into<String>) -> Self {
self.eyebrow = Some(eyebrow.into());
self
}
pub fn with_metric(mut self, metric: impl Into<String>) -> Self {
self.metric = Some(metric.into());
self
}
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
self.detail = Some(detail.into());
self
}
pub fn with_action(mut self, action: impl Into<String>) -> Self {
self.action = Some(action.into());
self
}
}
impl Component for SpotlightCard {
fn component_id(&self) -> &'static str {
"spotlight-card"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
fn default_label_left() -> String {
"Before".into()
}
fn default_label_right() -> String {
"After".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComparisonBlock {
pub wrong: String,
pub right: String,
#[serde(default)]
pub wrong_label: Option<String>,
#[serde(default)]
pub right_label: Option<String>,
#[serde(default = "default_label_left")]
pub label_left: String,
#[serde(default = "default_label_right")]
pub label_right: String,
#[serde(default)]
pub is_code: bool,
#[serde(default)]
pub note: Option<String>,
}
impl ComparisonBlock {
pub fn new(wrong: impl Into<String>, right: impl Into<String>) -> Self {
Self {
wrong: wrong.into(),
right: right.into(),
wrong_label: None,
right_label: None,
label_left: "Before".into(),
label_right: "After".into(),
is_code: false,
note: None,
}
}
pub fn code(mut self) -> Self {
self.is_code = true;
self
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
pub fn with_labels(
mut self,
wrong_label: impl Into<String>,
right_label: impl Into<String>,
) -> Self {
self.wrong_label = Some(wrong_label.into());
self.right_label = Some(right_label.into());
self
}
pub fn with_side_labels(
mut self,
label_left: impl Into<String>,
label_right: impl Into<String>,
) -> Self {
self.label_left = label_left.into();
self.label_right = label_right.into();
self
}
}
impl Component for ComparisonBlock {
fn component_id(&self) -> &'static str {
"comparison-block"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComparisonClusterItem {
pub label: String,
pub value: String,
#[serde(default)]
pub sub: Option<String>,
#[serde(default)]
pub status: Option<String>,
}
impl ComparisonClusterItem {
pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
Self {
label: label.into(),
value: value.into(),
sub: None,
status: None,
}
}
pub fn with_sub(mut self, sub: impl Into<String>) -> Self {
self.sub = Some(sub.into());
self
}
pub fn with_status(mut self, status: impl Into<String>) -> Self {
self.status = Some(status.into());
self
}
}
fn default_three() -> usize { 3 }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComparisonCluster {
pub items: Vec<ComparisonClusterItem>,
#[serde(default)]
pub title: Option<String>,
#[serde(default = "default_three")]
pub columns: usize,
}
impl ComparisonCluster {
pub fn new(items: Vec<ComparisonClusterItem>) -> Self {
Self { items, title: None, columns: 3 }
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn with_columns(mut self, columns: usize) -> Self {
self.columns = columns;
self
}
}
impl Component for ComparisonCluster {
fn component_id(&self) -> &'static str {
"comparison-cluster"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SideLabel {
pub heading: String,
#[serde(default)]
pub subheading: Option<String>,
#[serde(default)]
pub items: Vec<String>,
#[serde(default)]
pub text: Option<String>,
#[serde(default)]
pub divider: bool,
}
impl SideLabel {
pub fn new(heading: impl Into<String>) -> Self {
Self {
heading: heading.into(),
subheading: None,
items: Vec::new(),
text: None,
divider: false,
}
}
pub fn with_subheading(mut self, subheading: impl Into<String>) -> Self {
self.subheading = Some(subheading.into());
self
}
pub fn add_item(mut self, item: impl Into<String>) -> Self {
self.items.push(item.into());
self
}
pub fn with_text(mut self, text: impl Into<String>) -> Self {
self.text = Some(text.into());
self
}
pub fn with_divider(mut self) -> Self {
self.divider = true;
self
}
}
impl Component for SideLabel {
fn component_id(&self) -> &'static str {
"side-label"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenefitStrip {
pub titles: Vec<String>,
pub descriptions: Vec<String>,
#[serde(default)]
pub icons: Vec<String>,
#[serde(default = "default_benefit_columns")]
pub columns: usize,
}
fn default_benefit_columns() -> usize {
3
}
impl BenefitStrip {
pub fn new() -> Self {
Self {
titles: Vec::new(),
descriptions: Vec::new(),
icons: Vec::new(),
columns: default_benefit_columns(),
}
}
pub fn add_benefit(mut self, title: impl Into<String>, description: impl Into<String>) -> Self {
self.titles.push(title.into());
self.descriptions.push(description.into());
self
}
pub fn add_benefit_with_icon(mut self, icon: impl Into<String>, title: impl Into<String>, description: impl Into<String>) -> Self {
self.titles.push(title.into());
self.descriptions.push(description.into());
self.icons.push(icon.into());
self
}
pub fn with_columns(mut self, columns: usize) -> Self {
self.columns = columns.max(2).min(5);
self
}
}
impl Component for BenefitStrip {
fn component_id(&self) -> &'static str {
"benefit-strip"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricingCard {
pub name: String,
pub price: String,
#[serde(default)]
pub billing_period: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub features: Vec<String>,
#[serde(default)]
pub cta_label: Option<String>,
#[serde(default)]
pub highlighted: bool,
}
impl PricingCard {
pub fn new(name: impl Into<String>, price: impl Into<String>) -> Self {
Self {
name: name.into(),
price: price.into(),
billing_period: None,
description: None,
features: Vec::new(),
cta_label: None,
highlighted: false,
}
}
pub fn with_billing_period(mut self, period: impl Into<String>) -> Self {
self.billing_period = Some(period.into());
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn add_feature(mut self, feature: impl Into<String>) -> Self {
self.features.push(feature.into());
self
}
pub fn with_cta(mut self, label: impl Into<String>) -> Self {
self.cta_label = Some(label.into());
self
}
pub fn highlighted(mut self) -> Self {
self.highlighted = true;
self
}
}
impl Component for PricingCard {
fn component_id(&self) -> &'static str {
"pricing-card"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecommendationCard {
pub title: String,
pub description: String,
#[serde(default)]
pub impact: Option<String>,
#[serde(default)]
pub effort: Option<String>,
#[serde(default)]
pub priority: Option<String>,
}
impl RecommendationCard {
pub fn new(title: impl Into<String>, description: impl Into<String>) -> Self {
Self {
title: title.into(),
description: description.into(),
impact: None,
effort: None,
priority: None,
}
}
pub fn with_impact(mut self, impact: impl Into<String>) -> Self {
self.impact = Some(impact.into());
self
}
pub fn with_effort(mut self, effort: impl Into<String>) -> Self {
self.effort = Some(effort.into());
self
}
pub fn with_priority(mut self, priority: impl Into<String>) -> Self {
self.priority = Some(priority.into());
self
}
}
impl Component for RecommendationCard {
fn component_id(&self) -> &'static str {
"recommendation-card"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StepCardRow {
pub titles: Vec<String>,
pub descriptions: Vec<String>,
#[serde(default = "default_step_columns")]
pub columns: usize,
}
fn default_step_columns() -> usize {
3
}
impl StepCardRow {
pub fn new() -> Self {
Self {
titles: Vec::new(),
descriptions: Vec::new(),
columns: default_step_columns(),
}
}
pub fn add_step(mut self, title: impl Into<String>, description: impl Into<String>) -> Self {
self.titles.push(title.into());
self.descriptions.push(description.into());
self
}
pub fn with_columns(mut self, columns: usize) -> Self {
self.columns = columns.max(2).min(4);
self
}
}
impl Component for StepCardRow {
fn component_id(&self) -> &'static str {
"step-card-row"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Columns {
pub left: String,
pub right: String,
#[serde(default = "default_left_width")]
pub left_width: f64,
#[serde(default)]
pub gap: Option<String>,
}
fn default_left_width() -> f64 {
0.6
}
impl Columns {
pub fn new(left: impl Into<String>, right: impl Into<String>) -> Self {
Self {
left: left.into(),
right: right.into(),
left_width: default_left_width(),
gap: None,
}
}
pub fn with_ratio(mut self, left_width: f64) -> Self {
self.left_width = left_width.max(0.2).min(0.8);
self
}
pub fn with_gap(mut self, gap: impl Into<String>) -> Self {
self.gap = Some(gap.into());
self
}
}
impl Component for Columns {
fn component_id(&self) -> &'static str {
"columns"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaqList {
pub items: Vec<FaqItem>,
#[serde(default)]
pub title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaqItem {
pub question: String,
pub answer: String,
}
impl FaqList {
pub fn new() -> Self {
Self {
items: Vec::new(),
title: None,
}
}
pub fn add_item(mut self, question: impl Into<String>, answer: impl Into<String>) -> Self {
self.items.push(FaqItem {
question: question.into(),
answer: answer.into(),
});
self
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
}
impl Component for FaqList {
fn component_id(&self) -> &'static str {
"faq-list"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UseCaseCard {
pub title: String,
pub context: String,
pub problem: String,
pub solution: String,
#[serde(default)]
pub outcome: Option<String>,
}
impl UseCaseCard {
pub fn new(
title: impl Into<String>,
context: impl Into<String>,
problem: impl Into<String>,
solution: impl Into<String>,
) -> Self {
Self {
title: title.into(),
context: context.into(),
problem: problem.into(),
solution: solution.into(),
outcome: None,
}
}
pub fn with_outcome(mut self, outcome: impl Into<String>) -> Self {
self.outcome = Some(outcome.into());
self
}
}
impl Component for UseCaseCard {
fn component_id(&self) -> &'static str {
"use-case-card"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogoStrip {
pub labels: Vec<String>,
#[serde(default = "default_logo_columns")]
pub columns: usize,
#[serde(default)]
pub title: Option<String>,
}
fn default_logo_columns() -> usize {
4
}
impl LogoStrip {
pub fn new() -> Self {
Self {
labels: Vec::new(),
columns: default_logo_columns(),
title: None,
}
}
pub fn add_logo(mut self, label: impl Into<String>) -> Self {
self.labels.push(label.into());
self
}
pub fn with_columns(mut self, columns: usize) -> Self {
self.columns = columns.max(2).min(6);
self
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
}
impl Component for LogoStrip {
fn component_id(&self) -> &'static str {
"logo-strip"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PullQuote {
pub quote: String,
#[serde(default)]
pub attribution: Option<String>,
}
impl PullQuote {
pub fn new(quote: impl Into<String>) -> Self {
Self {
quote: quote.into(),
attribution: None,
}
}
pub fn with_attribution(mut self, attribution: impl Into<String>) -> Self {
self.attribution = Some(attribution.into());
self
}
}
impl Component for PullQuote {
fn component_id(&self) -> &'static str {
"pull-quote"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BigNumber {
pub value: String,
pub label: String,
#[serde(default)]
pub context: Option<String>,
}
impl BigNumber {
pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
Self {
value: value.into(),
label: label.into(),
context: None,
}
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}
}
impl Component for BigNumber {
fn component_id(&self) -> &'static str {
"big-number"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlossaryList {
pub items: Vec<GlossaryItem>,
#[serde(default)]
pub title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlossaryItem {
pub term: String,
pub definition: String,
}
impl GlossaryList {
pub fn new() -> Self {
Self {
items: Vec::new(),
title: None,
}
}
pub fn add_item(mut self, term: impl Into<String>, definition: impl Into<String>) -> Self {
self.items.push(GlossaryItem {
term: term.into(),
definition: definition.into(),
});
self
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
}
impl Component for GlossaryList {
fn component_id(&self) -> &'static str {
"glossary-list"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosisRow {
pub label: String,
pub diagnosis: String,
#[serde(default)]
pub status: Option<String>,
}
impl DiagnosisRow {
pub fn new(label: impl Into<String>, diagnosis: impl Into<String>) -> Self {
Self {
label: label.into(),
diagnosis: diagnosis.into(),
status: None,
}
}
pub fn with_status(mut self, status: impl Into<String>) -> Self {
self.status = Some(status.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosisPanel {
#[serde(default)]
pub title: Option<String>,
pub rows: Vec<DiagnosisRow>,
}
impl DiagnosisPanel {
pub fn new(rows: Vec<DiagnosisRow>) -> Self {
Self { title: None, rows }
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
}
impl Component for DiagnosisPanel {
fn component_id(&self) -> &'static str {
"diagnosis-panel"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DominantIssueSpotlight {
pub title: String,
pub severity: String,
pub body: String,
pub user_impact: String,
pub recommendation: String,
#[serde(default)]
pub eyebrow: Option<String>,
#[serde(default)]
pub affected_count: Option<u32>,
}
impl DominantIssueSpotlight {
pub fn new(
title: impl Into<String>,
severity: impl Into<String>,
body: impl Into<String>,
user_impact: impl Into<String>,
recommendation: impl Into<String>,
) -> Self {
Self {
title: title.into(),
severity: severity.into(),
body: body.into(),
user_impact: user_impact.into(),
recommendation: recommendation.into(),
eyebrow: None,
affected_count: None,
}
}
pub fn with_eyebrow(mut self, eyebrow: impl Into<String>) -> Self {
self.eyebrow = Some(eyebrow.into());
self
}
pub fn with_affected_count(mut self, count: u32) -> Self {
self.affected_count = Some(count);
self
}
}
impl Component for DominantIssueSpotlight {
fn component_id(&self) -> &'static str {
"dominant-issue-spotlight"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrongRightBlock {
pub wrong: String,
pub right: String,
#[serde(default)]
pub wrong_label: Option<String>,
#[serde(default)]
pub right_label: Option<String>,
#[serde(default)]
pub is_code: bool,
#[serde(default)]
pub note: Option<String>,
}
impl WrongRightBlock {
pub fn new(wrong: impl Into<String>, right: impl Into<String>) -> Self {
Self {
wrong: wrong.into(),
right: right.into(),
wrong_label: None,
right_label: None,
is_code: false,
note: None,
}
}
pub fn code(mut self) -> Self {
self.is_code = true;
self
}
pub fn with_labels(
mut self,
wrong_label: impl Into<String>,
right_label: impl Into<String>,
) -> Self {
self.wrong_label = Some(wrong_label.into());
self.right_label = Some(right_label.into());
self
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
}
impl Component for WrongRightBlock {
fn component_id(&self) -> &'static str {
"wrong-right-block"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}