(* GADT-based Tailwind HTML library with heterogeneous lists *)
(* Color utilities *)
let blue variant = Tailwind.Color.make `Blue ~variant:(match variant with
| 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
| 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
| _ -> `V600) ()
let gray variant = Tailwind.Color.make `Gray ~variant:(match variant with
| 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
| 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
| _ -> `V600) ()
let red variant = Tailwind.Color.make `Red ~variant:(match variant with
| 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
| 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
| _ -> `V600) ()
let green variant = Tailwind.Color.make `Green ~variant:(match variant with
| 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
| 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
| _ -> `V600) ()
let yellow variant = Tailwind.Color.make `Yellow ~variant:(match variant with
| 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
| 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
| _ -> `V600) ()
let indigo variant = Tailwind.Color.make `Indigo ~variant:(match variant with
| 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
| 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
| _ -> `V600) ()
let purple variant = Tailwind.Color.make `Purple ~variant:(match variant with
| 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
| 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
| _ -> `V600) ()
let pink variant = Tailwind.Color.make `Pink ~variant:(match variant with
| 50 -> `V50 | 100 -> `V100 | 200 -> `V200 | 300 -> `V300 | 400 -> `V400
| 500 -> `V500 | 600 -> `V600 | 700 -> `V700 | 800 -> `V800 | 900 -> `V900
| _ -> `V600) ()
let rem f = Tailwind.Size.rem f
let px = Tailwind.Size.px
let zero = Tailwind.Size.zero
let auto = Tailwind.Size.auto
let full = Tailwind.Size.full
let screen = Tailwind.Size.screen
let txt s = Htmlit.El.txt s
(* GADT for Tailwind properties with types indicating their category *)
type _ tw_prop =
| Text_color : Tailwind.Color.t -> [`Text_color] tw_prop
| Bg_color : Tailwind.Color.t -> [`Bg_color] tw_prop
| Font_size : Tailwind.Typography.font_size -> [`Font_size] tw_prop
| Font_weight : Tailwind.Typography.font_weight -> [`Font_weight] tw_prop
| Margin : Tailwind.Size.t -> [`Margin] tw_prop
| Margin_x : Tailwind.Size.t -> [`Margin] tw_prop
| Margin_y : Tailwind.Size.t -> [`Margin] tw_prop
| Margin_top : Tailwind.Size.t -> [`Margin] tw_prop
| Margin_bottom : Tailwind.Size.t -> [`Margin] tw_prop
| Margin_left : Tailwind.Size.t -> [`Margin] tw_prop
| Margin_right : Tailwind.Size.t -> [`Margin] tw_prop
| Padding : Tailwind.Size.t -> [`Padding] tw_prop
| Padding_x : Tailwind.Size.t -> [`Padding] tw_prop
| Padding_y : Tailwind.Size.t -> [`Padding] tw_prop
| Width : Tailwind.Size.t -> [`Width] tw_prop
| Height : Tailwind.Size.t -> [`Height] tw_prop
| Max_width : Tailwind.Size.t -> [`Width] tw_prop
| Min_height : Tailwind.Size.t -> [`Height] tw_prop
| Display_flex : [`Layout] tw_prop
| Display_grid : [`Layout] tw_prop
| Display_block : [`Layout] tw_prop
| Display_inline : [`Layout] tw_prop
| Display_inline_block : [`Layout] tw_prop
| Grid_cols : int -> [`Grid] tw_prop
| Grid_rows : int -> [`Grid] tw_prop
| Gap : Tailwind.Size.t -> [`Grid] tw_prop
| Gap_x : Tailwind.Size.t -> [`Grid] tw_prop
| Gap_y : Tailwind.Size.t -> [`Grid] tw_prop
| Items_center : [`Layout] tw_prop
| Items_start : [`Layout] tw_prop
| Items_end : [`Layout] tw_prop
| Justify_center : [`Layout] tw_prop
| Justify_between : [`Layout] tw_prop
| Justify_start : [`Layout] tw_prop
| Justify_end : [`Layout] tw_prop
| Flex_col : [`Layout] tw_prop
| Flex_row : [`Layout] tw_prop
| Text_center : [`Layout] tw_prop
| Text_left : [`Layout] tw_prop
| Text_right : [`Layout] tw_prop
| Rounded : [< `Sm | `Md | `Lg | `Full ] -> [`Effects] tw_prop
| Shadow : [< `Sm | `Md | `Lg ] -> [`Effects] tw_prop
| Border : [`Effects] tw_prop
| Border_color : Tailwind.Color.t -> [`Effects] tw_prop
| Transition : [`Effects] tw_prop
(* Heterogeneous list *)
type tw_list = tw_list_item list
and tw_list_item = Any : 'a tw_prop -> tw_list_item
(* Convert GADT properties to Tailwind classes *)
let to_tailwind_classes (props : tw_list) : Tailwind.t list =
let convert_prop : type a. a tw_prop -> Tailwind.t = function
| Text_color color -> Tailwind.Color.text color
| Bg_color color -> Tailwind.Color.bg color
| Font_size size -> Tailwind.Typography.(to_class (font_size size))
| Font_weight weight -> Tailwind.Typography.(to_class (font_weight weight))
| Margin size -> Tailwind.Spacing.(to_class (m size))
| Margin_x size -> Tailwind.Spacing.(to_class (mx size))
| Margin_y size -> Tailwind.Spacing.(to_class (my size))
| Margin_top size -> Tailwind.Spacing.(to_class (mt size))
| Margin_bottom size -> Tailwind.Spacing.(to_class (mb size))
| Margin_left size -> Tailwind.Spacing.(to_class (ml size))
| Margin_right size -> Tailwind.Spacing.(to_class (mr size))
| Padding size -> Tailwind.Spacing.(to_class (p size))
| Padding_x size -> Tailwind.Spacing.(to_class (px size))
| Padding_y size -> Tailwind.Spacing.(to_class (py size))
| Width size -> Tailwind.Layout.(to_class (width size))
| Height size -> Tailwind.Layout.(to_class (height size))
| Max_width size -> Tailwind.Layout.(to_class (max_width size))
| Min_height size -> Tailwind.Layout.(to_class (min_height size))
| Display_flex -> Tailwind.Display.flex
| Display_grid -> Tailwind.Display.grid
| Display_block -> Tailwind.Display.block
| Display_inline -> Tailwind.Display.inline
| Display_inline_block -> Tailwind.Display.inline_block
| Grid_cols n -> Tailwind.Grid.(to_class (template_cols (`Cols n)))
| Grid_rows n -> Tailwind.Grid.(to_class (template_rows (`Rows n)))
| Gap size -> Tailwind.Spacing.(to_class (gap `All size))
| Gap_x size -> Tailwind.Spacing.(to_class (gap `X size))
| Gap_y size -> Tailwind.Spacing.(to_class (gap `Y size))
| Items_center -> Tailwind.Flexbox.(to_class (align_items `Center))
| Items_start -> Tailwind.Flexbox.(to_class (align_items `Start))
| Items_end -> Tailwind.Flexbox.(to_class (align_items `End))
| Justify_center -> Tailwind.Flexbox.(to_class (justify `Center))
| Justify_between -> Tailwind.Flexbox.(to_class (justify `Between))
| Justify_start -> Tailwind.Flexbox.(to_class (justify `Start))
| Justify_end -> Tailwind.Flexbox.(to_class (justify `End))
| Flex_col -> Tailwind.Flexbox.(to_class (direction `Col))
| Flex_row -> Tailwind.Flexbox.(to_class (direction `Row))
| Text_center -> Tailwind.Typography.(to_class (text_align `Center))
| Text_left -> Tailwind.Typography.(to_class (text_align `Left))
| Text_right -> Tailwind.Typography.(to_class (text_align `Right))
| Rounded radius ->
(match radius with
| `Sm -> Tailwind.Effects.rounded_sm
| `Md -> Tailwind.Effects.rounded_md
| `Lg -> Tailwind.Effects.rounded_lg
| `Full -> Tailwind.Effects.rounded_full)
| Shadow size ->
(match size with
| `Sm -> Tailwind.Effects.shadow_sm
| `Md -> Tailwind.Effects.shadow_md
| `Lg -> Tailwind.Effects.shadow_lg)
| Border -> Tailwind.Effects.border
| Border_color color -> Tailwind.Color.border color
| Transition -> Tailwind.Effects.transition `All
in
List.map (fun (Any prop) -> convert_prop prop) props
(* Convert heterogeneous list to Tailwind.t *)
let styles props =
Tailwind.Css.tw (to_tailwind_classes props)
(* Helper for HTML class attribute *)
let classes_attr props =
Htmlit.At.class' (Tailwind.to_string (styles props))
(* Helper constructors for convenient usage *)
let text_color c = Any (Text_color c)
let bg_color c = Any (Bg_color c)
let font_size s = Any (Font_size s)
let font_weight w = Any (Font_weight w)
let margin s = Any (Margin s)
let margin_x s = Any (Margin_x s)
let margin_y s = Any (Margin_y s)
let margin_top s = Any (Margin_top s)
let margin_bottom s = Any (Margin_bottom s)
let margin_left s = Any (Margin_left s)
let margin_right s = Any (Margin_right s)
let padding s = Any (Padding s)
let padding_x s = Any (Padding_x s)
let padding_y s = Any (Padding_y s)
let width s = Any (Width s)
let height s = Any (Height s)
let max_width s = Any (Max_width s)
let min_height s = Any (Min_height s)
let flex = Any Display_flex
let grid = Any Display_grid
let block = Any Display_block
let inline = Any Display_inline
let inline_block = Any Display_inline_block
let grid_cols n = Any (Grid_cols n)
let grid_rows n = Any (Grid_rows n)
let gap s = Any (Gap s)
let gap_x s = Any (Gap_x s)
let gap_y s = Any (Gap_y s)
let items_center = Any Items_center
let items_start = Any Items_start
let items_end = Any Items_end
let justify_center = Any Justify_center
let justify_between = Any Justify_between
let justify_start = Any Justify_start
let justify_end = Any Justify_end
let flex_col = Any Flex_col
let flex_row = Any Flex_row
let text_center = Any Text_center
let text_left = Any Text_left
let text_right = Any Text_right
let rounded r = Any (Rounded r)
let shadow s = Any (Shadow s)
let border = Any Border
let border_color c = Any (Border_color c)
let transition = Any Transition
(* GADT-based element functions *)
let h1 ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.h1 ~at:attrs children
let h2 ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.h2 ~at:attrs children
let h3 ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.h3 ~at:attrs children
let h4 ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.h4 ~at:attrs children
let h5 ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.h5 ~at:attrs children
let h6 ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.h6 ~at:attrs children
let p ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.p ~at:attrs children
let div ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.div ~at:attrs children
let span ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.span ~at:attrs children
let button ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.button ~at:attrs children
let a ?styles ~href children =
let attrs = [Htmlit.At.href href] @ (match styles with
| Some s -> [classes_attr s]
| None -> []
) in
Htmlit.El.a ~at:attrs children
let img ?styles ~src ~alt () =
let attrs = [Htmlit.At.src src; Htmlit.At.alt alt] @ (match styles with
| Some s -> [classes_attr s]
| None -> []
) in
Htmlit.El.img ~at:attrs ()
let ul ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.ul ~at:attrs children
let ol ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.ol ~at:attrs children
let li ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.li ~at:attrs children
let section ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.section ~at:attrs children
let article ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.article ~at:attrs children
let nav ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.nav ~at:attrs children
let header ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.header ~at:attrs children
let footer ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.footer ~at:attrs children
let main ?styles children =
let attrs = match styles with
| Some s -> [classes_attr s]
| None -> []
in
Htmlit.El.main ~at:attrs children
(* Pre-built component helpers *)
let container children =
div ~styles:[
max_width (Tailwind.Size.rem 80.0);
margin_x auto;
padding_x (rem 1.0);
] children
let flex_center children =
div ~styles:[flex; items_center; justify_center] children
let card ?elevated children =
let shadow_style = if elevated = Some true then [shadow `Lg] else [shadow `Md] in
div ~styles:([
bg_color (Tailwind.Color.white);
rounded `Lg;
padding (rem 1.5);
] @ shadow_style) children
let btn_primary ?size children =
let size_styles = match size with
| Some `Sm -> [padding_x (rem 0.75); padding_y (rem 0.375); font_size `Sm]
| Some `Lg -> [padding_x (rem 2.0); padding_y (rem 0.75); font_size `Base]
| _ -> [padding_x (rem 1.0); padding_y (rem 0.5); font_size `Sm]
in
button ~styles:([
bg_color (blue 600);
text_color (Tailwind.Color.white);
font_weight `Medium;
rounded `Md;
transition;
] @ size_styles) children
let btn_secondary ?size children =
let size_styles = match size with
| Some `Sm -> [padding_x (rem 0.75); padding_y (rem 0.375); font_size `Sm]
| Some `Lg -> [padding_x (rem 2.0); padding_y (rem 0.75); font_size `Base]
| _ -> [padding_x (rem 1.0); padding_y (rem 0.5); font_size `Sm]
in
button ~styles:([
bg_color (gray 200);
text_color (gray 900);
font_weight `Medium;
rounded `Md;
transition;
] @ size_styles) children
let btn_outline ?size children =
let size_styles = match size with
| Some `Sm -> [padding_x (rem 0.75); padding_y (rem 0.375); font_size `Sm]
| Some `Lg -> [padding_x (rem 2.0); padding_y (rem 0.75); font_size `Base]
| _ -> [padding_x (rem 1.0); padding_y (rem 0.5); font_size `Sm]
in
button ~styles:([
bg_color (Tailwind.Color.transparent);
text_color (gray 700);
font_weight `Medium;
rounded `Md;
border;
border_color (gray 300);
transition;
] @ size_styles) children