Tailwind classes in OCaml
at main 5.7 kB view raw
1open Htmlit 2 3type variant = [ `Primary | `Secondary | `Outline | `Ghost | `Link ] 4type size = [ `Sm | `Default | `Lg | `Icon ] 5type state = [ `Default | `Loading | `Disabled ] 6 7type t = { 8 variant: variant; 9 size: size; 10 state: state; 11 icon: El.html option; 12 icon_position: [`Left | `Right]; 13 classes: Tailwind.t option; 14 attributes: (string * string) list; 15 children: El.html list; 16} 17 18let classes_attr tailwind_classes = 19 At.class' (Tailwind.to_string tailwind_classes) 20 21let base_button_classes = Tailwind.Css.tw [ 22 Tailwind.Display.inline_flex; 23 Tailwind.Flexbox.(to_class (align_items `Center)); 24 Tailwind.Flexbox.(to_class (justify `Center)); 25 Tailwind.Effects.rounded_md; 26 Tailwind.Typography.(to_class (font_size `Sm)); 27 Tailwind.Typography.(to_class (font_weight `Medium)); 28 Tailwind.Css.make "ring-offset-background"; 29 Tailwind.Effects.transition `Colors; 30 Tailwind.Css.make "focus-visible:outline-none"; 31 Tailwind.Css.make "focus-visible:ring-2"; 32 Tailwind.Css.make "focus-visible:ring-ring"; 33 Tailwind.Css.make "focus-visible:ring-offset-2"; 34 Tailwind.Css.make "disabled:pointer-events-none"; 35 Tailwind.Css.make "disabled:opacity-50"; 36] 37 38let variant_classes = function 39 | `Primary -> Tailwind.Css.tw [ 40 Tailwind.Color.bg (Tailwind.Color.make `Blue ~variant:`V600 ()); 41 Tailwind.Color.text Tailwind.Color.white; 42 Tailwind.Variants.hover (Tailwind.Color.bg (Tailwind.Color.make `Blue ~variant:`V700 ())); 43 ] 44 | `Secondary -> Tailwind.Css.tw [ 45 Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V200 ()); 46 Tailwind.Color.text (Tailwind.Color.make `Gray ~variant:`V900 ()); 47 Tailwind.Variants.hover (Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V300 ())); 48 ] 49 | `Outline -> Tailwind.Css.tw [ 50 Tailwind.Effects.border; 51 Tailwind.Color.border (Tailwind.Color.make `Gray ~variant:`V300 ()); 52 Tailwind.Color.bg Tailwind.Color.transparent; 53 Tailwind.Variants.hover (Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V100 ())); 54 ] 55 | `Ghost -> Tailwind.Css.tw [ 56 Tailwind.Color.bg Tailwind.Color.transparent; 57 Tailwind.Variants.hover (Tailwind.Color.bg (Tailwind.Color.make `Gray ~variant:`V100 ())); 58 ] 59 | `Link -> Tailwind.Css.tw [ 60 Tailwind.Color.bg Tailwind.Color.transparent; 61 Tailwind.Color.text (Tailwind.Color.make `Blue ~variant:`V600 ()); 62 Tailwind.Css.make "underline-offset-4"; 63 Tailwind.Variants.hover (Tailwind.Css.make "underline"); 64 ] 65 66let size_classes = function 67 | `Default -> Tailwind.Css.tw [ 68 Tailwind.Spacing.(to_class (px (Tailwind.Size.rem 1.0))); 69 Tailwind.Spacing.(to_class (py (Tailwind.Size.rem 0.5))); 70 ] 71 | `Sm -> Tailwind.Css.tw [ 72 Tailwind.Spacing.(to_class (px (Tailwind.Size.rem 0.75))); 73 Tailwind.Spacing.(to_class (py (Tailwind.Size.rem 0.375))); 74 ] 75 | `Lg -> Tailwind.Css.tw [ 76 Tailwind.Spacing.(to_class (px (Tailwind.Size.rem 2.0))); 77 Tailwind.Spacing.(to_class (py (Tailwind.Size.rem 0.75))); 78 ] 79 | `Icon -> Tailwind.Css.tw [ 80 Tailwind.Layout.(to_class (width (Tailwind.Size.rem 2.5))); 81 Tailwind.Layout.(to_class (height (Tailwind.Size.rem 2.5))); 82 ] 83 84let state_classes = function 85 | `Default -> Tailwind.Css.empty 86 | `Loading -> Tailwind.Css.tw [ 87 Tailwind.Css.make "cursor-not-allowed"; 88 Tailwind.Effects.(to_class (opacity 75)); 89 ] 90 | `Disabled -> Tailwind.Css.tw [ 91 Tailwind.Css.make "cursor-not-allowed"; 92 Tailwind.Effects.(to_class (opacity 50)); 93 ] 94 95let make ?(variant=`Primary) ?(size=`Default) ?(state=`Default) ?icon ?(icon_position=`Left) ?classes ?attributes ~children () = { 96 variant; 97 size; 98 state; 99 icon; 100 icon_position; 101 classes; 102 attributes = (match attributes with Some a -> a | None -> []); 103 children; 104} 105 106let to_html button = 107 let button_classes = Tailwind.Css.tw [ 108 base_button_classes; 109 variant_classes button.variant; 110 size_classes button.size; 111 state_classes button.state; 112 (match button.classes with Some c -> c | None -> Tailwind.Css.empty); 113 ] in 114 115 let base_attrs = [classes_attr button_classes] in 116 let state_attrs = match button.state with 117 | `Disabled -> [At.disabled] 118 | _ -> [] 119 in 120 let custom_attrs = List.map (fun (k, v) -> At.v k v) button.attributes in 121 let all_attrs = base_attrs @ state_attrs @ custom_attrs in 122 123 let loading_spinner = match button.state with 124 | `Loading -> [El.span ~at:[classes_attr (Tailwind.Css.tw [ 125 Tailwind.Css.make "animate-spin"; 126 Tailwind.Spacing.(to_class (mr (Tailwind.Size.rem 0.5))); 127 ])] [El.txt ""]] 128 | _ -> [] 129 in 130 131 let icon_element = match button.icon with 132 | Some icon -> [icon] 133 | None -> [] 134 in 135 136 let content = match button.icon_position with 137 | `Left -> loading_spinner @ icon_element @ button.children 138 | `Right -> loading_spinner @ button.children @ icon_element 139 in 140 141 El.button ~at:all_attrs content 142 143(* Shorthand functions *) 144let primary ?size ?state ?icon ?classes ~children () = 145 let btn = make ~variant:`Primary ?size ?state ?icon ?classes ~children () in 146 to_html btn 147 148let secondary ?size ?state ?icon ?classes ~children () = 149 let btn = make ~variant:`Secondary ?size ?state ?icon ?classes ~children () in 150 to_html btn 151 152let outline ?size ?state ?icon ?classes ~children () = 153 let btn = make ~variant:`Outline ?size ?state ?icon ?classes ~children () in 154 to_html btn 155 156let ghost ?size ?state ?icon ?classes ~children () = 157 let btn = make ~variant:`Ghost ?size ?state ?icon ?classes ~children () in 158 to_html btn 159 160let link ?size ?state ?classes ~children () = 161 let btn = make ~variant:`Link ?size ?state ?classes ~children () in 162 to_html btn