Components

This commit is contained in:
Søren Debois 2016-04-08 15:51:45 +02:00
parent 1fdcc78ba7
commit 02ac83a9f7
10 changed files with 470 additions and 143 deletions

View file

@ -44,7 +44,8 @@ reset model =
{ model | count = 0 }
update : Action -> Model -> (Model, Effects.Effects Action)
update : Action -> Model -> (Model, Effects Action)
update action model =
case Debug.log "" action of
IncreaseButtonAction action' ->

View file

@ -66,7 +66,7 @@ update action model =
case Debug.log "Action: " action of
LayoutAction a -> lift .layout (\m x->{m|layout =x}) LayoutAction Layout.update a model
ButtonsAction a -> lift .buttons (\m x->{m|buttons =x}) ButtonsAction Demo.Buttons.update a model
TextfieldAction a -> lift' .textfields (\m x->{m|textfields=x}) Demo.Textfields.update a model
TextfieldAction a -> lift .textfields (\m x->{m|textfields=x}) TextfieldAction Demo.Textfields.update a model
SnackbarAction a -> lift .snackbar (\m x->{m|snackbar =x}) SnackbarAction Demo.Snackbar.update a model
--TemplateAction a -> lift .template (\m x->{m|template =x}) TemplateAction Demo.Template.update a model
@ -115,7 +115,7 @@ tabs =
[Demo.Textfields.view (Signal.forwardTo addr TextfieldAction) model.textfields])
, ("Buttons", \addr model ->
[Demo.Buttons.view (Signal.forwardTo addr ButtonsAction) model.buttons])
, ("Grid", \addr model -> Demo.Grid.view)
, ("Grid", \addr model -> [ Demo.Grid.view ])
, ("Badges", \addr model -> Demo.Badges.view )
{-
, ("Template", \addr model ->

View file

@ -10,6 +10,7 @@ import Material.Grid as Grid
import Material.Icon as Icon
import Material.Style exposing (Style)
import Demo.Page as Page
-- MODEL
@ -132,9 +133,39 @@ view addr model =
]
)
)
|> (\contents ->
div []
[ Grid.grid [] contents
]
)
|> Grid.grid []
|> flip (::) []
|> Page.body "Buttons" srcUrl intro references
intro : Html
intro =
Page.fromMDL "https://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.
"""
srcUrl : String
srcUrl =
"https://github.com/debois/elm-mdl/blob/master/examples/Demo/Buttons.elm"
references : List (String, String)
references =
[ Page.package "http://package.elm-lang.org/packages/debois/elm-mdl/latest/Material-Button"
, Page.mds "https://www.google.com/design/spec/components/buttons.html"
, Page.mdl "https://www.getmdl.io/components/#buttons-section"
]

View file

@ -1,10 +1,14 @@
module Demo.Grid where
import Html exposing (..)
import Array
import Material.Grid exposing (..)
import Material.Style exposing (Style, css)
import Material.Color as Color
import Html exposing (..)
import Markdown
import Demo.Page as Page
-- Cell styling
@ -32,31 +36,36 @@ std = democell 200
-- Grid
view : List Html
color : Int -> Style
color k =
Array.get (k % Array.length Color.palette) Color.palette
|> Maybe.withDefault Color.Teal
|> flip Color.color Color.S500
|> Color.background
view : Html
view =
[ intro
, [1..12]
|> List.map (\i -> small [size All 1] [text "1"])
[ [1..12]
|> List.map (\i -> small [size All 1, color i] [text "1"])
|> grid []
, [1 .. 3]
|> List.map (\i -> std [size All 4] [text <| "4"])
|> List.map (\i -> std [size All 4, color i] [text <| "4"])
|> grid []
, [ std [size All 6] [text "6"]
, std [size All 4] [text "4"]
, std [size All 2] [text "2"]
, [ std [size All 6, color 16] [text "6"]
, std [size All 4, color 17] [text "4"]
, std [size All 2, color 18] [text "2"]
] |> grid []
, [ std [size All 6, size Tablet 8] [text "6 (8 tablet)"]
, std [size All 4, size Tablet 6] [text "4 (6 tablet)"]
, std [size All 2, size Phone 4] [text "2 (4 phone)"]
, [ std [size All 6, size Tablet 8, color 19] [text "6 (8 tablet)"]
, std [size All 4, size Tablet 6, color 20] [text "4 (6 tablet)"]
, std [size All 2, size Phone 4, color 21] [text "2 (4 phone)"]
] |> grid []
]
|> Page.body "Grid" srcUrl intro references
intro : Html
intro = """
From the
[Material Design Lite documentation](http://www.getmdl.io/components/#layout-section/grid):
intro =
Page.fromMDL "http://www.getmdl.io/components/#layout-section/grid" """
> The Material Design Lite (MDL) grid component is a simplified method for laying
> out content for multiple screen sizes. It reduces the usual coding burden
> required to correctly display blocks of content in a variety of display
@ -73,15 +82,17 @@ From the
> of columns for the current screen size, it takes up the entirety of its
> row.
#### See also
Resize your browser-window to observe the effect on the Grid below.
"""
- [Demo source code](https://github.com/debois/elm-mdl/blob/master/examples/Demo/Grid.elm)
- [elm-mdl package documentation](http://package.elm-lang.org/packages/debois/elm-mdl/latest/Material-Grid)
- [Material Design Specification](https://www.google.com/design/spec/layout/responsive-ui.html#responsive-ui-grid)
- [Material Design Lite documentation](http://www.getmdl.io/components/#layout-section/grid)
#### Demo
""" |> Markdown.toHtml
srcUrl : String
srcUrl =
"https://github.com/debois/elm-mdl/blob/master/examples/Demo/Grid.elm"
references : List (String, String)
references =
[ Page.package "http://package.elm-lang.org/packages/debois/elm-mdl/latest/Material-Grid"
, Page.mds "https://www.google.com/design/spec/layout/responsive-ui.html#responsive-ui-grid"
, Page.mdl "http://www.getmdl.io/components/#layout-section/grid"
]

View file

@ -14,6 +14,7 @@ import Material.Style as Style exposing (styled, cs, css, attribute)
import Material.Button as Button
import Material.Color as Color
import Material.Icon as Icon
import Material.Elevation as Elevation
-- REFERENCES
@ -78,24 +79,28 @@ fromMDS =
title : String -> Html
title t =
Style.div
Style.styled Html.h1
[ Color.text Color.primary
, cs "mdl-typography--display-4"
--, cs "mdl-typography--display-4"
-- TODO. Typography module
]
[]
[ text t ]
demoTitle : Html
demoTitle =
Style.div
div [] []
{-
Style.div
[ Color.text Color.primary
, cs "mdl-typography--display-1"
, css "text-align" "right"
, css "padding" ".5em"
, cs "mdl-typography--display2"
-- TODO. Typography module
]
[ text "Demo" ]
-}
-- VIEW SOURCE BUTTON
@ -110,6 +115,7 @@ fab url =
[ css "position" "fixed"
, css "right" "72px"
, css "bottom" "72px"
, css "z-index" "9999"
, Button.colored
--, attribute (href srcUrl)
, attribute (Html.Attributes.attribute "onclick" ("alert('foo!');")) --("window.location.href = '" ++ srcUrl ++ "';") )
@ -123,17 +129,20 @@ fab url =
body : String -> String -> Html -> List (String, String) -> List Html -> Html
body t srcUrl contents links demo =
div []
( title t
:: grid []
[ cell [ size All 6, size Phone 4 ] [ contents ]
, cell
[ size All 5, offset Desktop 1, size Phone 4, align Top ]
( references <| ("Demo source", srcUrl) :: links )
]
:: fab srcUrl
:: demoTitle
:: demo
)
[ title t
, grid []
[ cell [ size All 6, size Phone 4 ] [ contents ]
, cell
[ size All 5, offset Desktop 1, size Phone 4, align Top ]
( references <| ("Demo source", srcUrl) :: links )
]
, fab srcUrl
, Style.div
[ Elevation.shadow 2 ]
( demoTitle
:: demo
)
]

View file

@ -4,10 +4,11 @@ import Effects exposing (Effects, none)
import Html exposing (..)
import Html.Attributes exposing (class, style, key)
import Array exposing (Array)
import Time exposing (Time, millisecond)
import Material.Helpers exposing (map1st, map2nd)
import Material.Helpers exposing (map1st, map2nd, delay)
import Material.Color as Color
import Material.Style exposing (styled, cs)
import Material.Style exposing (styled, cs, css)
import Material.Snackbar as Snackbar
import Material.Button as Button exposing (Action(..))
import Material.Grid exposing (..)
@ -24,9 +25,19 @@ type alias Mdl =
Material.Model Action
type Square'
= Appearing
| Idle
| Disappearing
type alias Square =
(Int, Square')
type alias Model =
{ count : Int
, clicked : List Int
, squares : List Square
, mdl : Mdl
}
@ -34,7 +45,7 @@ type alias Model =
model : Model
model =
{ count = 0
, clicked = []
, squares = []
, mdl = Material.model
}
@ -43,9 +54,11 @@ model =
type Action
= Undo Int
| AddSnackbar
= AddSnackbar
| AddToast
| Appear Int
| Disappear Int
| Gone Int
| MDL (Material.Action Action)
@ -53,15 +66,31 @@ add : Model -> (Int -> Snackbar.Contents Action) -> (Model, Effects Action)
add model f =
let
(mdl', fx) =
Snackbar.add (f model.count) snackbarComponent model.mdl
Snackbar.add (f model.count) snackbar model.mdl
model' =
{ model
| mdl = mdl'
, count = model.count + 1
, clicked = model.count :: model.clicked
, squares = (model.count, Appearing) :: model.squares
}
in
(model', fx)
( model'
, Effects.batch
[ Effects.tick (always (Appear model.count))
, fx
]
)
mapSquare : Int -> (Square' -> Square') -> Model -> Model
mapSquare k f model =
{ model
| squares =
List.map
( \((k', sq) as s) -> if k /= k' then s else (k', f sq) )
model.squares
}
update : Action -> Model -> (Model, Effects Action)
@ -69,15 +98,25 @@ update action model =
case action of
AddSnackbar ->
add model
<| \k -> Snackbar.snackbar ("Snackbar message #" ++ toString k) "UNDO" (Undo k)
<| \k -> Snackbar.snackbar ("Snackbar message #" ++ toString k) "UNDO" (Disappear k)
AddToast ->
add model
<| \k -> Snackbar.toast <| "Toast message #" ++ toString k
Undo k ->
Appear k ->
( model |> mapSquare k (always Idle)
, none
)
Disappear k ->
( model |> mapSquare k (always Disappearing)
, delay transitionLength (Gone k)
)
Gone k ->
({ model
| clicked = List.filter ((/=) k) model.clicked
| squares = List.filter (fst >> (/=) k) model.squares
}
, none)
@ -86,6 +125,7 @@ update action model =
|> map1st (\m -> { model | mdl = m })
-- VIEW
@ -103,67 +143,121 @@ addToastButton =
[ Button.fwdClick AddToast ]
snackbarComponent : Snackbar.Instance Mdl Action
snackbarComponent =
-- TODO: Bad name
snackbar : Snackbar.Instance Mdl Action
snackbar =
Snackbar.instance MDL Snackbar.model
clickView : Model -> Int -> Html
clickView model k =
boxHeight : String
boxHeight = "48px"
boxWidth : String
boxWidth = "64px"
transitionLength : Time
transitionLength = 150 * millisecond
transitions : (String, String)
transitions =
("transition"
, "box-shadow 333ms ease-in-out 0s, "
++ "width " ++ toString transitionLength ++ "ms, "
++ "height " ++ toString transitionLength ++ "ms"
)
clickView : Model -> Square -> Html
clickView model (k, square) =
let
color =
Array.get ((k + 4) % Array.length Color.palette) Color.palette
|> Maybe.withDefault Color.Teal
|> flip Color.color Color.S500
sbmodel =
snackbarComponent.get model.mdl
selected' =
Snackbar.activeAction (snackbar.get model.mdl) == Just (Disappear k)
selected =
(k == sbmodel.seq - 1) &&
(Snackbar.isActive sbmodel /= Nothing)
(width, height, margin, selected) =
case square of
Idle ->
(boxWidth, boxHeight, "16px 16px", selected')
_ ->
("0", "0", "16px 0", False)
in
styled div
[ Color.background color
, Color.text Color.primaryContrast
-- TODO. Should have shadow styles someplace.
, Elevation.shadow (if selected then 8 else 2)
]
[ style
[ ("margin-right", "3ex")
, ("margin-bottom", "3ex")
, ("padding", "1.5ex")
, ("width", "4ex")
, ("border-radius", "2px")
div
[ style
[ ("height", boxHeight)
, ("width", width)
, ("position", "relative")
, ("display", "inline-block")
, ("text-align", "center")
, ("transition", "box-shadow 333ms ease-in-out 0s")
, ("margin", margin)
, ("transition",
"width " ++ toString transitionLength ++ "ms ease-in-out 0s, "
++ "margin " ++ toString transitionLength ++ "ms ease-in-out 0s"
)
, ("z-index", "0")
]
, key (toString k)
, key <| toString k
]
[ text <| toString k ]
[ styled div
[ Color.background color
, Color.text Color.primaryContrast
, Elevation.shadow (if selected then 8 else 2)
]
[ style
[ ("display", "inline-flex")
, ("align-items", "center")
, ("justify-content", "center")
, ("height", height)
, ("width", width)
, ("border-radius", "2px")
, transitions
, ("overflow", "hidden")
, ("box-sizing", "border-box")
, ("flex", "0 0 auto")
, ("position", "absolute")
, ("bottom", "0")
, ("left", "0")
]
]
[ div [] [ text <| toString k ] ]
]
view : Signal.Address Action -> Model -> Html
view addr model =
Page.body "Snackbar & Toast" srcUrl intro references
[ grid []
-- TODO. Buttons should be centered. Desperately need to be able
-- to add css/classes to top-level element of components (div
-- in grid, button in button, div in textfield etc.)
[ cell [ size All 2, size Phone 2, align Top ]
[ addToastButton.view addr model.mdl [] [ text "Toast" ]
[ grid [ css "margin-top" "32px" ]
[ cell
[ size All 2, size Phone 2, align Top ]
[ addToastButton.view addr model.mdl
[ Button.colored
, css "margin" "16px"
]
[ text "Toast" ]
]
, cell
[ size All 2, size Phone 2, align Top ]
[ addSnackbarButton.view addr model.mdl [] [ text "Snackbar" ]
[ addSnackbarButton.view addr model.mdl
[ Button.colored
, css "margin" "16px"
]
[ text "Snackbar" ]
]
, cell
[ size Desktop 7, size Tablet 3, size Phone 12, align Top ]
(model.clicked |> List.reverse |> List.map (clickView model))
[ size Desktop 7, offset Desktop 1
, size Tablet 3, offset Tablet 1
, size Phone 4
, align Top
]
(model.squares |> List.reverse |> List.map (clickView model))
]
, snackbarComponent.view addr model.mdl
, snackbar.view addr model.mdl
]

View file

@ -1,52 +1,215 @@
module Demo.Textfields where
import Array exposing (Array)
import Html exposing (Html)
import Effects exposing (Effects)
import Regex
import Material.Textfield as Textfield
import Material.Grid as Grid exposing (..)
import Material.Helpers exposing (map1st)
import Material
import Demo.Page as Page
type alias Model = Array Textfield.Model
-- MODEL
type alias Model =
{ mdl : Material.Model Action
, rx : (String, Regex.Regex)
}
rx0 : String
rx0 =
"[0-9]*"
setRegex : String -> (String, Regex.Regex)
setRegex str =
(str, Regex.regex str)
model : Model
model =
let t0 = Textfield.model in
[ t0
, { t0 | label = Just { text = "Labelled", float = False } }
, { t0 | label = Just { text = "Floating label", float = True }}
, { t0
model =
{ mdl = Material.model
, rx = setRegex rx0
}
-- ACTION, UPDATE
type Action
= MDL (Material.Action Action)
| Upd0 String
| Upd4 String
transferToDisabled : String -> Mdl -> Mdl
transferToDisabled str =
field3.map (\m ->
{ m
| value =
if str == "" then
""
else
"\"" ++ str ++ "\" (still disabled, though)"
})
match : String -> Regex.Regex -> Bool
match str rx =
Regex.find Regex.All rx str
|> List.any (.match >> (==) str)
checkRegex : String -> (String, Regex.Regex) -> Mdl -> Mdl
checkRegex str (rx', rx) mdl =
let
value4 = field4.get mdl |> .value
in
mdl |> field4.map (\m -> { m | error =
if match value4 rx then
Nothing
else
"Doesn't match regex ' " ++ rx' ++ "'" |> Just
})
update : Action -> Model -> (Model, Effects Action)
update action model =
case action of
MDL action' ->
Material.update MDL action' model.mdl
|> map1st (\mdl' -> { model | mdl = mdl' })
Upd0 str ->
( { model | mdl = transferToDisabled str model.mdl }
, Effects.none
)
Upd4 str ->
( { model | mdl = checkRegex str model.rx model.mdl }
, Effects.none
)
-- VIEW
m0 : Textfield.Model
m0 =
Textfield.model
type alias Mdl =
Material.Model Action
field0 : Textfield.Instance Mdl Action
field0 =
Textfield.instance 0 MDL m0
[ Textfield.fwdInput Upd0
]
field1 : Textfield.Instance Mdl Action
field1 =
Textfield.instance 1 MDL
{ m0 | label = Just { text = "Labelled", float = False } }
[]
field2 : Textfield.Instance Mdl Action
field2 =
Textfield.instance 2 MDL
{ m0
| label = Just { text = "Floating label", float = True }
}
[]
field3 : Textfield.Instance Mdl Action
field3 =
Textfield.instance 3 MDL
{ m0
| label = Just { text = "Disabled", float = False }
, isDisabled = True
}
, { t0
| label = Just { text = "With error and value", float = False }
, error = Just "The input is wrong!"
, value = "Incorrect input"
[]
field4 : Textfield.Instance Mdl Action
field4 =
Textfield.instance 4 MDL
{ m0
| label = Just { text = "With error checking", float = False }
}
]
|> Array.fromList
type Action =
Field Int Textfield.Action
update : Action -> Model -> Model
update (Field k action) fields =
Array.get k fields
|> Maybe.map (Textfield.update action)
|> Maybe.map (\field' -> Array.set k field' fields)
|> Maybe.withDefault fields
[ Textfield.fwdInput Upd4 ]
view : Signal.Address Action -> Model -> Html
view addr model =
model
|> Array.indexedMap (\k field ->
Textfield.view (Signal.forwardTo addr (Field k)) field
)
|> Array.toList
|> List.map (\x -> cell [size All 3] [x])
[ field0
, field1
, field2
, field3
, field4
]
|> List.map (\c ->
cell
[size All 4, offset Desktop 1]
[c.view addr model.mdl]
)
|> List.intersperse (cell [size All 1] [])
|> grid []
|> flip (::) []
|> Page.body "Textfields" srcUrl intro references
intro : Html
intro =
Page.fromMDL "http://www.getmdl.io/components/#textfields-section" """
> The Material Design Lite (MDL) text field component is an enhanced version of
> the standard HTML `<input type="text">` and `<input type="textarea">` elements.
> A text field consists of a horizontal line indicating where keyboard input
> can occur and, typically, text that clearly communicates the intended
> contents of the text field. The MDL text field component provides various
> types of text fields, and allows you to add both display and click effects.
>
> Text fields are a common 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 text field component's
> [Material Design specifications page](https://www.google.com/design/spec/components/text-fields.html)
> for details.
>
> The enhanced text field component has a more vivid visual look than a standard
> text field, and may be initially or programmatically disabled. There are three
> main types of text fields in the text field component, each with its own basic
> coding requirements. The types are single-line, multi-line, and expandable.
This implementation provides only single-line.
"""
srcUrl : String
srcUrl =
"https://github.com/debois/elm-mdl/blob/master/examples/Demo/Textfields.elm"
references : List (String, String)
references =
[ Page.package "http://package.elm-lang.org/packages/debois/elm-mdl/latest/Material-Textfield"
, Page.mds "https://www.google.com/design/spec/components/text-fields.html"
, Page.mdl "https://www.getmdl.io/components/#textfields-section"
]

View file

@ -3,6 +3,8 @@ module Material.Helpers where
import Html
import Html.Attributes
import Effects exposing (Effects)
import Time exposing (Time)
import Task
filter : (a -> List b -> c) -> a -> List (Maybe b) -> c
filter elem attr html =
@ -89,3 +91,12 @@ lift get set fwd update action model =
(submodel', e) = update action (get model)
in
(set model submodel', Effects.map fwd e)
delay : Time -> a -> Effects a
delay t x =
Task.sleep t
|> (flip Task.andThen) (always (Task.succeed x))
|> Effects.task

View file

@ -1,5 +1,5 @@
module Material.Snackbar
( Contents, Model, model, toast, snackbar, isActive
( Contents, Model, model, toast, snackbar, isActive, activeAction
, Action(Add, Action), update
, view
, Instance, instance, add
@ -26,7 +26,7 @@ import Time exposing (Time)
import Maybe exposing (andThen)
import Material.Component as Component exposing (Indexed)
import Material.Helpers exposing (mapFx, addFx)
import Material.Helpers exposing (mapFx, addFx, delay)
-- MODEL
@ -88,6 +88,7 @@ snackbar message actionMessage action =
{-| TODO
(Bad name)
-}
isActive : Model a -> Maybe (Contents a)
isActive model =
@ -99,6 +100,15 @@ isActive model =
Nothing
{-| TODO
-}
activeAction : Model a -> Maybe a
activeAction model =
isActive model
|> flip Maybe.andThen .action
|> Maybe.map snd
contentsOf : Model a -> Maybe (Contents a)
contentsOf model =
case model.state of
@ -121,13 +131,6 @@ type Transition
| Click
delay : Time -> a -> Effects a
delay t x =
Task.sleep t
|> (flip Task.andThen) (\_ -> Task.succeed x)
|> Effects.task
move : Transition -> Model a -> (Model a, Effects Transition)
move transition model =
case (model.state, transition) of

View file

@ -138,6 +138,7 @@ view : Signal.Address Action -> Model -> Html
view addr model =
let hasFloat = model.label |> Maybe.map .float |> Maybe.withDefault False
hasError = model.error |> Maybe.map (always True) |> Maybe.withDefault False
labelText = model.label |> Maybe.map .text
in
filter div
[ classList
@ -162,10 +163,13 @@ view addr model =
, onFocus addr Focus
]
[]
, model.label |> Maybe.map (\l ->
label [class "mdl-textfield__label"] [text l.text])
, model.error |> Maybe.map (\e ->
span [class "mdl-textfield__error"] [text e])
, Just <| label
[class "mdl-textfield__label"]
(case labelText of
Just str -> [ text str ]
Nothing -> [])
, model.error |> Maybe.map (\e ->
span [class "mdl-textfield__error"] [text e])
]