Added Dialog, and more...

This commit is contained in:
Lucas Payr 2020-04-21 06:30:32 +02:00
parent 82b59530fd
commit b73a19d3ce
12 changed files with 878 additions and 611 deletions

View File

@ -18,6 +18,7 @@
"elm/browser": "1.0.2 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0",
"elm/html": "1.0.0 <= v < 2.0.0",
"elm/svg": "1.0.1 <= v < 2.0.0",
"elm/time": "1.0.0 <= v < 2.0.0",
"elm-community/intdict": "3.0.0 <= v < 4.0.0",
"jasonliang512/elm-heroicons": "1.0.2 <= v < 2.0.0",

View File

@ -91,10 +91,9 @@ update msg model =
)
filterSelect : FilterSelect.Model -> Element Msg
filterSelect : FilterSelect.Model -> (String,Element Msg)
filterSelect model =
Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill]) <|
[ Element.el Heading.h3 <| Element.text "Filter Select"
( "Filter Select"
, case model.selected of
Just selected ->
Element.row Grid.compact
@ -128,13 +127,12 @@ filterSelect model =
)
|> Element.wrappedRow [ Element.spacing 10 ]
]
]
)
validatedInput : ValidatedInput.Model () ( String, String ) -> Element Msg
validatedInput : ValidatedInput.Model () ( String, String ) -> (String,Element Msg)
validatedInput model =
Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill]) <|
[ Element.el Heading.h3 <| Element.text "Validated Input"
( "Validated Input"
, ValidatedInput.view Input.simple
model
{ label = "First Name, Sir Name"
@ -152,19 +150,19 @@ validatedInput model =
|> Element.el (Tag.simple ++ Group.right ++ Color.primary)
]
}
]
)
view : Model -> Element Msg
view model =
Element.column (Grid.section ++ [ Element.centerX ])
[ Element.el Heading.h2 <| Element.text "Components"
, "Components have a Model, an Update- and sometimes even a Subscription-function. It takes some time to set them up correctly."
|> Element.text
|> List.singleton
|> Element.paragraph []
, Element.wrappedRow (Grid.simple ++ [Element.height <| Element.shrink]) <|
view : (Msg -> msg) -> Model ->
{ title : String
, description : String
, items : List (String,Element msg)
}
view msgMapper model =
{ title = "Components"
, description = "Components have a Model, an Update- and sometimes even a Subscription-function. It takes some time to set them up correctly."
, items =
[ filterSelect model.filterSelect
, validatedInput model.validatedInput
]
]
|> List.map (Tuple.mapSecond (Element.map msgMapper) )
}

View File

@ -5,7 +5,7 @@ import Browser.Dom as Dom exposing (Viewport)
import Browser.Events as Events
import Browser.Navigation as Navigation
import Component
import Element exposing (DeviceClass(..), Element)
import Element exposing (DeviceClass(..), Element,Attribute)
import Element.Input as Input
import Element.Font as Font
import Element.Border as Border
@ -29,21 +29,27 @@ import Stateless
import Task
import Time
import Widget
import Widget.Button exposing (ButtonStyle)
import Widget.FilterSelect as FilterSelect
import Widget.ScrollingNav as ScrollingNav
import Widget.Snackbar as Snackbar
import Widget.ValidatedInput as ValidatedInput
import Data.Section as Section exposing (Section(..))
import Array
type alias LoadedModel =
{ component : Component.Model
, stateless : Stateless.Model
, reusable : Reusable.Model
, scrollingNav : ScrollingNav.Model Section
, layout : Layout
, layout : Layout LoadedMsg
, displayDialog : Bool
, deviceClass : DeviceClass
, search : String
, window : { height : Int, width : Int }
, search :
{ raw : String
, current : String
, remaining : Int
}
}
@ -58,7 +64,7 @@ type LoadedMsg
| ComponentSpecific Component.Msg
| UpdateScrollingNav (ScrollingNav.Model Section -> ScrollingNav.Model Section)
| TimePassed Int
| AddSnackbar String
| AddSnackbar (String,Bool)
| ToggleDialog Bool
| ChangedSidebar (Maybe Part)
| Resized { width : Int, height : Int }
@ -72,9 +78,64 @@ type Msg
= LoadedSpecific LoadedMsg
| GotViewport Viewport
style : Style msg
textButton : ButtonStyle msg
textButton =
{ container = Button.simple
, label = Grid.simple
, disabled = Color.disabled
, active = Color.primary
}
simpleButton : ButtonStyle msg
simpleButton =
{ container = Button.simple ++ Color.primary
, label = Grid.simple
, disabled = Color.disabled
, active = Color.primary
}
style : Style
{ dialog :
{ containerColumn : List (Attribute msg)
, title : List (Attribute msg)
, buttonRow : List (Attribute msg)
, accept : ButtonStyle msg
, dismiss : ButtonStyle msg
}
} msg
style =
{ snackbar = Card.simple ++ Color.dark
{ dialog =
{ containerColumn =
Card.simple
++ Grid.simple
++ [ Element.width <| Element.minimum 280 <| Element.maximum 560 <| Element.fill ]
, title = Heading.h3
, buttonRow =
Grid.simple ++
[ Element.paddingEach
{ top = 28
, bottom = 0
, left = 0
, right = 0
}
]
, accept = simpleButton
, dismiss = textButton
}
, snackbar =
{ row =
Card.simple
++ Color.dark
++ Grid.simple
++ [ Element.paddingXY 8 6]
, button =
{ label = Grid.simple
, container = Button.simple ++ Color.dark
, disabled = Color.disabled
, active = Color.primary
}
, text = [Element.paddingXY 8 0]
}
, layout = Framework.responsiveLayout
, header =
Framework.container
@ -83,28 +144,42 @@ style =
, Element.height <| Element.px <| 42
]
, menuButton =
Button.simple ++ Group.center ++ Color.dark
, menuButtonSelected =
Color.primary
{ label = Grid.simple
, container = Button.simple ++ Group.center ++ Color.dark
, disabled = Color.disabled
, active = Color.primary
}
, sheetButton =
{ container =
Button.fill
++ Group.center
++ Color.light
++ [Font.alignLeft]
, sheetButtonSelected =
Color.primary
, tabButton =
, label = Grid.simple
, disabled = Color.disabled
, active = Color.primary
}
, menuTabButton =
{ container =
[ Element.height <| Element.px <| 42
, Border.widthEach
{ top = 0,
left = 0,
right = 0,
bottom = 8
bottom = 4
}
, Element.paddingEach
{ top = 12
, left = 8
, right = 8
, bottom = 4
}
, Border.color Color.black
]
, tabButtonSelected =
[ Border.color Color.turquoise
]
, label = Grid.simple
, disabled = Color.disabled
, active = [ Border.color Color.turquoise ]
}
, sheet =
Color.light ++ [ Element.width <| Element.maximum 256 <| Element.fill]
, menuIcon =
@ -116,7 +191,16 @@ style =
, searchIcon =
Icons.search |> Element.html |> Element.el []
, search =
Color.simple ++ [Font.color <| Element.rgb255 0 0 0 ]
Color.simple ++
Card.large ++
[Font.color <| Element.rgb255 0 0 0
, Element.padding 6
, Element.centerY
, Element.alignRight
]
, searchFill =
Color.light
++ Group.center
}
@ -125,7 +209,8 @@ initialModel { viewport } =
let
( scrollingNav, cmd ) =
ScrollingNav.init
{ labels = Section.toString
{ toString = Section.toString
, fromString = Section.fromString
, arrangement = Section.asList
, toMsg = \result ->
case result of
@ -141,13 +226,15 @@ initialModel { viewport } =
, scrollingNav = scrollingNav
, layout = Layout.init
, displayDialog = False
, deviceClass =
, window =
{ width = viewport.width |> round
, height = viewport.height |> round
}
|> Element.classifyDevice
|> .class
, search = ""
, search =
{ raw = ""
, current = ""
, remaining = 0
}
}
, cmd
)
@ -171,25 +258,23 @@ view model =
Layout.view []
{ dialog =
if m.displayDialog then
Just
{ onDismiss = Just <| ToggleDialog False
, content =
[ Element.el Heading.h3 <| Element.text "Dialog"
, "This is a dialog window"
{ body =
"This is a dialog window"
|> Element.text
|> List.singleton
|> Element.paragraph []
, Input.button (Button.simple ++ [ Element.alignRight ])
{ onPress = Just <| ToggleDialog False
, label = Element.text "Ok"
, title = Just "Dialog"
, accept = Just
{ text = "Ok"
, onPress = Just <| ToggleDialog False
}
]
|> Element.column
( Grid.simple
++ Card.large
++ [Element.centerX, Element.centerY]
)
, dismiss = Just
{ text = "Dismiss"
, onPress = Just <| ToggleDialog False
}
}
|> Widget.dialog style.dialog
|> Just
else
Nothing
@ -198,11 +283,12 @@ view model =
, [ m.scrollingNav
|> ScrollingNav.view
(\section ->
case section of
( case section of
ComponentViews ->
m.component
|> Component.view
|> Element.map ComponentSpecific
|> Component.view ComponentSpecific
ReusableViews ->
Reusable.view
@ -218,6 +304,39 @@ view model =
, changedSheet = ChangedSidebar
}
m.stateless
) |> (\{title,description,items} ->
[ Element.el Heading.h2 <| Element.text <| title
, if m.search.current == "" then
description
|> Element.text
|> List.singleton
|> Element.paragraph []
else Element.none
, items
|> (if m.search.current /= "" then
List.filter
( Tuple.first
>> String.toLower
>> String.contains (m.search.current |> String.toLower)
)
else
identity)
|> List.map
(\(name,elem) ->
[ Element.text name
|> Element.el Heading.h3
, elem
]
|> Element.column
(Grid.simple
++ Card.large
++ [Element.height <| Element.fill])
)
|> Element.wrappedRow
(Grid.simple ++ [Element.height <| Element.shrink])
]
|> Element.column (Grid.section ++ [ Element.centerX ])
)
)
]
|> Element.column Framework.container
@ -225,52 +344,35 @@ view model =
|> Element.column Grid.compact
, style = style
, layout = m.layout
, deviceClass = m.deviceClass
, window = m.window
, menu =
{ selected =
Section.asList
|> List.indexedMap (\i s -> (i,s))
|> List.filterMap
( \(i,s) ->
if m.scrollingNav
|> ScrollingNav.current Section.fromString
|> (==) (Just s)
then
Just i
else
Nothing
m.scrollingNav
|> ScrollingNav.toSelect
(\int ->
m.scrollingNav.arrangement
|> Array.fromList
|> Array.get int
|> Maybe.map JumpTo
)
|> List.head
|> Maybe.withDefault 0
, items =
Section.asList
|> List.map
(\label ->
{ icon = Element.none
, label = label |> Section.toString
, onPress = Just <| JumpTo <| label
}
)
}
, actions =
[ { onPress = Just <| Load "https://package.elm-lang.org/packages/Orasund/elm-ui-widgets/latest/"
, label = "Docs"
, text = "Docs"
, icon = Icons.book|> Element.html |> Element.el []
}
, { onPress = Just <| Load "https://github.com/Orasund/elm-ui-widgets"
, label = "Github"
, text = "Github"
, icon = Icons.github|> Element.html |> Element.el []
}
, { onPress = Nothing
, label = "Placeholder"
, text = "Placeholder"
, icon = Icons.circle|> Element.html |> Element.el []
}
, { onPress = Nothing
, label = "Placeholder"
, text = "Placeholder"
, icon = Icons.triangle|> Element.html |> Element.el []
}
, { onPress = Nothing
, label = "Placeholder"
, text = "Placeholder"
, icon = Icons.square|> Element.html |> Element.el []
}
]
@ -281,7 +383,7 @@ view model =
|> Element.el Heading.h1
, search =
Just
{ text = m.search
{ text = m.search.raw
, onChange = ChangedSearch
, label = "Search"
}
@ -330,15 +432,44 @@ updateLoaded msg model =
)
TimePassed int ->
let
search = model.search
in
( { model
| layout = model.layout |> Layout.timePassed int
, search =
if search.remaining > 0 then
if search.remaining <= int then
{ search
| current = search.raw
, remaining = 0
}
else
{ search
| remaining = search.remaining - int
}
else
model.search
}
, ScrollingNav.getPos
|> Task.perform UpdateScrollingNav
)
AddSnackbar string ->
( { model | layout = model.layout |> Layout.queueMessage string }
AddSnackbar (string,bool) ->
( { model
| layout = model.layout
|> Layout.queueMessage
{ text = string
, button = if bool then
Just
{ text = "Add"
, onPress = Just <|
(AddSnackbar ("This is another message", False))
}
else
Nothing
}
}
, Cmd.none
)
@ -347,8 +478,8 @@ updateLoaded msg model =
, Cmd.none
)
Resized screen ->
( { model | deviceClass = screen |> Element.classifyDevice |> .class }
Resized window ->
( { model | window = window }
, Cmd.none
)
@ -370,7 +501,17 @@ updateLoaded msg model =
)
ChangedSearch string ->
( { model | search = string},Cmd.none)
let
search = model.search
in
( { model | search =
{ search
| raw = string
, remaining = 300
}
}
, Cmd.none
)
Idle ->
( model , Cmd.none)

View File

@ -7,6 +7,7 @@ module Icons exposing
, triangle
, square
, search
, slash
)
import Html exposing (Html)
@ -66,6 +67,13 @@ circle =
[ Svg.circle [ cx "12", cy "12", r "10" ] []
]
slash : Html msg
slash =
svgFeatherIcon "slash"
[ Svg.circle [ cx "12", cy "12", r "10" ] []
, Svg.line [ x1 "4.93", y1 "4.93", x2 "19.07", y2 "19.07" ] []
]
triangle : Html msg
triangle =
svgFeatherIcon "triangle"

View File

@ -54,24 +54,37 @@ init =
SortTable.sortBy { title = "Name", asc = True }
snackbar : (String -> msg) -> Element msg
snackbar : ((String,Bool) -> msg) -> (String,Element msg)
snackbar addSnackbar =
[ Element.el Heading.h3 <| Element.text "Snackbar"
, Input.button Button.simple
{ onPress = Just <| addSnackbar "This is a notification. It will disappear after 10 seconds."
( "Snackbar"
, [Input.button Button.simple
{ onPress = Just <| addSnackbar <|
("This is a notification. It will disappear after 10 seconds."
, False
)
, label =
"Add Notification"
|> Element.text
|> List.singleton
|> Element.paragraph []
}
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
, Input.button Button.simple
{ onPress = Just <| addSnackbar <|
("You can add another notification if you want."
, True
)
, label =
"Add Notification with Action"
|> Element.text
|> List.singleton
|> Element.paragraph []
}
] |> Element.column Grid.simple
)
sortTable : SortTable.Model -> Element Msg
sortTable : SortTable.Model -> (String,Element Msg)
sortTable model =
[ Element.el Heading.h3 <| Element.text "Sort Table"
( "Sort Table"
, SortTable.view
{ content =
[ { id = 1, name = "Antonio", rating = 2.456 }
@ -139,35 +152,31 @@ sortTable model =
}
)
|> Element.table Grid.simple
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
)
scrollingNavCard : Element msg
scrollingNavCard : (String , Element msg )
scrollingNavCard =
[ Element.el Heading.h3 <| Element.text "Scrolling Nav"
("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
{ addSnackbar : (String,Bool) -> msg
, msgMapper : Msg -> msg
, model : Model
}
-> Element msg
-> { title : String
, description : String
, items : List (String,Element msg)
}
view { addSnackbar, msgMapper, model } =
Element.column (Grid.section ++ [ Element.centerX ])
[ Element.el Heading.h2 <| Element.text "Reusable Views"
, "Reusable views have an internal state but no update function. You will need to do some wiring, but nothing complicated."
|> Element.text
|> List.singleton
|> Element.paragraph []
, Element.wrappedRow (Grid.simple ++ [Element.height <| Element.shrink]) <|
{ title = "Reusable Views"
, description = "Reusable views have an internal state but no update function. You will need to do some wiring, but nothing complicated."
, items =
[ snackbar addSnackbar
, sortTable model |> Element.map msgMapper
, sortTable model |> Tuple.mapSecond (Element.map msgMapper)
, scrollingNavCard
]
]
}

View File

@ -17,16 +17,34 @@ import Heroicons.Solid as Heroicons
import Html exposing (Html)
import Html.Attributes as Attributes
import Set exposing (Set)
import Widget
import Widget.Button as Button exposing (ButtonStyle)
import Layout exposing (Part(..))
import Icons
import Widget
buttonStyle : ButtonStyle msg
buttonStyle =
{ label = [ Element.spacing 8]
, container = Button.simple
, disabled = Color.disabled
, active = Color.primary
}
tabButtonStyle :ButtonStyle msg
tabButtonStyle=
{ label = [ Element.spacing 8]
, container = Button.simple ++ Group.top
, disabled = Color.disabled
, active = Color.primary
}
type alias Model =
{ selected : Maybe Int
, multiSelected : Set Int
, isCollapsed : Bool
, carousel : Int
, tab : Int
, tab : Maybe Int
, button : Bool
}
@ -36,6 +54,7 @@ type Msg
| ToggleCollapsable Bool
| ChangedTab Int
| SetCarousel Int
| ToggleButton Bool
init : Model
@ -44,7 +63,8 @@ init =
, multiSelected = Set.empty
, isCollapsed = False
, carousel = 0
, tab = 1
, tab = Just 1
, button = True
}
@ -91,35 +111,31 @@ update msg model =
)
ChangedTab int ->
( { model | tab = int }, Cmd.none )
( { model | tab = Just int }, Cmd.none )
ToggleButton bool ->
( { model | button = bool }, Cmd.none )
select : Model -> Element Msg
select : Model -> (String,Element Msg)
select model =
[ Element.el Heading.h3 <| Element.text "Select"
, Widget.select
{ selected = model.selected
, options = [ 1, 2, 42 ]
, label = String.fromInt >> Element.text
, onChange = ChangedSelected
, attributes =
\selected ->
Button.simple
++ [ Border.width 0
, Border.rounded 0
]
++ (if selected then
Color.primary
else
[]
)
( "Select"
, { selected = model.selected
, options =
[ 1, 2, 42 ]
|> List.map (\int ->
{ text = String.fromInt int
, icon = Element.none
}
)
, onSelect = ChangedSelected >> Just
}
|> Widget.select
|> List.indexedMap
(\i ->
Element.el
(Button.simple
++ [ Element.padding <| 0 ]
Widget.selectButton
{ buttonStyle
| container = buttonStyle.container
++ (if i == 0 then
Group.left
@ -129,39 +145,31 @@ select model =
else
Group.center
)
}
)
)
|> Element.row Grid.compact
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
)
multiSelect : Model -> Element Msg
multiSelect : Model -> (String,Element Msg)
multiSelect model =
[ Element.el Heading.h3 <| Element.text "Multi Select"
, Widget.multiSelect
{ selected = model.multiSelected
, options = [ 1, 2, 42 ]
, label = String.fromInt >> Element.text
, onChange = ChangedMultiSelected
, attributes =
\selected ->
Button.simple
++ [ Border.width 0
, Border.rounded 0
]
++ (if selected then
Color.primary
else
[]
)
( "Multi Select"
, { selected = model.multiSelected
, options =
[ 1, 2, 42 ]
|> List.map (\int ->
{ text = String.fromInt int
, icon = Element.none
})
, onSelect = ChangedMultiSelected >> Just
}
|> Widget.multiSelect
|> List.indexedMap
(\i ->
Element.el
(Button.simple
++ [ Element.padding <| 0 ]
Widget.selectButton
{ buttonStyle
| container = buttonStyle.container
++ (if i == 0 then
Group.left
@ -171,16 +179,14 @@ multiSelect model =
else
Group.center
)
)
}
)
|> Element.row Grid.compact
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
)
collapsable : Model -> Element Msg
collapsable : Model -> (String,Element Msg)
collapsable model =
[ Element.el Heading.h3 <| Element.text "Collapsable"
( "Collapsable"
, Widget.collapsable
{ onToggle = ToggleCollapsable
, isCollapsed = model.isCollapsed
@ -196,28 +202,33 @@ collapsable model =
]
, content = Element.text <| "Hello World"
}
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
)
tab : Model -> Element Msg
tab : Model -> (String,Element Msg)
tab model =
[ Element.el Heading.h3 <| Element.text "Tab"
, Widget.tab Grid.simple
( "Tab"
, Widget.tab
{ tabButton = tabButtonStyle
, tabRow = Grid.simple
}
{ selected = model.tab
, options = [ 1, 2, 3 ]
, onChange = ChangedTab
, label = \int -> "Tab " ++ String.fromInt int |> Element.text
, content =
\selected ->
|> List.map (\int ->
{ text = "Tab " ++ (int |> String.fromInt)
, icon = Element.none
}
)
, onSelect = ChangedTab >> Just
} <|
(\selected ->
(case selected of
1 ->
Just 0 ->
"This is Tab 1"
2 ->
Just 1 ->
"This is the second tab"
3 ->
Just 2 ->
"The thrid and last tab"
_ ->
@ -225,32 +236,13 @@ tab model =
)
|> Element.text
|> Element.el (Card.small ++ Group.bottom)
, attributes =
\selected ->
Button.simple
++ Group.top
++ (if selected then
Color.primary
else
[]
)
}
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
)
scrim :
{ showDialog : msg
, changedSheet : Maybe Part -> msg
} -> Model -> Element msg
scrim {showDialog,changedSheet} model =
[ Element.el Heading.h3 <| Element.text "Scrim"
, Input.button Button.simple
{ onPress = Just showDialog
, label = Element.text <| "Show dialog"
}
, Input.button Button.simple
modal : (Maybe Part -> msg) -> Model -> (String,Element msg)
modal changedSheet model =
( "Modal"
, [ Input.button Button.simple
{ onPress = Just <| changedSheet <| Just LeftSheet
, label = Element.text <| "show left sheet"
}
@ -258,13 +250,21 @@ scrim {showDialog,changedSheet} model =
{ onPress = Just <| changedSheet <| Just RightSheet
, label = Element.text <| "show right sheet"
}
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
] |> Element.column Grid.simple
)
dialog : msg -> Model -> (String,Element msg)
dialog showDialog model =
( "Dialog"
, Input.button Button.simple
{ onPress = Just showDialog
, label = Element.text <| "Show dialog"
}
)
carousel : Model -> Element Msg
carousel : Model -> (String,Element Msg)
carousel model =
[ Element.el Heading.h3 <| Element.text "Carousel"
( "Carousel"
, Widget.carousel
{ content = ( Color.cyan, [ Color.yellow, Color.green, Color.red ] |> Array.fromList )
, current = model.carousel
@ -294,33 +294,47 @@ carousel model =
]
|> Element.row (Grid.simple ++ [ Element.centerX, Element.width <| Element.shrink ])
}
]
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
)
iconButton : Model -> (String,Element Msg)
iconButton model =
( "Icon Button"
, [Button.view buttonStyle
{ text = "disable me"
, icon = Icons.slash |> Element.html |> Element.el [] , onPress =
if model.button then
Just <| ToggleButton False
else
Nothing
}
, Button.view buttonStyle
{ text = "reset button"
, icon = Element.none
, onPress = Just <| ToggleButton True
}
] |> Element.column Grid.simple
)
view :
{ msgMapper : Msg -> msg
, showDialog : msg
, changedSheet : Maybe Part -> msg
} -> Model -> Element msg
} -> Model
-> { title : String
, description : String
, items : List (String,Element msg)
}
view { msgMapper, showDialog, changedSheet } model =
Element.column (Grid.section )
[ Element.el Heading.h2 <| Element.text "Stateless Views"
, "Stateless views are simple functions that view some content. No wiring required."
|> Element.text
|> List.singleton
|> Element.paragraph []
, Element.wrappedRow
(Grid.simple ++ [Element.height <| Element.shrink])
<|
[ select model |> Element.map msgMapper
, multiSelect model |> Element.map msgMapper
, collapsable model |> Element.map msgMapper
, scrim
{ showDialog = showDialog
, changedSheet = changedSheet
} model
, carousel model |> Element.map msgMapper
, tab model |> Element.map msgMapper
]
{ title = "Stateless Views"
, description = "Stateless views are simple functions that view some content. No wiring required."
, items =
[ iconButton model |> Tuple.mapSecond (Element.map msgMapper)
, select model |> Tuple.mapSecond (Element.map msgMapper)
, multiSelect model |> Tuple.mapSecond (Element.map msgMapper)
, collapsable model |> Tuple.mapSecond (Element.map msgMapper)
, modal changedSheet model
, carousel model |> Tuple.mapSecond (Element.map msgMapper)
, tab model |> Tuple.mapSecond (Element.map msgMapper)
, dialog showDialog model
]
}

View File

@ -1,112 +1,49 @@
module Core.Style exposing (Style, menuButton, menuButtonSelected, menuIconButton, menuTabButton, menuTabButtonSelected, sheetButton, sheetButtonSelected)
module Core.Style exposing (Style, menuButton, menuIconButton, menuTabButton, sheetButton)
import Element exposing (Attribute, Element)
import Element.Input as Input
import Html exposing (Html)
import Widget
import Widget.Button as Button exposing (Button, ButtonStyle)
type alias Style msg =
{ snackbar : List (Attribute msg)
type alias Style style msg =
{ style
| snackbar :
{ row : List (Attribute msg)
, text : List (Attribute msg)
, button : ButtonStyle msg
}
, layout : List (Attribute msg) -> Element msg -> Html msg
, header : List (Attribute msg)
, sheet : List (Attribute msg)
, menuButton : List (Attribute msg)
, menuButtonSelected : List (Attribute msg)
, sheetButton : List (Attribute msg)
, sheetButtonSelected : List (Attribute msg)
, tabButton : List (Attribute msg)
, tabButtonSelected : List (Attribute msg)
, sheetButton : ButtonStyle msg
, menuButton : ButtonStyle msg
, menuTabButton : ButtonStyle msg
, menuIcon : Element Never
, moreVerticalIcon : Element Never
, spacing : Int
, title : List (Attribute msg)
, searchIcon : Element Never
, search : List (Attribute msg)
, searchFill : List (Attribute msg)
}
type alias ButtonInfo msg =
{ label : String
, icon : Element Never
, onPress : Maybe msg
}
menuButton : Style style msg -> ( Bool, Button msg ) -> Element msg
menuButton style =
Widget.selectButton style.menuButton
menuButtonSelected : Style msg -> ButtonInfo msg -> Element msg
menuButtonSelected config { label, icon, onPress } =
Input.button (config.menuButton ++ config.menuButtonSelected)
{ onPress = onPress
, label =
Element.row [ Element.spacing 8 ]
[ icon |> Element.map never
, Element.text label
]
}
menuIconButton : Style style msg -> Button msg -> Element msg
menuIconButton style =
Button.viewIconOnly style.menuButton
menuButton : Style msg -> ButtonInfo msg -> Element msg
menuButton config { label, icon, onPress } =
Input.button config.menuButton
{ onPress = onPress
, label =
Element.row [ Element.spacing 8 ]
[ icon |> Element.map never
, Element.text label
]
}
sheetButton : Style style msg -> ( Bool, Button msg ) -> Element msg
sheetButton style =
Widget.selectButton style.sheetButton
menuIconButton : Style msg -> ButtonInfo msg -> Element msg
menuIconButton config { label, icon, onPress } =
Input.button config.menuButton
{ onPress = onPress
, label = icon |> Element.map never
}
sheetButton : Style msg -> ButtonInfo msg -> Element msg
sheetButton config { label, icon, onPress } =
Input.button config.sheetButton
{ onPress = onPress
, label =
Element.row [ Element.spacing 8 ]
[ icon |> Element.map never
, Element.text label
]
}
sheetButtonSelected : Style msg -> ButtonInfo msg -> Element msg
sheetButtonSelected config { label, icon, onPress } =
Input.button (config.sheetButton ++ config.sheetButtonSelected)
{ onPress = onPress
, label =
Element.row [ Element.spacing 8 ]
[ icon |> Element.map never
, Element.text label
]
}
menuTabButton : Style msg -> ButtonInfo msg -> Element msg
menuTabButton config { label, icon, onPress } =
Input.button (config.menuButton ++ config.tabButton)
{ onPress = onPress
, label =
Element.row [ Element.spacing 8 ]
[ icon |> Element.map never
, Element.text label
]
}
menuTabButtonSelected : Style msg -> ButtonInfo msg -> Element msg
menuTabButtonSelected config { label, icon, onPress } =
Input.button (config.menuButton ++ config.tabButton ++ config.tabButtonSelected)
{ onPress = onPress
, label =
Element.row [ Element.spacing 8 ]
[ icon |> Element.map never
, Element.text label
]
}
menuTabButton : Style style msg -> ( Bool, Button msg ) -> Element msg
menuTabButton style =
Widget.selectButton style.menuTabButton

View File

@ -1,16 +1,13 @@
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)
import Element.Background as Background
import Element.Events as Events
import Element.Input as Input
import Html exposing (Html)
import Html.Attributes as Attributes
import Widget
import Widget.Snackbar as Snackbar
import Widget exposing (Select)
import Widget.Button as Button exposing (Button)
import Widget.Snackbar as Snackbar exposing (Message)
type Part
@ -19,34 +16,34 @@ type Part
| Search
type alias Layout =
{ snackbar : Snackbar.Model String
type alias Layout msg =
{ snackbar : Snackbar.Model (Message msg)
, active : Maybe Part
}
init : Layout
init : Layout msg
init =
{ snackbar = Snackbar.init
, active = Nothing
}
queueMessage : String -> Layout -> Layout
queueMessage : Message msg -> Layout msg -> Layout msg
queueMessage message layout =
{ layout
| snackbar = layout.snackbar |> Snackbar.insert message
}
activate : Maybe Part -> Layout -> Layout
activate : Maybe Part -> Layout msg -> Layout msg
activate part layout =
{ layout
| active = part
}
timePassed : Int -> Layout -> Layout
timePassed : Int -> Layout msg -> Layout msg
timePassed sec layout =
case layout.active of
Just LeftSheet ->
@ -64,28 +61,31 @@ timePassed sec layout =
view :
List (Attribute msg)
->
{ deviceClass : DeviceClass
{ window : { height : Int, width : Int }
, dialog : Maybe { onDismiss : Maybe msg, content : Element msg }
, content : Element msg
, layout : Layout
, layout : Layout msg
, title : Element msg
, menu :
{ selected : Int
, items : List { label : String, icon : Element Never, onPress : Maybe msg }
}
, menu : Select msg
, search :
Maybe
{ onChange : String -> msg
, text : String
, label : String
}
, actions : List { label : String, icon : Element Never, onPress : Maybe msg }
, actions : List (Button msg)
, onChangedSidebar : Maybe Part -> msg
, style : Style msg
, style : Style style msg
}
-> Html msg
view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, dialog, content, style, layout } =
view attributes { search, title, onChangedSidebar, menu, actions, window, dialog, content, style, layout } =
let
deviceClass : DeviceClass
deviceClass =
window
|> Element.classifyDevice
|> .class
( primaryActions, moreActions ) =
( if (actions |> List.length) > 4 then
actions |> List.take 2
@ -115,30 +115,29 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
[ (if
(deviceClass == Phone)
|| (deviceClass == Tablet)
|| ((menu.items |> List.length) > 5)
|| ((menu.options |> List.length) > 5)
then
[ Input.button style.menuButton
[ Button.viewIconOnly style.menuButton
{ onPress = Just <| onChangedSidebar <| Just LeftSheet
, label = style.menuIcon |> Element.map never
, icon = style.menuIcon |> Element.map never
, text = "Menu"
}
, menu.items
, menu.selected
|> Maybe.andThen
(\option ->
menu.options
|> Array.fromList
|> Array.get menu.selected
|> Maybe.map (.label >> Element.text >> Element.el style.title)
|> Array.get option
)
|> Maybe.map (.text >> Element.text >> Element.el style.title)
|> Maybe.withDefault title
]
else
[ title
, menu.items
|> List.indexedMap
(\i ->
if i == menu.selected then
Style.menuTabButtonSelected style
else
Style.menuTabButton style
)
, menu
|> Widget.select
|> List.map (Style.menuTabButton style)
|> Element.row
[ Element.width <| Element.shrink
]
@ -146,9 +145,9 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
)
|> Element.row
[ Element.width <| Element.shrink
, Element.spacing 8
, Element.spacing style.spacing
]
, if deviceClass == Phone then
, if deviceClass == Phone || deviceClass == Tablet then
Element.none
else
@ -166,37 +165,45 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
}
)
|> Maybe.withDefault Element.none
, [ if deviceClass == Phone then
search
, [ search
|> Maybe.map
(\{ label } ->
[ Style.menuButton style
if deviceClass == Tablet then
[ Button.view style.menuButton
{ onPress = Just <| onChangedSidebar <| Just Search
, icon = style.searchIcon
, label = label
, text = label
}
]
else if deviceClass == Phone then
[ Style.menuIconButton style
{ onPress = Just <| onChangedSidebar <| Just Search
, icon = style.searchIcon
, text = label
}
]
)
|> Maybe.withDefault []
else
[]
)
|> Maybe.withDefault []
, primaryActions
|> List.map
(if deviceClass == Phone then
Style.menuIconButton style
else
Style.menuButton style
Button.view style.menuButton
)
, if moreActions |> List.isEmpty then
[]
else
[ Style.menuButton style
[ Button.viewIconOnly style.menuButton
{ onPress = Just <| onChangedSidebar <| Just RightSheet
, icon = style.moreVerticalIcon
, label = ""
, text = "More"
}
]
]
@ -210,7 +217,7 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
(style.header
++ [ Element.padding 0
, Element.centerX
, Element.spaceEvenly
, Element.spacing style.spacing
, Element.alignTop
, Element.width <| Element.fill
]
@ -218,13 +225,10 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
snackbar =
layout.snackbar
|> Snackbar.current
|> Snackbar.view style.snackbar identity
|> Maybe.map
(Element.text
>> List.singleton
>> Element.paragraph style.snackbar
>> Element.el
[ Element.padding 8
(Element.el
[ Element.padding style.spacing
, Element.alignBottom
, Element.alignRight
]
@ -236,15 +240,9 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
Just LeftSheet ->
[ [ title
]
, menu.items
|> List.indexedMap
(\i ->
if i == menu.selected then
Style.sheetButtonSelected style
else
Style.sheetButton style
)
, menu
|> Widget.select
|> List.map (Style.sheetButton style)
]
|> List.concat
|> Element.column [ Element.width <| Element.fill ]
@ -257,7 +255,7 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
Just RightSheet ->
moreActions
|> List.map (Style.sheetButton style)
|> List.map (Button.view style.sheetButton)
|> Element.column [ Element.width <| Element.fill ]
|> Element.el
(style.sheet
@ -269,7 +267,11 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
Just Search ->
case search of
Just { onChange, text, label } ->
Input.text style.search
Input.text
(style.searchFill
++ [ Element.width <| Element.fill
]
)
{ onChange = onChange
, text = text
, placeholder =
@ -297,33 +299,22 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
, Element.inFront snackbar
]
, if (layout.active /= Nothing) || (dialog /= Nothing) then
Widget.scrim
{ onDismiss =
Just <|
case dialog of
Just { onDismiss } ->
onDismiss
|> Maybe.withDefault
(Nothing
|> onChangedSidebar
)
(Element.height <| Element.px <| window.height)
:: (case dialog of
Just dialogConfig ->
Widget.modal dialogConfig
Nothing ->
Widget.modal
{ onDismiss =
Nothing
|> onChangedSidebar
, content = Element.none
|> Just
, content = sheet
}
)
else
[]
, [ Element.inFront sheet
, Element.inFront <|
case dialog of
Just element ->
element.content
Nothing ->
Element.none
]
]
)

View File

@ -1,11 +1,11 @@
module Widget exposing
( select, multiSelect, collapsable, carousel, scrim, tab
, dialog
( select, multiSelect, collapsable, carousel, modal, tab, dialog
, Dialog, MultiSelect, Select, selectButton
)
{-| This module contains functions for displaying data.
@docs select, multiSelect, collapsable, carousel, scrim, tab
@docs select, multiSelect, collapsable, carousel, modal, tab, dialog
# DEPRECATED
@ -20,67 +20,97 @@ import Element.Background as Background
import Element.Events as Events
import Element.Input as Input
import Set exposing (Set)
import Widget.Button as Button exposing (Button, ButtonStyle, TextButton)
type alias Select msg =
{ selected : Maybe Int
, options :
List
{ text : String
, icon : Element Never
}
, onSelect : Int -> Maybe msg
}
type alias MultiSelect msg =
{ selected : Set Int
, options :
List
{ text : String
, icon : Element Never
}
, onSelect : Int -> Maybe msg
}
type alias Dialog msg =
{ title : Maybe String
, body : Element msg
, accept : Maybe (TextButton msg)
, dismiss : Maybe (TextButton msg)
}
{-| A simple button
-}
selectButton :
ButtonStyle msg
-> ( Bool, Button msg )
-> Element msg
selectButton style ( selected, b ) =
b
|> Button.view
{ style
| container =
style.container
++ (if selected then
style.active
else
[]
)
}
{-| Selects one out of multiple options. This can be used for radio buttons or Menus.
-}
select :
{ selected : Maybe a
, options : List a
, label : a -> Element msg
, onChange : a -> msg
, attributes : Bool -> List (Attribute msg)
}
-> List (Element msg)
select { selected, options, label, onChange, attributes } =
Select msg
-> List ( Bool, Button msg )
select { selected, options, onSelect } =
options
|> List.map
(\a ->
Input.button (attributes (selected == Just a))
{ onPress = a |> onChange |> Just
, label = label a
|> List.indexedMap
(\i a ->
( selected == Just i
, { onPress = i |> onSelect
, text = a.text
, icon = a.icon
}
)
)
{-| Selects multible options. This can be used for checkboxes.
-}
multiSelect :
{ selected : Set comparable
, options : List comparable
, label : comparable -> Element msg
, onChange : comparable -> msg
, attributes : Bool -> List (Attribute msg)
}
-> List (Element msg)
multiSelect { selected, options, label, onChange, attributes } =
MultiSelect msg
-> List ( Bool, Button msg )
multiSelect { selected, options, onSelect } =
options
|> List.map
(\a ->
Input.button (attributes (selected |> Set.member a))
{ onPress = a |> onChange |> Just
, label =
label a
|> List.indexedMap
(\i a ->
( selected |> Set.member i
, { onPress = i |> onSelect
, text = a.text
, icon = a.icon
}
)
)
{-| Some collapsable content.
Widget.collapsable
{onToggle = ToggleCollapsable
,isCollapsed = model.isCollapsed
,label = Element.row Grid.compact
[ Element.html <|
if model.isCollapsed then
Heroicons.cheveronRight [ Attributes.width 20]
else
Heroicons.cheveronDown [ Attributes.width 20]
, Element.el Heading.h4 <|Element.text <| "Title"
]
,content = Element.text <| "Hello World"
}
-}
collapsable :
{ onToggle : Bool -> msg
@ -107,81 +137,92 @@ collapsable { onToggle, isCollapsed, label, content } =
{-| Displayes a list of contents in a tab
-}
tab :
List (Attribute msg)
->
{ selected : a
, options : List a
, onChange : a -> msg
, label : a -> Element msg
, content : a -> Element msg
, attributes : Bool -> List (Attribute msg)
{ style
| tabButton : ButtonStyle msg
, tabRow : List (Attribute msg)
}
-> Select msg
-> (Maybe Int -> Element msg)
-> Element msg
tab atts { selected, options, onChange, label, content, attributes } =
[ select
{ selected = Just selected
, options = options
, label = label
, onChange = onChange
, attributes = attributes
}
|> Element.row atts
, content selected
tab style options content =
[ options
|> select
|> List.map (selectButton style.tabButton)
|> Element.row style.tabRow
, options.selected
|> content
]
|> Element.column []
{-| DEPRECATED. Use scrim instead.
-}
dialog :
{ onDismiss : Maybe msg
, content : Element msg
{ containerColumn : List (Attribute msg)
, title : List (Attribute msg)
, buttonRow : List (Attribute msg)
, accept : ButtonStyle msg
, dismiss : ButtonStyle msg
}
-> Element msg
dialog { onDismiss, content } =
content
|> Element.el
[ Element.centerX
, Element.centerY
]
|> Element.el
([ Element.width <| Element.fill
, Element.height <| Element.fill
, Background.color <| Element.rgba255 0 0 0 0.5
]
++ (onDismiss
|> Maybe.map (Events.onClick >> List.singleton)
|> Maybe.withDefault []
)
)
-> Dialog msg
-> { onDismiss : Maybe msg, content : Element msg }
dialog style { title, body, accept, dismiss } =
{ onDismiss =
case ( accept, dismiss ) of
( Nothing, Nothing ) ->
Nothing
( Nothing, Just { onPress } ) ->
onPress
{-| A scrim to block the interaction with the site. Usefull for modals and side panels
If the scrim is clicked a message may be send. Also one can place an element infront.
Framework.Layout
[ Wiget.scrim
{ onDismiss = Just <| ToggleDialog False
( Just _, _ ) ->
Nothing
, content =
[ "This is a dialog window"
|> Element.text
, Input.button []
{onPress = Just <| ToggleDialog False
, label = Element.text "Ok"
}
]
|> Element.column
[ Element.centerX
Element.column
(style.containerColumn
++ [ Element.centerX
, Element.centerY
]
}
)
[ title
|> Maybe.map
(Element.text
>> Element.el style.title
)
|> Maybe.withDefault Element.none
, body
, Element.row
(style.buttonRow
++ [ Element.alignRight
, Element.width <| Element.shrink
]
)
(case ( accept, dismiss ) of
( Just acceptButton, Nothing ) ->
acceptButton
|> Button.viewTextOnly style.accept
|> List.singleton
( Just acceptButton, Just dismissButton ) ->
[ dismissButton
|> Button.viewTextOnly style.dismiss
, acceptButton
|> Button.viewTextOnly style.accept
]
_ ->
[]
)
]
}
{-| A modal.
NOTE: to stop the screen from scrolling, just set the height of the layout to the height of the screen.
-}
scrim : { onDismiss : Maybe msg, content : Element msg } -> List (Attribute msg)
scrim { onDismiss, content } =
Element.el
modal : { onDismiss : Maybe msg, content : Element msg } -> List (Attribute msg)
modal { onDismiss, content } =
[ Element.el
([ Element.width <| Element.fill
, Element.height <| Element.fill
, Background.color <| Element.rgba255 0 0 0 0.5
@ -193,7 +234,8 @@ scrim { onDismiss, content } =
)
content
|> Element.inFront
|> List.singleton
, Element.clip
]
{-| A Carousel circles through a non empty list of contents.

82
src/Widget/Button.elm Normal file
View File

@ -0,0 +1,82 @@
module Widget.Button exposing
( Button
, ButtonStyle
, TextButton
, view
, viewIconOnly
, viewTextOnly
)
import Element exposing (Attribute, Element)
import Element.Input as Input
import Element.Region as Region
type alias Button msg =
{ text : String
, icon : Element Never
, onPress : Maybe msg
}
type alias TextButton msg =
{ text : String
, onPress : Maybe msg
}
type alias ButtonStyle msg =
{ container : List (Attribute msg)
, disabled : List (Attribute msg)
, label : List (Attribute msg)
, active : List (Attribute msg)
}
viewIconOnly : ButtonStyle msg -> Button msg -> Element msg
viewIconOnly style { onPress, text, icon } =
Input.button
(style.container
++ (if onPress == Nothing then
style.disabled
else
[]
)
++ [ Region.description text ]
)
{ onPress = onPress
, label =
icon |> Element.map never
}
viewTextOnly : ButtonStyle msg -> TextButton msg -> Element msg
viewTextOnly style { onPress, text } =
view style
{ onPress = onPress
, text = text
, icon = Element.none
}
{-| The first argument can be used to define the spacing between the icon and the text
-}
view : ButtonStyle msg -> Button msg -> Element msg
view style { onPress, text, icon } =
Input.button
(style.container
++ (if onPress == Nothing then
style.disabled
else
[]
)
)
{ onPress = onPress
, label =
Element.row style.label
[ icon |> Element.map never
, Element.text text
]
}

View File

@ -1,7 +1,7 @@
module Widget.ScrollingNav exposing
( Model, init, view, viewSections, current
( Model, init, view, current
, jumpTo, syncPositions
, getPos, jumpToWithOffset, setPos
, getPos, jumpToWithOffset, setPos, toSelect
)
{-| The Scrolling Nav is a navigation bar thats updates while you scroll through
@ -20,16 +20,18 @@ the page. Clicking on a navigation button will scroll directly to that section.
-}
import Browser.Dom as Dom
import Element exposing (Attribute, Element)
import Element exposing (Element)
import Framework.Grid as Grid
import Html.Attributes as Attributes
import IntDict exposing (IntDict)
import Task exposing (Task)
import Widget exposing (Select)
{-| -}
type alias Model section =
{ labels : section -> String
{ toString : section -> String
, fromString : String -> Maybe section
, positions : IntDict String
, arrangement : List section
, scrollPos : Int
@ -39,13 +41,15 @@ type alias Model section =
{-| The intial state include the labels and the arrangement of the sections
-}
init :
{ labels : section -> String
{ toString : section -> String
, fromString : String -> Maybe section
, arrangement : List section
, toMsg : Result Dom.Error (Model section -> Model section) -> msg
}
-> ( Model section, Cmd msg )
init { labels, arrangement, toMsg } =
{ labels = labels
init { toString, fromString, arrangement, toMsg } =
{ toString = toString
, fromString = fromString
, positions = IntDict.empty
, arrangement = arrangement
, scrollPos = 0
@ -82,8 +86,8 @@ jumpTo :
}
-> Model section
-> Cmd msg
jumpTo { section, onChange } { labels } =
Dom.getElement (section |> labels)
jumpTo { section, onChange } { toString } =
Dom.getElement (section |> toString)
|> Task.andThen
(\{ element } ->
Dom.setViewport 0 element.y
@ -100,8 +104,8 @@ jumpToWithOffset :
}
-> Model section
-> Cmd msg
jumpToWithOffset { offset, section, onChange } { labels } =
Dom.getElement (section |> labels)
jumpToWithOffset { offset, section, onChange } { toString } =
Dom.getElement (section |> toString)
|> Task.andThen
(\{ element } ->
Dom.setViewport 0 (element.y - offset)
@ -111,11 +115,11 @@ jumpToWithOffset { offset, section, onChange } { labels } =
{-| -}
syncPositions : Model section -> Task Dom.Error (Model section -> Model section)
syncPositions { labels, arrangement } =
syncPositions { toString, arrangement } =
arrangement
|> List.map
(\label ->
Dom.getElement (labels label)
Dom.getElement (toString label)
|> Task.map
(\x ->
( x.element.y |> round
@ -133,7 +137,7 @@ syncPositions { labels, arrangement } =
| positions =
model.positions
|> IntDict.insert pos
(label |> model.labels)
(label |> model.toString)
}
)
m
@ -151,27 +155,29 @@ current fromString { positions, scrollPos } =
|> Maybe.andThen fromString
{-| -}
viewSections :
{ label : String -> Element msg
, fromString : String -> Maybe section
, onSelect : section -> msg
, attributes : Bool -> List (Attribute msg)
toSelect : (Int -> Maybe msg) -> Model section -> Select msg
toSelect onSelect ({ arrangement, toString, fromString } as model) =
{ selected =
arrangement
|> List.indexedMap (\i s -> ( i, s ))
|> List.filterMap
(\( i, s ) ->
if Just s == current fromString model then
Just i
else
Nothing
)
|> List.head
, options =
arrangement
|> List.map
(\s ->
{ text = toString s
, icon = Element.none
}
-> Model section
->
{ selected : Maybe section
, options : List section
, label : section -> Element msg
, onChange : section -> msg
, attributes : Bool -> List (Attribute msg)
}
viewSections { label, fromString, onSelect, attributes } ({ arrangement, labels } as model) =
{ selected = model |> current fromString
, options = arrangement
, label = \elem -> label (elem |> labels)
, onChange = onSelect
, attributes = attributes
)
, onSelect = onSelect
}
@ -180,13 +186,13 @@ view :
(section -> Element msg)
-> Model section
-> Element msg
view asElement { labels, arrangement } =
view asElement { toString, arrangement } =
arrangement
|> List.map
(\header ->
Element.el
[ header
|> labels
|> toString
|> Attributes.id
|> Element.htmlAttribute
, Element.width <| Element.fill

View File

@ -1,6 +1,7 @@
module Widget.Snackbar exposing
( Model, init, current, timePassed
, insert, insertFor, dismiss
, Message, view
)
{-| A [snackbar](https://material.io/components/snackbars/) shows notification, one at a time.
@ -17,7 +18,16 @@ module Widget.Snackbar exposing
-}
import Element exposing (Attribute, Element)
import Queue exposing (Queue)
import Widget
import Widget.Button as Button exposing (ButtonStyle, TextButton)
type alias Message msg =
{ text : String
, button : Maybe (TextButton msg)
}
{-| A snackbar has a queue of Notifications, each with the amount of ms the message should be displayed
@ -92,3 +102,31 @@ timePassed ms model =
current : Model a -> Maybe a
current model =
model.current |> Maybe.map Tuple.first
view :
{ row : List (Attribute msg)
, text : List (Attribute msg)
, button : ButtonStyle msg
}
-> (a -> Message msg)
-> Model a
-> Maybe (Element msg)
view style toMessage model =
model
|> current
|> Maybe.map
(toMessage
>> (\{ text, button } ->
[ text
|> Element.text
|> List.singleton
|> Element.paragraph style.text
, button
|> Maybe.map
(Button.viewTextOnly style.button)
|> Maybe.withDefault Element.none
]
|> Element.row style.row
)
)