impulse_thaw/table/
mod.rs

1use leptos::{either::Either, ev, html, prelude::*};
2use thaw_utils::{class_list, mount_style};
3
4#[component]
5pub fn Table(
6    #[prop(optional, into)] class: MaybeProp<String>,
7    children: Children,
8) -> impl IntoView {
9    mount_style("table", include_str!("./table.css"));
10
11    view! { <table class=class_list!["thaw-table", class]>{children()}</table> }
12}
13
14#[component]
15pub fn TableHeader(
16    #[prop(optional, into)] class: MaybeProp<String>,
17    children: Children,
18) -> impl IntoView {
19    view! { <thead class=class_list!["thaw-table-header", class]>{children()}</thead> }
20}
21
22#[component]
23pub fn TableHeaderCell(
24    #[prop(optional, into)] class: MaybeProp<String>,
25    #[prop(optional, into)] resizable: bool,
26    #[prop(optional, into)] min_width: Option<f64>,
27    #[prop(optional, into)] max_width: Option<f64>,
28    #[prop(optional)] children: Option<Children>,
29) -> impl IntoView {
30    let th_ref = NodeRef::<html::Th>::new();
31    let mouse_down_x = RwSignal::new(0);
32    let mouse_down_th_width = RwSignal::new(0.0);
33    let mouse = StoredValue::new(Vec::<WindowListenerHandle>::new());
34
35    let on_mouse_down = move |e: ev::MouseEvent| {
36        mouse_down_x.set(e.x());
37        let Some(th_el) = th_ref.get_untracked() else {
38            return;
39        };
40        let Ok(Some(css)) = window().get_computed_style(&th_el) else {
41            return;
42        };
43        if let Ok(width) = css.get_property_value("width") {
44            let width = web_sys::js_sys::Number::parse_float(&width);
45            mouse_down_th_width.set(width);
46        }
47
48        let on_mouse_move = window_event_listener(ev::mousemove, move |e: ev::MouseEvent| {
49            let mouse_x = e.x();
50            let mut new_width = mouse_down_th_width.get_untracked()
51                + f64::from(mouse_x - mouse_down_x.get_untracked());
52
53            if let Some(max_width) = max_width {
54                if new_width > max_width {
55                    new_width = max_width;
56                }
57            }
58            if let Some(min_width) = min_width {
59                if new_width < min_width {
60                    new_width = min_width;
61                }
62            }
63            if new_width < 0.0 {
64                new_width = 0.0;
65            }
66
67            if let Some(th_el) = th_ref.get_untracked() {
68                let mut style = format!("width: {new_width:.2}px");
69                if let Some(max_width) = max_width {
70                    style.push_str(&format!(";max-width: {max_width:.2}px"));
71                }
72                if let Some(min_width) = min_width {
73                    style.push_str(&format!(";min-width: {min_width:.2}px"));
74                }
75                let _ = th_el.set_attribute("style", &style);
76            }
77        });
78        let on_mouse_up = window_event_listener(ev::mouseup, move |_| {
79            mouse.update_value(|value| {
80                for handle in value.drain(..) {
81                    handle.remove();
82                }
83            });
84        });
85        mouse.update_value(|value| {
86            value.push(on_mouse_move);
87            value.push(on_mouse_up);
88        });
89    };
90    on_cleanup(move || {
91        mouse.update_value(|value| {
92            for handle in value.drain(..) {
93                handle.remove();
94            }
95        });
96    });
97    view! {
98        <th class=class_list!["thaw-table-header-cell", class] node_ref=th_ref>
99            <button class="thaw-table-header-cell__button" role="presentation">
100                {if let Some(children) = children {
101                    Either::Left(children())
102                } else {
103                    Either::Right(())
104                }}
105            </button>
106            {if resizable {
107                Either::Left(
108                    view! {
109                        <span class="thaw-table-header-cell__aside" on:mousedown=on_mouse_down>
110                            <div
111                                class="thaw-table-resize-handle"
112                                role="separator"
113                                aria-hidden="true"
114                            ></div>
115                        </span>
116                    },
117                )
118            } else {
119                Either::Right(())
120            }}
121        </th>
122    }
123}
124
125#[component]
126pub fn TableBody(
127    #[prop(optional, into)] class: MaybeProp<String>,
128    children: Children,
129) -> impl IntoView {
130    view! { <tbody class=class_list!["thaw-table-body", class]>{children()}</tbody> }
131}
132
133#[component]
134pub fn TableRow(
135    #[prop(optional, into)] class: MaybeProp<String>,
136    children: Children,
137) -> impl IntoView {
138    view! { <tr class=class_list!["thaw-table-row", class]>{children()}</tr> }
139}
140
141#[component]
142pub fn TableCell(
143    #[prop(optional, into)] class: MaybeProp<String>,
144    #[prop(optional)] children: Option<Children>,
145) -> impl IntoView {
146    view! {
147        <td class=class_list![
148            "thaw-table-cell", class
149        ]>
150            {if let Some(children) = children {
151                Either::Left(children())
152            } else {
153                Either::Right(())
154            }}
155        </td>
156    }
157}
158
159#[component]
160pub fn TableCellLayout(
161    #[prop(optional, into)] class: MaybeProp<String>,
162    #[prop(optional, into)] truncate: Signal<bool>,
163    children: Children,
164) -> impl IntoView {
165    view! {
166        <div class=class_list![
167            "thaw-table-cell-layout",
168            ("thaw-table-cell-layout--truncate", move || truncate.get()),
169            class
170        ]>
171            <div class="thaw-table-cell-layout__content">
172                <span class="thaw-table-cell-layout__main">{children()}</span>
173            </div>
174        </div>
175    }
176}