#![allow(unreachable_pub, dead_code)]
use leptos::prelude::*;
use crate::ui::scroll_area::scroll_area_boundary::ScrollArea;
use std::sync::Arc;
use canonrs_core::primitives::{
DataTableBulkBarPrimitive,
DataTablePrimitive, DataTableToolbarPrimitive,
DataTableTablePrimitive, DataTableHeadPrimitive, DataTableHeadRowPrimitive,
DataTableHeadCellPrimitive, DataTableBodyPrimitive, DataTableRowPrimitive,
DataTableCellPrimitive, DataTableEmptyPrimitive,
DataTableColgroupPrimitive, DataTableColPrimitive,
DataTableExpandHeadCellPrimitive, DataTableExpandCellPrimitive,
DataTableExpandBtnPrimitive, DataTableExpandRowPrimitive,
DataTableDensity, SortDirection,
};
use crate::ui::dropdown_menu::{
DropdownMenu, DropdownMenuItem, DropdownMenuCheckboxItem,
};
use super::types::{RowIdFn, RowLabelFn, ExpandRenderFn};
use crate::ui::context_menu::context_menu_boundary::{
ContextMenuContent, ContextMenuItem,
};
#[derive(Clone)]
pub struct DataTableColumn<T> {
pub key: String,
pub label: String,
pub render: Arc<dyn Fn(&T) -> String + Send + Sync>,
}
impl<T> DataTableColumn<T> {
pub fn new(key: impl Into<String>, label: impl Into<String>, render: impl Fn(&T) -> String + Send + Sync + 'static) -> Self {
Self { key: key.into(), label: label.into(), render: Arc::new(render) }
}
}
#[derive(Clone)]
pub struct BulkAction {
pub id: &'static str,
pub label: &'static str,
pub danger: bool,
}
impl BulkAction {
pub fn new(id: &'static str, label: &'static str) -> Self {
Self { id, label, danger: false }
}
pub fn danger(mut self) -> Self { self.danger = true; self }
}
#[derive(Clone)]
pub struct RowAction {
pub id: &'static str,
pub label: &'static str,
pub danger: bool,
pub inline: bool,
}
impl RowAction {
pub fn new(id: &'static str, label: &'static str) -> Self {
Self { id, label, danger: false, inline: false }
}
pub fn danger(mut self) -> Self { self.danger = true; self }
pub fn inline(mut self) -> Self { self.inline = true; self }
}
#[component]
pub fn DataTableStatic<T>(
data: Vec<T>,
columns: Vec<DataTableColumn<T>>,
#[prop(default = DataTableDensity::default())] density: DataTableDensity,
#[prop(into, default = String::new())] class: String,
#[prop(default = 10)] page_size: usize,
#[prop(default = false)] selectable: bool,
#[prop(into, default = String::new())] sync_chart: String,
#[prop(into, default = String::new())] sync_scope: String,
#[prop(default = false)] show_density: bool,
#[prop(default = false)] resizable: bool,
#[prop(default = ExpandRenderFn::default())] expand_render: ExpandRenderFn<T>,
#[prop(default = vec![])] row_actions: Vec<RowAction>,
#[prop(default = vec![])] bulk_actions: Vec<BulkAction>,
#[prop(default = RowIdFn::default())] row_id_fn: RowIdFn<T>,
#[prop(default = RowLabelFn::default())] row_label_fn: RowLabelFn<T>,
) -> impl IntoView
where
T: Clone + Send + Sync + 'static,
{
let total = data.len();
let total_pages = ((total as f64) / (page_size as f64)).ceil().max(1.0) as usize;
let has_expand = expand_render.0.is_some();
let col_count = columns.len()
+ if selectable { 1 } else { 0 }
+ if has_expand { 1 } else { 0 }
+ if !row_actions.is_empty() { 1 } else { 0 };
let visible_data = StoredValue::new(data.into_iter().enumerate().collect::<Vec<_>>());
let cols = StoredValue::new(columns.clone());
let expand_render = StoredValue::new(expand_render.0);
let row_actions = StoredValue::new(row_actions);
let bulk_actions = StoredValue::new(bulk_actions);
let row_id_fn = StoredValue::new(row_id_fn.0);
let row_label_fn = StoredValue::new(row_label_fn.0);
let initial_density = density.as_str();
view! {
<DataTablePrimitive
density=density
class=class
attr:data-rs-page-size=page_size.to_string()
attr:data-rs-current-page="1"
attr:data-rs-total-pages=total_pages.to_string()
attr:data-rs-selectable={selectable.then(|| "true")}
attr:data-rs-chart-sync=sync_chart.clone()
attr:data-rs-chart-sync-scope=sync_scope.clone()
>
<DataTableBulkBarPrimitive>
<span data-rs-datatable-bulk-count="">"0 selected"</span>
<div data-rs-datatable-bulk-actions="">
{bulk_actions.get_value().into_iter().map(|action| {
view! {
<button
type="button"
data-rs-datatable-bulk-action=action.id
class={if action.danger { "danger".to_string() } else { String::new() }}
>
{action.label}
</button>
}
}).collect::<Vec<_>>()}
</div>
<button type="button" data-rs-datatable-bulk-clear="">"✕ Clear"</button>
</DataTableBulkBarPrimitive>
<DataTableToolbarPrimitive>
<input type="text" data-rs-datatable-filter="" placeholder="Search..." />
<div data-rs-datatable-density-toggle="" hidden=(!show_density)>
<button type="button" data-rs-density-btn="compact"
data-active={if initial_density == "compact" { "true" } else { "false" }}>
"Compact"
</button>
<button type="button" data-rs-density-btn="comfortable"
data-active={if initial_density == "comfortable" { "true" } else { "false" }}>
"Comfortable"
</button>
<button type="button" data-rs-density-btn="spacious"
data-active={if initial_density == "spacious" { "true" } else { "false" }}>
"Spacious"
</button>
</div>
<DropdownMenu trigger_label="Columns">
{columns.iter().enumerate().map(|(idx, col)| {
let label = col.label.clone();
view! {
<DropdownMenuCheckboxItem attr:data-rs-col-index=idx.to_string()>
{label}
</DropdownMenuCheckboxItem>
}
}).collect::<Vec<_>>()}
</DropdownMenu>
</DataTableToolbarPrimitive>
<ScrollArea orientation=canonrs_core::primitives::ScrollOrientation::Horizontal auto_hide=false>
<DataTableTablePrimitive resizable=resizable>
<DataTableColgroupPrimitive>
<col data-rs-datatable-col="" data-rs-col-expand="" class=if has_expand { "" } else { "rs-col-hidden" } />
<col data-rs-datatable-col="" data-rs-col-select="" class=if selectable { "" } else { "rs-col-hidden" } />
{cols.get_value().into_iter().enumerate().map(|(idx, _)| {
view! { <DataTableColPrimitive col_index=idx.to_string() /> }
}).collect::<Vec<_>>()}
<col data-rs-datatable-col="" data-rs-col-actions="" class=if !row_actions.get_value().is_empty() { "" } else { "rs-col-hidden" } />
</DataTableColgroupPrimitive>
<DataTableHeadPrimitive>
<DataTableHeadRowPrimitive>
<DataTableExpandHeadCellPrimitive class=if has_expand { "" } else { "rs-col-hidden" } />
<th data-rs-datatable-head-cell="" scope="col" data-rs-col-select="" class=if selectable { "" } else { "rs-col-hidden" }>
<input type="checkbox" data-rs-datatable-select-all="" />
</th>
{cols.get_value().into_iter().enumerate().map(|(idx, col)| {
let key = col.key.clone();
let label = col.label.clone();
view! {
<DataTableHeadCellPrimitive
sort_key=key
sort_direction=SortDirection::None
col_index=idx.to_string()
resizable=resizable
>
<span data-rs-datatable-head-label="">{label}</span>
<span data-rs-datatable-sort-icon="" aria-hidden="true">"↕"</span>
</DataTableHeadCellPrimitive>
}
}).collect::<Vec<_>>()}
</DataTableHeadRowPrimitive>
</DataTableHeadPrimitive>
<DataTableBodyPrimitive>
{visible_data.get_value().into_iter().map(|(idx, row)| {
let row_cols = cols.get_value();
let expand_content: Option<AnyView> = expand_render.get_value().as_ref().map(|f| f(&row));
let has_expand_row = expand_content.is_some();
let has_actions = !row_actions.get_value().is_empty();
let ctx_actions = row_actions.get_value();
let row_label = row_label_fn.get_value().as_ref().map(|f| f(&row)).unwrap_or_default();
let real_id = row_id_fn.get_value().as_ref().map(|f| f(&row)).unwrap_or_else(|| idx.to_string());
let real_id = StoredValue::new(real_id);
let ctx_row_id = real_id;
let main_row = view! {
<DataTableRowPrimitive row_id=real_id.get_value() row_label=row_label row_index=idx>
{
let rid = real_id.get_value();
view! {
<DataTableExpandCellPrimitive row_id=rid.clone() class=if has_expand { "" } else { "rs-col-hidden" }>
{if has_expand { Some(view! { <DataTableExpandBtnPrimitive row_id=rid /> }) } else { None }}
</DataTableExpandCellPrimitive>
}
}
<td data-rs-datatable-cell="" data-rs-col-select="" class=if selectable { "" } else { "rs-col-hidden" }>
<input type="checkbox" data-rs-datatable-select-row="" value=idx.to_string() />
</td>
{row_cols.into_iter().enumerate().map(|(col_idx, col)| {
let value = (col.render)(&row);
view! {
<DataTableCellPrimitive col_index=col_idx.to_string()>
{value}
</DataTableCellPrimitive>
}
}).collect::<Vec<_>>()}
{
let actions = row_actions.get_value();
let has_actions_cell = !actions.is_empty();
let row_id = ctx_row_id.get_value();
let inline_actions: Vec<RowAction> = actions.iter().filter(|a| a.inline).cloned().collect();
let menu_actions: Vec<RowAction> = actions.iter().filter(|a| !a.inline).cloned().collect();
let has_menu = !menu_actions.is_empty();
let inline_views = inline_actions.into_iter().map(|action| {
let rid = row_id.clone();
view! {
<button type="button"
data-rs-datatable-action=action.id
data-rs-row-id=rid
data-rs-datatable-inline-action=""
class={if action.danger { "danger".to_string() } else { String::new() }}
>
{action.label}
</button>
}
}).collect::<Vec<_>>();
let menu_views = menu_actions.into_iter().map(|action| {
let rid = row_id.clone();
view! {
<DropdownMenuItem
class={if action.danger { "danger".to_string() } else { String::new() }}
>
<span data-rs-datatable-action=action.id data-rs-row-id=rid>
{action.label}
</span>
</DropdownMenuItem>
}
}).collect::<Vec<_>>();
view! {
<td data-rs-datatable-cell="" data-rs-col-actions="" class=if has_actions_cell { "" } else { "rs-col-hidden" }>
<div data-rs-datatable-actions-cell="">
<div data-rs-datatable-inline-actions="">{inline_views}</div>
<div data-rs-datatable-menu-actions="" hidden=(!has_menu)>
<DropdownMenu>{menu_views}</DropdownMenu>
</div>
</div>
</td>
}
}
</DataTableRowPrimitive>
};
view! {
<>
{main_row}
{expand_content.map(|content| {
let rid = real_id.get_value();
view! {
<DataTableExpandRowPrimitive row_id=rid colspan=col_count.to_string()>
{content}
</DataTableExpandRowPrimitive>
}
})}
</>
}
}).collect::<Vec<_>>()}
</DataTableBodyPrimitive>
</DataTableTablePrimitive></ScrollArea>
<div data-rs-datatable-context-menus="">
{visible_data.get_value().into_iter().map(|(idx, row)| {
let has_actions = !row_actions.get_value().is_empty();
let ctx_actions = row_actions.get_value();
let real_id2 = row_id_fn.get_value().as_ref().map(|f| f(&row)).unwrap_or_else(|| idx.to_string());
let ctx_row_id2 = real_id2.clone();
view! {
<div data-rs-datatable-row-context="" data-rs-context-menu=""
data-rs-row-id=ctx_row_id2 hidden=(!has_actions)>
<ContextMenuContent>
{ctx_actions.into_iter().map(|action| {
let rid2 = real_id2.clone();
view! {
<ContextMenuItem>
<span data-rs-datatable-action=action.id data-rs-row-id=rid2>
{action.label}
</span>
</ContextMenuItem>
}
}).collect::<Vec<_>>()}
</ContextMenuContent>
</div>
}
}).collect::<Vec<_>>()}
</div>
<DataTableEmptyPrimitive class="hidden".to_string()>
"No results found."
</DataTableEmptyPrimitive>
<div data-rs-datatable-pagination="">
<button type="button" data-rs-action="prev" data-rs-datatable-pagination-btn="" disabled=true>
"Previous"
</button>
<span data-rs-pagination-info="">{format!("1 of {}", total_pages)}</span>
<button type="button" data-rs-action="next" data-rs-datatable-pagination-btn="" disabled={total_pages <= 1}>
"Next"
</button>
</div>
</DataTablePrimitive>
}
}