impulse_thaw/table/
mod.rs1use 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}