elm-pages-v3-beta/examples/pokedex/src/Page/TailwindForm.elm

502 lines
15 KiB
Elm
Raw Normal View History

module Page.TailwindForm exposing (Data, Model, Msg, page)
import Css
import Css.Global
import DataSource exposing (DataSource)
import Dict exposing (Dict)
import Form exposing (Form)
import Head
import Head.Seo as Seo
import Html.Styled as Html exposing (Html)
import Html.Styled.Attributes as Attr exposing (css)
import Icon
import Page exposing (Page, PageWithState, StaticPayload)
import PageServerResponse exposing (PageServerResponse)
import Pages.PageUrl exposing (PageUrl)
import Pages.Url
import Server.Request as Request exposing (Request)
import Shared
import Tailwind.Breakpoints as Bp
import Tailwind.Utilities as Tw
import View exposing (View)
type alias Model =
{}
type alias Msg =
Never
type alias RouteParams =
{}
type alias User =
{ first : String
, last : String
, username : String
, email : String
, birthDay : String
}
defaultUser : User
defaultUser =
{ first = "Jane"
, last = "Doe"
, username = "janedoe"
, email = "janedoe@example.com"
, birthDay = "1969-07-20"
}
errorsView : List String -> Html.Html msg
errorsView errors =
case errors of
first :: rest ->
Html.div []
[ Html.ul
[ Attr.style "border" "solid red"
]
(List.map
(\error ->
Html.li []
[ Html.text error
]
)
(first :: rest)
)
]
[] ->
Html.div [] []
styleAttrs attrs =
List.map Attr.fromUnstyled attrs
--usernameInput : Html msg
usernameInput { toInput, toLabel, errors } =
Html.div []
[ Html.div
[ css
[ Bp.sm
[ Tw.grid
, Tw.grid_cols_3
, Tw.gap_4
, Tw.items_start
, Tw.border_t
, Tw.border_gray_200
, Tw.pt_5
]
]
]
[ Html.label
(styleAttrs toInput
++ [ Attr.for "username"
, css
[ Tw.block
, Tw.text_sm
, Tw.font_medium
, Tw.text_gray_700
, Bp.sm
[ Tw.mt_px
, Tw.pt_2
]
]
]
)
[ Html.text "Username" ]
, Html.div
[ css
[ Tw.mt_1
, Bp.sm
[ Tw.mt_0
, Tw.col_span_2
]
]
]
[ Html.div
[ css
[ Tw.max_w_lg
, Tw.flex
, Tw.rounded_md
, Tw.shadow_sm
, Tw.relative
]
]
[ Html.span
[ css
[ Tw.inline_flex
, Tw.items_center
, Tw.px_3
, Tw.rounded_l_md
, Tw.border
, Tw.border_r_0
, Tw.border_gray_300
, Tw.bg_gray_50
, Tw.text_gray_500
, Bp.sm
[ Tw.text_sm
]
]
]
[ Html.text "workcation.com/" ]
, Html.input
(styleAttrs toInput
++ [ Attr.type_ "text"
, Attr.name "username"
, Attr.id "username"
, Attr.attribute "autocomplete" "username"
, css
[ Tw.flex_1
, Tw.block
, Tw.w_full
, Tw.min_w_0
, Tw.rounded_none
, Tw.rounded_r_md
, Tw.border_gray_300
, Css.focus
[ Tw.ring_indigo_500
, Tw.border_indigo_500
]
, Bp.sm
[ Tw.text_sm
]
]
]
)
[]
, Html.div
[ css
[ Tw.absolute
, Tw.inset_y_0
, Tw.right_0
, Tw.pr_3
, Tw.flex
, Tw.items_center
, Tw.pointer_events_none
]
]
[ if errors |> List.isEmpty then
Html.text ""
else
Icon.error
]
]
]
]
, Html.div []
[ Html.p
[ css
[ Tw.mt_2
, Tw.text_sm
, Tw.text_red_600
]
]
[ errors |> String.join "\n" |> Html.text ]
]
]
inputWithErrors =
Html.div []
[ Html.label
[ Attr.for "email"
, css
[ Tw.block
, Tw.text_sm
, Tw.font_medium
, Tw.text_gray_700
]
]
[ Html.text "Email" ]
, Html.div
[ css
[ Tw.mt_1
, Tw.relative
, Tw.rounded_md
, Tw.shadow_sm
]
]
[ Html.input
[ Attr.type_ "email"
, Attr.name "email"
, Attr.id "email"
, css
[ Tw.block
, Tw.w_full
, Tw.pr_10
, Tw.border_red_300
, Tw.text_red_900
, Tw.placeholder_red_300
, Tw.rounded_md
, Css.focus
[ Tw.outline_none
, Tw.ring_red_500
, Tw.border_red_500
]
, Bp.sm
[ Tw.text_sm
]
]
, Attr.placeholder "you@example.com"
, Attr.value "adamwathan"
, Attr.attribute "aria-invalid" "true"
, Attr.attribute "aria-describedby" "email-error"
]
[]
, Html.div
[ css
[ Tw.absolute
, Tw.inset_y_0
, Tw.right_0
, Tw.pr_3
, Tw.flex
, Tw.items_center
, Tw.pointer_events_none
]
]
[ Icon.error
]
]
, Html.div []
[ Html.p
[ css
[ Tw.mt_2
, Tw.text_sm
, Tw.text_red_600
]
, Attr.id "email-error"
]
[ Html.text "Your password must be less than 4 characters." ]
]
]
form : User -> Form User (Html Never)
form user =
Form.succeed User
|> Form.required
(Form.input
"first"
(\{ toInput, toLabel, errors } ->
Html.div []
[ errorsView errors
, Html.label (styleAttrs toLabel)
[ Html.text "First"
]
, Html.input (styleAttrs toInput) []
]
)
|> Form.withInitialValue user.first
)
|> Form.required
(Form.input
"last"
(\{ toInput, toLabel, errors } ->
Html.div []
[ errorsView errors
, Html.label (styleAttrs toLabel)
[ Html.text "Last"
]
, Html.input (styleAttrs toInput) []
]
)
|> Form.withInitialValue user.last
)
|> Form.required
(Form.input "username" usernameInput
|> Form.withInitialValue user.username
|> Form.withServerValidation
(\username ->
if username == "asdf" then
DataSource.succeed [ "username is taken" ]
else
DataSource.succeed []
)
)
|> Form.required
(Form.input
"email"
(\{ toInput, toLabel, errors } ->
Html.div []
[ errorsView errors
, Html.label (styleAttrs toLabel)
[ Html.text "Email"
]
, Html.input (styleAttrs toInput) []
]
)
|> Form.withInitialValue user.email
)
|> Form.required
(Form.date
"dob"
(\{ toInput, toLabel, errors } ->
Html.div []
[ errorsView errors
, Html.label (styleAttrs toLabel)
[ Html.text "Date of Birth"
]
, Html.input (styleAttrs toInput) []
]
)
|> Form.withInitialValue user.birthDay
|> Form.withMinDate "1900-01-01"
|> Form.withMaxDate "2022-01-01"
)
|> Form.append
(Form.submit
(\{ attrs } ->
Html.button
(styleAttrs attrs
++ [ css
[ Tw.ml_3
, Tw.inline_flex
, Tw.justify_center
, Tw.py_2
, Tw.px_4
, Tw.border
, Tw.border_transparent
, Tw.shadow_sm
, Tw.text_sm
, Tw.font_medium
, Tw.rounded_md
, Tw.text_white
, Tw.bg_indigo_600
, Css.focus
[ Tw.outline_none
, Tw.ring_2
, Tw.ring_offset_2
, Tw.ring_indigo_500
]
, Css.hover
[ Tw.bg_indigo_700
]
, Tw.cursor_pointer
]
]
)
[ Html.text "Save" ]
)
)
page : Page RouteParams Data
page =
Page.serverRender
{ head = head
, data = data
}
|> Page.buildNoState { view = view }
type alias Data =
{ user : Maybe User
, errors : Maybe (Dict String { raw : String, errors : List String })
}
data : RouteParams -> Request (DataSource (PageServerResponse Data))
data routeParams =
Request.oneOf
[ Form.toRequest2 (form defaultUser)
|> Request.map
(\userOrErrors ->
userOrErrors
|> DataSource.map
(\result ->
(case result of
Ok ( user, errors ) ->
{ user = Just user
, errors = Just errors
}
Err errors ->
{ user = Nothing
, errors = Just errors
}
)
|> PageServerResponse.RenderPage
)
)
, PageServerResponse.RenderPage
{ user = Nothing
, errors = Nothing
}
|> DataSource.succeed
|> Request.succeed
]
head :
StaticPayload Data RouteParams
-> List Head.Tag
head static =
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = Pages.Url.external "TODO"
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = "TODO"
, locale = Nothing
, title = "TODO title" -- metadata.title -- TODO
}
|> Seo.website
view :
Maybe PageUrl
-> Shared.Model
-> StaticPayload Data RouteParams
-> View Msg
view maybeUrl sharedModel static =
let
user : User
user =
static.data.user
|> Maybe.withDefault defaultUser
in
{ title = "Form Example"
, body =
[ Html.div
[]
[ Css.Global.global Tw.globalStyles
, static.data.user
|> Maybe.map
(\user_ ->
Html.p
[ css
[ Css.backgroundColor (Css.rgb 163 251 163)
, Tw.p_4
]
]
[ Html.text <| "Successfully received user " ++ user_.first ++ " " ++ user_.last
]
)
|> Maybe.withDefault (Html.p [] [])
, Html.h1
[]
[ Html.text <| "Edit profile " ++ user.first ++ " " ++ user.last ]
, form user
|> Form.toHtml
(\attrs children -> Html.form (List.map Attr.fromUnstyled attrs) children)
static.data.errors
]
|> Html.toUnstyled
]
}