dwyl / learn-elm

:rainbow: discover the beautiful programming language that makes front-end web apps a joy to build and maintain!
https://github.com/dwyl/learn-elm
481 stars 41 forks source link

Robot buttons from Mars #141

Open andrewMacmurray opened 5 years ago

andrewMacmurray commented 5 years ago

Highlighting Brian Hicks's talk at Elm in the Spring Conference:

https://www.youtube.com/watch?v=PDyWP-0H4Zo

He goes into detail about designing an API for customising buttons with some core goals in mind - the api should be

The API we end up with uses an opaque data structure to configure a button:

type Button msg
    = Button (Options msg)

type alias Options msg =
    { size : Size
    , fill : Fill
    , onClick : msg
    , text : String
    }

type Fill
    = Outline
    | Solid

type Size
    = Small
    | Medium
    | Large

It exposes a button with default options:

button : msg -> String -> Button msg
button onClick text =
    Button <| defaultOptions onClick text

defaultOptions : msg -> String -> Options msg
defaultOptions onClick text =
    { size = Medium
    , fill = Outline
    , onClick = onClick
    , text = text
    }

And exposes a set of functions to customise the options on the button, e.g:

solid : Button msg -> Button msg
solid (Button options) =
    Button { options | fill = Solid }

large : Button msg -> Button msg
large (Button options) =
    Button { options | size = Large }

The key here is these functions compose nicely (so they can be piped). We end up with a lovely API that looks like this:

myButton =
    Button.button DoSomething "Click Me!"
        |> Button.solid
        |> Button.large

When we're done with the configuring, the API exposes a function like toHtml which takes those options and makes a html button to use in a view:

toHtml : Button msg -> Html msg
toHtml (Button options) =
    Html.button
        [ onClick options.onClick
        , style "padding" <| paddingFromSize options.size
        , style "background-color" <| backgroundFromFill options.fill
        ]
        [ text options.text ]

So myButton becomes:

myButton =
    Button.button DoSomething "Click Me!"
        |> Button.solid
        |> Button.large
        |> Button.toHtml

I started using this pattern at work to implement a design guide we got from a designer: it's honestly one of the nicest APIs I've used - it's so simple, flexible and powerful and the key thing for me is that it's painless to extend when you find you need more options (it's insane how many subtle variations buttons need). I'm in love 😍.

Going to experiment with this pattern for building other UI elements (one that springs to mind is something like cards).

Kudos to Brian for his fantastic talk ✨