Merge pull request #13 from c-mart/issue-11-progress-indicators

Add Progress Indicators
This commit is contained in:
Orasund 2020-08-13 06:18:45 +02:00 committed by GitHub
commit 1dfb6057db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 340 additions and 5 deletions

View File

@ -21,4 +21,4 @@ _Please only open Pull Requests for existing issues._
**Optional:**
* [ ] Added a Material design style in `src/Widget/Style/Material.elm`
* [ ] Added a Ellie to the docs (+ copied it into `example/src/Example/[Your Widget].elm`)
* [ ] Added an Example in `example/src/Example/[Your Widget].elm`

View File

@ -8,6 +8,7 @@ import Example.ExpansionPanel as ExpansionPanel
import Example.List as List
import Example.Modal as Modal
import Example.MultiSelect as MultiSelect
import Example.ProgressIndicator as ProgressIndicator
import Example.Select as Select
import Example.SortTable as SortTable
import Example.Tab as Tab
@ -27,6 +28,7 @@ type Example
| DialogExample
| TextInputExample
| ListExample
| ProgressIndicatorExample
asList : List Example
@ -41,6 +43,7 @@ asList =
, DialogExample
, TextInputExample
, ListExample
, ProgressIndicatorExample
]
|> List.sortBy toString
@ -78,6 +81,9 @@ toString example =
ListExample ->
"List"
ProgressIndicatorExample ->
"Progress Indicator"
fromString : String -> Maybe Example
fromString string =
@ -112,6 +118,9 @@ fromString string =
"List" ->
Just ListExample
"Progress Indicator" ->
Just ProgressIndicatorExample
_ ->
Nothing
@ -149,6 +158,9 @@ get example =
ListExample ->
.list
ProgressIndicatorExample ->
.progressIndicator
toTests : Example -> msg -> Style msg -> List ( String, Element msg )
toTests example =
@ -183,6 +195,9 @@ toTests example =
ListExample ->
Test.list
ProgressIndicatorExample ->
Test.progressIndicator
type Msg
= Button Button.Msg
@ -195,6 +210,7 @@ type Msg
| Dialog Dialog.Msg
| TextInput TextInput.Msg
| List List.Msg
| ProgressIndicator ProgressIndicator.Msg
type alias Model =
@ -208,6 +224,7 @@ type alias Model =
, dialog : Dialog.Model
, textInput : TextInput.Model
, list : List.Model
, progressIndicator : ProgressIndicator.Model
}
@ -231,6 +248,7 @@ type alias UpgradeCollection =
, dialog : UpgradeRecord Dialog.Model Dialog.Msg
, textInput : UpgradeRecord TextInput.Model TextInput.Msg
, list : UpgradeRecord List.Model List.Msg
, progressIndicator : UpgradeRecord ProgressIndicator.Model ProgressIndicator.Msg
}
@ -245,6 +263,7 @@ type alias ExampleView msg =
, dialog : Element msg
, textInput : Element msg
, list : Element msg
, progressIndicator : Element msg
}
@ -280,6 +299,9 @@ init =
( listModel, listMsg ) =
List.init
( progressIndicatorModel, progressIndicatorMsg ) =
ProgressIndicator.init
in
( { button = buttonModel
, select = selectModel
@ -291,6 +313,7 @@ init =
, dialog = dialogModel
, textInput = textInputModel
, list = listModel
, progressIndicator = progressIndicatorModel
}
, [ Cmd.map Button buttonMsg
, Cmd.map Select selectMsg
@ -302,6 +325,7 @@ init =
, Cmd.map Dialog dialogMsg
, Cmd.map TextInput textInputMsg
, Cmd.map List listMsg
, Cmd.map ProgressIndicator progressIndicatorMsg
]
|> Cmd.batch
)
@ -379,6 +403,13 @@ upgradeRecord =
, updateFun = List.update
, subscriptionsFun = List.subscriptions
}
, progressIndicator =
{ from = .progressIndicator
, to = \model a -> { model | progressIndicator = a }
, msgMapper = ProgressIndicator
, updateFun = ProgressIndicator.update
, subscriptionsFun = ProgressIndicator.subscriptions
}
}
@ -414,6 +445,9 @@ update msg model =
List m ->
updateField .list m
ProgressIndicator m ->
updateField .progressIndicator m
)
model
@ -434,6 +468,7 @@ subscriptions model =
, upgradeRecord.dialog |> subFun
, upgradeRecord.textInput |> subFun
, upgradeRecord.list |> subFun
, upgradeRecord.progressIndicator |> subFun
]
|> Sub.batch
@ -464,6 +499,8 @@ view msgMapper style model =
TextInput.view (TextInput >> msgMapper) style (.textInput model)
, list =
List.view (List >> msgMapper) style (.list model)
, progressIndicator =
ProgressIndicator.view (ProgressIndicator >> msgMapper) style (.progressIndicator model)
}

View File

@ -7,6 +7,7 @@ import Widget.Style
, DialogStyle
, ExpansionPanelStyle
, LayoutStyle
, ProgressIndicatorStyle
, RowStyle
, SortTableStyle
, TabStyle
@ -28,5 +29,6 @@ type alias Style msg =
, cardColumn : ColumnStyle msg
, sortTable : SortTableStyle msg
, selectButton : ButtonStyle msg
, progressIndicator : ProgressIndicatorStyle msg
, layout : LayoutStyle msg
}

View File

@ -20,6 +20,7 @@ import Widget.Style
, DialogStyle
, ExpansionPanelStyle
, LayoutStyle
, ProgressIndicatorStyle
, RowStyle
, SnackbarStyle
, SortTableStyle
@ -283,6 +284,19 @@ sortTable =
}
progressIndicatorStyle : ProgressIndicatorStyle msg
progressIndicatorStyle =
{ containerFunction =
\maybeProgress ->
case maybeProgress of
Nothing ->
Element.text "Indeterminate progress indicator"
Just progress ->
Element.text ("Determinate progress indicator, " ++ String.fromFloat progress ++ "completeness")
}
layout : LayoutStyle msg
layout =
{ container = []
@ -335,5 +349,6 @@ style =
, expansionPanel = expansionPanelStyle
, dialog = dialog
, selectButton = buttonStyle
, progressIndicator = progressIndicatorStyle
, layout = layout
}

View File

@ -52,5 +52,6 @@ style palette =
, chipButton = Material.chip palette
, expansionPanel = Material.expansionPanel palette
, dialog = Material.alertDialog palette
, progressIndicator = Material.progressIndicator palette
, layout = Material.layout palette
}

View File

@ -23,5 +23,6 @@ style =
, expansionPanel = Template.expansionPanel "expansionPanel"
, selectButton = Template.button "selectButton"
, dialog = Template.dialog "dialog"
, progressIndicator = Template.progressIndicator "progressIndicator"
, layout = Template.layout "layout"
}

View File

@ -0,0 +1,65 @@
module Example.ProgressIndicator exposing (Model, Msg, init, subscriptions, update, view)
import Browser
import Element exposing (Element)
import Widget
import Widget.Style exposing (ProgressIndicatorStyle)
import Widget.Style.Material as Material
type alias Style style msg =
{ style
| progressIndicator : ProgressIndicatorStyle msg
}
materialStyle : Style {} msg
materialStyle =
{ progressIndicator = Material.progressIndicator Material.defaultPalette
}
type Model
= MaybeProgress (Maybe Float)
type Msg
= ChangedProgress (Maybe Float)
init : ( Model, Cmd Msg )
init =
( MaybeProgress Nothing
, Cmd.none
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg _ =
case msg of
ChangedProgress maybeFloat ->
( MaybeProgress maybeFloat
, 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 : (Msg -> msg) -> Style style msg -> Model -> Element msg
view msgMapper style (MaybeProgress maybeProgress) =
Widget.circularProgressIndicator style.progressIndicator maybeProgress
main : Program () Model Msg
main =
Browser.element
{ init = always init
, view = view identity materialStyle >> Element.layout []
, update = update
, subscriptions = subscriptions
}

View File

@ -1,4 +1,4 @@
module View.Test exposing (button, dialog, expansionPanel, list, modal, multiSelect, select, sortTable, tab, textInput)
module View.Test exposing (button, dialog, expansionPanel, list, modal, multiSelect, progressIndicator, select, sortTable, tab, textInput)
import Data.Style exposing (Style)
import Element exposing (Element)
@ -511,3 +511,22 @@ list _ style =
|> Widget.column style.cardColumn
)
]
progressIndicator : msg -> Style msg -> List ( String, Element msg )
progressIndicator _ style =
let
determinateIndicators =
[ 0, 0.25, 0.50, 0.75, 1 ]
|> List.map
(\completeness ->
( "Determinate Progress Indicator, completeness " ++ String.fromFloat completeness
, Widget.circularProgressIndicator style.progressIndicator (Just completeness)
)
)
in
[ ( "Indeterminate Progress Indicator"
, Widget.circularProgressIndicator style.progressIndicator Nothing
)
]
++ determinateIndicators

View File

@ -0,0 +1,12 @@
module Internal.ProgressIndicator exposing (circularProgressIndicator)
import Element exposing (Element)
import Widget.Style exposing (ProgressIndicatorStyle)
circularProgressIndicator :
ProgressIndicatorStyle msg
-> Maybe Float
-> Element msg
circularProgressIndicator style maybeProgress =
style.containerFunction maybeProgress

View File

@ -9,6 +9,7 @@ module Widget exposing
, TextInput, textInput
, Tab, tab
, Dialog, ExpansionPanel
, circularProgressIndicator
)
{-| This module contains different stateless view functions. No wiring required.
@ -118,6 +119,15 @@ You can create you own widgets by sticking widgets types together.
@docs Dialog, ExpansionPanel
# Progress Indicator
![ProgressIndicator](TODO)
[Open in Ellie](TODO)
@docs ProgressIndicator, progressIndicator
-}
import Element exposing (Attribute, Element, Length)
@ -126,12 +136,13 @@ import Internal.Button as Button
import Internal.Dialog as Dialog
import Internal.ExpansionPanel as ExpansionPanel
import Internal.List as List
import Internal.ProgressIndicator as ProgressIndicator
import Internal.Select as Select
import Internal.SortTable as SortTable
import Internal.Tab as Tab
import Internal.TextInput as TextInput
import Set exposing (Set)
import Widget.Style exposing (ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, RowStyle, SortTableStyle, TabStyle, TextInputStyle)
import Widget.Style exposing (ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, ProgressIndicatorStyle, RowStyle, SortTableStyle, TabStyle, TextInputStyle)
@ -616,3 +627,28 @@ tab =
Tab.tab
in
fun
{----------------------------------------------------------
- PROGRESS INDICATOR
----------------------------------------------------------}
{-| Progress Indicator widget type
If `maybeProgress` is set to `Nothing`, an indeterminate progress indicator (e.g. spinner) will display.
If `maybeProgress` is set to `Just Float` (where the `Float` is proportion of completeness between 0 and 1 inclusive), a determinate progress indicator will visualize the progress.
-}
type alias ProgressIndicator =
Maybe Float
{-| Displays a circular progress indicator
-}
circularProgressIndicator :
ProgressIndicatorStyle msg
-> Maybe Float
-> Element msg
circularProgressIndicator =
ProgressIndicator.circularProgressIndicator

View File

@ -1,4 +1,4 @@
module Widget.Style exposing (ButtonStyle,ColumnStyle, DialogStyle, ExpansionPanelStyle, LayoutStyle, RowStyle, SnackbarStyle, SortTableStyle, TabStyle, TextInputStyle)
module Widget.Style exposing (ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, LayoutStyle, RowStyle, SnackbarStyle, SortTableStyle, TabStyle, TextInputStyle, ProgressIndicatorStyle)
{-| This module contains style types for every widget.
@ -132,3 +132,9 @@ type alias LayoutStyle msg =
, search : List (Attribute msg)
, searchFill : List (Attribute msg)
}
{-| -}
type alias ProgressIndicatorStyle msg =
{ containerFunction : Maybe Float -> Element msg
}

View File

@ -7,6 +7,7 @@ module Widget.Style.Material exposing
, alertDialog
, expansionPanel
, row, column
, progressIndicator
, snackbar
, tab, tabButton
, layout
@ -42,6 +43,7 @@ You can use the theme by copying the following code:
, cardColumn : ColumnStyle msg
, sortTable : SortTableStyle msg
, selectButton : ButtonStyle msg
, progressIndicator : ProgressIndicatorStyle msg
, layout : LayoutStyle msg
}
@ -69,6 +71,7 @@ You can use the theme by copying the following code:
, chipButton = Material.chip palette
, expansionPanel = Material.expansionPanel palette
, dialog = Material.alertDialog palette
, progressIndicator = Material.progressIndicator palette
, layout = Material.layout palette
}
@ -123,6 +126,11 @@ The [List widget](https://material.io/components/lists) is a very complex widget
@docs row, column
# Progress Indicator
@docs progressIndicator
# Snackbar
@docs snackbar
@ -156,6 +164,7 @@ import Widget.Style
, DialogStyle
, ExpansionPanelStyle
, LayoutStyle
, ProgressIndicatorStyle
, RowStyle
, SnackbarStyle
, TabStyle
@ -1201,6 +1210,121 @@ expansionPanel palette =
{-------------------------------------------------------------------------------
-- P R O G R E S S I N D I C A T O R
-------------------------------------------------------------------------------}
indeterminateCircularIcon : Color.Color -> List (Attribute msg) -> Element msg
indeterminateCircularIcon color attribs =
-- Based on example at https://codepen.io/FezVrasta/pen/oXrgdR
Svg.svg
[ Svg.Attributes.height "48px"
, Svg.Attributes.width "48px"
, Svg.Attributes.viewBox "0 0 66 66"
, Svg.Attributes.xmlSpace "http://www.w3.org/2000/svg"
]
[ Svg.g []
[ Svg.animateTransform
[ Svg.Attributes.attributeName "transform"
, Svg.Attributes.type_ "rotate"
, Svg.Attributes.values "0 33 33;270 33 33"
, Svg.Attributes.begin "0s"
, Svg.Attributes.dur "1.4s"
, Svg.Attributes.fill "freeze"
, Svg.Attributes.repeatCount "indefinite"
]
[]
, Svg.circle
[ Svg.Attributes.fill "none"
, Svg.Attributes.stroke (Color.toCssString color)
, Svg.Attributes.strokeWidth "5"
, Svg.Attributes.strokeLinecap "square"
, Svg.Attributes.cx "33"
, Svg.Attributes.cy "33"
, Svg.Attributes.r "30"
, Svg.Attributes.strokeDasharray "187"
, Svg.Attributes.strokeDashoffset "610"
]
[ Svg.animateTransform
[ Svg.Attributes.attributeName "transform"
, Svg.Attributes.type_ "rotate"
, Svg.Attributes.values "0 33 33;135 33 33;450 33 33"
, Svg.Attributes.begin "0s"
, Svg.Attributes.dur "1.4s"
, Svg.Attributes.fill "freeze"
, Svg.Attributes.repeatCount "indefinite"
]
[]
, Svg.animate
[ Svg.Attributes.attributeName "stroke-dashoffset"
, Svg.Attributes.values "187;46.75;187"
, Svg.Attributes.begin "0s"
, Svg.Attributes.dur "1.4s"
, Svg.Attributes.fill "freeze"
, Svg.Attributes.repeatCount "indefinite"
]
[]
]
]
]
|> Element.html
|> Element.el attribs
determinateCircularIcon : Color.Color -> List (Attribute msg) -> Float -> Element msg
determinateCircularIcon color attribs progress =
-- With help from https://css-tricks.com/building-progress-ring-quickly/
let
strokeDashoffset =
let
clampedProgress =
clamp 0 1 progress
in
-- 188 is circumference of circle in pixels
188 - (188 * clampedProgress)
|> round
in
Svg.svg
[ Svg.Attributes.height "48px"
, Svg.Attributes.width "48px"
, Svg.Attributes.viewBox "0 0 66 66"
, Svg.Attributes.xmlSpace "http://www.w3.org/2000/svg"
]
[ Svg.g []
[ Svg.circle
[ Svg.Attributes.fill "none"
, Svg.Attributes.stroke (Color.toCssString color)
, Svg.Attributes.strokeWidth "5"
, Svg.Attributes.strokeLinecap "butt"
, Svg.Attributes.cx "33"
, Svg.Attributes.cy "33"
, Svg.Attributes.r "30"
, Svg.Attributes.strokeDasharray "188 188"
, Svg.Attributes.strokeDashoffset (String.fromInt strokeDashoffset)
, Svg.Attributes.transform "rotate(-90 33 33)"
]
[]
]
]
|> Element.html
|> Element.el attribs
progressIndicator : Palette -> ProgressIndicatorStyle msg
progressIndicator palette =
{ containerFunction =
\maybeProgress ->
case maybeProgress of
Nothing ->
indeterminateCircularIcon palette.primary []
Just progress ->
determinateCircularIcon palette.primary [] progress
}
{-------------------------------------------------------------------------------
-- S N A C K B A R
-------------------------------------------------------------------------------}

View File

@ -1,6 +1,6 @@
module Widget.Style.Template exposing
( box, decoration, icon
, button, column, dialog, expansionPanel, layout, row, snackbar, sortTable, tab, textInput
, button, column, dialog, expansionPanel, layout, progressIndicator, row, snackbar, sortTable, tab, textInput
)
{-| ![Example using the Template style](https://orasund.github.io/elm-ui-widgets/assets/template-style.png)
@ -69,6 +69,7 @@ import Widget.Style
, DialogStyle
, ExpansionPanelStyle
, LayoutStyle
, ProgressIndicatorStyle
, RowStyle
, SnackbarStyle
, SortTableStyle
@ -339,6 +340,22 @@ sortTable string =
}
{-|
```
progressIndicator : String -> ProgressIndicatorStyle msg
progressIndicator string =
{ icon = (\_ -> icon <| string ++ ":icon")
}
```
-}
progressIndicator : String -> ProgressIndicatorStyle msg
progressIndicator string =
{ containerFunction = (\_ -> icon <| string ++ ":icon")
}
{-|
```