hblank-core 0.4.2

Framework-neutral component catalog and control model for Hblank adapters
Documentation
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CalloutTone {
    Note,
    Success,
    Warning,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DocBlock {
    Heading {
        level: u8,
        text: String,
    },
    Prose(String),
    Fixture {
        id: String,
    },
    Props,
    Controls,
    Source,
    Callout {
        tone: CalloutTone,
        title: String,
        body: String,
    },
    Custom {
        id: String,
        payload: String,
    },
}

impl DocBlock {
    #[must_use]
    pub fn heading(level: u8, text: impl Into<String>) -> Self {
        Self::Heading {
            level: level.clamp(1, 3),
            text: text.into(),
        }
    }

    #[must_use]
    pub fn prose(text: impl Into<String>) -> Self {
        Self::Prose(text.into())
    }

    #[must_use]
    pub fn fixture(id: impl Into<String>) -> Self {
        Self::Fixture { id: id.into() }
    }

    #[must_use]
    pub const fn props() -> Self {
        Self::Props
    }

    #[must_use]
    pub const fn controls() -> Self {
        Self::Controls
    }

    #[must_use]
    pub const fn source() -> Self {
        Self::Source
    }

    #[must_use]
    pub fn custom(id: impl Into<String>, payload: impl Into<String>) -> Self {
        Self::Custom {
            id: id.into(),
            payload: payload.into(),
        }
    }

    #[must_use]
    pub fn callout(tone: CalloutTone, title: impl Into<String>, body: impl Into<String>) -> Self {
        Self::Callout {
            tone,
            title: title.into(),
            body: body.into(),
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DocPage {
    blocks: Vec<DocBlock>,
}

impl DocPage {
    #[must_use]
    pub fn new(blocks: impl IntoIterator<Item = DocBlock>) -> Self {
        Self {
            blocks: blocks.into_iter().collect(),
        }
    }

    #[must_use]
    pub fn blocks(&self) -> &[DocBlock] {
        &self.blocks
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.blocks.is_empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn composes_typed_document_blocks_in_order() {
        let page = DocPage::new([
            DocBlock::heading(1, "Button"),
            DocBlock::prose("Use for primary actions."),
            DocBlock::props(),
            DocBlock::controls(),
            DocBlock::source(),
            DocBlock::custom("project::tokens", "spacing"),
        ]);

        assert_eq!(page.blocks().len(), 6);
        assert_eq!(
            page.blocks()[0],
            DocBlock::Heading {
                level: 1,
                text: "Button".to_owned(),
            }
        );
    }
}