mirror of
https://github.com/Orasund/elm-ui-widgets.git
synced 2024-11-22 13:14:10 +03:00
Added Dialog, and more...
This commit is contained in:
parent
82b59530fd
commit
b73a19d3ce
3
elm.json
3
elm.json
@ -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",
|
||||
@ -28,4 +29,4 @@
|
||||
"test-dependencies": {
|
||||
"elm-explorations/test": "1.2.1 <= v < 2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]) <|
|
||||
[ filterSelect model.filterSelect
|
||||
, validatedInput model.validatedInput
|
||||
|
||||
]
|
||||
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) )
|
||||
}
|
||||
|
@ -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 =
|
||||
Button.fill
|
||||
++ Group.center
|
||||
++ Color.light
|
||||
++ [Font.alignLeft]
|
||||
, sheetButtonSelected =
|
||||
Color.primary
|
||||
, tabButton =
|
||||
[ Element.height <| Element.px <| 42
|
||||
, Border.widthEach
|
||||
{ top = 0,
|
||||
left = 0,
|
||||
right = 0,
|
||||
bottom = 8
|
||||
}
|
||||
]
|
||||
, tabButtonSelected =
|
||||
[ Border.color Color.turquoise
|
||||
]
|
||||
{ container =
|
||||
Button.fill
|
||||
++ Group.center
|
||||
++ Color.light
|
||||
++ [Font.alignLeft]
|
||||
, 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 = 4
|
||||
}
|
||||
, Element.paddingEach
|
||||
{ top = 12
|
||||
, left = 8
|
||||
, right = 8
|
||||
, bottom = 4
|
||||
}
|
||||
, Border.color Color.black
|
||||
]
|
||||
, 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"
|
||||
}
|
||||
]
|
||||
|> Element.column
|
||||
( Grid.simple
|
||||
++ Card.large
|
||||
++ [Element.centerX, Element.centerY]
|
||||
)
|
||||
, title = Just "Dialog"
|
||||
, accept = Just
|
||||
{ text = "Ok"
|
||||
, onPress = Just <| ToggleDialog False
|
||||
}
|
||||
, 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
|
||||
)
|
||||
|> List.head
|
||||
|> Maybe.withDefault 0
|
||||
, items =
|
||||
Section.asList
|
||||
|> List.map
|
||||
(\label ->
|
||||
{ icon = Element.none
|
||||
, label = label |> Section.toString
|
||||
, onPress = Just <| JumpTo <| label
|
||||
}
|
||||
)
|
||||
}
|
||||
m.scrollingNav
|
||||
|> ScrollingNav.toSelect
|
||||
(\int ->
|
||||
m.scrollingNav.arrangement
|
||||
|> Array.fromList
|
||||
|> Array.get int
|
||||
|> Maybe.map JumpTo
|
||||
)
|
||||
, 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)
|
||||
|
@ -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"
|
||||
|
@ -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]) <|
|
||||
[ snackbar addSnackbar
|
||||
, sortTable model |> Element.map msgMapper
|
||||
, scrollingNavCard
|
||||
]
|
||||
{ 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 |> Tuple.mapSecond (Element.map msgMapper)
|
||||
, scrollingNavCard
|
||||
]
|
||||
}
|
||||
|
@ -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
|
||||
{ selected = model.tab
|
||||
, options = [ 1, 2, 3 ]
|
||||
, onChange = ChangedTab
|
||||
, label = \int -> "Tab " ++ String.fromInt int |> Element.text
|
||||
, content =
|
||||
\selected ->
|
||||
( "Tab"
|
||||
, Widget.tab
|
||||
{ tabButton = tabButtonStyle
|
||||
, tabRow = Grid.simple
|
||||
}
|
||||
{ selected = model.tab
|
||||
, options = [ 1, 2, 3 ]
|
||||
|> 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,46 +236,35 @@ 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"
|
||||
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"
|
||||
}
|
||||
, Input.button Button.simple
|
||||
{ onPress = Just <| changedSheet <| Just RightSheet
|
||||
, label = Element.text <| "show right sheet"
|
||||
}
|
||||
] |> 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"
|
||||
}
|
||||
, Input.button Button.simple
|
||||
{ onPress = Just <| changedSheet <| Just LeftSheet
|
||||
, label = Element.text <| "show left sheet"
|
||||
}
|
||||
, Input.button Button.simple
|
||||
{ onPress = Just <| changedSheet <| Just RightSheet
|
||||
, label = Element.text <| "show right sheet"
|
||||
}
|
||||
]
|
||||
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
]
|
||||
}
|
||||
|
@ -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)
|
||||
, 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)
|
||||
, menuIcon : Element Never
|
||||
, moreVerticalIcon : Element Never
|
||||
, spacing : Int
|
||||
, title : List (Attribute msg)
|
||||
, searchIcon : Element Never
|
||||
, search : 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)
|
||||
, 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
|
||||
|
185
src/Layout.elm
185
src/Layout.elm
@ -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
|
||||
|> Array.fromList
|
||||
|> Array.get menu.selected
|
||||
|> Maybe.map (.label >> Element.text >> Element.el style.title)
|
||||
, menu.selected
|
||||
|> Maybe.andThen
|
||||
(\option ->
|
||||
menu.options
|
||||
|> Array.fromList
|
||||
|> 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
|
||||
|> Maybe.map
|
||||
(\{ label } ->
|
||||
[ Style.menuButton style
|
||||
, [ search
|
||||
|> Maybe.map
|
||||
(\{ label } ->
|
||||
if deviceClass == Tablet then
|
||||
[ Button.view style.menuButton
|
||||
{ onPress = Just <| onChangedSidebar <| Just Search
|
||||
, icon = style.searchIcon
|
||||
, label = label
|
||||
, text = label
|
||||
}
|
||||
]
|
||||
)
|
||||
|> Maybe.withDefault []
|
||||
|
||||
else
|
||||
[]
|
||||
else if deviceClass == Phone then
|
||||
[ Style.menuIconButton style
|
||||
{ onPress = Just <| onChangedSidebar <| Just Search
|
||||
, icon = style.searchIcon
|
||||
, text = label
|
||||
}
|
||||
]
|
||||
|
||||
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,16 +225,13 @@ 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.alignBottom
|
||||
, Element.alignRight
|
||||
]
|
||||
(Element.el
|
||||
[ Element.padding style.spacing
|
||||
, Element.alignBottom
|
||||
, Element.alignRight
|
||||
]
|
||||
)
|
||||
|> Maybe.withDefault Element.none
|
||||
|
||||
@ -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 ->
|
||||
Nothing
|
||||
|> onChangedSidebar
|
||||
, content = Element.none
|
||||
}
|
||||
Nothing ->
|
||||
Widget.modal
|
||||
{ onDismiss =
|
||||
Nothing
|
||||
|> onChangedSidebar
|
||||
|> Just
|
||||
, content = sheet
|
||||
}
|
||||
)
|
||||
|
||||
else
|
||||
[]
|
||||
, [ Element.inFront sheet
|
||||
, Element.inFront <|
|
||||
case dialog of
|
||||
Just element ->
|
||||
element.content
|
||||
|
||||
Nothing ->
|
||||
Element.none
|
||||
]
|
||||
]
|
||||
)
|
||||
|
262
src/Widget.elm
262
src/Widget.elm
@ -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
|
||||
|
||||
( Just _, _ ) ->
|
||||
Nothing
|
||||
, content =
|
||||
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
|
||||
|
||||
|
||||
{-| 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
|
||||
, content =
|
||||
[ "This is a dialog window"
|
||||
|> Element.text
|
||||
, Input.button []
|
||||
{onPress = Just <| ToggleDialog False
|
||||
, label = Element.text "Ok"
|
||||
}
|
||||
]
|
||||
|> Element.column
|
||||
[ Element.centerX
|
||||
, Element.centerY
|
||||
( 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
82
src/Widget/Button.elm
Normal 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
|
||||
]
|
||||
}
|
@ -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)
|
||||
}
|
||||
-> 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
|
||||
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
|
||||
}
|
||||
)
|
||||
, 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
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user