Tailwind classes in OCaml
1open Htmlit 2 3type container_size = 4 | Sm 5 | Md 6 | Lg 7 | Xl 8 | Xl2 9 | Full 10 | Fluid 11 12type layout_type = 13 | Container of container_size * bool * bool 14 | Flex of Tailwind.Flexbox.direction option * Tailwind.Flexbox.justify option * Tailwind.Flexbox.align_items option * Tailwind.Flexbox.wrap option * Tailwind.Size.t option 15 | Grid of Tailwind.Grid.cols option * Tailwind.Grid.rows option * Tailwind.Size.t option * Tailwind.Size.t option * Tailwind.Size.t option * Tailwind.Grid.flow option 16 | Stack of Tailwind.Size.t option * Tailwind.Flexbox.align_items option 17 | Row of Tailwind.Size.t option * Tailwind.Flexbox.justify option * Tailwind.Flexbox.align_items option * bool option 18 | Sidebar of [`Left | `Right] option * Tailwind.Size.t option * bool option * El.html * El.html 19 | Page of El.html option * El.html option * El.html option * El.html 20 21type t = { 22 layout_type: layout_type; 23 classes: Tailwind.t option; 24 attributes: (string * string) list; 25 children: El.html list; 26} 27 28let classes_attr tailwind_classes = 29 At.class' (Tailwind.to_string tailwind_classes) 30 31let container_size_to_class = function 32 | Sm -> Tailwind.Css.make "max-w-sm" 33 | Md -> Tailwind.Css.make "max-w-md" 34 | Lg -> Tailwind.Css.make "max-w-lg" 35 | Xl -> Tailwind.Css.make "max-w-xl" 36 | Xl2 -> Tailwind.Css.make "max-w-2xl" 37 | Full -> Tailwind.Css.make "max-w-full" 38 | Fluid -> Tailwind.Layout.w_full 39 40let container ?size ?(center=true) ?(padding=true) ?classes ?attributes ~children () = { 41 layout_type = Container ( 42 (match size with Some s -> s | None -> Lg), 43 center, 44 padding 45 ); 46 classes; 47 attributes = (match attributes with Some a -> a | None -> []); 48 children; 49} 50 51let flex ?direction ?justify ?align ?wrap ?gap ?classes ?attributes ~children () = { 52 layout_type = Flex (direction, justify, align, wrap, gap); 53 classes; 54 attributes = (match attributes with Some a -> a | None -> []); 55 children; 56} 57 58let grid ?cols ?rows ?gap ?gap_x ?gap_y ?flow ?classes ?attributes ~children () = { 59 layout_type = Grid (cols, rows, gap, gap_x, gap_y, flow); 60 classes; 61 attributes = (match attributes with Some a -> a | None -> []); 62 children; 63} 64 65let stack ?gap ?align ?classes ?attributes ~children () = { 66 layout_type = Stack (gap, align); 67 classes; 68 attributes = (match attributes with Some a -> a | None -> []); 69 children; 70} 71 72let row ?gap ?justify ?align ?wrap ?classes ?attributes ~children () = { 73 layout_type = Row (gap, justify, align, wrap); 74 classes; 75 attributes = (match attributes with Some a -> a | None -> []); 76 children; 77} 78 79let sidebar ?side ?width ?(collapsible=false) ?classes ?attributes ~sidebar ~content () = { 80 layout_type = Sidebar (side, width, Some collapsible, sidebar, content); 81 classes; 82 attributes = (match attributes with Some a -> a | None -> []); 83 children = []; 84} 85 86let page ?header ?footer ?sidebar ?classes ?attributes ~main () = { 87 layout_type = Page (header, footer, sidebar, main); 88 classes; 89 attributes = (match attributes with Some a -> a | None -> []); 90 children = []; 91} 92 93let to_html layout = 94 let base_classes = match layout.layout_type with 95 | Container (size, center, padding) -> 96 let size_class = container_size_to_class size in 97 let center_class = if center then Tailwind.Css.make "mx-auto" else Tailwind.Css.empty in 98 let padding_class = if padding then Tailwind.Spacing.(to_class (px (Tailwind.Size.rem 1.0))) else Tailwind.Css.empty in 99 Tailwind.Css.tw [size_class; center_class; padding_class] 100 101 | Flex (direction, justify, align, wrap, gap) -> 102 let dir_classes = match direction with Some d -> [Tailwind.Flexbox.(to_class (direction d))] | None -> [] in 103 let justify_classes = match justify with Some j -> [Tailwind.Flexbox.(to_class (justify j))] | None -> [] in 104 let align_classes = match align with Some a -> [Tailwind.Flexbox.(to_class (align_items a))] | None -> [] in 105 let wrap_classes = match wrap with Some w -> [Tailwind.Flexbox.(to_class (wrap w))] | None -> [] in 106 let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in 107 Tailwind.Css.tw ([Tailwind.Display.flex] @ dir_classes @ justify_classes @ align_classes @ wrap_classes @ gap_classes) 108 109 | Grid (cols, rows, gap, gap_x, gap_y, _flow) -> 110 let col_classes = match cols with Some c -> [Tailwind.Grid.(to_class (template_cols c))] | None -> [] in 111 let row_classes = match rows with Some r -> [Tailwind.Grid.(to_class (template_rows r))] | None -> [] in 112 let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in 113 let gap_x_classes = match gap_x with Some g -> [Tailwind.Spacing.(to_class (gap `X g))] | None -> [] in 114 let gap_y_classes = match gap_y with Some g -> [Tailwind.Spacing.(to_class (gap `Y g))] | None -> [] in 115 let flow_classes = [] in 116 Tailwind.Css.tw ([Tailwind.Display.grid] @ col_classes @ row_classes @ gap_classes @ gap_x_classes @ gap_y_classes @ flow_classes) 117 118 | Stack (gap, align) -> 119 let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in 120 let align_classes = match align with Some a -> [Tailwind.Flexbox.(to_class (align_items a))] | None -> [] in 121 Tailwind.Css.tw ([Tailwind.Display.flex; Tailwind.Flexbox.(to_class (direction `Col))] @ gap_classes @ align_classes) 122 123 | Row (gap, justify, align, wrap) -> 124 let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in 125 let justify_classes = match justify with Some j -> [Tailwind.Flexbox.(to_class (justify j))] | None -> [] in 126 let align_classes = match align with Some a -> [Tailwind.Flexbox.(to_class (align_items a))] | None -> [] in 127 let wrap_classes = match wrap with Some true -> [Tailwind.Flexbox.(to_class (wrap `Wrap))] | Some false -> [Tailwind.Flexbox.(to_class (wrap `Nowrap))] | None -> [] in 128 Tailwind.Css.tw ([Tailwind.Display.flex; Tailwind.Flexbox.(to_class (direction `Row))] @ gap_classes @ justify_classes @ align_classes @ wrap_classes) 129 130 | Sidebar (_side, _width, _collapsible, _sidebar_content, _main_content) -> 131 Tailwind.Css.tw [Tailwind.Display.flex] 132 133 | Page (_header, _footer, _sidebar, _main) -> 134 Tailwind.Css.tw [Tailwind.Display.flex; Tailwind.Flexbox.(to_class (direction `Col)); Tailwind.Layout.h_screen] 135 in 136 137 let final_classes = Tailwind.Css.tw [ 138 base_classes; 139 (match layout.classes with Some c -> c | None -> Tailwind.Css.empty); 140 ] in 141 142 let base_attrs = [classes_attr final_classes] in 143 let custom_attrs = List.map (fun (k, v) -> At.v k v) layout.attributes in 144 let all_attrs = base_attrs @ custom_attrs in 145 146 match layout.layout_type with 147 | Sidebar (_side, width, _collapsible, sidebar_content, main_content) -> 148 let sidebar_width = match width with Some w -> w | None -> Tailwind.Size.rem 16.0 in 149 let sidebar_classes = Tailwind.Css.tw [ 150 Tailwind.Layout.(to_class (width sidebar_width)); 151 Tailwind.Flexbox.(to_class (shrink (Some 0))); 152 Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V50 ()); 153 ] in 154 let main_classes = Tailwind.Css.tw [ 155 Tailwind.Flexbox.(to_class (grow (Some 1))); 156 Tailwind.Layout.(to_class (overflow `All `Auto)); 157 ] in 158 El.div ~at:all_attrs [ 159 El.div ~at:[classes_attr sidebar_classes] [sidebar_content]; 160 El.div ~at:[classes_attr main_classes] [main_content]; 161 ] 162 163 | Page (header, footer, sidebar, main) -> 164 let content = List.filter_map (fun x -> x) [ 165 header; 166 (match sidebar with 167 | Some sb -> Some (El.div [sb; main]) 168 | None -> Some main); 169 footer; 170 ] in 171 El.div ~at:all_attrs content 172 173 | _ -> El.div ~at:all_attrs layout.children 174 175let spacer ?size () = 176 let size_class = match size with 177 | Some s -> Tailwind.Layout.(to_class (height s)) 178 | None -> Tailwind.Layout.(to_class (height (Tailwind.Size.rem 1.0))) 179 in 180 El.div ~at:[classes_attr (Tailwind.Css.tw [size_class])] [] 181 182let divider ?orientation ?classes () = 183 let base_classes = match orientation with 184 | Some `Vertical -> [ 185 Tailwind.Layout.(to_class (width `Px)); 186 Tailwind.Layout.h_full; 187 Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V300 ()); 188 ] 189 | Some `Horizontal | None -> [ 190 Tailwind.Layout.w_full; 191 Tailwind.Layout.(to_class (height `Px)); 192 Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V300 ()); 193 ] 194 in 195 let divider_classes = Tailwind.Css.tw (base_classes @ 196 (match classes with Some c -> [c] | None -> [])) in 197 El.div ~at:[classes_attr divider_classes] []