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