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] []