Tailwind classes in OCaml
1open Htmlit
2
3type container_size =
4 | Sm
5 | Md
6 | Lg
7 | Xl
8 | Xl2
9 | Full
10 | Fluid
11
12type layout_type =
13 | Container of container_size * bool * bool
14 | Flex of Tailwind.Flexbox.direction option * Tailwind.Flexbox.justify option * Tailwind.Flexbox.align_items option * Tailwind.Flexbox.wrap option * Tailwind.Size.t option
15 | Grid of Tailwind.Grid.cols option * Tailwind.Grid.rows option * Tailwind.Size.t option * Tailwind.Size.t option * Tailwind.Size.t option * Tailwind.Grid.flow option
16 | Stack of Tailwind.Size.t option * Tailwind.Flexbox.align_items option
17 | Row of Tailwind.Size.t option * Tailwind.Flexbox.justify option * Tailwind.Flexbox.align_items option * bool option
18 | Sidebar of [`Left | `Right] option * Tailwind.Size.t option * bool option * El.html * El.html
19 | Page of El.html option * El.html option * El.html option * El.html
20
21type t = {
22 layout_type: layout_type;
23 classes: Tailwind.t option;
24 attributes: (string * string) list;
25 children: El.html list;
26}
27
28let classes_attr tailwind_classes =
29 At.class' (Tailwind.to_string tailwind_classes)
30
31let container_size_to_class = function
32 | Sm -> Tailwind.Css.make "max-w-sm"
33 | Md -> Tailwind.Css.make "max-w-md"
34 | Lg -> Tailwind.Css.make "max-w-lg"
35 | Xl -> Tailwind.Css.make "max-w-xl"
36 | Xl2 -> Tailwind.Css.make "max-w-2xl"
37 | Full -> Tailwind.Css.make "max-w-full"
38 | Fluid -> Tailwind.Layout.w_full
39
40let container ?size ?(center=true) ?(padding=true) ?classes ?attributes ~children () = {
41 layout_type = Container (
42 (match size with Some s -> s | None -> Lg),
43 center,
44 padding
45 );
46 classes;
47 attributes = (match attributes with Some a -> a | None -> []);
48 children;
49}
50
51let flex ?direction ?justify ?align ?wrap ?gap ?classes ?attributes ~children () = {
52 layout_type = Flex (direction, justify, align, wrap, gap);
53 classes;
54 attributes = (match attributes with Some a -> a | None -> []);
55 children;
56}
57
58let grid ?cols ?rows ?gap ?gap_x ?gap_y ?flow ?classes ?attributes ~children () = {
59 layout_type = Grid (cols, rows, gap, gap_x, gap_y, flow);
60 classes;
61 attributes = (match attributes with Some a -> a | None -> []);
62 children;
63}
64
65let stack ?gap ?align ?classes ?attributes ~children () = {
66 layout_type = Stack (gap, align);
67 classes;
68 attributes = (match attributes with Some a -> a | None -> []);
69 children;
70}
71
72let row ?gap ?justify ?align ?wrap ?classes ?attributes ~children () = {
73 layout_type = Row (gap, justify, align, wrap);
74 classes;
75 attributes = (match attributes with Some a -> a | None -> []);
76 children;
77}
78
79let sidebar ?side ?width ?(collapsible=false) ?classes ?attributes ~sidebar ~content () = {
80 layout_type = Sidebar (side, width, Some collapsible, sidebar, content);
81 classes;
82 attributes = (match attributes with Some a -> a | None -> []);
83 children = [];
84}
85
86let page ?header ?footer ?sidebar ?classes ?attributes ~main () = {
87 layout_type = Page (header, footer, sidebar, main);
88 classes;
89 attributes = (match attributes with Some a -> a | None -> []);
90 children = [];
91}
92
93let to_html layout =
94 let base_classes = match layout.layout_type with
95 | Container (size, center, padding) ->
96 let size_class = container_size_to_class size in
97 let center_class = if center then Tailwind.Css.make "mx-auto" else Tailwind.Css.empty in
98 let padding_class = if padding then Tailwind.Spacing.(to_class (px (Tailwind.Size.rem 1.0))) else Tailwind.Css.empty in
99 Tailwind.Css.tw [size_class; center_class; padding_class]
100
101 | Flex (direction, justify, align, wrap, gap) ->
102 let dir_classes = match direction with Some d -> [Tailwind.Flexbox.(to_class (direction d))] | None -> [] in
103 let justify_classes = match justify with Some j -> [Tailwind.Flexbox.(to_class (justify j))] | None -> [] in
104 let align_classes = match align with Some a -> [Tailwind.Flexbox.(to_class (align_items a))] | None -> [] in
105 let wrap_classes = match wrap with Some w -> [Tailwind.Flexbox.(to_class (wrap w))] | None -> [] in
106 let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in
107 Tailwind.Css.tw ([Tailwind.Display.flex] @ dir_classes @ justify_classes @ align_classes @ wrap_classes @ gap_classes)
108
109 | Grid (cols, rows, gap, gap_x, gap_y, _flow) ->
110 let col_classes = match cols with Some c -> [Tailwind.Grid.(to_class (template_cols c))] | None -> [] in
111 let row_classes = match rows with Some r -> [Tailwind.Grid.(to_class (template_rows r))] | None -> [] in
112 let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in
113 let gap_x_classes = match gap_x with Some g -> [Tailwind.Spacing.(to_class (gap `X g))] | None -> [] in
114 let gap_y_classes = match gap_y with Some g -> [Tailwind.Spacing.(to_class (gap `Y g))] | None -> [] in
115 let flow_classes = [] in
116 Tailwind.Css.tw ([Tailwind.Display.grid] @ col_classes @ row_classes @ gap_classes @ gap_x_classes @ gap_y_classes @ flow_classes)
117
118 | Stack (gap, align) ->
119 let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in
120 let align_classes = match align with Some a -> [Tailwind.Flexbox.(to_class (align_items a))] | None -> [] in
121 Tailwind.Css.tw ([Tailwind.Display.flex; Tailwind.Flexbox.(to_class (direction `Col))] @ gap_classes @ align_classes)
122
123 | Row (gap, justify, align, wrap) ->
124 let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in
125 let justify_classes = match justify with Some j -> [Tailwind.Flexbox.(to_class (justify j))] | None -> [] in
126 let align_classes = match align with Some a -> [Tailwind.Flexbox.(to_class (align_items a))] | None -> [] in
127 let wrap_classes = match wrap with Some true -> [Tailwind.Flexbox.(to_class (wrap `Wrap))] | Some false -> [Tailwind.Flexbox.(to_class (wrap `Nowrap))] | None -> [] in
128 Tailwind.Css.tw ([Tailwind.Display.flex; Tailwind.Flexbox.(to_class (direction `Row))] @ gap_classes @ justify_classes @ align_classes @ wrap_classes)
129
130 | Sidebar (_side, _width, _collapsible, _sidebar_content, _main_content) ->
131 Tailwind.Css.tw [Tailwind.Display.flex]
132
133 | Page (_header, _footer, _sidebar, _main) ->
134 Tailwind.Css.tw [Tailwind.Display.flex; Tailwind.Flexbox.(to_class (direction `Col)); Tailwind.Layout.h_screen]
135 in
136
137 let final_classes = Tailwind.Css.tw [
138 base_classes;
139 (match layout.classes with Some c -> c | None -> Tailwind.Css.empty);
140 ] in
141
142 let base_attrs = [classes_attr final_classes] in
143 let custom_attrs = List.map (fun (k, v) -> At.v k v) layout.attributes in
144 let all_attrs = base_attrs @ custom_attrs in
145
146 match layout.layout_type with
147 | Sidebar (_side, width, _collapsible, sidebar_content, main_content) ->
148 let sidebar_width = match width with Some w -> w | None -> Tailwind.Size.rem 16.0 in
149 let sidebar_classes = Tailwind.Css.tw [
150 Tailwind.Layout.(to_class (width sidebar_width));
151 Tailwind.Flexbox.(to_class (shrink (Some 0)));
152 Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V50 ());
153 ] in
154 let main_classes = Tailwind.Css.tw [
155 Tailwind.Flexbox.(to_class (grow (Some 1)));
156 Tailwind.Layout.(to_class (overflow `All `Auto));
157 ] in
158 El.div ~at:all_attrs [
159 El.div ~at:[classes_attr sidebar_classes] [sidebar_content];
160 El.div ~at:[classes_attr main_classes] [main_content];
161 ]
162
163 | Page (header, footer, sidebar, main) ->
164 let content = List.filter_map (fun x -> x) [
165 header;
166 (match sidebar with
167 | Some sb -> Some (El.div [sb; main])
168 | None -> Some main);
169 footer;
170 ] in
171 El.div ~at:all_attrs content
172
173 | _ -> El.div ~at:all_attrs layout.children
174
175let spacer ?size () =
176 let size_class = match size with
177 | Some s -> Tailwind.Layout.(to_class (height s))
178 | None -> Tailwind.Layout.(to_class (height (Tailwind.Size.rem 1.0)))
179 in
180 El.div ~at:[classes_attr (Tailwind.Css.tw [size_class])] []
181
182let divider ?orientation ?classes () =
183 let base_classes = match orientation with
184 | Some `Vertical -> [
185 Tailwind.Layout.(to_class (width `Px));
186 Tailwind.Layout.h_full;
187 Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V300 ());
188 ]
189 | Some `Horizontal | None -> [
190 Tailwind.Layout.w_full;
191 Tailwind.Layout.(to_class (height `Px));
192 Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V300 ());
193 ]
194 in
195 let divider_classes = Tailwind.Css.tw (base_classes @
196 (match classes with Some c -> [c] | None -> [])) in
197 El.div ~at:[classes_attr divider_classes] []