Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Robot buttons from Mars #141

Open
andrewMacmurray opened this issue Jul 6, 2019 · 0 comments
Open

Robot buttons from Mars #141

andrewMacmurray opened this issue Jul 6, 2019 · 0 comments

Comments

@andrewMacmurray
Copy link
Member

andrewMacmurray commented Jul 6, 2019

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

  • Consistent
  • Flexible enough to allow variations
  • Constraining (not too much variation)
  • Have sensible defaults
  • Prevent misuse

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 ✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants