thaw 0.4.8

An easy to use leptos component library
Documentation
use leptos::{either::Either, ev, html, prelude::*};
use thaw_utils::{class_list, mount_style};

#[component]
pub fn Table(
    #[prop(optional, into)] class: MaybeProp<String>,
    children: Children,
) -> impl IntoView {
    mount_style("table", include_str!("./table.css"));

    view! { <table class=class_list!["thaw-table", class]>{children()}</table> }
}

#[component]
pub fn TableHeader(
    #[prop(optional, into)] class: MaybeProp<String>,
    children: Children,
) -> impl IntoView {
    view! { <thead class=class_list!["thaw-table-header", class]>{children()}</thead> }
}

#[component]
pub fn TableHeaderCell(
    #[prop(optional, into)] class: MaybeProp<String>,
    #[prop(optional, into)] resizable: bool,
    #[prop(optional, into)] min_width: Option<f64>,
    #[prop(optional, into)] max_width: Option<f64>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    let th_ref = NodeRef::<html::Th>::new();
    let mouse_down_x = RwSignal::new(0);
    let mouse_down_th_width = RwSignal::new(0.0);
    let mouse = StoredValue::new(Vec::<WindowListenerHandle>::new());

    let on_mouse_down = move |e: ev::MouseEvent| {
        mouse_down_x.set(e.x());
        let Some(th_el) = th_ref.get_untracked() else {
            return;
        };
        let Ok(Some(css)) = window().get_computed_style(&th_el) else {
            return;
        };
        if let Ok(width) = css.get_property_value("width") {
            let width = web_sys::js_sys::Number::parse_float(&width);
            mouse_down_th_width.set(width);
        }

        let on_mouse_move = window_event_listener(ev::mousemove, move |e: ev::MouseEvent| {
            let mouse_x = e.x();
            let mut new_width = mouse_down_th_width.get_untracked()
                + f64::from(mouse_x - mouse_down_x.get_untracked());

            if let Some(max_width) = max_width {
                if new_width > max_width {
                    new_width = max_width;
                }
            }
            if let Some(min_width) = min_width {
                if new_width < min_width {
                    new_width = min_width;
                }
            }
            if new_width < 0.0 {
                new_width = 0.0;
            }

            if let Some(th_el) = th_ref.get_untracked() {
                let mut style = format!("width: {new_width:.2}px");
                if let Some(max_width) = max_width {
                    style.push_str(&format!(";max-width: {max_width:.2}px"));
                }
                if let Some(min_width) = min_width {
                    style.push_str(&format!(";min-width: {min_width:.2}px"));
                }
                let _ = th_el.set_attribute("style", &style);
            }
        });
        let on_mouse_up = window_event_listener(ev::mouseup, move |_| {
            mouse.update_value(|value| {
                for handle in value.drain(..) {
                    handle.remove();
                }
            });
        });
        mouse.update_value(|value| {
            value.push(on_mouse_move);
            value.push(on_mouse_up);
        });
    };
    on_cleanup(move || {
        mouse.update_value(|value| {
            for handle in value.drain(..) {
                handle.remove();
            }
        });
    });
    view! {
        <th class=class_list!["thaw-table-header-cell", class] node_ref=th_ref>
            <button class="thaw-table-header-cell__button" role="presentation">
                {if let Some(children) = children {
                    Either::Left(children())
                } else {
                    Either::Right(())
                }}
            </button>
            {if resizable {
                Either::Left(
                    view! {
                        <span class="thaw-table-header-cell__aside" on:mousedown=on_mouse_down>
                            <div
                                class="thaw-table-resize-handle"
                                role="separator"
                                aria-hidden="true"
                            ></div>
                        </span>
                    },
                )
            } else {
                Either::Right(())
            }}
        </th>
    }
}

#[component]
pub fn TableBody(
    #[prop(optional, into)] class: MaybeProp<String>,
    children: Children,
) -> impl IntoView {
    view! { <tbody class=class_list!["thaw-table-body", class]>{children()}</tbody> }
}

#[component]
pub fn TableRow(
    #[prop(optional, into)] class: MaybeProp<String>,
    children: Children,
) -> impl IntoView {
    view! { <tr class=class_list!["thaw-table-row", class]>{children()}</tr> }
}

#[component]
pub fn TableCell(
    #[prop(optional, into)] class: MaybeProp<String>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    view! {
        <td class=class_list![
            "thaw-table-cell", class
        ]>
            {if let Some(children) = children {
                Either::Left(children())
            } else {
                Either::Right(())
            }}
        </td>
    }
}

#[component]
pub fn TableCellLayout(
    #[prop(optional, into)] class: MaybeProp<String>,
    #[prop(optional, into)] truncate: Signal<bool>,
    children: Children,
) -> impl IntoView {
    view! {
        <div class=class_list![
            "thaw-table-cell-layout",
            ("thaw-table-cell-layout--truncate", move || truncate.get()),
            class
        ]>
            <div class="thaw-table-cell-layout__content">
                <span class="thaw-table-cell-layout__main">{children()}</span>
            </div>
        </div>
    }
}