Skip to main content

ui_storybook/stories/
shell.rs

1//! Shell stories — `DropZone`, `StatusBar`, plus M-UI.2 `NavigationRail`
2//! + `AppShell` compositions.
3
4use leptos::prelude::*;
5
6use crate::components::shell::{
7    AppSection, AppShell, DropZone, DropZoneState, NavigationRail, StatusBar, StatusKind,
8};
9use crate::fixtures::shell::{sample_nav_items, sample_user_avatar, sample_workspace_badge};
10
11use super::{Story, StoryViewport, render};
12
13/// All shell-surface stories, in display order.
14#[must_use]
15pub fn stories() -> Vec<Story> {
16    vec![
17        Story {
18            id: "drop-zone-idle",
19            category: "App surfaces",
20            title: "Drop zone — idle",
21            viewport: StoryViewport::Auto,
22            render: render_drop_zone_idle,
23        },
24        Story {
25            id: "drop-zone-active",
26            category: "App surfaces",
27            title: "Drop zone — active (file dragged)",
28            viewport: StoryViewport::Auto,
29            render: render_drop_zone_active,
30        },
31        Story {
32            id: "status-bar-ready",
33            category: "Shell",
34            title: "Status bar — ready",
35            viewport: StoryViewport::Auto,
36            render: render_status_ready,
37        },
38        Story {
39            id: "status-bar-busy",
40            category: "Shell",
41            title: "Status bar — encoding",
42            viewport: StoryViewport::Auto,
43            render: render_status_busy,
44        },
45        Story {
46            id: "status-bar-error",
47            category: "Shell",
48            title: "Status bar — error",
49            viewport: StoryViewport::Auto,
50            render: render_status_error,
51        },
52        // ─── M-UI.2 navigation rail / app shell ──────────────
53        Story {
54            id: "nav-rail-record-active",
55            category: "Shell",
56            title: "Nav rail — Record active",
57            viewport: StoryViewport::Fixed {
58                width: 88,
59                height: 460,
60            },
61            render: render_nav_record,
62        },
63        Story {
64            id: "nav-rail-library-active",
65            category: "Shell",
66            title: "Nav rail — Library active",
67            viewport: StoryViewport::Fixed {
68                width: 88,
69                height: 460,
70            },
71            render: render_nav_library,
72        },
73        Story {
74            id: "nav-rail-editor-active",
75            category: "Shell",
76            title: "Nav rail — Editor active",
77            viewport: StoryViewport::Fixed {
78                width: 88,
79                height: 460,
80            },
81            render: render_nav_editor,
82        },
83        Story {
84            id: "nav-rail-cursor-active",
85            category: "Shell",
86            title: "Nav rail — Cursor active",
87            viewport: StoryViewport::Fixed {
88                width: 88,
89                height: 460,
90            },
91            render: render_nav_cursor,
92        },
93        Story {
94            id: "nav-rail-with-counts",
95            category: "Shell",
96            title: "Nav rail — with notification count",
97            viewport: StoryViewport::Fixed {
98                width: 88,
99                height: 460,
100            },
101            render: render_nav_with_counts,
102        },
103        Story {
104            id: "app-shell-three-pane",
105            category: "Compositions",
106            title: "App shell — three-pane editor layout",
107            viewport: StoryViewport::Fixed {
108                width: 760,
109                height: 480,
110            },
111            render: render_app_shell_three_pane,
112        },
113    ]
114}
115
116fn render_drop_zone_idle() -> String {
117    render(view! {
118        <DropZone state=DropZoneState::Idle hint="⌘O to browse" />
119    })
120}
121
122fn render_drop_zone_active() -> String {
123    render(view! {
124        <DropZone state=DropZoneState::Active />
125    })
126}
127
128fn render_status_ready() -> String {
129    render(view! {
130        <StatusBar
131            fps=60.0
132            encoder="H.264 · idle"
133            file_bytes=0
134            kind=StatusKind::Ready
135        />
136    })
137}
138
139fn render_status_busy() -> String {
140    render(view! {
141        <StatusBar
142            fps=60.0
143            encoder="H.264 · 9.4 Mbps"
144            file_bytes=24_117_248_u64
145            kind=StatusKind::Busy
146            detail="Encoding · 38%"
147        />
148    })
149}
150
151fn render_status_error() -> String {
152    render(view! {
153        <StatusBar
154            fps=0.0
155            encoder="H.264"
156            file_bytes=12_582_912_u64
157            kind=StatusKind::Error
158            detail="VideoToolbox: out of memory"
159        />
160    })
161}
162
163fn render_nav_rail(active: AppSection, extra_count: bool) -> String {
164    let workspace = sample_workspace_badge();
165    let user = sample_user_avatar();
166    let items = sample_nav_items(extra_count);
167    render(view! {
168        <div style="width:88px; height:460px; background:#0b0b0d; border:1px solid var(--line-subtle); border-radius:8px;">
169            <NavigationRail items=items active=active workspace=workspace user=user />
170        </div>
171    })
172}
173
174fn render_nav_record() -> String {
175    render_nav_rail(AppSection::Record, false)
176}
177fn render_nav_library() -> String {
178    render_nav_rail(AppSection::Library, false)
179}
180fn render_nav_editor() -> String {
181    render_nav_rail(AppSection::Editor, false)
182}
183fn render_nav_cursor() -> String {
184    render_nav_rail(AppSection::Cursor, false)
185}
186fn render_nav_with_counts() -> String {
187    render_nav_rail(AppSection::Library, true)
188}
189
190fn render_app_shell_three_pane() -> String {
191    let workspace = sample_workspace_badge();
192    let user = sample_user_avatar();
193    let items = sample_nav_items(false);
194    render(view! {
195        <div class="shell-story">
196            <AppShell
197                rail=ToChildren::to_children(move || view! {
198                    <NavigationRail
199                        items=items
200                        active=AppSection::Editor
201                        workspace=workspace
202                        user=user
203                    />
204                })
205                titlebar=ToChildren::to_children(move || view! {
206                    <span>"screen-studio · editor · Recording 02"</span>
207                })
208                main=ToChildren::to_children(move || view! {
209                    <div class="shell-story-main-placeholder">
210                        "Main pane — editor canvas / library grid / cursor preview goes here"
211                    </div>
212                })
213                inspector=ToChildren::to_children(move || view! {
214                    <div class="shell-story-inspector-placeholder">
215                        <h3 style="font-size:13px;margin:0 0 8px;color:var(--text-primary)">"Inspector"</h3>
216                        <p>"Property rows render here in UI-18."</p>
217                    </div>
218                })
219                footer=ToChildren::to_children(move || view! {
220                    <span>"Encoding · 38%   ·   FPS 60"</span>
221                })
222            />
223        </div>
224    })
225}