Tailwind classes in OCaml
1open Htmlit 2 3(** Card variants *) 4type variant = 5 | Default 6 | Outlined 7 | Elevated 8 | Flat 9 10(** Card configuration *) 11type t = { 12 variant: variant; 13 header: El.html option; 14 footer: El.html option; 15 image: El.html option; 16 padding: bool; 17 hoverable: bool; 18 clickable: bool; 19 classes: Tailwind.t option; 20 attributes: (string * string) list; 21 children: El.html list; 22} 23 24let classes_attr tailwind_classes = 25 At.class' (Tailwind.to_string tailwind_classes) 26 27let base_card_classes = Tailwind.tw [ 28 Tailwind.Effects.rounded_lg; 29 Tailwind.Color.bg Tailwind.Color.white; 30 Tailwind.Color.text (Tailwind.Color.make `Gray ~variant:`V900 ()); 31] 32 33let variant_classes = function 34 | Default -> Tailwind.tw [ 35 Tailwind.Effects.border; 36 Tailwind.Color.border (Tailwind.Color.make `Gray ~variant:`V200 ()); 37 Tailwind.Effects.shadow_sm; 38 ] 39 | Outlined -> Tailwind.tw [ 40 Tailwind.Effects.border_2; 41 Tailwind.Color.border (Tailwind.Color.make `Gray ~variant:`V300 ()); 42 ] 43 | Elevated -> Tailwind.tw [ 44 Tailwind.Effects.shadow_lg; 45 Tailwind.Effects.border; 46 Tailwind.Color.border (Tailwind.Color.make `Gray ~variant:`V200 ()); 47 ] 48 | Flat -> Tailwind.tw [ 49 Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V50 ()); 50 ] 51 52let make ?variant ?header ?footer ?image ?(padding=true) ?(hoverable=false) ?(clickable=false) ?classes ?attributes ~children () = { 53 variant = (match variant with Some v -> v | None -> Default); 54 header; 55 footer; 56 image; 57 padding; 58 hoverable; 59 clickable; 60 classes; 61 attributes = (match attributes with Some a -> a | None -> []); 62 children; 63} 64 65let to_html card = 66 let card_classes = Tailwind.tw [ 67 base_card_classes; 68 variant_classes card.variant; 69 (if card.padding then Tailwind.Spacing.(to_class (p (Tailwind.Size.rem 1.5))) else Tailwind.Css.empty); 70 (if card.hoverable then Tailwind.Variants.hover (Tailwind.Effects.shadow_md) else Tailwind.Css.empty); 71 (if card.clickable then Tailwind.tw [ 72 Tailwind.Css.make "cursor-pointer"; 73 Tailwind.Variants.hover (Tailwind.Effects.shadow_md); 74 ] else Tailwind.Css.empty); 75 (match card.classes with Some c -> c | None -> Tailwind.Css.empty); 76 ] in 77 78 let base_attrs = [classes_attr card_classes] in 79 let custom_attrs = List.map (fun (k, v) -> At.v k v) card.attributes in 80 let all_attrs = base_attrs @ custom_attrs in 81 82 let content = List.filter_map (fun x -> x) [ 83 card.image; 84 card.header; 85 Some (El.div card.children); 86 card.footer; 87 ] in 88 89 El.div ~at:all_attrs content 90 91(** Card header section *) 92let header ?classes ?title ?subtitle ?actions ~children () = 93 let header_classes = Tailwind.tw [ 94 Tailwind.Display.flex; 95 Tailwind.Flexbox.(to_class (direction `Col)); 96 Tailwind.Spacing.(to_class (p (Tailwind.Size.rem 1.5))); 97 (match classes with Some c -> c | None -> Tailwind.Css.empty); 98 ] in 99 100 let title_element = match title with 101 | Some t -> Some (El.h3 ~at:[classes_attr (Tailwind.tw [ 102 Tailwind.Typography.(to_class (font_size `Lg)); 103 Tailwind.Typography.(to_class (font_weight `Semibold)); 104 Tailwind.Spacing.(to_class (mb (Tailwind.Size.rem 0.5))); 105 ])] [El.txt t]) 106 | None -> None 107 in 108 109 let subtitle_element = match subtitle with 110 | Some s -> Some (El.p ~at:[classes_attr (Tailwind.tw [ 111 Tailwind.Typography.(to_class (font_size `Sm)); 112 Tailwind.Color.text (Tailwind.Color.make `Gray ~variant:`V600 ()); 113 ])] [El.txt s]) 114 | None -> None 115 in 116 117 let content = List.filter_map (fun x -> x) [ 118 title_element; 119 subtitle_element; 120 (if children = [] then None else Some (El.div children)); 121 actions; 122 ] in 123 124 El.div ~at:[classes_attr header_classes] content 125 126(** Card body section *) 127let body ?classes ~children () = 128 let body_classes = Tailwind.tw [ 129 Tailwind.Spacing.(to_class (p (Tailwind.Size.rem 1.5))); 130 Tailwind.Spacing.(to_class (pt (Tailwind.Size.zero))); 131 (match classes with Some c -> c | None -> Tailwind.Css.empty); 132 ] in 133 134 El.div ~at:[classes_attr body_classes] children 135 136(** Card footer section *) 137let footer ?classes ?actions ~children () = 138 let footer_classes = Tailwind.tw [ 139 Tailwind.Display.flex; 140 Tailwind.Flexbox.(to_class (align_items `Center)); 141 Tailwind.Spacing.(to_class (p (Tailwind.Size.rem 1.5))); 142 Tailwind.Spacing.(to_class (pt (Tailwind.Size.zero))); 143 (match classes with Some c -> c | None -> Tailwind.Css.empty); 144 ] in 145 146 let content = children @ (match actions with Some a -> a | None -> []) in 147 El.div ~at:[classes_attr footer_classes] content 148 149(** Card image section *) 150let image ?classes ?alt ?(cover=true) ~src () = 151 let img_classes = Tailwind.tw [ 152 Tailwind.Layout.w_full; 153 Tailwind.Layout.(to_class (height (Tailwind.Size.rem 12.0))); 154 (if cover then Tailwind.Layout.(to_class (object_fit `Cover)) else Tailwind.Css.empty); 155 Tailwind.Effects.(to_class (rounded `Top `Lg)); 156 (match classes with Some c -> c | None -> Tailwind.Css.empty); 157 ] in 158 159 El.img ~at:[ 160 classes_attr img_classes; 161 At.src src; 162 At.alt (match alt with Some a -> a | None -> ""); 163 ] () 164 165(** Create a card grid *) 166let grid ?cols ?gap ?classes ~cards () = 167 let base_classes = [Tailwind.Display.grid] in 168 169 let col_classes = match cols with 170 | Some col_list -> List.fold_left (fun acc col -> 171 match col with 172 | `Xs n -> acc @ [Tailwind.Grid.(to_class (template_cols (`Cols n)))] 173 | `Sm n -> acc @ [Tailwind.Responsive.(to_class (at_breakpoint `Sm (Tailwind.Grid.(to_class (template_cols (`Cols n))))))] 174 | `Md n -> acc @ [Tailwind.Responsive.(to_class (at_breakpoint `Md (Tailwind.Grid.(to_class (template_cols (`Cols n))))))] 175 | `Lg n -> acc @ [Tailwind.Responsive.(to_class (at_breakpoint `Lg (Tailwind.Grid.(to_class (template_cols (`Cols n))))))] 176 | `Xl n -> acc @ [Tailwind.Responsive.(to_class (at_breakpoint `Xl (Tailwind.Grid.(to_class (template_cols (`Cols n))))))] 177 ) [] col_list 178 | None -> [Tailwind.Grid.(to_class (template_cols (`Cols 1)))] 179 in 180 181 let gap_classes = match gap with 182 | Some g -> [Tailwind.Spacing.(to_class (gap `All g))] 183 | None -> [] 184 in 185 186 let grid_classes = Tailwind.tw (base_classes @ col_classes @ gap_classes @ 187 (match classes with Some c -> [c] | None -> [])) in 188 189 let card_elements = List.map to_html cards in 190 El.div ~at:[classes_attr grid_classes] card_elements