diff --git a/example/elm.json b/example/elm.json index 9f18fca..ec138fb 100644 --- a/example/elm.json +++ b/example/elm.json @@ -17,6 +17,7 @@ "feathericons/elm-feather": "1.4.0", "jasonliang512/elm-heroicons": "1.0.2", "mdgriffith/elm-ui": "1.1.5", + "ryannhg/elm-spa": "4.1.0", "turboMaCk/queue": "1.0.2", "wernerdegroot/listzipper": "4.0.0" }, diff --git a/example/src/Data/Example.elm b/example/src/Data/Example.elm new file mode 100644 index 0000000..881a8dd --- /dev/null +++ b/example/src/Data/Example.elm @@ -0,0 +1,187 @@ +module Data.Example exposing (Model, Msg, init, subscriptions, toCardList, update, view) + +import Data.Style exposing (Style) +import Element exposing (Element) +import Example.Button as Button +import Example.ExpansionPanel as ExpansionPanel +import Example.MultiSelect as MultiSelect +import Example.Select as Select +import Example.Tab as Tab +import Framework.Grid as Grid +import View.Test as Test + + +type Msg + = Button Button.Msg + | Select Select.Msg + | MultiSelect MultiSelect.Msg + | ExpansionPanel ExpansionPanel.Msg + | Tab Tab.Msg + + +type alias Model = + { button : Button.Model + , select : Select.Model + , multiSelect : MultiSelect.Model + , expansionPanel : ExpansionPanel.Model + , tab : Tab.Model + } + + +init : ( Model, Cmd Msg ) +init = + let + ( buttonModel, buttonMsg ) = + Button.init + + ( selectModel, selectMsg ) = + Select.init + + ( multiSelectModel, multiSelectMsg ) = + MultiSelect.init + + ( expansionPanelModel, expansionPanelMsg ) = + ExpansionPanel.init + + ( tabModel, tabMsg ) = + Tab.init + in + ( { button = buttonModel + , select = selectModel + , multiSelect = multiSelectModel + , expansionPanel = expansionPanelModel + , tab = tabModel + } + , [ Cmd.map Button buttonMsg + , Cmd.map Select selectMsg + , Cmd.map MultiSelect multiSelectMsg + , Cmd.map Tab tabMsg + ] + |> Cmd.batch + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Button buttonMsg -> + Button.update buttonMsg model.button + |> Tuple.mapBoth + (\a -> { model | button = a }) + (Cmd.map Button) + + Select selectMsg -> + Select.update selectMsg model.select + |> Tuple.mapBoth + (\a -> { model | select = a }) + (Cmd.map Select) + + MultiSelect multiSelectMsg -> + MultiSelect.update multiSelectMsg model.multiSelect + |> Tuple.mapBoth + (\a -> { model | multiSelect = a }) + (Cmd.map MultiSelect) + + ExpansionPanel expansionPanelMsg -> + ExpansionPanel.update expansionPanelMsg model.expansionPanel + |> Tuple.mapBoth + (\a -> { model | expansionPanel = a }) + (Cmd.map ExpansionPanel) + + Tab tabMsg -> + Tab.update tabMsg model.tab + |> Tuple.mapBoth + (\a -> { model | tab = a }) + (Cmd.map Tab) + + +subscriptions : Model -> Sub Msg +subscriptions model = + [ Button.subscriptions model.button |> Sub.map Button + , Select.subscriptions model.select |> Sub.map Select + , MultiSelect.subscriptions model.multiSelect |> Sub.map MultiSelect + , ExpansionPanel.subscriptions model.expansionPanel |> Sub.map ExpansionPanel + , Tab.subscriptions model.tab |> Sub.map Tab + ] + |> Sub.batch + + +view : + (Msg -> msg) + -> Style msg + -> Model + -> + { button : Element msg + , select : Element msg + , multiSelect : Element msg + , expansionPanel : Element msg + , tab : Element msg + } +view msgMapper style model = + { button = + Button.view + (Button >> msgMapper) + style + model.button + , select = + Select.view + (Select >> msgMapper) + style + model.select + , multiSelect = + MultiSelect.view + (MultiSelect >> msgMapper) + style + model.multiSelect + , expansionPanel = + ExpansionPanel.view + (ExpansionPanel >> msgMapper) + style + model.expansionPanel + , tab = + Tab.view + (Tab >> msgMapper) + style + model.tab + } + + +toCardList : + { idle : msg + , msgMapper : Msg -> msg + , style : Style msg + , model : Model + } + -> List ( String, Element msg, Element msg ) +toCardList { idle, msgMapper, style, model } = + [ { title = "Icon Button" + , example = .button + , test = Test.iconButton + } + , { title = "Select" + , example = .select + , test = Test.select + } + , { title = "Multi Select" + , example = .multiSelect + , test = Test.multiSelect + } + , { title = "Expansion Panel" + , example = .expansionPanel + , test = Test.expansionPanel + } + , { title = "Tab" + , example = .tab + , test = Test.tab + } + ] + |> List.map + (\{ title, example, test } -> + ( title + , model + |> view msgMapper style + |> example + , test idle style + |> Element.column Grid.simple + ) + ) diff --git a/example/src/Data/Style/ElmUiFramework.elm b/example/src/Data/Style/ElmUiFramework.elm index 5f11b08..b16d5cc 100644 --- a/example/src/Data/Style/ElmUiFramework.elm +++ b/example/src/Data/Style/ElmUiFramework.elm @@ -29,7 +29,7 @@ textButton = simpleButton : ButtonStyle msg simpleButton = - { container = Button.simple ++ Color.primary + { container = Button.simple ++ Color.success , labelRow = Grid.simple , ifDisabled = Color.disabled , ifActive = Color.primary @@ -211,7 +211,7 @@ row = cardColumn : ColumnStyle msg cardColumn = - { containerColumn = Grid.compact + { containerColumn = Grid.compact ++ [Element.height <| Element.fill] , element = Card.large ++ [Element.height <| Element.fill] , ifFirst = Group.top , ifLast = Group.bottom diff --git a/example/src/Example/Button.elm b/example/src/Example/Button.elm new file mode 100644 index 0000000..36211f0 --- /dev/null +++ b/example/src/Example/Button.elm @@ -0,0 +1,83 @@ +module Example.Button exposing (Model, Msg, init, subscriptions, update, view) + +import Element exposing (Element) +import FeatherIcons +import Widget +import Widget.Style exposing (ButtonStyle) + + +type alias Style style msg = + { style + | primaryButton : ButtonStyle msg + , button : ButtonStyle msg + } + + +type alias Model = + { isButtonEnabled : Bool + } + + +type Msg + = ChangedButtonStatus Bool + + +init : ( Model, Cmd Msg ) +init = + ( { isButtonEnabled = True + } + , Cmd.none + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + ChangedButtonStatus bool -> + ( { model | isButtonEnabled = bool } + , Cmd.none + ) + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Sub.none + + +view : (Msg -> msg) -> Style style msg -> Model -> Element msg +view msgMapper style model = + [ Widget.button style.primaryButton + { text = "disable me" + , icon = + FeatherIcons.slash + |> FeatherIcons.withSize 16 + |> FeatherIcons.toHtml [] + |> Element.html + |> Element.el [] + , onPress = + if model.isButtonEnabled then + ChangedButtonStatus False + |> msgMapper + |> Just + + else + Nothing + } + , Widget.iconButton style.button + { text = "reset" + , icon = + FeatherIcons.repeat + |> FeatherIcons.withSize 16 + |> FeatherIcons.toHtml [] + |> Element.html + |> Element.el [] + , onPress = + ChangedButtonStatus True + |> msgMapper + |> Just + } + ] + |> Element.row + [ Element.spaceEvenly + , Element.width <| Element.fill + ] diff --git a/example/src/Example/ExpansionPanel.elm b/example/src/Example/ExpansionPanel.elm new file mode 100644 index 0000000..69cb3c3 --- /dev/null +++ b/example/src/Example/ExpansionPanel.elm @@ -0,0 +1,53 @@ +module Example.ExpansionPanel exposing (Model, Msg, init, subscriptions, update, view) + +import Element exposing (Element) +import Widget +import Widget.Style exposing (ExpansionPanelStyle) + + +type alias Style style msg = + { style + | expansionPanel : ExpansionPanelStyle msg + } + + +type alias Model = + { isExpanded : Bool } + + +type Msg + = ToggleCollapsable Bool + + +init : ( Model, Cmd Msg ) +init = + ( { isExpanded = False } + , Cmd.none + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + ToggleCollapsable bool -> + ( { model + | isExpanded = bool + } + , Cmd.none + ) + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Sub.none + + +view : (Msg -> msg) -> Style style msg -> Model -> Element msg +view msgMapper style model = + { onToggle = ToggleCollapsable >> msgMapper + , isExpanded = model.isExpanded + , icon = Element.none + , text = "Title" + , content = Element.text <| "Hello World" + } + |> Widget.expansionPanel style.expansionPanel diff --git a/example/src/Example/MultiSelect.elm b/example/src/Example/MultiSelect.elm new file mode 100644 index 0000000..977d4ec --- /dev/null +++ b/example/src/Example/MultiSelect.elm @@ -0,0 +1,72 @@ +module Example.MultiSelect exposing (Model, Msg, init, subscriptions, update, view) + +import Element exposing (Element) +import Set exposing (Set) +import Widget +import Widget.Style exposing (ButtonStyle, RowStyle) + + +type alias Style style msg = + { style + | row : RowStyle msg + , button : ButtonStyle msg + } + + +type alias Model = + { selected : Set Int + } + + +type Msg + = ChangedSelected Int + + +init : ( Model, Cmd Msg ) +init = + ( { selected = Set.empty } + , Cmd.none + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + ChangedSelected int -> + ( { model + | selected = + model.selected + |> (if model.selected |> Set.member int then + Set.remove int + + else + Set.insert int + ) + } + , Cmd.none + ) + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Sub.none + + +view : (Msg -> msg) -> Style style msg -> Model -> Element msg +view msgMapper style model = + { selected = model.selected + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = ChangedSelected >> msgMapper >> Just + } + |> Widget.multiSelect + |> Widget.buttonRow + { list = style.row + , button = style.button + } diff --git a/example/src/Example/Select.elm b/example/src/Example/Select.elm new file mode 100644 index 0000000..fbebe32 --- /dev/null +++ b/example/src/Example/Select.elm @@ -0,0 +1,64 @@ +module Example.Select exposing (Model, Msg, init, subscriptions, update, view) + +import Element exposing (Attribute, Element) +import FeatherIcons +import Widget +import Widget.Style exposing (ButtonStyle, RowStyle) + + +type alias Style style msg = + { style + | row : RowStyle msg + , button : ButtonStyle msg + } + + +type alias Model = + { selected : Maybe Int } + + +type Msg + = ChangedSelected Int + + +init : ( Model, Cmd Msg ) +init = + ( { selected = Nothing } + , Cmd.none + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + ChangedSelected int -> + ( { model + | selected = Just int + } + , Cmd.none + ) + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Sub.none + + +view : (Msg -> msg) -> Style style msg -> Model -> Element msg +view msgMapper style model = + { selected = model.selected + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = ChangedSelected >> msgMapper >> Just + } + |> Widget.select + |> Widget.buttonRow + { list = style.row + , button = style.button + } diff --git a/example/src/Example/Tab.elm b/example/src/Example/Tab.elm new file mode 100644 index 0000000..9da1e79 --- /dev/null +++ b/example/src/Example/Tab.elm @@ -0,0 +1,76 @@ +module Example.Tab exposing (Model, Msg, init, subscriptions, update, view) + +import Element exposing (Element) +import Widget +import Widget.Style exposing (TabStyle) + + +type alias Style style msg = + { style + | tab : TabStyle msg + } + + +type alias Model = + { selected : Maybe Int + } + + +type Msg + = ChangedTab Int + + +init : ( Model, Cmd Msg ) +init = + ( { selected = Nothing + } + , Cmd.none + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + ChangedTab int -> + ( { model | selected = Just int } + , Cmd.none + ) + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Sub.none + + +view : (Msg -> msg) -> Style style msg -> Model -> Element msg +view msgMapper style model = + Widget.tab style.tab + { tabs = + { selected = model.selected + , options = + [ 1, 2, 3 ] + |> List.map + (\int -> + { text = "Tab " ++ (int |> String.fromInt) + , icon = Element.none + } + ) + , onSelect = ChangedTab >> msgMapper >> Just + } + , content = + \selected -> + (case selected of + Just 0 -> + "This is Tab 1" + + Just 1 -> + "This is the second tab" + + Just 2 -> + "The thrid and last tab" + + _ -> + "Please select a tab" + ) + |> Element.text + } diff --git a/example/src/Example.elm b/example/src/Main.elm similarity index 92% rename from example/src/Example.elm rename to example/src/Main.elm index 1b3674a..10dc053 100644 --- a/example/src/Example.elm +++ b/example/src/Main.elm @@ -1,4 +1,4 @@ -module Example exposing (main) +module Main exposing (main) import Array import Browser @@ -6,6 +6,8 @@ import Browser.Dom as Dom exposing (Viewport) import Browser.Events as Events import Browser.Navigation as Navigation import Data.Section as Section exposing (Section(..)) +import Data.Style as Style exposing (Style) +import Data.Theme as Theme exposing (Theme(..)) import Element exposing (Attribute, DeviceClass(..), Element) import Element.Border as Border import Element.Font as Font @@ -32,15 +34,10 @@ import Widget import Widget.ScrollingNav as ScrollingNav import Widget.Snackbar as Snackbar import Widget.Style exposing (ButtonStyle) -import Data.Style as Style exposing (Style) -import Data.Theme as Theme exposing (Theme(..)) - - type alias LoadedModel = { stateless : Stateless.Model - , reusable : Reusable.Model , scrollingNav : ScrollingNav.Model Section , layout : Layout LoadedMsg , displayDialog : Bool @@ -61,7 +58,6 @@ type Model type LoadedMsg = StatelessSpecific Stateless.Msg - | ReusableSpecific Reusable.Msg | UpdateScrollingNav (ScrollingNav.Model Section -> ScrollingNav.Model Section) | TimePassed Int | AddSnackbar ( String, Bool ) @@ -97,9 +93,11 @@ initialModel { viewport } = Err _ -> Idle } + + ( stateless, statelessCmd ) = + Stateless.init in - ( { stateless = Stateless.init - , reusable = Reusable.init + ( { stateless = stateless , scrollingNav = scrollingNav , layout = Layout.init , displayDialog = False @@ -114,7 +112,10 @@ initialModel { viewport } = } , theme = ElmUiFramework } - , cmd + , [ cmd + , statelessCmd |> Cmd.map StatelessSpecific + ] + |> Cmd.batch ) @@ -127,7 +128,6 @@ init () = view : Model -> Html Msg view model = - case model of Loading -> Element.none |> Framework.responsiveLayout [] @@ -173,8 +173,6 @@ view model = ReusableViews -> Reusable.view m.theme { addSnackbar = AddSnackbar - , model = m.reusable - , msgMapper = ReusableSpecific } StatelessViews -> @@ -198,10 +196,10 @@ view model = , items |> (if m.search.current /= "" then List.filter - (\(a,_,_) -> + (\( a, _, _ ) -> a - |> String.toLower - |> String.contains (m.search.current |> String.toLower) + |> String.toLower + |> String.contains (m.search.current |> String.toLower) ) else @@ -209,10 +207,13 @@ view model = ) |> List.map (\( name, elem, more ) -> - [ Element.text name - |> Element.el Heading.h3 - , elem + [ [ Element.text name + |> Element.el (Heading.h3 ++ [ Element.height <| Element.shrink ]) + , elem + ] + |> Element.column Grid.simple , more + |> Element.el [ Element.height <| Element.fill ] ] |> Widget.column style.cardColumn ) @@ -226,7 +227,7 @@ view model = |> Element.column Framework.container ] |> Element.column Grid.compact - , style =style + , style = style , layout = m.layout , window = m.window , menu = @@ -247,17 +248,21 @@ view model = , text = "Github" , icon = Icons.github |> Element.html |> Element.el [] } - , { onPress = if m.theme /= ElmUiFramework then - Just <| SetTheme <| ElmUiFramework - else - Nothing + , { 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 + , { onPress = + if m.theme /= Template then + Just <| SetTheme <| Template + + else + Nothing , text = "Template Theme" , icon = Icons.penTool |> Element.html |> Element.el [] } @@ -291,17 +296,6 @@ view model = updateLoaded : LoadedMsg -> LoadedModel -> ( LoadedModel, Cmd LoadedMsg ) updateLoaded msg model = case msg of - ReusableSpecific m -> - ( model.reusable - |> Reusable.update m - |> (\reusable -> - { model - | reusable = reusable - } - ) - , Cmd.none - ) - StatelessSpecific m -> model.stateless |> Stateless.update m @@ -408,7 +402,7 @@ updateLoaded msg model = } , Cmd.none ) - + SetTheme theme -> ( { model | theme = theme } , Cmd.none diff --git a/example/src/Reusable.elm b/example/src/Reusable.elm index a87b5ef..cf8e3fa 100644 --- a/example/src/Reusable.elm +++ b/example/src/Reusable.elm @@ -1,4 +1,4 @@ -module Reusable exposing (Model, Msg, init, update, view) +module Reusable exposing ( view) import Browser import Element exposing (Color, Element) @@ -22,17 +22,9 @@ import Time import Widget import Widget.ScrollingNav as ScrollingNav import Widget.Snackbar as Snackbar -import Widget.SortTable as SortTable import Data.Style exposing (Style) import Data.Theme as Theme exposing (Theme) -type alias Model = - SortTable.Model - - -type Msg - = SortBy { title : String, asc : Bool } - type alias Item = { name : String @@ -41,17 +33,6 @@ type alias Item = } -update : Msg -> Model -> Model -update msg model = - case msg of - SortBy m -> - m - - -init : Model -init = - SortTable.sortBy { title = "Name", asc = True } - snackbar : Style msg -> (( String, Bool ) -> msg) -> ( String, Element msg,Element msg ) snackbar style addSnackbar = @@ -82,78 +63,7 @@ snackbar style addSnackbar = ) -sortTable : Style Msg -> SortTable.Model -> ( String, Element Msg,Element Msg ) -sortTable style model = - ( "Sort Table" - , SortTable.view - { content = - [ { id = 1, name = "Antonio", rating = 2.456 } - , { id = 2, name = "Ana", rating = 1.34 } - , { id = 3, name = "Alfred", rating = 4.22 } - , { id = 4, name = "Thomas", rating = 3 } - ] - , columns = - [ SortTable.intColumn - { title = "Id" - , value = .id - , toString = \int -> "#" ++ String.fromInt int - } - , SortTable.stringColumn - { title = "Name" - , value = .name - , toString = identity - } - , SortTable.floatColumn - { title = "rating" - , value = .rating - , toString = String.fromFloat - } - ] - , model = model - } - |> (\{ data, columns } -> - { data = data - , columns = - columns - |> List.map - (\config -> - { header = - Input.button [ Font.bold ] - { onPress = - { title = config.header - , asc = - if config.header == model.title then - not model.asc - else - True - } - |> SortBy - |> Just - , label = - if config.header == model.title then - [ config.header |> Element.text - , Element.html <| - if model.asc then - Heroicons.cheveronUp [ Attributes.width 16 ] - - else - Heroicons.cheveronDown [ Attributes.width 16 ] - ] - |> Element.row (Grid.simple ++ [ Font.bold ]) - - else - config.header |> Element.text - } - , view = config.view >> Element.text - , width = Element.fill - } - ) - } - ) - |> Element.table Grid.simple - , Element.none - ) scrollingNavCard : Style msg -> ( String, Element msg, Element msg ) @@ -169,15 +79,13 @@ scrollingNavCard style = view : Theme -> { addSnackbar : ( String, Bool ) -> msg - , msgMapper : Msg -> msg - , model : Model } -> { title : String , description : String , items : List ( String, Element msg,Element msg ) } -view theme { addSnackbar, msgMapper, model } = +view theme { addSnackbar } = let style = Theme.toStyle theme in @@ -185,8 +93,6 @@ view theme { addSnackbar, msgMapper, model } = , description = "Reusable views have an internal state but no update function. You will need to do some wiring, but nothing complicated." , items = [ 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 8487d25..c70301c 100644 --- a/example/src/Stateless.elm +++ b/example/src/Stateless.elm @@ -1,96 +1,66 @@ module Stateless exposing (Model, Msg, init, update, view) -import Array exposing (Array) +import Array +import Data.Example as Example import Data.Style exposing (Style) +import Data.Theme as Theme exposing (Theme) 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 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 Icons import Layout exposing (Part(..)) import Set exposing (Set) import Widget -import Widget.Style exposing (ButtonStyle) -import Data.Theme as Theme exposing (Theme) + type alias Model = - { selected : Maybe Int - , multiSelected : Set Int - , chipTextInput : Set String - , isExpanded : Bool + { chipTextInput : Set String , carousel : Int - , tab : Maybe Int - , button : Bool , textInput : String + , table : { title : String, asc : Bool } + , example : Example.Model } type Msg - = ChangedSelected Int - | ChangedMultiSelected Int - | ToggleCollapsable Bool - | ToggleTextInputChip String - | ChangedTab Int + = ToggleTextInputChip String | SetCarousel Int - | ToggleButton Bool | SetTextInput String + | ChangedSorting String + | ExampleSpecific Example.Msg | Idle -init : Model +init : ( Model, Cmd Msg ) init = - { selected = Nothing - , multiSelected = Set.empty - , chipTextInput = Set.empty - , isExpanded = False - , carousel = 0 - , tab = Just 1 - , button = True - , textInput = "" - } + let + ( example, cmd ) = + Example.init + in + ( { chipTextInput = Set.empty + , carousel = 0 + , textInput = "" + , table = { title = "Name", asc = True } + , example = example + } + , cmd |> Cmd.map ExampleSpecific + ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of - ChangedSelected int -> - ( { model - | selected = Just int - } - , Cmd.none - ) - - ChangedMultiSelected int -> - ( { model - | multiSelected = - model.multiSelected - |> (if model.multiSelected |> Set.member int then - Set.remove int - - else - Set.insert int - ) - } - , Cmd.none - ) - - ToggleCollapsable bool -> - ( { model - | isExpanded = bool - } - , Cmd.none + ExampleSpecific exampleMsg -> + let + ( exampleModel, exampleCmd ) = + Example.update exampleMsg model.example + in + ( { model | example = exampleModel } + , exampleCmd |> Cmd.map ExampleSpecific ) ToggleTextInputChip string -> @@ -118,127 +88,30 @@ update msg model = , Cmd.none ) - ChangedTab int -> - ( { model | tab = Just int }, Cmd.none ) - - ToggleButton bool -> - ( { model | button = bool }, Cmd.none ) - SetTextInput string -> ( { model | textInput = string }, Cmd.none ) + ChangedSorting string -> + ( { model + | table = + { title = string + , asc = + if model.table.title == string then + not model.table.asc + + else + True + } + } + , Cmd.none + ) + Idle -> ( model, Cmd.none ) -select : Style Msg -> Model -> ( String, Element Msg,Element Msg ) -select style model = - let - buttonStyle = - style.button - in - ( "Select" - , { selected = model.selected - , options = - [ 1, 2, 42 ] - |> List.map - (\int -> - { text = String.fromInt int - , icon = Element.none - } - ) - , onSelect = ChangedSelected >> Just - } - |> Widget.select - |> Widget.buttonRow - { list = style.row - , button = style.button - } - , Element.none - ) - - -multiSelect : Style Msg -> Model -> ( String, Element Msg, Element Msg ) -multiSelect style model = - let - buttonStyle = - style.button - in - ( "Multi Select" - , { selected = model.multiSelected - , options = - [ 1, 2, 42 ] - |> List.map - (\int -> - { text = String.fromInt int - , icon = Element.none - } - ) - , onSelect = ChangedMultiSelected >> Just - } - |> Widget.multiSelect - |> Widget.buttonRow - { list = style.row - , button = style.button - } - , Element.none - ) - -expansionPanel : Style Msg -> Model -> (String,Element Msg,Element Msg) -expansionPanel style model = - ( "Expansion Panel" - , { onToggle = ToggleCollapsable - , isExpanded = model.isExpanded - , icon = Element.none - , text = "Title" - , content = Element.text <| "Hello World" - } - |>Widget.expansionPanel style.expansionPanel - , Element.none - ) - - - - -tab : Style Msg -> Model -> ( String, Element Msg, Element Msg ) -tab style model = - ( "Tab" - , Widget.tab style.tab - { tabs = - { selected = model.tab - , options = - [ 1, 2, 3 ] - |> List.map - (\int -> - { text = "Tab " ++ (int |> String.fromInt) - , icon = Element.none - } - ) - , onSelect = ChangedTab >> Just - } - , content = - \selected -> - (case selected of - Just 0 -> - "This is Tab 1" - - Just 1 -> - "This is the second tab" - - Just 2 -> - "The thrid and last tab" - - _ -> - "Please select a tab" - ) - |> Element.text - } - , Element.none - ) - - -modal : Style msg -> (Maybe Part -> msg) -> Model -> ( String, Element msg,Element msg ) -modal style changedSheet model = +modal : Style msg -> (Maybe Part -> msg) -> Model -> ( String, Element msg, Element msg ) +modal style changedSheet _ = ( "Modal" , [ Widget.button style.button { onPress = Just <| changedSheet <| Just LeftSheet @@ -252,12 +125,12 @@ modal style changedSheet model = } ] |> Element.column Grid.simple - ,Element.none + , Element.none ) dialog : Style msg -> msg -> Model -> ( String, Element msg, Element msg ) -dialog style showDialog model = +dialog style showDialog _ = ( "Dialog" , Widget.button style.button { onPress = Just showDialog @@ -330,66 +203,6 @@ carousel style 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 - , 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 : Style Msg -> Model -> ( String, Element Msg, Element Msg ) textInput style model = ( "Chip Text Input" @@ -432,16 +245,112 @@ textInput style model = |> Element.wrappedRow [ Element.spacing 10 ] ] |> Element.column Grid.simple + , [ Element.row Grid.spacedEvenly + [ "Nothing Selected" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { chips = [] + , text = "" + , placeholder = Nothing + , label = "Label" + , onChange = always Idle + } + |> Widget.textInput style.textInput + ] + , Element.row Grid.spacedEvenly + [ "Some chips" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { chips = + [ { icon = Icons.triangle |> Element.html |> Element.el [] + , text = "A" + , onPress = Just Idle + } + , { icon = Icons.circle |> Element.html |> Element.el [] + , text = "B" + , onPress = Just Idle + } + ] + , text = "" + , placeholder = Nothing + , label = "Label" + , onChange = always Idle + } + |> Widget.textInput style.textInput + ] + ] + |> Element.column Grid.simple + ) + + +list : Style Msg -> Model -> ( String, Element Msg, Element Msg ) +list style _ = + ( "List" + , [ Element.text <| "A" + , Element.text <| "B" + , Element.text <| "C" + ] + |> Widget.column style.cardColumn + , Element.none + ) + + +sortTable : Style Msg -> Model -> ( String, Element Msg, Element Msg ) +sortTable _ model = + ( "Sort Table" + , Widget.sortTable + { containerTable = Grid.simple + , headerButton = + { container = [] + , labelRow = [] + , ifDisabled = [] + , ifActive = [] + } + , ascIcon = Heroicons.cheveronUp [ Attributes.width 16 ] |> Element.html + , descIcon = Heroicons.cheveronDown [ Attributes.width 16 ] |> Element.html + , defaultIcon = Element.none + } + { content = + [ { id = 1, name = "Antonio", rating = 2.456 } + , { id = 2, name = "Ana", rating = 1.34 } + , { id = 3, name = "Alfred", rating = 4.22 } + , { id = 4, name = "Thomas", rating = 3 } + ] + , columns = + [ Widget.intColumn + { title = "Id" + , value = .id + , toString = \int -> "#" ++ String.fromInt int + , width = Element.fill + } + , Widget.stringColumn + { title = "Name" + , value = .name + , toString = identity + , width = Element.fill + } + , Widget.floatColumn + { title = "rating" + , value = .rating + , toString = String.fromFloat + , width = Element.fill + } + ] + , asc = model.table.asc + , sortBy = model.table.title + , onChange = ChangedSorting + } , Element.none ) view : - Theme -> - { msgMapper : Msg -> msg - , showDialog : msg - , changedSheet : Maybe Part -> msg - } + Theme + -> + { msgMapper : Msg -> msg + , showDialog : msg + , changedSheet : Maybe Part -> msg + } -> Model -> { title : String @@ -450,9 +359,10 @@ view : } view theme { msgMapper, showDialog, changedSheet } model = let - style = Theme.toStyle theme + style = + Theme.toStyle theme - map (a,b,c) = + map ( a, b, c ) = ( a , b |> Element.map msgMapper , c |> Element.map msgMapper @@ -461,14 +371,17 @@ view theme { msgMapper, showDialog, changedSheet } model = { title = "Stateless Views" , description = "Stateless views are simple functions that view some content. No wiring required." , items = - [ 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 - ] + Example.toCardList + { idle = Idle |> msgMapper + , msgMapper = ExampleSpecific >> msgMapper + , style = style + , model = model.example + } + ++ [ modal style changedSheet model + , carousel style model |> map + , dialog style showDialog model + , textInput style model |> map + , list style model |> map + , sortTable style model |> map + ] } diff --git a/example/src/View/Test.elm b/example/src/View/Test.elm new file mode 100644 index 0000000..2264e7f --- /dev/null +++ b/example/src/View/Test.elm @@ -0,0 +1,390 @@ +module View.Test exposing (expansionPanel, iconButton, multiSelect, select, tab) + +import Array +import Data.Style exposing (Style) +import Data.Theme as Theme exposing (Theme) +import Element exposing (Element) +import Element.Background as Background +import Framework.Card as Card +import Framework.Color as Color +import Framework.Grid as Grid +import Heroicons.Solid as Heroicons +import Html.Attributes as Attributes +import Icons +import Layout exposing (Part(..)) +import Set exposing (Set) +import Widget + + +iconButton : msg -> Style msg -> List (Element msg) +iconButton idle style = + [ Element.row Grid.spacedEvenly + [ "Button" + |> Element.text + , Widget.button style.button + { text = "Button" + , icon = Icons.triangle |> Element.html |> Element.el [] + , onPress = Just idle + } + ] + , Element.row Grid.spacedEvenly + [ "Text button" + |> Element.text + , Widget.textButton style.button + { text = "Button" + , onPress = Just idle + } + ] + , Element.row Grid.spacedEvenly + [ "Icon button" + |> Element.text + , Widget.iconButton style.button + { text = "Button" + , icon = Icons.triangle |> Element.html |> Element.el [] + , onPress = Just idle + } + ] + , Element.row Grid.spacedEvenly + [ "Disabled button" + |> Element.text + , Widget.button style.button + { text = "Button" + , icon = Icons.triangle |> Element.html |> Element.el [] + , onPress = Nothing + } + ] + , Element.row Grid.spacedEvenly + [ "Inactive Select button" + |> Element.text + , Widget.selectButton style.button + ( False + , { text = "Button" + , icon = Icons.triangle |> Element.html |> Element.el [] + , onPress = Just idle + } + ) + ] + , Element.row Grid.spacedEvenly + [ "Active Select button" + |> Element.text + , Widget.selectButton style.button + ( True + , { text = "Button" + , icon = Icons.triangle |> Element.html |> Element.el [] + , onPress = Just idle + } + ) + ] + ] + + +select : msg -> Style msg -> List (Element msg) +select idle style = + [ Element.row Grid.spacedEvenly + [ "First selected" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Just 0 + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + |> Widget.select + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + , Element.row Grid.spacedEvenly + [ "Nothing selected" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Nothing + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + |> Widget.select + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + , Element.row Grid.spacedEvenly + [ "Invalid selection" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Just -1 + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + |> Widget.select + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + , Element.row Grid.spacedEvenly + [ "Disabled selection" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Just 0 + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always Nothing + } + |> Widget.select + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + , Element.row Grid.spacedEvenly + [ "Empty Options" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Nothing + , options = + [] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + |> Widget.select + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + ] + + +multiSelect : msg -> Style msg -> List (Element msg) +multiSelect idle style = + [ Element.row Grid.spacedEvenly + [ "Some selected" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Set.fromList [ 0, 1 ] + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + |> Widget.multiSelect + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + , Element.row Grid.spacedEvenly + [ "Nothing selected" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Set.empty + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + |> Widget.multiSelect + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + , Element.row Grid.spacedEvenly + [ "Invalid selection" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Set.singleton -1 + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + |> Widget.multiSelect + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + , Element.row Grid.spacedEvenly + [ "Disabled selection" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Set.singleton 0 + , options = + [ 1, 2, 42 ] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always Nothing + } + |> Widget.multiSelect + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + , Element.row Grid.spacedEvenly + [ "Empty Options" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { selected = Set.empty + , options = + [] + |> List.map + (\int -> + { text = String.fromInt int + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + |> Widget.multiSelect + |> Widget.buttonRow + { list = style.row + , button = style.button + } + ] + ] + + +expansionPanel : msg -> Style msg -> List (Element msg) +expansionPanel idle style = + [ Element.row Grid.spacedEvenly + [ "Collapsed" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { onToggle = always idle + , isExpanded = False + , icon = Icons.triangle |> Element.html |> Element.el [] + , text = "Button" + , content = Element.text <| "Hidden Message" + } + |> Widget.expansionPanel style.expansionPanel + ] + , Element.row Grid.spacedEvenly + [ "Expanded" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , { onToggle = always idle + , isExpanded = True + , icon = Icons.triangle |> Element.html |> Element.el [] + , text = "Button" + , content = Element.text <| "Hidden Message" + } + |> Widget.expansionPanel style.expansionPanel + ] + ] + + +tab : msg -> Style msg -> List (Element msg) +tab idle style = + [ Element.row Grid.spacedEvenly + [ "Nothing selected" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , Widget.tab style.tab + { tabs = + { selected = Nothing + , options = + [ 1, 2, 3 ] + |> List.map + (\int -> + { text = int |> String.fromInt + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + , content = + \selected -> + (case selected of + Nothing -> + "Please select a tab" + + _ -> + "" + ) + |> Element.text + } + ] + , Element.row Grid.spacedEvenly + [ "Tab selected" + |> Element.text + |> Element.el [ Element.width <| Element.fill ] + , Widget.tab style.tab + { tabs = + { selected = Just 0 + , options = + [ 1, 2, 3 ] + |> List.map + (\int -> + { text = int |> String.fromInt + , icon = Element.none + } + ) + , onSelect = always idle >> Just + } + , content = + \selected -> + (case selected of + Just 0 -> + "First Tab selected" + + _ -> + "Please select a tab" + ) + |> Element.text + } + ] + ] diff --git a/src/Internal/SortTable.elm b/src/Internal/SortTable.elm new file mode 100644 index 0000000..e2e41c1 --- /dev/null +++ b/src/Internal/SortTable.elm @@ -0,0 +1,181 @@ +module Internal.SortTable exposing + ( Column + , ColumnType + , SortTable + , floatColumn + , intColumn + , sortTable + , stringColumn + , unsortableColumn + ) + +import Element exposing (Element, Length) +import Internal.Button as Button +import Widget.Style exposing (SortTableStyle) + + +{-| A Sortable list allows you to sort coulmn. +-} +type ColumnType a + = StringColumn { value : a -> String, toString : String -> String } + | IntColumn { value : a -> Int, toString : Int -> String } + | FloatColumn { value : a -> Float, toString : Float -> String } + | UnsortableColumn (a -> String) + + +{-| The Model contains the sorting column name and if ascending or descending. +-} +type alias SortTable a msg = + { content : List a + , columns : List (Column a) + , sortBy : String + , asc : Bool + , onChange : String -> msg + } + + +type Column a + = Column + { title : String + , content : ColumnType a + , width : Length + } + + +unsortableColumn : { title : String, toString : a -> String, width : Length } -> Column a +unsortableColumn { title, toString, width } = + Column + { title = title + , content = UnsortableColumn toString + , width = width + } + + +{-| A Column containing a Int +-} +intColumn : { title : String, value : a -> Int, toString : Int -> String, width : Length } -> Column a +intColumn { title, value, toString, width } = + Column + { title = title + , content = IntColumn { value = value, toString = toString } + , width = width + } + + +{-| A Column containing a Float +-} +floatColumn : { title : String, value : a -> Float, toString : Float -> String, width : Length } -> Column a +floatColumn { title, value, toString, width } = + Column + { title = title + , content = FloatColumn { value = value, toString = toString } + , width = width + } + + +{-| A Column containing a String +-} +stringColumn : { title : String, value : a -> String, toString : String -> String, width : Length } -> Column a +stringColumn { title, value, toString, width } = + Column + { title = title + , content = StringColumn { value = value, toString = toString } + , width = width + } + + +{-| The View +-} +sortTable : + SortTableStyle msg + -> SortTable a msg + -> Element msg +sortTable style model = + let + findTitle : List (Column a) -> Maybe (ColumnType a) + findTitle list = + case list of + [] -> + Nothing + + (Column head) :: tail -> + if head.title == model.sortBy then + Just head.content + + else + findTitle tail + in + Element.table style.containerTable + { data = + model.content + |> (model.columns + |> findTitle + |> Maybe.andThen + (\c -> + case c of + StringColumn { value } -> + Just <| List.sortBy value + + IntColumn { value } -> + Just <| List.sortBy value + + FloatColumn { value } -> + Just <| List.sortBy value + + UnsortableColumn _ -> + Nothing + ) + |> Maybe.withDefault identity + ) + |> (if model.asc then + identity + + else + List.reverse + ) + , columns = + model.columns + |> List.map + (\(Column column) -> + { header = + Button.button style.headerButton + { text = column.title + , icon = + if column.title == model.sortBy then + if model.asc then + style.ascIcon + + else + style.descIcon + + else + style.defaultIcon + , onPress = + case column.content of + UnsortableColumn _ -> + Nothing + + _ -> + Just <| model.onChange <| column.title + } + , width = column.width + , view = + (case column.content of + IntColumn { value, toString } -> + value >> toString + + FloatColumn { value, toString } -> + value >> toString + + StringColumn { value, toString } -> + value >> toString + + UnsortableColumn toString -> + toString + ) + >> Element.text + >> List.singleton + >> Element.paragraph [] + } + ) + } diff --git a/src/Widget.elm b/src/Widget.elm index 95a6888..38b08c0 100644 --- a/src/Widget.elm +++ b/src/Widget.elm @@ -3,8 +3,10 @@ module Widget exposing , Select, MultiSelect, selectButton, select, multiSelect , Dialog, modal, dialog , ExpansionPanel, expansionPanel + , row, column, buttonRow, buttonColumn + , ColumnType, sortTable, floatColumn, intColumn, stringColumn, unsortableColumn , TextInputStyle, textInput, carousel, tab - , Tab, buttonColumn, buttonRow, column, row + , Tab ) {-| This module contains functions for displaying data. @@ -30,6 +32,16 @@ module Widget exposing @docs ExpansionPanel, expansionPanel +# List + +@docs row, column, buttonRow, buttonColumn + + +# SortTable + +@docs ColumnType, sortTable, floatColumn, intColumn, stringColumn, unsortableColumn + + # Other Widgets @docs TextInputStyle, textInput, carousel, tab @@ -37,16 +49,17 @@ module Widget exposing -} import Array exposing (Array) -import Element exposing (Attribute, Element) +import Element exposing (Attribute, Element, Length) 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.SortTable as SortTable import Internal.TextInput as TextInput import Set exposing (Set) -import Widget.Style exposing (ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, RowStyle, TabStyle) +import Widget.Style exposing (ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, RowStyle, SortTableStyle, TabStyle) @@ -322,6 +335,87 @@ buttonColumn = +{---------------------------------------------------------- +- SORT TABLE +----------------------------------------------------------} + + +{-| A Sortable list allows you to sort coulmn. +-} +type alias ColumnType a = + SortTable.ColumnType a + + +type alias Column a = + SortTable.Column a + + +unsortableColumn : + { title : String + , toString : a -> String + , width : Length + } + -> Column a +unsortableColumn = + SortTable.unsortableColumn + + +{-| A Column containing a Int +-} +intColumn : + { title : String + , value : a -> Int + , toString : Int -> String + , width : Length + } + -> Column a +intColumn = + SortTable.intColumn + + +{-| A Column containing a Float +-} +floatColumn : + { title : String + , value : a -> Float + , toString : Float -> String + , width : Length + } + -> Column a +floatColumn = + SortTable.floatColumn + + +{-| A Column containing a String +-} +stringColumn : + { title : String + , value : a -> String + , toString : String -> String + , width : Length + } + -> Column a +stringColumn = + SortTable.stringColumn + + +{-| The View +-} +sortTable : + SortTableStyle msg + -> + { content : List a + , columns : List (Column a) + , sortBy : String + , asc : Bool + , onChange : String -> msg + } + -> Element msg +sortTable = + SortTable.sortTable + + + {---------------------------------------------------------- - OTHER STATELESS WIDGETS ----------------------------------------------------------} diff --git a/src/Widget/SortTable.elm b/src/Widget/SortTable.elm deleted file mode 100644 index 1b2e41e..0000000 --- a/src/Widget/SortTable.elm +++ /dev/null @@ -1,215 +0,0 @@ -module Widget.SortTable exposing - ( Model, init, view, sortBy - , intColumn, floatColumn, stringColumn - ) - -{-| A Sortable list allows you to sort coulmn. - - SortTable.view - { content = - [ {id = 1, name = "Antonio", rating = 2.456} - , {id = 2, name = "Ana", rating = 1.34} - , {id = 3, name = "Alfred", rating = 4.22} - , {id = 4, name = "Thomas", rating = 3 } - ] - , columns = - [ SortTable.intColumn - { title = "Id" - , value = .id - , toString = \int -> "#" ++ String.fromInt int - } - , SortTable.stringColumn - { title = "Name" - , value = .name - , toString = identity - } - , SortTable.floatColumn - { title = "rating" - , value = .rating - , toString = String.fromFloat - } - ] - , model = model - } - |> (\{data,columns} -> - {data = data - ,columns = columns - |> List.map (\config-> - { header = - Input.button [Font.bold] - { onPress = - { title = config.header - , asc = - if config.header == model.title then - not model.asc - else - True - } - |> SortBy - |> Just - , label = - if config.header == model.title then - [ config.header |> Element.text - , Element.text <| - if model.asc then - "/\" - else - "\/" - ] - |> Element.row [Font.bold] - else - config.header |> Element.text - } - , view = config.view >> Element.text - , width = Element.fill - } - ) - }) - |> Element.table [] - - -# Basics - -@docs Model, init, view, sortBy - - -# Columns - -@docs intColumn, floatColumn, stringColumn - --} - - -type ColumnType a - = StringColumn { value : a -> String, toString : String -> String } - | IntColumn { value : a -> Int, toString : Int -> String } - | FloatColumn { value : a -> Float, toString : Float -> String } - - -{-| The Model contains the sorting column name and if ascending or descending. --} -type alias Model = - { title : String - , asc : Bool - } - - -type alias Column a = - { title : String - , content : ColumnType a - } - - -{-| The initial State setting the sorting column name to the empty string. --} -init : Model -init = - { title = "", asc = True } - - -{-| A Column containing a Int --} -intColumn : { title : String, value : a -> Int, toString : Int -> String } -> Column a -intColumn { title, value, toString } = - { title = title - , content = IntColumn { value = value, toString = toString } - } - - -{-| A Column containing a Float --} -floatColumn : { title : String, value : a -> Float, toString : Float -> String } -> Column a -floatColumn { title, value, toString } = - { title = title - , content = FloatColumn { value = value, toString = toString } - } - - -{-| A Column containing a String --} -stringColumn : { title : String, value : a -> String, toString : String -> String } -> Column a -stringColumn { title, value, toString } = - { title = title - , content = StringColumn { value = value, toString = toString } - } - - -{-| Change the sorting criteras. - - sortBy = - identity - --} -sortBy : { title : String, asc : Bool } -> Model -sortBy = - identity - - -{-| The View --} -view : - { content : List a - , columns : List (Column a) - , model : Model - } - -> - { data : List a - , columns : List { header : String, view : a -> String } - } -view { content, columns, model } = - let - findTitle : List (Column a) -> Maybe (ColumnType a) - findTitle list = - case list of - [] -> - Nothing - - head :: tail -> - if head.title == model.title then - Just head.content - - else - findTitle tail - in - { data = - content - |> (columns - |> findTitle - |> Maybe.map - (\c -> - case c of - StringColumn { value } -> - List.sortBy value - - IntColumn { value } -> - List.sortBy value - - FloatColumn { value } -> - List.sortBy value - ) - |> Maybe.withDefault identity - ) - |> (if model.asc then - identity - - else - List.reverse - ) - , columns = - columns - |> List.map - (\column -> - { header = column.title - , view = - case column.content of - IntColumn { value, toString } -> - value >> toString - - FloatColumn { value, toString } -> - value >> toString - - StringColumn { value, toString } -> - value >> toString - } - ) - } diff --git a/src/Widget/Style.elm b/src/Widget/Style.elm index d88e328..5e8af81 100644 --- a/src/Widget/Style.elm +++ b/src/Widget/Style.elm @@ -1,4 +1,4 @@ -module Widget.Style exposing (ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, RowStyle, SnackbarStyle, Style, TabStyle, TextInputStyle) +module Widget.Style exposing (ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, RowStyle, SnackbarStyle, SortTableStyle, Style, TabStyle, TextInputStyle) import Element exposing (Attribute, Element) import Html exposing (Html) @@ -72,6 +72,15 @@ type alias ColumnStyle msg = } +type alias SortTableStyle msg = + { containerTable : List (Attribute msg) + , headerButton : ButtonStyle msg + , ascIcon : Element Never + , descIcon : Element Never + , defaultIcon : Element Never + } + + type alias Style style msg = { style | snackbar : SnackbarStyle msg