Add custom element support

This commit is contained in:
Lukas Turcani 2021-12-20 19:38:11 +01:00
parent 94542e85d3
commit 61e39d98eb
6 changed files with 714 additions and 28 deletions

BIN
docs/assets/sortTableV2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -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

View File

@ -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
}

View File

@ -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 []
}

View File

@ -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
)
}
)
}

View File

@ -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
----------------------------------------------------------}