ui_storybook/components/editor/
editor_shell.rs1use leptos::prelude::*;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct ToolbarActionView {
13 pub id: &'static str,
15 pub label: &'static str,
17 pub icon: &'static str,
19 pub selected: bool,
21 pub disabled: bool,
23}
24
25#[derive(Debug, Clone, PartialEq)]
27pub struct EditorShellView {
28 pub document_title: String,
30 pub document_subtitle: Option<String>,
32 pub has_clip_loaded: bool,
34 pub toolbar_actions: Vec<ToolbarActionView>,
36 pub export_enabled: bool,
38 pub share_enabled: bool,
40}
41
42#[component]
43pub fn EditorTitleBar(
44 #[prop(into)]
46 title: String,
47 #[prop(optional, into)]
49 subtitle: String,
50 has_clip_loaded: bool,
52) -> impl IntoView {
53 let title_text = if has_clip_loaded {
54 title
55 } else {
56 "No clip loaded".to_owned()
57 };
58 let class = if has_clip_loaded {
59 "editor-titlebar"
60 } else {
61 "editor-titlebar editor-titlebar-empty"
62 };
63 let subtitle_view = (!subtitle.is_empty() && has_clip_loaded)
64 .then(|| view! { <span class="editor-titlebar-subtitle">{subtitle}</span> });
65 view! {
66 <header class=class>
67 <span class="editor-titlebar-traffic" aria-hidden="true">
68 <span class="traffic-dot traffic-close"></span>
69 <span class="traffic-dot traffic-min"></span>
70 <span class="traffic-dot traffic-max"></span>
71 </span>
72 <span class="editor-titlebar-text">
73 <span class="editor-titlebar-title">{title_text}</span>
74 {subtitle_view}
75 </span>
76 </header>
77 }
78}
79
80#[component]
81pub fn EditorToolbar(
82 actions: Vec<ToolbarActionView>,
84 export_enabled: bool,
86 share_enabled: bool,
88 #[prop(optional)]
94 on_action: Option<Callback<String>>,
95) -> impl IntoView {
96 let action_chips: Vec<_> = actions
97 .into_iter()
98 .map(|a| {
99 let mut class = String::from("editor-action");
100 if a.selected {
101 class.push_str(" editor-action-selected");
102 }
103 if a.disabled {
104 class.push_str(" editor-action-disabled");
105 }
106 let id = a.id;
107 let label = a.label;
108 let icon = a.icon;
109 let disabled = a.disabled;
110 let pressed = a.selected;
111 view! {
112 <button
113 class=class
114 data-id=id
115 disabled=disabled
116 aria-pressed=pressed
117 on:click=move |_| {
118 if let Some(cb) = on_action {
119 cb.run(id.to_owned());
120 }
121 }
122 >
123 <span class="editor-action-icon" aria-hidden="true">{icon}</span>
124 <span class="editor-action-label">{label}</span>
125 </button>
126 }
127 })
128 .collect();
129 view! {
130 <div class="editor-toolbar" role="toolbar" aria-label="Editor tools">
131 <div class="editor-toolbar-actions">{action_chips}</div>
132 <div class="editor-toolbar-end">
133 <button class="btn btn-outline btn-sm" disabled=!share_enabled>"Share"</button>
134 <button class="btn btn-default btn-sm" disabled=!export_enabled>"Export"</button>
135 </div>
136 </div>
137 }
138}
139
140#[component]
141pub fn EditorShell(
142 view: EditorShellView,
144 #[prop(optional)]
146 canvas: Option<Children>,
147 #[prop(optional)]
149 inspector: Option<Children>,
150 #[prop(optional)]
152 timeline: Option<Children>,
153 #[prop(optional, into)]
156 on_action: Option<Callback<String>>,
157) -> impl IntoView {
158 let EditorShellView {
159 document_title,
160 document_subtitle,
161 has_clip_loaded,
162 toolbar_actions,
163 export_enabled,
164 share_enabled,
165 } = view;
166 view! {
167 <section class="editor-shell" role="main" aria-label="Editor">
168 <EditorTitleBar
169 title=document_title
170 subtitle=document_subtitle.unwrap_or_default()
171 has_clip_loaded=has_clip_loaded
172 />
173 <EditorToolbar
174 actions=toolbar_actions
175 export_enabled=export_enabled
176 share_enabled=share_enabled
177 on_action=on_action.unwrap_or_else(|| Callback::new(|_: String| {}))
178 />
179 <div class="editor-body">
180 <div class="editor-canvas">
181 {canvas.map(|c| c())}
182 </div>
183 <aside class="editor-inspector" aria-label="Inspector">
184 {inspector.map(|i| i())}
185 </aside>
186 </div>
187 <footer class="editor-timeline">
188 {timeline.map(|t| t())}
189 </footer>
190 </section>
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn empty_titlebar_is_marked() {
200 let v = EditorShellView {
201 document_title: "Demo".into(),
202 document_subtitle: None,
203 has_clip_loaded: false,
204 toolbar_actions: vec![],
205 export_enabled: false,
206 share_enabled: false,
207 };
208 assert!(!v.has_clip_loaded);
209 }
210}