Last active
June 16, 2020 22:56
-
-
Save anthonny/1b6a73782a6ad94c611849b9a5d4cbbf to your computer and use it in GitHub Desktop.
Todo App
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Main exposing (main) | |
import Browser | |
import Html exposing (Html, a, div, form, input, li, text, ul) | |
import Html.Attributes exposing (class, classList, href, placeholder, style, target, value) | |
import Html.Events exposing (onClick, onInput, onSubmit) | |
import Svg | |
import Svg.Attributes as SvgAttr | |
type alias Model = | |
{ todo : String | |
, todos : List Todo | |
, filter : Filter | |
} | |
type alias Todo = | |
{ id : Int | |
, value : String | |
, status : TodoStatus | |
} | |
type TodoStatus | |
= Checked | |
| Unchecked | |
type Filter | |
= All | |
| Active | |
| Completed | |
main : Program () Model Msg | |
main = | |
Browser.element | |
{ init = init | |
, view = view | |
, update = update | |
, subscriptions = subscriptions | |
} | |
init : () -> ( Model, Cmd Msg ) | |
init _ = | |
( { todo = "", todos = [], filter = All }, Cmd.none ) | |
view : Model -> Html Msg | |
view model = | |
div [ class "w-screen h-screen flex justify-center items-center bg-gray-500 text-gray-800" ] | |
[ div | |
[ class "flex flex-col h-full" | |
, style "width" "400px" | |
, style "max-height" "80%" | |
] | |
[ div | |
[ class "flex-1 flex flex-col bg-white shadow-xl overflow-hidden" | |
] | |
[ form [ onSubmit AddTodo ] | |
[ input [ class "w-full px-4 py-4 shadow-sm", value model.todo, placeholder "Add a todo...", onInput InputChanged ] [] | |
] | |
, div [ class "flex-1 overflow-scroll" ] | |
(List.map viewTodo (filterTodosBy model.filter model.todos)) | |
, viewFooter model | |
] | |
, div [ class "mt-2 text-center text-sm text-gray-800" ] | |
[ div [] | |
[ text "Made with ❤️ by " | |
, link "https://twitter.com/anthonny_q" "@anthonny_q" | |
] | |
, div [ class "text-xs" ] | |
[ text "Propulsed by " | |
, link "https://elm-lang.org/" "Elm" | |
, text " and " | |
, link "https://www.meteor.com/" "Meteor" | |
] | |
] | |
] | |
] | |
link : String -> String -> Html msg | |
link url label = | |
a [ class "underline text-blue-600", href url, target "_blank" ] [ text label ] | |
viewTodo : Todo -> Html Msg | |
viewTodo todo = | |
div [ class "flex items-center border-b-2 border-gray-100 px-4 py-4 fill-current" ] | |
[ div [ class "text-gray-500 stroke-current cursor-pointer", onClick <| ToggleStatus todo.id ] [ statusIcon todo ] | |
, div [ class "ml-4" ] [ text todo.value ] | |
] | |
filterTodosBy : Filter -> List Todo -> List Todo | |
filterTodosBy filter todos = | |
List.filter (isFilteredTodo filter) todos | |
viewFooter : Model -> Html Msg | |
viewFooter model = | |
div [ class "flex justify-between items-center px-4 py-2 bg-gray-800 text-white" ] | |
[ div [] [ text <| activeItemsvalue model.todos ] | |
, div [] | |
[ ul [ class "flex text-gray-600" ] | |
[ li [ class "cursor-pointer", onClick (FilterBy All), classList [ ( "text-white", model.filter == All ) ] ] [ text "All" ] | |
, li [ class "cursor-pointer ml-4", onClick (FilterBy Active), classList [ ( "text-white", model.filter == Active ) ] ] [ text "Active" ] | |
, li [ class "cursor-pointer ml-4", onClick (FilterBy Completed), classList [ ( "text-white", model.filter == Completed ) ] ] [ text "Completed" ] | |
] | |
] | |
] | |
activeItemsvalue : List Todo -> String | |
activeItemsvalue todos = | |
(String.fromInt <| List.length <| List.filter (\todo -> todo.status == Unchecked) todos) ++ " items left" | |
statusIcon : Todo -> Html msg | |
statusIcon todo = | |
case todo.status of | |
Checked -> | |
checkedIcon | |
Unchecked -> | |
squareIcon | |
checkedIcon : Html msg | |
checkedIcon = | |
Svg.svg [ SvgAttr.viewBox "0 0 512 512", SvgAttr.width "24", SvgAttr.height "24" ] | |
[ Svg.path [ SvgAttr.fill "none", SvgAttr.strokeLinecap "round", SvgAttr.strokeLinejoin "round", SvgAttr.strokeWidth "32", SvgAttr.d "M352 176L217.6 336 160 272" ] | |
[] | |
, Svg.rect [ SvgAttr.fill "none", SvgAttr.x "64", SvgAttr.y "64", SvgAttr.width "384", SvgAttr.height "384", SvgAttr.rx "48", SvgAttr.ry "48", SvgAttr.strokeLinejoin "round", SvgAttr.strokeWidth "32" ] | |
[] | |
] | |
squareIcon : Html msg | |
squareIcon = | |
Svg.svg [ SvgAttr.viewBox "0 0 512 512", SvgAttr.width "24", SvgAttr.height "24" ] | |
[ Svg.path [ SvgAttr.d "M416 448H96a32.09 32.09 0 01-32-32V96a32.09 32.09 0 0132-32h320a32.09 32.09 0 0132 32v320a32.09 32.09 0 01-32 32z", SvgAttr.fill "none", SvgAttr.strokeLinecap "round", SvgAttr.strokeLinejoin "round", SvgAttr.strokeWidth "32" ] | |
[] | |
] | |
type Msg | |
= InputChanged String | |
| AddTodo | |
| ToggleStatus Int | |
| FilterBy Filter | |
update : Msg -> Model -> ( Model, Cmd Msg ) | |
update msg model = | |
case msg of | |
InputChanged value -> | |
( { model | todo = value }, Cmd.none ) | |
AddTodo -> | |
if String.isEmpty (String.trim model.todo) then | |
( model, Cmd.none ) | |
else | |
( { model | todo = "", todos = { id = List.length model.todos + 1, value = model.todo, status = Unchecked } :: model.todos }, Cmd.none ) | |
ToggleStatus todoId -> | |
let | |
updateTodo todo = | |
if todo.id == todoId then | |
{ todo | status = toggleTodoStatus todo.status } | |
else | |
todo | |
in | |
( { model | todos = List.map updateTodo model.todos }, Cmd.none ) | |
FilterBy selectedFilter -> | |
( { model | filter = selectedFilter }, Cmd.none ) | |
isFilteredTodo : Filter -> Todo -> Bool | |
isFilteredTodo filter todo = | |
case ( filter, todo.status ) of | |
( All, _ ) -> | |
True | |
( Completed, Checked ) -> | |
True | |
( Active, Unchecked ) -> | |
True | |
_ -> | |
False | |
toggleTodoStatus : TodoStatus -> TodoStatus | |
toggleTodoStatus status = | |
case status of | |
Checked -> | |
Unchecked | |
Unchecked -> | |
Checked | |
subscriptions : Model -> Sub Msg | |
subscriptions _ = | |
Sub.none |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment