mod core;
mod dialog;
mod empty;
mod filterbar;
mod head;
mod pager;
mod rows;
mod skeleton;
mod toolbar;
mod widths;
use topcoat::{
context::Cx,
runtime::Event,
view::{Attributes, StaticClass, attributes, class},
};
use super::{
WiredTable,
column::ColumnWidth,
state::{TableSignals, TableState, query_of, with_return},
};
pub(super) struct Frame<'t> {
columns: Vec<ColumnHead<'t>>,
search: bool,
filter_bar: bool,
delete_prefix: Option<&'t str>,
edit_prefix: Option<&'t str>,
view_prefix: Option<&'t str>,
actions_prefix: Option<&'t str>,
bulk_delete: bool,
bulk_actions: Vec<BulkAction<'t>>,
row_actions: usize,
return_to: Option<&'t str>,
}
struct ColumnHead<'t> {
name: &'t str,
label: &'t str,
sortable: bool,
width: ColumnWidth,
}
struct BulkAction<'t> {
name: &'static str,
label: &'t str,
confirm: bool,
}
impl<M> WiredTable<M> {
pub(super) fn frame(&self) -> Frame<'_>
where
M: toasty::schema::Model,
{
Frame {
columns: self
.columns
.iter()
.map(|col| ColumnHead {
name: col.name(),
label: col.label(),
sortable: col.is_sortable(),
width: col.column_width(),
})
.collect(),
search: self.search_enabled(),
filter_bar: self.filter_bar_enabled(),
delete_prefix: self.delete_prefix(),
edit_prefix: self.edit_prefix(),
view_prefix: self.view_prefix(),
actions_prefix: self.actions_prefix(),
bulk_delete: self.bulk_delete_enabled(),
bulk_actions: self
.bulk_custom_actions()
.map(|action| BulkAction {
name: action.name,
label: &action.label,
confirm: action.confirm,
})
.collect(),
row_actions: self.row_custom_actions().count(),
return_to: self.return_to(),
}
}
}
impl Frame<'_> {
fn bulk_enabled(&self) -> bool {
self.bulk_delete || !self.bulk_actions.is_empty()
}
fn action_url(&self, url: String) -> String {
match self.return_to {
Some(target) => with_return(&url, target),
None => url,
}
}
}
const BAR_CLASS: StaticClass =
class!("flex flex-wrap items-center gap-2 border-b border-border p-3");
const SEARCH_FIELD_CLASS: StaticClass = class!("relative w-full sm:w-72");
const SEARCH_ICON_CLASS: StaticClass = class!(
"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
);
pub(crate) const TABLE_CARD_CLASS: StaticClass = class!(
"overflow-hidden rounded-xl border border-border bg-card text-card-foreground shadow-sm"
);
const QUIET_LINK_CLASS: StaticClass = class!("text-sm text-muted-foreground hover:text-foreground");
const EMPTY_LINK_CLASS: StaticClass =
class!("text-sm font-medium text-foreground underline underline-offset-4");
pub(super) fn table_dom_id(state: &TableState, suffix: &str) -> String {
format!("{}-{suffix}", state.prefix.as_deref().unwrap_or("table"))
}
pub(super) fn live_link(cx: &Cx, url: String, signals: &TableSignals) -> Attributes {
let query = signals.query.clone();
let next = query_of(&url).to_string();
attributes! {
cx =>
href=(url)
@click=$(|e: Event| {
if e.ctrl_key {
return;
}
if e.meta_key {
return;
}
if e.shift_key {
return;
}
if e.alt_key {
return;
}
e.prevent_default();
query.set(next.clone());
})
}
}