diff --git a/docs/assets/sortTableV2.png b/docs/assets/sortTableV2.png new file mode 100644 index 0000000..c145f71 Binary files /dev/null and b/docs/assets/sortTableV2.png differ diff --git a/explorer/src/Main.elm b/explorer/src/Main.elm index fa947d6..651432f 100644 --- a/explorer/src/Main.elm +++ b/explorer/src/Main.elm @@ -14,6 +14,7 @@ import Page.ProgressIndicator import Page.Select import Page.Snackbar import Page.SortTable +import Page.SortTableV2 import Page.Switch import Page.Tab import Page.TextInput @@ -29,6 +30,7 @@ pages = |> UIExplorer.nextPage "Password Input" Page.PasswordInput.page |> UIExplorer.nextPage "Text Input" Page.TextInput.page |> UIExplorer.nextPage "Sort Table" Page.SortTable.page + |> UIExplorer.nextPage "Sort Table V2" Page.SortTableV2.page |> UIExplorer.nextPage "Snackbar" Page.Snackbar.page |> UIExplorer.nextPage "Item" Page.Item.page |> UIExplorer.nextPage "ProgressIndicator" Page.ProgressIndicator.page diff --git a/explorer/src/Page/SortTableV2.elm b/explorer/src/Page/SortTableV2.elm new file mode 100644 index 0000000..95a2fbb --- /dev/null +++ b/explorer/src/Page/SortTableV2.elm @@ -0,0 +1,286 @@ +module Page.SortTableV2 exposing (page) + +{-| This is an example Page. If you want to add your own pages, simple copy and modify this one. +-} + +import Element exposing (Element) +import Material.Icons +import Material.Icons.Types exposing (Coloring(..)) +import Page +import UIExplorer.Story as Story exposing (StorySelectorModel, StorySelectorMsg) +import UIExplorer.Tile as Tile exposing (Context, Tile, TileMsg) +import Widget +import Widget.Icon +import Widget.Material as Material + + +{-| The title of this page +-} +title : String +title = + "Sort Table V2" + + +{-| The description. I've taken this description directly from the [Material-UI-Specification](https://material.io/components/buttons) +-} +description : String +description = + "A simple sort table with custom elements in columns." + + +{-| List of view functions. Essentially, anything that takes a Button as input. +-} +viewFunctions = + let + viewTable style content columns asc sortBy { palette } () = + Widget.sortTableV2 (style palette) + { content = content + , columns = columns + , asc = asc + , sortBy = sortBy + , onChange = always () + } + --Don't forget to change the title + |> Page.viewTile "Widget.sortTableV2" + in + [ viewTable ] + |> List.foldl Story.addTile + Story.initStaticTiles + + +{-| Let's you play around with the options. +Note that the order of these stories must follow the order of the arguments from the view functions. +-} +book : Tile.Group ( StorySelectorModel, () ) (TileMsg StorySelectorMsg ()) flags +book = + Story.book (Just "Options") + viewFunctions + --Adding a option for different styles. + |> Story.addStory + (Story.optionListStory "Style" + ( "SortTable", Material.sortTable ) + [] + ) + |> Story.addStory + (Story.optionListStory "Content" + ( "Data" + , [ { id = 1, name = "Antonio", rating = 2.456, hash = Nothing } + , { id = 2, name = "Ana", rating = 1.34, hash = Just "45jf" } + , { id = 3, name = "Alfred", rating = 4.22, hash = Just "6fs1" } + , { id = 4, name = "Thomas", rating = 3, hash = Just "k52f" } + ] + ) + [ ( "None", [] ) + ] + ) + --Changing the text of the label + |> Story.addStory + (Story.optionListStory "Columns" + ( "5 Columns" + , [ Widget.intColumnV2 + { title = "Id" + , value = .id + , toString = \int -> "#" ++ String.fromInt int + , width = Element.fill + } + , Widget.stringColumnV2 + { title = "Name" + , value = .name + , toString = identity + , width = Element.fill + } + , Widget.floatColumnV2 + { title = "Rating" + , value = .rating + , toString = String.fromFloat + , width = Element.fill + } + , Widget.customColumnV2 + { title = "Action" + , value = + \{name} -> + Element.el + [ Element.padding 10 + ] + (Widget.iconButton + (Material.containedButton Material.defaultPalette) + { text = name + , icon = + Widget.Icon.elmMaterialIcons + Color + Material.Icons.favorite + , onPress = Nothing + } + ) + , width = Element.fill + } + , Widget.unsortableColumnV2 + { title = "Hash" + , toString = .hash >> Maybe.withDefault "None" + , width = Element.fill + } + ] + ) + [ ( "1 Column" + , [ Widget.intColumnV2 + { title = "Id" + , value = .id + , toString = \int -> "#" ++ String.fromInt int + , width = Element.fill + } + ] + ) + , ( "None", [] ) + ] + ) + --Change the Icon + |> Story.addStory + (Story.boolStory "Sort ascendingly" + ( True + , False + ) + True + ) + |> Story.addStory + (Story.optionListStory "Sort by" + ( "Id", "Id" ) + [ ( "Name", "Name" ) + , ( "Rating", "Rating" ) + , ( "Hash", "Hash" ) + , ( "None", "" ) + ] + ) + |> Story.build + + + +{- This next section is essentially just a normal Elm program. -} +-------------------------------------------------------------------------------- +-- Interactive Demonstration +-------------------------------------------------------------------------------- + + +type alias Model = + { title : String + , asc : Bool + } + + +type Msg + = ChangedSorting String + | PressedButton String + + +init : ( Model, Cmd Msg ) +init = + ( { title = "Name", asc = True } + , Cmd.none + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + ChangedSorting string -> + ( { title = string + , asc = + if model.title == string then + not model.asc + + else + True + } + , Cmd.none + ) + PressedButton _ -> + ( model, Cmd.none ) + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Sub.none + + +{-| You can remove the msgMapper. But by doing so, make sure to also change `msg` to `Msg` in the line below. +-} +view : Context -> Model -> Element Msg +view { palette } model = + Widget.sortTableV2 (Material.sortTable palette) + { content = + [ { id = 1, name = "Antonio", rating = 2.456, hash = Nothing } + , { id = 2, name = "Ana", rating = 1.34, hash = Just "45jf" } + , { id = 3, name = "Alfred", rating = 4.22, hash = Just "6fs1" } + , { id = 4, name = "Thomas", rating = 3, hash = Just "k52f" } + ] + , columns = + [ Widget.intColumnV2 + { title = "Id" + , value = .id + , toString = \int -> "#" ++ String.fromInt int + , width = Element.fill + } + , Widget.stringColumnV2 + { title = "Name" + , value = .name + , toString = identity + , width = Element.fill + } + , Widget.floatColumnV2 + { title = "Rating" + , value = .rating + , toString = String.fromFloat + , width = Element.fill + } + , Widget.customColumnV2 + { title = "Action" + , value = + \{name} -> + Element.el + [ Element.padding 10 + ] + (Widget.iconButton + (Material.containedButton Material.defaultPalette) + { text = name + , icon = + Widget.Icon.elmMaterialIcons + Color + Material.Icons.favorite + , onPress = Just <| PressedButton name + } + ) + , width = Element.fill + } + , Widget.unsortableColumnV2 + { title = "Hash" + , toString = .hash >> Maybe.withDefault "None" + , width = Element.fill + } + ] + , asc = model.asc + , sortBy = model.title + , onChange = ChangedSorting + } + + + +-------------------------------------------------------------------------------- +-- DO NOT MODIFY ANYTHING AFTER THIS LINE +-------------------------------------------------------------------------------- + + +demo : Tile Model Msg flags +demo = + { init = always init + , update = update + , view = Page.demo view + , subscriptions = subscriptions + } + + +page = + Page.create + { title = title + , description = description + , book = book + , demo = demo + } diff --git a/src/Internal/SortTable.elm b/src/Internal/SortTable.elm index 02ffe69..b20f287 100644 --- a/src/Internal/SortTable.elm +++ b/src/Internal/SortTable.elm @@ -34,11 +34,10 @@ type alias SortTableStyle msg = {-| A Sortable list allows you to sort coulmn. -} -type ColumnType a msg +type ColumnType a = StringColumn { value : a -> String, toString : String -> String } | IntColumn { value : a -> Int, toString : Int -> String } | FloatColumn { value : a -> Float, toString : Float -> String } - | ElementColumn { value : a -> Element msg } | UnsortableColumn (a -> String) @@ -46,22 +45,22 @@ type ColumnType a msg -} type alias SortTable a msg = { content : List a - , columns : List (Column a msg) + , columns : List (Column a) , sortBy : String , asc : Bool , onChange : String -> msg } -type Column a msg +type Column a = Column { title : String - , content : ColumnType a msg + , content : ColumnType a , width : Length } -unsortableColumn : { title : String, toString : a -> String, width : Length } -> Column a msg +unsortableColumn : { title : String, toString : a -> String, width : Length } -> Column a unsortableColumn { title, toString, width } = Column { title = title @@ -72,7 +71,7 @@ unsortableColumn { title, toString, width } = {-| A Column containing a Int -} -intColumn : { title : String, value : a -> Int, toString : Int -> String, width : Length } -> Column a msg +intColumn : { title : String, value : a -> Int, toString : Int -> String, width : Length } -> Column a intColumn { title, value, toString, width } = Column { title = title @@ -83,7 +82,7 @@ intColumn { title, value, toString, width } = {-| A Column containing a Float -} -floatColumn : { title : String, value : a -> Float, toString : Float -> String, width : Length } -> Column a msg +floatColumn : { title : String, value : a -> Float, toString : Float -> String, width : Length } -> Column a floatColumn { title, value, toString, width } = Column { title = title @@ -94,7 +93,7 @@ floatColumn { title, value, toString, width } = {-| A Column containing a String -} -stringColumn : { title : String, value : a -> String, toString : String -> String, width : Length } -> Column a msg +stringColumn : { title : String, value : a -> String, toString : String -> String, width : Length } -> Column a stringColumn { title, value, toString, width } = Column { title = title @@ -111,7 +110,7 @@ sortTable : -> Element msg sortTable style model = let - findTitle : List (Column a msg) -> Maybe (ColumnType a msg) + findTitle : List (Column a) -> Maybe (ColumnType a) findTitle list = case list of [] -> @@ -141,9 +140,6 @@ sortTable style model = FloatColumn { value } -> Just <| List.sortBy value - ElementColumn _ -> - Nothing - UnsortableColumn _ -> Nothing ) @@ -184,20 +180,18 @@ sortTable style model = , view = (case column.content of IntColumn { value, toString } -> - value >> toString >> Element.text + value >> toString FloatColumn { value, toString } -> - value >> toString >> Element.text + value >> toString StringColumn { value, toString } -> - value >> toString >> Element.text - - ElementColumn { value } -> - value + value >> toString UnsortableColumn toString -> - toString >> Element.text + toString ) + >> Element.text >> List.singleton >> Element.paragraph [] } diff --git a/src/Internal/SortTableV2.elm b/src/Internal/SortTableV2.elm new file mode 100644 index 0000000..b24c89f --- /dev/null +++ b/src/Internal/SortTableV2.elm @@ -0,0 +1,212 @@ +module Internal.SortTableV2 exposing + ( ColumnV2 + , ColumnTypeV2 + , SortTableV2 + , floatColumnV2 + , intColumnV2 + , sortTableV2 + , stringColumnV2 + , unsortableColumnV2 + , customColumnV2 + ) + +import Element exposing (Attribute, Element, Length) +import Internal.Button as Button exposing (ButtonStyle) +import Widget.Icon exposing (Icon) +import Internal.SortTable as SortTable + + +{-| A Sortable list allows you to sort column. +-} +type ColumnTypeV2 a msg + = StringColumn { value : a -> String, toString : String -> String } + | IntColumn { value : a -> Int, toString : Int -> String } + | FloatColumn { value : a -> Float, toString : Float -> String } + | CustomColumn { value : a -> Element msg } + | UnsortableColumn (a -> String) + + +{-| The Model contains the sorting column name and if ascending or descending. +-} +type alias SortTableV2 a msg = + { content : List a + , columns : List (ColumnV2 a msg) + , sortBy : String + , asc : Bool + , onChange : String -> msg + } + + +type ColumnV2 a msg + = Column + { title : String + , content : ColumnTypeV2 a msg + , width : Length + } + + +unsortableColumnV2 : { title : String, toString : a -> String, width : Length } -> ColumnV2 a msg +unsortableColumnV2 { title, toString, width } = + Column + { title = title + , content = UnsortableColumn toString + , width = width + } + + +{-| A Column containing a Int +-} +intColumnV2 : { title : String, value : a -> Int, toString : Int -> String, width : Length } -> ColumnV2 a msg +intColumnV2 { title, value, toString, width } = + Column + { title = title + , content = IntColumn { value = value, toString = toString } + , width = width + } + + +{-| A Column containing a Float +-} +floatColumnV2 : { title : String, value : a -> Float, toString : Float -> String, width : Length } -> ColumnV2 a msg +floatColumnV2 { title, value, toString, width } = + Column + { title = title + , content = FloatColumn { value = value, toString = toString } + , width = width + } + + +{-| A Column containing a String +-} +stringColumnV2 : { title : String, value : a -> String, toString : String -> String, width : Length } -> ColumnV2 a msg +stringColumnV2 { title, value, toString, width } = + Column + { title = title + , content = StringColumn { value = value, toString = toString } + , width = width + } + +{-| A Column containing an Element +-} +customColumnV2 : { title : String, value : a -> Element msg, width : Length } -> ColumnV2 a msg +customColumnV2 { title, value, width } = + Column + { title = title + , content = CustomColumn { value = value } + , width = width + } + + +{-| The View +-} +sortTableV2 : + SortTable.SortTableStyle msg + -> SortTableV2 a msg + -> Element msg +sortTableV2 style model = + let + findTitle : List (ColumnV2 a msg) -> Maybe (ColumnTypeV2 a msg) + 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.elementTable + { 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 + + CustomColumn _ -> + Nothing + + UnsortableColumn _ -> + Nothing + ) + |> Maybe.withDefault identity + ) + |> (if model.asc then + identity + + else + List.reverse + ) + , columns = + model.columns + |> List.map + (\(Column column) -> + { header = + Button.button style.content.header + { text = column.title + , icon = + if column.title == model.sortBy then + if model.asc then + style.content.ascIcon + + else + style.content.descIcon + + else + style.content.defaultIcon + , onPress = + case column.content of + UnsortableColumn _ -> + Nothing + + CustomColumn _ -> + Nothing + + _ -> + Just <| model.onChange <| column.title + } + , width = column.width + , view = + (case column.content of + IntColumn { value, toString } -> + value + >> toString + >> Element.text + >> List.singleton + >> Element.paragraph [] + + FloatColumn { value, toString } -> + value + >> toString + >> Element.text + >> List.singleton + >> Element.paragraph [] + + StringColumn { value, toString } -> + value + >> toString + >> Element.text + >> List.singleton + >> Element.paragraph [] + + CustomColumn { value } -> + value >> Element.el [] + + UnsortableColumn toString -> + toString >> Element.text + ) + } + ) + } diff --git a/src/Widget.elm b/src/Widget.elm index 5b85bf3..cc442ae 100644 --- a/src/Widget.elm +++ b/src/Widget.elm @@ -19,6 +19,7 @@ module Widget exposing , itemList , AppBarStyle, menuBar, tabBar , SortTableStyle, SortTable, Column, sortTable, floatColumn, intColumn, stringColumn, unsortableColumn + , SortTableV2, ColumnV2, sortTableV2, floatColumnV2, intColumnV2, stringColumnV2, unsortableColumnV2, customColumnV2 , TextInputStyle, TextInput, textInput, usernameInput, emailInput, searchInput, spellCheckedInput , PasswordInputStyle, PasswordInput, newPasswordInputV2, currentPasswordInputV2 , TabStyle, Tab, tab @@ -131,6 +132,13 @@ You can create you own widgets by sticking widgets types together. @docs SortTableStyle, SortTable, Column, sortTable, floatColumn, intColumn, stringColumn, unsortableColumn +# Sort Table V2 + +![Sort Table V2](https://orasund.github.io/elm-ui-widgets/assets/sortTableV2.png) + +@docs SortTableStyle, SortTableV2, ColumnV2, sortTableV2, floatColumnV2, intColumnV2, stringColumnV2, customColumnV2, unsortableColumnV2 + + # Text Input ![textInput](https://orasund.github.io/elm-ui-widgets/assets/textInput.png) @@ -172,6 +180,7 @@ import Internal.PasswordInput as PasswordInput import Internal.ProgressIndicator as ProgressIndicator import Internal.Select as Select import Internal.SortTable as SortTable +import Internal.SortTableV2 as SortTableV2 import Internal.Switch as Switch import Internal.Tab as Tab import Internal.TextInput as TextInput @@ -1901,15 +1910,15 @@ type alias SortTableStyle msg = {-| Column for the Sort Table widget type -} -type alias Column a msg = - SortTable.Column a msg +type alias Column a = + SortTable.Column a {-| Sort Table widget type -} type alias SortTable a msg = { content : List a - , columns : List (Column a msg) + , columns : List (Column a) , sortBy : String , asc : Bool , onChange : String -> msg @@ -1923,7 +1932,7 @@ unsortableColumn : , toString : a -> String , width : Length } - -> Column a msg + -> Column a unsortableColumn = SortTable.unsortableColumn @@ -1936,7 +1945,7 @@ intColumn : , toString : Int -> String , width : Length } - -> Column a msg + -> Column a intColumn = SortTable.intColumn @@ -1949,7 +1958,7 @@ floatColumn : , toString : Float -> String , width : Length } - -> Column a msg + -> Column a floatColumn = SortTable.floatColumn @@ -1969,7 +1978,7 @@ stringColumn : , toString : String -> String , width : Length } - -> Column a msg + -> Column a stringColumn = SortTable.stringColumn @@ -2033,7 +2042,7 @@ sortTable : SortTableStyle msg -> { content : List a - , columns : List (Column a msg) + , columns : List (Column a) , sortBy : String , asc : Bool , onChange : String -> msg @@ -2049,6 +2058,189 @@ sortTable = +{---------------------------------------------------------- +- SORT TABLE V2 +----------------------------------------------------------} + + +{-| Column for the Sort Table V2 widget type +-} +type alias ColumnV2 a msg = + SortTableV2.ColumnV2 a msg + + +{-| Sort Table V2 widget type +-} +type alias SortTableV2 a msg = + { content : List a + , columns : List (ColumnV2 a msg) + , sortBy : String + , asc : Bool + , onChange : String -> msg + } + + +{-| An unsortable ColumnV2, when trying to sort by this column, nothing will change. +-} +unsortableColumnV2 : + { title : String + , toString : a -> String + , width : Length + } + -> ColumnV2 a msg +unsortableColumnV2 = + SortTableV2.unsortableColumnV2 + + +{-| A ColumnV2 containing a Int +-} +intColumnV2 : + { title : String + , value : a -> Int + , toString : Int -> String + , width : Length + } + -> ColumnV2 a msg +intColumnV2 = + SortTableV2.intColumnV2 + + +{-| A ColumnV2 containing a Float +-} +floatColumnV2 : + { title : String + , value : a -> Float + , toString : Float -> String + , width : Length + } + -> ColumnV2 a msg +floatColumnV2 = + SortTableV2.floatColumnV2 + + +{-| A ColumnV2 containing an Element + +`value` will be used for displaying content. + +This column is not sortable. +-} +customColumnV2 : + { title : String + , value : a -> Element msg + , width : Length + } + -> ColumnV2 a msg +customColumnV2 = + SortTableV2.customColumnV2 + + +{-| A ColumnV2 containing a String + +`value >> toString` field will be used for displaying the content. + +`value` will be used for comparing the content + +For example `value = String.toLower` will make the sorting case-insensitive. + +-} +stringColumnV2 : + { title : String + , value : a -> String + , toString : String -> String + , width : Length + } + -> ColumnV2 a msg +stringColumnV2 = + SortTableV2.stringColumnV2 + + +{-| A table where the rows can be sorted by columns + + import Widget.Material as Material + import Element + + type Msg + = ChangedSorting String + | PressedButton String + + sortBy : String + sortBy = + "Id" + + asc : Bool + asc = + True + + Widget.sortTableV2 (Material.sortTable Material.defaultPalette) + { content = + [ { id = 1, name = "Antonio", rating = 2.456, hash = Nothing } + , { id = 2, name = "Ana", rating = 1.34, hash = Just "45jf" } + , { id = 3, name = "Alfred", rating = 4.22, hash = Just "6fs1" } + , { id = 4, name = "Thomas", rating = 3, hash = Just "k52f" } + ] + , columns = + [ Widget.intColumnV2 + { title = "Id" + , value = .id + , toString = \int -> "#" ++ String.fromInt int + , width = Element.fill + } + , Widget.stringColumnV2 + { title = "Name" + , value = .name + , toString = identity + , width = Element.fill + } + , Widget.floatColumnV2 + { title = "Rating" + , value = .rating + , toString = String.fromFloat + , width = Element.fill + } + , Widget.customColumnV2 + { title = "Action" + , value = + \{name} -> + Widget.textButton + (Material.textButton Material.defaultPalette) + { text = name + , onPress = Just <| PressedButton name + } + , width = Element.fill + } + , Widget.unsortableColumnV2 + { title = "Hash" + , toString = (\{hash} -> hash |> Maybe.withDefault "None") + , width = Element.fill + } + ] + , asc = asc + , sortBy = sortBy + , onChange = ChangedSorting + } + |> always "Ignore this line" --> "Ignore this line" + +-} +sortTableV2 : + SortTableStyle msg + -> + { content : List a + , columns : List (ColumnV2 a msg) + , sortBy : String + , asc : Bool + , onChange : String -> msg + } + -> Element msg +sortTableV2 = + let + fun : SortTableStyle msg -> SortTableV2 a msg -> Element msg + fun = + SortTableV2.sortTableV2 + in + fun + + + {---------------------------------------------------------- - TAB ----------------------------------------------------------}