-
Notifications
You must be signed in to change notification settings - Fork 132
/
Button.elm
435 lines (336 loc) · 10.7 KB
/
Button.elm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
module Material.Button
exposing
( Model
, defaultModel
, Msg
, update
, view
, flat
, raised
, fab
, minifab
, icon
, plain
, colored
, primary
, accent
, ripple
, disabled
, Property
, link
, render
, type_
, react
)
{-| From the [Material Design Lite documentation](http://www.getmdl.io/components/#buttons-section):
> The Material Design Lite (MDL) button component is an enhanced version of the
> standard HTML `<button>` element. A button consists of text and/or an image that
> clearly communicates what action will occur when the user clicks or touches it.
> The MDL button component provides various types of buttons, and allows you to
> add both display and click effects.
>
> Buttons are a ubiquitous feature of most user interfaces, regardless of a
> site's content or function. Their design and use is therefore an important
> factor in the overall user experience. See the button component's Material
> Design specifications page for details.
>
> The available button display types are flat (default), raised, fab, mini-fab,
> and icon; any of these types may be plain (light gray) or colored, and may be
> initially or programmatically disabled. The fab, mini-fab, and icon button
> types typically use a small image as their caption rather than text.
See also the
[Material Design Specification]([https://www.google.com/design/spec/components/buttons.html).
Refer to
[this site](https://debois.github.io/elm-mdl/#buttons)
for a live demo.
# Render
@docs render
# Options
@docs Property
@docs type_
## Appearance
@docs plain, colored, primary, accent
@docs ripple, disabled
## Type
Refer to the
[Material Design Specification](https://www.google.com/design/spec/components/buttons.html)
for details about what type of buttons are appropriate for which situations.
@docs flat, raised, fab, minifab, icon
@docs link
# Elm architecture
@docs Model, defaultModel, Msg, update, view
# Internal use
@docs react
-}
import Html exposing (..)
import Html.Attributes exposing (..)
import Material.Component as Component exposing (Indexed, Index)
import Material.Helpers as Helpers
import Material.Options as Options exposing (cs, when)
import Material.Options.Internal as Internal
import Material.Ripple as Ripple
-- MODEL
{-|
-}
type alias Model =
Ripple.Model
{-|
-}
defaultModel : Model
defaultModel =
Ripple.model
-- ACTION, UPDATE
{-|
-}
type alias Msg =
Ripple.Msg
{-| Component update.
-}
update : Msg -> Model -> ( Model, Cmd Msg )
update action =
Ripple.update action
-- VIEW
type alias Config =
{ ripple : Bool
, link : Bool
}
defaultConfig : Config
defaultConfig =
{ ripple = False
, link = False
}
{-| Properties for Button options.
-}
type alias Property m =
Options.Property Config m
{-| Turn the `Button` from `button`-element to an `a`-element.
This allows for a button that looks like a button but can also
perform link actions.
Button.render Mdl [0] model.mdl
[ Button.link
, Options.attribute <|
Html.Attributes.href "#some-url"
]
[ text "Link Button" ]
-}
link : String -> Property m
link href =
Options.many
[ Internal.option (\options -> { options | link = True })
, Internal.attribute <| Html.Attributes.href href
]
{-| Set button to ripple when clicked.
-}
ripple : Property m
ripple =
(\options -> { options | ripple = True })
|> Internal.option
{-| Set button to "disabled".
-}
disabled : Property m
disabled =
Internal.attribute <| Html.Attributes.disabled True
{-| Plain, uncolored button (default).
-}
plain : Property m
plain =
Options.nop
{-| Color button with primary or accent color depending on button type.
-}
colored : Property m
colored =
cs "mdl-button--colored"
{-| Color button with primary color.
-}
primary : Property m
primary =
cs "mdl-button--primary"
{-| Color button with accent color.
-}
accent : Property m
accent =
cs "mdl-button--accent"
{-| Sets the type of the button e.g.
Button.render ...
[ Button.type' "submit"
]
[ ... ]
-}
type_ : String -> Property m
type_ =
Html.Attributes.type_ >> Internal.attribute
{- Ladies & Gentlemen: My nastiest hack ever.
Buttons with ripples are implemented as
<button> ... <span> ... </span></button>
elements. The button must blur itself when the mouse goes up or leaves, and the
(ripple) span must clear its animation state under the same events.
Unfortunately, on firefox, mousedown, mouseleave etc. don't trigger on elements
inside buttons, so we have to install all handlers on button. But the only way
I know of to blur something is the `Helpers.blurOn` trick, which seemingly precludes
also doing anything on the elm side. We work around this by manually triggering
a 'touchcancel' event on the inner span.
Obviously, once Elm gets proper support for controlling focus/blur, we can dispense
with all this nonsense.
-}
blurAndForward : String -> Attribute m
blurAndForward event =
Html.Attributes.attribute
("on" ++ event)
-- NOTE: IE Does not properly support 'new Event()'. This is a temporary workaround
"this.blur(); (function(self) { var e = document.createEvent('Event'); e.initEvent('touchcancel', true, true); self.lastChild.dispatchEvent(e); }(this));"
{-| Component view function.
-}
view : (Msg -> m) -> Model -> List (Property m) -> List (Html m) -> Html m
view lift model config html =
let
summary =
Internal.collect defaultConfig config
listeners =
Options.many
[ Ripple.down lift "mousedown"
, Ripple.down lift "touchstart"
, Ripple.up lift "touchcancel"
, Ripple.up lift "mouseup"
, Ripple.up lift "blur"
, Ripple.up lift "mouseleave"
]
in
Internal.apply summary
(if summary.config.link then Html.a else Html.button)
[ cs "mdl-button"
, cs "mdl-js-button"
, cs "mdl-js-ripple-effect" |> when summary.config.ripple
, listeners
]
[ Helpers.blurOn "mouseup"
, Helpers.blurOn "mouseleave"
, Helpers.blurOn "touchend"
]
(if summary.config.ripple then
List.concat
[ html
, [ Html.map lift <|
Ripple.view_
[ class "mdl-button__ripple-container" ]
model
]
]
else
html
)
{-| From the
[Material Design Specification](https://www.google.com/design/spec/components/buttons.html#buttons-flat-buttons):
> Flat buttons are printed on material. They do not lift, but fill with color on
> press.
>
> Use flat buttons in the following locations:
>
> - On toolbars
> - In dialogs, to unify the button action with the dialog content
> - Inline, with padding, so the user can easily find them
Example use (uncolored flat button, assuming properly setup model):
import Material.Button as Button
flatButton : Model -> Html
flatButton model =
Button.render Mdl [0] model.mdl
[ Button.flat ]
[ text "Click me!" ]
-}
flat : Property m
flat =
Options.nop
{-| From the
[Material Design Specification](https://www.google.com/design/spec/components/buttons.html#buttons-raised-buttons):
> Raised buttons add dimension to mostly flat layouts. They emphasize functions
> on busy or wide spaces.
>
> Raised buttons behave like a piece of material resting on another sheet –
> they lift and fill with color on press.
Example use (colored raised button, assuming properly setup model):
import Material.Button as Button
raisedButton : Model -> Html
raisedButton model =
Button.render Mdl [0] model.mdl
[ Button.raised ]
[ text "Click me!" ]
-}
raised : Property m
raised =
cs "mdl-button--raised"
{-| Floating Msg Button. From the
[Material Design Specification](https://www.google.com/design/spec/components/buttons-floating-action-button.html):
> Floating action buttons are used for a promoted action. They are distinguished
> by a circled icon floating above the UI and have motion behaviors that include
> morphing, launching, and a transferring anchor point.
>
> Floating action buttons come in two sizes:
>
> - Default size: For most use cases
> - Mini size: Only used to create visual continuity with other screen elements
This constructor produces the default size, use `minifab` to get the mini-size.
Example use (colored with a '+' icon):
import Material.Button as Button
import Material.Icon as Icon
fabButton : Model -> Html
fabButton model =
Button.render Mdl [0] model.mdl
[ Button.fab ]
[ Icon.i "add" ]
-}
fab : Property m
fab =
cs "mdl-button--fab"
{-| Mini-sized variant of a Floating Msg Button; refer to `fab`.
-}
minifab : Property m
minifab =
cs "mdl-button--mini-fab"
{-| The [Material Design Lite implementation](https://www.getmdl.io/components/index.html#buttons-section)
also offers an "icon button", which we
re-implement here. See also
[Material Design Specification](http://www.google.com/design/spec/components/buttons.html#buttons-toggle-buttons).
Example use (no color, displaying a '+' icon):
import Material.Button as Button
import Material.Icon as Icon
iconButton : Html
iconButton =
Button.render Mdl [0] model.mdl
[ Button.icon ]
[ Icon.i "add" ]
-}
icon : Property m
icon =
cs "mdl-button--icon"
-- COMPONENT
type alias Store s =
{ s | button : Indexed Model }
( get, set ) =
Component.indexed .button (\x y -> { y | button = x }) Ripple.model
{-| Component react function (update variant). Internal use only.
-}
react :
(Component.Msg Msg textfield menu layout toggles tooltip tabs dispatch -> m)
-> Msg
-> Index
-> Store s
-> ( Maybe (Store s), Cmd m )
react =
Component.react get set Component.ButtonMsg (Component.generalise update)
{-| Component render. Below is an example, assuming boilerplate setup as
indicated in `Material` and a user message `PollMsg`.
Button.render Mdl [0] model.mdl
[ Button.raised
, Button.ripple
, Options.onClick PollMsg
]
[ text "Fetch new" ]
-}
render :
(Component.Msg Msg textfield menu snackbar toggles tooltip tabs dispatch -> m)
-> Index
-> { a | button : Indexed Ripple.Model }
-> List (Property m)
-> List (Html m)
-> Html m
render =
Component.render get view Component.ButtonMsg