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
= ComponentViews
| ReusableViews
= ReusableViews
| StatelessViews
asList : List Section
asList =
[ StatelessViews, ReusableViews, ComponentViews ]
[ StatelessViews, ReusableViews ]
toString : Section -> String
toString section =
case section of
ComponentViews ->
"Component"
ReusableViews ->
"Reusable"
@ -21,11 +21,10 @@ toString section =
StatelessViews ->
"Stateless"
fromString : String -> Maybe Section
fromString string =
case string of
"Component" ->
Just ComponentViews
"Reusable" ->
Just ReusableViews
@ -34,4 +33,4 @@ fromString string =
Just StatelessViews
_ ->
Nothing
Nothing

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.Input as Input
import Element.Font as Font
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
import Widget.Style exposing (ButtonStyle, DialogStyle, ExpansionPanelStyle,
SnackbarStyle ,RowStyle,ColumnStyle,TextInputStyle,TabStyle)
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
}
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
type alias Style msg =
Widget.Style.Style
{ dialog : DialogStyle msg
, expansionPanel : ExpansionPanelStyle msg
, button : ButtonStyle msg
, primaryButton : ButtonStyle msg
, tab : TabStyle msg
, textInput : TextInputStyle msg
, chipButton : ButtonStyle msg
, row : RowStyle msg
, column : ColumnStyle msg
, cardColumn : ColumnStyle msg
}
, button : ButtonStyle msg
, primaryButton : ButtonStyle msg
, tabButton : ButtonStyle msg
, textInput : TextInputStyle msg
, chipButton : ButtonStyle msg
} msg
style =
{ button = buttonStyle
, 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)
import Array
import Browser
import Browser.Dom as Dom exposing (Viewport)
import Browser.Events as Events
import Browser.Navigation as Navigation
import Component
import Element exposing (DeviceClass(..), Element,Attribute)
import Element.Input as Input
import Element.Font as Font
import Data.Section as Section exposing (Section(..))
import Element exposing (Attribute, DeviceClass(..), Element)
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
@ -21,35 +22,35 @@ import Framework.Tag as Tag
import Html exposing (Html)
import Html.Attributes as Attributes
import Icons
import Layout exposing (Part, Layout)
import Data.Style exposing (style)
import Layout exposing (Layout, Part)
import Reusable
import Set exposing (Set)
import Stateless
import Task
import Time
import Widget
import Widget.Style exposing (ButtonStyle)
import Widget.FilterSelect as FilterSelect
import Widget.ScrollingNav as ScrollingNav
import Widget.Snackbar as Snackbar
import Widget.ValidatedInput as ValidatedInput
import Data.Section as Section exposing (Section(..))
import Array
import Widget.Style exposing (ButtonStyle)
import Data.Style as Style exposing (Style)
import Data.Theme as Theme exposing (Theme(..))
type alias LoadedModel =
{ component : Component.Model
, stateless : Stateless.Model
{ stateless : Stateless.Model
, reusable : Reusable.Model
, scrollingNav : ScrollingNav.Model Section
, layout : Layout LoadedMsg
, displayDialog : Bool
, window : { height : Int, width : Int }
, search :
, search :
{ raw : String
, current : String
, remaining : Int
}
, theme : Theme
}
@ -61,16 +62,16 @@ type Model
type LoadedMsg
= StatelessSpecific Stateless.Msg
| ReusableSpecific Reusable.Msg
| ComponentSpecific Component.Msg
| UpdateScrollingNav (ScrollingNav.Model Section -> ScrollingNav.Model Section)
| TimePassed Int
| AddSnackbar (String,Bool)
| AddSnackbar ( String, Bool )
| ToggleDialog Bool
| ChangedSidebar (Maybe Part)
| Resized { width : Int, height : Int }
| Load String
| JumpTo Section
| ChangedSearch String
| SetTheme Theme
| Idle
@ -87,16 +88,17 @@ initialModel { viewport } =
{ toString = Section.toString
, fromString = Section.fromString
, arrangement = Section.asList
, toMsg = \result ->
case result of
Ok fun ->
UpdateScrollingNav fun
Err _ ->
Idle
, toMsg =
\result ->
case result of
Ok fun ->
UpdateScrollingNav fun
Err _ ->
Idle
}
in
( { component = Component.init
, stateless = Stateless.init
( { stateless = Stateless.init
, reusable = Reusable.init
, scrollingNav = scrollingNav
, layout = Layout.init
@ -105,11 +107,12 @@ initialModel { viewport } =
{ width = viewport.width |> round
, height = viewport.height |> round
}
, search =
, search =
{ raw = ""
, current = ""
, remaining = 0
}
, theme = ElmUiFramework
}
, cmd
)
@ -124,32 +127,40 @@ init () =
view : Model -> Html Msg
view model =
case model of
Loading ->
Element.none |> Framework.responsiveLayout []
Loaded m ->
let
style : Style msg
style =
Theme.toStyle m.theme
in
Html.map LoadedSpecific <|
Layout.view []
{ dialog =
if m.displayDialog then
{ body =
"This is a dialog window"
|> Element.text
|> List.singleton
|> Element.paragraph []
|> Element.text
|> List.singleton
|> Element.paragraph []
, title = Just "Dialog"
, accept = Just
{ text = "Ok"
, onPress = Just <| ToggleDialog False
}
, dismiss = Just
{ text = "Dismiss"
, onPress = Just <| ToggleDialog False
}
, accept =
Just
{ text = "Ok"
, onPress = Just <| ToggleDialog False
}
, dismiss =
Just
{ text = "Dismiss"
, onPress = Just <| ToggleDialog False
}
}
|> Widget.dialog style.dialog
|> Just
|> Widget.dialog style.dialog
|> Just
else
Nothing
@ -158,104 +169,116 @@ view model =
, [ m.scrollingNav
|> ScrollingNav.view
(\section ->
( case section of
ComponentViews ->
m.component
|> Component.view ComponentSpecific
(case section of
ReusableViews ->
Reusable.view
Reusable.view m.theme
{ addSnackbar = AddSnackbar
, model = m.reusable
, msgMapper = ReusableSpecific
}
StatelessViews ->
Stateless.view
Stateless.view m.theme
{ msgMapper = StatelessSpecific
, showDialog = ToggleDialog True
, changedSheet = ChangedSidebar
}
m.stateless
) |> (\{title,description,items} ->
[ Element.el Heading.h2 <| Element.text <| title
, if m.search.current == "" then
description
)
|> (\{ 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
else
Element.none
, items
|> (if m.search.current /= "" then
List.filter
(\(a,_,_) ->
a
|> String.toLower
|> String.contains (m.search.current |> String.toLower)
)
else
identity
)
|> List.map
(\( name, elem, more ) ->
[ 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 ])
)
, elem
, more
]
|> Widget.column style.cardColumn
)
|> Element.wrappedRow
(Grid.simple ++ [ Element.height <| Element.shrink ])
]
|> Element.column (Grid.section ++ [ Element.centerX ])
)
)
]
|> Element.column Framework.container
]
|> Element.column Grid.compact
, style = style
, style =style
, layout = m.layout
, window = m.window
, menu =
m.scrollingNav
|> ScrollingNav.toSelect
(\int ->
m.scrollingNav.arrangement
|> Array.fromList
|> Array.get int
|> Maybe.map JumpTo
)
m.scrollingNav
|> ScrollingNav.toSelect
(\int ->
m.scrollingNav.arrangement
|> Array.fromList
|> Array.get int
|> Maybe.map JumpTo
)
, actions =
[ { onPress = Just <| Load "https://package.elm-lang.org/packages/Orasund/elm-ui-widgets/latest/"
, 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"
, 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
, text = "Placeholder"
, icon = Icons.circle|> Element.html |> Element.el []
, icon = Icons.circle |> Element.html |> Element.el []
}
, { onPress = Nothing
, text = "Placeholder"
, icon = Icons.triangle|> Element.html |> Element.el []
, icon = Icons.triangle |> Element.html |> Element.el []
}
, { onPress = Nothing
, text = "Placeholder"
, icon = Icons.square|> Element.html |> Element.el []
, icon = Icons.square |> Element.html |> Element.el []
}
]
, onChangedSidebar = ChangedSidebar
, title =
, title =
"Elm-Ui-Widgets"
|> Element.text
|> Element.el Heading.h1
|> Element.text
|> Element.el Heading.h1
, search =
Just
{ text = m.search.raw
@ -268,17 +291,6 @@ view model =
updateLoaded : LoadedMsg -> LoadedModel -> ( LoadedModel, Cmd LoadedMsg )
updateLoaded msg model =
case msg of
ComponentSpecific m ->
model.component
|> Component.update m
|> Tuple.mapBoth
(\component ->
{ model
| component = component
}
)
(Cmd.map ComponentSpecific)
ReusableSpecific m ->
( model.reusable
|> Reusable.update m
@ -300,15 +312,16 @@ updateLoaded msg model =
}
)
(Cmd.map StatelessSpecific)
UpdateScrollingNav fun ->
( { model | scrollingNav = model.scrollingNav |> fun}
( { model | scrollingNav = model.scrollingNav |> fun }
, Cmd.none
)
TimePassed int ->
let
search = model.search
search =
model.search
in
( { model
| layout = model.layout |> Layout.timePassed int
@ -316,13 +329,15 @@ updateLoaded msg model =
if search.remaining > 0 then
if search.remaining <= int then
{ search
| current = search.raw
, remaining = 0
| current = search.raw
, remaining = 0
}
else
{ search
| remaining = search.remaining - int
| remaining = search.remaining - int
}
else
model.search
}
@ -330,21 +345,25 @@ updateLoaded msg model =
|> Task.perform UpdateScrollingNav
)
AddSnackbar (string,bool) ->
( { model
| layout = model.layout
|> Layout.queueMessage
{ text = string
, button = if bool then
Just
{ text = "Add"
, onPress = Just <|
(AddSnackbar ("This is another message", False))
}
else
Nothing
}
}
AddSnackbar ( string, bool ) ->
( { model
| layout =
model.layout
|> Layout.queueMessage
{ text = string
, button =
if bool then
Just
{ text = "Add"
, onPress =
Just <|
AddSnackbar ( "This is another message", False )
}
else
Nothing
}
}
, Cmd.none
)
@ -362,34 +381,41 @@ updateLoaded msg model =
( { model | layout = model.layout |> Layout.activate sidebar }
, Cmd.none
)
Load string ->
( model, Navigation.load string)
( model, Navigation.load string )
JumpTo section ->
( model
, model.scrollingNav
|> ScrollingNav.jumpTo
|> ScrollingNav.jumpTo
{ section = section
, onChange = always Idle
}
)
ChangedSearch string ->
let
search = model.search
search =
model.search
in
( { model | search =
( { model
| search =
{ search
| raw = string
, remaining = 300
| raw = string
, remaining = 300
}
}
}
, Cmd.none
)
SetTheme theme ->
( { model | theme = theme }
, Cmd.none
)
Idle ->
( model , Cmd.none)
( model, Cmd.none )
update : Msg -> Model -> ( Model, Cmd Msg )

View File

@ -1,17 +1,19 @@
module Icons exposing
( book
, chevronDown
, chevronLeft
, chevronRight
, circle
, github
, menu
, moreVertical
, circle
, triangle
, square
, repeat
, search
, slash
, repeat
, chevronDown
, chevronRight
, chevronLeft
, square
, triangle
, chevronUp
, penTool
)
import Html exposing (Html)
@ -33,6 +35,7 @@ svgFeatherIcon className =
, width "16"
]
chevronDown : Html msg
chevronDown =
svgFeatherIcon "chevron-down"
@ -45,12 +48,20 @@ chevronRight =
svgFeatherIcon "chevron-right"
[ Svg.polyline [ points "9 18 15 12 9 6" ] []
]
chevronLeft : Html msg
chevronLeft =
svgFeatherIcon "chevron-left"
[ 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 =
svgFeatherIcon "repeat"
@ -60,6 +71,14 @@ repeat =
, 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 =
@ -84,6 +103,7 @@ menu =
, Svg.line [ x1 "3", y1 "18", x2 "21", y2 "18" ] []
]
moreVertical : Html msg
moreVertical =
svgFeatherIcon "more-vertical"
@ -92,12 +112,14 @@ moreVertical =
, Svg.circle [ cx "12", cy "19", r "1" ] []
]
circle : Html msg
circle =
svgFeatherIcon "circle"
[ Svg.circle [ cx "12", cy "12", r "10" ] []
]
slash : Html msg
slash =
svgFeatherIcon "slash"
@ -105,21 +127,24 @@ slash =
, Svg.line [ x1 "4.93", y1 "4.93", x2 "19.07", y2 "19.07" ] []
]
triangle : Html msg
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" ] []
]
square : Html msg
square =
svgFeatherIcon "square"
[ Svg.rect [ Svg.Attributes.x "3", y "3", width "18", height "18", rx "2", ry "2" ] []
]
search : Html msg
search =
svgFeatherIcon "search"
[ Svg.circle [ cx "11", cy "11", r "8" ] []
, Svg.line [ x1 "21", y1 "21", x2 "16.65", y2 "16.65" ] []
]
]

View File

@ -20,12 +20,11 @@ import Html.Attributes as Attributes
import Set exposing (Set)
import Time
import Widget
import Widget.FilterSelect as FilterSelect
import Widget.ScrollingNav as ScrollingNav
import Widget.Snackbar as Snackbar
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 =
SortTable.Model
@ -54,36 +53,37 @@ init =
SortTable.sortBy { title = "Name", asc = True }
snackbar : ((String,Bool) -> msg) -> (String,Element msg)
snackbar addSnackbar =
snackbar : Style msg -> (( String, Bool ) -> msg) -> ( String, Element msg,Element msg )
snackbar style addSnackbar =
( "Snackbar"
, [Input.button Button.simple
{ onPress = Just <| addSnackbar <|
("This is a notification. It will disappear after 10 seconds."
, False
)
, label =
"Add Notification"
|> Element.text
|> List.singleton
|> Element.paragraph []
}
, Input.button Button.simple
{ onPress = Just <| addSnackbar <|
("You can add another notification if you want."
, True
)
, label =
"Add Notification with Action"
|> Element.text
|> List.singleton
|> Element.paragraph []
}
] |> Element.column Grid.simple
, [ Widget.button style.button
{ onPress =
Just <|
addSnackbar <|
( "This is a notification. It will disappear after 10 seconds."
, False
)
, text = "Add Notification"
, icon =Element.none
}
, Widget.button style.button
{ onPress =
Just <|
addSnackbar <|
( "You can add another notification if you want."
, True
)
, text = "Add Notification with Action"
, icon = Element.none
}
]
|> 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"
, SortTable.view
{ content =
@ -152,31 +152,41 @@ sortTable model =
}
)
|> Element.table Grid.simple
, Element.none
)
scrollingNavCard : (String , Element msg )
scrollingNavCard =
("Scrolling Nav"
scrollingNavCard : Style msg -> ( String, Element msg, Element msg )
scrollingNavCard style =
( "Scrolling Nav"
, Element.text "Resize the screen and open the side-menu. Then start scrolling to see the scrolling navigation in action."
|> List.singleton
|> Element.paragraph []
, Element.none
)
view :
{ addSnackbar : (String,Bool) -> msg
Theme ->
{ addSnackbar : ( String, Bool ) -> msg
, msgMapper : Msg -> msg
, model : Model
}
-> { title : String
->
{ title : 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"
, description = "Reusable views have an internal state but no update function. You will need to do some wiring, but nothing complicated."
, items =
[ snackbar addSnackbar
, sortTable model |> Tuple.mapSecond (Element.map msgMapper)
, scrollingNavCard
[ snackbar style addSnackbar
, sortTable style model |> \(a,b,c) ->
(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)
import Array exposing (Array)
import Data.Style exposing (Style)
import Element exposing (Element)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
import Framework.Button as Button
import Framework.Card as Card
@ -16,19 +18,18 @@ import Framework.Tag as Tag
import Heroicons.Solid as Heroicons
import Html exposing (Html)
import Html.Attributes as Attributes
import Set exposing (Set)
import Widget.Style exposing (ButtonStyle)
import Layout exposing (Part(..))
import Icons
import Layout exposing (Part(..))
import Set exposing (Set)
import Widget
import Element.Font as Font
import Data.Style exposing (style)
import Widget.Style exposing (ButtonStyle)
import Data.Theme as Theme exposing (Theme)
type alias Model =
{ selected : Maybe Int
, multiSelected : Set Int
, chipTextInput : Set String
, isCollapsed : Bool
, isExpanded : Bool
, carousel : Int
, tab : Maybe Int
, button : Bool
@ -53,7 +54,7 @@ init =
{ selected = Nothing
, multiSelected = Set.empty
, chipTextInput = Set.empty
, isCollapsed = False
, isExpanded = False
, carousel = 0
, tab = Just 1
, button = True
@ -87,20 +88,22 @@ update msg model =
ToggleCollapsable bool ->
( { model
| isCollapsed = bool
| isExpanded = bool
}
, Cmd.none
)
ToggleTextInputChip string ->
( { model
| chipTextInput =
model.chipTextInput |>
if model.chipTextInput |> Set.member string then
Set.remove string
else
Set.insert string
}
model.chipTextInput
|> (if model.chipTextInput |> Set.member string then
Set.remove string
else
Set.insert string
)
}
, Cmd.none
)
@ -117,132 +120,104 @@ update msg model =
ChangedTab int ->
( { model | tab = Just int }, Cmd.none )
ToggleButton bool ->
( { model | button = bool }, Cmd.none )
SetTextInput string ->
( {model | textInput = string },Cmd.none)
( { model | textInput = string }, Cmd.none )
Idle ->
( model, Cmd.none)
( model, Cmd.none )
select : Model -> (String,Element Msg)
select model =
select : Style Msg -> Model -> ( String, Element Msg,Element Msg )
select style model =
let
buttonStyle = style.button
buttonStyle =
style.button
in
( "Select"
, { selected = model.selected
, options =
[ 1, 2, 42 ]
|> List.map (\int ->
{ text = String.fromInt int
, icon = Element.none
}
)
, options =
[ 1, 2, 42 ]
|> List.map
(\int ->
{ text = String.fromInt int
, icon = Element.none
}
)
, onSelect = ChangedSelected >> Just
}
|> Widget.select
|> List.indexedMap
(\i ->
Widget.selectButton
{ buttonStyle
| container = buttonStyle.container
++ (if i == 0 then
Group.left
else if i == 2 then
Group.right
else
Group.center
)
}
)
|> Element.row Grid.compact
|> Widget.buttonRow
{ list = style.row
, button = style.button
}
, Element.none
)
multiSelect : Model -> (String,Element Msg)
multiSelect model =
multiSelect : Style Msg -> Model -> ( String, Element Msg, Element Msg )
multiSelect style model =
let
buttonStyle = style.button
buttonStyle =
style.button
in
( "Multi Select"
, { selected = model.multiSelected
, options =
[ 1, 2, 42 ]
|> List.map (\int ->
{ text = String.fromInt int
, icon = Element.none
})
, options =
[ 1, 2, 42 ]
|> List.map
(\int ->
{ text = String.fromInt int
, icon = Element.none
}
)
, onSelect = ChangedMultiSelected >> Just
}
|> Widget.multiSelect
|> List.indexedMap
(\i ->
Widget.selectButton
{ buttonStyle
| container = buttonStyle.container
++ (if i == 0 then
Group.left
else if i == 2 then
Group.right
else
Group.center
)
}
)
|> Element.row Grid.compact
|> Widget.buttonRow
{ list = style.row
, button = style.button
}
, Element.none
)
collapsable : Model -> (String,Element Msg)
collapsable model =
( "Collapsable"
expansionPanel : Style Msg -> Model -> (String,Element Msg,Element Msg)
expansionPanel style model =
( "Expansion Panel"
, { onToggle = ToggleCollapsable
, isCollapsed = model.isCollapsed
, label =
Element.row (Grid.simple ++ [Element.width<| Element.fill])
[ if model.isCollapsed then
Icons.chevronRight |> Element.html |> Element.el []
else
Icons.chevronDown |> Element.html |> Element.el []
, Element.text <| "Title"
]
, isExpanded = model.isExpanded
, icon = Element.none
, text = "Title"
, content = Element.text <| "Hello World"
}
|>Widget.collapsable
{ containerColumn = Card.simple ++ Grid.simple
++ [ Element.padding 8 ]
, button = []
}
|>Widget.expansionPanel style.expansionPanel
, Element.none
)
tab : Model -> (String,Element Msg)
tab model =
tab : Style Msg -> Model -> ( String, Element Msg, Element Msg )
tab style model =
( "Tab"
, Widget.tab
{ button = style.tabButton
, optionRow = Grid.simple
, containerColumn = Grid.compact
}
, Widget.tab style.tab
{ tabs =
{ selected = model.tab
, options = [ 1, 2, 3 ]
|> List.map (\int ->
{ text = "Tab " ++ (int |> String.fromInt)
, icon = Element.none
}
)
, options =
[ 1, 2, 3 ]
|> List.map
(\int ->
{ text = "Tab " ++ (int |> String.fromInt)
, icon = Element.none
}
)
, onSelect = ChangedTab >> Just
} <|
(\selected ->
}
, content =
\selected ->
(case selected of
Just 0 ->
"This is Tab 1"
@ -257,57 +232,69 @@ tab model =
"Please select a tab"
)
|> Element.text
|> Element.el (Card.small ++ Group.bottom)
)
)
modal : (Maybe Part -> msg) -> Model -> (String,Element msg)
modal changedSheet model =
( "Modal"
, [ Input.button Button.simple
{ onPress = Just <| changedSheet <| Just LeftSheet
, label = Element.text <| "show left sheet"
}
, Input.button Button.simple
{ onPress = Just <| changedSheet <| Just RightSheet
, label = Element.text <| "show right sheet"
}
] |> Element.column Grid.simple
)
dialog : msg -> Model -> (String,Element msg)
dialog showDialog model =
( "Dialog"
, Input.button Button.simple
{ onPress = Just showDialog
, label = Element.text <| "Show dialog"
}
, Element.none
)
carousel : Model -> (String,Element Msg)
carousel model =
modal : Style msg -> (Maybe Part -> msg) -> Model -> ( String, Element msg,Element msg )
modal style changedSheet model =
( "Modal"
, [ Widget.button style.button
{ onPress = Just <| changedSheet <| Just LeftSheet
, text = "show left sheet"
, icon = Element.none
}
, Widget.button style.button
{ onPress = Just <| changedSheet <| Just RightSheet
, text = "show right sheet"
, icon = Element.none
}
]
|> Element.column Grid.simple
,Element.none
)
dialog : Style msg -> msg -> Model -> ( String, Element msg, Element msg )
dialog style showDialog model =
( "Dialog"
, Widget.button style.button
{ onPress = Just showDialog
, text = "Show dialog"
, icon = Element.none
}
, Element.none
)
carousel : Style Msg -> Model -> ( String, Element Msg, Element Msg )
carousel style model =
( "Carousel"
, Widget.carousel
{ content = ( Color.cyan, [ Color.yellow, Color.green, Color.red ] |> Array.fromList )
, current = model.carousel
, label =
\c ->
[ Element.el [Element.centerY] <|
Widget.iconButton style.button
{ onPress =
model.carousel - 1
|> \i ->
if i < 0 then
Nothing
else
SetCarousel i
|> Just
, icon =
Icons.chevronLeft
|> Element.html
|> Element.el []
, text = "Previous"
}
[ Element.el [ Element.centerY ] <|
Widget.iconButton style.button
{ onPress =
model.carousel
- 1
|> (\i ->
if i < 0 then
Nothing
else
SetCarousel i
|> Just
)
, icon =
Icons.chevronLeft
|> Element.html
|> Element.el []
, text = "Previous"
}
, Element.el
(Card.simple
++ [ Background.color <| c
@ -319,112 +306,169 @@ carousel model =
Element.none
, Element.el [ Element.centerY ] <|
Widget.iconButton style.button
{ onPress = model.carousel + 1
|> \i ->
if i >= 4 then
Nothing
else
SetCarousel i
|> Just
, icon =
Icons.chevronRight
|> Element.html
|> Element.el []
, text = "Next"
}
{ onPress =
model.carousel
+ 1
|> (\i ->
if i >= 4 then
Nothing
else
SetCarousel i
|> Just
)
, icon =
Icons.chevronRight
|> Element.html
|> Element.el []
, text = "Next"
}
]
|> 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"
, [ [ Widget.button style.primaryButton
{ text = "disable me"
, icon = Icons.slash |> Element.html |> Element.el [] , onPress =
if model.button then
Just <| ToggleButton False
else
Nothing
}
, Widget.iconButton style.button
{ text = "reset"
, icon = Icons.repeat |> Element.html |> Element.el []
, onPress = Just <| ToggleButton True
}
]
|> Element.row Grid.simple
, Widget.button style.button
{ text = "reset button"
, icon = Element.none
, onPress = Just <| ToggleButton True
, [ Widget.button style.primaryButton
{ text = "disable me"
, icon = Icons.slash |> Element.html |> Element.el []
, onPress =
if model.button then
Just <| ToggleButton False
else
Nothing
}
] |> Element.column Grid.simple
, Widget.iconButton style.button
{ text = "reset"
, icon = Icons.repeat |> Element.html |> Element.el []
, onPress = Just <| ToggleButton True
}
]
|> Element.row Grid.simple
, Element.column Grid.simple
[ Element.row Grid.spacedEvenly
[ "Button"
|> Element.text
, Widget.button style.button
{ text = "reset"
, icon = Icons.repeat |> Element.html |> Element.el []
, onPress = Just <| ToggleButton True
}
]
, 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"
, [ { chips =
, [ { chips =
model.chipTextInput
|> Set.toList
|> List.map (\string ->
{ icon = Element.none
, text = string
, onPress =
string
|> ToggleTextInputChip
|> Just
}
)
|> Set.toList
|> List.map
(\string ->
{ icon = Element.none
, text = string
, onPress =
string
|> ToggleTextInputChip
|> Just
}
)
, text = model.textInput
, placeholder = Nothing
, label = "Chips"
, onChange = SetTextInput
}
|> Widget.textInput style.textInput
, model.chipTextInput
, model.chipTextInput
|> Set.diff
(["A","B","C"]
([ "A", "B", "C" ]
|> Set.fromList
)
|> Set.toList
|> List.map
(\string ->
Input.button (Button.simple ++ Tag.simple)
Widget.button style.textInput.chipButton
{ onPress =
string
|> ToggleTextInputChip
|> Just
, label = Element.text string
|> ToggleTextInputChip
|> Just
, text = string
, icon = Element.none
}
)
|> Element.wrappedRow [ Element.spacing 10 ]
] |> Element.column Grid.simple
]
|> Element.column Grid.simple
, Element.none
)
view :
view :
Theme ->
{ msgMapper : Msg -> msg
, showDialog : msg
, changedSheet : Maybe Part -> msg
} -> Model
-> { title : String
}
-> Model
->
{ title : String
, description : String
, items : List (String,Element msg)
, items : List ( String, Element msg, Element msg )
}
view { msgMapper, showDialog, changedSheet } model =
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"
, description = "Stateless views are simple functions that view some content. No wiring required."
, items =
[ iconButton model |> Tuple.mapSecond (Element.map msgMapper)
, select model |> Tuple.mapSecond (Element.map msgMapper)
, multiSelect model |> Tuple.mapSecond (Element.map msgMapper)
, collapsable model |> Tuple.mapSecond (Element.map msgMapper)
, modal changedSheet model
, carousel model |> Tuple.mapSecond (Element.map msgMapper)
, tab model |> Tuple.mapSecond (Element.map msgMapper)
, dialog showDialog model
, textInput model |> Tuple.mapSecond (Element.map msgMapper)
[ iconButton style model |> map
, select style model |> map
, multiSelect style model |> map
, expansionPanel style model |> map
, modal style changedSheet model
, carousel style model |> map
, tab style model |> map
, dialog style showDialog model
, textInput style model |> map
]
}

View File

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

View File

@ -51,10 +51,10 @@ dialog style { title, body, accept, dismiss } =
Nothing
, content =
Element.column
(style.containerColumn
++ [ Element.centerX
, Element.centerY
]
([ Element.centerX
, Element.centerY
]
++ style.containerColumn
)
[ title
|> Maybe.map
@ -64,22 +64,22 @@ dialog style { title, body, accept, dismiss } =
|> Maybe.withDefault Element.none
, body
, Element.row
(style.buttonRow
++ [ Element.alignRight
, Element.width <| Element.shrink
]
([ Element.alignRight
, Element.width <| Element.shrink
]
++ style.buttonRow
)
(case ( accept, dismiss ) of
( Just acceptButton, Nothing ) ->
acceptButton
|> Button.textButton style.accept
|> Button.textButton style.acceptButton
|> List.singleton
( Just acceptButton, Just dismissButton ) ->
[ dismissButton
|> Button.textButton style.dismiss
|> Button.textButton style.dismissButton
, 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 Internal.Button as Button exposing (Button)
@ -39,7 +39,7 @@ selectButton style ( selected, b ) =
| container =
style.container
++ (if selected then
style.active
style.ifActive
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
, Select, MultiSelect, selectButton, select, multiSelect
, 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.
@ -23,20 +25,28 @@ module Widget exposing
@docs Dialog, modal, dialog
# ExpansionPanel
@docs ExpansionPanel, expansionPanel
# Other Widgets
@docs TextInputStyle, textInput, collapsable, carousel, tab
@docs TextInputStyle, textInput, carousel, tab
-}
import Array exposing (Array)
import Element exposing (Attribute, Element)
import Element.Input as Input exposing (Placeholder)
import Element.Input exposing (Placeholder)
import Internal.Button as Button
import Internal.Dialog as Dialog
import Internal.ExpansionPanel as ExpansionPanel
import Internal.List as List
import Internal.Select as Select
import Internal.TextInput as TextInput
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 =
{ chip : ButtonStyle msg
{ chipButton : ButtonStyle msg
, containerRow : List (Attribute msg)
, chipsRow : List (Attribute msg)
, input : List (Attribute msg)
@ -240,65 +281,75 @@ textInput :
, onChange : String -> msg
}
-> Element msg
textInput style { chips, placeholder, label, text, onChange } =
Element.row style.containerRow
[ 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
}
]
textInput =
TextInput.textInput
{-| Some collapsable content.
-}
collapsable :
{ containerColumn : List (Attribute msg)
, button : List (Attribute msg)
{----------------------------------------------------------
- LIST
----------------------------------------------------------}
row : RowStyle msg -> List (Element msg) -> Element msg
row =
List.row
column : ColumnStyle msg -> List (Element msg) -> Element msg
column =
List.column
buttonRow :
{ list : RowStyle msg
, button : ButtonStyle msg
}
->
{ onToggle : Bool -> msg
, isCollapsed : Bool
, label : Element msg
, content : Element msg
}
-> List ( Bool, Button msg )
-> Element msg
collapsable style { onToggle, isCollapsed, label, content } =
Element.column style.containerColumn <|
[ Input.button style.button
{ onPress = Just <| onToggle <| not isCollapsed
, label = label
}
]
++ (if isCollapsed then
[]
buttonRow =
List.buttonRow
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
-}
tab :
{ button : ButtonStyle msg
, optionRow : List (Attribute msg)
, containerColumn : List (Attribute msg)
}
-> Select msg
-> (Maybe Int -> Element msg)
TabStyle msg
->
{ tabs : Select msg
, content : Maybe Int -> Element msg
}
-> Element msg
tab style options content =
[ options
tab style { tabs, content } =
[ tabs
|> select
|> List.map (selectButton style.button)
|> Element.row style.optionRow
, options.selected
, tabs.selected
|> content
|> Element.el style.content
]
|> 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 Widget exposing (TextButton)
import Widget.Style exposing (ButtonStyle)
import Widget.Style exposing (SnackbarStyle)
type alias Message msg =
@ -105,10 +105,7 @@ current model =
view :
{ row : List (Attribute msg)
, text : List (Attribute msg)
, button : ButtonStyle msg
}
SnackbarStyle msg
-> (a -> Message msg)
-> Model a
-> Maybe (Element msg)
@ -127,6 +124,6 @@ view style toMessage model =
(Widget.textButton style.button)
|> 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 Html exposing (Html)
@ -6,9 +6,9 @@ import Html exposing (Html)
type alias ButtonStyle msg =
{ container : List (Attribute msg)
, disabled : List (Attribute msg)
, label : List (Attribute msg)
, active : List (Attribute msg)
, labelRow : List (Attribute msg)
, ifDisabled : List (Attribute msg)
, ifActive : List (Attribute msg)
}
@ -16,18 +16,65 @@ type alias DialogStyle msg =
{ containerColumn : List (Attribute msg)
, title : List (Attribute msg)
, buttonRow : List (Attribute msg)
, accept : ButtonStyle msg
, dismiss : ButtonStyle msg
, acceptButton : 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 =
{ style
| snackbar :
{ row : List (Attribute msg)
, text : List (Attribute msg)
, button : ButtonStyle msg
}
| snackbar : SnackbarStyle msg
, layout : List (Attribute msg) -> Element msg -> Html msg
, header : 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
}