Tailwind classes in OCaml
at main 15 kB view raw
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