From 732a18720d35db474602e28118cf0c042b48256c Mon Sep 17 00:00:00 2001 From: Lucas Payr Date: Wed, 6 May 2020 14:29:28 +0200 Subject: [PATCH] starting work on List-Widget --- example/src/Component.elm | 233 ----------- example/src/Data/Section.elm | 17 +- example/src/Data/Style.elm | 235 +---------- example/src/Data/Style/ElmUiFramework.elm | 291 +++++++++++++ example/src/Data/Style/Template.elm | 142 +++++++ example/src/Data/Theme.elm | 17 + example/src/Example.elm | 298 ++++++++------ example/src/Icons.elm | 41 +- example/src/Reusable.elm | 90 ++-- example/src/Stateless.elm | 480 ++++++++++++---------- src/Internal/Button.elm | 8 +- src/Internal/Dialog.elm | 22 +- src/Internal/ExpansionPanel.elm | 46 +++ src/Internal/List.elm | 108 +++++ src/Internal/Select.elm | 4 +- src/Internal/Tab.elm | 24 ++ src/Internal/TextInput.elm | 34 ++ src/Widget.elm | 151 ++++--- src/Widget/FilterMultiSelect.elm | 108 ----- src/Widget/FilterSelect.elm | 93 ----- src/Widget/Snackbar.elm | 11 +- src/Widget/Style.elm | 69 +++- src/Widget/ValidatedInput.elm | 161 -------- 23 files changed, 1373 insertions(+), 1310 deletions(-) delete mode 100644 example/src/Component.elm create mode 100644 example/src/Data/Style/ElmUiFramework.elm create mode 100644 example/src/Data/Style/Template.elm create mode 100644 example/src/Data/Theme.elm create mode 100644 src/Internal/ExpansionPanel.elm create mode 100644 src/Internal/List.elm create mode 100644 src/Internal/Tab.elm create mode 100644 src/Internal/TextInput.elm delete mode 100644 src/Widget/FilterMultiSelect.elm delete mode 100644 src/Widget/FilterSelect.elm delete mode 100644 src/Widget/ValidatedInput.elm diff --git a/example/src/Component.elm b/example/src/Component.elm deleted file mode 100644 index e2b4d5a..0000000 --- a/example/src/Component.elm +++ /dev/null @@ -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) ) - } diff --git a/example/src/Data/Section.elm b/example/src/Data/Section.elm index 4dfb07c..28d9d9a 100644 --- a/example/src/Data/Section.elm +++ b/example/src/Data/Section.elm @@ -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 \ No newline at end of file + Nothing diff --git a/example/src/Data/Style.elm b/example/src/Data/Style.elm index 6b95d84..ad1deb4 100644 --- a/example/src/Data/Style.elm +++ b/example/src/Data/Style.elm @@ -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 - } \ No newline at end of file + msg \ No newline at end of file diff --git a/example/src/Data/Style/ElmUiFramework.elm b/example/src/Data/Style/ElmUiFramework.elm new file mode 100644 index 0000000..5f11b08 --- /dev/null +++ b/example/src/Data/Style/ElmUiFramework.elm @@ -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 + } diff --git a/example/src/Data/Style/Template.elm b/example/src/Data/Style/Template.elm new file mode 100644 index 0000000..1dca97d --- /dev/null +++ b/example/src/Data/Style/Template.elm @@ -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" + } \ No newline at end of file diff --git a/example/src/Data/Theme.elm b/example/src/Data/Theme.elm new file mode 100644 index 0000000..65d172c --- /dev/null +++ b/example/src/Data/Theme.elm @@ -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 diff --git a/example/src/Example.elm b/example/src/Example.elm index 051e028..1b3674a 100644 --- a/example/src/Example.elm +++ b/example/src/Example.elm @@ -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 ) diff --git a/example/src/Icons.elm b/example/src/Icons.elm index bf413af..be94eae 100644 --- a/example/src/Icons.elm +++ b/example/src/Icons.elm @@ -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" ] [] - ] \ No newline at end of file + ] diff --git a/example/src/Reusable.elm b/example/src/Reusable.elm index c911b18..a87b5ef 100644 --- a/example/src/Reusable.elm +++ b/example/src/Reusable.elm @@ -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 ] } diff --git a/example/src/Stateless.elm b/example/src/Stateless.elm index ace8b0e..8487d25 100644 --- a/example/src/Stateless.elm +++ b/example/src/Stateless.elm @@ -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 ] } diff --git a/src/Internal/Button.elm b/src/Internal/Button.elm index 610fafc..0b8a96e 100644 --- a/src/Internal/Button.elm +++ b/src/Internal/Button.elm @@ -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 ] diff --git a/src/Internal/Dialog.elm b/src/Internal/Dialog.elm index 5da2108..97a55eb 100644 --- a/src/Internal/Dialog.elm +++ b/src/Internal/Dialog.elm @@ -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 ] _ -> diff --git a/src/Internal/ExpansionPanel.elm b/src/Internal/ExpansionPanel.elm new file mode 100644 index 0000000..d8f7a1b --- /dev/null +++ b/src/Internal/ExpansionPanel.elm @@ -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 + ] diff --git a/src/Internal/List.elm b/src/Internal/List.elm new file mode 100644 index 0000000..55fb29f --- /dev/null +++ b/src/Internal/List.elm @@ -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 diff --git a/src/Internal/Select.elm b/src/Internal/Select.elm index 1c93b21..f6acc5d 100644 --- a/src/Internal/Select.elm +++ b/src/Internal/Select.elm @@ -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 [] diff --git a/src/Internal/Tab.elm b/src/Internal/Tab.elm new file mode 100644 index 0000000..131b560 --- /dev/null +++ b/src/Internal/Tab.elm @@ -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 diff --git a/src/Internal/TextInput.elm b/src/Internal/TextInput.elm new file mode 100644 index 0000000..4d56f81 --- /dev/null +++ b/src/Internal/TextInput.elm @@ -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 + } + ] diff --git a/src/Widget.elm b/src/Widget.elm index 532ef86..95a6888 100644 --- a/src/Widget.elm +++ b/src/Widget.elm @@ -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 diff --git a/src/Widget/FilterMultiSelect.elm b/src/Widget/FilterMultiSelect.elm deleted file mode 100644 index 4d0ac79..0000000 --- a/src/Widget/FilterMultiSelect.elm +++ /dev/null @@ -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 diff --git a/src/Widget/FilterSelect.elm b/src/Widget/FilterSelect.elm deleted file mode 100644 index 8d6bbf9..0000000 --- a/src/Widget/FilterSelect.elm +++ /dev/null @@ -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 diff --git a/src/Widget/Snackbar.elm b/src/Widget/Snackbar.elm index 2c03a61..4116da1 100644 --- a/src/Widget/Snackbar.elm +++ b/src/Widget/Snackbar.elm @@ -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 ) ) diff --git a/src/Widget/Style.elm b/src/Widget/Style.elm index c1ca2ea..d88e328 100644 --- a/src/Widget/Style.elm +++ b/src/Widget/Style.elm @@ -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) diff --git a/src/Widget/ValidatedInput.elm b/src/Widget/ValidatedInput.elm deleted file mode 100644 index e3aa08e..0000000 --- a/src/Widget/ValidatedInput.elm +++ /dev/null @@ -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 - }