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_grid : [`Layout] tw_prop
74 | Display_block : [`Layout] tw_prop
75 | Display_inline : [`Layout] tw_prop
76 | Display_inline_block : [`Layout] tw_prop
77 | Grid_cols : int -> [`Grid] tw_prop
78 | Grid_rows : int -> [`Grid] tw_prop
79 | Gap : Tailwind.Size.t -> [`Grid] tw_prop
80 | Gap_x : Tailwind.Size.t -> [`Grid] tw_prop
81 | Gap_y : Tailwind.Size.t -> [`Grid] tw_prop
82 | Items_center : [`Layout] tw_prop
83 | Items_start : [`Layout] tw_prop
84 | Items_end : [`Layout] tw_prop
85 | Justify_center : [`Layout] tw_prop
86 | Justify_between : [`Layout] tw_prop
87 | Justify_start : [`Layout] tw_prop
88 | Justify_end : [`Layout] tw_prop
89 | Flex_col : [`Layout] tw_prop
90 | Flex_row : [`Layout] tw_prop
91 | Text_center : [`Layout] tw_prop
92 | Text_left : [`Layout] tw_prop
93 | Text_right : [`Layout] tw_prop
94 | Rounded : [< `Sm | `Md | `Lg | `Full ] -> [`Effects] tw_prop
95 | Shadow : [< `Sm | `Md | `Lg ] -> [`Effects] tw_prop
96 | Border : [`Effects] tw_prop
97 | Border_color : Tailwind.Color.t -> [`Effects] tw_prop
98 | Transition : [`Effects] tw_prop
99
100(* Heterogeneous list *)
101type tw_list = tw_list_item list
102and tw_list_item = Any : 'a tw_prop -> tw_list_item
103
104(* Convert GADT properties to Tailwind classes *)
105let to_tailwind_classes (props : tw_list) : Tailwind.t list =
106 let convert_prop : type a. a tw_prop -> Tailwind.t = function
107 | Text_color color -> Tailwind.Color.text color
108 | Bg_color color -> Tailwind.Color.bg color
109 | Font_size size -> Tailwind.Typography.(to_class (font_size size))
110 | Font_weight weight -> Tailwind.Typography.(to_class (font_weight weight))
111 | Margin size -> Tailwind.Spacing.(to_class (m size))
112 | Margin_x size -> Tailwind.Spacing.(to_class (mx size))
113 | Margin_y size -> Tailwind.Spacing.(to_class (my size))
114 | Margin_top size -> Tailwind.Spacing.(to_class (mt size))
115 | Margin_bottom size -> Tailwind.Spacing.(to_class (mb size))
116 | Margin_left size -> Tailwind.Spacing.(to_class (ml size))
117 | Margin_right size -> Tailwind.Spacing.(to_class (mr size))
118 | Padding size -> Tailwind.Spacing.(to_class (p size))
119 | Padding_x size -> Tailwind.Spacing.(to_class (px size))
120 | Padding_y size -> Tailwind.Spacing.(to_class (py size))
121 | Width size -> Tailwind.Layout.(to_class (width size))
122 | Height size -> Tailwind.Layout.(to_class (height size))
123 | Max_width size -> Tailwind.Layout.(to_class (max_width size))
124 | Min_height size -> Tailwind.Layout.(to_class (min_height size))
125 | Display_flex -> Tailwind.Display.flex
126 | Display_grid -> Tailwind.Display.grid
127 | Display_block -> Tailwind.Display.block
128 | Display_inline -> Tailwind.Display.inline
129 | Display_inline_block -> Tailwind.Display.inline_block
130 | Grid_cols n -> Tailwind.Grid.(to_class (template_cols (`Cols n)))
131 | Grid_rows n -> Tailwind.Grid.(to_class (template_rows (`Rows n)))
132 | Gap size -> Tailwind.Spacing.(to_class (gap `All size))
133 | Gap_x size -> Tailwind.Spacing.(to_class (gap `X size))
134 | Gap_y size -> Tailwind.Spacing.(to_class (gap `Y size))
135 | Items_center -> Tailwind.Flexbox.(to_class (align_items `Center))
136 | Items_start -> Tailwind.Flexbox.(to_class (align_items `Start))
137 | Items_end -> Tailwind.Flexbox.(to_class (align_items `End))
138 | Justify_center -> Tailwind.Flexbox.(to_class (justify `Center))
139 | Justify_between -> Tailwind.Flexbox.(to_class (justify `Between))
140 | Justify_start -> Tailwind.Flexbox.(to_class (justify `Start))
141 | Justify_end -> Tailwind.Flexbox.(to_class (justify `End))
142 | Flex_col -> Tailwind.Flexbox.(to_class (direction `Col))
143 | Flex_row -> Tailwind.Flexbox.(to_class (direction `Row))
144 | Text_center -> Tailwind.Typography.(to_class (text_align `Center))
145 | Text_left -> Tailwind.Typography.(to_class (text_align `Left))
146 | Text_right -> Tailwind.Typography.(to_class (text_align `Right))
147 | Rounded radius ->
148 (match radius with
149 | `Sm -> Tailwind.Effects.rounded_sm
150 | `Md -> Tailwind.Effects.rounded_md
151 | `Lg -> Tailwind.Effects.rounded_lg
152 | `Full -> Tailwind.Effects.rounded_full)
153 | Shadow size ->
154 (match size with
155 | `Sm -> Tailwind.Effects.shadow_sm
156 | `Md -> Tailwind.Effects.shadow_md
157 | `Lg -> Tailwind.Effects.shadow_lg)
158 | Border -> Tailwind.Effects.border
159 | Border_color color -> Tailwind.Color.border color
160 | Transition -> Tailwind.Effects.transition `All
161 in
162 List.map (fun (Any prop) -> convert_prop prop) props
163
164(* Convert heterogeneous list to Tailwind.t *)
165let styles props =
166 Tailwind.Css.tw (to_tailwind_classes props)
167
168(* Helper for HTML class attribute *)
169let classes_attr props =
170 Htmlit.At.class' (Tailwind.to_string (styles props))
171
172(* Helper constructors for convenient usage *)
173let text_color c = Any (Text_color c)
174let bg_color c = Any (Bg_color c)
175let font_size s = Any (Font_size s)
176let font_weight w = Any (Font_weight w)
177let margin s = Any (Margin s)
178let margin_x s = Any (Margin_x s)
179let margin_y s = Any (Margin_y s)
180let margin_top s = Any (Margin_top s)
181let margin_bottom s = Any (Margin_bottom s)
182let margin_left s = Any (Margin_left s)
183let margin_right s = Any (Margin_right s)
184let padding s = Any (Padding s)
185let padding_x s = Any (Padding_x s)
186let padding_y s = Any (Padding_y s)
187let width s = Any (Width s)
188let height s = Any (Height s)
189let max_width s = Any (Max_width s)
190let min_height s = Any (Min_height s)
191let flex = Any Display_flex
192let grid = Any Display_grid
193let block = Any Display_block
194let inline = Any Display_inline
195let inline_block = Any Display_inline_block
196let grid_cols n = Any (Grid_cols n)
197let grid_rows n = Any (Grid_rows n)
198let gap s = Any (Gap s)
199let gap_x s = Any (Gap_x s)
200let gap_y s = Any (Gap_y s)
201let items_center = Any Items_center
202let items_start = Any Items_start
203let items_end = Any Items_end
204let justify_center = Any Justify_center
205let justify_between = Any Justify_between
206let justify_start = Any Justify_start
207let justify_end = Any Justify_end
208let flex_col = Any Flex_col
209let flex_row = Any Flex_row
210let text_center = Any Text_center
211let text_left = Any Text_left
212let text_right = Any Text_right
213let rounded r = Any (Rounded r)
214let shadow s = Any (Shadow s)
215let border = Any Border
216let border_color c = Any (Border_color c)
217let transition = Any Transition
218
219(* GADT-based element functions *)
220let h1 ?styles children =
221 let attrs = match styles with
222 | Some s -> [classes_attr s]
223 | None -> []
224 in
225 Htmlit.El.h1 ~at:attrs children
226
227let h2 ?styles children =
228 let attrs = match styles with
229 | Some s -> [classes_attr s]
230 | None -> []
231 in
232 Htmlit.El.h2 ~at:attrs children
233
234let h3 ?styles children =
235 let attrs = match styles with
236 | Some s -> [classes_attr s]
237 | None -> []
238 in
239 Htmlit.El.h3 ~at:attrs children
240
241let h4 ?styles children =
242 let attrs = match styles with
243 | Some s -> [classes_attr s]
244 | None -> []
245 in
246 Htmlit.El.h4 ~at:attrs children
247
248let h5 ?styles children =
249 let attrs = match styles with
250 | Some s -> [classes_attr s]
251 | None -> []
252 in
253 Htmlit.El.h5 ~at:attrs children
254
255let h6 ?styles children =
256 let attrs = match styles with
257 | Some s -> [classes_attr s]
258 | None -> []
259 in
260 Htmlit.El.h6 ~at:attrs children
261
262let p ?styles children =
263 let attrs = match styles with
264 | Some s -> [classes_attr s]
265 | None -> []
266 in
267 Htmlit.El.p ~at:attrs children
268
269let div ?styles children =
270 let attrs = match styles with
271 | Some s -> [classes_attr s]
272 | None -> []
273 in
274 Htmlit.El.div ~at:attrs children
275
276let span ?styles children =
277 let attrs = match styles with
278 | Some s -> [classes_attr s]
279 | None -> []
280 in
281 Htmlit.El.span ~at:attrs children
282
283let button ?styles children =
284 let attrs = match styles with
285 | Some s -> [classes_attr s]
286 | None -> []
287 in
288 Htmlit.El.button ~at:attrs children
289
290let a ?styles ~href children =
291 let attrs = [Htmlit.At.href href] @ (match styles with
292 | Some s -> [classes_attr s]
293 | None -> []
294 ) in
295 Htmlit.El.a ~at:attrs children
296
297let img ?styles ~src ~alt () =
298 let attrs = [Htmlit.At.src src; Htmlit.At.alt alt] @ (match styles with
299 | Some s -> [classes_attr s]
300 | None -> []
301 ) in
302 Htmlit.El.img ~at:attrs ()
303
304let ul ?styles children =
305 let attrs = match styles with
306 | Some s -> [classes_attr s]
307 | None -> []
308 in
309 Htmlit.El.ul ~at:attrs children
310
311let ol ?styles children =
312 let attrs = match styles with
313 | Some s -> [classes_attr s]
314 | None -> []
315 in
316 Htmlit.El.ol ~at:attrs children
317
318let li ?styles children =
319 let attrs = match styles with
320 | Some s -> [classes_attr s]
321 | None -> []
322 in
323 Htmlit.El.li ~at:attrs children
324
325let section ?styles children =
326 let attrs = match styles with
327 | Some s -> [classes_attr s]
328 | None -> []
329 in
330 Htmlit.El.section ~at:attrs children
331
332let article ?styles children =
333 let attrs = match styles with
334 | Some s -> [classes_attr s]
335 | None -> []
336 in
337 Htmlit.El.article ~at:attrs children
338
339let nav ?styles children =
340 let attrs = match styles with
341 | Some s -> [classes_attr s]
342 | None -> []
343 in
344 Htmlit.El.nav ~at:attrs children
345
346let header ?styles children =
347 let attrs = match styles with
348 | Some s -> [classes_attr s]
349 | None -> []
350 in
351 Htmlit.El.header ~at:attrs children
352
353let footer ?styles children =
354 let attrs = match styles with
355 | Some s -> [classes_attr s]
356 | None -> []
357 in
358 Htmlit.El.footer ~at:attrs children
359
360let main ?styles children =
361 let attrs = match styles with
362 | Some s -> [classes_attr s]
363 | None -> []
364 in
365 Htmlit.El.main ~at:attrs children
366
367(* Pre-built component helpers *)
368let container children =
369 div ~styles:[
370 max_width (Tailwind.Size.rem 80.0);
371 margin_x auto;
372 padding_x (rem 1.0);
373 ] children
374
375let flex_center children =
376 div ~styles:[flex; items_center; justify_center] children
377
378let card ?elevated children =
379 let shadow_style = if elevated = Some true then [shadow `Lg] else [shadow `Md] in
380 div ~styles:([
381 bg_color (Tailwind.Color.white);
382 rounded `Lg;
383 padding (rem 1.5);
384 ] @ shadow_style) children
385
386let btn_primary ?size children =
387 let size_styles = match size with
388 | Some `Sm -> [padding_x (rem 0.75); padding_y (rem 0.375); font_size `Sm]
389 | Some `Lg -> [padding_x (rem 2.0); padding_y (rem 0.75); font_size `Base]
390 | _ -> [padding_x (rem 1.0); padding_y (rem 0.5); font_size `Sm]
391 in
392 button ~styles:([
393 bg_color (blue 600);
394 text_color (Tailwind.Color.white);
395 font_weight `Medium;
396 rounded `Md;
397 transition;
398 ] @ size_styles) children
399
400let btn_secondary ?size children =
401 let size_styles = match size with
402 | Some `Sm -> [padding_x (rem 0.75); padding_y (rem 0.375); font_size `Sm]
403 | Some `Lg -> [padding_x (rem 2.0); padding_y (rem 0.75); font_size `Base]
404 | _ -> [padding_x (rem 1.0); padding_y (rem 0.5); font_size `Sm]
405 in
406 button ~styles:([
407 bg_color (gray 200);
408 text_color (gray 900);
409 font_weight `Medium;
410 rounded `Md;
411 transition;
412 ] @ size_styles) children
413
414let btn_outline ?size children =
415 let size_styles = match size with
416 | Some `Sm -> [padding_x (rem 0.75); padding_y (rem 0.375); font_size `Sm]
417 | Some `Lg -> [padding_x (rem 2.0); padding_y (rem 0.75); font_size `Base]
418 | _ -> [padding_x (rem 1.0); padding_y (rem 0.5); font_size `Sm]
419 in
420 button ~styles:([
421 bg_color (Tailwind.Color.transparent);
422 text_color (gray 700);
423 font_weight `Medium;
424 rounded `Md;
425 border;
426 border_color (gray 300);
427 transition;
428 ] @ size_styles) children