starting work on List-Widget

This commit is contained in:
Lucas Payr 2020-05-06 14:29:28 +02:00
parent 8bb1d17371
commit 732a18720d
23 changed files with 1373 additions and 1310 deletions

View File

@ -1,233 +0,0 @@
module Component exposing (Model, Msg(..), init, update, view)
import Browser
import Element exposing (Color, Element)
import Element.Background as Background
import Element.Input as Input
import Element.Border as Border
import Element.Font as Font
import Framework
import Framework.Button as Button
import Framework.Card as Card
import Framework.Color as Color
import Framework.Grid as Grid
import Framework.Group as Group
import Framework.Heading as Heading
import Framework.Input as Input
import Framework.Tag as Tag
import Heroicons.Solid as Heroicons
import Html exposing (Html)
import Html.Attributes as Attributes
import Set exposing (Set)
import Time
import Widget
import Widget.Style exposing (ButtonStyle)
import Widget.FilterSelect as FilterSelect
import Widget.FilterMultiSelect as FilterMultiSelect
import Widget.ScrollingNav as ScrollingNav
import Widget.Snackbar as Snackbar
import Widget.ValidatedInput as ValidatedInput
import Data.Style exposing (style)
type alias Model =
{ filterSelect : FilterSelect.Model
, filterMultiSelect : FilterMultiSelect.Model
, validatedInput : ValidatedInput.Model () ( String, String )
}
type Msg
= FilterSelectSpecific FilterSelect.Msg
| FilterMultiSelectSpecific FilterMultiSelect.Msg
| ValidatedInputSpecific ValidatedInput.Msg
init : Model
init =
{ filterSelect =
[ "Apple"
, "Kiwi"
, "Strawberry"
, "Pineapple"
, "Mango"
, "Grapes"
, "Watermelon"
, "Orange"
, "Lemon"
, "Blueberry"
, "Grapefruit"
, "Coconut"
, "Cherry"
, "Banana"
]
|> Set.fromList
|> FilterSelect.init
, filterMultiSelect =
[ "Apple"
, "Kiwi"
, "Strawberry"
, "Pineapple"
, "Mango"
, "Grapes"
, "Watermelon"
, "Orange"
, "Lemon"
, "Blueberry"
, "Grapefruit"
, "Coconut"
, "Cherry"
, "Banana"
]
|> Set.fromList
|> FilterMultiSelect.init
, validatedInput =
ValidatedInput.init
{ value = ( "John", "Doe" )
, validator =
\string ->
case string |> String.split " " of
[ first, second ] ->
Ok ( first, second )
_ ->
Err ()
, toString =
\( first, second ) -> first ++ " " ++ second
}
}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
FilterSelectSpecific m ->
( { model
| filterSelect = model.filterSelect |> FilterSelect.update m
}
, Cmd.none
)
FilterMultiSelectSpecific m ->
( { model
| filterMultiSelect = model.filterMultiSelect |> FilterMultiSelect.update m
}
, Cmd.none
)
ValidatedInputSpecific m ->
( { model
| validatedInput = model.validatedInput |> ValidatedInput.update m
}
, Cmd.none
)
filterSelect : FilterSelect.Model -> (String,Element Msg)
filterSelect model =
( "Filter Select"
, case model.selected of
Just selected ->
Element.row Grid.compact
[ Element.el (Tag.simple ++ Group.left) <| Element.text <| selected
, Input.button (Tag.simple ++ Group.right ++ Color.danger)
{ onPress = Just <| FilterSelectSpecific <| FilterSelect.Selected Nothing
, label = Element.html <| Heroicons.x [ Attributes.width 16 ]
}
]
Nothing ->
Element.column Grid.simple
[ FilterSelect.viewInput Input.simple
model
{ msgMapper = FilterSelectSpecific
, placeholder =
Just <|
Input.placeholder [] <|
Element.text <|
"Fruit"
, label = "Fruit"
}
, model
|> FilterSelect.viewOptions
|> List.map
(\string ->
Input.button (Button.simple ++ Tag.simple)
{ onPress = Just <| FilterSelectSpecific <| FilterSelect.Selected <| Just <| string
, label = Element.text string
}
)
|> Element.wrappedRow [ Element.spacing 10 ]
]
)
filterMultiSelect : FilterMultiSelect.Model -> (String,Element Msg)
filterMultiSelect model =
( "Filter Multi Select"
, [ FilterMultiSelect.viewInput model
{ msgMapper = FilterMultiSelectSpecific
, placeholder =
Just <|
Input.placeholder [] <|
Element.text <|
"Fruit"
, label = "Fruit"
, toChip = \string ->
{ text = string
, onPress = Just <| FilterMultiSelectSpecific <| FilterMultiSelect.ToggleSelected <| string
, icon = Element.none
}
}
|> Widget.textInput style.textInput
, model
|> FilterMultiSelect.viewOptions
|> List.map
(\string ->
Input.button (Button.simple ++ Tag.simple)
{ onPress = Just <| FilterMultiSelectSpecific <| FilterMultiSelect.ToggleSelected <| string
, label = Element.text string
}
)
|> Element.wrappedRow [ Element.spacing 10 ]
]
|> Element.column Grid.simple
)
validatedInput : ValidatedInput.Model () ( String, String ) -> (String,Element Msg)
validatedInput model =
( "Validated Input"
, ValidatedInput.view Input.simple
model
{ label = "First Name, Sir Name"
, msgMapper = ValidatedInputSpecific
, placeholder = Nothing
, readOnly =
\maybeTuple ->
Element.row Grid.compact
[ maybeTuple
|> (\( a, b ) -> a ++ " " ++ b)
|> Element.text
|> Element.el (Tag.simple ++ Group.left)
, Heroicons.pencil [ Attributes.width 16 ]
|> Element.html
|> Element.el (Tag.simple ++ Group.right ++ Color.primary)
]
}
)
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
, filterMultiSelect model.filterMultiSelect
, validatedInput model.validatedInput
]
|> List.map (Tuple.mapSecond (Element.map msgMapper) )
}

View File

@ -1,19 +1,19 @@
module Data.Section exposing (Section(..),asList,toString,fromString) module Data.Section exposing (Section(..), asList, fromString, toString)
type Section type Section
= ComponentViews = ReusableViews
| ReusableViews
| StatelessViews | StatelessViews
asList : List Section asList : List Section
asList = asList =
[ StatelessViews, ReusableViews, ComponentViews ] [ StatelessViews, ReusableViews ]
toString : Section -> String toString : Section -> String
toString section = toString section =
case section of case section of
ComponentViews ->
"Component"
ReusableViews -> ReusableViews ->
"Reusable" "Reusable"
@ -21,11 +21,10 @@ toString section =
StatelessViews -> StatelessViews ->
"Stateless" "Stateless"
fromString : String -> Maybe Section fromString : String -> Maybe Section
fromString string = fromString string =
case string of case string of
"Component" ->
Just ComponentViews
"Reusable" -> "Reusable" ->
Just ReusableViews Just ReusableViews

View File

@ -1,223 +1,20 @@
module Data.Style exposing (style) module Data.Style exposing (Style)
import Widget exposing (TextInputStyle)
import Widget.Style exposing (Style,ButtonStyle)
import Element exposing (Attribute) import Element exposing (Attribute)
import Element.Input as Input import Widget.Style exposing (ButtonStyle, DialogStyle, ExpansionPanelStyle,
import Element.Font as Font SnackbarStyle ,RowStyle,ColumnStyle,TextInputStyle,TabStyle)
import Element.Border as Border
import Framework
import Framework.Button as Button
import Framework.Card as Card
import Framework.Color as Color
import Framework.Grid as Grid
import Framework.Group as Group
import Framework.Heading as Heading
import Framework.Input as Input
import Framework.Tag as Tag
import Icons
textButton : ButtonStyle msg type alias Style msg =
textButton = Widget.Style.Style
{ container = Button.simple { dialog : DialogStyle msg
, label = Grid.simple , expansionPanel : ExpansionPanelStyle msg
, disabled = Color.disabled
, active = Color.primary
}
simpleButton : ButtonStyle msg
simpleButton =
{ container = Button.simple ++ Color.primary
, label = Grid.simple
, disabled = Color.disabled
, active = Color.primary
}
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
}
textInputStyle =
{ chip = chipButtonStyle
, chipsRow =
[ Element.width <| Element.shrink
, Element.spacing <| 4
, Element.paddingEach
{ top = 8
, left = 0
, right = 0
, bottom = 8
}
]
, containerRow =
Button.simple
++ Color.light
++ [ Border.color <| Element.rgb255 186 189 182
, Font.alignLeft
, Element.paddingXY 8 0
, Element.height <| Element.px <|42
]
++ Grid.simple
, input =
Color.light
++ [ Element.padding 8
]
}
chipButtonStyle : ButtonStyle msg
chipButtonStyle =
{ container = Tag.simple
, disabled = []
, label = Grid.simple
, active = Color.primary
}
style : Style
{ dialog :
{ containerColumn : List (Attribute msg)
, title : List (Attribute msg)
, buttonRow : List (Attribute msg)
, accept : ButtonStyle msg
, dismiss : ButtonStyle msg
}
, button : ButtonStyle msg , button : ButtonStyle msg
, primaryButton : ButtonStyle msg , primaryButton : ButtonStyle msg
, tabButton : ButtonStyle msg , tab : TabStyle msg
, textInput : TextInputStyle msg , textInput : TextInputStyle msg
, chipButton : ButtonStyle msg , chipButton : ButtonStyle msg
} msg , row : RowStyle msg
style = , column : ColumnStyle msg
{ button = buttonStyle , cardColumn : ColumnStyle msg
, primaryButton = simpleButton
, tabButton = tabButtonStyle
, textInput = textInputStyle
, chipButton = chipButtonStyle
, 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
, Element.height <| Element.px <|54]
, button =
{ label = Grid.simple
, container = Button.simple ++ Color.dark
, disabled = Color.disabled
, active = Color.primary
}
, text = [Element.paddingXY 8 0]
}
, layout = Framework.responsiveLayout
{--\a w ->
Html.div []
[ Html.node "meta"
[ Attributes.attribute "name" "viewport"
, Attributes.attribute "content" "width=device-width, initial-scale=1.0"
]
[]
, Element.layoutWith
{options = (Element.focusStyle
{ borderColor = Nothing
, backgroundColor = Nothing
, shadow = Nothing
}
|> List.singleton)
}
(Framework.layoutAttributes ++ a) <| w
]--}
, header =
Framework.container
++ Color.dark
++ [ Element.padding <| 0
, Element.height <| Element.px <| 42
]
, menuButton =
{ label = Grid.simple
, container = Button.simple ++ Group.center ++ Color.dark
, disabled = Color.disabled
, active = Color.primary
}
, sheetButton =
{ container =
Button.fill
++ Group.center
++ Color.light
++ [Font.alignLeft]
, 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 =
Icons.menu |> Element.html |> Element.el []
, moreVerticalIcon =
Icons.moreVertical |> Element.html |> Element.el []
, spacing = 8
, title = Heading.h2
, searchIcon =
Icons.search |> Element.html |> Element.el []
, search =
Color.simple ++
Card.large ++
[Font.color <| Element.rgb255 0 0 0
, Element.padding 6
, Element.centerY
, Element.alignRight
]
, searchFill =
Color.light
++ Group.center
} }
msg

View File

@ -0,0 +1,291 @@
module Data.Style.ElmUiFramework exposing (style)
import Element exposing (Attribute)
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
import Framework
import Framework.Button as Button
import Framework.Card as Card
import Framework.Color as Color
import Framework.Grid as Grid
import Framework.Group as Group
import Framework.Heading as Heading
import Framework.Input as Input
import Framework.Tag as Tag
import Icons
import Data.Style exposing (Style)
import Widget.Style exposing (ButtonStyle, DialogStyle, ExpansionPanelStyle,
SnackbarStyle ,RowStyle,ColumnStyle,TextInputStyle,TabStyle)
textButton : ButtonStyle msg
textButton =
{ container = Button.simple
, labelRow = Grid.simple
, ifDisabled = Color.disabled
, ifActive = Color.primary
}
simpleButton : ButtonStyle msg
simpleButton =
{ container = Button.simple ++ Color.primary
, labelRow = Grid.simple
, ifDisabled = Color.disabled
, ifActive = Color.primary
}
menuTabButton : ButtonStyle msg
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
]
, labelRow = Grid.simple
, ifDisabled = Color.disabled
, ifActive = [ Border.color Color.turquoise ]
}
menuButton : ButtonStyle msg
menuButton =
{ labelRow = Grid.simple
, container = Button.simple ++ Group.center ++ Color.dark
, ifDisabled = Color.disabled
, ifActive = Color.primary
}
sheetButton : ButtonStyle msg
sheetButton =
{ container =
Button.fill
++ Group.center
++ Color.light
++ [ Font.alignLeft ]
, labelRow = Grid.simple
, ifDisabled = Color.disabled
, ifActive = Color.primary
}
buttonStyle : ButtonStyle msg
buttonStyle =
{ labelRow = [ Element.spacing 8 ]
, container = Button.simple
, ifDisabled = Color.disabled
, ifActive = Color.primary
}
snackbarButton : ButtonStyle msg
snackbarButton =
{ labelRow = Grid.simple
, container = Button.simple ++ Color.dark
, ifDisabled = Color.disabled
, ifActive = Color.primary
}
tabButtonStyle : ButtonStyle msg
tabButtonStyle =
{ labelRow = [ Element.spacing 8 ]
, container = Button.simple ++ Group.top
, ifDisabled = Color.disabled
, ifActive = Color.primary
}
textInputStyle : TextInputStyle msg
textInputStyle =
{ chipButton = chipButtonStyle
, chipsRow =
[ Element.width <| Element.shrink
, Element.spacing <| 4
, Element.paddingEach
{ top = 8
, left = 0
, right = 0
, bottom = 8
}
]
, containerRow =
Button.simple
++ Color.light
++ [ Border.color <| Element.rgb255 186 189 182
, Font.alignLeft
, Element.paddingXY 8 0
, Element.height <| Element.px <| 42
]
++ Grid.simple
, input =
Color.light
++ [ Element.padding 8
]
}
chipButtonStyle : ButtonStyle msg
chipButtonStyle =
{ container = Tag.simple
, ifDisabled = []
, labelRow = Grid.simple
, ifActive = Color.primary
}
expansionPanelStyle : ExpansionPanelStyle msg
expansionPanelStyle =
{ containerColumn = Card.simple ++ Grid.simple ++ [Element.height <| Element.shrink]
, panelRow = Grid.spacedEvenly ++ [Element.height <| Element.shrink]
, labelRow = Grid.simple ++ [Element.height <| Element.shrink]
, content = []
, expandIcon = Icons.chevronDown |> Element.html |> Element.el []
, collapseIcon = Icons.chevronUp |> Element.html |> Element.el []
}
dialog : DialogStyle msg
dialog =
{ containerColumn =
Card.simple
++ Grid.simple
++ [ Element.centerY
, 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
}
]
, acceptButton = simpleButton
, dismissButton = textButton
}
snackbar : SnackbarStyle msg
snackbar =
{ containerRow =
Card.simple
++ Color.dark
++ Grid.simple
++ [ Element.paddingXY 8 6
, Element.height <| Element.px <| 54
]
, button = snackbarButton
, text = [ Element.paddingXY 8 0 ]
}
tab : TabStyle msg
tab =
{ button = tabButtonStyle
, optionRow = Grid.simple
, containerColumn = Grid.compact
, content = (Card.small ++ Group.bottom)
}
row : RowStyle msg
row =
{ containerRow = Grid.compact
, element = []
, ifFirst = Group.left
, ifLast = Group.right
, ifCenter = Group.center
}
cardColumn : ColumnStyle msg
cardColumn =
{ containerColumn = Grid.compact
, element = Card.large ++ [Element.height <| Element.fill]
, ifFirst = Group.top
, ifLast = Group.bottom
, ifCenter = Group.center
}
column : ColumnStyle msg
column =
{ containerColumn = Grid.compact
, element = []
, ifFirst = Group.top
, ifLast = Group.bottom
, ifCenter =Group.center
}
style : Style msg
style =
{ row = row
, cardColumn = cardColumn
, column = column
, button = buttonStyle
, primaryButton = simpleButton
, tab = tab
, textInput = textInputStyle
, chipButton = chipButtonStyle
, expansionPanel = expansionPanelStyle
, dialog = dialog
, snackbar = snackbar
, layout = Framework.responsiveLayout
{--\a w ->
Html.div []
[ Html.node "meta"
[ Attributes.attribute "name" "viewport"
, Attributes.attribute "content" "width=device-width, initial-scale=1.0"
]
[]
, Element.layoutWith
{options = (Element.focusStyle
{ borderColor = Nothing
, backgroundColor = Nothing
, shadow = Nothing
}
|> List.singleton)
}
(Framework.layoutAttributes ++ a) <| w
]--}
, header =
Framework.container
++ Color.dark
++ [ Element.padding <| 0
, Element.height <| Element.px <| 42
]
, menuButton = menuButton
, sheetButton = sheetButton
, menuTabButton = menuTabButton
, sheet =
Color.light ++ [ Element.width <| Element.maximum 256 <| Element.fill ]
, menuIcon =
Icons.menu |> Element.html |> Element.el []
, moreVerticalIcon =
Icons.moreVertical |> Element.html |> Element.el []
, spacing = 8
, title = Heading.h2
, searchIcon =
Icons.search |> Element.html |> Element.el []
, search =
Color.simple
++ Card.large
++ [ Font.color <| Element.rgb255 0 0 0
, Element.padding 6
, Element.centerY
, Element.alignRight
]
, searchFill =
Color.light ++ Group.center
}

View File

@ -0,0 +1,142 @@
module Data.Style.Template exposing (style)
import Data.Style exposing (Style)
import Element exposing (Attribute,Element)
import Element.Border as Border
import Element.Font as Font
import Element.Background as Background
import Widget.Style exposing (ButtonStyle, DialogStyle, ExpansionPanelStyle,
SnackbarStyle ,TextInputStyle,TabStyle,ColumnStyle,RowStyle)
fontSize : Int
fontSize = 10
box : String -> List (Attribute msg)
box string =
[ Border.width 1
, Background.color <| Element.rgba 1 1 1 0.5
, Element.padding 10
, Element.spacing 10
, Element.above <|
Element.el [Font.size <| fontSize] <|
Element.text string
]
decoration : String -> List (Attribute msg)
decoration string =
[ Element.below <|
Element.el [Font.size <| fontSize] <|
Element.text string
, Background.color <| Element.rgb 0.66 0.66 0.66
]
icon : String -> Element msg
icon string =
Element.none
|> Element.el
[ Element.width <| Element.px 12
, Element.height <| Element.px 12
, Border.rounded 6
, Border.width 1
, Element.above <|
Element.el [Font.size <| fontSize] <|
Element.text string
]
button : String -> ButtonStyle msg
button string =
{ container = box <| string ++ ":container"
, labelRow = box <| string ++ ":labelRow"
, ifDisabled = decoration <| string ++ ":ifDisabled"
, ifActive = decoration <| string ++ ":ifActive"
}
snackbar : String -> SnackbarStyle msg
snackbar string =
{ containerRow = box <| string ++ ":containerRow"
, button = button <| string ++ ":button"
, text = box <| string ++ ":text"
}
dialog : String -> DialogStyle msg
dialog string =
{ containerColumn = box <| string ++ ":containerColumn"
, title = box <| string ++ ":title"
, buttonRow = box <| string ++ ":buttonRow"
, acceptButton = button <| string ++ ":acceptButton"
, dismissButton = button <| string ++ ":dismissButton"
}
expansionPanel : String -> ExpansionPanelStyle msg
expansionPanel string =
{ containerColumn = box <| string ++ ":containerColumn"
, panelRow = box <| string ++ ":panelRow"
, labelRow = box <| string ++ ":labelRow"
, content = box <| string ++ ":content"
, expandIcon = icon <| string ++ ":expandIcon"
, collapseIcon = icon <| string ++ ":collapseIcon"
}
textInput : String -> TextInputStyle msg
textInput string =
{ chipButton = button <| string ++ ":chipButton"
, chipsRow = box <| string ++ ":chipsRow"
, containerRow = box <| string ++ ":containerRow"
, input = box <| string ++ ":input"
}
tab : String -> TabStyle msg
tab string =
{ button = button <| string ++ ":button"
, optionRow = box <| string ++ ":optionRow"
, containerColumn = box <| string ++ ":containerColumn"
, content = box <| string ++ ":content"
}
row : String -> RowStyle msg
row string =
{ containerRow = box <| string ++ ":containerRow"
, element = box <| string ++ ":element"
, ifFirst = box <| string ++ ":ifFirst"
, ifLast = box <| string ++ ":ifLast"
, ifCenter = box <| string ++ ":ifCenter"
}
column : String -> ColumnStyle msg
column string =
{ containerColumn = box <| string ++ ":containerColumn"
, element = box <| string ++ ":element"
, ifFirst = box <| string ++ ":ifFirst"
, ifLast = box <| string ++ ":ifLast"
, ifCenter = box <| string ++ ":ifCenter"
}
style :Style msg
style =
{ row = row <| "row"
, cardColumn = column <| "cardRow"
, column = column <| "column"
, button = button <| "button"
, primaryButton = button <| "primaryButton"
, tab = tab <| "tab"
, textInput = textInput <| "textInput"
, chipButton = button <| "chipButton"
, expansionPanel = expansionPanel "expansionPanel"
, dialog = dialog "dialog"
, snackbar = snackbar "snackbar"
, layout = Element.layout
, header = box "header"
, menuButton = button "menuButton"
, sheetButton = button "sheetButton"
, menuTabButton = button "menuTabButton"
, sheet = box "sheet"
, menuIcon = icon "menuIcon"
, moreVerticalIcon = icon "moreVerticalIcon"
, spacing = 8
, title = box "title"
, searchIcon = icon "searchIcon"
, search = box "search"
, searchFill = box "searchFill"
}

View File

@ -0,0 +1,17 @@
module Data.Theme exposing (Theme(..),toStyle)
import Data.Style exposing (Style)
import Data.Style.ElmUiFramework
import Data.Style.Template
type Theme =
ElmUiFramework
| Template
toStyle : Theme -> Style msg
toStyle theme =
case theme of
ElmUiFramework ->
Data.Style.ElmUiFramework.style
Template ->
Data.Style.Template.style

View File

@ -1,14 +1,15 @@
module Example exposing (main) module Example exposing (main)
import Array
import Browser import Browser
import Browser.Dom as Dom exposing (Viewport) 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 Data.Section as Section exposing (Section(..))
import Element exposing (DeviceClass(..), Element,Attribute) import Element exposing (Attribute, DeviceClass(..), Element)
import Element.Input as Input
import Element.Font as Font
import Element.Border as Border import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
import Framework import Framework
import Framework.Button as Button import Framework.Button as Button
import Framework.Card as Card import Framework.Card as Card
@ -21,25 +22,24 @@ import Framework.Tag as Tag
import Html exposing (Html) import Html exposing (Html)
import Html.Attributes as Attributes import Html.Attributes as Attributes
import Icons import Icons
import Layout exposing (Part, Layout) import Layout exposing (Layout, Part)
import Data.Style exposing (style)
import Reusable import Reusable
import Set exposing (Set) import Set exposing (Set)
import Stateless import Stateless
import Task import Task
import Time import Time
import Widget import Widget
import Widget.Style exposing (ButtonStyle)
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.Style exposing (ButtonStyle)
import Data.Section as Section exposing (Section(..)) import Data.Style as Style exposing (Style)
import Array import Data.Theme as Theme exposing (Theme(..))
type alias LoadedModel = type alias LoadedModel =
{ 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 LoadedMsg , layout : Layout LoadedMsg
@ -50,6 +50,7 @@ type alias LoadedModel =
, current : String , current : String
, remaining : Int , remaining : Int
} }
, theme : Theme
} }
@ -61,16 +62,16 @@ type Model
type LoadedMsg type LoadedMsg
= StatelessSpecific Stateless.Msg = StatelessSpecific Stateless.Msg
| ReusableSpecific Reusable.Msg | ReusableSpecific Reusable.Msg
| ComponentSpecific Component.Msg
| UpdateScrollingNav (ScrollingNav.Model Section -> ScrollingNav.Model Section) | UpdateScrollingNav (ScrollingNav.Model Section -> ScrollingNav.Model Section)
| TimePassed Int | TimePassed Int
| AddSnackbar (String,Bool) | AddSnackbar ( String, Bool )
| ToggleDialog Bool | ToggleDialog Bool
| ChangedSidebar (Maybe Part) | ChangedSidebar (Maybe Part)
| Resized { width : Int, height : Int } | Resized { width : Int, height : Int }
| Load String | Load String
| JumpTo Section | JumpTo Section
| ChangedSearch String | ChangedSearch String
| SetTheme Theme
| Idle | Idle
@ -87,16 +88,17 @@ initialModel { viewport } =
{ toString = Section.toString { toString = Section.toString
, fromString = Section.fromString , fromString = Section.fromString
, arrangement = Section.asList , arrangement = Section.asList
, toMsg = \result -> , toMsg =
\result ->
case result of case result of
Ok fun -> Ok fun ->
UpdateScrollingNav fun UpdateScrollingNav fun
Err _ -> Err _ ->
Idle Idle
} }
in in
( { component = Component.init ( { stateless = Stateless.init
, stateless = Stateless.init
, reusable = Reusable.init , reusable = Reusable.init
, scrollingNav = scrollingNav , scrollingNav = scrollingNav
, layout = Layout.init , layout = Layout.init
@ -110,6 +112,7 @@ initialModel { viewport } =
, current = "" , current = ""
, remaining = 0 , remaining = 0
} }
, theme = ElmUiFramework
} }
, cmd , cmd
) )
@ -124,11 +127,17 @@ init () =
view : Model -> Html Msg view : Model -> Html Msg
view model = view model =
case model of case model of
Loading -> Loading ->
Element.none |> Framework.responsiveLayout [] Element.none |> Framework.responsiveLayout []
Loaded m -> Loaded m ->
let
style : Style msg
style =
Theme.toStyle m.theme
in
Html.map LoadedSpecific <| Html.map LoadedSpecific <|
Layout.view [] Layout.view []
{ dialog = { dialog =
@ -139,11 +148,13 @@ view model =
|> List.singleton |> List.singleton
|> Element.paragraph [] |> Element.paragraph []
, title = Just "Dialog" , title = Just "Dialog"
, accept = Just , accept =
Just
{ text = "Ok" { text = "Ok"
, onPress = Just <| ToggleDialog False , onPress = Just <| ToggleDialog False
} }
, dismiss = Just , dismiss =
Just
{ text = "Dismiss" { text = "Dismiss"
, onPress = Just <| ToggleDialog False , onPress = Just <| ToggleDialog False
} }
@ -158,57 +169,55 @@ view model =
, [ m.scrollingNav , [ m.scrollingNav
|> ScrollingNav.view |> ScrollingNav.view
(\section -> (\section ->
( case section of (case section of
ComponentViews ->
m.component
|> Component.view ComponentSpecific
ReusableViews -> ReusableViews ->
Reusable.view Reusable.view m.theme
{ addSnackbar = AddSnackbar { addSnackbar = AddSnackbar
, model = m.reusable , model = m.reusable
, msgMapper = ReusableSpecific , msgMapper = ReusableSpecific
} }
StatelessViews -> StatelessViews ->
Stateless.view Stateless.view m.theme
{ msgMapper = StatelessSpecific { msgMapper = StatelessSpecific
, showDialog = ToggleDialog True , showDialog = ToggleDialog True
, changedSheet = ChangedSidebar , changedSheet = ChangedSidebar
} }
m.stateless m.stateless
) |> (\{title,description,items} -> )
|> (\{ title, description, items } ->
[ Element.el Heading.h2 <| Element.text <| title [ Element.el Heading.h2 <| Element.text <| title
, if m.search.current == "" then , if m.search.current == "" then
description description
|> Element.text |> Element.text
|> List.singleton |> List.singleton
|> Element.paragraph [] |> Element.paragraph []
else Element.none
else
Element.none
, items , items
|> (if m.search.current /= "" then |> (if m.search.current /= "" then
List.filter List.filter
( Tuple.first (\(a,_,_) ->
>> String.toLower a
>> String.contains (m.search.current |> String.toLower) |> String.toLower
|> String.contains (m.search.current |> String.toLower)
) )
else else
identity) identity
)
|> List.map |> List.map
(\(name,elem) -> (\( name, elem, more ) ->
[ Element.text name [ Element.text name
|> Element.el Heading.h3 |> Element.el Heading.h3
, elem , elem
, more
] ]
|> Element.column |> Widget.column style.cardColumn
(Grid.simple
++ Card.large
++ [Element.height <| Element.fill])
) )
|> Element.wrappedRow |> Element.wrappedRow
(Grid.simple ++ [Element.height <| Element.shrink]) (Grid.simple ++ [ Element.height <| Element.shrink ])
] ]
|> Element.column (Grid.section ++ [ Element.centerX ]) |> Element.column (Grid.section ++ [ Element.centerX ])
) )
@ -217,7 +226,7 @@ view model =
|> Element.column Framework.container |> Element.column Framework.container
] ]
|> Element.column Grid.compact |> Element.column Grid.compact
, style = style , style =style
, layout = m.layout , layout = m.layout
, window = m.window , window = m.window
, menu = , menu =
@ -232,23 +241,37 @@ view model =
, 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/"
, text = "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"
, text = "Github" , text = "Github"
, icon = Icons.github|> Element.html |> Element.el [] , icon = Icons.github |> Element.html |> Element.el []
}
, { onPress = if m.theme /= ElmUiFramework then
Just <| SetTheme <| ElmUiFramework
else
Nothing
, text = "Elm-Ui-Framework Theme"
, icon = Icons.penTool |> Element.html |> Element.el []
}
, { onPress = if m.theme /= Template then
Just <| SetTheme <| Template
else
Nothing
, text = "Template Theme"
, icon = Icons.penTool |> Element.html |> Element.el []
} }
, { onPress = Nothing , { onPress = Nothing
, text = "Placeholder" , text = "Placeholder"
, icon = Icons.circle|> Element.html |> Element.el [] , icon = Icons.circle |> Element.html |> Element.el []
} }
, { onPress = Nothing , { onPress = Nothing
, text = "Placeholder" , text = "Placeholder"
, icon = Icons.triangle|> Element.html |> Element.el [] , icon = Icons.triangle |> Element.html |> Element.el []
} }
, { onPress = Nothing , { onPress = Nothing
, text = "Placeholder" , text = "Placeholder"
, icon = Icons.square|> Element.html |> Element.el [] , icon = Icons.square |> Element.html |> Element.el []
} }
] ]
, onChangedSidebar = ChangedSidebar , onChangedSidebar = ChangedSidebar
@ -268,17 +291,6 @@ view model =
updateLoaded : LoadedMsg -> LoadedModel -> ( LoadedModel, Cmd LoadedMsg ) updateLoaded : LoadedMsg -> LoadedModel -> ( LoadedModel, Cmd LoadedMsg )
updateLoaded msg model = updateLoaded msg model =
case msg of case msg of
ComponentSpecific m ->
model.component
|> Component.update m
|> Tuple.mapBoth
(\component ->
{ model
| component = component
}
)
(Cmd.map ComponentSpecific)
ReusableSpecific m -> ReusableSpecific m ->
( model.reusable ( model.reusable
|> Reusable.update m |> Reusable.update m
@ -302,13 +314,14 @@ updateLoaded msg model =
(Cmd.map StatelessSpecific) (Cmd.map StatelessSpecific)
UpdateScrollingNav fun -> UpdateScrollingNav fun ->
( { model | scrollingNav = model.scrollingNav |> fun} ( { model | scrollingNav = model.scrollingNav |> fun }
, Cmd.none , Cmd.none
) )
TimePassed int -> TimePassed int ->
let let
search = model.search search =
model.search
in in
( { model ( { model
| layout = model.layout |> Layout.timePassed int | layout = model.layout |> Layout.timePassed int
@ -319,10 +332,12 @@ updateLoaded msg model =
| current = search.raw | current = search.raw
, remaining = 0 , remaining = 0
} }
else else
{ search { search
| remaining = search.remaining - int | remaining = search.remaining - int
} }
else else
model.search model.search
} }
@ -330,17 +345,21 @@ updateLoaded msg model =
|> Task.perform UpdateScrollingNav |> Task.perform UpdateScrollingNav
) )
AddSnackbar (string,bool) -> AddSnackbar ( string, bool ) ->
( { model ( { model
| layout = model.layout | layout =
model.layout
|> Layout.queueMessage |> Layout.queueMessage
{ text = string { text = string
, button = if bool then , button =
if bool then
Just Just
{ text = "Add" { text = "Add"
, onPress = Just <| , onPress =
(AddSnackbar ("This is another message", False)) Just <|
AddSnackbar ( "This is another message", False )
} }
else else
Nothing Nothing
} }
@ -364,7 +383,7 @@ updateLoaded msg model =
) )
Load string -> Load string ->
( model, Navigation.load string) ( model, Navigation.load string )
JumpTo section -> JumpTo section ->
( model ( model
@ -377,9 +396,11 @@ updateLoaded msg model =
ChangedSearch string -> ChangedSearch string ->
let let
search = model.search search =
model.search
in in
( { model | search = ( { model
| search =
{ search { search
| raw = string | raw = string
, remaining = 300 , remaining = 300
@ -388,8 +409,13 @@ updateLoaded msg model =
, Cmd.none , Cmd.none
) )
SetTheme theme ->
( { model | theme = theme }
, Cmd.none
)
Idle -> Idle ->
( model , Cmd.none) ( model, Cmd.none )
update : Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )

View File

@ -1,17 +1,19 @@
module Icons exposing module Icons exposing
( book ( book
, chevronDown
, chevronLeft
, chevronRight
, circle
, github , github
, menu , menu
, moreVertical , moreVertical
, circle , repeat
, triangle
, square
, search , search
, slash , slash
, repeat , square
, chevronDown , triangle
, chevronRight , chevronUp
, chevronLeft , penTool
) )
import Html exposing (Html) import Html exposing (Html)
@ -33,6 +35,7 @@ svgFeatherIcon className =
, width "16" , width "16"
] ]
chevronDown : Html msg chevronDown : Html msg
chevronDown = chevronDown =
svgFeatherIcon "chevron-down" svgFeatherIcon "chevron-down"
@ -45,12 +48,20 @@ chevronRight =
svgFeatherIcon "chevron-right" svgFeatherIcon "chevron-right"
[ Svg.polyline [ points "9 18 15 12 9 6" ] [] [ Svg.polyline [ points "9 18 15 12 9 6" ] []
] ]
chevronLeft : Html msg chevronLeft : Html msg
chevronLeft = chevronLeft =
svgFeatherIcon "chevron-left" svgFeatherIcon "chevron-left"
[ Svg.polyline [ points "15 18 9 12 15 6" ] [] [ Svg.polyline [ points "15 18 9 12 15 6" ] []
] ]
chevronUp : Html msg
chevronUp =
svgFeatherIcon "chevron-up"
[ Svg.polyline [ points "18 15 12 9 6 15" ] []
]
repeat : Html msg repeat : Html msg
repeat = repeat =
svgFeatherIcon "repeat" svgFeatherIcon "repeat"
@ -60,6 +71,14 @@ repeat =
, Svg.path [ d "M21 13v2a4 4 0 0 1-4 4H3" ] [] , Svg.path [ d "M21 13v2a4 4 0 0 1-4 4H3" ] []
] ]
penTool : Html msg
penTool =
svgFeatherIcon "pen-tool"
[ Svg.path [ d "M12 19l7-7 3 3-7 7-3-3z" ] []
, Svg.path [ d "M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z" ] []
, Svg.path [ d "M2 2l7.586 7.586" ] []
, Svg.circle [ cx "11", cy "11", r "2" ] []
]
book : Html msg book : Html msg
book = book =
@ -84,6 +103,7 @@ menu =
, Svg.line [ x1 "3", y1 "18", x2 "21", y2 "18" ] [] , Svg.line [ x1 "3", y1 "18", x2 "21", y2 "18" ] []
] ]
moreVertical : Html msg moreVertical : Html msg
moreVertical = moreVertical =
svgFeatherIcon "more-vertical" svgFeatherIcon "more-vertical"
@ -92,12 +112,14 @@ moreVertical =
, Svg.circle [ cx "12", cy "19", r "1" ] [] , Svg.circle [ cx "12", cy "19", r "1" ] []
] ]
circle : Html msg circle : Html msg
circle = circle =
svgFeatherIcon "circle" svgFeatherIcon "circle"
[ Svg.circle [ cx "12", cy "12", r "10" ] [] [ Svg.circle [ cx "12", cy "12", r "10" ] []
] ]
slash : Html msg slash : Html msg
slash = slash =
svgFeatherIcon "slash" svgFeatherIcon "slash"
@ -105,18 +127,21 @@ slash =
, Svg.line [ x1 "4.93", y1 "4.93", x2 "19.07", y2 "19.07" ] [] , 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"
[ Svg.path [ d "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" ] [] [ Svg.path [ d "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" ] []
] ]
square : Html msg square : Html msg
square = square =
svgFeatherIcon "square" svgFeatherIcon "square"
[ Svg.rect [ Svg.Attributes.x "3", y "3", width "18", height "18", rx "2", ry "2" ] [] [ Svg.rect [ Svg.Attributes.x "3", y "3", width "18", height "18", rx "2", ry "2" ] []
] ]
search : Html msg search : Html msg
search = search =
svgFeatherIcon "search" svgFeatherIcon "search"

View File

@ -20,12 +20,11 @@ import Html.Attributes as Attributes
import Set exposing (Set) import Set exposing (Set)
import Time import Time
import Widget import Widget
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.SortTable as SortTable import Widget.SortTable as SortTable
import Widget.ValidatedInput as ValidatedInput import Data.Style exposing (Style)
import Data.Theme as Theme exposing (Theme)
type alias Model = type alias Model =
SortTable.Model SortTable.Model
@ -54,36 +53,37 @@ init =
SortTable.sortBy { title = "Name", asc = True } SortTable.sortBy { title = "Name", asc = True }
snackbar : ((String,Bool) -> msg) -> (String,Element msg) snackbar : Style msg -> (( String, Bool ) -> msg) -> ( String, Element msg,Element msg )
snackbar addSnackbar = snackbar style addSnackbar =
( "Snackbar" ( "Snackbar"
, [Input.button Button.simple , [ Widget.button style.button
{ onPress = Just <| addSnackbar <| { onPress =
("This is a notification. It will disappear after 10 seconds." Just <|
addSnackbar <|
( "This is a notification. It will disappear after 10 seconds."
, False , False
) )
, label = , text = "Add Notification"
"Add Notification" , icon =Element.none
|> Element.text
|> List.singleton
|> Element.paragraph []
} }
, Input.button Button.simple , Widget.button style.button
{ onPress = Just <| addSnackbar <| { onPress =
("You can add another notification if you want." Just <|
addSnackbar <|
( "You can add another notification if you want."
, True , True
) )
, label = , text = "Add Notification with Action"
"Add Notification with Action" , icon = Element.none
|> Element.text
|> List.singleton
|> Element.paragraph []
} }
] |> Element.column Grid.simple ]
|> Element.column Grid.simple
, Element.none
) )
sortTable : SortTable.Model -> (String,Element Msg)
sortTable model = sortTable : Style Msg -> SortTable.Model -> ( String, Element Msg,Element Msg )
sortTable style model =
( "Sort Table" ( "Sort Table"
, SortTable.view , SortTable.view
{ content = { content =
@ -152,31 +152,41 @@ sortTable model =
} }
) )
|> Element.table Grid.simple |> Element.table Grid.simple
, Element.none
) )
scrollingNavCard : (String , Element msg )
scrollingNavCard = scrollingNavCard : Style msg -> ( String, Element msg, Element msg )
("Scrolling Nav" scrollingNavCard style =
( "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.none
) )
view : view :
{ addSnackbar : (String,Bool) -> msg Theme ->
{ addSnackbar : ( String, Bool ) -> msg
, msgMapper : Msg -> msg , msgMapper : Msg -> msg
, model : Model , model : Model
} }
-> { title : String ->
{ title : String
, description : String , description : String
, items : List (String,Element msg) , items : List ( String, Element msg,Element msg )
} }
view { addSnackbar, msgMapper, model } = view theme { addSnackbar, msgMapper, model } =
let
style = Theme.toStyle theme
in
{ title = "Reusable Views" { title = "Reusable Views"
, description = "Reusable views have an internal state but no update function. You will need to do some wiring, but nothing complicated." , description = "Reusable views have an internal state but no update function. You will need to do some wiring, but nothing complicated."
, items = , items =
[ snackbar addSnackbar [ snackbar style addSnackbar
, sortTable model |> Tuple.mapSecond (Element.map msgMapper) , sortTable style model |> \(a,b,c) ->
, scrollingNavCard (a,b |> Element.map msgMapper,c |> Element.map msgMapper)
, scrollingNavCard style
] ]
} }

View File

@ -1,9 +1,11 @@
module Stateless exposing (Model, Msg, init, update, view) module Stateless exposing (Model, Msg, init, update, view)
import Array exposing (Array) import Array exposing (Array)
import Data.Style exposing (Style)
import Element exposing (Element) import Element exposing (Element)
import Element.Background as Background import Element.Background as Background
import Element.Border as Border import Element.Border as Border
import Element.Font as Font
import Element.Input as Input import Element.Input as Input
import Framework.Button as Button import Framework.Button as Button
import Framework.Card as Card import Framework.Card as Card
@ -16,19 +18,18 @@ import Framework.Tag as Tag
import Heroicons.Solid as Heroicons 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 Widget.Style exposing (ButtonStyle)
import Layout exposing (Part(..))
import Icons import Icons
import Layout exposing (Part(..))
import Set exposing (Set)
import Widget import Widget
import Element.Font as Font import Widget.Style exposing (ButtonStyle)
import Data.Style exposing (style) import Data.Theme as Theme exposing (Theme)
type alias Model = type alias Model =
{ selected : Maybe Int { selected : Maybe Int
, multiSelected : Set Int , multiSelected : Set Int
, chipTextInput : Set String , chipTextInput : Set String
, isCollapsed : Bool , isExpanded : Bool
, carousel : Int , carousel : Int
, tab : Maybe Int , tab : Maybe Int
, button : Bool , button : Bool
@ -53,7 +54,7 @@ init =
{ selected = Nothing { selected = Nothing
, multiSelected = Set.empty , multiSelected = Set.empty
, chipTextInput = Set.empty , chipTextInput = Set.empty
, isCollapsed = False , isExpanded = False
, carousel = 0 , carousel = 0
, tab = Just 1 , tab = Just 1
, button = True , button = True
@ -87,7 +88,7 @@ update msg model =
ToggleCollapsable bool -> ToggleCollapsable bool ->
( { model ( { model
| isCollapsed = bool | isExpanded = bool
} }
, Cmd.none , Cmd.none
) )
@ -95,11 +96,13 @@ update msg model =
ToggleTextInputChip string -> ToggleTextInputChip string ->
( { model ( { model
| chipTextInput = | chipTextInput =
model.chipTextInput |> model.chipTextInput
if model.chipTextInput |> Set.member string then |> (if model.chipTextInput |> Set.member string then
Set.remove string Set.remove string
else else
Set.insert string Set.insert string
)
} }
, Cmd.none , Cmd.none
) )
@ -122,22 +125,24 @@ update msg model =
( { model | button = bool }, Cmd.none ) ( { model | button = bool }, Cmd.none )
SetTextInput string -> SetTextInput string ->
( {model | textInput = string },Cmd.none) ( { model | textInput = string }, Cmd.none )
Idle -> Idle ->
( model, Cmd.none) ( model, Cmd.none )
select : Model -> (String,Element Msg) select : Style Msg -> Model -> ( String, Element Msg,Element Msg )
select model = select style model =
let let
buttonStyle = style.button buttonStyle =
style.button
in in
( "Select" ( "Select"
, { selected = model.selected , { selected = model.selected
, options = , options =
[ 1, 2, 42 ] [ 1, 2, 42 ]
|> List.map (\int -> |> List.map
(\int ->
{ text = String.fromInt int { text = String.fromInt int
, icon = Element.none , icon = Element.none
} }
@ -145,104 +150,74 @@ select model =
, onSelect = ChangedSelected >> Just , onSelect = ChangedSelected >> Just
} }
|> Widget.select |> Widget.select
|> List.indexedMap |> Widget.buttonRow
(\i -> { list = style.row
Widget.selectButton , button = style.button
{ buttonStyle
| container = buttonStyle.container
++ (if i == 0 then
Group.left
else if i == 2 then
Group.right
else
Group.center
)
} }
) , Element.none
|> Element.row Grid.compact
) )
multiSelect : Model -> (String,Element Msg) multiSelect : Style Msg -> Model -> ( String, Element Msg, Element Msg )
multiSelect model = multiSelect style model =
let let
buttonStyle = style.button buttonStyle =
style.button
in in
( "Multi Select" ( "Multi Select"
, { selected = model.multiSelected , { selected = model.multiSelected
, options = , options =
[ 1, 2, 42 ] [ 1, 2, 42 ]
|> List.map (\int -> |> List.map
(\int ->
{ text = String.fromInt int { text = String.fromInt int
, icon = Element.none , icon = Element.none
}) }
)
, onSelect = ChangedMultiSelected >> Just , onSelect = ChangedMultiSelected >> Just
} }
|> Widget.multiSelect |> Widget.multiSelect
|> List.indexedMap |> Widget.buttonRow
(\i -> { list = style.row
Widget.selectButton , button = style.button
{ buttonStyle
| container = buttonStyle.container
++ (if i == 0 then
Group.left
else if i == 2 then
Group.right
else
Group.center
)
} }
) , Element.none
|> Element.row Grid.compact
) )
collapsable : Model -> (String,Element Msg) expansionPanel : Style Msg -> Model -> (String,Element Msg,Element Msg)
collapsable model = expansionPanel style model =
( "Collapsable" ( "Expansion Panel"
, { onToggle = ToggleCollapsable , { onToggle = ToggleCollapsable
, isCollapsed = model.isCollapsed , isExpanded = model.isExpanded
, label = , icon = Element.none
Element.row (Grid.simple ++ [Element.width<| Element.fill]) , text = "Title"
[ if model.isCollapsed then
Icons.chevronRight |> Element.html |> Element.el []
else
Icons.chevronDown |> Element.html |> Element.el []
, Element.text <| "Title"
]
, content = Element.text <| "Hello World" , content = Element.text <| "Hello World"
} }
|>Widget.collapsable |>Widget.expansionPanel style.expansionPanel
{ containerColumn = Card.simple ++ Grid.simple , Element.none
++ [ Element.padding 8 ]
, button = []
}
) )
tab : Model -> (String,Element Msg)
tab model =
tab : Style Msg -> Model -> ( String, Element Msg, Element Msg )
tab style model =
( "Tab" ( "Tab"
, Widget.tab , Widget.tab style.tab
{ button = style.tabButton { tabs =
, optionRow = Grid.simple
, containerColumn = Grid.compact
}
{ selected = model.tab { selected = model.tab
, options = [ 1, 2, 3 ] , options =
|> List.map (\int -> [ 1, 2, 3 ]
|> List.map
(\int ->
{ text = "Tab " ++ (int |> String.fromInt) { text = "Tab " ++ (int |> String.fromInt)
, icon = Element.none , icon = Element.none
} }
) )
, onSelect = ChangedTab >> Just , onSelect = ChangedTab >> Just
} <| }
(\selected -> , content =
\selected ->
(case selected of (case selected of
Just 0 -> Just 0 ->
"This is Tab 1" "This is Tab 1"
@ -257,51 +232,63 @@ tab model =
"Please select a tab" "Please select a tab"
) )
|> Element.text |> Element.text
|> Element.el (Card.small ++ Group.bottom) }
) , Element.none
) )
modal : (Maybe Part -> msg) -> Model -> (String,Element msg)
modal changedSheet model = modal : Style msg -> (Maybe Part -> msg) -> Model -> ( String, Element msg,Element msg )
modal style changedSheet model =
( "Modal" ( "Modal"
, [ Input.button Button.simple , [ Widget.button style.button
{ onPress = Just <| changedSheet <| Just LeftSheet { onPress = Just <| changedSheet <| Just LeftSheet
, label = Element.text <| "show left sheet" , text = "show left sheet"
, icon = Element.none
} }
, Input.button Button.simple , Widget.button style.button
{ onPress = Just <| changedSheet <| Just RightSheet { onPress = Just <| changedSheet <| Just RightSheet
, label = Element.text <| "show right sheet" , text = "show right sheet"
, icon = Element.none
} }
] |> Element.column Grid.simple ]
|> Element.column Grid.simple
,Element.none
) )
dialog : msg -> Model -> (String,Element msg)
dialog showDialog model = dialog : Style msg -> msg -> Model -> ( String, Element msg, Element msg )
dialog style showDialog model =
( "Dialog" ( "Dialog"
, Input.button Button.simple , Widget.button style.button
{ onPress = Just showDialog { onPress = Just showDialog
, label = Element.text <| "Show dialog" , text = "Show dialog"
, icon = Element.none
} }
, Element.none
) )
carousel : Model -> (String,Element Msg)
carousel model = carousel : Style Msg -> Model -> ( String, Element Msg, Element Msg )
carousel style model =
( "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
, label = , label =
\c -> \c ->
[ Element.el [Element.centerY] <| [ Element.el [ Element.centerY ] <|
Widget.iconButton style.button Widget.iconButton style.button
{ onPress = { onPress =
model.carousel - 1 model.carousel
|> \i -> - 1
|> (\i ->
if i < 0 then if i < 0 then
Nothing Nothing
else else
SetCarousel i SetCarousel i
|> Just |> Just
)
, icon = , icon =
Icons.chevronLeft Icons.chevronLeft
|> Element.html |> Element.html
@ -319,13 +306,17 @@ carousel model =
Element.none Element.none
, Element.el [ Element.centerY ] <| , Element.el [ Element.centerY ] <|
Widget.iconButton style.button Widget.iconButton style.button
{ onPress = model.carousel + 1 { onPress =
|> \i -> model.carousel
+ 1
|> (\i ->
if i >= 4 then if i >= 4 then
Nothing Nothing
else else
SetCarousel i SetCarousel i
|> Just |> Just
)
, icon = , icon =
Icons.chevronRight Icons.chevronRight
|> Element.html |> Element.html
@ -335,16 +326,20 @@ carousel model =
] ]
|> Element.row (Grid.simple ++ [ Element.centerX, Element.width <| Element.shrink ]) |> Element.row (Grid.simple ++ [ Element.centerX, Element.width <| Element.shrink ])
} }
, Element.none
) )
iconButton : Model -> (String,Element Msg)
iconButton model = iconButton : Style Msg -> Model -> ( String, Element Msg, Element Msg )
iconButton style model =
( "Icon Button" ( "Icon Button"
, [ [ Widget.button style.primaryButton , [ Widget.button style.primaryButton
{ text = "disable me" { text = "disable me"
, icon = Icons.slash |> Element.html |> Element.el [] , onPress = , icon = Icons.slash |> Element.html |> Element.el []
, onPress =
if model.button then if model.button then
Just <| ToggleButton False Just <| ToggleButton False
else else
Nothing Nothing
} }
@ -355,21 +350,54 @@ iconButton model =
} }
] ]
|> Element.row Grid.simple |> Element.row Grid.simple
, Element.column Grid.simple
[ Element.row Grid.spacedEvenly
[ "Button"
|> Element.text
, Widget.button style.button , Widget.button style.button
{ text = "reset button" { text = "reset"
, icon = Element.none , icon = Icons.repeat |> Element.html |> Element.el []
, onPress = Just <| ToggleButton True , onPress = Just <| ToggleButton True
} }
] |> Element.column Grid.simple ]
, Element.row Grid.spacedEvenly
[ "Text button"
|> Element.text
, Widget.textButton style.button
{ text = "reset"
, onPress = Just <| ToggleButton True
}
]
, Element.row Grid.spacedEvenly
[ "Button"
|> Element.text
, Widget.iconButton style.button
{ text = "reset"
, icon = Icons.repeat |> Element.html |> Element.el []
, onPress = Just <| ToggleButton True
}
]
, Element.row Grid.spacedEvenly
[ "Disabled button"
|> Element.text
, Widget.button style.button
{ text = "reset"
, icon = Icons.repeat |> Element.html |> Element.el []
, onPress = Nothing
}
]
]
) )
textInput : Model -> (String,Element Msg)
textInput model = textInput : Style Msg -> Model -> ( String, Element Msg, Element Msg )
textInput style model =
( "Chip Text Input" ( "Chip Text Input"
, [ { chips = , [ { chips =
model.chipTextInput model.chipTextInput
|> Set.toList |> Set.toList
|> List.map (\string -> |> List.map
(\string ->
{ icon = Element.none { icon = Element.none
, text = string , text = string
, onPress = , onPress =
@ -386,45 +414,61 @@ textInput model =
|> Widget.textInput style.textInput |> Widget.textInput style.textInput
, model.chipTextInput , model.chipTextInput
|> Set.diff |> Set.diff
(["A","B","C"] ([ "A", "B", "C" ]
|> Set.fromList |> Set.fromList
) )
|> Set.toList |> Set.toList
|> List.map |> List.map
(\string -> (\string ->
Input.button (Button.simple ++ Tag.simple) Widget.button style.textInput.chipButton
{ onPress = { onPress =
string string
|> ToggleTextInputChip |> ToggleTextInputChip
|> Just |> Just
, label = Element.text string , text = string
, icon = Element.none
} }
) )
|> Element.wrappedRow [ Element.spacing 10 ] |> Element.wrappedRow [ Element.spacing 10 ]
] |> Element.column Grid.simple ]
|> Element.column Grid.simple
, Element.none
) )
view : view :
Theme ->
{ msgMapper : Msg -> msg { msgMapper : Msg -> msg
, showDialog : msg , showDialog : msg
, changedSheet : Maybe Part -> msg , changedSheet : Maybe Part -> msg
} -> Model
-> { title : String
, description : String
, items : List (String,Element msg)
} }
view { msgMapper, showDialog, changedSheet } model = -> Model
->
{ title : String
, description : String
, items : List ( String, Element msg, Element msg )
}
view theme { msgMapper, showDialog, changedSheet } model =
let
style = Theme.toStyle theme
map (a,b,c) =
( a
, b |> Element.map msgMapper
, c |> Element.map msgMapper
)
in
{ title = "Stateless Views" { title = "Stateless Views"
, description = "Stateless views are simple functions that view some content. No wiring required." , description = "Stateless views are simple functions that view some content. No wiring required."
, items = , items =
[ iconButton model |> Tuple.mapSecond (Element.map msgMapper) [ iconButton style model |> map
, select model |> Tuple.mapSecond (Element.map msgMapper) , select style model |> map
, multiSelect model |> Tuple.mapSecond (Element.map msgMapper) , multiSelect style model |> map
, collapsable model |> Tuple.mapSecond (Element.map msgMapper) , expansionPanel style model |> map
, modal changedSheet model , modal style changedSheet model
, carousel model |> Tuple.mapSecond (Element.map msgMapper) , carousel style model |> map
, tab model |> Tuple.mapSecond (Element.map msgMapper) , tab style model |> map
, dialog showDialog model , dialog style showDialog model
, textInput model |> Tuple.mapSecond (Element.map msgMapper) , textInput style model |> map
] ]
} }

View File

@ -14,8 +14,8 @@ import Widget.Style exposing (ButtonStyle)
type alias Button msg = type alias Button msg =
{ text : String { text : String
, icon : Element Never
, onPress : Maybe msg , onPress : Maybe msg
, icon : Element Never
} }
@ -30,7 +30,7 @@ iconButton style { onPress, text, icon } =
Input.button Input.button
(style.container (style.container
++ (if onPress == Nothing then ++ (if onPress == Nothing then
style.disabled style.ifDisabled
else else
[] []
@ -60,7 +60,7 @@ button style { onPress, text, icon } =
Input.button Input.button
(style.container (style.container
++ (if onPress == Nothing then ++ (if onPress == Nothing then
style.disabled style.ifDisabled
else else
[] []
@ -68,7 +68,7 @@ button style { onPress, text, icon } =
) )
{ onPress = onPress { onPress = onPress
, label = , label =
Element.row style.label Element.row style.labelRow
[ icon |> Element.map never [ icon |> Element.map never
, Element.text text , Element.text text
] ]

View File

@ -51,10 +51,10 @@ dialog style { title, body, accept, dismiss } =
Nothing Nothing
, content = , content =
Element.column Element.column
(style.containerColumn ([ Element.centerX
++ [ Element.centerX
, Element.centerY , Element.centerY
] ]
++ style.containerColumn
) )
[ title [ title
|> Maybe.map |> Maybe.map
@ -64,22 +64,22 @@ dialog style { title, body, accept, dismiss } =
|> Maybe.withDefault Element.none |> Maybe.withDefault Element.none
, body , body
, Element.row , Element.row
(style.buttonRow ([ Element.alignRight
++ [ Element.alignRight
, Element.width <| Element.shrink , Element.width <| Element.shrink
] ]
++ style.buttonRow
) )
(case ( accept, dismiss ) of (case ( accept, dismiss ) of
( Just acceptButton, Nothing ) -> ( Just acceptButton, Nothing ) ->
acceptButton acceptButton
|> Button.textButton style.accept |> Button.textButton style.acceptButton
|> List.singleton |> List.singleton
( Just acceptButton, Just dismissButton ) -> ( Just acceptButton, Just dismissButton ) ->
[ dismissButton [ dismissButton
|> Button.textButton style.dismiss |> Button.textButton style.dismissButton
, acceptButton , acceptButton
|> Button.textButton style.accept |> Button.textButton style.acceptButton
] ]
_ -> _ ->

View File

@ -0,0 +1,46 @@
module Internal.ExpansionPanel exposing (ExpansionPanel, expansionPanel)
{-| Part of Material Design Lists
-}
import Element exposing (Element)
import Element.Events as Events
import Widget.Style exposing (ExpansionPanelStyle)
type alias ExpansionPanel msg =
{ onToggle : Bool -> msg
, icon : Element Never
, text : String
, content : Element msg
, isExpanded : Bool
}
expansionPanel :
ExpansionPanelStyle msg
-> ExpansionPanel msg
-> Element msg
expansionPanel style model =
Element.column style.containerColumn <|
[ Element.row
((Events.onClick <| model.onToggle <| not model.isExpanded)
:: style.panelRow
)
[ Element.row style.labelRow
[ model.icon |> Element.map never
, model.text |> Element.text
]
, Element.map never <|
if model.isExpanded then
style.collapseIcon
else
style.expandIcon
]
, if model.isExpanded then
Element.el style.content <| model.content
else
Element.none
]

108
src/Internal/List.elm Normal file
View File

@ -0,0 +1,108 @@
module Internal.List exposing (buttonColumn, buttonRow, column, row)
import Element exposing (Attribute, Element)
import Internal.Button exposing (Button)
import Internal.Select as Select
import Widget.Style exposing (ButtonStyle, ColumnStyle, RowStyle)
internal :
{ list
| element : List (Attribute msg)
, ifFirst : List (Attribute msg)
, ifLast : List (Attribute msg)
, ifCenter : List (Attribute msg)
}
-> List (Element msg)
-> List (Element msg)
internal style list =
list
|> List.indexedMap
(\i ->
Element.el <|
style.element
++ (if List.length list == 1 then
[]
else if i == 0 then
style.ifFirst
else if i == (List.length list - 1) then
style.ifLast
else
style.ifCenter
)
)
row : RowStyle msg -> List (Element msg) -> Element msg
row style =
internal style >> Element.row style.containerRow
column : ColumnStyle msg -> List (Element msg) -> Element msg
column style =
internal style >> Element.column style.containerColumn
internalButton :
{ list :
{ list
| element : List (Attribute msg)
, ifFirst : List (Attribute msg)
, ifLast : List (Attribute msg)
, ifCenter : List (Attribute msg)
}
, button : ButtonStyle msg
}
-> List ( Bool, Button msg )
-> List (Element msg)
internalButton style list =
list
|> List.indexedMap
(\i ->
Select.selectButton
{ container =
style.button.container
++ style.list.element
++ (if List.length list == 1 then
[]
else if i == 0 then
style.list.ifFirst
else if i == (List.length list - 1) then
style.list.ifLast
else
style.list.ifCenter
)
, labelRow =
style.button.labelRow
, ifDisabled =
style.button.ifDisabled
, ifActive =
style.button.ifActive
}
)
buttonRow :
{ list : RowStyle msg
, button : ButtonStyle msg
}
-> List ( Bool, Button msg )
-> Element msg
buttonRow style =
internalButton style >> Element.row style.list.containerRow
buttonColumn :
{ list : ColumnStyle msg
, button : ButtonStyle msg
}
-> List ( Bool, Button msg )
-> Element msg
buttonColumn style =
internalButton style >> Element.column style.list.containerColumn

View File

@ -1,4 +1,4 @@
module Internal.Select exposing (multiSelect, select, selectButton) module Internal.Select exposing (MultiSelect, Select, multiSelect, select, selectButton)
import Element exposing (Element) import Element exposing (Element)
import Internal.Button as Button exposing (Button) import Internal.Button as Button exposing (Button)
@ -39,7 +39,7 @@ selectButton style ( selected, b ) =
| container = | container =
style.container style.container
++ (if selected then ++ (if selected then
style.active style.ifActive
else else
[] []

24
src/Internal/Tab.elm Normal file
View File

@ -0,0 +1,24 @@
module Internal.Tab exposing (Tab, tab)
import Element exposing (Element)
import Internal.Select as Select exposing (Select)
import Widget.Style exposing (TabStyle)
type alias Tab msg =
{ tabs : Select msg
, content : Maybe Int -> Element msg
}
tab : TabStyle msg -> Tab msg -> Element msg
tab style { tabs, content } =
[ tabs
|> Select.select
|> List.map (Select.selectButton style.button)
|> Element.row style.optionRow
, tabs.selected
|> content
|> Element.el style.content
]
|> Element.column style.containerColumn

View File

@ -0,0 +1,34 @@
module Internal.TextInput exposing (TextInput, textInput)
import Element exposing (Element)
import Element.Input as Input exposing (Placeholder)
import Internal.Button as Button exposing (Button)
import Widget.Style exposing (TextInputStyle)
type alias TextInput msg =
{ chips : List (Button msg)
, text : String
, placeholder : Maybe (Placeholder msg)
, label : String
, onChange : String -> msg
}
textInput : TextInputStyle msg -> TextInput msg -> Element msg
textInput style { chips, placeholder, label, text, onChange } =
Element.row style.containerRow
[ if chips |> List.isEmpty then
Element.none
else
chips
|> List.map (Button.button style.chipButton)
|> Element.row style.chipsRow
, Input.text style.input
{ onChange = onChange
, text = text
, placeholder = placeholder
, label = Input.labelHidden label
}
]

View File

@ -2,7 +2,9 @@ module Widget exposing
( Button, TextButton, iconButton, textButton, button ( Button, TextButton, iconButton, textButton, button
, Select, MultiSelect, selectButton, select, multiSelect , Select, MultiSelect, selectButton, select, multiSelect
, Dialog, modal, dialog , Dialog, modal, dialog
, TextInputStyle, textInput, collapsable, carousel, tab , ExpansionPanel, expansionPanel
, TextInputStyle, textInput, carousel, tab
, Tab, buttonColumn, buttonRow, column, row
) )
{-| This module contains functions for displaying data. {-| This module contains functions for displaying data.
@ -23,20 +25,28 @@ module Widget exposing
@docs Dialog, modal, dialog @docs Dialog, modal, dialog
# ExpansionPanel
@docs ExpansionPanel, expansionPanel
# Other Widgets # Other Widgets
@docs TextInputStyle, textInput, collapsable, carousel, tab @docs TextInputStyle, textInput, carousel, tab
-} -}
import Array exposing (Array) import Array exposing (Array)
import Element exposing (Attribute, Element) import Element exposing (Attribute, Element)
import Element.Input as Input exposing (Placeholder) import Element.Input exposing (Placeholder)
import Internal.Button as Button import Internal.Button as Button
import Internal.Dialog as Dialog import Internal.Dialog as Dialog
import Internal.ExpansionPanel as ExpansionPanel
import Internal.List as List
import Internal.Select as Select import Internal.Select as Select
import Internal.TextInput as TextInput
import Set exposing (Set) import Set exposing (Set)
import Widget.Style exposing (ButtonStyle, DialogStyle) import Widget.Style exposing (ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, RowStyle, TabStyle)
@ -216,13 +226,44 @@ dialog =
{---------------------------------------------------------- {----------------------------------------------------------
- OTHER STATELESS WIDGETS - DIALOG
----------------------------------------------------------}
type alias ExpansionPanel msg =
{ onToggle : Bool -> msg
, icon : Element Never
, text : String
, expandIcon : Element Never
, collapseIcon : Element Never
, content : Element msg
, isExpanded : Bool
}
expansionPanel :
ExpansionPanelStyle msg
->
{ onToggle : Bool -> msg
, icon : Element Never
, text : String
, content : Element msg
, isExpanded : Bool
}
-> Element msg
expansionPanel =
ExpansionPanel.expansionPanel
{----------------------------------------------------------
- TEXT INPUT
----------------------------------------------------------} ----------------------------------------------------------}
{-| -} {-| -}
type alias TextInputStyle msg = type alias TextInputStyle msg =
{ chip : ButtonStyle msg { chipButton : ButtonStyle msg
, containerRow : List (Attribute msg) , containerRow : List (Attribute msg)
, chipsRow : List (Attribute msg) , chipsRow : List (Attribute msg)
, input : List (Attribute msg) , input : List (Attribute msg)
@ -240,65 +281,75 @@ textInput :
, onChange : String -> msg , onChange : String -> msg
} }
-> Element msg -> Element msg
textInput style { chips, placeholder, label, text, onChange } = textInput =
Element.row style.containerRow TextInput.textInput
[ chips
|> List.map (Button.button style.chip)
|> Element.row style.chipsRow
, Input.text style.input
{ onChange = onChange
, text = text
, placeholder = placeholder
, label = Input.labelHidden label
}
]
{-| Some collapsable content.
-} {----------------------------------------------------------
collapsable : - LIST
{ containerColumn : List (Attribute msg) ----------------------------------------------------------}
, button : List (Attribute msg)
}
-> row : RowStyle msg -> List (Element msg) -> Element msg
{ onToggle : Bool -> msg row =
, isCollapsed : Bool List.row
, label : Element msg
, content : Element msg
column : ColumnStyle msg -> List (Element msg) -> Element msg
column =
List.column
buttonRow :
{ list : RowStyle msg
, button : ButtonStyle msg
} }
-> List ( Bool, Button msg )
-> Element msg -> Element msg
collapsable style { onToggle, isCollapsed, label, content } = buttonRow =
Element.column style.containerColumn <| List.buttonRow
[ Input.button style.button
{ onPress = Just <| onToggle <| not isCollapsed
, label = label
}
]
++ (if isCollapsed then
[]
else
[ content ] buttonColumn :
) { list : ColumnStyle msg
, button : ButtonStyle msg
}
-> List ( Bool, Button msg )
-> Element msg
buttonColumn =
List.buttonColumn
{----------------------------------------------------------
- OTHER STATELESS WIDGETS
----------------------------------------------------------}
type alias Tab msg =
{ tabs : Select msg
, content : Maybe Int -> Element msg
}
{-| Displayes a list of contents in a tab {-| Displayes a list of contents in a tab
-} -}
tab : tab :
{ button : ButtonStyle msg TabStyle msg
, optionRow : List (Attribute msg) ->
, containerColumn : List (Attribute msg) { tabs : Select msg
, content : Maybe Int -> Element msg
} }
-> Select msg
-> (Maybe Int -> Element msg)
-> Element msg -> Element msg
tab style options content = tab style { tabs, content } =
[ options [ tabs
|> select |> select
|> List.map (selectButton style.button) |> List.map (selectButton style.button)
|> Element.row style.optionRow |> Element.row style.optionRow
, options.selected , tabs.selected
|> content |> content
|> Element.el style.content
] ]
|> Element.column style.containerColumn |> Element.column style.containerColumn

View File

@ -1,108 +0,0 @@
module Widget.FilterMultiSelect exposing (Model, Msg(..), init, update, viewInput, viewOptions)
{-|
@docs Model, Msg, init, update, viewInput, viewOptions
-}
import Element.Input exposing (Placeholder)
import Set exposing (Set)
import Widget exposing (Button)
{-| The Model containing the raw value, the selected value and all the possible options.
-}
type alias Model =
{ raw : String
, selected : Set String
, options : Set String
}
{-| The Msg is exposed by design. You can unselect by sending `Selected Nothing`.
-}
type Msg
= ChangedRaw String
| ToggleSelected String
{-| The initial state contains the set of possible options.
-}
init : Set String -> Model
init options =
{ raw = ""
, selected = Set.empty
, options = options
}
{-| Updates the Model
-}
update : Msg -> Model -> Model
update msg model =
case msg of
ChangedRaw string ->
{ model
| raw = string
}
ToggleSelected string ->
if model.selected |> Set.member string then
{ model
| selected = model.selected |> Set.remove string
}
else
{ model
| selected = model.selected |> Set.insert string
, raw = ""
}
{-| A wrapper around Input.text.
-}
viewInput :
Model
->
{ msgMapper : Msg -> msg
, placeholder : Maybe (Placeholder msg)
, label : String
, toChip : String -> Button msg
}
->
{ chips : List (Button msg)
, text : String
, placeholder : Maybe (Placeholder msg)
, label : String
, onChange : String -> msg
}
viewInput model { msgMapper, placeholder, label, toChip } =
{ chips =
model.selected
|> Set.toList
|> List.map toChip
, text = model.raw
, placeholder = placeholder
, label = label
, onChange = ChangedRaw >> msgMapper
}
{-| Returns a List of all options that matches the filter.
-}
viewOptions : Model -> List String
viewOptions { raw, options, selected } =
if raw == "" then
[]
else
options
|> Set.filter (String.toUpper >> String.contains (raw |> String.toUpper))
|> Set.filter
(\string ->
selected
|> Set.member string
|> not
)
|> Set.toList

View File

@ -1,93 +0,0 @@
module Widget.FilterSelect exposing (Model, Msg(..), init, update, viewInput, viewOptions)
{-|
@docs Model, Msg, init, update, viewInput, viewOptions
-}
import Element exposing (Attribute, Element)
import Element.Input as Input exposing (Placeholder)
import Set exposing (Set)
{-| The Model containing the raw value, the selected value and all the possible options.
-}
type alias Model =
{ raw : String
, selected : Maybe String
, options : Set String
}
{-| The Msg is exposed by design. You can unselect by sending `Selected Nothing`.
-}
type Msg
= ChangedRaw String
| Selected (Maybe String)
{-| The initial state contains the set of possible options.
-}
init : Set String -> Model
init options =
{ raw = ""
, selected = Nothing
, options = options
}
{-| Updates the Model
-}
update : Msg -> Model -> Model
update msg model =
case msg of
ChangedRaw string ->
{ model
| raw = string
}
Selected maybe ->
{ model
| selected = maybe
}
|> (case maybe of
Just string ->
\m -> { m | raw = string }
Nothing ->
identity
)
{-| A wrapper around Input.text.
-}
viewInput :
List (Attribute msg)
-> Model
->
{ msgMapper : Msg -> msg
, placeholder : Maybe (Placeholder msg)
, label : String
}
-> Element msg
viewInput attributes model { msgMapper, placeholder, label } =
Input.text attributes
{ onChange = ChangedRaw >> msgMapper
, text = model.raw
, placeholder = placeholder
, label = Input.labelHidden label
}
{-| Returns a List of all options that matches the filter.
-}
viewOptions : Model -> List String
viewOptions { raw, options } =
if raw == "" then
[]
else
options
|> Set.filter (String.toUpper >> String.contains (raw |> String.toUpper))
|> Set.toList

View File

@ -18,10 +18,10 @@ module Widget.Snackbar exposing
-} -}
import Element exposing (Attribute, Element) import Element exposing (Element)
import Queue exposing (Queue) import Queue exposing (Queue)
import Widget exposing (TextButton) import Widget exposing (TextButton)
import Widget.Style exposing (ButtonStyle) import Widget.Style exposing (SnackbarStyle)
type alias Message msg = type alias Message msg =
@ -105,10 +105,7 @@ current model =
view : view :
{ row : List (Attribute msg) SnackbarStyle msg
, text : List (Attribute msg)
, button : ButtonStyle msg
}
-> (a -> Message msg) -> (a -> Message msg)
-> Model a -> Model a
-> Maybe (Element msg) -> Maybe (Element msg)
@ -127,6 +124,6 @@ view style toMessage model =
(Widget.textButton style.button) (Widget.textButton style.button)
|> Maybe.withDefault Element.none |> Maybe.withDefault Element.none
] ]
|> Element.row style.row |> Element.row style.containerRow
) )
) )

View File

@ -1,4 +1,4 @@
module Widget.Style exposing (ButtonStyle, DialogStyle, Style) module Widget.Style exposing (ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, RowStyle, SnackbarStyle, Style, TabStyle, TextInputStyle)
import Element exposing (Attribute, Element) import Element exposing (Attribute, Element)
import Html exposing (Html) import Html exposing (Html)
@ -6,9 +6,9 @@ import Html exposing (Html)
type alias ButtonStyle msg = type alias ButtonStyle msg =
{ container : List (Attribute msg) { container : List (Attribute msg)
, disabled : List (Attribute msg) , labelRow : List (Attribute msg)
, label : List (Attribute msg) , ifDisabled : List (Attribute msg)
, active : List (Attribute msg) , ifActive : List (Attribute msg)
} }
@ -16,18 +16,65 @@ type alias DialogStyle msg =
{ containerColumn : List (Attribute msg) { containerColumn : List (Attribute msg)
, title : List (Attribute msg) , title : List (Attribute msg)
, buttonRow : List (Attribute msg) , buttonRow : List (Attribute msg)
, accept : ButtonStyle msg , acceptButton : ButtonStyle msg
, dismiss : ButtonStyle msg , dismissButton : ButtonStyle msg
}
type alias ExpansionPanelStyle msg =
{ containerColumn : List (Attribute msg)
, panelRow : List (Attribute msg)
, labelRow : List (Attribute msg)
, content : List (Attribute msg)
, expandIcon : Element Never
, collapseIcon : Element Never
}
type alias SnackbarStyle msg =
{ containerRow : List (Attribute msg)
, text : List (Attribute msg)
, button : ButtonStyle msg
}
type alias TextInputStyle msg =
{ chipButton : ButtonStyle msg
, containerRow : List (Attribute msg)
, chipsRow : List (Attribute msg)
, input : List (Attribute msg)
}
type alias TabStyle msg =
{ button : ButtonStyle msg
, optionRow : List (Attribute msg)
, containerColumn : List (Attribute msg)
, content : List (Attribute msg)
}
type alias RowStyle msg =
{ containerRow : List (Attribute msg)
, element : List (Attribute msg)
, ifFirst : List (Attribute msg)
, ifLast : List (Attribute msg)
, ifCenter : List (Attribute msg)
}
type alias ColumnStyle msg =
{ containerColumn : List (Attribute msg)
, element : List (Attribute msg)
, ifFirst : List (Attribute msg)
, ifLast : List (Attribute msg)
, ifCenter : List (Attribute msg)
} }
type alias Style style msg = type alias Style style msg =
{ style { style
| snackbar : | snackbar : SnackbarStyle msg
{ row : List (Attribute msg)
, text : List (Attribute msg)
, button : ButtonStyle msg
}
, layout : List (Attribute msg) -> Element msg -> Html msg , layout : List (Attribute msg) -> Element msg -> Html msg
, header : List (Attribute msg) , header : List (Attribute msg)
, sheet : List (Attribute msg) , sheet : List (Attribute msg)

View File

@ -1,161 +0,0 @@
module Widget.ValidatedInput exposing
( Model, Msg, init, update, view
, getError, getRaw, getValue
)
{-| The validated Input is a wrapper around `Input.text`.
They can validate the input and return an error if nessarry.
# Basics
@docs Model, Msg, init, update, view
# Access the Model
@docs getError, getRaw, getValue
-}
import Element exposing (Attribute, Element)
import Element.Events as Events
import Element.Input as Input exposing (Placeholder)
{-| -}
type Model err a
= Model
{ raw : Maybe String
, value : a
, err : Maybe err
, validator : String -> Result err a
, toString : a -> String
}
{-| returns the raw value (the value that the user currently sees)
-}
getRaw : Model err a -> String
getRaw (Model { raw, value, toString }) =
case raw of
Just string ->
string
Nothing ->
value |> toString
{-| returns the value (the value that has been last successfully validated)
-}
getValue : Model err a -> a
getValue (Model { value }) =
value
{-| returns the error (if one exists)
-}
getError : Model err a -> Maybe err
getError (Model { err }) =
err
{-| -}
type Msg
= ChangedRaw String
| LostFocus
| StartEditing
{-| The initial state contains
- `value`: starting value
- `validator`: a vaidation function (a decoder)
- `toString`: a function that returns a string representation
-}
init : { value : a, validator : String -> Result err a, toString : a -> String } -> Model err a
init { validator, toString, value } =
Model
{ raw = Nothing
, value = value
, err = Nothing
, validator = validator
, toString = toString
}
{-| -}
update : Msg -> Model err a -> Model err a
update msg (Model model) =
case msg of
StartEditing ->
Model
{ model
| raw = model.value |> model.toString |> Just
}
ChangedRaw string ->
Model
{ model
| raw = Just string
, err = Nothing
}
LostFocus ->
case model.raw of
Just string ->
case model.validator string of
Ok value ->
Model
{ model
| value = value
, raw = Nothing
, err = Nothing
}
Err err ->
Model
{ model
| raw = Nothing
, err = Just err
}
Nothing ->
Model model
{-| the view function, the parameters include
- `msgMapper`: A function wrapping the `Msg` into a `msg`
- `placeholder`: See Element.text for more information
- `label`: The (hidden) label of the input (needed for screen readers)
- `readOnly`: a representation of the validated value
(clicking on the element will turn on edit mode)
-}
view :
List (Attribute msg)
-> Model err a
->
{ msgMapper : Msg -> msg
, placeholder : Maybe (Placeholder msg)
, label : String
, readOnly : a -> Element msg
}
-> Element msg
view attributes (Model model) { msgMapper, placeholder, label, readOnly } =
case model.raw of
Just string ->
Input.text (attributes ++ [ Events.onLoseFocus <| msgMapper <| LostFocus ])
{ onChange = ChangedRaw >> msgMapper
, text = string
, placeholder = placeholder
, label = Input.labelHidden label
}
Nothing ->
Input.button []
{ onPress = Just (StartEditing |> msgMapper)
, label = model.value |> readOnly
}