1use 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
36pub trait StoryArg: Sized {
38 fn story_arg() -> Self;
39}
40
41pub 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
52pub 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}