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