mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-12-22 19:31:39 +03:00
502 lines
15 KiB
Elm
502 lines
15 KiB
Elm
|
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
|
||
|
]
|
||
|
}
|