tablo_core/schema/
layouts.rs1use tablo_ui::{card_content, card_header, card_title, field_group as ui_field_group};
4use topcoat::{
5 Result,
6 context::Cx,
7 view::{StaticClass, class, *},
8};
9
10use super::{
11 Schema,
12 fields::Field,
13 tree::{IntoSchema, Source, render_nodes},
14};
15
16const PANEL: StaticClass = class!(
18 "flex flex-col gap-5 rounded-xl border border-border bg-card py-6 text-card-foreground shadow-sm"
19);
20
21#[derive(Debug)]
23pub struct Section {
24 title: String,
25 pub(crate) children: Schema,
26 extra_class: Option<String>,
27}
28
29impl Section {
30 pub fn new(title: impl Into<String>) -> Self {
31 Self {
32 title: title.into(),
33 children: Schema::empty(),
34 extra_class: None,
35 }
36 }
37
38 pub fn schema(mut self, children: impl IntoSchema) -> Self {
39 self.children = children.into_schema();
40 self
41 }
42
43 pub fn class(mut self, class: impl Into<String>) -> Self {
45 self.extra_class = Some(class.into());
46 self
47 }
48
49 pub(crate) async fn render<'a>(
50 &self,
51 cx: &'a Cx,
52 fields: &[Field],
53 source: &Source<'_>,
54 ) -> Result<BoxView<'a>> {
55 let title = self.title.clone();
56 let extra = self.extra_class.clone();
57 if !self.children.nodes.is_empty() {
58 let child_view = render_nodes(cx, &self.children.nodes, fields, source).await?;
59 Ok(view! {
60 cx =>
61 <div class=(class!(PANEL, extra.clone()))>
62 card_header(card_title((title)))
63 card_content(
64 attrs: attributes! { class="flex flex-col gap-6" },
65 (child_view)
66 )
67 </div>
68 }
69 .boxed())
70 } else {
71 Ok(view! {
72 cx =>
73 <div class=(class!(PANEL, extra.clone()))>
74 card_header(card_title((title)))
75 </div>
76 }
77 .boxed())
78 }
79 }
80}
81
82#[derive(Debug, Default)]
84pub struct Group {
85 pub(crate) children: Schema,
86}
87
88impl Group {
89 pub fn new() -> Self {
90 Self::default()
91 }
92
93 pub fn schema(mut self, children: impl IntoSchema) -> Self {
94 self.children = children.into_schema();
95 self
96 }
97
98 pub(crate) async fn render<'a>(
99 &self,
100 cx: &'a Cx,
101 fields: &[Field],
102 source: &Source<'_>,
103 ) -> Result<BoxView<'a>> {
104 let child_view = render_nodes(cx, &self.children.nodes, fields, source).await?;
105 Ok(view! { cx => ui_field_group((child_view)) }.boxed())
106 }
107}
108
109#[derive(Debug)]
111pub struct Grid {
112 cols: u8,
113 pub(crate) children: Schema,
114}
115
116impl Grid {
117 pub fn new(cols: u8) -> Self {
118 Self {
119 cols: cols.clamp(1, 12),
120 children: Schema::empty(),
121 }
122 }
123
124 pub fn schema(mut self, children: impl IntoSchema) -> Self {
125 self.children = children.into_schema();
126 self
127 }
128
129 pub(crate) async fn render<'a>(
130 &self,
131 cx: &'a Cx,
132 fields: &[Field],
133 source: &Source<'_>,
134 ) -> Result<BoxView<'a>> {
135 let class: &'static str = match self.cols {
138 1 => "grid grid-cols-1 gap-4",
139 2 => "grid grid-cols-2 gap-4",
140 3 => "grid grid-cols-3 gap-4",
141 4 => "grid grid-cols-4 gap-4",
142 5 => "grid grid-cols-5 gap-4",
143 6 => "grid grid-cols-6 gap-4",
144 7 => "grid grid-cols-7 gap-4",
145 8 => "grid grid-cols-8 gap-4",
146 9 => "grid grid-cols-9 gap-4",
147 10 => "grid grid-cols-10 gap-4",
148 11 => "grid grid-cols-11 gap-4",
149 _ => "grid grid-cols-12 gap-4",
150 };
151 let child_view = render_nodes(cx, &self.children.nodes, fields, source).await?;
152 Ok(view! { cx => <div class=(class)>(child_view)</div> }.boxed())
153 }
154}
155
156#[cfg(test)]
157mod tests;