htmx_components/server/
table.rs1use rscx::{component, html, props, CollectFragment};
2
3use super::html_element::HtmlElement;
4
5use rscx_web_macros::*;
6
7pub enum TableHeading {
8 Title(String),
9 Empty(String),
10}
11
12impl TableHeading {
13 pub fn title(title: impl Into<String>) -> TableHeading {
14 TableHeading::Title(title.into())
15 }
16 pub fn empty(sr_only_text: impl Into<String>) -> TableHeading {
17 TableHeading::Empty(sr_only_text.into())
18 }
19}
20
21pub type TableHeadings = Vec<TableHeading>;
22
23#[props]
24pub struct TableProps {
25 headings: TableHeadings,
26 body: Vec<String>,
27}
28
29#[component]
30pub fn Table(props: TableProps) -> String {
31 html! {
32 <table class="min-w-full divide-y divide-gray-300">
33 <TableHeadingsRow headings=props.headings />
34 <TableBody body=props.body />
35 </table>
36 }
37}
38
39pub enum TDVariant {
40 Default,
41 First,
42 Last,
43 LastNonEmptyHeading,
44}
45
46#[props]
47pub struct TableDataProps {
48 children: String,
49
50 #[builder(default=TDVariant::Default)]
51 variant: TDVariant,
52}
53
54#[component]
55pub fn TableData(props: TableDataProps) -> String {
56 let td_class = match props.variant {
57 TDVariant::Default => "whitespace-nowrap px-3 py-4 text-sm text-gray-500",
58 TDVariant::First => {
59 "whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6"
60 }
61 TDVariant::Last => {
62 "relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6"
63 }
64 TDVariant::LastNonEmptyHeading => {
65 "whitespace-nowrap py-4 pl-3 pr-4 text-left text-sm font-medium sm:pr-6"
66 }
67 };
68
69 html! {
70 <td class=td_class>{props.children}</td>
71 }
72}
73
74#[component]
75fn TableHeadingsRow(headings: TableHeadings) -> String {
76 html! {
77 <thead class="bg-gray-50">
78 <tr>
79 {headings.iter().enumerate().map(|(i, heading)| {
80 let th_class = match i {
81 0 => "py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6",
83
84 _ if i == headings.len() - 1 => "py-3.5 pl-3 pr-4 text-left text-sm font-semibold text-gray-900 sm:pr-6",
86
87 _ => "px-3 py-3.5 text-left text-sm font-semibold text-gray-900",
89 };
90
91 match heading {
92 TableHeading::Title(heading) => html! {
93 <th scope="col" class=th_class>{heading}</th>
94 },
95 TableHeading::Empty(sr_only_text) => html! {
96 <th scope="col" class=format!("relative {}", th_class)>
97 <span class="sr-only">{sr_only_text}</span>
98 </th>
99 },
100 }
101 }).collect_fragment()}
102 </tr>
103 </thead>
104 }
105}
106
107#[component]
108fn TableBody(body: Vec<String>) -> String {
109 html! {
110 <tbody class="divide-y divide-gray-200 bg-white">
111 {
112 body.iter().map(|row| html! {
113 <tr data-loading-states>{row}</tr>
114 })
115 .collect_fragment()
116 }
117 </tbody>
118 }
119}
120
121#[html_element]
122pub struct ActionLinkProps {
123 children: String,
124 #[builder(setter(into))]
125 sr_text: String,
126}
127
128#[component]
129pub fn ActionLink(props: ActionLinkProps) -> String {
130 html! {
131 <HtmlElement
132 tag="a"
133 class=format!("cursor-pointer text-indigo-600 hover:text-indigo-900, {}", props.class).trim()
134 attrs=spread_attrs!(props | omit(class))
135 >
136 {props.children}<span class="sr-only">{props.sr_text}</span>
137 </HtmlElement>
138 }
139}
140
141#[derive(Clone)]
142pub struct Confirm {
143 pub title: String,
144 pub message: String,
145}
146
147#[html_element]
148pub struct DeleteActionLinkProps {
149 children: String,
150 confirm: Confirm,
151
152 #[builder(setter(into))]
153 sr_text: String,
154
155 #[builder(default = false)]
156 show_loader_on_delete: bool,
157}
158
159#[component]
160pub fn DeleteActionLink(props: DeleteActionLinkProps) -> String {
161 html! {
162 <ActionLink
163 sr_text=props.sr_text
164 attrs=spread_attrs!(props)
165 .set("hx-confirm", props.confirm.title)
166 .set("data-confirm-message", props.confirm.message)
167 .set_if("data-loading-disable", "true".into(), props.show_loader_on_delete)
168 >
169 {if props.show_loader_on_delete {
170 html! {
171 <div class="htmx-indicator inline-flex animate-spin mr-2 items-center justify-center rounded-full w-4 h-4 bg-gradient-to-tr from-gray-500 to-white">
172 <span class="inline h-3 w-3 rounded-full bg-white hover:bg-gray-50"></span>
173 </div>
174 }
175 } else { String::from("") }}
176 {props.children}
177 </ActionLink>
178 }
179}
180
181#[component]
182pub fn TableDataActions(children: String) -> String {
183 html! {
184 <div class="inline-flex gap-4">
185 {children}
186 </div>
187 }
188}