Tailwind classes in OCaml
1(* GADT-based Tailwind HTML library with heterogeneous lists *)
2
3(* Color utilities *)
4let blue variant = Tailwind.Color.make `Blue ~variant:(match variant with
5 | 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
6 | 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
7 | _ -> `V600) ()
8
9let gray variant = Tailwind.Color.make `Gray ~variant:(match variant with
10 | 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
11 | 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
12 | _ -> `V600) ()
13
14let red variant = Tailwind.Color.make `Red ~variant:(match variant with
15 | 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
16 | 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
17 | _ -> `V600) ()
18
19let green variant = Tailwind.Color.make `Green ~variant:(match variant with
20 | 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
21 | 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
22 | _ -> `V600) ()
23
24let yellow variant = Tailwind.Color.make `Yellow ~variant:(match variant with
25 | 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
26 | 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
27 | _ -> `V600) ()
28
29let indigo variant = Tailwind.Color.make `Indigo ~variant:(match variant with
30 | 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
31 | 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
32 | _ -> `V600) ()
33
34let purple variant = Tailwind.Color.make `Purple ~variant:(match variant with
35 | 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
36 | 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
37 | _ -> `V600) ()
38
39let pink variant = Tailwind.Color.make `Pink ~variant:(match variant with
40 | 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
41 | 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
42 | _ -> `V600) ()
43
44let rem f = Tailwind.Size.rem f
45let px = Tailwind.Size.px
46let zero = Tailwind.Size.zero
47let auto = Tailwind.Size.auto
48let full = Tailwind.Size.full
49let screen = Tailwind.Size.screen
50let txt s = Htmlit.El.txt s
51
52(* GADT for Tailwind properties with types indicating their category *)
53type _ tw_prop =
54 | Text_color : Tailwind.Color.t -> [`Text_color] tw_prop
55 | Bg_color : Tailwind.Color.t -> [`Bg_color] tw_prop
56 | Font_size : Tailwind.Typography.font_size -> [`Font_size] tw_prop
57 | Font_weight : Tailwind.Typography.font_weight -> [`Font_weight] tw_prop
58 | Margin : Tailwind.Size.t -> [`Margin] tw_prop
59 | Margin_x : Tailwind.Size.t -> [`Margin] tw_prop
60 | Margin_y : Tailwind.Size.t -> [`Margin] tw_prop
61 | Margin_top : Tailwind.Size.t -> [`Margin] tw_prop
62 | Margin_bottom : Tailwind.Size.t -> [`Margin] tw_prop
63 | Margin_left : Tailwind.Size.t -> [`Margin] tw_prop
64 | Margin_right : Tailwind.Size.t -> [`Margin] tw_prop
65 | Padding : Tailwind.Size.t -> [`Padding] tw_prop
66 | Padding_x : Tailwind.Size.t -> [`Padding] tw_prop
67 | Padding_y : Tailwind.Size.t -> [`Padding] tw_prop
68 | Width : Tailwind.Size.t -> [`Width] tw_prop
69 | Height : Tailwind.Size.t -> [`Height] tw_prop
70 | Max_width : Tailwind.Size.t -> [`Width] tw_prop
71 | Min_height : Tailwind.Size.t -> [`Height] tw_prop
72 | Display_flex : [`Layout] tw_prop
73 | Display_block : [`Layout] tw_prop
74 | Display_inline : [`Layout] tw_prop
75 | Display_inline_block : [`Layout] tw_prop
76 | Items_center : [`Layout] tw_prop
77 | Items_start : [`Layout] tw_prop
78 | Items_end : [`Layout] tw_prop
79 | Justify_center : [`Layout] tw_prop
80 | Justify_between : [`Layout] tw_prop
81 | Justify_start : [`Layout] tw_prop
82 | Justify_end : [`Layout] tw_prop
83 | Flex_col : [`Layout] tw_prop
84 | Flex_row : [`Layout] tw_prop
85 | Text_center : [`Layout] tw_prop
86 | Text_left : [`Layout] tw_prop
87 | Text_right : [`Layout] tw_prop
88 | Rounded : [< `Sm | `Md | `Lg | `Full ] -> [`Effects] tw_prop
89 | Shadow : [< `Sm | `Md | `Lg ] -> [`Effects] tw_prop
90 | Border : [`Effects] tw_prop
91 | Border_color : Tailwind.Color.t -> [`Effects] tw_prop
92 | Transition : [`Effects] tw_prop
93
94(* Heterogeneous list *)
95type tw_list = tw_list_item list
96and tw_list_item = Any : 'a tw_prop -> tw_list_item
97
98(* Convert GADT properties to Tailwind classes *)
99let to_tailwind_classes (props : tw_list) : Tailwind.t list =
100 let convert_prop : type a. a tw_prop -> Tailwind.t = function
101 | Text_color color -> Tailwind.Color.text color
102 | Bg_color color -> Tailwind.Color.bg color
103 | Font_size size -> Tailwind.Typography.(to_class (font_size size))
104 | Font_weight weight -> Tailwind.Typography.(to_class (font_weight weight))
105 | Margin size -> Tailwind.Spacing.(to_class (m size))
106 | Margin_x size -> Tailwind.Spacing.(to_class (mx size))
107 | Margin_y size -> Tailwind.Spacing.(to_class (my size))
108 | Margin_top size -> Tailwind.Spacing.(to_class (mt size))
109 | Margin_bottom size -> Tailwind.Spacing.(to_class (mb size))
110 | Margin_left size -> Tailwind.Spacing.(to_class (ml size))
111 | Margin_right size -> Tailwind.Spacing.(to_class (mr size))
112 | Padding size -> Tailwind.Spacing.(to_class (p size))
113 | Padding_x size -> Tailwind.Spacing.(to_class (px size))
114 | Padding_y size -> Tailwind.Spacing.(to_class (py size))
115 | Width size -> Tailwind.Layout.(to_class (width size))
116 | Height size -> Tailwind.Layout.(to_class (height size))
117 | Max_width size -> Tailwind.Layout.(to_class (max_width size))
118 | Min_height size -> Tailwind.Layout.(to_class (min_height size))
119 | Display_flex -> Tailwind.Display.flex
120 | Display_block -> Tailwind.Display.block
121 | Display_inline -> Tailwind.Display.inline
122 | Display_inline_block -> Tailwind.Display.inline_block
123 | Items_center -> Tailwind.Flexbox.(to_class (align_items `Center))
124 | Items_start -> Tailwind.Flexbox.(to_class (align_items `Start))
125 | Items_end -> Tailwind.Flexbox.(to_class (align_items `End))
126 | Justify_center -> Tailwind.Flexbox.(to_class (justify `Center))
127 | Justify_between -> Tailwind.Flexbox.(to_class (justify `Between))
128 | Justify_start -> Tailwind.Flexbox.(to_class (justify `Start))
129 | Justify_end -> Tailwind.Flexbox.(to_class (justify `End))
130 | Flex_col -> Tailwind.Flexbox.(to_class (direction `Col))
131 | Flex_row -> Tailwind.Flexbox.(to_class (direction `Row))
132 | Text_center -> Tailwind.Typography.(to_class (text_align `Center))
133 | Text_left -> Tailwind.Typography.(to_class (text_align `Left))
134 | Text_right -> Tailwind.Typography.(to_class (text_align `Right))
135 | Rounded radius ->
136 (match radius with
137 | `Sm -> Tailwind.Effects.rounded_sm
138 | `Md -> Tailwind.Effects.rounded_md
139 | `Lg -> Tailwind.Effects.rounded_lg
140 | `Full -> Tailwind.Effects.rounded_full)
141 | Shadow size ->
142 (match size with
143 | `Sm -> Tailwind.Effects.shadow_sm
144 | `Md -> Tailwind.Effects.shadow_md
145 | `Lg -> Tailwind.Effects.shadow_lg)
146 | Border -> Tailwind.Effects.border
147 | Border_color color -> Tailwind.Color.border color
148 | Transition -> Tailwind.Effects.transition `All
149 in
150 List.map (fun (Any prop) -> convert_prop prop) props
151
152(* Convert heterogeneous list to Tailwind.t *)
153let styles props =
154 Tailwind.Css.tw (to_tailwind_classes props)
155
156(* Helper for HTML class attribute *)
157let classes_attr props =
158 Htmlit.At.class' (Tailwind.to_string (styles props))
159
160(* Helper constructors for convenient usage *)
161let text_color c = Any (Text_color c)
162let bg_color c = Any (Bg_color c)
163let font_size s = Any (Font_size s)
164let font_weight w = Any (Font_weight w)
165let margin s = Any (Margin s)
166let margin_x s = Any (Margin_x s)
167let margin_y s = Any (Margin_y s)
168let margin_top s = Any (Margin_top s)
169let margin_bottom s = Any (Margin_bottom s)
170let margin_left s = Any (Margin_left s)
171let margin_right s = Any (Margin_right s)
172let padding s = Any (Padding s)
173let padding_x s = Any (Padding_x s)
174let padding_y s = Any (Padding_y s)
175let width s = Any (Width s)
176let height s = Any (Height s)
177let max_width s = Any (Max_width s)
178let min_height s = Any (Min_height s)
179let flex = Any Display_flex
180let block = Any Display_block
181let inline = Any Display_inline
182let inline_block = Any Display_inline_block
183let items_center = Any Items_center
184let items_start = Any Items_start
185let items_end = Any Items_end
186let justify_center = Any Justify_center
187let justify_between = Any Justify_between
188let justify_start = Any Justify_start
189let justify_end = Any Justify_end
190let flex_col = Any Flex_col
191let flex_row = Any Flex_row
192let text_center = Any Text_center
193let text_left = Any Text_left
194let text_right = Any Text_right
195let rounded r = Any (Rounded r)
196let shadow s = Any (Shadow s)
197let border = Any Border
198let border_color c = Any (Border_color c)
199let transition = Any Transition
200
201(* GADT-based element functions *)
202let h1 ?styles children =
203 let attrs = match styles with
204 | Some s -> [classes_attr s]
205 | None -> []
206 in
207 Htmlit.El.h1 ~at:attrs children
208
209let h2 ?styles children =
210 let attrs = match styles with
211 | Some s -> [classes_attr s]
212 | None -> []
213 in
214 Htmlit.El.h2 ~at:attrs children
215
216let h3 ?styles children =
217 let attrs = match styles with
218 | Some s -> [classes_attr s]
219 | None -> []
220 in
221 Htmlit.El.h3 ~at:attrs children
222
223let h4 ?styles children =
224 let attrs = match styles with
225 | Some s -> [classes_attr s]
226 | None -> []
227 in
228 Htmlit.El.h4 ~at:attrs children
229
230let h5 ?styles children =
231 let attrs = match styles with
232 | Some s -> [classes_attr s]
233 | None -> []
234 in
235 Htmlit.El.h5 ~at:attrs children
236
237let h6 ?styles children =
238 let attrs = match styles with
239 | Some s -> [classes_attr s]
240 | None -> []
241 in
242 Htmlit.El.h6 ~at:attrs children
243
244let p ?styles children =
245 let attrs = match styles with
246 | Some s -> [classes_attr s]
247 | None -> []
248 in
249 Htmlit.El.p ~at:attrs children
250
251let div ?styles children =
252 let attrs = match styles with
253 | Some s -> [classes_attr s]
254 | None -> []
255 in
256 Htmlit.El.div ~at:attrs children
257
258let span ?styles children =
259 let attrs = match styles with
260 | Some s -> [classes_attr s]
261 | None -> []
262 in
263 Htmlit.El.span ~at:attrs children
264
265let button ?styles children =
266 let attrs = match styles with
267 | Some s -> [classes_attr s]
268 | None -> []
269 in
270 Htmlit.El.button ~at:attrs children
271
272let a ?styles ~href children =
273 let attrs = [Htmlit.At.href href] @ (match styles with
274 | Some s -> [classes_attr s]
275 | None -> []
276 ) in
277 Htmlit.El.a ~at:attrs children
278
279let img ?styles ~src ~alt () =
280 let attrs = [Htmlit.At.src src; Htmlit.At.alt alt] @ (match styles with
281 | Some s -> [classes_attr s]
282 | None -> []
283 ) in
284 Htmlit.El.img ~at:attrs ()
285
286let ul ?styles children =
287 let attrs = match styles with
288 | Some s -> [classes_attr s]
289 | None -> []
290 in
291 Htmlit.El.ul ~at:attrs children
292
293let ol ?styles children =
294 let attrs = match styles with
295 | Some s -> [classes_attr s]
296 | None -> []
297 in
298 Htmlit.El.ol ~at:attrs children
299
300let li ?styles children =
301 let attrs = match styles with
302 | Some s -> [classes_attr s]
303 | None -> []
304 in
305 Htmlit.El.li ~at:attrs children
306
307let section ?styles children =
308 let attrs = match styles with
309 | Some s -> [classes_attr s]
310 | None -> []
311 in
312 Htmlit.El.section ~at:attrs children
313
314let article ?styles children =
315 let attrs = match styles with
316 | Some s -> [classes_attr s]
317 | None -> []
318 in
319 Htmlit.El.article ~at:attrs children
320
321let nav ?styles children =
322 let attrs = match styles with
323 | Some s -> [classes_attr s]
324 | None -> []
325 in
326 Htmlit.El.nav ~at:attrs children
327
328let header ?styles children =
329 let attrs = match styles with
330 | Some s -> [classes_attr s]
331 | None -> []
332 in
333 Htmlit.El.header ~at:attrs children
334
335let footer ?styles children =
336 let attrs = match styles with
337 | Some s -> [classes_attr s]
338 | None -> []
339 in
340 Htmlit.El.footer ~at:attrs children
341
342let main ?styles children =
343 let attrs = match styles with
344 | Some s -> [classes_attr s]
345 | None -> []
346 in
347 Htmlit.El.main ~at:attrs children
348
349(* Pre-built component helpers *)
350let container children =
351 div ~styles:[
352 max_width (Tailwind.Size.rem 80.0);
353 margin_x auto;
354 padding_x (rem 1.0);
355 ] children
356
357let flex_center children =
358 div ~styles:[flex; items_center; justify_center] children
359
360let card ?elevated children =
361 let shadow_style = if elevated = Some true then [shadow `Lg] else [shadow `Md] in
362 div ~styles:([
363 bg_color (Tailwind.Color.white);
364 rounded `Lg;
365 padding (rem 1.5);
366 ] @ shadow_style) children
367
368let btn_primary ?size children =
369 let size_styles = match size with
370 | Some `Sm -> [padding_x (rem 0.75); padding_y (rem 0.375); font_size `Sm]
371 | Some `Lg -> [padding_x (rem 2.0); padding_y (rem 0.75); font_size `Base]
372 | _ -> [padding_x (rem 1.0); padding_y (rem 0.5); font_size `Sm]
373 in
374 button ~styles:([
375 bg_color (blue 600);
376 text_color (Tailwind.Color.white);
377 font_weight `Medium;
378 rounded `Md;
379 transition;
380 ] @ size_styles) children
381
382let btn_secondary ?size children =
383 let size_styles = match size with
384 | Some `Sm -> [padding_x (rem 0.75); padding_y (rem 0.375); font_size `Sm]
385 | Some `Lg -> [padding_x (rem 2.0); padding_y (rem 0.75); font_size `Base]
386 | _ -> [padding_x (rem 1.0); padding_y (rem 0.5); font_size `Sm]
387 in
388 button ~styles:([
389 bg_color (gray 200);
390 text_color (gray 900);
391 font_weight `Medium;
392 rounded `Md;
393 transition;
394 ] @ size_styles) children
395
396let btn_outline ?size children =
397 let size_styles = match size with
398 | Some `Sm -> [padding_x (rem 0.75); padding_y (rem 0.375); font_size `Sm]
399 | Some `Lg -> [padding_x (rem 2.0); padding_y (rem 0.75); font_size `Base]
400 | _ -> [padding_x (rem 1.0); padding_y (rem 0.5); font_size `Sm]
401 in
402 button ~styles:([
403 bg_color (Tailwind.Color.transparent);
404 text_color (gray 700);
405 font_weight `Medium;
406 rounded `Md;
407 border;
408 border_color (gray 300);
409 transition;
410 ] @ size_styles) children