open Htmlit type container_size = | Sm | Md | Lg | Xl | Xl2 | Full | Fluid type layout_type = | Container of container_size * bool * bool | Flex of Tailwind.Flexbox.direction option * Tailwind.Flexbox.justify option * Tailwind.Flexbox.align_items option * Tailwind.Flexbox.wrap option * Tailwind.Size.t option | 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 | Stack of Tailwind.Size.t option * Tailwind.Flexbox.align_items option | Row of Tailwind.Size.t option * Tailwind.Flexbox.justify option * Tailwind.Flexbox.align_items option * bool option | Sidebar of [`Left | `Right] option * Tailwind.Size.t option * bool option * El.html * El.html | Page of El.html option * El.html option * El.html option * El.html type t = { layout_type: layout_type; classes: Tailwind.t option; attributes: (string * string) list; children: El.html list; } let classes_attr tailwind_classes = At.class' (Tailwind.to_string tailwind_classes) let container_size_to_class = function | Sm -> Tailwind.Css.make "max-w-sm" | Md -> Tailwind.Css.make "max-w-md" | Lg -> Tailwind.Css.make "max-w-lg" | Xl -> Tailwind.Css.make "max-w-xl" | Xl2 -> Tailwind.Css.make "max-w-2xl" | Full -> Tailwind.Css.make "max-w-full" | Fluid -> Tailwind.Layout.w_full let container ?size ?(center=true) ?(padding=true) ?classes ?attributes ~children () = { layout_type = Container ( (match size with Some s -> s | None -> Lg), center, padding ); classes; attributes = (match attributes with Some a -> a | None -> []); children; } let flex ?direction ?justify ?align ?wrap ?gap ?classes ?attributes ~children () = { layout_type = Flex (direction, justify, align, wrap, gap); classes; attributes = (match attributes with Some a -> a | None -> []); children; } let grid ?cols ?rows ?gap ?gap_x ?gap_y ?flow ?classes ?attributes ~children () = { layout_type = Grid (cols, rows, gap, gap_x, gap_y, flow); classes; attributes = (match attributes with Some a -> a | None -> []); children; } let stack ?gap ?align ?classes ?attributes ~children () = { layout_type = Stack (gap, align); classes; attributes = (match attributes with Some a -> a | None -> []); children; } let row ?gap ?justify ?align ?wrap ?classes ?attributes ~children () = { layout_type = Row (gap, justify, align, wrap); classes; attributes = (match attributes with Some a -> a | None -> []); children; } let sidebar ?side ?width ?(collapsible=false) ?classes ?attributes ~sidebar ~content () = { layout_type = Sidebar (side, width, Some collapsible, sidebar, content); classes; attributes = (match attributes with Some a -> a | None -> []); children = []; } let page ?header ?footer ?sidebar ?classes ?attributes ~main () = { layout_type = Page (header, footer, sidebar, main); classes; attributes = (match attributes with Some a -> a | None -> []); children = []; } let to_html layout = let base_classes = match layout.layout_type with | Container (size, center, padding) -> let size_class = container_size_to_class size in let center_class = if center then Tailwind.Css.make "mx-auto" else Tailwind.Css.empty in let padding_class = if padding then Tailwind.Spacing.(to_class (px (Tailwind.Size.rem 1.0))) else Tailwind.Css.empty in Tailwind.Css.tw [size_class; center_class; padding_class] | Flex (direction, justify, align, wrap, gap) -> let dir_classes = match direction with Some d -> [Tailwind.Flexbox.(to_class (direction d))] | None -> [] in let justify_classes = match justify with Some j -> [Tailwind.Flexbox.(to_class (justify j))] | None -> [] in let align_classes = match align with Some a -> [Tailwind.Flexbox.(to_class (align_items a))] | None -> [] in let wrap_classes = match wrap with Some w -> [Tailwind.Flexbox.(to_class (wrap w))] | None -> [] in let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in Tailwind.Css.tw ([Tailwind.Display.flex] @ dir_classes @ justify_classes @ align_classes @ wrap_classes @ gap_classes) | Grid (cols, rows, gap, gap_x, gap_y, _flow) -> let col_classes = match cols with Some c -> [Tailwind.Grid.(to_class (template_cols c))] | None -> [] in let row_classes = match rows with Some r -> [Tailwind.Grid.(to_class (template_rows r))] | None -> [] in let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in let gap_x_classes = match gap_x with Some g -> [Tailwind.Spacing.(to_class (gap `X g))] | None -> [] in let gap_y_classes = match gap_y with Some g -> [Tailwind.Spacing.(to_class (gap `Y g))] | None -> [] in let flow_classes = [] in Tailwind.Css.tw ([Tailwind.Display.grid] @ col_classes @ row_classes @ gap_classes @ gap_x_classes @ gap_y_classes @ flow_classes) | Stack (gap, align) -> let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in let align_classes = match align with Some a -> [Tailwind.Flexbox.(to_class (align_items a))] | None -> [] in Tailwind.Css.tw ([Tailwind.Display.flex; Tailwind.Flexbox.(to_class (direction `Col))] @ gap_classes @ align_classes) | Row (gap, justify, align, wrap) -> let gap_classes = match gap with Some g -> [Tailwind.Spacing.(to_class (gap `All g))] | None -> [] in let justify_classes = match justify with Some j -> [Tailwind.Flexbox.(to_class (justify j))] | None -> [] in let align_classes = match align with Some a -> [Tailwind.Flexbox.(to_class (align_items a))] | None -> [] in let wrap_classes = match wrap with Some true -> [Tailwind.Flexbox.(to_class (wrap `Wrap))] | Some false -> [Tailwind.Flexbox.(to_class (wrap `Nowrap))] | None -> [] in Tailwind.Css.tw ([Tailwind.Display.flex; Tailwind.Flexbox.(to_class (direction `Row))] @ gap_classes @ justify_classes @ align_classes @ wrap_classes) | Sidebar (_side, _width, _collapsible, _sidebar_content, _main_content) -> Tailwind.Css.tw [Tailwind.Display.flex] | Page (_header, _footer, _sidebar, _main) -> Tailwind.Css.tw [Tailwind.Display.flex; Tailwind.Flexbox.(to_class (direction `Col)); Tailwind.Layout.h_screen] in let final_classes = Tailwind.Css.tw [ base_classes; (match layout.classes with Some c -> c | None -> Tailwind.Css.empty); ] in let base_attrs = [classes_attr final_classes] in let custom_attrs = List.map (fun (k, v) -> At.v k v) layout.attributes in let all_attrs = base_attrs @ custom_attrs in match layout.layout_type with | Sidebar (_side, width, _collapsible, sidebar_content, main_content) -> let sidebar_width = match width with Some w -> w | None -> Tailwind.Size.rem 16.0 in let sidebar_classes = Tailwind.Css.tw [ Tailwind.Layout.(to_class (width sidebar_width)); Tailwind.Flexbox.(to_class (shrink (Some 0))); Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V50 ()); ] in let main_classes = Tailwind.Css.tw [ Tailwind.Flexbox.(to_class (grow (Some 1))); Tailwind.Layout.(to_class (overflow `All `Auto)); ] in El.div ~at:all_attrs [ El.div ~at:[classes_attr sidebar_classes] [sidebar_content]; El.div ~at:[classes_attr main_classes] [main_content]; ] | Page (header, footer, sidebar, main) -> let content = List.filter_map (fun x -> x) [ header; (match sidebar with | Some sb -> Some (El.div [sb; main]) | None -> Some main); footer; ] in El.div ~at:all_attrs content | _ -> El.div ~at:all_attrs layout.children let spacer ?size () = let size_class = match size with | Some s -> Tailwind.Layout.(to_class (height s)) | None -> Tailwind.Layout.(to_class (height (Tailwind.Size.rem 1.0))) in El.div ~at:[classes_attr (Tailwind.Css.tw [size_class])] [] let divider ?orientation ?classes () = let base_classes = match orientation with | Some `Vertical -> [ Tailwind.Layout.(to_class (width `Px)); Tailwind.Layout.h_full; Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V300 ()); ] | Some `Horizontal | None -> [ Tailwind.Layout.w_full; Tailwind.Layout.(to_class (height `Px)); Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V300 ()); ] in let divider_classes = Tailwind.Css.tw (base_classes @ (match classes with Some c -> [c] | None -> [])) in El.div ~at:[classes_attr divider_classes] []