(* 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