This commit is contained in:
Søren Debois 2016-04-10 10:29:03 +02:00
commit 4e9b8e6746
26 changed files with 2412 additions and 453 deletions

View file

@ -3,4 +3,7 @@ install:
- npm install -g elm - npm install -g elm
- elm-package install -y - elm-package install -y
script: script:
- elm-make --yes examples/Component.elm
- elm-make --yes examples/Component-TEA.elm
- elm-make --yes examples/Demo.elm - elm-make --yes examples/Demo.elm

View file

@ -1,6 +1,9 @@
PAGES=../elm-mdl-gh-pages PAGES=../elm-mdl-gh-pages
elm.js: comp:
elm-make examples/Component.elm --warn --output elm.js
demo:
elm-make examples/Demo.elm --warn --output elm.js elm-make examples/Demo.elm --warn --output elm.js
wip-pages : wip-pages :
@ -11,14 +14,14 @@ pages :
elm-make examples/Demo.elm --output $(PAGES)/elm.js elm-make examples/Demo.elm --output $(PAGES)/elm.js
(cd $(PAGES); git commit -am "Update."; git push origin gh-pages) (cd $(PAGES); git commit -am "Update."; git push origin gh-pages)
clean : cleanish :
rm -f elm.js index.html rm -f elm.js index.html
veryclean : clean :
rm -rf elm-stuff/build-artifacts rm -rf elm-stuff/build-artifacts
distclean : clean distclean : clean
rm -rf elm-stuff rm -rf elm-stuff
.PHONY : pages elm.js clean veryclean distclean .PHONY : pages elm.js clean cleanish distclean

View file

@ -1,5 +1,7 @@
# Material Design Components in Elm # Material Design Components in Elm
[![Build Status](https://travis-ci.org/debois/elm-mdl.svg?branch=master)](https://travis-ci.org/debois/elm-mdl)
Port of Google's Port of Google's
[Material Design Lite](https://www.getmdl.io/) [Material Design Lite](https://www.getmdl.io/)
CSS/JS implementation of the CSS/JS implementation of the

View file

@ -15,7 +15,8 @@
"Material.Button", "Material.Button",
"Material.Textfield", "Material.Textfield",
"Material.Layout", "Material.Layout",
"Material.Grid" "Material.Grid",
"Material.Component"
], ],
"dependencies": { "dependencies": {
"debois/elm-dom": "1.0.0 <= v < 2.0.0", "debois/elm-dom": "1.0.0 <= v < 2.0.0",
@ -23,7 +24,8 @@
"evancz/elm-effects": "2.0.1 <= v < 3.0.0", "evancz/elm-effects": "2.0.1 <= v < 3.0.0",
"evancz/elm-html": "4.0.2 <= v < 5.0.0", "evancz/elm-html": "4.0.2 <= v < 5.0.0",
"evancz/elm-markdown": "2.0.1 <= v < 3.0.0", "evancz/elm-markdown": "2.0.1 <= v < 3.0.0",
"evancz/start-app": "2.0.2 <= v < 3.0.0" "evancz/start-app": "2.0.2 <= v < 3.0.0",
"sporto/hop": "3.0.0 <= v < 4.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.16.0 <= v < 0.17.0"
} }

146
examples/Component-TEA.elm Normal file
View file

@ -0,0 +1,146 @@
import StartApp
import Html exposing (..)
import Html.Attributes exposing (href, class, style)
import Effects exposing (Effects, Never)
import Task exposing (Task)
import Material.Button as Button
import Material.Scheme
-- MODEL
type alias Model =
{ count : Int
, increaseButtonModel : Button.Model
, resetButtonModel : Button.Model
}
model : Model
model =
{ count = 0
, increaseButtonModel = Button.model True -- With ripple animation
, resetButtonModel = Button.model False -- Without ripple animation
}
-- ACTION, UPDATE
type Action
= IncreaseButtonAction Button.Action
| ResetButtonAction Button.Action
increase : Model -> Model
increase model =
{ model | count = model.count + 1 }
reset : Model -> Model
reset model =
{ model | count = 0 }
update : Action -> Model -> (Model, Effects Action)
update action model =
case Debug.log "" action of
IncreaseButtonAction action' ->
let
(submodel, fx) =
Button.update action' model.increaseButtonModel
model' =
case action' of
Button.Click ->
increase model
_ ->
model
in
( { model' | increaseButtonModel = submodel }
, Effects.map IncreaseButtonAction fx
)
ResetButtonAction action' ->
let
(submodel, fx) =
Button.update action' model.resetButtonModel
model' =
case action' of
Button.Click ->
reset model
_ ->
model
in
( { model' | resetButtonModel = submodel }
, Effects.map ResetButtonAction fx
)
-- VIEW
view : Signal.Address Action -> Model -> Html
view addr model =
div
[ style
[ ("margin", "auto")
, ("padding-left", "5%")
, ("padding-right", "5%")
]
]
[ text ("Current count: " ++ toString model.count )
, Button.flat
(Signal.forwardTo addr IncreaseButtonAction)
model.increaseButtonModel
[]
[ text "Increase" ]
, Button.flat
(Signal.forwardTo addr ResetButtonAction)
model.resetButtonModel
[]
[ text "Reset" ]
]
|> Material.Scheme.top
{- The remainder of this file is Elm/StartApp boilerplate.
-}
-- SETUP
init : (Model, Effects.Effects Action)
init = (model, Effects.none)
inputs : List (Signal.Signal Action)
inputs =
[
]
app : StartApp.App Model
app =
StartApp.start
{ init = init
, view = view
, update = update
, inputs = inputs
}
main : Signal Html
main =
app.html
-- PORTS
port tasks : Signal (Task Never ())
port tasks =
app.tasks

158
examples/Component.elm Normal file
View file

@ -0,0 +1,158 @@
import StartApp
import Html exposing (..)
import Html.Attributes exposing (href, class, style)
import Effects exposing (Effects, Never)
import Task exposing (Task)
import Material
import Material.Scheme
import Material.Button as Button
-- MODEL
type alias Model =
{ count : Int
, mdl : Material.Model Action
-- Boilerplate: mdl is the Model store for any and all MDL components you need.
}
model : Model
model =
{ count = 0
, mdl = Material.model
-- Boilerplate: Always use this initial MDL model store.
}
-- ACTION, UPDATE
type Action
= Increase
| Reset
| MDL (Material.Action Action)
-- Boilerplate: Action for MDL actions (ripple animations etc.).
update : Action -> Model -> (Model, Effects Action)
update action model =
case Debug.log "" action of
Increase ->
( { model | count = model.count + 1 }
, Effects.none
)
Reset ->
( { model | count = 0 }
, Effects.none
)
{- Boilerplate: MDL action handler. It should always look like this, except
you can of course choose to put its saved model someplace other than
model.mdl.
-}
MDL action' ->
let (mdl', fx) =
Material.update MDL action' model.mdl
in
( { model | mdl = mdl' } , fx )
-- VIEW
type alias Mdl = Material.Model Action
{- We construct the instances of the Button component that we need, one
for the increase button, one for the reset button. First, the increase
button. The arguments are:
- An instance id (the `0`). Every component that uses the same model collection
(model.mdl in this file) must have a distinct instance id.
- An Action creator (`MDL`), lifting MDL actions to your Action type.
- A button view (`flat`).
- An initial model (`(Button.model True)`---a button with a ripple animation.
- A list of observations you want to make of the button (final argument).
In this case, we hook up Click events of the button to the `Increase` action
defined above.
-}
increase : Button.Instance Mdl Action
increase =
Button.instance 0 MDL Button.flat (Button.model True)
[ Button.fwdClick Increase ]
{- Next, the reset button. This one has id 1, does not ripple, and forwards its
click event to our Reset action.
-}
reset : Button.Instance Mdl Action
reset =
Button.instance 1 MDL Button.flat (Button.model False)
[ Button.fwdClick Reset ]
{- Notice that we did not have to add increase and reset separately to model.mdl,
and we did not have to add to our update actions to handle their internal events.
-}
view : Signal.Address Action -> Model -> Html
view addr model =
div
[ style
[ ("margin", "auto")
, ("padding-left", "5%")
, ("padding-right", "5%")
]
]
[ text ("Current count: " ++ toString model.count )
, increase.view addr model.mdl [] [ text "Increase" ]
, reset.view addr model.mdl [] [ text "Reset" ]
-- Note that we use the .view function of our component instances to
-- actually render the component.
]
|> Material.Scheme.top
{- The remainder of this file is Elm/StartApp boilerplate.
-}
-- SETUP
init : (Model, Effects.Effects Action)
init = (model, Effects.none)
inputs : List (Signal.Signal Action)
inputs =
[
]
app : StartApp.App Model
app =
StartApp.start
{ init = init
, view = view
, update = update
, inputs = inputs
}
main : Signal Html
main =
app.html
-- PORTS
port tasks : Signal (Task Never ())
port tasks =
app.tasks

View file

@ -1,6 +1,7 @@
module Main (..) where
import StartApp import StartApp
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (href, class, style) import Html.Attributes exposing (href, class, style, key)
import Signal exposing (Signal) import Signal exposing (Signal)
import Effects exposing (..) import Effects exposing (..)
import Task import Task
@ -8,20 +9,57 @@ import Signal
import Task exposing (Task) import Task exposing (Task)
import Array exposing (Array) import Array exposing (Array)
import Material.Color as Color import Hop
import Material.Layout as Layout exposing (defaultLayoutModel) import Hop.Types
import Hop.Navigate exposing (navigateTo)
import Hop.Matchers exposing (match1)
import Material exposing (lift, lift') import Material.Color as Color
import Material.Layout
import Material.Layout as Layout exposing (defaultLayoutModel)
import Material.Helpers exposing (lift, lift')
import Material.Style as Style import Material.Style as Style
import Material import Material.Scheme as Scheme
import Demo.Buttons import Demo.Buttons
import Demo.Grid import Demo.Grid
import Demo.Textfields import Demo.Textfields
import Demo.Snackbar import Demo.Snackbar
import Demo.Badges import Demo.Badges
import Demo.Elevation
--import Demo.Template --import Demo.Template
-- ROUTING
type Route
= Tab Int
| E404
type alias Routing =
( Route, Hop.Types.Location )
route0 : Routing
route0 =
( Tab 0, Hop.Types.newLocation )
router : Hop.Types.Router Route
router =
Hop.new
{ notFound = E404
, matchers =
( match1 (Tab 0) "/"
:: (tabs |> List.indexedMap (\idx (_, path, _) ->
match1 (Tab idx) ("/" ++ path))
)
)
}
-- MODEL -- MODEL
@ -36,6 +74,7 @@ layoutModel =
type alias Model = type alias Model =
{ layout : Layout.Model { layout : Layout.Model
, routing : Routing
, buttons : Demo.Buttons.Model , buttons : Demo.Buttons.Model
, textfields : Demo.Textfields.Model , textfields : Demo.Textfields.Model
, snackbar : Demo.Snackbar.Model , snackbar : Demo.Snackbar.Model
@ -46,6 +85,7 @@ type alias Model =
model : Model model : Model
model = model =
{ layout = layoutModel { layout = layoutModel
, routing = route0
, buttons = Demo.Buttons.model , buttons = Demo.Buttons.model
, textfields = Demo.Textfields.model , textfields = Demo.Textfields.model
, snackbar = Demo.Snackbar.model , snackbar = Demo.Snackbar.model
@ -53,32 +93,71 @@ model =
} }
-- ACTION, UPDATE -- ACTION, UPDATE
type Action type Action
= LayoutAction Layout.Action -- Hop
= ApplyRoute ( Route, Hop.Types.Location )
| HopAction ()
-- Tabs
| LayoutAction Layout.Action
| ButtonsAction Demo.Buttons.Action | ButtonsAction Demo.Buttons.Action
| TextfieldAction Demo.Textfields.Action | TextfieldAction Demo.Textfields.Action
| SnackbarAction Demo.Snackbar.Action | SnackbarAction Demo.Snackbar.Action
--| TemplateAction Demo.Template.Action --| TemplateAction Demo.Template.Action
update : Action -> Model -> (Model, Effects.Effects Action) nth : Int -> List a -> Maybe a
nth k xs =
List.drop k xs |> List.head
update : Action -> Model -> ( Model, Effects Action )
update action model = update action model =
case Debug.log "Action: " action of case action of
LayoutAction a -> lift .layout (\m x->{m|layout =x}) LayoutAction Layout.update a model LayoutAction a ->
let
( lifted, layoutFx ) =
lift .layout (\m x -> { m | layout = x }) LayoutAction Layout.update a model
routeFx =
case a of
Layout.SwitchTab k ->
nth k tabs
|> Maybe.map (\(_, path, _) -> Effects.map HopAction (navigateTo path))
|> Maybe.withDefault Effects.none
_ ->
Effects.none
in
( lifted, Effects.batch [ layoutFx, routeFx ] )
ApplyRoute route ->
( { model
| routing = route
, layout = setTab model.layout (fst route)
}
, Effects.none
)
HopAction _ ->
( model, Effects.none )
ButtonsAction a -> lift .buttons (\m x->{m|buttons =x}) ButtonsAction Demo.Buttons.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 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 --TemplateAction a -> lift .template (\m x->{m|template =x}) TemplateAction Demo.Template.update a model
-- VIEW -- VIEW
type alias Addr = Signal.Address Action type alias Addr =
Signal.Address Action
drawer : List Html drawer : List Html
@ -86,11 +165,11 @@ drawer =
[ Layout.title "Example drawer" [ Layout.title "Example drawer"
, Layout.navigation , Layout.navigation
[ Layout.link [ Layout.link
[ href "https://www.getmdl.io/components/index.html" ] [ href "https://github.com/debois/elm-mdl" ]
[ text "MDL" ] [ text "github" ]
, Layout.link , Layout.link
[ href "https://www.google.com/design/spec/material-design/introduction.html"] [ href "http://package.elm-lang.org/packages/debois/elm-mdl/latest/" ]
[ text "Material Design"] [ text "elm-package" ]
] ]
] ]
@ -112,31 +191,50 @@ header =
] ]
tabs : List (String, Addr -> Model -> List Html) tabs : List (String, String, Addr -> Model -> Html)
tabs = tabs =
[ ("Snackbar", \addr model -> [ ("Buttons", "buttons", \addr model ->
[Demo.Snackbar.view (Signal.forwardTo addr SnackbarAction) model.snackbar]) Demo.Buttons.view (Signal.forwardTo addr ButtonsAction) model.buttons)
, ("Textfields", \addr model -> , ("Badges", "badges", \addr model -> Demo.Badges.view )
[Demo.Textfields.view (Signal.forwardTo addr TextfieldAction) model.textfields]) , ("Elevation", "elevation", \addr model -> Demo.Elevation.view )
, ("Buttons", \addr model -> , ("Grid", "grid", \addr model -> Demo.Grid.view)
[Demo.Buttons.view (Signal.forwardTo addr ButtonsAction) model.buttons]) , ("Snackbar", "snackbar", \addr model ->
, ("Grid", \addr model -> Demo.Grid.view) Demo.Snackbar.view (Signal.forwardTo addr SnackbarAction) model.snackbar)
, ("Badges", \addr model -> Demo.Badges.view ) , ("Textfields", "textfields", \addr model ->
Demo.Textfields.view (Signal.forwardTo addr TextfieldAction) model.textfields)
{- {-
, ("Template", \addr model -> , ("Template", \addr model ->
[Demo.Template.view (Signal.forwardTo addr TemplateAction) model.template]) [Demo.Template.view (Signal.forwardTo addr TemplateAction) model.template])
-} -}
] ]
tabViews : Array (Addr -> Model -> List Html)
tabViews = List.map snd tabs |> Array.fromList e404 : Addr -> Model -> Html
e404 _ _ =
div
[
]
[ Style.styled Html.h1
[ Style.cs "mdl-typography--display-4"
, Color.background Color.primary
]
[]
[ text "404" ]
]
tabViews : Array (Addr -> Model -> Html)
tabViews = List.map (\(_,_,v) -> v) tabs |> Array.fromList
tabTitles : List Html tabTitles : List Html
tabTitles = List.map (fst >> text) tabs tabTitles =
List.map (\(x,_,_) -> text x) tabs
stylesheet : Html stylesheet : Html
stylesheet = Style.stylesheet """ stylesheet =
Style.stylesheet """
blockquote:before { content: none; } blockquote:before { content: none; }
blockquote:after { content: none; } blockquote:after { content: none; }
blockquote { blockquote {
@ -150,50 +248,67 @@ stylesheet = Style.stylesheet """
*/ */
} }
p, blockquote { p, blockquote {
max-width: 33em; max-width: 40em;
font-size: 13px; }
h1, h2 {
/* TODO. Need typography module with kerning. */
margin-left: -3px;
} }
""" """
setTab : Layout.Model -> Route -> Layout.Model
setTab layout route =
let
idx =
case route of
Tab k -> k
E404 -> -1
in
{ layout | selectedTab = idx }
view : Signal.Address Action -> Model -> Html view : Signal.Address Action -> Model -> Html
view addr model = view addr model =
let top = let
top =
div div
[ style [ style
[ ("margin", "auto") [ ( "margin", "auto" )
, ("padding-left", "5%") , ( "padding-left", "8%" )
, ("padding-right", "5%") , ( "padding-right", "8%" )
] ]
, key <| toString (fst model.routing)
]
[ (Array.get model.layout.selectedTab tabViews
|> Maybe.withDefault e404)
addr
model
] ]
((Array.get model.layout.selectedTab tabViews
|> Maybe.withDefault (\addr model ->
[div [] [text "This can't happen."]]
)
) addr model)
in in
Layout.view (Signal.forwardTo addr LayoutAction) model.layout Layout.view (Signal.forwardTo addr LayoutAction) model.layout
{ header = header { header = header
, drawer = drawer , drawer = drawer
, tabs = tabTitles , tabs = tabTitles
, main = [ top ] , main = [ stylesheet, top ]
} }
{- The following line is not needed when you manually set up {- The following line is not needed when you manually set up
your html, as done with page.html. Removing it will then your html, as done with page.html. Removing it will then
fix the flicker you see on load. fix the flicker you see on load.
-} -}
|> Material.topWithScheme Color.Teal Color.Red |> Scheme.topWithScheme Color.Teal Color.Red
init : (Model, Effects.Effects Action) init : ( Model, Effects.Effects Action )
init = (model, Effects.none) init =
( model, Effects.none )
inputs : List (Signal.Signal Action) inputs : List (Signal.Signal Action)
inputs = inputs =
[ Layout.setupSignals LayoutAction [ Layout.setupSignals LayoutAction
, Signal.map ApplyRoute router.signal
] ]
@ -206,6 +321,7 @@ app =
, inputs = inputs , inputs = inputs
} }
main : Signal Html main : Signal Html
main = main =
app.html app.html
@ -217,3 +333,8 @@ main =
port tasks : Signal (Task.Task Never ()) port tasks : Signal (Task.Task Never ())
port tasks = port tasks =
app.tasks app.tasks
port routeRunTask : Task () ()
port routeRunTask =
router.run

View file

@ -2,34 +2,86 @@ module Demo.Badges (..) where
import Html exposing (..) import Html exposing (..)
import Material.Badge as Badge import Material.Badge as Badge
import Material.Style exposing (..) import Material.Style as Style exposing (styled)
import Material.Icon as Icon import Material.Icon as Icon
import Material.Grid exposing (..)
import Demo.Page as Page
-- VIEW -- VIEW
view : List Html c : List Html -> Cell
c = cell [ size All 4 ]
view : Html
view = view =
[ div [ grid
[] []
[ p [] [] [ c [Style.span [ Badge.withBadge "2" ] [text "Badge"] ]
, styled span [ Badge.withBadge "2" ] [] [ text "Span with badge" ] , c [Style.span
, p [] [] [ Badge.withBadge "22", Badge.noBackground ]
, styled span [ Badge.withBadge "22", Badge.noBackground ] [] [ text "Span with no background badge" ] [ text "No background" ]
, p [] [] ]
, styled span [ Badge.withBadge "33", Badge.overlap ] [] [ text "Span with badge overlap" ] , c [Style.span
, p [] [] [ Badge.withBadge "33", Badge.overlap ]
, styled span [ Badge.withBadge "99", Badge.overlap, Badge.noBackground ] [] [ text "Span with badge overlap and no background" ] [ text "Overlap" ]
, p [] [] ]
, styled span [ Badge.withBadge "" ] [] [ text "Span with HTML symbol - Black heart suit" ] , c [Style.span
, p [] [] [ Badge.withBadge "99", Badge.overlap, Badge.noBackground ]
, styled span [ Badge.withBadge "" ] [] [ text "Span with HTML symbol - Rightwards arrow" ] [ text "Overlap, no background" ]
, p [] [] ]
, styled span [ Badge.withBadge "Δ" ] [] [ text "Span with HTML symbol - Delta" ] , c [Style.span
, p [] [] [ Badge.withBadge "" ]
, span [] [ text "Icon with badge" ] [ text "Symbol" ]
, Icon.view "face" [ Icon.size24, Badge.withBadge "33", Badge.overlap ] [] ]
, Icon.view "face" [ Icon.size48, Badge.withBadge "33", Badge.overlap ] [] , c [ Icon.view "flight_takeoff" [ Icon.size24, Badge.withBadge "33", Badge.overlap ] [] ]
] ]
] ]
|> Page.body "Badges" srcUrl intro references
intro : Html
intro =
Page.fromMDL "http://www.getmdl.io/components/#badges-section" """
> The Material Design Lite (MDL) badge component is an onscreen notification
> element. A badge consists of a small circle, typically containing a number or
> other characters, that appears in proximity to another object. A badge can be
> both a notifier that there are additional items associated with an object and
> an indicator of how many items there are.
>
> You can use a badge to unobtrusively draw the user's attention to items they
> might not otherwise notice, or to emphasize that items may need their
> attention. For example:
>
> - A "New messages" notification might be followed by a badge containing the
> number of unread messages.
> - A "You have unpurchased items in your shopping cart" reminder might include
> a badge showing the number of items in the cart.
> - A "Join the discussion!" button might have an accompanying badge indicating the
> number of users currently participating in the discussion.
>
> A badge is almost
> always positioned near a link so that the user has a convenient way to access
> the additional information indicated by the badge. However, depending on the
> intent, the badge itself may or may not be part of the link.
>
> Badges are a new feature in user interfaces, and provide users with a visual clue to help them discover additional relevant content. Their design and use is therefore an important factor in the overall user experience.
>
"""
srcUrl : String
srcUrl =
"https://github.com/debois/elm-mdl/blob/master/examples/Demo/Badges.elm"
references : List (String, String)
references =
[ Page.package "http://package.elm-lang.org/packages/debois/elm-mdl/latest/Material-Badge"
--, Page.mds "https://www.google.com/design/spec/components/buttons.html"
, Page.mdl "https://www.getmdl.io/components/#badges-section"
]

View file

@ -10,15 +10,19 @@ import Material.Grid as Grid
import Material.Icon as Icon import Material.Icon as Icon
import Material.Style exposing (Style) import Material.Style exposing (Style)
import Demo.Page as Page
-- MODEL -- MODEL
type alias Index = (Int, Int) type alias Index = (Int, Int)
type alias View = type alias View =
Signal.Address Button.Action -> Button.Model -> List Style -> List Html -> Html Signal.Address Button.Action -> Button.Model -> List Style -> List Html -> Html
type alias View' = type alias View' =
Signal.Address Button.Action -> Button.Model -> Html Signal.Address Button.Action -> Button.Model -> Html
@ -69,7 +73,8 @@ model =
-- ACTION, UPDATE -- ACTION, UPDATE
type Action = Action Index Button.Action type Action
= Action Index Button.Action
type alias Model = type alias Model =
@ -79,7 +84,9 @@ type alias Model =
update : Action -> Model -> (Model, Effects.Effects Action) update : Action -> Model -> (Model, Effects.Effects Action)
update (Action idx action) model = update action model =
case action of
Action idx action ->
Dict.get idx model.buttons Dict.get idx model.buttons
|> Maybe.map (\m0 -> |> Maybe.map (\m0 ->
let let
@ -93,6 +100,7 @@ update (Action idx action) model =
-- VIEW -- VIEW
view : Signal.Address Action -> Model -> Html view : Signal.Address Action -> Model -> Html
view addr model = view addr model =
buttons |> List.concatMap (\row -> buttons |> List.concatMap (\row ->
@ -126,3 +134,38 @@ view addr model =
) )
) )
|> Grid.grid [] |> 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

@ -0,0 +1,85 @@
module Demo.Elevation where
import Html exposing (..)
import Material.Style as Style exposing (cs, css)
import Material.Elevation as Elevation
import Demo.Page as Page
-- VIEW
elevate : Int -> Html
elevate k =
Style.div
[ css "height" "96px"
, css "width" "128px"
, css "margin" "40px"
, css "display" "inline-flex"
, css "flex-flow" "row wrap"
, css "justify-content" "center"
, css "align-items" "center"
, Elevation.shadow k
]
[ Style.div
[ cs ".mdl-typography--title-color-contrast"
-- TODO. Typography!
]
[ text <| toString k ]
]
view : Html
view =
0 :: Elevation.validElevations
|> List.map elevate
|> Page.body "Elevation" srcUrl intro references
intro : Html
intro =
Page.fromMDL "https://github.com/google/material-design-lite/blob/master/src/shadow/README.md" """
> The Material Design Lite (MDL) shadow is not a component in the same sense as
> an MDL card, menu, or textbox; it is a visual effect that can be assigned to a
> user interface element. The effect simulates a three-dimensional positioning of
> the element, as though it is slightly raised above the surface it rests upon
> a positive z-axis value, in user interface terms. The shadow starts at the
> edges of the element and gradually fades outward, providing a realistic 3-D
> effect.
>
> Shadows are a convenient and intuitive means of distinguishing an element from
> its surroundings. A shadow can draw the user's eye to an object and emphasize
> the object's importance, uniqueness, or immediacy.
>
> Shadows are a well-established feature in user interfaces, and provide users
> with a visual clue to an object's intended use or value. Their design and use
> is an important factor in the overall user experience.)
The [Material Design Specification](https://www.google.com/design/spec/what-is-material/elevation-shadows.html#elevation-shadows-elevation-android-)
pre-defines appropriate elevation for most UI elements; you need to manually
assign shadows only to your own elements.
You are encouraged to visit the
[Material Design specification](https://www.google.com/design/spec/what-is-material/elevation-shadows.html)
for details about appropriate use of shadows.
"""
srcUrl : String
srcUrl =
"https://github.com/debois/elm-mdl/blob/master/examples/Demo/Elevation.elm"
references : List (String, String)
references =
[ Page.package "http://package.elm-lang.org/packages/debois/elm-mdl/latest/Material-Elevation"
, Page.mds "https://www.google.com/design/spec/what-is-material/elevation-shadows.html"
, Page.mdl "https://github.com/google/material-design-lite/blob/master/src/shadow/README.md"
]

View file

@ -1,14 +1,19 @@
module Demo.Grid where module Demo.Grid where
import Html exposing (..)
import Array
import Material.Grid exposing (..) import Material.Grid exposing (..)
import Material.Style exposing (Style, css) import Material.Style exposing (Style, css)
import Material.Color as Color
import Markdown import Demo.Page as Page
import Html exposing (..)
-- Cell styling -- Cell styling
style : Int -> List Style style : Int -> List Style
style h = style h =
[ css "text-sizing" "border-box" [ css "text-sizing" "border-box"
@ -19,44 +24,61 @@ style h =
, css "color" "white" , css "color" "white"
] ]
-- Cell variants -- Cell variants
democell : Int -> List Style -> List Html -> Cell democell : Int -> List Style -> List Html -> Cell
democell k styling = democell k styling =
cell <| List.concat [style k, styling] cell <| List.concat [style k, styling]
small : List Style -> List Html -> Cell small : List Style -> List Html -> Cell
small = democell 50 small = democell 50
std : List Style -> List Html -> Cell std : List Style -> List Html -> Cell
std = democell 200 std = democell 200
-- Grid -- Grid
view : List Html
color : Int -> Style
color k =
Array.get ((k + 7) % Array.length Color.palette) Color.palette
|> Maybe.withDefault Color.Teal
|> flip Color.color Color.S500
|> Color.background
view : Html
view = view =
[ [1..12] [ p []
|> List.map (\i -> small [size All 1] [text "1"]) [ text """Resize your browser-window and observe the effect on the Grid
below. Note in particular what happens to the top and bottom rows."""
]
, [1..12 ]
|> List.map (\i -> small [size All 1, color i] [text "1"])
|> grid [] |> grid []
, [1 .. 3] , [1 .. 3]
|> List.map (\i -> std [size All 4] [text <| "4"]) |> List.map (\i -> std [size All 4, color i] [text <| "4"])
|> grid [] |> grid []
, [ std [size All 6] [text "6"] , [ std [size All 6, color 16] [text "6"]
, std [size All 4] [text "4"] , std [size All 4, color 17] [text "4"]
, std [size All 2] [text "2"] , std [size All 2, color 18] [text "2"]
] |> grid [] ] |> grid []
, [ std [size All 6, size Tablet 8] [text "6 (8 tablet)"] , [ std [size All 6, size Tablet 8, color 19] [text "6 (8 tablet)"]
, std [size All 4, size Tablet 6] [text "4 (6 tablet)"] , std [size All 4, size Tablet 6, color 20] [text "4 (6 tablet)"]
, std [size All 2, size Phone 4] [text "2 (4 phone)"] , std [size All 2, size Phone 4, color 21] [text "2 (4 phone)"]
] |> grid [] ] |> grid []
] ]
|> Page.body "Grid" srcUrl intro references
intro : Html intro : Html
intro = """ intro =
From the Page.fromMDL "http://www.getmdl.io/components/#layout-section/grid" """
[Material Design Lite documentation](http://www.getmdl.io/components/#layout-section/grid):
> The Material Design Lite (MDL) grid component is a simplified method for laying > 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 > out content for multiple screen sizes. It reduces the usual coding burden
> required to correctly display blocks of content in a variety of display > required to correctly display blocks of content in a variety of display
@ -72,16 +94,15 @@ From the
> - If a cell has a specified column size equal to or larger than the number > - If a cell has a specified column size equal to or larger than the number
> of columns for the current screen size, it takes up the entirety of its > of columns for the current screen size, it takes up the entirety of its
> row. > row.
"""
#### See also srcUrl : String
srcUrl =
- [Demo source code](https://github.com/debois/elm-mdl/blob/master/examples/Demo/Grid.elm) "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
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"
]

148
examples/Demo/Page.elm Normal file
View file

@ -0,0 +1,148 @@
module Demo.Page
( demo, package, mds, mdl
, fromMDL, fromMDS
, body
)
where
import Html exposing (..)
import Html.Attributes exposing (href, class)
import Markdown
import Material.Grid exposing (..)
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
-- REFERENCES
demo : String -> (String, String)
demo url =
( "Demo source", url )
package : String -> (String, String)
package url =
( "Package documentation", url )
mds : String -> (String, String)
mds url =
( "Material Design Specification", url )
mdl : String -> (String, String)
mdl url =
( "Material Design Lite documentation", url )
references : List (String, String) -> List Html
references links =
[ text "References"
, ul []
( links |> List.map (\(str, url) ->
li [] [ a [ href url ] [ text str ] ]
)
)
]
-- DOCUMENTATION QUOTES
from : String -> String -> String -> Html
from title url body =
div []
[ text "From the "
, a [ href url ] [ text title ]
, text ":"
, Markdown.toHtml body
]
fromMDL : String -> String -> Html
fromMDL =
from "Material Design Lite documentation"
fromMDS : String -> String -> Html
fromMDS =
from "Material Design Specification"
-- TITLES
title : String -> Html
title t =
Style.styled Html.h1
[ Color.text Color.primary
--, cs "mdl-typography--display-4"
-- TODO. Typography module
]
[]
[ text t ]
demoTitle : Html
demoTitle =
Style.styled Html.h2
[ Color.text Color.primary
]
[]
[ text "Example" ]
-- VIEW SOURCE BUTTON
addr : Signal.Address Button.Action
addr = (Signal.mailbox Button.Click).address
fab : String -> Html
fab url =
Button.fab addr (Button.model False)
[ css "position" "fixed"
, css "right" "48px"
, css "top" "72px"
, css "z-index" "100"
, Button.colored
--, attribute (href srcUrl)
, attribute (Html.Attributes.attribute "onclick" ("alert('foo!');")) --("window.location.href = '" ++ srcUrl ++ "';") )
]
[ Icon.i "link" ]
-- BODY
body : String -> String -> Html -> List (String, String) -> List Html -> Html
body t srcUrl contents links demo =
div []
[ title t
, grid [ noSpacing ]
[ cell [ size All 6, size Phone 4 ] [ contents ]
, cell
[ size All 5, offset Desktop 1, size Phone 4, align Top
, css "position" "relative"
]
( references <| ("Demo source", srcUrl) :: links )
]
--, fab srcUrl
-- TODO: buttons can't be links (yet)
-- TODO: FAB placement.
, demoTitle
, Style.div
[ css "margin-bottom" "48px"
--, css "margin-top" "48px"
-- , Elevation.shadow 2
]
demo
]

View file

@ -4,36 +4,49 @@ import Effects exposing (Effects, none)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, style, key) import Html.Attributes exposing (class, style, key)
import Array exposing (Array) import Array exposing (Array)
import Time exposing (Time, millisecond)
import Markdown import Material.Helpers exposing (map1st, map2nd, delay)
import Material.Color as Color 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.Snackbar as Snackbar
import Material.Button as Button exposing (Action(..)) import Material.Button as Button exposing (Action(..))
import Material.Grid exposing (..) import Material.Grid exposing (..)
import Material exposing (lift, lift') import Material.Elevation as Elevation
import Material
import Demo.Page as Page
-- MODEL -- MODEL
type alias Mdl =
Material.Model Action
type Square'
= Appearing
| Idle
| Disappearing
type alias Square =
(Int, Square')
type alias Model = type alias Model =
{ count : Int { count : Int
, clicked : List Int , squares : List Square
, snackbar : Snackbar.Model Action , mdl : Mdl
, toastButton : Button.Model
, snackbarButton : Button.Model
} }
model : Model model : Model
model = model =
{ count = 0 { count = 0
, clicked = [] , squares = []
, snackbar = Snackbar.model , mdl = Material.model
, toastButton = Button.model True
, snackbarButton = Button.model True
} }
@ -41,138 +54,220 @@ model =
type Action type Action
= Undo Int = AddSnackbar
-- Components | AddToast
| SnackbarAction (Snackbar.Action Action) | Appear Int
| ToastButtonAction Button.Action | Disappear Int
| SnackbarButtonAction Button.Action | Gone Int
| MDL (Material.Action Action)
snackbar : Int -> Snackbar.Contents Action add : Model -> (Int -> Snackbar.Contents Action) -> (Model, Effects Action)
snackbar k = add model f =
Snackbar.snackbar
("Snackbar message #" ++ toString k)
"UNDO"
(Undo k)
toast : Int -> Snackbar.Contents Action
toast k =
Snackbar.toast
<| "Toast message #" ++ toString k
add : (Int -> Snackbar.Contents Action) -> Model -> (Model, Effects Action)
add f model =
let let
(snackbar', effects) = (mdl', fx) =
Snackbar.update (Snackbar.Add (f model.count)) model.snackbar Snackbar.add (f model.count) snackbar model.mdl
in model' =
({ model { model
| snackbar = snackbar' | mdl = mdl'
, count = model.count + 1 , count = model.count + 1
, clicked = model.count :: model.clicked , squares = (model.count, Appearing) :: model.squares
}
in
( 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
} }
, Effects.map SnackbarAction effects)
update : Action -> Model -> (Model, Effects Action) update : Action -> Model -> (Model, Effects Action)
update action model = update action model =
case action of case action of
SnackbarButtonAction Click -> AddSnackbar ->
add snackbar model add model
<| \k -> Snackbar.snackbar ("Snackbar message #" ++ toString k) "UNDO" (Disappear k)
ToastButtonAction Click -> AddToast ->
add toast model 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 ({ model
| clicked = List.filter ((/=) k) model.clicked | squares = List.filter (fst >> (/=) k) model.squares
} }
, none) , none)
SnackbarAction (Snackbar.Action action') MDL action' ->
-> update action' model Material.update MDL action' model.mdl
|> map1st (\m -> { model | mdl = m })
SnackbarAction action' -> lift .snackbar (\m x -> {m|snackbar =x}) SnackbarAction Snackbar.update action' model
ToastButtonAction action' -> lift .toastButton (\m x -> {m|toastButton =x}) ToastButtonAction Button.update action' model
SnackbarButtonAction action' -> lift .snackbarButton (\m x -> {m|snackbarButton=x}) SnackbarButtonAction Button.update action' model
-- VIEW -- VIEW
clickView : Model -> Int -> Html addSnackbarButton : Button.Instance Mdl Action
clickView model k = addSnackbarButton =
Button.instance 0 MDL
Button.raised (Button.model True)
[ Button.fwdClick AddSnackbar ]
addToastButton : Button.Instance Mdl Action
addToastButton =
Button.instance 1 MDL
Button.raised (Button.model True)
[ Button.fwdClick AddToast ]
-- TODO: Bad name
snackbar : Snackbar.Instance Mdl Action
snackbar =
Snackbar.instance MDL Snackbar.model
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 let
color = color =
Array.get ((k + 4) % Array.length Color.palette) Color.palette Array.get ((k + 4) % Array.length Color.palette) Color.palette
|> Maybe.withDefault Color.Teal |> Maybe.withDefault Color.Teal
|> flip Color.color Color.S500 |> flip Color.color Color.S500
selected = selected' =
(k == model.snackbar.seq - 1) && Snackbar.activeAction (snackbar.get model.mdl) == Just (Disappear k)
(Snackbar.isActive model.snackbar /= Nothing)
(width, height, margin, selected) =
case square of
Idle ->
(boxWidth, boxHeight, "16px 16px", selected')
_ ->
("0", "0", "16px 0", False)
in in
styled div div
[ style
[ ("height", boxHeight)
, ("width", width)
, ("position", "relative")
, ("display", "inline-block")
, ("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
]
[ styled div
[ Color.background color [ Color.background color
, Color.text Color.primaryContrast , Color.text Color.primaryContrast
-- TODO. Should have shadow styles someplace. , Elevation.shadow (if selected then 8 else 2)
, cs <| "mdl-shadow--" ++ if selected then "8dp" else "2dp"
] ]
[ style [ style
[ ("margin-right", "3ex") [ ("display", "inline-flex")
, ("margin-bottom", "3ex") , ("align-items", "center")
, ("padding", "1.5ex") , ("justify-content", "center")
, ("width", "4ex") , ("height", height)
, ("width", width)
, ("border-radius", "2px") , ("border-radius", "2px")
, ("display", "inline-block") , transitions
, ("text-align", "center") , ("overflow", "hidden")
, ("transition", "box-shadow 333ms ease-in-out 0s") , ("box-sizing", "border-box")
, ("flex", "0 0 auto")
, ("position", "absolute")
, ("bottom", "0")
, ("left", "0")
] ]
, key (toString k)
] ]
[ text <| toString k ] [ div [] [ text <| toString k ] ]
]
view : Signal.Address Action -> Model -> Html view : Signal.Address Action -> Model -> Html
view addr model = view addr model =
div [] Page.body "Snackbar & Toast" srcUrl intro references
[ h1 [ class "mdl-typography--display-4-color-contrast" ] [ text "Snackbars & Toasts" ] [ p []
, intro [ text """Click the buttons below to activate the snackbar. Note that
, grid [] multiple activations are automatically queued."""
-- TODO. Buttons should be centered. Desperately need to be able ]
-- to add css/classes to top-level element of components (div , grid [ ] --css "margin-top" "32px" ]
-- in grid, button in button, div in textfield etc.) [ cell
[ cell [ size All 2, size Phone 2, align Top ] [ size All 2, size Phone 2, align Top ]
[ Button.raised [ addToastButton.view addr model.mdl
(Signal.forwardTo addr ToastButtonAction) [ Button.colored
model.toastButton , css "margin" "16px"
[] ]
[ text "Toast" ] [ text "Toast" ]
] ]
, cell [ size All 2, size Phone 2, align Top ] , cell
[ Button.raised [ size All 2, size Phone 2, align Top ]
(Signal.forwardTo addr SnackbarButtonAction) [ addSnackbarButton.view addr model.mdl
model.snackbarButton [ Button.colored
[] , css "margin" "16px"
]
[ text "Snackbar" ] [ text "Snackbar" ]
] ]
, cell , cell
[ size Desktop 7, size Tablet 3, size Phone 12, align Top ] [ size Desktop 7, offset Desktop 1
(model.clicked |> List.reverse |> List.map (clickView model)) , size Tablet 3, offset Tablet 1
, size Phone 4
, align Top
] ]
, Snackbar.view (Signal.forwardTo addr SnackbarAction) model.snackbar (model.squares |> List.reverse |> List.map (clickView model))
]
, snackbar.view addr model.mdl
] ]
intro : Html intro : Html
intro = """ intro =
From the Page.fromMDL "https://www.getmdl.io/components/index.html#snackbar-section" """
[Material Design Lite documentation](https://www.getmdl.io/components/index.html#snackbar-section).
> The Material Design Lite (MDL) __snackbar__ component is a container used to > The Material Design Lite (MDL) __snackbar__ component is a container used to
> notify a user of an operation's status. It displays at the bottom of the > notify a user of an operation's status. It displays at the bottom of the
> screen. A snackbar may contain an action button to execute a command for the > screen. A snackbar may contain an action button to execute a command for the
@ -180,15 +275,19 @@ From the
> example. Actions should not be to close the snackbar. By not providing an > example. Actions should not be to close the snackbar. By not providing an
> action, the snackbar becomes a __toast__ component. > action, the snackbar becomes a __toast__ component.
#### See also """
- [Demo source code](https://github.com/debois/elm-mdl/blob/master/examples/Demo/Snackbar.elm)
- [elm-mdl package documentation](http://package.elm-lang.org/packages/debois/elm-mdl/latest/Material-Snackbar) srcUrl : String
- [Material Design Specification](https://www.google.com/design/spec/components/snackbars-toasts.html) srcUrl =
- [Material Design Lite documentation](https://www.getmdl.io/components/index.html#snackbar-section) "https://github.com/debois/elm-mdl/blob/master/examples/Demo/Snackbar.elm"
#### Demo
references : List (String, String)
""" |> Markdown.toHtml references =
[ Page.package "http://package.elm-lang.org/packages/debois/elm-mdl/latest/Material-Snackbar"
, Page.mds "https://www.google.com/design/spec/components/snackbars-toasts.html"
, Page.mdl "https://www.getmdl.io/components/index.html#snackbar-section"
]

View file

@ -1,52 +1,211 @@
module Demo.Textfields where module Demo.Textfields where
import Array exposing (Array)
import Html exposing (Html) import Html exposing (Html)
import Effects exposing (Effects)
import Regex
import Material.Textfield as Textfield import Material.Textfield as Textfield
import Material.Grid as Grid exposing (..) 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 : Model
model = model =
let t0 = Textfield.model in { mdl = Material.model
[ t0 , rx = setRegex rx0
, { t0 | label = Just { text = "Labelled", float = False } } }
, { t0 | label = Just { text = "Floating label", float = True }}
, { t0
-- 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)"
})
{- Check that rx matches all of str.
-}
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 } | label = Just { text = "Disabled", float = False }
, isDisabled = True , isDisabled = True
} }
, { t0 []
| label = Just { text = "With error and value", float = False }
, error = Just "The input is wrong!"
, value = "Incorrect input"
}
]
|> Array.fromList
type Action = field4 : Textfield.Instance Mdl Action
Field Int Textfield.Action field4 =
Textfield.instance 4 MDL
{ m0 | label = Just { text = "With error checking", float = False } }
update : Action -> Model -> Model [ Textfield.fwdInput Upd4 ]
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
view : Signal.Address Action -> Model -> Html view : Signal.Address Action -> Model -> Html
view addr model = view addr model =
model [ field0
|> Array.indexedMap (\k field -> , field1
Textfield.view (Signal.forwardTo addr (Field k)) field , field2
, field3
, field4
]
|> List.map (\c ->
cell
[size All 4, offset Desktop 1]
[c.view addr model.mdl]
) )
|> Array.toList |> List.intersperse (cell [size All 1] [])
|> List.map (\x -> cell [size All 3] [x])
|> grid [] |> grid []
|> flip (::) []
|> (::) (Html.text "Try entering text into some of the textfields below.")
|> 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

@ -1,126 +1,187 @@
module Material module Material
( topWithScheme, top ( Model, model
, Updater', Updater, lift, lift' , Action, update
) where )
where
{-| Material Design component library for Elm based on Google's {-|
Material Design component library for Elm based on Google's
[Material Design Lite](https://www.getmdl.io/). [Material Design Lite](https://www.getmdl.io/).
This module contains only initial CSS setup and convenience function for alleviating Click
the pain of the missing component architecture in Elm. [here](https://debois.github.io/elm-mdl/)
for a live demo.
# Loading CSS # Component model
@docs topWithScheme, top
# Component convienience The component model of the library is simply the Elm Architecture (TEA), i.e.,
@docs Updater', Updater, lift', lift each component has types `Model` and `Action`, and values `view` and `update`. A
-} minimal example using this library in plain TEA can be found
[here](https://github.com/debois/elm-mdl/blob/master/examples/Component-TEA.elm).
Using more than a few component in plain TEA is unwieldy because of the large
amount of boilerplate one has to write. This library provides the "component
support" for getting rid of most of that boilerplate. A minimal example using
component support is
[here](http://github.com/debois/elm-mdl/blob/master/examples/Component.elm).
import String It is important to note that component support lives __within__ TEA;
import Html exposing (..) it is not an alternative architecture.
import Html.Attributes exposing (..)
import Effects exposing (..)
import Material.Color exposing (Palette(..), Color) # Getting started
The easiest way to get started is to start with one of the minimal examples above.
We recommend going with the library's
[component support](http://github.com/debois/elm-mdl/blob/master/examples/Component.elm)
rather than working directly in plain Elm Architecture.
scheme : Palette -> Palette -> String # Component Support
scheme primary accent =
[ "https://code.getmdl.io/1.1.2/" ++ Material.Color.scheme primary accent
, "https://fonts.googleapis.com/icon?family=Material+Icons"
, "https://fonts.googleapis.com/css?family=Roboto:400,300,500|Roboto+Mono|Roboto+Condensed:400,700&subset=latin,latin-ext"
]
|> List.map (\url -> "@import url(" ++ url ++ ");")
|> String.join "\n"
This module contains only convenience functions for working with nested
components in the Elm architecture. A minimal example using this library
with component support can be found
[here](http://github.com/debois/elm-mdl/blob/master/examples/Component.elm).
We encourage you to use the library in this fashion.
All examples in this subsection is from the
[above minimal example](http://github.com/debois/elm-mdl/blob/master/examples/Component.elm)
{-| Top-level container for Material components. This will force loading of Here is how you use component support in general. First, boilerplate.
Material Design Lite CSS files Any component you use must be contained
in this container, OR you must manually add something like the following to 1. Include `Material`:
your .html file:
<!-- MDL --> <!-- MDL -->
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300,500|Roboto+Mono|Roboto+Condensed:400,700&subset=latin,latin-ext' rel='stylesheet' type='text/css'> <link href='https://fonts.googleapis.com/css?family=Roboto:400,300,500|Roboto+Mono|Roboto+Condensed:400,700&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.min.css" /> <link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.min.css" />
Supply primary and accent colors as parameters. Refer to the 2. Add a model container Material components to your model:
Material Design Lite [Custom CSS theme builder](https://www.getmdl.io/customize/index.html)
to preview combinations.
Please be aware that Grey, Blue Grey, and Brown cannot be secondary colors. If type alias Model =
you choose them as such anyway, you will get the default theme. { ...
, mdl : Material.Model
}
Using this top-level container is not recommended, as most browsers will load model : Model =
css concurrently with rendering the initial page, which will produce a flicker { ...
on page load. The container is included only to provide an option to get started , mdl = Material.model
quickly and for use with elm-reactor. }
-} 3. Add an action for Material components.
topWithScheme: Palette -> Palette -> Html -> Html
topWithScheme primary accent content = type Action =
div [] <| ...
{- Trick from Peter Damoc to load CSS outside of <head>. | MDL (Material.Action Action)
https://github.com/pdamoc/elm-mdl/blob/master/src/Mdl.elm#L63
-} 4. Handle that action in your update function as follows:
[ node "style"
[ type' "text/css"] update action model =
[ Html.text <| scheme primary accent] case action of
, content ...
MDL action' ->
let (mdl', fx) =
Material.update MDL action' model.mdl
in
( { model | mdl = mdl' } , fx )
Next, make the component instances you need. Do this in the View section of your
source file. Let's say you need a textfield for name entry, and you'd like to
be notifed whenever the field changes value through your own NameChanged action:
import Material.Textfield as Textfield
...
type Action =
...
| NameChanged String
...
update action model =
case action of
...
NameChanged name ->
-- Do whatever you need to do.
...
nameInput : Textfield.Instance Material.Model Action
nameInput =
Textfield.instance 2 MDL Textfield.model
[ Textfield.fwdInput NameChanged
] ]
view addr model =
...
nameInput.view addr model.mdl
{-| Top-level container with default color scheme.
The win relative to using plain Elm Architecture is that adding a component
neither requires you to update your model, your Actions, nor your update function.
(As in the above example, you will frequently have to update the latter two anyway,
but now it's not boilerplate, its "business logic".)
## Optimising for size
Using this module will force all elm-mdl components to be built and included in
your application. If this is unacceptable, you can custom-build a version of this
module that uses only the components you need. To do so, you need to re-implement
the present module, modifying the values `model` and `Model` by commenting out the
components you are not using. The module source can be found
[here](https://github.com/debois/elm-mdl/blob/master/src/Material.elm).
You do not need to re-build the entire elm-mdl library; simply copy the
source of this module, give it a new name, modify as it as indicated above,
then use your modified module rather than this one.
@docs Model, model, Action, update
-} -}
top : Html -> Html
top content = import Dict
-- Force default color-scheme by picking an invalid combination. import Effects exposing (Effects)
topWithScheme Grey Grey content
import Material.Button as Button
import Material.Textfield as Textfield
import Material.Snackbar as Snackbar
import Material.Component as Component exposing (Indexed)
{-| Model encompassing all Material components. Since some components store
{-| TODO. user actions in their model (notably Snackbar), the model is generic in the
type of such "observations".
-} -}
type alias Updater' action model = type alias Model obs =
action -> model -> model { button : Indexed Button.Model
, textfield : Indexed Textfield.Model
, snackbar : Maybe (Snackbar.Model obs)
}
{-| TODO. {-| Initial model.
-} -}
type alias Updater action model = model : Model obs
action -> model -> (model, Effects action) model =
{ button = Dict.empty
type alias ComponentModel model components = , textfield = Dict.empty
{ model | components : components } , snackbar = Nothing
}
{-| TODO. {-| Action encompassing actions of all Material components.
-} -}
lift' : type alias Action obs =
(model -> submodel) -> -- get Component.Action (Model obs) obs
(model -> submodel -> model) -> -- set
Updater' subaction submodel -> -- update
subaction -> -- action
model -> -- model
(model, Effects action)
lift' get set update action model =
(set model (update action (get model)), Effects.none)
{-| TODO. {-| Update function for the above Action.
-} -}
lift : update :
(model -> submodel) -> -- get (Action obs -> obs)
(model -> submodel -> model) -> -- set -> Action obs
(subaction -> action) -> -- fwd -> Model obs
Updater subaction submodel -> -- update -> (Model obs, Effects obs)
subaction -> -- action update =
model -> -- model Component.update
(model, Effects action)
lift get set fwd update action model =
let
(submodel', e) = update action (get model)
in
(set model submodel', Effects.map fwd e)

View file

@ -30,7 +30,8 @@ module Material.Badge
@docs withBadge, noBackground, overlap @docs withBadge, noBackground, overlap
-} -}
import Material.Style exposing (Style, cs, attrib, multiple) import Html.Attributes
import Material.Style exposing (Style, cs, attribute, multiple)
{-| Optional style for Badge. No background for badge {-| Optional style for Badge. No background for badge
@ -55,4 +56,7 @@ overlap =
-} -}
withBadge : String -> Style withBadge : String -> Style
withBadge databadge = withBadge databadge =
multiple [cs "mdl-badge", attrib "data-badge" databadge] multiple
[ cs "mdl-badge"
, attribute (Html.Attributes.attribute "data-badge" databadge)
]

View file

@ -1,7 +1,8 @@
module Material.Button module Material.Button
( Model, model, Action(Click), update ( Model, model, Action(Click), update
, flat, raised, fab, minifab, icon , flat, raised, fab, minifab, icon
, Button, colored, primary, accent , colored, primary, accent
, View, State, Instance, instance, fwdClick
) where ) where
{-| From the [Material Design Lite documentation](http://www.getmdl.io/components/#buttons-section): {-| From the [Material Design Lite documentation](http://www.getmdl.io/components/#buttons-section):
@ -25,11 +26,11 @@ module Material.Button
See also the See also the
[Material Design Specification]([https://www.google.com/design/spec/components/buttons.html). [Material Design Specification]([https://www.google.com/design/spec/components/buttons.html).
# Component # Elm architecture
@docs Model, model, Action, update @docs Model, model, Action, update, View
# Style # Style
@docs Button, colored, primary, accent @docs colored, primary, accent
# View # View
Refer to the Refer to the
@ -38,6 +39,9 @@ for details about what type of buttons are appropriate for which situations.
@docs flat, raised, fab, minifab, icon @docs flat, raised, fab, minifab, icon
# Component
@docs State, Instance, instance, fwdClick
-} -}
import Html exposing (..) import Html exposing (..)
@ -49,6 +53,7 @@ import Signal exposing (Address, forwardTo)
import Material.Helpers as Helpers import Material.Helpers as Helpers
import Material.Style exposing (Style, cs, cs', styled) import Material.Style exposing (Style, cs, cs', styled)
import Material.Ripple as Ripple import Material.Ripple as Ripple
import Material.Component as Component exposing (Indexed)
{-| MDL button. {-| MDL button.
-} -}
@ -105,11 +110,6 @@ update action model =
-- VIEW -- VIEW
{-| Type tag for button styles.
-}
type Button = X
{-| Color button with primary or accent color depending on button type. {-| Color button with primary or accent color depending on button type.
-} -}
colored : Style colored : Style
@ -123,6 +123,7 @@ primary : Style
primary = primary =
cs "mdl-button--primary" cs "mdl-button--primary"
{-| Color button with accent color. {-| Color button with accent color.
-} -}
accent : Style accent : Style
@ -130,8 +131,6 @@ accent =
cs "mdl-button--accent" cs "mdl-button--accent"
{-| Component view.
-}
view : String -> Address Action -> Model -> List Style -> List Html -> Html view : String -> Address Action -> Model -> List Style -> List Html -> Html
view kind addr model styling html = view kind addr model styling html =
styled button styled button
@ -143,19 +142,26 @@ view kind addr model styling html =
) )
[ Helpers.blurOn "mouseup" [ Helpers.blurOn "mouseup"
, Helpers.blurOn "mouseleave" , Helpers.blurOn "mouseleave"
, onClick addr Click , Html.Events.onClick addr Click
] ]
(case model of (case model of
S (Just ripple) -> S (Just ripple) ->
Ripple.view Ripple.view
(forwardTo addr Ripple) (forwardTo addr Ripple)
[ class "mdl-button__ripple-container" [ class "mdl-button__ripple-container"
, Helpers.blurOn "mouseup" ] , Helpers.blurOn "mouseup"
]
ripple ripple
:: html :: html
_ -> html) _ -> html)
{-| Type of button views.
-}
type alias View =
Address Action -> Model -> List Style -> List Html -> Html
{-| From the {-| From the
[Material Design Specification](https://www.google.com/design/spec/components/buttons.html#buttons-flat-buttons): [Material Design Specification](https://www.google.com/design/spec/components/buttons.html#buttons-flat-buttons):
@ -176,7 +182,7 @@ Example use (uncolored flat button, assuming properly setup model):
flatButton = Button.flat addr model Button.Plain [text "Click me!"] flatButton = Button.flat addr model Button.Plain [text "Click me!"]
-} -}
flat : Address Action -> Model -> List Style -> List Html -> Html flat : View
flat = view "" flat = view ""
@ -197,7 +203,7 @@ Example use (colored raised button, assuming properly setup model):
raisedButton = Button.raised addr model Button.Colored [text "Click me!"] raisedButton = Button.raised addr model Button.Colored [text "Click me!"]
-} -}
raised : Address Action -> Model -> List Style -> List Html -> Html raised : View
raised = view "mdl-button--raised" raised = view "mdl-button--raised"
@ -223,13 +229,13 @@ Example use (colored with a '+' icon):
fabButton : Html fabButton : Html
fabButton = fab addr model Colored [Icon.i "add"] fabButton = fab addr model Colored [Icon.i "add"]
-} -}
fab : Address Action -> Model -> List Style -> List Html -> Html fab : View
fab = view "mdl-button--fab" fab = view "mdl-button--fab"
{-| Mini-sized variant of a Floating Action Button; refer to `fab`. {-| Mini-sized variant of a Floating Action Button; refer to `fab`.
-} -}
minifab : Address Action -> Model -> List Style -> List Html -> Html minifab : View
minifab = view "mdl-button--mini-fab" minifab = view "mdl-button--mini-fab"
@ -245,5 +251,56 @@ Example use (no color, displaying a '+' icon):
iconButton : Html iconButton : Html
iconButton = icon addr model Plain [Icon.i "add"] iconButton = icon addr model Plain [Icon.i "add"]
-} -}
icon : Address Action -> Model -> List Style -> List Html -> Html icon : View
icon = view "mdl-button--icon" icon = view "mdl-button--icon"
-- COMPONENT
{-|
-}
type alias State s =
{ s | button : Indexed Model }
type alias Observer obs =
Component.Observer Action obs
{-|
-}
type alias Instance state obs =
Component.Instance
Model
state
Action
obs
(List Style -> List Html -> Html)
{-| Component instance.
-}
instance :
Int
-> (Component.Action (State state) obs -> obs)
-> (Address Action -> Model -> List Style -> List Html -> Html)
-> Model
-> List (Observer obs)
-> Instance (State state) obs
instance id lift view model0 observers =
Component.instance
view update .button (\x y -> {y | button = x}) id lift model0 observers
{-| Lift the button Click action to your own action. E.g.,
-}
fwdClick : obs -> (Observer obs)
fwdClick obs action =
case action of
Click -> Just obs
_ -> Nothing

351
src/Material/Component.elm Normal file
View file

@ -0,0 +1,351 @@
module Material.Component
( embed, embedIndexed, Embedding, Observer
, Indexed
, Instance, instance, instance1
, update
, Action
) where
{-|
The Elm Architecture is conceptually very nice, but it forces us to write large
amounts of boilerplate whenever we need to use a "component". We must:
1. Retain the state of the component in our Model
2. Add the components actions to our Action
3. Dispatch those actions in our update
None of these things have anything to do with what we want from the component,
namely rendering it in our View function, and potentially reacting to some
(but not all) of its actions---e.g., we want to react to a Click of a button,
but we don't care when it updates its animation state.
This module provides an extensible mechanism for collecting arbitrary
(differently-typed) Elm Architecture components into a single component with
a single Action type and update function. The module is used internally to
produce `instance` functions; if you are using elm-mdl (and are not interested in
optimising for compiled program size or writing your own components), you
should ignore this module and look instead at `Material`.
# Embeddings
@docs Indexed, Embedding, embed, embedIndexed
# Instance construction
@docs Action, Instance, Observer, instance, instance1
# Instance consumption
@docs update
-}
import Effects exposing (Effects)
import Task
import Dict exposing (Dict)
import Material.Helpers exposing (map1, map2, map1st, map2nd, Update, Update')
-- TYPES
{-| Standard EA view function type.
-}
type alias View model action a =
Signal.Address action -> model -> a
-- EMBEDDING MODELS
{-| Indexed families of things.
-}
type alias Indexed a =
Dict Int a
{-| An __embedding__ of an Elm Architecture component is a variant in which
view and update functions know how to extract and update their model
from a larger master model.
-}
type alias Embedding model container action a =
{ view : View container action a
, update : Update container action
, getModel : container -> model
, setModel : model -> container -> container
}
{-| Embed a component. Third and fourth arguments are a getter (extract the
local model from the container) and a setter (update local model in the
container).
It is instructive to compare the types of the view and update function in
the input and output:
{- Input -} {- Output -}
View model action a View container action a
Update model action Update container action
-}
embed :
View model action a -> -- Given a view function,
Update model action -> -- an update function
(container -> model) -> -- a getter
(model -> container -> container) -> -- a setter
Embedding model container action a -- produce an Embedding.
embed view update get set =
{ view =
\addr model -> view addr (get model)
, update =
\action model ->
update action (get model)
|> map1st (flip set model)
, getModel = get
, setModel = set
}
{-| We are interested in particular embeddings where components of the same
type all have their state living inside a shared `Dict`; the individual
component has a key used to look up its own state.
-}
embedIndexed :
View model action a -> -- Given a view function,
Update model action -> -- an update function
(container -> Indexed model) -> -- a getter
(Indexed model -> container -> container) -> -- a setter
model -> -- an initial model for this instance
Int -> -- an instance id (*)
Embedding model container action a -- ... produce a Component.
embedIndexed view update get set model0 id =
let
get' model =
Dict.get id (get model) |> Maybe.withDefault model0
set' submodel model =
set (Dict.insert id submodel (get model)) model
in
embed view update get' set'
-- LIFTING ACTIONS
{-| Similarly to how embeddings enable collecting models of different type
in a single model container, we need to collect actions in a single "master
action" type. Obviously, actions need to be eventually executed by running
the corresponding update function. To avoid this master action type explicitly
representing the Action/update pairs of elm-mdl components, we represent an
action of an individual component as a partially applied update function; that
is, a function `container -> container`. E.g., the `Click` action of Button is
conceptually represented as:
embeddedButton : Embedding Button.Model container action ...
embeddedButton =
embedIndexed
Button.view Button.update .button {\m x -> {m|button=x} Button.model 0
clickAction : container -> container
clickAction = embeddedButton.update Button.click
When all Material components are embedded in the same `container` model, we
then have a uniform update mechanism.
We lost the ability to inspect the action when we did this, though. To be
able to react to some actions of a component, we add to our `container ->
container` type for actions a potential __observation__ of type `obs`.
In practice, this observation type `obs` will be the Action of the TEA
component __hosting__ MDL components.
Altogether, accounting also for effects, we arrive at the following type.
-}
type Action container obs =
A (container -> (container, Effects (Action container obs), Maybe obs))
{-| Type of observers, i.e., functions that take an actual action of the
underlying TEA component to an observation. E.g., Button has an Observer for
its `Click` action.
-}
type alias Observer action obs =
action -> Maybe obs
{-| Generic update function for Action.
-}
update :
(Action container obs -> obs) ->
Update' container (Action container obs) obs
update fwd (A f) container =
let
(container', fx, obs) =
f container
|> map2 (Effects.map fwd)
in
case obs of
Nothing ->
(container', fx)
Just x ->
(container', Effects.batch [ fx, Effects.task (Task.succeed x) ])
-- INSTANCES
{-| Type of component instances. A component instance contains a view,
get/set/map for the inner model, and a forwarder lifting component
actions to observations.
-}
type alias Instance model container action obs a =
{ view : View container obs a
, get : container -> model
, set : model -> container -> container
, map : (model -> model) -> container -> container
, fwd : action -> obs
}
{- TEA update function variant where running the function
produces not just a new model and an effect, but also an
observation.
-}
type alias Step model action obs =
action -> model -> (model, Effects action, Maybe obs)
{- Partially apply a step function to an action, producing a generic Action.
-}
pack : (Step model action obs) -> action -> Action model obs
pack update action =
A (update action >> map2 (Effects.map (pack update)))
{- Convert an update function to a step function by applying a
function that converts the action input to the update function into
an observation.
-}
observe : Observer action obs -> Update model action -> Step model action obs
observe f update action =
update action >> (\(model', effects) -> (model', effects, f action))
{- Return the first non-Nothing value in the list, or Nothing if no such
exists.
-}
pick : (a -> Maybe b) -> List a -> Maybe b
pick f xs =
case xs of
[] -> Nothing
x :: xs' ->
case f x of
Nothing -> pick f xs'
x -> x
{- Promote a list of Observers to a single Observer by picking, for a given
action, the first one that succeeds.
-}
connect : List (Observer action obs) -> Observer action obs
connect observers subaction =
pick ((|>) subaction) observers
{-| Given a lifting function, a list of observers and an embedding, construct an
Instance.
-}
instance'
: (Action container obs -> obs)
-> List (Observer action obs)
-> Embedding model container action a
-> Instance model container action obs a
instance' lift observers embedding =
let
fwd =
pack (observe (connect observers) embedding.update) >> lift
get =
embedding.getModel
set =
embedding.setModel
in
{ view =
\addr ->
embedding.view (Signal.forwardTo addr fwd)
, get = get
, set = set
, map = \f model -> set (f (get model)) model
, fwd = fwd
}
{-| It is helpful to see parameter names:
instance view update get set id lift model0 observers =
...
Convert a regular Elm Architecture component (`view`, `update`) to a component
which knows how to access its model inside a generic container model (`get`,
`set`), and which dispatches generic `Action` updates, lifted to the consumers
action type `obs` (`lift`). You can react to actions in custom way by providing
observers (`observers`). You must also provide an initial model (`model0`) and an
identifier for the instance (`id`). The identifier must be unique for all
instances of the same type stored in the same model (overapproximating rule of
thumb: if they are in the same file, they need distinct ids.)
Its instructive to compare the types of the input and output views:
{- Input -} {- Output -}
View model action a View container obs a
That is, this function fully converts a view from its own `model` and `action`
to the master `container` model and `observation` action.
-}
instance
: View model action a
-> Update model action
-> (container -> Indexed model)
-> (Indexed model -> container -> container)
-> Int
-> (Action container obs -> obs)
-> model
-> List (Observer action obs)
-> Instance model container action obs a
instance view update get set id lift model0 observers =
embedIndexed view update get set model0 id
|> instance' lift observers
{-| Variant of `instance` for components that are naturally singletons
(e.g., snackbar, layout).
-}
instance1
: View model action a
-> Update model action
-> (container -> Maybe model)
-> (Maybe model -> container -> container)
-> (Action container obs -> obs)
-> model
-> List (Observer action obs)
-> Instance model container action obs a
instance1 view update get set lift model0 observers =
embed view update (get >> Maybe.withDefault model0) (Just >> set)
|> instance' lift observers

View file

@ -0,0 +1,73 @@
module Material.Elevation
( shadow
, validElevations
, transition
) where
{-| From the [Material Design Lite documentation](https://github.com/google/material-design-lite/blob/master/src/shadow/README.md)
> The Material Design Lite (MDL) shadow is not a component in the same sense as
> an MDL card, menu, or textbox; it is a visual effect that can be assigned to a
> user interface element. The effect simulates a three-dimensional positioning of
> the element, as though it is slightly raised above the surface it rests upon
> a positive z-axis value, in user interface terms. The shadow starts at the
> edges of the element and gradually fades outward, providing a realistic 3-D
> effect.
>
> Shadows are a convenient and intuitive means of distinguishing an element from
> its surroundings. A shadow can draw the user's eye to an object and emphasize
> the object's importance, uniqueness, or immediacy.
>
> Shadows are a well-established feature in user interfaces, and provide users
> with a visual clue to an object's intended use or value. Their design and use
> is an important factor in the overall user experience.)
The [Material Design Specification](https://www.google.com/design/spec/what-is-material/elevation-shadows.html#elevation-shadows-elevation-android-)
pre-defines appropriate elevation for most UI elements; you need to manually
assign shadows only to your own elements.
You are encouraged to visit the
[Material Design specification](https://www.google.com/design/spec/what-is-material/elevation-shadows.html)
for details about appropriate use of shadows.
# Component
@docs shadow, validElevations, transition
-}
import Material.Style exposing (..)
{-| Indicate the elevation of an element by giving it a shadow.
The `z` argument indicates intended elevation; valid values
are 2, 3, 4, 6, 8, 16, 24. Invalid values produce no shadow.
(The specification uses only the values 1-6, 8, 9, 12, 16, 24 for standard UI
elements; MDL sources define all values 0-24, but omits most from production css.)
-}
shadow : Int -> Style
shadow z =
cs ("mdl-shadow--" ++ toString z ++ "dp")
{-| Programmatically accessible valid elevations for `shadow`.
-}
validElevations : List Int
validElevations =
[ 2, 3, 4, 6, 8, 16, 24 ]
{-| Add a CSS-transition to changes in elevation. Supply a transition
duration in milliseconds as argument.
NB! This style dictated by neither MDL nor the Material Design
Specification.
-}
transition : String -> Style
transition duration =
css "transition" ("box-shadow " ++ toString duration ++ "ms ease-in-out 0s")

View file

@ -3,23 +3,14 @@ module Material.Helpers where
import Html import Html
import Html.Attributes import Html.Attributes
import Effects exposing (Effects) import Effects exposing (Effects)
import Time exposing (Time)
import Task
filter : (a -> List b -> c) -> a -> List (Maybe b) -> c filter : (a -> List b -> c) -> a -> List (Maybe b) -> c
filter elem attr html = filter elem attr html =
elem attr (List.filterMap (\x -> x) html) elem attr (List.filterMap (\x -> x) html)
mapWithIndex : (Int -> a -> b) -> List a -> List b
mapWithIndex f xs =
let
loop k ys =
case ys of
[] -> []
y :: ys -> f k y :: loop (k+1) ys
in
loop 0 xs
effect : Effects b -> a -> (a, Effects b) effect : Effects b -> a -> (a, Effects b)
effect e x = (x, e) effect e x = (x, e)
@ -43,3 +34,69 @@ clip lower upper k = Basics.max lower (Basics.min k upper)
blurOn : String -> Html.Attribute blurOn : String -> Html.Attribute
blurOn evt = blurOn evt =
Html.Attributes.attribute ("on" ++ evt) <| "this.blur()" Html.Attributes.attribute ("on" ++ evt) <| "this.blur()"
-- TUPLES
map1 : (a -> a') -> (a, b, c) -> (a', b, c)
map1 f (x,y,z) = (f x, y, z)
map2 : (b -> b') -> (a, b, c) -> (a, b', c)
map2 f (x,y,z) = (x, f y, z)
map1st : (a -> c) -> (a,b) -> (c,b)
map1st f (x,y) = (f x, y)
map2nd : (b -> c) -> (a,b) -> (a,c)
map2nd f (x,y) = (x, f y)
{- Variant of EA update function type, where effects may be
lifted to a different type.
-}
type alias Update' model action action' =
action -> model -> (model, Effects action')
{-| Standard EA update function type.
-}
type alias Update model action =
Update' model action action
lift' :
(model -> submodel) -> -- get
(model -> submodel -> model) -> -- set
(subaction -> submodel -> submodel) ->
subaction -> -- action
model -> -- model
(model, Effects action)
lift' get set update action model =
(set model (update action (get model)), Effects.none)
lift :
(model -> submodel) -> -- get
(model -> submodel -> model) -> -- set
(subaction -> action) -> -- fwd
Update submodel subaction -> -- update
subaction -> -- action
model -> -- model
(model, Effects action)
lift get set fwd update action model =
let
(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

@ -352,7 +352,7 @@ tabsView addr model tabs =
, ("is-casting-shadow", model.mode == Standard) , ("is-casting-shadow", model.mode == Standard)
] ]
] ]
(tabs |> mapWithIndex (\tabIndex tab -> (tabs |> List.indexedMap (\tabIndex tab ->
filter a filter a
[ classList [ classList
[ ("mdl-layout__tab", True) [ ("mdl-layout__tab", True)

84
src/Material/Scheme.elm Normal file
View file

@ -0,0 +1,84 @@
module Material.Scheme
( topWithScheme, top
) where
{-|
The elm-mdl library depends on Google's MDL CSS implementation, and your
application must load this CSS in order for elm-mdl to function correctly.
There are two ways to accomplish this:
1. Load CSS from HTML by adding suitable `<link ...>` directives to the
HTML-file containing your app, or
2. Load CSS from Elm (by inserting `style` elements into the DOM).
# Load CSS from HTML
To load CSS manually, add the following to your
<!-- MDL -->
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300,500|Roboto+Mono|Roboto+Condensed:400,700&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.min.css" />
# Loading CSS from Elm
@docs topWithScheme, top
-}
import String
import Html exposing (..)
import Html.Attributes exposing (..)
import Material.Color exposing (Palette(..), Color)
scheme : Palette -> Palette -> String
scheme primary accent =
[ "https://code.getmdl.io/1.1.3/" ++ Material.Color.scheme primary accent
, "https://fonts.googleapis.com/icon?family=Material+Icons"
, "https://fonts.googleapis.com/css?family=Roboto:400,300,500|Roboto+Mono|Roboto+Condensed:400,700&subset=latin,latin-ext"
]
|> List.map (\url -> "@import url(" ++ url ++ ");")
|> String.join "\n"
{-| Top-level container for Material components. This will force loading of
Material Design Lite CSS files by inserting an appropriate `style` element.
Supply primary and accent colors as parameters. Refer to the Material Design
Lite [Custom CSS theme builder](https://www.getmdl.io/customize/index.html)
to preview combinations. Please be aware that Grey, Blue Grey, and Brown
cannot be secondary colors. If you choose them as such anyway, you will get the
default theme.
**NB!** Using this top-level container is not recommended, as most browsers
will load CSS requested from `style` elements concurrently with rendering the
initial page, which will produce a flicker on page load. The container is
included only to provide an option to get started quickly and for use with
elm-reactor.
TODO: Usage example
-}
topWithScheme: Palette -> Palette -> Html -> Html
topWithScheme primary accent content =
div [] <|
{- Trick from Peter Damoc to load CSS outside of <head>.
https://github.com/pdamoc/elm-mdl/blob/master/src/Mdl.elm#L63
-}
[ node "style"
[ type' "text/css"]
[ Html.text <| scheme primary accent]
, content
]
{-| Top-level container with default color scheme. See `topWithScheme` above.
-}
top : Html -> Html
top content =
-- Force default color-scheme by picking an invalid combination.
topWithScheme Grey Grey content

83
src/Material/Shadow.elm Normal file
View file

@ -0,0 +1,83 @@
module Material.Elevation
( shadow
, transition
) where
{-| From the [Material Design Lite documentation](https://github.com/google/material-design-lite/blob/master/src/shadow/README.md)
> The Material Design Lite (MDL) shadow is not a component in the same sense as
> an MDL card, menu, or textbox; it is a visual effect that can be assigned to a
> user interface element. The effect simulates a three-dimensional positioning of
> the element, as though it is slightly raised above the surface it rests upon
> a positive z-axis value, in user interface terms. The shadow starts at the
> edges of the element and gradually fades outward, providing a realistic 3-D
> effect.
>
> Shadows are a convenient and intuitive means of distinguishing an element from
> its surroundings. A shadow can draw the user's eye to an object and emphasize
> the object's importance, uniqueness, or immediacy.
>
> Shadows are a well-established feature in user interfaces, and provide users
> with a visual clue to an object's intended use or value. Their design and use
> is an important factor in the overall user experience.)
See also the
[Material Design specification](https://www.google.com/design/spec/what-is-material/elevation-shadows.html)
.
# Component
@docs shadow, transition
# View
@docs view
-}
import Effects exposing (Effects, none)
import Html exposing (..)
-- MODEL
{-| Component model.
-}
type alias Model =
{
}
{-| Default component model constructor.
-}
model : Model
model =
{
}
-- ACTION, UPDATE
{-| Component action.
-}
type Action
= MyAction
{-| Component update.
-}
update : Action -> Model -> (Model, Effects Action)
update action model =
(model, none)
-- VIEW
{-| Component view.
-}
view : Signal.Address Action -> Model -> Html
view addr model =
div [] [ h1 [] [ text "TEMPLATE" ] ]

View file

@ -1,7 +1,8 @@
module Material.Snackbar module Material.Snackbar
( Contents, Model, model, toast, snackbar, isActive ( Contents, Model, model, toast, snackbar, isActive, activeAction
, Action(Add, Action), update , Action(Add, Action), update
, view , view
, Instance, instance, add
) where ) where
{-| TODO {-| TODO
@ -24,7 +25,8 @@ import Task
import Time exposing (Time) import Time exposing (Time)
import Maybe exposing (andThen) import Maybe exposing (andThen)
import Material.Helpers exposing (mapFx, addFx) import Material.Component as Component exposing (Indexed)
import Material.Helpers exposing (mapFx, addFx, delay)
-- MODEL -- MODEL
@ -44,7 +46,7 @@ type alias Contents a =
-} -}
type alias Model a = type alias Model a =
{ queue : List (Contents a) { queue : List (Contents a)
, state : State a , state : State' a
, seq : Int , seq : Int
} }
@ -84,7 +86,9 @@ snackbar message actionMessage action =
, fade = 250 , fade = 250
} }
{-| TODO {-| TODO
(Bad name)
-} -}
isActive : Model a -> Maybe (Contents a) isActive : Model a -> Maybe (Contents a)
isActive model = isActive model =
@ -96,6 +100,15 @@ isActive model =
Nothing 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 a -> Maybe (Contents a)
contentsOf model = contentsOf model =
case model.state of case model.state of
@ -107,7 +120,7 @@ contentsOf model =
-- SNACKBAR STATE MACHINE -- SNACKBAR STATE MACHINE
type State a type State' a
= Inert = Inert
| Active (Contents a) | Active (Contents a)
| Fading (Contents a) | Fading (Contents a)
@ -118,13 +131,6 @@ type Transition
| Click | 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 a -> (Model a, Effects Transition)
move transition model = move transition model =
case (model.state, transition) of case (model.state, transition) of
@ -270,3 +276,61 @@ view addr model =
) )
buttonBody buttonBody
] ]
-- COMPONENT
{-|
-}
type alias State s obs =
{ s | snackbar : Maybe (Model obs) }
{-|
-}
type alias Instance state obs =
Component.Instance (Model obs) state (Action obs) obs Html
{-|
-}
type alias Observer obs =
Component.Observer (Action obs) obs
actionObserver : Observer ons
actionObserver action =
case action of
Action action' ->
Just action'
_ ->
Nothing
{-| Component instance.
-}
instance
: (Component.Action (State state obs) obs -> obs)
-> (Model obs)
-> Instance (State state obs) obs
instance lift model0 =
Component.instance1
view update .snackbar (\x y -> {y | snackbar = x}) lift model0 [ actionObserver ]
{-|
TODO
-}
add :
Contents obs
-> Instance (State state obs) obs
-> (State state obs)
-> (State state obs, Effects obs)
add contents inst model =
let
(sb, fx) =
update (Add contents) (inst.get model)
in
(inst.set sb model, Effects.map inst.fwd fx)

View file

@ -1,8 +1,7 @@
module Material.Style module Material.Style
( Style ( Style
, styled , cs, cs', css, css', attribute, multiple
, cs, cs', css, css', attrib, multiple , styled, div, span, stylesheet
, stylesheet
) where ) where
@ -17,10 +16,10 @@ add to or remove from the contents of an already constructed class Attribute.)
@docs Style @docs Style
# Constructors # Constructors
@docs cs, cs', css, css', attrib, multiple @docs cs, cs', css, css', attribute, multiple
# Application # Application
@docs styled @docs styled, div, span
# Convenience # Convenience
@docs stylesheet @docs stylesheet
@ -41,44 +40,32 @@ import Html.Attributes
type Style type Style
= Class String = Class String
| CSS (String, String) | CSS (String, String)
| Attr (String, String) | Attr Html.Attribute
| Multiple (List Style) | Multiple (List Style)
| NOP | NOP
multipleOf : Style -> Maybe (List Style)
multipleOf style = type alias Summary =
{ attrs : List Attribute
, classes : List String
, css : List (String, String)
}
collect1 : Style -> Summary -> Summary
collect1 style ({ classes, css, attrs } as acc) =
case style of case style of
Multiple multiple -> Just multiple Class x -> { acc | classes = x :: classes }
_ -> Nothing CSS x -> { acc | css = x :: css }
Attr x -> { acc | attrs = x :: attrs }
Multiple styles -> List.foldl collect1 acc styles
NOP -> acc
attrOf : Style -> Maybe (String, String) collect : List Style -> Summary
attrOf style = collect =
case style of List.foldl collect1 { classes=[], css=[], attrs=[] }
Attr attrib -> Just attrib
_ -> Nothing
cssOf : Style -> Maybe (String, String)
cssOf style =
case style of
CSS css -> Just css
_ -> Nothing
classOf : Style -> Maybe String
classOf style =
case style of
Class c -> Just c
_ -> Nothing
flatten : Style -> List Style -> List Style
flatten style styles =
case style of
Multiple styles' ->
List.foldl flatten styles' styles
style ->
style :: styles
{-| Handle the common case of setting attributes of a standard Html node {-| Handle the common case of setting attributes of a standard Html node
from a List Style. Use like this: from a List Style. Use like this:
@ -96,19 +83,41 @@ Note that if you do specify `style`, `class`, or `classList` attributes in
(*), they will be discarded. (*), they will be discarded.
-} -}
styled : (List Attribute -> a) -> List Style -> List Attribute -> a styled : (List Attribute -> a) -> List Style -> List Attribute -> a
styled ctor styles attrs = styled ctor styles attrs' =
let let
flatStyles = List.foldl flatten [] styles { classes, css, attrs } = collect styles
styleAttrs = (List.filterMap attrOf flatStyles)
|> List.map (\attrib -> Html.Attributes.attribute (fst attrib) ( snd attrib))
in in
ctor ctor
( Html.Attributes.style (List.filterMap cssOf flatStyles) ( Html.Attributes.style css
:: Html.Attributes.class (String.join " " (List.filterMap classOf flatStyles)) :: Html.Attributes.class (String.join " " classes)
:: List.append attrs styleAttrs :: List.append attrs attrs'
) )
{-| Handle the ultra-common case of setting attributes of a div element.
Use like this:
myDiv : Html
myDiv =
Style.div
[ Color.background Color.primary
, Color.text Color.accentContrast
]
[ text "I'm in color!" ]
-}
div : List Style -> List Html -> Html
div styles elems =
styled Html.div styles [] elems
{-| Convenience function for the reasonably common case of setting attributes
of a span element. See also `div`.
-}
span : List Style -> List Html -> Html
span styles elems =
styled Html.span styles [] elems
{-| Add a HTML class to a component. (Name chosen to avoid clashing with {-| Add a HTML class to a component. (Name chosen to avoid clashing with
Html.Attributes.class.) Html.Attributes.class.)
@ -130,11 +139,13 @@ css : String -> String -> Style
css key value = css key value =
CSS (key, value) CSS (key, value)
{-| Add a custom attribute {-| Add a custom attribute
-} -}
attrib : String -> String -> Style attribute : Html.Attribute -> Style
attrib key value = attribute attr =
Attr (key, value) Attr attr
{-| Add a custom attribute {-| Add a custom attribute
-} -}
@ -142,6 +153,7 @@ multiple : List Style -> Style
multiple styles = multiple styles =
Multiple (styles) Multiple (styles)
{-| Conditionally add a CSS style to a component {-| Conditionally add a CSS style to a component
-} -}
css' : String -> String -> Bool -> Style css' : String -> String -> Bool -> Style

View file

@ -26,15 +26,22 @@ This implementation provides only single-line.
# Configuration # Configuration
@docs Kind, Label @docs Kind, Label
# Component # Elm Architecture
@docs Action, Model, model, update, view @docs Action, Model, model, update, view
# Component
@docs State, Instance
@docs instance, fwdInput, fwdBlur, fwdFocus
-} -}
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (..) import Html.Events exposing (..)
import Effects
import Material.Helpers exposing (filter) import Material.Helpers exposing (filter)
import Material.Component as Component exposing (Indexed)
-- MODEL -- MODEL
@ -131,6 +138,7 @@ view : Signal.Address Action -> Model -> Html
view addr model = view addr model =
let hasFloat = model.label |> Maybe.map .float |> Maybe.withDefault False let hasFloat = model.label |> Maybe.map .float |> Maybe.withDefault False
hasError = model.error |> Maybe.map (always True) |> Maybe.withDefault False hasError = model.error |> Maybe.map (always True) |> Maybe.withDefault False
labelText = model.label |> Maybe.map .text
in in
filter div filter div
[ classList [ classList
@ -155,8 +163,71 @@ view addr model =
, onFocus addr Focus , onFocus addr Focus
] ]
[] []
, model.label |> Maybe.map (\l -> , Just <| label
label [class "mdl-textfield__label"] [text l.text]) [class "mdl-textfield__label"]
(case labelText of
Just str -> [ text str ]
Nothing -> [])
, model.error |> Maybe.map (\e -> , model.error |> Maybe.map (\e ->
span [class "mdl-textfield__error"] [text e]) span [class "mdl-textfield__error"] [text e])
] ]
-- COMPONENT
{-|
-}
type alias State state =
{ state | textfield : Indexed Model }
{-|
-}
type alias Instance state obs =
Component.Instance Model state Action obs Html
{-| Component constructor. See module `Material`.
-}
instance :
Int
-> (Component.Action (State state) obs -> obs)
-> Model
-> List (Component.Observer Action obs)
-> Instance (State state) obs
instance =
let
update' action model = (update action model, Effects.none)
in
Component.instance view update' .textfield (\x y -> {y | textfield = x})
{-| Lift the button Click action to your own action. E.g.,
-}
fwdInput : (String -> obs) -> Action -> Maybe obs
fwdInput f action =
case action of
Input str -> Just (f str)
_ -> Nothing
{-| Lift the Blur action to your own action.
-}
fwdBlur : obs -> Action -> Maybe obs
fwdBlur o action =
case action of
Blur -> Just o
_ -> Nothing
{-| Lift the Focus action to your own action.
-}
fwdFocus : obs -> Action -> Maybe obs
fwdFocus o action =
case action of
Focus -> Just o
_ -> Nothing