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/browser": "1.0.2 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0", "elm/core": "1.0.0 <= v < 2.0.0",
"elm/html": "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/time": "1.0.0 <= v < 2.0.0",
"elm-community/intdict": "3.0.0 <= v < 4.0.0", "elm-community/intdict": "3.0.0 <= v < 4.0.0",
"jasonliang512/elm-heroicons": "1.0.2 <= v < 2.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 = filterSelect model =
Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill]) <| ( "Filter Select"
[ Element.el Heading.h3 <| Element.text "Filter Select"
, case model.selected of , case model.selected of
Just selected -> Just selected ->
Element.row Grid.compact Element.row Grid.compact
@ -128,13 +127,12 @@ filterSelect model =
) )
|> Element.wrappedRow [ Element.spacing 10 ] |> Element.wrappedRow [ Element.spacing 10 ]
] ]
] )
validatedInput : ValidatedInput.Model () ( String, String ) -> Element Msg validatedInput : ValidatedInput.Model () ( String, String ) -> (String,Element Msg)
validatedInput model = validatedInput model =
Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill]) <| ( "Validated Input"
[ Element.el Heading.h3 <| Element.text "Validated Input"
, ValidatedInput.view Input.simple , ValidatedInput.view Input.simple
model model
{ label = "First Name, Sir Name" { label = "First Name, Sir Name"
@ -152,19 +150,19 @@ validatedInput model =
|> Element.el (Tag.simple ++ Group.right ++ Color.primary) |> Element.el (Tag.simple ++ Group.right ++ Color.primary)
] ]
} }
] )
view : Model -> Element Msg view : (Msg -> msg) -> Model ->
view model = { title : String
Element.column (Grid.section ++ [ Element.centerX ]) , description : String
[ Element.el Heading.h2 <| Element.text "Components" , items : List (String,Element msg)
, "Components have a Model, an Update- and sometimes even a Subscription-function. It takes some time to set them up correctly." }
|> Element.text view msgMapper model =
|> List.singleton { title = "Components"
|> Element.paragraph [] , description = "Components have a Model, an Update- and sometimes even a Subscription-function. It takes some time to set them up correctly."
, Element.wrappedRow (Grid.simple ++ [Element.height <| Element.shrink]) <| , items =
[ filterSelect model.filterSelect [ filterSelect model.filterSelect
, validatedInput model.validatedInput , 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.Events as Events
import Browser.Navigation as Navigation import Browser.Navigation as Navigation
import Component import Component
import Element exposing (DeviceClass(..), Element) import Element exposing (DeviceClass(..), Element,Attribute)
import Element.Input as Input import Element.Input as Input
import Element.Font as Font import Element.Font as Font
import Element.Border as Border import Element.Border as Border
@ -29,21 +29,27 @@ import Stateless
import Task import Task
import Time import Time
import Widget import Widget
import Widget.Button exposing (ButtonStyle)
import Widget.FilterSelect as FilterSelect import Widget.FilterSelect as FilterSelect
import Widget.ScrollingNav as ScrollingNav import Widget.ScrollingNav as ScrollingNav
import Widget.Snackbar as Snackbar import Widget.Snackbar as Snackbar
import Widget.ValidatedInput as ValidatedInput import Widget.ValidatedInput as ValidatedInput
import Data.Section as Section exposing (Section(..)) import Data.Section as Section exposing (Section(..))
import Array
type alias LoadedModel = type alias LoadedModel =
{ component : Component.Model { component : Component.Model
, stateless : Stateless.Model , stateless : Stateless.Model
, reusable : Reusable.Model , reusable : Reusable.Model
, scrollingNav : ScrollingNav.Model Section , scrollingNav : ScrollingNav.Model Section
, layout : Layout , layout : Layout LoadedMsg
, displayDialog : Bool , displayDialog : Bool
, deviceClass : DeviceClass , window : { height : Int, width : Int }
, search : String , search :
{ raw : String
, current : String
, remaining : Int
}
} }
@ -58,7 +64,7 @@ type LoadedMsg
| ComponentSpecific Component.Msg | ComponentSpecific Component.Msg
| UpdateScrollingNav (ScrollingNav.Model Section -> ScrollingNav.Model Section) | UpdateScrollingNav (ScrollingNav.Model Section -> ScrollingNav.Model Section)
| TimePassed Int | TimePassed Int
| AddSnackbar String | AddSnackbar (String,Bool)
| ToggleDialog Bool | ToggleDialog Bool
| ChangedSidebar (Maybe Part) | ChangedSidebar (Maybe Part)
| Resized { width : Int, height : Int } | Resized { width : Int, height : Int }
@ -72,9 +78,64 @@ type Msg
= LoadedSpecific LoadedMsg = LoadedSpecific LoadedMsg
| GotViewport Viewport | 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 = 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 , layout = Framework.responsiveLayout
, header = , header =
Framework.container Framework.container
@ -83,28 +144,42 @@ style =
, Element.height <| Element.px <| 42 , Element.height <| Element.px <| 42
] ]
, menuButton = , menuButton =
Button.simple ++ Group.center ++ Color.dark { label = Grid.simple
, menuButtonSelected = , container = Button.simple ++ Group.center ++ Color.dark
Color.primary , disabled = Color.disabled
, active = Color.primary
}
, sheetButton = , sheetButton =
Button.fill { container =
++ Group.center Button.fill
++ Color.light ++ Group.center
++ [Font.alignLeft] ++ Color.light
, sheetButtonSelected = ++ [Font.alignLeft]
Color.primary , label = Grid.simple
, tabButton = , disabled = Color.disabled
[ Element.height <| Element.px <| 42 , active = Color.primary
, Border.widthEach }
{ top = 0, , menuTabButton =
left = 0, { container =
right = 0, [ Element.height <| Element.px <| 42
bottom = 8 , Border.widthEach
} { top = 0,
] left = 0,
, tabButtonSelected = right = 0,
[ Border.color Color.turquoise 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 = , sheet =
Color.light ++ [ Element.width <| Element.maximum 256 <| Element.fill] Color.light ++ [ Element.width <| Element.maximum 256 <| Element.fill]
, menuIcon = , menuIcon =
@ -116,7 +191,16 @@ style =
, searchIcon = , searchIcon =
Icons.search |> Element.html |> Element.el [] Icons.search |> Element.html |> Element.el []
, search = , 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 let
( scrollingNav, cmd ) = ( scrollingNav, cmd ) =
ScrollingNav.init ScrollingNav.init
{ labels = Section.toString { toString = Section.toString
, fromString = Section.fromString
, arrangement = Section.asList , arrangement = Section.asList
, toMsg = \result -> , toMsg = \result ->
case result of case result of
@ -141,13 +226,15 @@ initialModel { viewport } =
, scrollingNav = scrollingNav , scrollingNav = scrollingNav
, layout = Layout.init , layout = Layout.init
, displayDialog = False , displayDialog = False
, deviceClass = , window =
{ width = viewport.width |> round { width = viewport.width |> round
, height = viewport.height |> round , height = viewport.height |> round
} }
|> Element.classifyDevice , search =
|> .class { raw = ""
, search = "" , current = ""
, remaining = 0
}
} }
, cmd , cmd
) )
@ -171,25 +258,23 @@ view model =
Layout.view [] Layout.view []
{ dialog = { dialog =
if m.displayDialog then if m.displayDialog then
Just { body =
{ onDismiss = Just <| ToggleDialog False "This is a dialog window"
, content =
[ Element.el Heading.h3 <| Element.text "Dialog"
, "This is a dialog window"
|> Element.text |> Element.text
|> List.singleton |> List.singleton
|> Element.paragraph [] |> Element.paragraph []
, Input.button (Button.simple ++ [ Element.alignRight ]) , title = Just "Dialog"
{ onPress = Just <| ToggleDialog False , accept = Just
, label = Element.text "Ok" { 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 else
Nothing Nothing
@ -198,11 +283,12 @@ view model =
, [ m.scrollingNav , [ m.scrollingNav
|> ScrollingNav.view |> ScrollingNav.view
(\section -> (\section ->
case section of ( case section of
ComponentViews -> ComponentViews ->
m.component m.component
|> Component.view |> Component.view ComponentSpecific
|> Element.map ComponentSpecific
ReusableViews -> ReusableViews ->
Reusable.view Reusable.view
@ -218,6 +304,39 @@ view model =
, changedSheet = ChangedSidebar , changedSheet = ChangedSidebar
} }
m.stateless 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 |> Element.column Framework.container
@ -225,52 +344,35 @@ view model =
|> Element.column Grid.compact |> Element.column Grid.compact
, style = style , style = style
, layout = m.layout , layout = m.layout
, deviceClass = m.deviceClass , window = m.window
, menu = , menu =
{ selected = m.scrollingNav
Section.asList |> ScrollingNav.toSelect
|> List.indexedMap (\i s -> (i,s)) (\int ->
|> List.filterMap m.scrollingNav.arrangement
( \(i,s) -> |> Array.fromList
if m.scrollingNav |> Array.get int
|> ScrollingNav.current Section.fromString |> Maybe.map JumpTo
|> (==) (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
}
)
}
, actions = , actions =
[ { onPress = Just <| Load "https://package.elm-lang.org/packages/Orasund/elm-ui-widgets/latest/" [ { 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 [] , icon = Icons.book|> Element.html |> Element.el []
} }
, { onPress = Just <| Load "https://github.com/Orasund/elm-ui-widgets" , { onPress = Just <| Load "https://github.com/Orasund/elm-ui-widgets"
, label = "Github" , text = "Github"
, icon = Icons.github|> Element.html |> Element.el [] , icon = Icons.github|> Element.html |> Element.el []
} }
, { onPress = Nothing , { onPress = Nothing
, label = "Placeholder" , text = "Placeholder"
, icon = Icons.circle|> Element.html |> Element.el [] , icon = Icons.circle|> Element.html |> Element.el []
} }
, { onPress = Nothing , { onPress = Nothing
, label = "Placeholder" , text = "Placeholder"
, icon = Icons.triangle|> Element.html |> Element.el [] , icon = Icons.triangle|> Element.html |> Element.el []
} }
, { onPress = Nothing , { onPress = Nothing
, label = "Placeholder" , text = "Placeholder"
, icon = Icons.square|> Element.html |> Element.el [] , icon = Icons.square|> Element.html |> Element.el []
} }
] ]
@ -281,7 +383,7 @@ view model =
|> Element.el Heading.h1 |> Element.el Heading.h1
, search = , search =
Just Just
{ text = m.search { text = m.search.raw
, onChange = ChangedSearch , onChange = ChangedSearch
, label = "Search" , label = "Search"
} }
@ -330,15 +432,44 @@ updateLoaded msg model =
) )
TimePassed int -> TimePassed int ->
let
search = model.search
in
( { model ( { model
| layout = model.layout |> Layout.timePassed int | 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 , ScrollingNav.getPos
|> Task.perform UpdateScrollingNav |> Task.perform UpdateScrollingNav
) )
AddSnackbar string -> AddSnackbar (string,bool) ->
( { model | layout = model.layout |> Layout.queueMessage string } ( { 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 , Cmd.none
) )
@ -347,8 +478,8 @@ updateLoaded msg model =
, Cmd.none , Cmd.none
) )
Resized screen -> Resized window ->
( { model | deviceClass = screen |> Element.classifyDevice |> .class } ( { model | window = window }
, Cmd.none , Cmd.none
) )
@ -370,7 +501,17 @@ updateLoaded msg model =
) )
ChangedSearch string -> ChangedSearch string ->
( { model | search = string},Cmd.none) let
search = model.search
in
( { model | search =
{ search
| raw = string
, remaining = 300
}
}
, Cmd.none
)
Idle -> Idle ->
( model , Cmd.none) ( model , Cmd.none)

View File

@ -7,6 +7,7 @@ module Icons exposing
, triangle , triangle
, square , square
, search , search
, slash
) )
import Html exposing (Html) import Html exposing (Html)
@ -66,6 +67,13 @@ circle =
[ Svg.circle [ cx "12", cy "12", r "10" ] [] [ 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 : Html msg
triangle = triangle =
svgFeatherIcon "triangle" svgFeatherIcon "triangle"

View File

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

View File

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

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

View File

@ -1,16 +1,13 @@
module Layout exposing (Layout, Part(..), activate, init, queueMessage, timePassed, view) module Layout exposing (Layout, Part(..), activate, init, queueMessage, timePassed, view)
import Array import Array
import Browser.Dom exposing (Viewport)
import Core.Style as Style exposing (Style) import Core.Style as Style exposing (Style)
import Element exposing (Attribute, DeviceClass(..), Element) import Element exposing (Attribute, DeviceClass(..), Element)
import Element.Background as Background
import Element.Events as Events
import Element.Input as Input import Element.Input as Input
import Html exposing (Html) import Html exposing (Html)
import Html.Attributes as Attributes import Widget exposing (Select)
import Widget import Widget.Button as Button exposing (Button)
import Widget.Snackbar as Snackbar import Widget.Snackbar as Snackbar exposing (Message)
type Part type Part
@ -19,34 +16,34 @@ type Part
| Search | Search
type alias Layout = type alias Layout msg =
{ snackbar : Snackbar.Model String { snackbar : Snackbar.Model (Message msg)
, active : Maybe Part , active : Maybe Part
} }
init : Layout init : Layout msg
init = init =
{ snackbar = Snackbar.init { snackbar = Snackbar.init
, active = Nothing , active = Nothing
} }
queueMessage : String -> Layout -> Layout queueMessage : Message msg -> Layout msg -> Layout msg
queueMessage message layout = queueMessage message layout =
{ layout { layout
| snackbar = layout.snackbar |> Snackbar.insert message | snackbar = layout.snackbar |> Snackbar.insert message
} }
activate : Maybe Part -> Layout -> Layout activate : Maybe Part -> Layout msg -> Layout msg
activate part layout = activate part layout =
{ layout { layout
| active = part | active = part
} }
timePassed : Int -> Layout -> Layout timePassed : Int -> Layout msg -> Layout msg
timePassed sec layout = timePassed sec layout =
case layout.active of case layout.active of
Just LeftSheet -> Just LeftSheet ->
@ -64,28 +61,31 @@ timePassed sec layout =
view : view :
List (Attribute msg) List (Attribute msg)
-> ->
{ deviceClass : DeviceClass { window : { height : Int, width : Int }
, dialog : Maybe { onDismiss : Maybe msg, content : Element msg } , dialog : Maybe { onDismiss : Maybe msg, content : Element msg }
, content : Element msg , content : Element msg
, layout : Layout , layout : Layout msg
, title : Element msg , title : Element msg
, menu : , menu : Select msg
{ selected : Int
, items : List { label : String, icon : Element Never, onPress : Maybe msg }
}
, search : , search :
Maybe Maybe
{ onChange : String -> msg { onChange : String -> msg
, text : String , text : String
, label : String , label : String
} }
, actions : List { label : String, icon : Element Never, onPress : Maybe msg } , actions : List (Button msg)
, onChangedSidebar : Maybe Part -> msg , onChangedSidebar : Maybe Part -> msg
, style : Style msg , style : Style style msg
} }
-> Html 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 let
deviceClass : DeviceClass
deviceClass =
window
|> Element.classifyDevice
|> .class
( primaryActions, moreActions ) = ( primaryActions, moreActions ) =
( if (actions |> List.length) > 4 then ( if (actions |> List.length) > 4 then
actions |> List.take 2 actions |> List.take 2
@ -115,30 +115,29 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
[ (if [ (if
(deviceClass == Phone) (deviceClass == Phone)
|| (deviceClass == Tablet) || (deviceClass == Tablet)
|| ((menu.items |> List.length) > 5) || ((menu.options |> List.length) > 5)
then then
[ Input.button style.menuButton [ Button.viewIconOnly style.menuButton
{ onPress = Just <| onChangedSidebar <| Just LeftSheet { onPress = Just <| onChangedSidebar <| Just LeftSheet
, label = style.menuIcon |> Element.map never , icon = style.menuIcon |> Element.map never
, text = "Menu"
} }
, menu.items , menu.selected
|> Array.fromList |> Maybe.andThen
|> Array.get menu.selected (\option ->
|> Maybe.map (.label >> Element.text >> Element.el style.title) menu.options
|> Array.fromList
|> Array.get option
)
|> Maybe.map (.text >> Element.text >> Element.el style.title)
|> Maybe.withDefault title |> Maybe.withDefault title
] ]
else else
[ title [ title
, menu.items , menu
|> List.indexedMap |> Widget.select
(\i -> |> List.map (Style.menuTabButton style)
if i == menu.selected then
Style.menuTabButtonSelected style
else
Style.menuTabButton style
)
|> Element.row |> Element.row
[ Element.width <| Element.shrink [ Element.width <| Element.shrink
] ]
@ -146,9 +145,9 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
) )
|> Element.row |> Element.row
[ Element.width <| Element.shrink [ Element.width <| Element.shrink
, Element.spacing 8 , Element.spacing style.spacing
] ]
, if deviceClass == Phone then , if deviceClass == Phone || deviceClass == Tablet then
Element.none Element.none
else else
@ -166,37 +165,45 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
} }
) )
|> Maybe.withDefault Element.none |> Maybe.withDefault Element.none
, [ if deviceClass == Phone then , [ search
search |> Maybe.map
|> Maybe.map (\{ label } ->
(\{ label } -> if deviceClass == Tablet then
[ Style.menuButton style [ Button.view style.menuButton
{ onPress = Just <| onChangedSidebar <| Just Search { onPress = Just <| onChangedSidebar <| Just Search
, icon = style.searchIcon , 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 , primaryActions
|> List.map |> List.map
(if deviceClass == Phone then (if deviceClass == Phone then
Style.menuIconButton style Style.menuIconButton style
else else
Style.menuButton style Button.view style.menuButton
) )
, if moreActions |> List.isEmpty then , if moreActions |> List.isEmpty then
[] []
else else
[ Style.menuButton style [ Button.viewIconOnly style.menuButton
{ onPress = Just <| onChangedSidebar <| Just RightSheet { onPress = Just <| onChangedSidebar <| Just RightSheet
, icon = style.moreVerticalIcon , icon = style.moreVerticalIcon
, label = "" , text = "More"
} }
] ]
] ]
@ -210,7 +217,7 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
(style.header (style.header
++ [ Element.padding 0 ++ [ Element.padding 0
, Element.centerX , Element.centerX
, Element.spaceEvenly , Element.spacing style.spacing
, Element.alignTop , Element.alignTop
, Element.width <| Element.fill , Element.width <| Element.fill
] ]
@ -218,16 +225,13 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
snackbar = snackbar =
layout.snackbar layout.snackbar
|> Snackbar.current |> Snackbar.view style.snackbar identity
|> Maybe.map |> Maybe.map
(Element.text (Element.el
>> List.singleton [ Element.padding style.spacing
>> Element.paragraph style.snackbar , Element.alignBottom
>> Element.el , Element.alignRight
[ Element.padding 8 ]
, Element.alignBottom
, Element.alignRight
]
) )
|> Maybe.withDefault Element.none |> Maybe.withDefault Element.none
@ -236,15 +240,9 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
Just LeftSheet -> Just LeftSheet ->
[ [ title [ [ title
] ]
, menu.items , menu
|> List.indexedMap |> Widget.select
(\i -> |> List.map (Style.sheetButton style)
if i == menu.selected then
Style.sheetButtonSelected style
else
Style.sheetButton style
)
] ]
|> List.concat |> List.concat
|> Element.column [ Element.width <| Element.fill ] |> Element.column [ Element.width <| Element.fill ]
@ -257,7 +255,7 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
Just RightSheet -> Just RightSheet ->
moreActions moreActions
|> List.map (Style.sheetButton style) |> List.map (Button.view style.sheetButton)
|> Element.column [ Element.width <| Element.fill ] |> Element.column [ Element.width <| Element.fill ]
|> Element.el |> Element.el
(style.sheet (style.sheet
@ -269,7 +267,11 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
Just Search -> Just Search ->
case search of case search of
Just { onChange, text, label } -> Just { onChange, text, label } ->
Input.text style.search Input.text
(style.searchFill
++ [ Element.width <| Element.fill
]
)
{ onChange = onChange { onChange = onChange
, text = text , text = text
, placeholder = , placeholder =
@ -297,33 +299,22 @@ view attributes { search, title, onChangedSidebar, menu, actions, deviceClass, d
, Element.inFront snackbar , Element.inFront snackbar
] ]
, if (layout.active /= Nothing) || (dialog /= Nothing) then , if (layout.active /= Nothing) || (dialog /= Nothing) then
Widget.scrim (Element.height <| Element.px <| window.height)
{ onDismiss = :: (case dialog of
Just <| Just dialogConfig ->
case dialog of Widget.modal dialogConfig
Just { onDismiss } ->
onDismiss
|> Maybe.withDefault
(Nothing
|> onChangedSidebar
)
Nothing -> Nothing ->
Nothing Widget.modal
|> onChangedSidebar { onDismiss =
, content = Element.none Nothing
} |> onChangedSidebar
|> Just
, content = sheet
}
)
else 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 module Widget exposing
( select, multiSelect, collapsable, carousel, scrim, tab ( select, multiSelect, collapsable, carousel, modal, tab, dialog
, dialog , Dialog, MultiSelect, Select, selectButton
) )
{-| This module contains functions for displaying data. {-| This module contains functions for displaying data.
@docs select, multiSelect, collapsable, carousel, scrim, tab @docs select, multiSelect, collapsable, carousel, modal, tab, dialog
# DEPRECATED # DEPRECATED
@ -20,67 +20,97 @@ import Element.Background as Background
import Element.Events as Events import Element.Events as Events
import Element.Input as Input import Element.Input as Input
import Set exposing (Set) 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. {-| Selects one out of multiple options. This can be used for radio buttons or Menus.
-} -}
select : select :
{ selected : Maybe a Select msg
, options : List a -> List ( Bool, Button msg )
, label : a -> Element msg select { selected, options, onSelect } =
, onChange : a -> msg
, attributes : Bool -> List (Attribute msg)
}
-> List (Element msg)
select { selected, options, label, onChange, attributes } =
options options
|> List.map |> List.indexedMap
(\a -> (\i a ->
Input.button (attributes (selected == Just a)) ( selected == Just i
{ onPress = a |> onChange |> Just , { onPress = i |> onSelect
, label = label a , text = a.text
} , icon = a.icon
}
)
) )
{-| Selects multible options. This can be used for checkboxes. {-| Selects multible options. This can be used for checkboxes.
-} -}
multiSelect : multiSelect :
{ selected : Set comparable MultiSelect msg
, options : List comparable -> List ( Bool, Button msg )
, label : comparable -> Element msg multiSelect { selected, options, onSelect } =
, onChange : comparable -> msg
, attributes : Bool -> List (Attribute msg)
}
-> List (Element msg)
multiSelect { selected, options, label, onChange, attributes } =
options options
|> List.map |> List.indexedMap
(\a -> (\i a ->
Input.button (attributes (selected |> Set.member a)) ( selected |> Set.member i
{ onPress = a |> onChange |> Just , { onPress = i |> onSelect
, label = , text = a.text
label a , icon = a.icon
} }
)
) )
{-| Some collapsable content. {-| 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 : collapsable :
{ onToggle : Bool -> msg { onToggle : Bool -> msg
@ -107,81 +137,92 @@ collapsable { onToggle, isCollapsed, label, content } =
{-| Displayes a list of contents in a tab {-| Displayes a list of contents in a tab
-} -}
tab : tab :
List (Attribute msg) { style
-> | tabButton : ButtonStyle msg
{ selected : a , tabRow : List (Attribute msg)
, options : List a }
, onChange : a -> msg -> Select msg
, label : a -> Element msg -> (Maybe Int -> Element msg)
, content : a -> Element msg
, attributes : Bool -> List (Attribute msg)
}
-> Element msg -> Element msg
tab atts { selected, options, onChange, label, content, attributes } = tab style options content =
[ select [ options
{ selected = Just selected |> select
, options = options |> List.map (selectButton style.tabButton)
, label = label |> Element.row style.tabRow
, onChange = onChange , options.selected
, attributes = attributes |> content
}
|> Element.row atts
, content selected
] ]
|> Element.column [] |> Element.column []
{-| DEPRECATED. Use scrim instead.
-}
dialog : dialog :
{ onDismiss : Maybe msg { containerColumn : List (Attribute msg)
, content : Element msg , title : List (Attribute msg)
, buttonRow : List (Attribute msg)
, accept : ButtonStyle msg
, dismiss : ButtonStyle msg
} }
-> Element msg -> Dialog msg
dialog { onDismiss, content } = -> { onDismiss : Maybe msg, content : Element msg }
content dialog style { title, body, accept, dismiss } =
|> Element.el { onDismiss =
[ Element.centerX case ( accept, dismiss ) of
, Element.centerY ( Nothing, Nothing ) ->
] Nothing
|> Element.el
([ Element.width <| Element.fill ( Nothing, Just { onPress } ) ->
, Element.height <| Element.fill onPress
, Background.color <| Element.rgba255 0 0 0 0.5
] ( Just _, _ ) ->
++ (onDismiss Nothing
|> Maybe.map (Events.onClick >> List.singleton) , content =
|> Maybe.withDefault [] 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 ) ->
{-| A scrim to block the interaction with the site. Usefull for modals and side panels [ dismissButton
|> Button.viewTextOnly style.dismiss
If the scrim is clicked a message may be send. Also one can place an element infront. , acceptButton
|> Button.viewTextOnly style.accept
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
] ]
}
_ ->
[]
)
] ]
}
{-| 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) modal : { onDismiss : Maybe msg, content : Element msg } -> List (Attribute msg)
scrim { onDismiss, content } = modal { onDismiss, content } =
Element.el [ Element.el
([ Element.width <| Element.fill ([ Element.width <| Element.fill
, Element.height <| Element.fill , Element.height <| Element.fill
, Background.color <| Element.rgba255 0 0 0 0.5 , Background.color <| Element.rgba255 0 0 0 0.5
@ -193,7 +234,8 @@ scrim { onDismiss, content } =
) )
content content
|> Element.inFront |> Element.inFront
|> List.singleton , Element.clip
]
{-| A Carousel circles through a non empty list of contents. {-| 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 module Widget.ScrollingNav exposing
( Model, init, view, viewSections, current ( Model, init, view, current
, jumpTo, syncPositions , jumpTo, syncPositions
, getPos, jumpToWithOffset, setPos , getPos, jumpToWithOffset, setPos, toSelect
) )
{-| The Scrolling Nav is a navigation bar thats updates while you scroll through {-| 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 Browser.Dom as Dom
import Element exposing (Attribute, Element) import Element exposing (Element)
import Framework.Grid as Grid import Framework.Grid as Grid
import Html.Attributes as Attributes import Html.Attributes as Attributes
import IntDict exposing (IntDict) import IntDict exposing (IntDict)
import Task exposing (Task) import Task exposing (Task)
import Widget exposing (Select)
{-| -} {-| -}
type alias Model section = type alias Model section =
{ labels : section -> String { toString : section -> String
, fromString : String -> Maybe section
, positions : IntDict String , positions : IntDict String
, arrangement : List section , arrangement : List section
, scrollPos : Int , scrollPos : Int
@ -39,13 +41,15 @@ type alias Model section =
{-| The intial state include the labels and the arrangement of the sections {-| The intial state include the labels and the arrangement of the sections
-} -}
init : init :
{ labels : section -> String { toString : section -> String
, fromString : String -> Maybe section
, arrangement : List section , arrangement : List section
, toMsg : Result Dom.Error (Model section -> Model section) -> msg , toMsg : Result Dom.Error (Model section -> Model section) -> msg
} }
-> ( Model section, Cmd msg ) -> ( Model section, Cmd msg )
init { labels, arrangement, toMsg } = init { toString, fromString, arrangement, toMsg } =
{ labels = labels { toString = toString
, fromString = fromString
, positions = IntDict.empty , positions = IntDict.empty
, arrangement = arrangement , arrangement = arrangement
, scrollPos = 0 , scrollPos = 0
@ -82,8 +86,8 @@ jumpTo :
} }
-> Model section -> Model section
-> Cmd msg -> Cmd msg
jumpTo { section, onChange } { labels } = jumpTo { section, onChange } { toString } =
Dom.getElement (section |> labels) Dom.getElement (section |> toString)
|> Task.andThen |> Task.andThen
(\{ element } -> (\{ element } ->
Dom.setViewport 0 element.y Dom.setViewport 0 element.y
@ -100,8 +104,8 @@ jumpToWithOffset :
} }
-> Model section -> Model section
-> Cmd msg -> Cmd msg
jumpToWithOffset { offset, section, onChange } { labels } = jumpToWithOffset { offset, section, onChange } { toString } =
Dom.getElement (section |> labels) Dom.getElement (section |> toString)
|> Task.andThen |> Task.andThen
(\{ element } -> (\{ element } ->
Dom.setViewport 0 (element.y - offset) 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 : Model section -> Task Dom.Error (Model section -> Model section)
syncPositions { labels, arrangement } = syncPositions { toString, arrangement } =
arrangement arrangement
|> List.map |> List.map
(\label -> (\label ->
Dom.getElement (labels label) Dom.getElement (toString label)
|> Task.map |> Task.map
(\x -> (\x ->
( x.element.y |> round ( x.element.y |> round
@ -133,7 +137,7 @@ syncPositions { labels, arrangement } =
| positions = | positions =
model.positions model.positions
|> IntDict.insert pos |> IntDict.insert pos
(label |> model.labels) (label |> model.toString)
} }
) )
m m
@ -151,27 +155,29 @@ current fromString { positions, scrollPos } =
|> Maybe.andThen fromString |> Maybe.andThen fromString
{-| -} toSelect : (Int -> Maybe msg) -> Model section -> Select msg
viewSections : toSelect onSelect ({ arrangement, toString, fromString } as model) =
{ label : String -> Element msg { selected =
, fromString : String -> Maybe section arrangement
, onSelect : section -> msg |> List.indexedMap (\i s -> ( i, s ))
, attributes : Bool -> List (Attribute msg) |> List.filterMap
} (\( i, s ) ->
-> Model section if Just s == current fromString model then
-> Just i
{ selected : Maybe section
, options : List section else
, label : section -> Element msg Nothing
, onChange : section -> msg )
, attributes : Bool -> List (Attribute msg) |> List.head
} , options =
viewSections { label, fromString, onSelect, attributes } ({ arrangement, labels } as model) = arrangement
{ selected = model |> current fromString |> List.map
, options = arrangement (\s ->
, label = \elem -> label (elem |> labels) { text = toString s
, onChange = onSelect , icon = Element.none
, attributes = attributes }
)
, onSelect = onSelect
} }
@ -180,13 +186,13 @@ view :
(section -> Element msg) (section -> Element msg)
-> Model section -> Model section
-> Element msg -> Element msg
view asElement { labels, arrangement } = view asElement { toString, arrangement } =
arrangement arrangement
|> List.map |> List.map
(\header -> (\header ->
Element.el Element.el
[ header [ header
|> labels |> toString
|> Attributes.id |> Attributes.id
|> Element.htmlAttribute |> Element.htmlAttribute
, Element.width <| Element.fill , Element.width <| Element.fill

View File

@ -1,6 +1,7 @@
module Widget.Snackbar exposing module Widget.Snackbar exposing
( Model, init, current, timePassed ( Model, init, current, timePassed
, insert, insertFor, dismiss , insert, insertFor, dismiss
, Message, view
) )
{-| A [snackbar](https://material.io/components/snackbars/) shows notification, one at a time. {-| 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 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 {-| 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 a -> Maybe a
current model = current model =
model.current |> Maybe.map Tuple.first 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
)
)