onasty/web/src/Components/Form.elm (view raw)
Oleksandr Smirnov
Oleksandr Smirnov
olexsmir@gmail.com feat(web): add oauth login (#213)..., 8 months ago
olexsmir@gmail.com feat(web): add oauth login (#213)..., 8 months ago
| 1 | module Components.Form exposing (ButtonStyle(..), CanBeClicked, InputStyle(..), button, input, oauthButton, 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 | | PrimaryReverse CanBeClicked |
| 90 | | Secondary CanBeClicked |
| 91 | | SecondaryDisabled CanBeClicked |
| 92 | | SecondaryDanger |
| 93 | | OauthButton CanBeClicked |
| 94 | |
| 95 | |
| 96 | button : { text : String, disabled : Bool, onClick : msg, style : ButtonStyle } -> Html msg |
| 97 | button opts = |
| 98 | H.button |
| 99 | [ A.type_ "button" |
| 100 | , E.onClick opts.onClick |
| 101 | , A.class (buttonStyleToClass opts.style "") |
| 102 | , A.disabled opts.disabled |
| 103 | ] |
| 104 | [ H.text opts.text ] |
| 105 | |
| 106 | |
| 107 | submitButton : { text : String, disabled : Bool, class : String, style : ButtonStyle } -> Html msg |
| 108 | submitButton opts = |
| 109 | H.button |
| 110 | [ A.type_ "submit" |
| 111 | , A.class (buttonStyleToClass opts.style opts.class) |
| 112 | , A.disabled opts.disabled |
| 113 | ] |
| 114 | [ H.text opts.text ] |
| 115 | |
| 116 | |
| 117 | oauthButton : { text : String, disabled : Bool, onClick : msg, iconURL : String } -> Html msg |
| 118 | oauthButton { text, disabled, onClick, iconURL } = |
| 119 | H.button |
| 120 | [ A.type_ "button" |
| 121 | , A.class (buttonStyleToClass (OauthButton (not disabled)) "mt-2") |
| 122 | , A.disabled disabled |
| 123 | , E.onClick onClick |
| 124 | ] |
| 125 | [ H.div [ A.class "flex" ] |
| 126 | [ H.img [ A.class "w-5 h-5 mr-3", A.src iconURL ] [] |
| 127 | , H.text text |
| 128 | ] |
| 129 | ] |
| 130 | |
| 131 | |
| 132 | buttonStyleToClass : ButtonStyle -> String -> String |
| 133 | buttonStyleToClass style appendClasses = |
| 134 | case style of |
| 135 | Primary canBeClicked -> |
| 136 | getButtonClasses canBeClicked |
| 137 | appendClasses |
| 138 | "px-6 py-2 bg-gray-300 text-gray-500 rounded-md cursor-not-allowed transition-colors" |
| 139 | "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" |
| 140 | |
| 141 | PrimaryReverse canBeClicked -> |
| 142 | getButtonClasses canBeClicked |
| 143 | appendClasses |
| 144 | "items-center gap-3 px-3 py-2 text-left rounded-md transition-colors bg-black text-white" |
| 145 | "items-center gap-3 px-3 py-2 text-left rounded-md transition-colors text-gray-700 hover:bg-gray-100" |
| 146 | |
| 147 | SecondaryDanger -> |
| 148 | "text-gray-600 hover:text-red-600 transition-colors" |
| 149 | |
| 150 | Secondary canBeClicked -> |
| 151 | getButtonClasses canBeClicked |
| 152 | appendClasses |
| 153 | "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" |
| 154 | "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" |
| 155 | |
| 156 | SecondaryDisabled canBeClicked -> |
| 157 | getButtonClasses canBeClicked |
| 158 | appendClasses |
| 159 | "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" |
| 160 | "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" |
| 161 | |
| 162 | OauthButton canBeClicked -> |
| 163 | getButtonClasses canBeClicked |
| 164 | appendClasses |
| 165 | "w-full flex items-center justify-center gap-3 px-4 py-2 rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 bg-white hover:bg-gray-50 border border-gray-300 text-gray-700" |
| 166 | "w-full flex items-center justify-center gap-3 px-4 py-2 rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 bg-white hover:bg-gray-50 border border-gray-300 text-gray-700 opacity-50 cursor-not-allowed" |
| 167 | |
| 168 | |
| 169 | getButtonClasses : Bool -> String -> String -> String -> String |
| 170 | getButtonClasses cond extend whenTrue whenFalse = |
| 171 | let |
| 172 | cls = |
| 173 | if String.isEmpty extend then |
| 174 | "" |
| 175 | |
| 176 | else |
| 177 | " " ++ extend |
| 178 | in |
| 179 | if cond then |
| 180 | whenTrue ++ cls |
| 181 | |
| 182 | else |
| 183 | whenFalse ++ cls |