added Search, refactored scrollingNav

This commit is contained in:
Lucas Payr 2020-04-10 08:04:33 +02:00
parent 57a221aa45
commit 82b59530fd
8 changed files with 249 additions and 157 deletions

View File

@ -154,17 +154,6 @@ validatedInput model =
}
]
scrollingNavCard : Element msg
scrollingNavCard =
[ Element.el Heading.h3 <| Element.text "Scrolling Nav"
, Element.text "Resize the screen and open the side-menu. Then start scrolling to see the scrolling navigation in action."
|> List.singleton
|> Element.paragraph []
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
view : Model -> Element Msg
view model =
Element.column (Grid.section ++ [ Element.centerX ])
@ -176,6 +165,6 @@ view model =
, Element.wrappedRow (Grid.simple ++ [Element.height <| Element.shrink]) <|
[ filterSelect model.filterSelect
, validatedInput model.validatedInput
, scrollingNavCard
]
]

View File

@ -21,7 +21,7 @@ import Framework.Tag as Tag
import Html exposing (Html)
import Html.Attributes as Attributes
import Icons
import Layout exposing (Direction, Layout)
import Layout exposing (Part, Layout)
import Core.Style exposing (Style)
import Reusable
import Set exposing (Set)
@ -43,6 +43,7 @@ type alias LoadedModel =
, layout : Layout
, displayDialog : Bool
, deviceClass : DeviceClass
, search : String
}
@ -55,14 +56,16 @@ type LoadedMsg
= StatelessSpecific Stateless.Msg
| ReusableSpecific Reusable.Msg
| ComponentSpecific Component.Msg
| ScrollingNavSpecific (ScrollingNav.Msg Section)
| UpdateScrollingNav (ScrollingNav.Model Section -> ScrollingNav.Model Section)
| TimePassed Int
| AddSnackbar String
| ToggleDialog Bool
| ChangedSidebar (Maybe Direction)
| ChangedSidebar (Maybe Part)
| Resized { width : Int, height : Int }
| Load String
| JumpTo Section
| ChangedSearch String
| Idle
type Msg
@ -109,6 +112,11 @@ style =
, moreVerticalIcon =
Icons.moreVertical |> Element.html |> Element.el []
, spacing = 8
, title = Heading.h2
, searchIcon =
Icons.search |> Element.html |> Element.el []
, search =
Color.simple ++ [Font.color <| Element.rgb255 0 0 0 ]
}
@ -119,6 +127,12 @@ initialModel { viewport } =
ScrollingNav.init
{ labels = Section.toString
, arrangement = Section.asList
, toMsg = \result ->
case result of
Ok fun ->
UpdateScrollingNav fun
Err _ ->
Idle
}
in
( { component = Component.init
@ -133,8 +147,9 @@ initialModel { viewport } =
}
|> Element.classifyDevice
|> .class
, search = ""
}
, cmd |> Cmd.map ScrollingNavSpecific
, cmd
)
@ -261,16 +276,15 @@ view model =
]
, onChangedSidebar = ChangedSidebar
, title =
(if m.deviceClass == Phone || m.deviceClass == Tablet then
m.scrollingNav
|> ScrollingNav.current Section.fromString
|> Maybe.map Section.toString
|> Maybe.withDefault "Elm-Ui-Widgets"
else
"Elm-Ui-Widgets"
)
"Elm-Ui-Widgets"
|> Element.text
|> Element.el Heading.h1
, search =
Just
{ text = m.search
, onChange = ChangedSearch
, label = "Search"
}
}
@ -309,23 +323,18 @@ updateLoaded msg model =
}
)
(Cmd.map StatelessSpecific)
ScrollingNavSpecific m ->
model.scrollingNav
|> ScrollingNav.update m
|> Tuple.mapBoth
(\scrollingNav ->
{ model
| scrollingNav = scrollingNav
}
)
(Cmd.map ScrollingNavSpecific)
UpdateScrollingNav fun ->
( { model | scrollingNav = model.scrollingNav |> fun}
, Cmd.none
)
TimePassed int ->
( { model
| layout = model.layout |> Layout.timePassed int
}
, Cmd.none
, ScrollingNav.getPos
|> Task.perform UpdateScrollingNav
)
AddSnackbar string ->
@ -344,7 +353,7 @@ updateLoaded msg model =
)
ChangedSidebar sidebar ->
( { model | layout = model.layout |> Layout.setSidebar sidebar }
( { model | layout = model.layout |> Layout.activate sidebar }
, Cmd.none
)
@ -354,9 +363,17 @@ updateLoaded msg model =
JumpTo section ->
( model
, model.scrollingNav
|> ScrollingNav.jumpTo section
|> Cmd.map ScrollingNavSpecific
|> ScrollingNav.jumpTo
{ section = section
, onChange = always Idle
}
)
ChangedSearch string ->
( { model | search = string},Cmd.none)
Idle ->
( model , Cmd.none)
update : Msg -> Model -> ( Model, Cmd Msg )
@ -377,9 +394,7 @@ update msg model =
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ ScrollingNav.subscriptions
|> Sub.map ScrollingNavSpecific
, Time.every 50 (always (TimePassed 50))
[ Time.every 50 (always (TimePassed 50))
, Events.onResize (\h w -> Resized { height = h, width = w })
]
|> Sub.map LoadedSpecific

View File

@ -6,6 +6,7 @@ module Icons exposing
, circle
, triangle
, square
, search
)
import Html exposing (Html)
@ -75,4 +76,11 @@ square : Html msg
square =
svgFeatherIcon "square"
[ Svg.rect [ Svg.Attributes.x "3", y "3", width "18", height "18", rx "2", ry "2" ] []
]
search : Html msg
search =
svgFeatherIcon "search"
[ Svg.circle [ cx "11", cy "11", r "8" ] []
, Svg.line [ x1 "21", y1 "21", x2 "16.65", y2 "16.65" ] []
]

View File

@ -142,6 +142,15 @@ sortTable model =
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
scrollingNavCard : Element msg
scrollingNavCard =
[ Element.el Heading.h3 <| Element.text "Scrolling Nav"
, Element.text "Resize the screen and open the side-menu. Then start scrolling to see the scrolling navigation in action."
|> List.singleton
|> Element.paragraph []
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
view :
{ addSnackbar : String -> msg
@ -159,5 +168,6 @@ view { addSnackbar, msgMapper, model } =
, Element.wrappedRow (Grid.simple ++ [Element.height <| Element.shrink]) <|
[ snackbar addSnackbar
, sortTable model |> Element.map msgMapper
, scrollingNavCard
]
]

View File

@ -18,7 +18,7 @@ import Html exposing (Html)
import Html.Attributes as Attributes
import Set exposing (Set)
import Widget
import Layout exposing (Direction(..))
import Layout exposing (Part(..))
type alias Model =
@ -242,7 +242,7 @@ tab model =
scrim :
{ showDialog : msg
, changedSheet : Maybe Direction -> msg
, changedSheet : Maybe Part -> msg
} -> Model -> Element msg
scrim {showDialog,changedSheet} model =
[ Element.el Heading.h3 <| Element.text "Scrim"
@ -251,11 +251,11 @@ scrim {showDialog,changedSheet} model =
, label = Element.text <| "Show dialog"
}
, Input.button Button.simple
{ onPress = Just <| changedSheet <| Just Left
{ onPress = Just <| changedSheet <| Just LeftSheet
, label = Element.text <| "show left sheet"
}
, Input.button Button.simple
{ onPress = Just <| changedSheet <| Just Right
{ onPress = Just <| changedSheet <| Just RightSheet
, label = Element.text <| "show right sheet"
}
]
@ -301,7 +301,7 @@ carousel model =
view :
{ msgMapper : Msg -> msg
, showDialog : msg
, changedSheet : Maybe Direction -> msg
, changedSheet : Maybe Part -> msg
} -> Model -> Element msg
view { msgMapper, showDialog, changedSheet } model =
Element.column (Grid.section )

View File

@ -1,4 +1,4 @@
module Core.Style exposing (Style,menuTabButtonSelected,menuTabButton, menuButton, menuButtonSelected, menuIconButton, sheetButton, sheetButtonSelected)
module Core.Style exposing (Style, menuButton, menuButtonSelected, menuIconButton, menuTabButton, menuTabButtonSelected, sheetButton, sheetButtonSelected)
import Element exposing (Attribute, Element)
import Element.Input as Input
@ -19,10 +19,20 @@ type alias Style msg =
, menuIcon : Element Never
, moreVerticalIcon : Element Never
, spacing : Int
, title : List (Attribute msg)
, searchIcon : Element Never
, search : List (Attribute msg)
}
menuButtonSelected : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
type alias ButtonInfo msg =
{ label : String
, icon : Element Never
, onPress : Maybe msg
}
menuButtonSelected : Style msg -> ButtonInfo msg -> Element msg
menuButtonSelected config { label, icon, onPress } =
Input.button (config.menuButton ++ config.menuButtonSelected)
{ onPress = onPress
@ -34,7 +44,7 @@ menuButtonSelected config { label, icon, onPress } =
}
menuButton : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
menuButton : Style msg -> ButtonInfo msg -> Element msg
menuButton config { label, icon, onPress } =
Input.button config.menuButton
{ onPress = onPress
@ -46,7 +56,7 @@ menuButton config { label, icon, onPress } =
}
menuIconButton : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
menuIconButton : Style msg -> ButtonInfo msg -> Element msg
menuIconButton config { label, icon, onPress } =
Input.button config.menuButton
{ onPress = onPress
@ -54,7 +64,7 @@ menuIconButton config { label, icon, onPress } =
}
sheetButton : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
sheetButton : Style msg -> ButtonInfo msg -> Element msg
sheetButton config { label, icon, onPress } =
Input.button config.sheetButton
{ onPress = onPress
@ -66,7 +76,7 @@ sheetButton config { label, icon, onPress } =
}
sheetButtonSelected : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
sheetButtonSelected : Style msg -> ButtonInfo msg -> Element msg
sheetButtonSelected config { label, icon, onPress } =
Input.button (config.sheetButton ++ config.sheetButtonSelected)
{ onPress = onPress
@ -77,7 +87,8 @@ sheetButtonSelected config { label, icon, onPress } =
]
}
menuTabButton : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
menuTabButton : Style msg -> ButtonInfo msg -> Element msg
menuTabButton config { label, icon, onPress } =
Input.button (config.menuButton ++ config.tabButton)
{ onPress = onPress
@ -88,7 +99,8 @@ menuTabButton config { label, icon, onPress } =
]
}
menuTabButtonSelected : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
menuTabButtonSelected : Style msg -> ButtonInfo msg -> Element msg
menuTabButtonSelected config { label, icon, onPress } =
Input.button (config.menuButton ++ config.tabButton ++ config.tabButtonSelected)
{ onPress = onPress
@ -97,4 +109,4 @@ menuTabButtonSelected config { label, icon, onPress } =
[ icon |> Element.map never
, Element.text label
]
}
}

View File

@ -1,5 +1,6 @@
module Layout exposing (Direction(..), Layout, init, queueMessage, setSidebar, timePassed, view)
module Layout exposing (Layout, Part(..), activate, init, queueMessage, timePassed, view)
import Array
import Browser.Dom exposing (Viewport)
import Core.Style as Style exposing (Style)
import Element exposing (Attribute, DeviceClass(..), Element)
@ -12,21 +13,22 @@ import Widget
import Widget.Snackbar as Snackbar
type Direction
= Left
| Right
type Part
= LeftSheet
| RightSheet
| Search
type alias Layout =
{ snackbar : Snackbar.Model String
, sheet : Maybe Direction
, active : Maybe Part
}
init : Layout
init =
{ snackbar = Snackbar.init
, sheet = Nothing
, active = Nothing
}
@ -37,24 +39,27 @@ queueMessage message layout =
}
setSidebar : Maybe Direction -> Layout -> Layout
setSidebar direction layout =
activate : Maybe Part -> Layout -> Layout
activate part layout =
{ layout
| sheet = direction
| active = part
}
timePassed : Int -> Layout -> Layout
timePassed sec layout =
case layout.sheet of
Nothing ->
case layout.active of
Just LeftSheet ->
layout
Just RightSheet ->
layout
_ ->
{ layout
| snackbar = layout.snackbar |> Snackbar.timePassed sec
}
_ ->
layout
view :
List (Attribute msg)
@ -68,12 +73,18 @@ view :
{ selected : Int
, items : List { label : String, icon : Element Never, onPress : Maybe msg }
}
, search :
Maybe
{ onChange : String -> msg
, text : String
, label : String
}
, actions : List { label : String, icon : Element Never, onPress : Maybe msg }
, onChangedSidebar : Maybe Direction -> msg
, onChangedSidebar : Maybe Part -> msg
, style : Style msg
}
-> Html msg
view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, content, style, layout } =
view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, dialog, content, style, layout } =
let
( primaryActions, moreActions ) =
( if (actions |> List.length) > 4 then
@ -107,10 +118,14 @@ view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, c
|| ((menu.items |> List.length) > 5)
then
[ Input.button style.menuButton
{ onPress = Just <| onChangedSidebar <| Just Left
{ onPress = Just <| onChangedSidebar <| Just LeftSheet
, label = style.menuIcon |> Element.map never
}
, title
, menu.items
|> Array.fromList
|> Array.get menu.selected
|> Maybe.map (.label >> Element.text >> Element.el style.title)
|> Maybe.withDefault title
]
else
@ -133,7 +148,40 @@ view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, c
[ Element.width <| Element.shrink
, Element.spacing 8
]
, [ primaryActions
, if deviceClass == Phone then
Element.none
else
search
|> Maybe.map
(\{ onChange, text, label } ->
Input.text style.search
{ onChange = onChange
, text = text
, placeholder =
Just <|
Input.placeholder [] <|
Element.text label
, label = Input.labelHidden label
}
)
|> Maybe.withDefault Element.none
, [ if deviceClass == Phone then
search
|> Maybe.map
(\{ label } ->
[ Style.menuButton style
{ onPress = Just <| onChangedSidebar <| Just Search
, icon = style.searchIcon
, label = label
}
]
)
|> Maybe.withDefault []
else
[]
, primaryActions
|> List.map
(if deviceClass == Phone then
Style.menuIconButton style
@ -146,7 +194,7 @@ view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, c
else
[ Style.menuButton style
{ onPress = Just <| onChangedSidebar <| Just Right
{ onPress = Just <| onChangedSidebar <| Just RightSheet
, icon = style.moreVerticalIcon
, label = ""
}
@ -182,10 +230,13 @@ view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, c
]
)
|> Maybe.withDefault Element.none
sheet =
case layout.sheet of
Just Left ->
menu.items
case layout.active of
Just LeftSheet ->
[ [ title
]
, menu.items
|> List.indexedMap
(\i ->
if i == menu.selected then
@ -194,6 +245,8 @@ view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, c
else
Style.sheetButton style
)
]
|> List.concat
|> Element.column [ Element.width <| Element.fill ]
|> Element.el
(style.sheet
@ -202,7 +255,7 @@ view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, c
]
)
Just Right ->
Just RightSheet ->
moreActions
|> List.map (Style.sheetButton style)
|> Element.column [ Element.width <| Element.fill ]
@ -213,6 +266,26 @@ view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, c
]
)
Just Search ->
case search of
Just { onChange, text, label } ->
Input.text style.search
{ onChange = onChange
, text = text
, placeholder =
Just <|
Input.placeholder [] <|
Element.text label
, label = Input.labelHidden label
}
|> Element.el
[ Element.alignTop
, Element.width <| Element.fill
]
Nothing ->
Element.none
Nothing ->
Element.none
in
@ -223,7 +296,7 @@ view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, c
, [ Element.inFront nav
, Element.inFront snackbar
]
, if (layout.sheet /= Nothing) || (dialog /= Nothing) then
, if (layout.active /= Nothing) || (dialog /= Nothing) then
Widget.scrim
{ onDismiss =
Just <|

View File

@ -1,7 +1,7 @@
module Widget.ScrollingNav exposing
( Model, Msg, init, update, subscriptions, view, viewSections, current
( Model, init, view, viewSections, current
, jumpTo, syncPositions
, jumpToWithOffset
, getPos, jumpToWithOffset, setPos
)
{-| The Scrolling Nav is a navigation bar thats updates while you scroll through
@ -24,8 +24,7 @@ import Element exposing (Attribute, Element)
import Framework.Grid as Grid
import Html.Attributes as Attributes
import IntDict exposing (IntDict)
import Task
import Time
import Task exposing (Task)
{-| -}
@ -37,23 +36,15 @@ type alias Model section =
}
{-| -}
type Msg section
= GotHeaderPos section (Result Dom.Error Int)
| ChangedViewport (Result Dom.Error ())
| SyncPosition Int
| JumpTo section
| TimePassed
{-| The intial state include the labels and the arrangement of the sections
-}
init :
{ labels : section -> String
, arrangement : List section
, toMsg : Result Dom.Error (Model section -> Model section) -> msg
}
-> ( Model section, Cmd (Msg section) )
init { labels, arrangement } =
-> ( Model section, Cmd msg )
init { labels, arrangement, toMsg } =
{ labels = labels
, positions = IntDict.empty
, arrangement = arrangement
@ -62,97 +53,91 @@ init { labels, arrangement } =
|> (\a ->
( a
, syncPositions a
|> Task.attempt toMsg
)
)
{-| -}
update : Msg section -> Model section -> ( Model section, Cmd (Msg section) )
update msg model =
case msg of
GotHeaderPos label result ->
( case result of
Ok pos ->
{ model
| positions =
model.positions
|> IntDict.insert pos
(label |> model.labels)
}
Err _ ->
model
, Cmd.none
)
ChangedViewport _ ->
( model, Cmd.none )
SyncPosition pos ->
( { model
| scrollPos = pos
}
, Cmd.none
)
TimePassed ->
( model
, Dom.getViewport
|> Task.map (.viewport >> .y >> round)
|> Task.perform SyncPosition
)
JumpTo elem ->
( model
, model
|> jumpTo elem
getPos : Task x (Model selection -> Model selection)
getPos =
Dom.getViewport
|> Task.map
(\int model ->
{ model
| scrollPos = int.viewport.y |> round
}
)
{-| -}
subscriptions : Sub (Msg msg)
subscriptions =
Time.every 100 (always TimePassed)
setPos : Int -> Model section -> Model section
setPos pos model =
{ model | scrollPos = pos }
{-| scrolls the screen to the respective section
-}
jumpTo : section -> Model section -> Cmd (Msg msg)
jumpTo section { labels } =
jumpTo :
{ section : section
, onChange : Result Dom.Error () -> msg
}
-> Model section
-> Cmd msg
jumpTo { section, onChange } { labels } =
Dom.getElement (section |> labels)
|> Task.andThen
(\{ element } ->
Dom.setViewport 0 (element.y)
Dom.setViewport 0 element.y
)
|> Task.attempt ChangedViewport
|> Task.attempt onChange
{-| scrolls the screen to the respective section with some offset
-}
jumpToWithOffset : Float -> section -> Model section -> Cmd (Msg msg)
jumpToWithOffset offset section { labels } =
jumpToWithOffset :
{ offset : Float
, section : section
, onChange : Result Dom.Error () -> msg
}
-> Model section
-> Cmd msg
jumpToWithOffset { offset, section, onChange } { labels } =
Dom.getElement (section |> labels)
|> Task.andThen
(\{ element } ->
Dom.setViewport 0 (element.y - offset)
)
|> Task.attempt ChangedViewport
|> Task.attempt onChange
{-| -}
syncPositions : Model section -> Cmd (Msg section)
syncPositions : Model section -> Task Dom.Error (Model section -> Model section)
syncPositions { labels, arrangement } =
arrangement
|> List.map
(\label ->
Dom.getElement (labels label)
|> Task.map
(.element
>> .y
>> round
(\x ->
( x.element.y |> round
, label
)
)
|> Task.attempt
(GotHeaderPos label)
)
|> Cmd.batch
|> Task.sequence
|> Task.map
(\list m ->
list
|> List.foldl
(\( pos, label ) model ->
{ model
| positions =
model.positions
|> IntDict.insert pos
(label |> model.labels)
}
)
m
)
{-| -}
@ -170,7 +155,7 @@ current fromString { positions, scrollPos } =
viewSections :
{ label : String -> Element msg
, fromString : String -> Maybe section
, msgMapper : Msg section -> msg
, onSelect : section -> msg
, attributes : Bool -> List (Attribute msg)
}
-> Model section
@ -181,11 +166,11 @@ viewSections :
, onChange : section -> msg
, attributes : Bool -> List (Attribute msg)
}
viewSections { label, fromString, msgMapper, attributes } ({ arrangement, scrollPos, labels, positions } as model) =
viewSections { label, fromString, onSelect, attributes } ({ arrangement, labels } as model) =
{ selected = model |> current fromString
, options = arrangement
, label = \elem -> label (elem |> labels)
, onChange = JumpTo >> msgMapper
, onChange = onSelect
, attributes = attributes
}