onasty/web/src/Components/Form.elm (view raw)
| 1 | module Components.Form exposing (ButtonStyle(..), CanBeClicked, InputStyle(..), button, input, submitButton) |
| 2 | |
| 3 | import Html as H exposing (Html) |
| 4 | import Html.Attributes as A |
| 5 | import Html.Events as E |
| 6 | |
| 7 | |
| 8 | |
| 9 | -- INPUT |
| 10 | |
| 11 | |
| 12 | type InputStyle |
| 13 | = Simple |
| 14 | | Complex |
| 15 | { prefix : String |
| 16 | , helpText : String |
| 17 | } |
| 18 | |
| 19 | |
| 20 | input : |
| 21 | { id : String |
| 22 | , field : field |
| 23 | , type_ : String |
| 24 | , value : String |
| 25 | , label : String |
| 26 | , placeholder : String |
| 27 | , required : Bool |
| 28 | , onInput : String -> msg |
| 29 | , style : InputStyle |
| 30 | , error : Maybe String |
| 31 | } |
| 32 | -> Html msg |
| 33 | input opts = |
| 34 | let |
| 35 | style = |
| 36 | case opts.style of |
| 37 | Simple -> |
| 38 | { prefix = H.text "", help = H.text "" } |
| 39 | |
| 40 | Complex complex -> |
| 41 | { prefix = H.span [ A.class "text-gray-500 text-md whitespace-nowrap" ] [ H.text complex.prefix ] |
| 42 | , help = H.p [ A.class "text-xs text-gray-500 mt-1" ] [ H.text complex.helpText ] |
| 43 | } |
| 44 | |
| 45 | error = |
| 46 | case opts.error of |
| 47 | Nothing -> |
| 48 | { element = H.text "", inputAdditionalClasses = "border-gray-300 focus:ring-black " } |
| 49 | |
| 50 | Just err -> |
| 51 | { element = H.p [ A.class "text-red-600 text-xs mt-1" ] [ H.text err ] |
| 52 | , inputAdditionalClasses = " border-red-400 focus:ring-red-500" |
| 53 | } |
| 54 | in |
| 55 | H.div [ A.class "space-y-2" ] |
| 56 | [ H.label |
| 57 | [ A.for opts.id |
| 58 | , A.class "block text-sm font-medium text-gray-700" |
| 59 | ] |
| 60 | [ H.text opts.label ] |
| 61 | , H.div [ A.class "flex items-center" ] |
| 62 | [ style.prefix |
| 63 | , H.input |
| 64 | [ A.class ("w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:border-transparent transition-colors" ++ error.inputAdditionalClasses) |
| 65 | , A.type_ opts.type_ |
| 66 | , A.value opts.value |
| 67 | , A.id opts.id |
| 68 | , A.placeholder opts.placeholder |
| 69 | , A.required opts.required |
| 70 | , E.onInput opts.onInput |
| 71 | ] |
| 72 | [] |
| 73 | ] |
| 74 | , error.element |
| 75 | , style.help |
| 76 | ] |
| 77 | |
| 78 | |
| 79 | |
| 80 | -- BUTTON |
| 81 | |
| 82 | |
| 83 | type alias CanBeClicked = |
| 84 | Bool |
| 85 | |
| 86 | |
| 87 | type ButtonStyle |
| 88 | = Primary CanBeClicked |
| 89 | | Secondary CanBeClicked |
| 90 | | SecondaryDisabled CanBeClicked |
| 91 | | SecondaryDanger |
| 92 | |
| 93 | |
| 94 | button : { text : String, disabled : Bool, onClick : msg, style : ButtonStyle } -> Html msg |
| 95 | button opts = |
| 96 | H.button |
| 97 | [ A.type_ "button" |
| 98 | , E.onClick opts.onClick |
| 99 | , A.class (buttonStyleToClass opts.style "") |
| 100 | , A.disabled opts.disabled |
| 101 | ] |
| 102 | [ H.text opts.text ] |
| 103 | |
| 104 | |
| 105 | submitButton : { text : String, disabled : Bool, class : String, style : ButtonStyle } -> Html msg |
| 106 | submitButton opts = |
| 107 | H.button |
| 108 | [ A.type_ "submit" |
| 109 | , A.class (buttonStyleToClass opts.style opts.class) |
| 110 | , A.disabled opts.disabled |
| 111 | ] |
| 112 | [ H.text opts.text ] |
| 113 | |
| 114 | |
| 115 | buttonStyleToClass : ButtonStyle -> String -> String |
| 116 | buttonStyleToClass style appendClasses = |
| 117 | case style of |
| 118 | Primary canBeClicked -> |
| 119 | getButtonClasses canBeClicked |
| 120 | appendClasses |
| 121 | "px-6 py-2 bg-gray-300 text-gray-500 rounded-md cursor-not-allowed transition-colors" |
| 122 | "px-6 py-2 bg-black text-white rounded-md hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 transition-colors" |
| 123 | |
| 124 | SecondaryDanger -> |
| 125 | "text-gray-600 hover:text-red-600 transition-colors" |
| 126 | |
| 127 | Secondary canBeClicked -> |
| 128 | getButtonClasses canBeClicked |
| 129 | appendClasses |
| 130 | "px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 transition-colors bg-green-100 border-green-300 text-green-700" |
| 131 | "px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 transition-colors border-gray-300 text-gray-700 hover:bg-gray-50" |
| 132 | |
| 133 | SecondaryDisabled canBeClicked -> |
| 134 | getButtonClasses canBeClicked |
| 135 | appendClasses |
| 136 | "w-full px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 transition-colors mt-3 border border-gray-300 text-gray-400 cursor-not-allowed" |
| 137 | "w-full px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 transition-colors mt-3 border border-gray-300 text-gray-700 hover:bg-gray-50" |
| 138 | |
| 139 | |
| 140 | getButtonClasses : Bool -> String -> String -> String -> String |
| 141 | getButtonClasses cond extend whenTrue whenFalse = |
| 142 | let |
| 143 | cls = |
| 144 | if String.isEmpty extend then |
| 145 | "" |
| 146 | |
| 147 | else |
| 148 | " " ++ extend |
| 149 | in |
| 150 | if cond then |
| 151 | whenTrue ++ cls |
| 152 | |
| 153 | else |
| 154 | whenFalse ++ cls |