ui_storybook/components/cursor/
cursor_studio_shell.rs1use leptos::prelude::*;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum CursorStyle {
10 System,
12 Arrow,
14 Soft,
16 Dot,
18 Ring,
20 Reticle,
22 Tactile,
24 Hide,
26}
27
28impl CursorStyle {
29 #[must_use]
31 pub fn label(self) -> &'static str {
32 match self {
33 CursorStyle::System => "System",
34 CursorStyle::Arrow => "Arrow",
35 CursorStyle::Soft => "Soft",
36 CursorStyle::Dot => "Dot",
37 CursorStyle::Ring => "Ring",
38 CursorStyle::Reticle => "Reticle",
39 CursorStyle::Tactile => "Tactile",
40 CursorStyle::Hide => "Hide",
41 }
42 }
43
44 #[must_use]
46 pub fn slug(self) -> &'static str {
47 match self {
48 CursorStyle::System => "system",
49 CursorStyle::Arrow => "arrow",
50 CursorStyle::Soft => "soft",
51 CursorStyle::Dot => "dot",
52 CursorStyle::Ring => "ring",
53 CursorStyle::Reticle => "reticle",
54 CursorStyle::Tactile => "tactile",
55 CursorStyle::Hide => "hide",
56 }
57 }
58
59 #[must_use]
61 pub fn glyph(self) -> &'static str {
62 match self {
63 CursorStyle::System => "◖",
64 CursorStyle::Arrow => "➤",
65 CursorStyle::Soft => "❥",
66 CursorStyle::Dot => "●",
67 CursorStyle::Ring => "○",
68 CursorStyle::Reticle => "⊕",
69 CursorStyle::Tactile => "◎",
70 CursorStyle::Hide => "∅",
71 }
72 }
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
77pub struct CursorStyleTileView {
78 pub style: CursorStyle,
80 pub label: Option<&'static str>,
82 pub selected: bool,
84 pub disabled: bool,
86}
87
88#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct CursorStylePickerView {
91 pub tiles: Vec<CursorStyleTileView>,
93}
94
95#[component]
96pub fn CursorStyleTile(view: CursorStyleTileView) -> impl IntoView {
97 let style = view.style;
98 let mut class = String::from("cursor-style-tile");
99 if view.selected {
100 class.push_str(" cursor-style-tile-selected");
101 }
102 if view.disabled {
103 class.push_str(" cursor-style-tile-disabled");
104 }
105 let label = view.label.unwrap_or_else(|| style.label());
106 let glyph = style.glyph();
107 let slug = style.slug();
108 let pressed = view.selected;
109 view! {
110 <button class=class data-style=slug aria-pressed=pressed disabled=view.disabled>
111 <span class="cursor-style-tile-preview" aria-hidden="true">{glyph}</span>
112 <span class="cursor-style-tile-label">{label}</span>
113 </button>
114 }
115}
116
117#[component]
118pub fn CursorStylePicker(view: CursorStylePickerView) -> impl IntoView {
119 let tiles: Vec<_> = view
120 .tiles
121 .into_iter()
122 .map(|t| view! { <CursorStyleTile view=t /> })
123 .collect();
124 view! {
125 <section class="cursor-style-picker" aria-label="Cursor style">
126 <span class="cursor-style-heading">"CURSOR STYLE"</span>
127 <div class="cursor-style-tiles">{tiles}</div>
128 </section>
129 }
130}
131
132#[derive(Clone, Debug, PartialEq, Eq)]
134pub struct CursorStudioShellView {
135 pub picker: CursorStylePickerView,
137}
138
139#[component]
140pub fn CursorStudioShell(
141 view: CursorStudioShellView,
143 #[prop(optional)]
145 preview: Option<Children>,
146 #[prop(optional)]
148 inspector: Option<Children>,
149) -> impl IntoView {
150 view! {
151 <section class="cursor-studio-shell" aria-label="Cursor Studio">
152 <div class="cursor-studio-body">
153 <div class="cursor-studio-preview">{preview.map(|p| p())}</div>
154 <aside class="cursor-studio-inspector">{inspector.map(|i| i())}</aside>
155 </div>
156 <CursorStylePicker view=view.picker />
157 </section>
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn cursor_style_slugs_unique() {
167 let styles = [
168 CursorStyle::System,
169 CursorStyle::Arrow,
170 CursorStyle::Soft,
171 CursorStyle::Dot,
172 CursorStyle::Ring,
173 CursorStyle::Reticle,
174 CursorStyle::Tactile,
175 CursorStyle::Hide,
176 ];
177 let slugs: Vec<_> = styles.iter().map(|s| s.slug()).collect();
178 let mut sorted = slugs.clone();
179 sorted.sort_unstable();
180 sorted.dedup();
181 assert_eq!(sorted.len(), slugs.len());
182 }
183
184 #[test]
185 fn cursor_style_label_round_trips() {
186 for s in [CursorStyle::Arrow, CursorStyle::Dot, CursorStyle::Hide] {
187 assert!(!s.label().is_empty());
188 assert!(!s.glyph().is_empty());
189 }
190 }
191}