Skip to main content

dioxus_showcase/
lib.rs

1//! Public facade crate for dioxus-showcase.
2use dioxus::prelude::*;
3use dioxus_showcase_core::StoryDefinition;
4
5pub mod prelude {
6    pub use crate::{GeneratedStory, ShowcaseStoryFactory};
7    pub use crate::{StoryArg, StoryArgs, StoryProps, StoryVariant};
8    pub use dioxus_showcase_core::{ShowcaseRegistry, StoryDefinition, StoryEntry};
9    pub use dioxus_showcase_macros::{showcase, story, StoryProps};
10}
11
12pub use dioxus_showcase_core as core;
13pub use dioxus_showcase_macros as macros;
14
15pub fn slugify_title(title: &str) -> String {
16    let mut out = String::with_capacity(title.len());
17    let mut prev_dash = false;
18
19    for ch in title.chars() {
20        let normalized = ch.to_ascii_lowercase();
21        if normalized.is_ascii_alphanumeric() {
22            out.push(normalized);
23            prev_dash = false;
24            continue;
25        }
26
27        if !prev_dash {
28            out.push('-');
29            prev_dash = true;
30        }
31    }
32
33    out.trim_matches('-').to_owned()
34}
35
36/// Produces a single default story value for one component parameter.
37pub trait StoryArg: Sized {
38    fn story_arg() -> Self;
39}
40
41/// Produces one or more story values for aggregate props types.
42pub trait StoryArgs: Sized {
43    fn stories() -> Vec<Self>;
44}
45
46impl<T: StoryArg> StoryArgs for T {
47    fn stories() -> Vec<Self> {
48        vec![T::story_arg()]
49    }
50}
51
52/// Produces one or more named prop sets for a showcase component.
53pub trait StoryProps: Sized {
54    fn stories() -> Vec<StoryVariant<Self>>;
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct StoryVariant<T> {
59    pub name: Option<String>,
60    pub value: T,
61}
62
63impl<T> StoryVariant<T> {
64    pub fn unnamed(value: T) -> Self {
65        Self { name: None, value }
66    }
67
68    pub fn named(name: impl Into<String>, value: T) -> Self {
69        Self { name: Some(name.into()), value }
70    }
71}
72
73macro_rules! impl_story_arg_with_default {
74    ($($ty:ty),* $(,)?) => {
75        $(
76            impl StoryArg for $ty {
77                fn story_arg() -> Self {
78                    Self::default()
79                }
80            }
81        )*
82    };
83}
84
85impl_story_arg_with_default!(
86    bool, char, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
87);
88
89impl StoryArg for &'static str {
90    fn story_arg() -> Self {
91        "Lorem Ipsum"
92    }
93}
94
95impl StoryArg for String {
96    fn story_arg() -> Self {
97        "Lorem Ipsum".to_string()
98    }
99}
100
101impl<T> StoryArg for Option<T> {
102    fn story_arg() -> Self {
103        None
104    }
105}
106
107impl<T> StoryArg for Vec<T> {
108    fn story_arg() -> Self {
109        Vec::new()
110    }
111}
112
113impl StoryArg for Element {
114    fn story_arg() -> Self {
115        rsx! {
116            div { "Story content" }
117        }
118    }
119}
120
121impl<T: Sized + 'static> StoryArg for EventHandler<T> {
122    fn story_arg() -> Self {
123        Callback::new(|_| {})
124    }
125}
126
127pub struct GeneratedStory {
128    pub definition: StoryDefinition,
129    pub render: Box<dyn Fn() -> Element>,
130}
131
132pub trait ShowcaseStoryFactory {
133    fn create(source_path: &str, module_path: &str) -> Vec<GeneratedStory>;
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    #[derive(Default)]
140    struct DemoArgs;
141
142    impl StoryArg for DemoArgs {
143        fn story_arg() -> Self {
144            Default::default()
145        }
146    }
147
148    #[test]
149    fn story_arg_trait_is_implementable() {
150        fn assert_story_arg<T: StoryArg>() {}
151        assert_story_arg::<DemoArgs>();
152    }
153
154    #[test]
155    fn story_args_trait_is_derived_from_story_arg() {
156        fn assert_story_args<T: StoryArgs>() {}
157        assert_story_args::<DemoArgs>();
158    }
159
160    #[derive(Default)]
161    struct DemoProps;
162
163    impl StoryProps for DemoProps {
164        fn stories() -> Vec<StoryVariant<Self>> {
165            vec![StoryVariant::unnamed(Default::default())]
166        }
167    }
168
169    #[test]
170    fn story_props_trait_supports_named_variants() {
171        let stories = DemoProps::stories();
172        assert_eq!(stories.len(), 1);
173        assert_eq!(stories[0].name, None);
174    }
175
176    #[test]
177    fn prelude_reexports_core_types() {
178        let definition = prelude::StoryDefinition {
179            id: "id".to_owned(),
180            title: "title".to_owned(),
181            source_path: "showcase/button.stories.rs".to_owned(),
182            module_path: "module::story".to_owned(),
183            renderer_symbol: "story_renderer".to_owned(),
184            tags: vec!["tag".to_owned()],
185        };
186
187        let entry = prelude::StoryEntry { definition, renderer_symbol: "story_renderer" };
188
189        let mut registry = prelude::ShowcaseRegistry::default();
190        registry.register(entry);
191        assert_eq!(registry.story_count(), 1);
192    }
193
194    #[test]
195    fn element_story_arg_defaults_to_placeholder_markup() {
196        let element: Element = StoryArg::story_arg();
197        assert!(element.is_ok());
198    }
199}