leptos_arrow_grid/context_menu.rs
1//! Context menu component for the data grid.
2
3use leptos::prelude::*;
4
5/// Actions available from the context menu.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ContextAction {
8 /// Copy selected rows as TSV.
9 Copy,
10 /// Select all rows.
11 SelectAll,
12 /// Download visible/selected rows as a CSV file.
13 Download,
14}
15
16/// Placement coordinates for the context menu.
17#[derive(Clone, Copy, Debug)]
18pub struct MenuPosition {
19 /// Page X coordinate.
20 pub x: f64,
21 /// Page Y coordinate.
22 pub y: f64,
23}
24
25/// Context menu component rendered inside the grid container.
26///
27/// Positioned at `(x, y)` in page coordinates. Backdrop covers
28/// the full viewport to catch dismiss clicks.
29#[component]
30pub fn GridContextMenu(
31 /// Position of the menu (None = hidden).
32 position: Signal<Option<MenuPosition>>,
33 /// Callback when an action is selected.
34 on_action: Callback<ContextAction>,
35 /// Callback to close the menu.
36 on_close: Callback<()>,
37 /// Number of currently selected rows.
38 selected_count: Signal<usize>,
39) -> impl IntoView {
40 let copy_label = Signal::derive(move || {
41 let count = selected_count.get();
42 if count > 0 {
43 format!("Copy ({count} rows)")
44 } else {
45 "Copy".to_string()
46 }
47 });
48
49 view! {
50 <Show when=move || position.get().is_some()>
51 {move || {
52 let Some(pos) = position.get() else {
53 return view! { <div /> }.into_any();
54 };
55 view! {
56 <div
57 class="dg-context-backdrop"
58 on:pointerdown=move |_| on_close.run(())
59 on:contextmenu=move |ev| {
60 ev.prevent_default();
61 on_close.run(());
62 }
63 />
64 <div
65 class="dg-context-menu"
66 style:left=format!("{}px", pos.x)
67 style:top=format!("{}px", pos.y)
68 >
69 <button
70 class="dg-context-item"
71 on:click=move |_| {
72 on_action.run(ContextAction::Copy);
73 on_close.run(());
74 }
75 >
76 {copy_label}
77 <span class="dg-context-shortcut">{"Ctrl+C"}</span>
78 </button>
79 <button
80 class="dg-context-item"
81 on:click=move |_| {
82 on_action.run(ContextAction::SelectAll);
83 on_close.run(());
84 }
85 >
86 "Select All"
87 <span class="dg-context-shortcut">{"Ctrl+A"}</span>
88 </button>
89 <button
90 class="dg-context-item"
91 on:click=move |_| {
92 on_action.run(ContextAction::Download);
93 on_close.run(());
94 }
95 >
96 "Download CSV"
97 <span class="dg-context-shortcut">{"Ctrl+S"}</span>
98 </button>
99 </div>
100 }.into_any()
101 }}
102 </Show>
103 }
104}