Implemented Waterfall headermode. Fixes #21.

This commit is contained in:
Søren Debois 2016-03-28 21:25:11 +02:00
parent d1f56c1024
commit 5c2f86b13a
4 changed files with 167 additions and 66 deletions

View file

@ -9,6 +9,7 @@ import Task exposing (Task)
import Array exposing (Array) import Array exposing (Array)
import Material.Layout as Layout exposing (defaultLayoutModel) import Material.Layout as Layout exposing (defaultLayoutModel)
import Material.Icon as Icon
import Material import Material
import Demo.Buttons import Demo.Buttons
@ -30,6 +31,8 @@ layoutModel : Layout.Model
layoutModel = layoutModel =
{ defaultLayoutModel { defaultLayoutModel
| state = Layout.initState (List.length tabs) | state = Layout.initState (List.length tabs)
, mode = Layout.Waterfall True
, fixedHeader = False
} }
@ -83,26 +86,28 @@ drawer =
[ Layout.title "Example drawer" [ Layout.title "Example drawer"
, Layout.navigation , Layout.navigation
[ Layout.link [ Layout.link
[href "https://github.com/debois/elm-mdl"] [ href "https://www.getmdl.io/components/index.html" ]
[text "github"] [ text "MDL" ]
, Layout.link , Layout.link
[href "http://package.elm-lang.org/packages/debois/elm-mdl/1.0.0/"] [ href "https://www.google.com/design/spec/material-design/introduction.html"]
[text "elm-package"] [ text "Material Design"]
] ]
] ]
header : List Html header : List Html
header = header =
[ Layout.title "elm-mdl" [ Layout.row
, Layout.spacer [ Layout.title "elm-mdl"
, Layout.navigation , Layout.spacer
[ Layout.link , Layout.navigation
[ href "https://www.getmdl.io/components/index.html" ] [ Layout.link
[ text "MDL" ] [href "https://github.com/debois/elm-mdl"]
, Layout.link [span [] [text "github"] ]
[ href "https://www.google.com/design/spec/material-design/introduction.html"] , Layout.link
[ text "Material Design"] [href "http://package.elm-lang.org/packages/debois/elm-mdl/latest/"]
[text "elm-package"]
]
] ]
] ]
@ -142,9 +147,9 @@ view addr model =
in in
Layout.view (Signal.forwardTo addr LayoutAction) model.layout Layout.view (Signal.forwardTo addr LayoutAction) model.layout
{ header = Just header { header = header
, drawer = Just drawer , drawer = drawer
, tabs = Just tabTitles , tabs = tabTitles
, main = [ top ] , main = [ top ]
} }
{- The following line is not needed when you manually set up {- The following line is not needed when you manually set up

View file

@ -9,7 +9,7 @@
<!-- 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.1/material.teal-red.min.css" /> <link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.teal-red.min.css" />
</head> </head>
<body> <body>

View file

@ -73,7 +73,7 @@ css primary accent =
BlueGrey -> "" BlueGrey -> ""
_ -> "." ++ toString primary ++ "-" ++ toString accent _ -> "." ++ toString primary ++ "-" ++ toString accent
in in
[ "https://code.getmdl.io/1.1.1/material" ++ cssFile ++ ".min.css" [ "https://code.getmdl.io/1.1.3/material" ++ cssFile ++ ".min.css"
, "https://fonts.googleapis.com/icon?family=Material+Icons" , "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" , "https://fonts.googleapis.com/css?family=Roboto:400,300,500|Roboto+Mono|Roboto+Condensed:400,700&subset=latin,latin-ext"
] ]
@ -90,7 +90,7 @@ 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.1/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 Supply primary and accent colors as parameters. Refer to the
Material Design Lite [Custom CSS theme builder](https://www.getmdl.io/customize/index.html) Material Design Lite [Custom CSS theme builder](https://www.getmdl.io/customize/index.html)

View file

@ -1,8 +1,8 @@
module Material.Layout module Material.Layout
( setupSizeChangeSignal ( setupSizeChangeSignal
, Mode, Model, defaultLayoutModel, initState , Mode(..), Model, defaultLayoutModel, initState
, Action(SwitchTab, ToggleDrawer), update , Action(SwitchTab, ToggleDrawer), update
, spacer, title, navigation, link , row, spacer, title, navigation, link
, Contents, view , Contents, view
) where ) where
@ -36,7 +36,7 @@ module Material.Layout
@docs Contents, view @docs Contents, view
## Sub-views ## Sub-views
@docs spacer, title, navigation, link @docs row, spacer, title, navigation, link
# Setup # Setup
@docs setupSizeChangeSignal @docs setupSizeChangeSignal
@ -47,14 +47,18 @@ import Array exposing (Array)
import Maybe exposing (andThen, map) import Maybe exposing (andThen, map)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onClick) import Html.Events exposing (onClick, on)
import Effects exposing (Effects) import Effects exposing (Effects)
import Window import Window
import Json.Decode as Json
import Material.Helpers exposing (..) import Material.Helpers exposing (..)
import Material.Ripple as Ripple import Material.Ripple as Ripple
import Material.Icon as Icon import Material.Icon as Icon
import DOM
-- SETUP -- SETUP
@ -82,6 +86,8 @@ setupSizeChangeSignal f =
type alias State' = type alias State' =
{ tabs : Array Ripple.Model { tabs : Array Ripple.Model
, isSmallScreen : Bool , isSmallScreen : Bool
, isCompact : Bool
, isAnimating : Bool
} }
@ -131,6 +137,8 @@ initState : Int -> State
initState no_tabs = initState no_tabs =
S { tabs = Array.repeat no_tabs Ripple.model S { tabs = Array.repeat no_tabs Ripple.model
, isSmallScreen = False -- TODO , isSmallScreen = False -- TODO
, isCompact = False
, isAnimating = False
} }
@ -163,8 +171,11 @@ type Action
| ToggleDrawer | ToggleDrawer
-- Private -- Private
| SmallScreen Bool -- True means small screen | SmallScreen Bool -- True means small screen
| ScrollTab Int | ScrollTab Float
| ScrollContents Float
| Ripple Int Ripple.Action | Ripple Int Ripple.Action
| Click
| TransitionEnd
{-| Component update. {-| Component update.
@ -172,7 +183,7 @@ type Action
update : Action -> Model -> (Model, Effects Action) update : Action -> Model -> (Model, Effects Action)
update action model = update action model =
let (S state) = model.state in let (S state) = model.state in
case action of case action of
SmallScreen isSmall -> SmallScreen isSmall ->
{ model { model
| state = S ({ state | isSmallScreen = isSmall }) | state = S ({ state | isSmallScreen = isSmall })
@ -198,15 +209,37 @@ update action model =
in in
({ model | state = S state' }, effect) ({ model | state = S state' }, effect)
ScrollTab tab -> ScrollTab tab ->
(model, Effects.none) -- TODO (model, Effects.none) -- TODO
ScrollContents scrollTop ->
let
headerVisible = state.isSmallScreen || model.fixedHeader
state' =
{ state
| isCompact = scrollTop > 0
, isAnimating = headerVisible
}
in
( { model | state = S state' }, Effects.none )
TransitionEnd ->
( { model | state = S { state | isAnimating = False } }
, Effects.none
)
Click ->
( { model | state = S { state | isAnimating = True, isCompact = False } }
, Effects.none
)
-- AUXILIARY VIEWS -- AUXILIARY VIEWS
{-| Push subsequent elements in header row or drawer column to the right/bottom. {-| Push subsequent elements in header row or drawer column to the right/bottom.
-} -}
spacer : Html spacer : Html
@ -233,6 +266,12 @@ link attrs contents =
a (class "mdl-navigation__link" :: attrs) contents a (class "mdl-navigation__link" :: attrs) contents
{-| Header row.
-}
row : List Html -> Html
row =
div [ class "mdl-layout__header-row" ]
-- MAIN VIEWS -- MAIN VIEWS
@ -243,18 +282,34 @@ link attrs contents =
- A `Seamed` header does not cast shadow, is permanently affixed to the top of the - A `Seamed` header does not cast shadow, is permanently affixed to the top of the
screen. screen.
- A `Scroll`'ing header scrolls with contents. - A `Scroll`'ing header scrolls with contents.
- A `Waterfall` header drops either the top (argument True) or bottom (argument False)
header-row when content scrolls.
-} -}
type Mode type Mode
= Standard = Standard
| Seamed | Seamed
| Scroll | Scroll
-- | Waterfall | Waterfall Bool
isWaterfall : Mode -> Bool
isWaterfall mode =
case mode of
Waterfall _ -> True
_ -> False
type alias Addr = Signal.Address Action type alias Addr = Signal.Address Action
toList : Maybe a -> List a
toList x =
case x of
Nothing -> []
Just y -> [y]
tabsView : Addr -> Model -> List Html -> Html tabsView : Addr -> Model -> List Html -> Html
tabsView addr model tabs = tabsView addr model tabs =
let chevron direction offset = let chevron direction offset =
@ -277,6 +332,7 @@ tabsView addr model tabs =
[ ("mdl-layout__tab-bar", True) [ ("mdl-layout__tab-bar", True)
, ("mdl-js-ripple-effect", model.rippleTabs) , ("mdl-js-ripple-effect", model.rippleTabs)
, ("mds-js-ripple-effect--ignore-events", model.rippleTabs) , ("mds-js-ripple-effect--ignore-events", model.rippleTabs)
, ("is-casting-shadow", model.mode == Standard)
] ]
] ]
(tabs |> mapWithIndex (\tabIndex tab -> (tabs |> mapWithIndex (\tabIndex tab ->
@ -302,18 +358,44 @@ tabsView addr model tabs =
] ]
headerView : Model -> (Maybe Html, Maybe (List Html), Maybe Html) -> Html headerView : Addr -> Model -> (Maybe Html, List Html, Maybe Html) -> Html
headerView model (drawerButton, row, tabs) = headerView addr model (drawerButton, rows, tabs) =
filter Html.header let
[ classList mode =
[ ("mdl-layout__header", True) case model.mode of
, ("is-casting-shadow", model.mode == Standard) Standard -> ""
] Scroll -> "mdl-layout__header--scroll"
] Seamed -> "mdl-layout__header--seamed"
[ drawerButton Waterfall True -> "mdl-layout__header--waterfall mdl-layout__header--waterfall-hide-top"
, row |> Maybe.map (div [ class "mdl-layout__header-row" ]) Waterfall False -> "mdl-layout__header--waterfall"
, tabs in
] Html.header
([ classList
[ ("mdl-layout__header", True)
, ("is-casting-shadow",
model.mode == Standard ||
(isWaterfall model.mode && (s model).isCompact)
)
, ("is-animating", (s model).isAnimating)
, ("is-compact", (s model).isCompact)
, (mode, mode /= "")
]
]
|> List.append (
if isWaterfall model.mode then
[ onClick addr Click
, on "transitionend" Json.value (\_ -> Signal.message addr TransitionEnd)
]
else
[]
)
)
(List.concatMap (\x -> x)
[ toList drawerButton
, rows
, toList tabs
]
)
drawerButton : Addr -> Html drawerButton : Addr -> Html
@ -349,17 +431,18 @@ drawerView addr model elems =
{-| Content of the layout only (contents of main pane is set elsewhere). Every {-| Content of the layout only (contents of main pane is set elsewhere). Every
part is optional. If `header` is `Nothing`, tabs will not be shown. part is optional; if you supply an empty list for either, the sub-component is
omitted.
The `header` and `drawer` contains the contents of the header row and drawer, The `header` and `drawer` contains the contents of the header rows and drawer,
respectively. Use `spacer`, `title`, `nav`, and respectively. Use `row`, `spacer`, `title`, `nav`, and `link`, as well as
`link`, as well as regular Html to construct these. The `tabs` contains regular Html to construct these. The `tabs` contains
the title of each tab. the title of each tab.
-} -}
type alias Contents = type alias Contents =
{ header : Maybe (List Html) { header : List Html
, drawer : Maybe (List Html) , drawer : List Html
, tabs : Maybe (List Html) , tabs : List Html
, main : List Html , main : List Html
} }
@ -371,11 +454,11 @@ view addr model { drawer, header, tabs, main } =
let let
(contentDrawerButton, headerDrawerButton) = (contentDrawerButton, headerDrawerButton) =
case (drawer, header, model.fixedHeader) of case (drawer, header, model.fixedHeader) of
(Just _, Just _, True) -> (_ :: _, _ :: _, True) ->
-- Drawer with fixedHeader: Add the button to the header -- Drawer with fixedHeader: Add the button to the header
(Nothing, Just <| drawerButton addr) (Nothing, Just <| drawerButton addr)
(Just _, _, _) -> (_ :: _, _, _) ->
-- Drawer, no or non-fixed header: Add the button before contents. -- Drawer, no or non-fixed header: Add the button before contents.
(Just <| drawerButton addr, Nothing) (Just <| drawerButton addr, Nothing)
@ -383,38 +466,51 @@ view addr model { drawer, header, tabs, main } =
-- No drawer: no button. -- No drawer: no button.
(Nothing, Nothing) (Nothing, Nothing)
mode = hasHeader =
case model.mode of not (List.isEmpty tabs && List.isEmpty header)
Standard -> ""
Scroll -> "mdl-layout__header-scroll"
-- Waterfall -> "mdl-layout__header-waterfall"
Seamed -> "mdl-layout__header-seamed"
hasHeader = tabsElems =
tabs /= Nothing || header /= Nothing if List.isEmpty tabs then
Nothing
else
Just (tabsView addr model tabs)
in in
div div
[ class "mdl-layout__container" ] [ classList
[ ("mdl-layout__container", True)
, ("has-scrolling-header", model.mode == Scroll)
]
]
[ filter div [ filter div
[ classList [ classList
[ ("mdl-layout", True) [ ("mdl-layout ", True)
, ("is-upgraded", True) , ("is-upgraded", True)
, ("is-small-screen", (s model).isSmallScreen) , ("is-small-screen", (s model).isSmallScreen)
, ("has-drawer", drawer /= Nothing) , ("has-drawer", drawer /= [])
, ("has-tabs", tabs /= Nothing) , ("has-tabs", tabs /= [])
, ("mdl-js-layout", True) , ("mdl-js-layout", True)
, ("mdl-layout--fixed-drawer", model.fixedDrawer && drawer /= Nothing) , ("mdl-layout--fixed-drawer", model.fixedDrawer && drawer /= [])
, ("mdl-layout--fixed-header", model.fixedHeader && hasHeader) , ("mdl-layout--fixed-header", model.fixedHeader && hasHeader)
, ("mdl-layout--fixed-tabs", model.fixedTabs && tabs /= Nothing) , ("mdl-layout--fixed-tabs", model.fixedTabs && tabs /= [])
] ]
] ]
[ if hasHeader then [ if hasHeader then
Just <| headerView model (headerDrawerButton, header, Maybe.map (tabsView addr model) tabs) Just <| headerView addr model (headerDrawerButton, header, tabsElems)
else else
Nothing Nothing
, drawer |> Maybe.map (\_ -> obfuscator addr model) , if List.isEmpty drawer then Nothing else Just (obfuscator addr model)
, drawer |> Maybe.map (drawerView addr model) , if List.isEmpty drawer then Nothing else Just (drawerView addr model drawer)
, contentDrawerButton , contentDrawerButton
, Just <| main' [ class "mdl-layout__content" ] main , main'
( class "mdl-layout__content"
:: (
if isWaterfall model.mode then
[ on "scroll" (DOM.target DOM.scrollTop) (ScrollContents >> Signal.message addr) ]
else
[]
)
)
main
|> Just
] ]
] ]