mirror of
https://github.com/Orasund/elm-ui-widgets.git
synced 2024-11-22 13:14:10 +03:00
Merge branch 'unstable'
This commit is contained in:
commit
d076c94886
154
README.md
154
README.md
@ -1,23 +1,139 @@
|
||||
# Elm-Ui-Widgets
|
||||
|
||||
Usefull Widgets written for Elm-ui.
|
||||
These include:
|
||||
This package contains **independend** widgets (no components) written for [Elm-Ui](https://dark.elm.dmy.fr/packages/mdgriffith/elm-ui/latest/). These widgets have no dependencies to other parts of this package. So you can just use as much as you need.
|
||||
|
||||
* Select
|
||||
* Tab
|
||||
* Multi Select
|
||||
* Collapsable
|
||||
* Dialog
|
||||
* Carousel
|
||||
* Snackbar
|
||||
* Sort Table
|
||||
* Filter Select
|
||||
* Validated Input
|
||||
* Scrolling Nav
|
||||
It also supports custom themes and has a material design theme already ready to use.
|
||||
|
||||
Examples of all widgets can be found [here](https://orasund.github.io/elm-ui-widgets/). For styling, I used my own [Orasund/elm-ui-framework](https://package.elm-lang.org/packages/Orasund/elm-ui-framework/latest/).
|
||||
[Examples of all widgets can be found here](https://orasund.github.io/elm-ui-widgets/).
|
||||
|
||||
## Why create such a package?
|
||||
![Example using the Material Design style](https://orasund.github.io/elm-ui-widgets/assets/material-style.png)
|
||||
|
||||
## Concept
|
||||
|
||||
**Summary**
|
||||
|
||||
* Each widget comes with a _Widget Type_ and a _Style Type_. The Widget Type is an abstract representation of the widget and the Style Type has all styling attributes.
|
||||
* Widget Types can be used as building Blocks for more complicated Widgets
|
||||
(Button -> Select Buttons -> Menu -> Layout)
|
||||
|
||||
**Example**
|
||||
|
||||
Let's look at the button widget.
|
||||
|
||||
```elm
|
||||
button: ButtonStyle msg
|
||||
->
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
-> Element msg
|
||||
```
|
||||
|
||||
In comparison to Elm-Ui's button, we see that `List (Attribute msg)` has changed into a _Style Type_.
|
||||
```
|
||||
type alias ButtonStyle msg =
|
||||
{ container : List (Attribute msg)
|
||||
, labelRow : List (Attribute msg)
|
||||
, ifDisabled : List (Attribute msg)
|
||||
, ifActive : List (Attribute msg)
|
||||
}
|
||||
```
|
||||
|
||||
For actually displaying the button we have a few different implementations:
|
||||
|
||||
``` elm
|
||||
{-| Button with only an icon and no text -}
|
||||
iconButton :
|
||||
ButtonStyle msg
|
||||
->
|
||||
{ text : String --for screen readers
|
||||
, icon : Element Never
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
-> Element msg
|
||||
|
||||
{-| Button with a text but no icon -}
|
||||
textButton :
|
||||
ButtonStyle msg
|
||||
->
|
||||
{ textButton
|
||||
| text : String
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
-> Element msg
|
||||
|
||||
{-| Button with both icon and text -}
|
||||
button :
|
||||
ButtonStyle msg
|
||||
->
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
-> Element msg
|
||||
```
|
||||
|
||||
We also have a `Widget Type` for the button:
|
||||
|
||||
```
|
||||
type alias Button msg =
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
```
|
||||
|
||||
We can use it to build more complex widgets, for example a select button:
|
||||
|
||||
```
|
||||
type alias Select msg =
|
||||
{ selected : Maybe Int
|
||||
, options :
|
||||
List
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
}
|
||||
, onSelect : Int -> Maybe msg
|
||||
}
|
||||
|
||||
select :
|
||||
Select msg
|
||||
-> List ( Bool, Button msg )
|
||||
|
||||
selectButton :
|
||||
ButtonStyle msg
|
||||
-> ( Bool, Button msg )
|
||||
-> Element msg
|
||||
```
|
||||
|
||||
## Reusable Views vs. Components
|
||||
|
||||
In Elm we like to use reusable views instead of components.
|
||||
At first this packages had a few components, but they where so complicated to use in comparison, so they got slowly turned into reusable views one by one.
|
||||
|
||||
Most could be reduced even further into _view functions_: reusable views without a model.
|
||||
Currently we have only three reusable views: `Widget.Layout`, `Widget.ScrollingNav` and `Widget.Snackbar`.
|
||||
|
||||
## Alternatives
|
||||
|
||||
For comparison, here are some alternative packages for creating UIs:
|
||||
|
||||
* **Using Elm-Ui**
|
||||
* [lucamug/style-framework](https://dark.elm.dmy.fr/packages/lucamug/style-framework/latest/) - Full customization requires the cloning of the package.
|
||||
* [jxxcarlson/elm-widget](https://dark.elm.dmy.fr/packages/jxxcarlson/elm-widget/latest/Widget-Button) - Uses a Builder pattern. Has some redefined customizations.
|
||||
* [QiTASC/hatchinq](https://dark.elm.dmy.fr/packages/QiTASC/hatchinq/latest/) - Similar Arroach but still in experimental phase
|
||||
* **Using Elm/Html**
|
||||
* [nathanjohnson320/elm-ui-components](https://dark.elm.dmy.fr/packages/nathanjohnson320/elm-ui-components/latest/) - Uses the elm/html way of styling.
|
||||
* [NoRedInk/noredink-ui](https://dark.elm.dmy.fr/packages/NoRedInk/noredink-ui/latest/) - Similar Approach but no customization options.
|
||||
* [peterszerzo/elm-natural-ui](https://dark.elm.dmy.fr/packages/peterszerzo/elm-natural-ui/latest) - Uses custom Attributes with some customization.
|
||||
* **Ui Frameworks**
|
||||
* [aforemny/material-components-web-elm](https://dark.elm.dmy.fr/packages/aforemny/material-components-web-elm/latest/) - Wrapper of Material design using custom elements.
|
||||
* [afidegnum/elm-tailwind](https://dark.elm.dmy.fr/packages/afidegnum/elm-tailwind/latest/) - Wrapper of Tailwind by including the tailwind stylesheet.
|
||||
* [surprisetalk/elm-bulma](https://dark.elm.dmy.fr/packages/surprisetalk/elm-bulma/latest/) - Wrapper for Bulma by including the bulma stylesheet.
|
||||
* [rundis/elm-bootstrap](https://dark.elm.dmy.fr/packages/rundis/elm-bootstrap/latest/) - Wrapper for Bootstrap by including the bootstrap stylesheet.
|
||||
|
||||
## Motivation
|
||||
|
||||
After looking at the current packages that implement various reusable views (and components) I noticed two things:
|
||||
|
||||
@ -26,10 +142,6 @@ After looking at the current packages that implement various reusable views (and
|
||||
|
||||
This package tries to solve both of these problems.
|
||||
|
||||
## Why does this package also include components?
|
||||
## Changelog
|
||||
|
||||
I wrote a component whenever the boilerplate of a similar reusable view is less than just include the wiring in the package.
|
||||
|
||||
## Where will it go from here
|
||||
|
||||
I really would like to write a native material-design implementation in Elm. But after doing this package as a first step, (Which I already wrote while having the material.io docs as reference) I am not quite sure how I can avoid a lot of boilerplating. It seems like a [Master View Type](https://www.freecodecamp.org/news/scaling-elm-views-with-master-view-types/) would be the solution, but I'm not quite sure how I can ensure the customizability when my entire page can be described as a single type. (I don't want to know how many parameters such a type would need).
|
||||
* **Version 2.0** - Complete rewrite of the package. Now including a material design implementation.
|
10183
docs/index.html
10183
docs/index.html
File diff suppressed because it is too large
Load Diff
BIN
docs/select.png
BIN
docs/select.png
Binary file not shown.
Before Width: | Height: | Size: 933 B |
20213
docs/unstable/index.html
20213
docs/unstable/index.html
File diff suppressed because it is too large
Load Diff
12
elm.json
12
elm.json
@ -6,22 +6,26 @@
|
||||
"version": "1.0.0",
|
||||
"exposed-modules": [
|
||||
"Widget",
|
||||
"Widget.FilterSelect",
|
||||
"Widget.ValidatedInput",
|
||||
"Widget.Style",
|
||||
"Widget.Style.Material",
|
||||
"Widget.Style.Template",
|
||||
"Widget.Layout",
|
||||
"Widget.ScrollingNav",
|
||||
"Widget.Snackbar",
|
||||
"Widget.SortTable"
|
||||
"Widget.Snackbar"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
"Orasund/elm-ui-framework": "1.6.1 <= v < 2.0.0",
|
||||
"avh4/elm-color": "1.0.0 <= v < 2.0.0",
|
||||
"elm/browser": "1.0.2 <= v < 2.0.0",
|
||||
"elm/core": "1.0.0 <= v < 2.0.0",
|
||||
"elm/html": "1.0.0 <= v < 2.0.0",
|
||||
"elm/svg": "1.0.1 <= v < 2.0.0",
|
||||
"elm/time": "1.0.0 <= v < 2.0.0",
|
||||
"elm-community/intdict": "3.0.0 <= v < 4.0.0",
|
||||
"jasonliang512/elm-heroicons": "1.0.2 <= v < 2.0.0",
|
||||
"mdgriffith/elm-ui": "1.1.5 <= v < 2.0.0",
|
||||
"noahzgordon/elm-color-extra": "1.0.2 <= v < 2.0.0",
|
||||
"turboMaCk/queue": "1.0.2 <= v < 2.0.0",
|
||||
"wernerdegroot/listzipper": "4.0.0 <= v < 5.0.0"
|
||||
},
|
||||
|
@ -8,21 +8,29 @@
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"Orasund/elm-ui-framework": "1.6.1",
|
||||
"avh4/elm-color": "1.0.0",
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/svg": "1.0.1",
|
||||
"elm/time": "1.0.0",
|
||||
"elm-community/intdict": "3.0.0",
|
||||
"feathericons/elm-feather": "1.4.0",
|
||||
"jasonliang512/elm-heroicons": "1.0.2",
|
||||
"mdgriffith/elm-ui": "1.1.5",
|
||||
"noahzgordon/elm-color-extra": "1.0.2",
|
||||
"ryannhg/elm-spa": "4.1.0",
|
||||
"turboMaCk/any-set": "1.4.0",
|
||||
"turboMaCk/queue": "1.0.2",
|
||||
"wernerdegroot/listzipper": "4.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/regex": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2"
|
||||
"elm/virtual-dom": "1.0.2",
|
||||
"fredcy/elm-parseint": "2.0.1",
|
||||
"turboMaCk/any-dict": "2.3.0"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
|
@ -1,181 +0,0 @@
|
||||
module Component exposing (Model, Msg(..), init, update, view)
|
||||
|
||||
import Browser
|
||||
import Element exposing (Color, Element)
|
||||
import Element.Background as Background
|
||||
import Element.Input as Input
|
||||
import Framework
|
||||
import Framework.Button as Button
|
||||
import Framework.Card as Card
|
||||
import Framework.Color as Color
|
||||
import Framework.Grid as Grid
|
||||
import Framework.Group as Group
|
||||
import Framework.Heading as Heading
|
||||
import Framework.Input as Input
|
||||
import Framework.Tag as Tag
|
||||
import Heroicons.Solid as Heroicons
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attributes
|
||||
import Set exposing (Set)
|
||||
import Time
|
||||
import Widget
|
||||
import Widget.FilterSelect as FilterSelect
|
||||
import Widget.ScrollingNav as ScrollingNav
|
||||
import Widget.Snackbar as Snackbar
|
||||
import Widget.ValidatedInput as ValidatedInput
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ filterSelect : FilterSelect.Model
|
||||
, validatedInput : ValidatedInput.Model () ( String, String )
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= FilterSelectSpecific FilterSelect.Msg
|
||||
| ValidatedInputSpecific ValidatedInput.Msg
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ filterSelect =
|
||||
[ "Apple"
|
||||
, "Kiwi"
|
||||
, "Strawberry"
|
||||
, "Pineapple"
|
||||
, "Mango"
|
||||
, "Grapes"
|
||||
, "Watermelon"
|
||||
, "Orange"
|
||||
, "Lemon"
|
||||
, "Blueberry"
|
||||
, "Grapefruit"
|
||||
, "Coconut"
|
||||
, "Cherry"
|
||||
, "Banana"
|
||||
]
|
||||
|> Set.fromList
|
||||
|> FilterSelect.init
|
||||
, validatedInput =
|
||||
ValidatedInput.init
|
||||
{ value = ( "John", "Doe" )
|
||||
, validator =
|
||||
\string ->
|
||||
case string |> String.split " " of
|
||||
[ first, second ] ->
|
||||
Ok ( first, second )
|
||||
|
||||
_ ->
|
||||
Err ()
|
||||
, toString =
|
||||
\( first, second ) -> first ++ " " ++ second
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
FilterSelectSpecific m ->
|
||||
( { model
|
||||
| filterSelect = model.filterSelect |> FilterSelect.update m
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ValidatedInputSpecific m ->
|
||||
( { model
|
||||
| validatedInput = model.validatedInput |> ValidatedInput.update m
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
filterSelect : FilterSelect.Model -> Element Msg
|
||||
filterSelect model =
|
||||
Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill]) <|
|
||||
[ Element.el Heading.h3 <| Element.text "Filter Select"
|
||||
, case model.selected of
|
||||
Just selected ->
|
||||
Element.row Grid.compact
|
||||
[ Element.el (Tag.simple ++ Group.left) <| Element.text <| selected
|
||||
, Input.button (Tag.simple ++ Group.right ++ Color.danger)
|
||||
{ onPress = Just <| FilterSelectSpecific <| FilterSelect.Selected Nothing
|
||||
, label = Element.html <| Heroicons.x [ Attributes.width 16 ]
|
||||
}
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
Element.column Grid.simple
|
||||
[ FilterSelect.viewInput Input.simple
|
||||
model
|
||||
{ msgMapper = FilterSelectSpecific
|
||||
, placeholder =
|
||||
Just <|
|
||||
Input.placeholder [] <|
|
||||
Element.text <|
|
||||
"Fruit"
|
||||
, label = "Fruit"
|
||||
}
|
||||
, model
|
||||
|> FilterSelect.viewOptions
|
||||
|> List.map
|
||||
(\string ->
|
||||
Input.button (Button.simple ++ Tag.simple)
|
||||
{ onPress = Just <| FilterSelectSpecific <| FilterSelect.Selected <| Just <| string
|
||||
, label = Element.text string
|
||||
}
|
||||
)
|
||||
|> Element.wrappedRow [ Element.spacing 10 ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
validatedInput : ValidatedInput.Model () ( String, String ) -> Element Msg
|
||||
validatedInput model =
|
||||
Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill]) <|
|
||||
[ Element.el Heading.h3 <| Element.text "Validated Input"
|
||||
, ValidatedInput.view Input.simple
|
||||
model
|
||||
{ label = "First Name, Sir Name"
|
||||
, msgMapper = ValidatedInputSpecific
|
||||
, placeholder = Nothing
|
||||
, readOnly =
|
||||
\maybeTuple ->
|
||||
Element.row Grid.compact
|
||||
[ maybeTuple
|
||||
|> (\( a, b ) -> a ++ " " ++ b)
|
||||
|> Element.text
|
||||
|> Element.el (Tag.simple ++ Group.left)
|
||||
, Heroicons.pencil [ Attributes.width 16 ]
|
||||
|> Element.html
|
||||
|> Element.el (Tag.simple ++ Group.right ++ Color.primary)
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
scrollingNavCard : Element msg
|
||||
scrollingNavCard =
|
||||
[ Element.el Heading.h3 <| Element.text "Scrolling Nav"
|
||||
, Element.text "Resize the screen and open the side-menu. Then start scrolling to see the scrolling navigation in action."
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
]
|
||||
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
|
||||
|
||||
view : Model -> Element Msg
|
||||
view model =
|
||||
Element.column (Grid.section ++ [ Element.centerX ])
|
||||
[ Element.el Heading.h2 <| Element.text "Components"
|
||||
, "Components have a Model, an Update- and sometimes even a Subscription-function. It takes some time to set them up correctly."
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
, Element.wrappedRow (Grid.simple ++ [Element.height <| Element.shrink]) <|
|
||||
[ filterSelect model.filterSelect
|
||||
, validatedInput model.validatedInput
|
||||
, scrollingNavCard
|
||||
]
|
||||
]
|
536
example/src/Data/Example.elm
Normal file
536
example/src/Data/Example.elm
Normal file
@ -0,0 +1,536 @@
|
||||
module Data.Example exposing (Example, Model, Msg, asList, fromString, init, subscriptions, toCardList, toString, update, view)
|
||||
|
||||
import Data.Style exposing (Style)
|
||||
import Element exposing (Element)
|
||||
import Example.Button as Button
|
||||
import Example.Dialog as Dialog
|
||||
import Example.ExpansionPanel as ExpansionPanel
|
||||
import Example.List as List
|
||||
import Example.Modal as Modal
|
||||
import Example.MultiSelect as MultiSelect
|
||||
import Example.Select as Select
|
||||
import Example.SortTable as SortTable
|
||||
import Example.Tab as Tab
|
||||
import Example.TextInput as TextInput
|
||||
import Framework.Grid as Grid
|
||||
import View.Test as Test
|
||||
|
||||
|
||||
type Example
|
||||
= ButtonExample
|
||||
| SelectExample
|
||||
| MultiSelectExample
|
||||
| ExpansionPanelExample
|
||||
| TabExample
|
||||
| SortTableExample
|
||||
| ModalExample
|
||||
| DialogExample
|
||||
| TextInputExample
|
||||
| ListExample
|
||||
|
||||
|
||||
asList : List Example
|
||||
asList =
|
||||
[ ButtonExample
|
||||
, SelectExample
|
||||
, MultiSelectExample
|
||||
, ExpansionPanelExample
|
||||
, TabExample
|
||||
, SortTableExample
|
||||
, ModalExample
|
||||
, DialogExample
|
||||
, TextInputExample
|
||||
, ListExample
|
||||
]
|
||||
|> List.sortBy toString
|
||||
|
||||
|
||||
toString : Example -> String
|
||||
toString example =
|
||||
case example of
|
||||
ButtonExample ->
|
||||
"Button"
|
||||
|
||||
SelectExample ->
|
||||
"Select"
|
||||
|
||||
MultiSelectExample ->
|
||||
"Multi Select"
|
||||
|
||||
ExpansionPanelExample ->
|
||||
"ExpansionPanel"
|
||||
|
||||
TabExample ->
|
||||
"Tab"
|
||||
|
||||
SortTableExample ->
|
||||
"SortTable"
|
||||
|
||||
ModalExample ->
|
||||
"Modal"
|
||||
|
||||
DialogExample ->
|
||||
"Dialog"
|
||||
|
||||
TextInputExample ->
|
||||
"TextInput"
|
||||
|
||||
ListExample ->
|
||||
"List"
|
||||
|
||||
|
||||
fromString : String -> Maybe Example
|
||||
fromString string =
|
||||
case string of
|
||||
"Button" ->
|
||||
Just ButtonExample
|
||||
|
||||
"Select" ->
|
||||
Just SelectExample
|
||||
|
||||
"Multi Select" ->
|
||||
Just MultiSelectExample
|
||||
|
||||
"ExpansionPanel" ->
|
||||
Just ExpansionPanelExample
|
||||
|
||||
"Tab" ->
|
||||
Just TabExample
|
||||
|
||||
"SortTable" ->
|
||||
Just SortTableExample
|
||||
|
||||
"Modal" ->
|
||||
Just ModalExample
|
||||
|
||||
"Dialog" ->
|
||||
Just DialogExample
|
||||
|
||||
"TextInput" ->
|
||||
Just TextInputExample
|
||||
|
||||
"List" ->
|
||||
Just ListExample
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
||||
get : Example -> ExampleView msg -> Element msg
|
||||
get example =
|
||||
case example of
|
||||
ButtonExample ->
|
||||
.button
|
||||
|
||||
SelectExample ->
|
||||
.select
|
||||
|
||||
MultiSelectExample ->
|
||||
.multiSelect
|
||||
|
||||
ExpansionPanelExample ->
|
||||
.expansionPanel
|
||||
|
||||
TabExample ->
|
||||
.tab
|
||||
|
||||
SortTableExample ->
|
||||
.sortTable
|
||||
|
||||
ModalExample ->
|
||||
.modal
|
||||
|
||||
DialogExample ->
|
||||
.dialog
|
||||
|
||||
TextInputExample ->
|
||||
.textInput
|
||||
|
||||
ListExample ->
|
||||
.list
|
||||
|
||||
|
||||
toTests : Example -> msg -> Style msg -> List ( String, Element msg )
|
||||
toTests example =
|
||||
case example of
|
||||
ButtonExample ->
|
||||
Test.button
|
||||
|
||||
SelectExample ->
|
||||
Test.select
|
||||
|
||||
MultiSelectExample ->
|
||||
Test.multiSelect
|
||||
|
||||
ExpansionPanelExample ->
|
||||
Test.expansionPanel
|
||||
|
||||
TabExample ->
|
||||
Test.tab
|
||||
|
||||
SortTableExample ->
|
||||
Test.sortTable
|
||||
|
||||
ModalExample ->
|
||||
Test.modal
|
||||
|
||||
DialogExample ->
|
||||
Test.dialog
|
||||
|
||||
TextInputExample ->
|
||||
Test.textInput
|
||||
|
||||
ListExample ->
|
||||
Test.list
|
||||
|
||||
|
||||
type Msg
|
||||
= Button Button.Msg
|
||||
| Select Select.Msg
|
||||
| MultiSelect MultiSelect.Msg
|
||||
| ExpansionPanel ExpansionPanel.Msg
|
||||
| Tab Tab.Msg
|
||||
| SortTable SortTable.Msg
|
||||
| Modal Modal.Msg
|
||||
| Dialog Dialog.Msg
|
||||
| TextInput TextInput.Msg
|
||||
| List List.Msg
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ button : Button.Model
|
||||
, select : Select.Model
|
||||
, multiSelect : MultiSelect.Model
|
||||
, expansionPanel : ExpansionPanel.Model
|
||||
, tab : Tab.Model
|
||||
, sortTable : SortTable.Model
|
||||
, modal : Modal.Model
|
||||
, dialog : Dialog.Model
|
||||
, textInput : TextInput.Model
|
||||
, list : List.Model
|
||||
}
|
||||
|
||||
|
||||
type alias UpgradeRecord model msg =
|
||||
{ from : Model -> model
|
||||
, to : Model -> model -> Model
|
||||
, msgMapper : msg -> Msg
|
||||
, updateFun : msg -> model -> ( model, Cmd msg )
|
||||
, subscriptionsFun : model -> Sub msg
|
||||
}
|
||||
|
||||
|
||||
type alias UpgradeCollection =
|
||||
{ button : UpgradeRecord Button.Model Button.Msg
|
||||
, select : UpgradeRecord Select.Model Select.Msg
|
||||
, multiSelect : UpgradeRecord MultiSelect.Model MultiSelect.Msg
|
||||
, expansionPanel : UpgradeRecord ExpansionPanel.Model ExpansionPanel.Msg
|
||||
, tab : UpgradeRecord Tab.Model Tab.Msg
|
||||
, sortTable : UpgradeRecord SortTable.Model SortTable.Msg
|
||||
, modal : UpgradeRecord Modal.Model Modal.Msg
|
||||
, dialog : UpgradeRecord Dialog.Model Dialog.Msg
|
||||
, textInput : UpgradeRecord TextInput.Model TextInput.Msg
|
||||
, list : UpgradeRecord List.Model List.Msg
|
||||
}
|
||||
|
||||
|
||||
type alias ExampleView msg =
|
||||
{ button : Element msg
|
||||
, select : Element msg
|
||||
, multiSelect : Element msg
|
||||
, expansionPanel : Element msg
|
||||
, tab : Element msg
|
||||
, sortTable : Element msg
|
||||
, modal : Element msg
|
||||
, dialog : Element msg
|
||||
, textInput : Element msg
|
||||
, list : Element msg
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
( sortTableModel, sortTableMsg ) =
|
||||
SortTable.init
|
||||
|
||||
( modalModel, modalMsg ) =
|
||||
Modal.init
|
||||
|
||||
( dialogModel, dialogMsg ) =
|
||||
Dialog.init
|
||||
|
||||
( textInputModel, textInputMsg ) =
|
||||
TextInput.init
|
||||
|
||||
( listModel, listMsg ) =
|
||||
List.init
|
||||
in
|
||||
( { button = buttonModel
|
||||
, select = selectModel
|
||||
, multiSelect = multiSelectModel
|
||||
, expansionPanel = expansionPanelModel
|
||||
, tab = tabModel
|
||||
, sortTable = sortTableModel
|
||||
, modal = modalModel
|
||||
, dialog = dialogModel
|
||||
, textInput = textInputModel
|
||||
, list = listModel
|
||||
}
|
||||
, [ Cmd.map Button buttonMsg
|
||||
, Cmd.map Select selectMsg
|
||||
, Cmd.map MultiSelect multiSelectMsg
|
||||
, Cmd.map ExpansionPanel expansionPanelMsg
|
||||
, Cmd.map Tab tabMsg
|
||||
, Cmd.map SortTable sortTableMsg
|
||||
, Cmd.map Modal modalMsg
|
||||
, Cmd.map Dialog dialogMsg
|
||||
, Cmd.map TextInput textInputMsg
|
||||
, Cmd.map List listMsg
|
||||
]
|
||||
|> Cmd.batch
|
||||
)
|
||||
|
||||
|
||||
upgradeRecord : UpgradeCollection
|
||||
upgradeRecord =
|
||||
{ button =
|
||||
{ from = .button
|
||||
, to = \model a -> { model | button = a }
|
||||
, msgMapper = Button
|
||||
, updateFun = Button.update
|
||||
, subscriptionsFun = Button.subscriptions
|
||||
}
|
||||
, select =
|
||||
{ from = .select
|
||||
, to = \model a -> { model | select = a }
|
||||
, msgMapper = Select
|
||||
, updateFun = Select.update
|
||||
, subscriptionsFun = Select.subscriptions
|
||||
}
|
||||
, multiSelect =
|
||||
{ from = .multiSelect
|
||||
, to = \model a -> { model | multiSelect = a }
|
||||
, msgMapper = MultiSelect
|
||||
, updateFun = MultiSelect.update
|
||||
, subscriptionsFun = MultiSelect.subscriptions
|
||||
}
|
||||
, expansionPanel =
|
||||
{ from = .expansionPanel
|
||||
, to = \model a -> { model | expansionPanel = a }
|
||||
, msgMapper = ExpansionPanel
|
||||
, updateFun = ExpansionPanel.update
|
||||
, subscriptionsFun = ExpansionPanel.subscriptions
|
||||
}
|
||||
, tab =
|
||||
{ from = .tab
|
||||
, to = \model a -> { model | tab = a }
|
||||
, msgMapper = Tab
|
||||
, updateFun = Tab.update
|
||||
, subscriptionsFun = Tab.subscriptions
|
||||
}
|
||||
, sortTable =
|
||||
{ from = .sortTable
|
||||
, to = \model a -> { model | sortTable = a }
|
||||
, msgMapper = SortTable
|
||||
, updateFun = SortTable.update
|
||||
, subscriptionsFun = SortTable.subscriptions
|
||||
}
|
||||
, modal =
|
||||
{ from = .modal
|
||||
, to = \model a -> { model | modal = a }
|
||||
, msgMapper = Modal
|
||||
, updateFun = Modal.update
|
||||
, subscriptionsFun = Modal.subscriptions
|
||||
}
|
||||
, dialog =
|
||||
{ from = .dialog
|
||||
, to = \model a -> { model | dialog = a }
|
||||
, msgMapper = Dialog
|
||||
, updateFun = Dialog.update
|
||||
, subscriptionsFun = Dialog.subscriptions
|
||||
}
|
||||
, textInput =
|
||||
{ from = .textInput
|
||||
, to = \model a -> { model | textInput = a }
|
||||
, msgMapper = TextInput
|
||||
, updateFun = TextInput.update
|
||||
, subscriptionsFun = TextInput.subscriptions
|
||||
}
|
||||
, list =
|
||||
{ from = .list
|
||||
, to = \model a -> { model | list = a }
|
||||
, msgMapper = List
|
||||
, updateFun = List.update
|
||||
, subscriptionsFun = List.subscriptions
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
(case msg of
|
||||
Button m ->
|
||||
updateField .button m
|
||||
|
||||
Select m ->
|
||||
updateField .select m
|
||||
|
||||
MultiSelect m ->
|
||||
updateField .multiSelect m
|
||||
|
||||
ExpansionPanel m ->
|
||||
updateField .expansionPanel m
|
||||
|
||||
Tab m ->
|
||||
updateField .tab m
|
||||
|
||||
SortTable m ->
|
||||
updateField .sortTable m
|
||||
|
||||
Modal m ->
|
||||
updateField .modal m
|
||||
|
||||
Dialog m ->
|
||||
updateField .dialog m
|
||||
|
||||
TextInput m ->
|
||||
updateField .textInput m
|
||||
|
||||
List m ->
|
||||
updateField .list m
|
||||
)
|
||||
model
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
let
|
||||
subFun { from, msgMapper, subscriptionsFun } =
|
||||
subscriptionsFun (from model) |> Sub.map msgMapper
|
||||
in
|
||||
[ upgradeRecord.button |> subFun
|
||||
, upgradeRecord.select |> subFun
|
||||
, upgradeRecord.multiSelect |> subFun
|
||||
, upgradeRecord.expansionPanel |> subFun
|
||||
, upgradeRecord.tab |> subFun
|
||||
, upgradeRecord.sortTable |> subFun
|
||||
, upgradeRecord.modal |> subFun
|
||||
, upgradeRecord.dialog |> subFun
|
||||
, upgradeRecord.textInput |> subFun
|
||||
, upgradeRecord.list |> subFun
|
||||
]
|
||||
|> Sub.batch
|
||||
|
||||
|
||||
view :
|
||||
(Msg -> msg)
|
||||
-> Style msg
|
||||
-> Model
|
||||
-> ExampleView msg
|
||||
view msgMapper style model =
|
||||
{ button =
|
||||
Button.view (Button >> msgMapper) style (.button model)
|
||||
, select =
|
||||
Select.view (Select >> msgMapper) style (.select model)
|
||||
, multiSelect =
|
||||
MultiSelect.view (MultiSelect >> msgMapper) style (.multiSelect model)
|
||||
, expansionPanel =
|
||||
ExpansionPanel.view (ExpansionPanel >> msgMapper) style (.expansionPanel model)
|
||||
, tab =
|
||||
Tab.view (Tab >> msgMapper) style (.tab model)
|
||||
, sortTable =
|
||||
SortTable.view (SortTable >> msgMapper) style (.sortTable model)
|
||||
, modal =
|
||||
Modal.view (Modal >> msgMapper) style (.modal model)
|
||||
, dialog =
|
||||
Dialog.view (Dialog >> msgMapper) style (.dialog model)
|
||||
, textInput =
|
||||
TextInput.view (TextInput >> msgMapper) style (.textInput model)
|
||||
, list =
|
||||
List.view (List >> msgMapper) style (.list model)
|
||||
}
|
||||
|
||||
|
||||
toCardList :
|
||||
{ idle : msg
|
||||
, msgMapper : Msg -> msg
|
||||
, style : Style msg
|
||||
, model : Model
|
||||
}
|
||||
-> List ( String, Element msg, Element msg )
|
||||
toCardList { idle, msgMapper, style, model } =
|
||||
asList
|
||||
|> List.map
|
||||
(\example ->
|
||||
{ title = example |> toString
|
||||
, example = example |> get
|
||||
, test = example |> toTests
|
||||
}
|
||||
)
|
||||
|> List.map
|
||||
(\{ title, example, test } ->
|
||||
( title
|
||||
, model
|
||||
|> view msgMapper style
|
||||
|> example
|
||||
, test idle style
|
||||
|> List.map
|
||||
(\( name, elem ) ->
|
||||
Element.row Grid.spacedEvenly
|
||||
[ name
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.wrappedRow [ Element.width <| Element.shrink ]
|
||||
, elem
|
||||
|> Element.el
|
||||
[ Element.paddingEach
|
||||
{ top = 0
|
||||
, right = 0
|
||||
, bottom = 0
|
||||
, left = 8
|
||||
}
|
||||
, Element.width <| Element.shrink
|
||||
]
|
||||
]
|
||||
)
|
||||
|> Element.column
|
||||
(Grid.simple
|
||||
++ [ Element.width <| Element.fill ]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
{-------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------}
|
||||
|
||||
|
||||
updateField :
|
||||
(UpgradeCollection -> UpgradeRecord model msg)
|
||||
-> msg
|
||||
-> Model
|
||||
-> ( Model, Cmd Msg )
|
||||
updateField getter msg model =
|
||||
let
|
||||
{ from, to, msgMapper, updateFun } =
|
||||
getter upgradeRecord
|
||||
in
|
||||
updateFun msg (from model)
|
||||
|> Tuple.mapBoth (to model) (Cmd.map msgMapper)
|
@ -1,32 +1,29 @@
|
||||
module Data.Section exposing (Section(..),asList,toString,fromString)
|
||||
module Data.Section exposing (Section(..), asList, fromString, toString)
|
||||
|
||||
|
||||
type Section
|
||||
= ComponentViews
|
||||
| ReusableViews
|
||||
= ReusableViews
|
||||
| StatelessViews
|
||||
|
||||
|
||||
asList : List Section
|
||||
asList =
|
||||
[ StatelessViews, ReusableViews, ComponentViews ]
|
||||
[ StatelessViews, ReusableViews ]
|
||||
|
||||
|
||||
toString : Section -> String
|
||||
toString section =
|
||||
case section of
|
||||
ComponentViews ->
|
||||
"Component"
|
||||
|
||||
ReusableViews ->
|
||||
"Reusable"
|
||||
|
||||
StatelessViews ->
|
||||
"Stateless"
|
||||
|
||||
|
||||
fromString : String -> Maybe Section
|
||||
fromString string =
|
||||
case string of
|
||||
"Component" ->
|
||||
Just ComponentViews
|
||||
|
||||
"Reusable" ->
|
||||
Just ReusableViews
|
||||
|
||||
|
32
example/src/Data/Style.elm
Normal file
32
example/src/Data/Style.elm
Normal file
@ -0,0 +1,32 @@
|
||||
module Data.Style exposing (Style)
|
||||
|
||||
import Widget.Style
|
||||
exposing
|
||||
( ButtonStyle
|
||||
, ColumnStyle
|
||||
, DialogStyle
|
||||
, ExpansionPanelStyle
|
||||
, LayoutStyle
|
||||
, RowStyle
|
||||
, SortTableStyle
|
||||
, TabStyle
|
||||
, TextInputStyle
|
||||
)
|
||||
|
||||
|
||||
type alias Style msg =
|
||||
{ dialog : DialogStyle msg
|
||||
, expansionPanel : ExpansionPanelStyle msg
|
||||
, button : ButtonStyle msg
|
||||
, primaryButton : ButtonStyle msg
|
||||
, tab : TabStyle msg
|
||||
, textInput : TextInputStyle msg
|
||||
, chipButton : ButtonStyle msg
|
||||
, row : RowStyle msg
|
||||
, buttonRow : RowStyle msg
|
||||
, column : ColumnStyle msg
|
||||
, cardColumn : ColumnStyle msg
|
||||
, sortTable : SortTableStyle msg
|
||||
, selectButton : ButtonStyle msg
|
||||
, layout : LayoutStyle msg
|
||||
}
|
339
example/src/Data/Style/ElmUiFramework.elm
Normal file
339
example/src/Data/Style/ElmUiFramework.elm
Normal file
@ -0,0 +1,339 @@
|
||||
module Data.Style.ElmUiFramework exposing (style)
|
||||
|
||||
import Data.Style exposing (Style)
|
||||
import Element
|
||||
import Element.Border as Border
|
||||
import Element.Font as Font
|
||||
import Framework
|
||||
import Framework.Button as Button
|
||||
import Framework.Card as Card
|
||||
import Framework.Color as Color
|
||||
import Framework.Grid as Grid
|
||||
import Framework.Group as Group
|
||||
import Framework.Heading as Heading
|
||||
import Framework.Tag as Tag
|
||||
import Icons
|
||||
import Widget.Style
|
||||
exposing
|
||||
( ButtonStyle
|
||||
, ColumnStyle
|
||||
, DialogStyle
|
||||
, ExpansionPanelStyle
|
||||
, LayoutStyle
|
||||
, RowStyle
|
||||
, SnackbarStyle
|
||||
, SortTableStyle
|
||||
, TabStyle
|
||||
, TextInputStyle
|
||||
)
|
||||
|
||||
|
||||
textButton : ButtonStyle msg
|
||||
textButton =
|
||||
{ container = Button.simple
|
||||
, labelRow = Grid.simple
|
||||
, text = []
|
||||
, ifDisabled = Color.disabled
|
||||
, ifActive = Color.primary
|
||||
, otherwise = []
|
||||
}
|
||||
|
||||
|
||||
simpleButton : ButtonStyle msg
|
||||
simpleButton =
|
||||
{ container = Button.simple ++ Color.success
|
||||
, labelRow = Grid.simple
|
||||
, text = []
|
||||
, ifDisabled = Color.disabled
|
||||
, ifActive = Color.primary
|
||||
, otherwise = []
|
||||
}
|
||||
|
||||
|
||||
menuTabButton : ButtonStyle msg
|
||||
menuTabButton =
|
||||
{ container =
|
||||
[ Element.height <| Element.px <| 42
|
||||
, Border.widthEach
|
||||
{ top = 0
|
||||
, left = 0
|
||||
, right = 0
|
||||
, bottom = 4
|
||||
}
|
||||
, Element.paddingEach
|
||||
{ top = 12
|
||||
, left = 8
|
||||
, right = 8
|
||||
, bottom = 4
|
||||
}
|
||||
, Border.color Color.black
|
||||
]
|
||||
, labelRow = Grid.simple
|
||||
, text = []
|
||||
, ifDisabled = Color.disabled
|
||||
, ifActive = [ Border.color Color.turquoise ]
|
||||
, otherwise = []
|
||||
}
|
||||
|
||||
|
||||
menuButton : ButtonStyle msg
|
||||
menuButton =
|
||||
{ labelRow = Grid.simple
|
||||
, container = Button.simple ++ Group.center ++ Color.dark
|
||||
, text = []
|
||||
, ifDisabled = Color.disabled
|
||||
, ifActive = Color.primary
|
||||
, otherwise = []
|
||||
}
|
||||
|
||||
|
||||
sheetButton : ButtonStyle msg
|
||||
sheetButton =
|
||||
{ container =
|
||||
Button.fill
|
||||
++ Group.center
|
||||
++ Color.light
|
||||
++ [ Font.alignLeft ]
|
||||
, labelRow = Grid.simple
|
||||
, text = []
|
||||
, ifDisabled = Color.disabled
|
||||
, ifActive = Color.primary
|
||||
, otherwise = []
|
||||
}
|
||||
|
||||
|
||||
buttonStyle : ButtonStyle msg
|
||||
buttonStyle =
|
||||
{ labelRow = [ Element.spacing 8 ]
|
||||
, container = Button.simple
|
||||
, text = []
|
||||
, ifDisabled = Color.disabled
|
||||
, ifActive = Color.primary
|
||||
, otherwise = []
|
||||
}
|
||||
|
||||
|
||||
snackbarButton : ButtonStyle msg
|
||||
snackbarButton =
|
||||
{ labelRow = Grid.simple
|
||||
, container = Button.simple ++ Color.dark
|
||||
, text = []
|
||||
, ifDisabled = Color.disabled
|
||||
, ifActive = Color.primary
|
||||
, otherwise = []
|
||||
}
|
||||
|
||||
|
||||
tabButtonStyle : ButtonStyle msg
|
||||
tabButtonStyle =
|
||||
{ labelRow = [ Element.spacing 8 ]
|
||||
, container = Button.simple ++ Group.top
|
||||
, text = []
|
||||
, ifDisabled = Color.disabled
|
||||
, ifActive = Color.primary
|
||||
, otherwise = []
|
||||
}
|
||||
|
||||
|
||||
textInputStyle : TextInputStyle msg
|
||||
textInputStyle =
|
||||
{ chipButton = chipButtonStyle
|
||||
, chipsRow =
|
||||
[ Element.width <| Element.shrink
|
||||
, Element.spacing <| 4
|
||||
, Element.paddingEach
|
||||
{ top = 8
|
||||
, left = 0
|
||||
, right = 0
|
||||
, bottom = 8
|
||||
}
|
||||
]
|
||||
, containerRow =
|
||||
Button.simple
|
||||
++ Color.light
|
||||
++ [ Border.color <| Element.rgb255 186 189 182
|
||||
, Font.alignLeft
|
||||
, Element.paddingXY 8 0
|
||||
, Element.height <| Element.px <| 42
|
||||
]
|
||||
++ Grid.simple
|
||||
, input =
|
||||
Color.light
|
||||
++ [ Element.padding 8
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
chipButtonStyle : ButtonStyle msg
|
||||
chipButtonStyle =
|
||||
{ container = Tag.simple
|
||||
, text = []
|
||||
, ifDisabled = []
|
||||
, labelRow = Grid.simple
|
||||
, ifActive = Color.primary
|
||||
, otherwise = []
|
||||
}
|
||||
|
||||
|
||||
expansionPanelStyle : ExpansionPanelStyle msg
|
||||
expansionPanelStyle =
|
||||
{ containerColumn = Card.simple ++ Grid.simple ++ [ Element.height <| Element.shrink ]
|
||||
, panelRow = Grid.spacedEvenly ++ [ Element.height <| Element.shrink ]
|
||||
, labelRow = Grid.simple ++ [ Element.height <| Element.shrink ]
|
||||
, content = []
|
||||
, expandIcon = Icons.chevronDown |> Element.html |> Element.el []
|
||||
, collapseIcon = Icons.chevronUp |> Element.html |> Element.el []
|
||||
}
|
||||
|
||||
|
||||
dialog : DialogStyle msg
|
||||
dialog =
|
||||
{ containerColumn =
|
||||
Card.simple
|
||||
++ Grid.simple
|
||||
++ [ Element.centerY
|
||||
, Element.width <| Element.minimum 280 <| Element.maximum 560 <| Element.fill
|
||||
]
|
||||
, title = Heading.h3
|
||||
, text = []
|
||||
, buttonRow =
|
||||
Grid.simple
|
||||
++ [ Element.paddingEach
|
||||
{ top = 28
|
||||
, bottom = 0
|
||||
, left = 0
|
||||
, right = 0
|
||||
}
|
||||
]
|
||||
, acceptButton = simpleButton
|
||||
, dismissButton = textButton
|
||||
}
|
||||
|
||||
|
||||
snackbar : SnackbarStyle msg
|
||||
snackbar =
|
||||
{ containerRow =
|
||||
Card.simple
|
||||
++ Color.dark
|
||||
++ Grid.simple
|
||||
++ [ Element.paddingXY 8 6
|
||||
, Element.height <| Element.px <| 54
|
||||
]
|
||||
, button = snackbarButton
|
||||
, text = [ Element.paddingXY 8 0 ]
|
||||
}
|
||||
|
||||
|
||||
tab : TabStyle msg
|
||||
tab =
|
||||
{ button = tabButtonStyle
|
||||
, optionRow = Grid.simple
|
||||
, containerColumn = Grid.compact
|
||||
, content = Card.small ++ Group.bottom
|
||||
}
|
||||
|
||||
|
||||
row : RowStyle msg
|
||||
row =
|
||||
{ containerRow = Grid.simple
|
||||
, element = []
|
||||
, ifFirst = Group.left
|
||||
, ifLast = Group.right
|
||||
, otherwise = Group.center
|
||||
}
|
||||
|
||||
|
||||
buttonRow : RowStyle msg
|
||||
buttonRow =
|
||||
{ containerRow = Grid.compact
|
||||
, element = []
|
||||
, ifFirst = Group.left
|
||||
, ifLast = Group.right
|
||||
, otherwise = Group.center
|
||||
}
|
||||
|
||||
|
||||
cardColumn : ColumnStyle msg
|
||||
cardColumn =
|
||||
{ containerColumn = Grid.compact ++ [ Element.height <| Element.fill ]
|
||||
, element = Card.large ++ [ Element.height <| Element.fill ]
|
||||
, ifFirst = Group.top
|
||||
, ifLast = Group.bottom
|
||||
, otherwise = Group.center
|
||||
}
|
||||
|
||||
|
||||
column : ColumnStyle msg
|
||||
column =
|
||||
{ containerColumn = Grid.compact
|
||||
, element = []
|
||||
, ifFirst = Group.top
|
||||
, ifLast = Group.bottom
|
||||
, otherwise = Group.center
|
||||
}
|
||||
|
||||
|
||||
sortTable : SortTableStyle msg
|
||||
sortTable =
|
||||
{ containerTable = Grid.simple
|
||||
, headerButton = tabButtonStyle
|
||||
, ascIcon = Icons.chevronUp |> Element.html |> Element.el []
|
||||
, descIcon = Icons.chevronDown |> Element.html |> Element.el []
|
||||
, defaultIcon = Element.none
|
||||
}
|
||||
|
||||
|
||||
layout : LayoutStyle msg
|
||||
layout =
|
||||
{ container = []
|
||||
, snackbar = snackbar
|
||||
, layout = Framework.responsiveLayout
|
||||
, header =
|
||||
Framework.container
|
||||
++ Color.dark
|
||||
++ [ Element.padding <| 0
|
||||
, Element.height <| Element.px <| 42
|
||||
]
|
||||
, menuButton = menuButton
|
||||
, sheetButton = sheetButton
|
||||
, menuTabButton = menuTabButton
|
||||
, sheet =
|
||||
Color.light ++ [ Element.width <| Element.maximum 256 <| Element.fill ]
|
||||
, menuIcon =
|
||||
Icons.menu |> Element.html |> Element.el []
|
||||
, moreVerticalIcon =
|
||||
Icons.moreVertical |> Element.html |> Element.el []
|
||||
, spacing = 8
|
||||
, title = Heading.h2
|
||||
, searchIcon =
|
||||
Icons.search |> Element.html |> Element.el []
|
||||
, search =
|
||||
Color.simple
|
||||
++ Card.large
|
||||
++ [ Font.color <| Element.rgb255 0 0 0
|
||||
, Element.padding 6
|
||||
, Element.centerY
|
||||
, Element.alignRight
|
||||
]
|
||||
, searchFill =
|
||||
Color.light ++ Group.center
|
||||
}
|
||||
|
||||
|
||||
style : Style msg
|
||||
style =
|
||||
{ sortTable = sortTable
|
||||
, row = row
|
||||
, buttonRow = buttonRow
|
||||
, cardColumn = cardColumn
|
||||
, column = column
|
||||
, button = buttonStyle
|
||||
, primaryButton = simpleButton
|
||||
, tab = tab
|
||||
, textInput = textInputStyle
|
||||
, chipButton = chipButtonStyle
|
||||
, expansionPanel = expansionPanelStyle
|
||||
, dialog = dialog
|
||||
, selectButton = buttonStyle
|
||||
, layout = layout
|
||||
}
|
56
example/src/Data/Style/Material.elm
Normal file
56
example/src/Data/Style/Material.elm
Normal file
@ -0,0 +1,56 @@
|
||||
module Data.Style.Material exposing (style)
|
||||
|
||||
import Color exposing (Color)
|
||||
import Color.Accessibility as Accessibility
|
||||
import Color.Convert as Convert
|
||||
import Data.Style exposing (Style)
|
||||
import Element exposing (Attribute, Element)
|
||||
import Element.Background as Background
|
||||
import Element.Border as Border
|
||||
import Element.Font as Font
|
||||
import Html.Attributes as Attributes
|
||||
import Icons
|
||||
import Widget.Style
|
||||
exposing
|
||||
( ButtonStyle
|
||||
, ColumnStyle
|
||||
, DialogStyle
|
||||
, ExpansionPanelStyle
|
||||
, LayoutStyle
|
||||
, RowStyle
|
||||
, SnackbarStyle
|
||||
, SortTableStyle
|
||||
, TabStyle
|
||||
, TextInputStyle
|
||||
)
|
||||
import Widget.Style.Material as Material exposing (Palette)
|
||||
import Widget.Style.Template as Template
|
||||
|
||||
|
||||
sortTable : Palette -> SortTableStyle msg
|
||||
sortTable palette =
|
||||
{ containerTable = []
|
||||
, headerButton = Material.textButton palette
|
||||
, ascIcon = Icons.chevronUp |> Element.html |> Element.el []
|
||||
, descIcon = Icons.chevronDown |> Element.html |> Element.el []
|
||||
, defaultIcon = Element.none
|
||||
}
|
||||
|
||||
|
||||
style : Palette -> Style msg
|
||||
style palette =
|
||||
{ sortTable = sortTable palette
|
||||
, row = Material.row
|
||||
, buttonRow = Material.buttonRow
|
||||
, cardColumn = Material.cardColumn palette
|
||||
, column = Material.column
|
||||
, button = Material.outlinedButton palette
|
||||
, primaryButton = Material.containedButton palette
|
||||
, selectButton = Material.toggleButton palette
|
||||
, tab = Material.tab palette
|
||||
, textInput = Material.textInput palette
|
||||
, chipButton = Material.chip palette
|
||||
, expansionPanel = Material.expansionPanel palette
|
||||
, dialog = Material.alertDialog palette
|
||||
, layout = Material.layout palette
|
||||
}
|
27
example/src/Data/Style/Template.elm
Normal file
27
example/src/Data/Style/Template.elm
Normal file
@ -0,0 +1,27 @@
|
||||
module Data.Style.Template exposing (style)
|
||||
|
||||
import Data.Style exposing (Style)
|
||||
import Element exposing (Attribute, Element)
|
||||
import Element.Background as Background
|
||||
import Element.Border as Border
|
||||
import Element.Font as Font
|
||||
import Widget.Style.Template as Template
|
||||
|
||||
|
||||
style : Style msg
|
||||
style =
|
||||
{ sortTable = Template.sortTable <| "sortTable"
|
||||
, row = Template.row <| "row"
|
||||
, buttonRow = Template.row <| "buttonRow"
|
||||
, cardColumn = Template.column <| "cardRow"
|
||||
, column = Template.column <| "column"
|
||||
, button = Template.button <| "button"
|
||||
, primaryButton = Template.button <| "primaryButton"
|
||||
, tab = Template.tab <| "tab"
|
||||
, textInput = Template.textInput <| "textInput"
|
||||
, chipButton = Template.button <| "chipButton"
|
||||
, expansionPanel = Template.expansionPanel "expansionPanel"
|
||||
, selectButton = Template.button "selectButton"
|
||||
, dialog = Template.dialog "dialog"
|
||||
, layout = Template.layout "layout"
|
||||
}
|
30
example/src/Data/Theme.elm
Normal file
30
example/src/Data/Theme.elm
Normal file
@ -0,0 +1,30 @@
|
||||
module Data.Theme exposing (Theme(..), toStyle)
|
||||
|
||||
import Data.Style exposing (Style)
|
||||
import Data.Style.ElmUiFramework
|
||||
import Data.Style.Material
|
||||
import Data.Style.Template
|
||||
import Widget.Style.Material
|
||||
|
||||
|
||||
type Theme
|
||||
= ElmUiFramework
|
||||
| Template
|
||||
| Material
|
||||
| DarkMaterial
|
||||
|
||||
|
||||
toStyle : Theme -> Style msg
|
||||
toStyle theme =
|
||||
case theme of
|
||||
ElmUiFramework ->
|
||||
Data.Style.ElmUiFramework.style
|
||||
|
||||
Template ->
|
||||
Data.Style.Template.style
|
||||
|
||||
Material ->
|
||||
Data.Style.Material.style Widget.Style.Material.defaultPalette
|
||||
|
||||
DarkMaterial ->
|
||||
Data.Style.Material.style Widget.Style.Material.darkPalette
|
@ -1,395 +0,0 @@
|
||||
module Example exposing (main)
|
||||
|
||||
import Browser
|
||||
import Browser.Dom as Dom exposing (Viewport)
|
||||
import Browser.Events as Events
|
||||
import Browser.Navigation as Navigation
|
||||
import Component
|
||||
import Element exposing (DeviceClass(..), Element)
|
||||
import Element.Input as Input
|
||||
import Element.Font as Font
|
||||
import Element.Border as Border
|
||||
import Framework
|
||||
import Framework.Button as Button
|
||||
import Framework.Card as Card
|
||||
import Framework.Color as Color
|
||||
import Framework.Grid as Grid
|
||||
import Framework.Group as Group
|
||||
import Framework.Heading as Heading
|
||||
import Framework.Input as Input
|
||||
import Framework.Tag as Tag
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attributes
|
||||
import Icons
|
||||
import Layout exposing (Direction, Layout)
|
||||
import Core.Style exposing (Style)
|
||||
import Reusable
|
||||
import Set exposing (Set)
|
||||
import Stateless
|
||||
import Task
|
||||
import Time
|
||||
import Widget
|
||||
import Widget.FilterSelect as FilterSelect
|
||||
import Widget.ScrollingNav as ScrollingNav
|
||||
import Widget.Snackbar as Snackbar
|
||||
import Widget.ValidatedInput as ValidatedInput
|
||||
import Data.Section as Section exposing (Section(..))
|
||||
|
||||
type alias LoadedModel =
|
||||
{ component : Component.Model
|
||||
, stateless : Stateless.Model
|
||||
, reusable : Reusable.Model
|
||||
, scrollingNav : ScrollingNav.Model Section
|
||||
, layout : Layout
|
||||
, displayDialog : Bool
|
||||
, deviceClass : DeviceClass
|
||||
}
|
||||
|
||||
|
||||
type Model
|
||||
= Loading
|
||||
| Loaded LoadedModel
|
||||
|
||||
|
||||
type LoadedMsg
|
||||
= StatelessSpecific Stateless.Msg
|
||||
| ReusableSpecific Reusable.Msg
|
||||
| ComponentSpecific Component.Msg
|
||||
| ScrollingNavSpecific (ScrollingNav.Msg Section)
|
||||
| TimePassed Int
|
||||
| AddSnackbar String
|
||||
| ToggleDialog Bool
|
||||
| ChangedSidebar (Maybe Direction)
|
||||
| Resized { width : Int, height : Int }
|
||||
| Load String
|
||||
| JumpTo Section
|
||||
|
||||
|
||||
type Msg
|
||||
= LoadedSpecific LoadedMsg
|
||||
| GotViewport Viewport
|
||||
|
||||
style : Style msg
|
||||
style =
|
||||
{ snackbar = Card.simple ++ Color.dark
|
||||
, layout = Framework.responsiveLayout
|
||||
, header =
|
||||
Framework.container
|
||||
++ Color.dark
|
||||
++ [ Element.padding <| 0
|
||||
, Element.height <| Element.px <| 42
|
||||
]
|
||||
, menuButton =
|
||||
Button.simple ++ Group.center ++ Color.dark
|
||||
, menuButtonSelected =
|
||||
Color.primary
|
||||
, sheetButton =
|
||||
Button.fill
|
||||
++ Group.center
|
||||
++ Color.light
|
||||
++ [Font.alignLeft]
|
||||
, sheetButtonSelected =
|
||||
Color.primary
|
||||
, tabButton =
|
||||
[ Element.height <| Element.px <| 42
|
||||
, Border.widthEach
|
||||
{ top = 0,
|
||||
left = 0,
|
||||
right = 0,
|
||||
bottom = 8
|
||||
}
|
||||
]
|
||||
, tabButtonSelected =
|
||||
[ Border.color Color.turquoise
|
||||
]
|
||||
, sheet =
|
||||
Color.light ++ [ Element.width <| Element.maximum 256 <| Element.fill]
|
||||
, menuIcon =
|
||||
Icons.menu |> Element.html |> Element.el []
|
||||
, moreVerticalIcon =
|
||||
Icons.moreVertical |> Element.html |> Element.el []
|
||||
, spacing = 8
|
||||
}
|
||||
|
||||
|
||||
initialModel : Viewport -> ( LoadedModel, Cmd LoadedMsg )
|
||||
initialModel { viewport } =
|
||||
let
|
||||
( scrollingNav, cmd ) =
|
||||
ScrollingNav.init
|
||||
{ labels = Section.toString
|
||||
, arrangement = Section.asList
|
||||
}
|
||||
in
|
||||
( { component = Component.init
|
||||
, stateless = Stateless.init
|
||||
, reusable = Reusable.init
|
||||
, scrollingNav = scrollingNav
|
||||
, layout = Layout.init
|
||||
, displayDialog = False
|
||||
, deviceClass =
|
||||
{ width = viewport.width |> round
|
||||
, height = viewport.height |> round
|
||||
}
|
||||
|> Element.classifyDevice
|
||||
|> .class
|
||||
}
|
||||
, cmd |> Cmd.map ScrollingNavSpecific
|
||||
)
|
||||
|
||||
|
||||
init : () -> ( Model, Cmd Msg )
|
||||
init () =
|
||||
( Loading
|
||||
, Task.perform GotViewport Dom.getViewport
|
||||
)
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
case model of
|
||||
Loading ->
|
||||
Element.none |> Framework.responsiveLayout []
|
||||
|
||||
Loaded m ->
|
||||
Html.map LoadedSpecific <|
|
||||
Layout.view []
|
||||
{ dialog =
|
||||
if m.displayDialog then
|
||||
Just
|
||||
{ onDismiss = Just <| ToggleDialog False
|
||||
, content =
|
||||
[ Element.el Heading.h3 <| Element.text "Dialog"
|
||||
, "This is a dialog window"
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
, Input.button (Button.simple ++ [ Element.alignRight ])
|
||||
{ onPress = Just <| ToggleDialog False
|
||||
, label = Element.text "Ok"
|
||||
}
|
||||
]
|
||||
|> Element.column
|
||||
( Grid.simple
|
||||
++ Card.large
|
||||
++ [Element.centerX, Element.centerY]
|
||||
)
|
||||
}
|
||||
|
||||
else
|
||||
Nothing
|
||||
, content =
|
||||
[ Element.el [ Element.height <| Element.px <| 42 ] <| Element.none
|
||||
, [ m.scrollingNav
|
||||
|> ScrollingNav.view
|
||||
(\section ->
|
||||
case section of
|
||||
ComponentViews ->
|
||||
m.component
|
||||
|> Component.view
|
||||
|> Element.map ComponentSpecific
|
||||
|
||||
ReusableViews ->
|
||||
Reusable.view
|
||||
{ addSnackbar = AddSnackbar
|
||||
, model = m.reusable
|
||||
, msgMapper = ReusableSpecific
|
||||
}
|
||||
|
||||
StatelessViews ->
|
||||
Stateless.view
|
||||
{ msgMapper = StatelessSpecific
|
||||
, showDialog = ToggleDialog True
|
||||
, changedSheet = ChangedSidebar
|
||||
}
|
||||
m.stateless
|
||||
)
|
||||
]
|
||||
|> Element.column Framework.container
|
||||
]
|
||||
|> Element.column Grid.compact
|
||||
, style = style
|
||||
, layout = m.layout
|
||||
, deviceClass = m.deviceClass
|
||||
, menu =
|
||||
{ selected =
|
||||
Section.asList
|
||||
|> List.indexedMap (\i s -> (i,s))
|
||||
|> List.filterMap
|
||||
( \(i,s) ->
|
||||
if m.scrollingNav
|
||||
|> ScrollingNav.current Section.fromString
|
||||
|> (==) (Just s)
|
||||
then
|
||||
Just i
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
|> List.head
|
||||
|> Maybe.withDefault 0
|
||||
, items =
|
||||
Section.asList
|
||||
|> List.map
|
||||
(\label ->
|
||||
{ icon = Element.none
|
||||
, label = label |> Section.toString
|
||||
, onPress = Just <| JumpTo <| label
|
||||
}
|
||||
)
|
||||
}
|
||||
, actions =
|
||||
[ { onPress = Just <| Load "https://package.elm-lang.org/packages/Orasund/elm-ui-widgets/latest/"
|
||||
, label = "Docs"
|
||||
, icon = Icons.book|> Element.html |> Element.el []
|
||||
}
|
||||
, { onPress = Just <| Load "https://github.com/Orasund/elm-ui-widgets"
|
||||
, label = "Github"
|
||||
, icon = Icons.github|> Element.html |> Element.el []
|
||||
}
|
||||
, { onPress = Nothing
|
||||
, label = "Placeholder"
|
||||
, icon = Icons.circle|> Element.html |> Element.el []
|
||||
}
|
||||
, { onPress = Nothing
|
||||
, label = "Placeholder"
|
||||
, icon = Icons.triangle|> Element.html |> Element.el []
|
||||
}
|
||||
, { onPress = Nothing
|
||||
, label = "Placeholder"
|
||||
, icon = Icons.square|> Element.html |> Element.el []
|
||||
}
|
||||
]
|
||||
, onChangedSidebar = ChangedSidebar
|
||||
, title =
|
||||
(if m.deviceClass == Phone || m.deviceClass == Tablet then
|
||||
m.scrollingNav
|
||||
|> ScrollingNav.current Section.fromString
|
||||
|> Maybe.map Section.toString
|
||||
|> Maybe.withDefault "Elm-Ui-Widgets"
|
||||
else
|
||||
"Elm-Ui-Widgets"
|
||||
)
|
||||
|> Element.text
|
||||
|> Element.el Heading.h1
|
||||
}
|
||||
|
||||
|
||||
updateLoaded : LoadedMsg -> LoadedModel -> ( LoadedModel, Cmd LoadedMsg )
|
||||
updateLoaded msg model =
|
||||
case msg of
|
||||
ComponentSpecific m ->
|
||||
model.component
|
||||
|> Component.update m
|
||||
|> Tuple.mapBoth
|
||||
(\component ->
|
||||
{ model
|
||||
| component = component
|
||||
}
|
||||
)
|
||||
(Cmd.map ComponentSpecific)
|
||||
|
||||
ReusableSpecific m ->
|
||||
( model.reusable
|
||||
|> Reusable.update m
|
||||
|> (\reusable ->
|
||||
{ model
|
||||
| reusable = reusable
|
||||
}
|
||||
)
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
StatelessSpecific m ->
|
||||
model.stateless
|
||||
|> Stateless.update m
|
||||
|> Tuple.mapBoth
|
||||
(\stateless ->
|
||||
{ model
|
||||
| stateless = stateless
|
||||
}
|
||||
)
|
||||
(Cmd.map StatelessSpecific)
|
||||
|
||||
ScrollingNavSpecific m ->
|
||||
model.scrollingNav
|
||||
|> ScrollingNav.update m
|
||||
|> Tuple.mapBoth
|
||||
(\scrollingNav ->
|
||||
{ model
|
||||
| scrollingNav = scrollingNav
|
||||
}
|
||||
)
|
||||
(Cmd.map ScrollingNavSpecific)
|
||||
|
||||
TimePassed int ->
|
||||
( { model
|
||||
| layout = model.layout |> Layout.timePassed int
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
AddSnackbar string ->
|
||||
( { model | layout = model.layout |> Layout.queueMessage string }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ToggleDialog bool ->
|
||||
( { model | displayDialog = bool }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Resized screen ->
|
||||
( { model | deviceClass = screen |> Element.classifyDevice |> .class }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ChangedSidebar sidebar ->
|
||||
( { model | layout = model.layout |> Layout.setSidebar sidebar }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Load string ->
|
||||
( model, Navigation.load string)
|
||||
|
||||
JumpTo section ->
|
||||
( model
|
||||
, model.scrollingNav
|
||||
|> ScrollingNav.jumpTo section
|
||||
|> Cmd.map ScrollingNavSpecific
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case ( model, msg ) of
|
||||
( Loading, GotViewport viewport ) ->
|
||||
initialModel viewport
|
||||
|> Tuple.mapBoth Loaded (Cmd.map LoadedSpecific)
|
||||
|
||||
( Loaded state, LoadedSpecific m ) ->
|
||||
updateLoaded m state
|
||||
|> Tuple.mapBoth Loaded (Cmd.map LoadedSpecific)
|
||||
|
||||
_ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
Sub.batch
|
||||
[ ScrollingNav.subscriptions
|
||||
|> Sub.map ScrollingNavSpecific
|
||||
, Time.every 50 (always (TimePassed 50))
|
||||
, Events.onResize (\h w -> Resized { height = h, width = w })
|
||||
]
|
||||
|> Sub.map LoadedSpecific
|
||||
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = init
|
||||
, view = view
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
96
example/src/Example/Button.elm
Normal file
96
example/src/Example/Button.elm
Normal file
@ -0,0 +1,96 @@
|
||||
module Example.Button exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import FeatherIcons
|
||||
import Widget
|
||||
import Widget.Style exposing (ButtonStyle, RowStyle)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| primaryButton : ButtonStyle msg
|
||||
, button : ButtonStyle msg
|
||||
, row : RowStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ primaryButton = Material.containedButton Material.defaultPalette
|
||||
, button = Material.outlinedButton Material.defaultPalette
|
||||
, row = Material.row
|
||||
}
|
||||
|
||||
|
||||
type Model
|
||||
= IsButtonEnabled Bool
|
||||
|
||||
|
||||
type Msg
|
||||
= ChangedButtonStatus Bool
|
||||
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( IsButtonEnabled True
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg _ =
|
||||
case msg of
|
||||
ChangedButtonStatus bool ->
|
||||
( IsButtonEnabled bool
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view msgMapper style (IsButtonEnabled isButtonEnabled) =
|
||||
[ Widget.button style.primaryButton
|
||||
{ text = "disable me"
|
||||
, icon =
|
||||
FeatherIcons.slash
|
||||
|> FeatherIcons.withSize 16
|
||||
|> FeatherIcons.toHtml []
|
||||
|> Element.html
|
||||
|> Element.el []
|
||||
, onPress =
|
||||
if 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
|
||||
}
|
||||
]
|
||||
|> Widget.row style.row
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
104
example/src/Example/Dialog.elm
Normal file
104
example/src/Example/Dialog.elm
Normal file
@ -0,0 +1,104 @@
|
||||
module Example.Dialog exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import FeatherIcons
|
||||
import Widget
|
||||
import Widget.Style exposing (ButtonStyle, DialogStyle)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| dialog : DialogStyle msg
|
||||
, primaryButton : ButtonStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ dialog = Material.alertDialog Material.defaultPalette
|
||||
, primaryButton = Material.containedButton Material.defaultPalette
|
||||
}
|
||||
|
||||
type Model
|
||||
= IsOpen Bool
|
||||
|
||||
|
||||
type Msg
|
||||
= OpenDialog Bool
|
||||
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( IsOpen True
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg _ =
|
||||
case msg of
|
||||
OpenDialog bool ->
|
||||
( IsOpen bool
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view msgMapper style (IsOpen isOpen) =
|
||||
Widget.button style.primaryButton
|
||||
{ text = "show Dialog"
|
||||
, icon =
|
||||
FeatherIcons.eye
|
||||
|> FeatherIcons.withSize 16
|
||||
|> FeatherIcons.toHtml []
|
||||
|> Element.html
|
||||
|> Element.el []
|
||||
, onPress =
|
||||
OpenDialog True
|
||||
|> msgMapper
|
||||
|> Just
|
||||
}
|
||||
|> Element.el
|
||||
([ Element.height <| Element.minimum 200 <| Element.fill
|
||||
, Element.width <| Element.minimum 400 <| Element.fill
|
||||
]
|
||||
++ (if isOpen then
|
||||
{ text = "This is a dialog window"
|
||||
, title = Just "Dialog"
|
||||
, accept =
|
||||
Just
|
||||
{ text = "Ok"
|
||||
, onPress =
|
||||
Just <|
|
||||
msgMapper <|
|
||||
OpenDialog False
|
||||
}
|
||||
, dismiss =
|
||||
Just
|
||||
{ text = "Dismiss"
|
||||
, onPress =
|
||||
Just <|
|
||||
msgMapper <|
|
||||
OpenDialog False
|
||||
}
|
||||
}
|
||||
|> Widget.dialog style.dialog
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
)
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
65
example/src/Example/ExpansionPanel.elm
Normal file
65
example/src/Example/ExpansionPanel.elm
Normal file
@ -0,0 +1,65 @@
|
||||
module Example.ExpansionPanel exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Widget
|
||||
import Widget.Style exposing (ExpansionPanelStyle)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| expansionPanel : ExpansionPanelStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ expansionPanel = Material.expansionPanel Material.defaultPalette
|
||||
}
|
||||
|
||||
type Model
|
||||
= IsExpanded Bool
|
||||
|
||||
|
||||
type Msg
|
||||
= ToggleCollapsable Bool
|
||||
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( IsExpanded False
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg _ =
|
||||
case msg of
|
||||
ToggleCollapsable bool ->
|
||||
( IsExpanded bool
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view msgMapper style (IsExpanded isExpanded) =
|
||||
{ onToggle = ToggleCollapsable >> msgMapper
|
||||
, isExpanded = isExpanded
|
||||
, icon = Element.none
|
||||
, text = "Title"
|
||||
, content = Element.text <| "Hello World"
|
||||
}
|
||||
|> Widget.expansionPanel style.expansionPanel
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
61
example/src/Example/List.elm
Normal file
61
example/src/Example/List.elm
Normal file
@ -0,0 +1,61 @@
|
||||
module Example.List exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Widget
|
||||
import Widget.Style exposing (ColumnStyle)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| cardColumn : ColumnStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ cardColumn = Material.cardColumn Material.defaultPalette
|
||||
}
|
||||
|
||||
type alias Model =
|
||||
()
|
||||
|
||||
|
||||
type alias Msg =
|
||||
Never
|
||||
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( ()
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update _ () =
|
||||
( ()
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions () =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view _ style () =
|
||||
[ Element.text <| "A"
|
||||
, Element.text <| "B"
|
||||
, Element.text <| "C"
|
||||
]
|
||||
|> Widget.column style.cardColumn
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
103
example/src/Example/Modal.elm
Normal file
103
example/src/Example/Modal.elm
Normal file
@ -0,0 +1,103 @@
|
||||
module Example.Modal exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import FeatherIcons
|
||||
import Widget
|
||||
import Widget.Style exposing (ButtonStyle, ColumnStyle)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| cardColumn : ColumnStyle msg
|
||||
, primaryButton : ButtonStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ cardColumn = Material.cardColumn Material.defaultPalette
|
||||
, primaryButton = Material.containedButton Material.defaultPalette
|
||||
}
|
||||
|
||||
type Model
|
||||
= IsEnabled Bool
|
||||
|
||||
|
||||
type Msg
|
||||
= ToggleModal Bool
|
||||
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( IsEnabled True
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg _ =
|
||||
case msg of
|
||||
ToggleModal bool ->
|
||||
( IsEnabled bool
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view msgMapper style (IsEnabled isEnabled) =
|
||||
Widget.button style.primaryButton
|
||||
{ text = "show Modal"
|
||||
, icon =
|
||||
FeatherIcons.eye
|
||||
|> FeatherIcons.withSize 16
|
||||
|> FeatherIcons.toHtml []
|
||||
|> Element.html
|
||||
|> Element.el []
|
||||
, onPress =
|
||||
ToggleModal True
|
||||
|> msgMapper
|
||||
|> Just
|
||||
}
|
||||
|> Element.el
|
||||
([ Element.height <| Element.minimum 200 <| Element.fill
|
||||
, Element.width <| Element.minimum 400 <| Element.fill
|
||||
]
|
||||
++ (if isEnabled then
|
||||
Widget.modal
|
||||
{ onDismiss =
|
||||
ToggleModal False
|
||||
|> msgMapper
|
||||
|> Just
|
||||
, content =
|
||||
"Click on the area around this box to close it."
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
|> List.singleton
|
||||
|> Widget.column style.cardColumn
|
||||
|> Element.el
|
||||
[ Element.height <| Element.px 100
|
||||
, Element.width <| Element.px 250
|
||||
, Element.centerX
|
||||
, Element.centerY
|
||||
]
|
||||
}
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
)
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
84
example/src/Example/MultiSelect.elm
Normal file
84
example/src/Example/MultiSelect.elm
Normal file
@ -0,0 +1,84 @@
|
||||
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)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| buttonRow : RowStyle msg
|
||||
, selectButton : ButtonStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ buttonRow = Material.buttonRow
|
||||
, selectButton = Material.toggleButton Material.defaultPalette
|
||||
}
|
||||
|
||||
type 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 (Selected selected) =
|
||||
case msg of
|
||||
ChangedSelected int ->
|
||||
( selected
|
||||
|> (if selected |> Set.member int then
|
||||
Set.remove int
|
||||
|
||||
else
|
||||
Set.insert int
|
||||
)
|
||||
|> Selected
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view msgMapper style (Selected selected) =
|
||||
{ selected = 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
76
example/src/Example/Select.elm
Normal file
76
example/src/Example/Select.elm
Normal file
@ -0,0 +1,76 @@
|
||||
module Example.Select exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Widget
|
||||
import Widget.Style exposing (ButtonStyle, RowStyle)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| buttonRow : RowStyle msg
|
||||
, selectButton : ButtonStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ buttonRow = Material.buttonRow
|
||||
, selectButton = Material.toggleButton Material.defaultPalette
|
||||
}
|
||||
|
||||
type 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 _ =
|
||||
case msg of
|
||||
ChangedSelected int ->
|
||||
( Selected <| Just int
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view msgMapper style (Selected selected) =
|
||||
{ selected = 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
114
example/src/Example/SortTable.elm
Normal file
114
example/src/Example/SortTable.elm
Normal file
@ -0,0 +1,114 @@
|
||||
module Example.SortTable exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Widget
|
||||
import Widget.Style exposing (SortTableStyle)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| sortTable : SortTableStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ sortTable =
|
||||
{ containerTable = []
|
||||
, headerButton = Material.textButton Material.defaultPalette
|
||||
, ascIcon =
|
||||
Material.expansionPanel Material.defaultPalette
|
||||
|> .collapseIcon
|
||||
, descIcon =
|
||||
Material.expansionPanel Material.defaultPalette
|
||||
|> .expandIcon
|
||||
, defaultIcon = Element.none
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ title : String
|
||||
, asc : Bool
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= ChangedSorting 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
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view msgMapper style model =
|
||||
Widget.sortTable style.sortTable
|
||||
{ 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.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
|
||||
}
|
||||
, Widget.unsortableColumn
|
||||
{ title = "Hash"
|
||||
, toString = .hash >> Maybe.withDefault "None"
|
||||
, width = Element.fill
|
||||
}
|
||||
]
|
||||
, asc = model.asc
|
||||
, sortBy = model.title
|
||||
, onChange = ChangedSorting >> msgMapper
|
||||
}
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
88
example/src/Example/Tab.elm
Normal file
88
example/src/Example/Tab.elm
Normal file
@ -0,0 +1,88 @@
|
||||
module Example.Tab exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Widget
|
||||
import Widget.Style exposing (TabStyle)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| tab : TabStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ tab = Material.tab Material.defaultPalette
|
||||
}
|
||||
|
||||
type 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 _ =
|
||||
case msg of
|
||||
ChangedTab int ->
|
||||
( Selected <| Just int
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view msgMapper style (Selected selected) =
|
||||
Widget.tab style.tab
|
||||
{ tabs =
|
||||
{ selected = selected
|
||||
, options =
|
||||
[ 1, 2, 3 ]
|
||||
|> List.map
|
||||
(\int ->
|
||||
{ text = "Tab " ++ (int |> String.fromInt)
|
||||
, icon = Element.none
|
||||
}
|
||||
)
|
||||
, onSelect = ChangedTab >> msgMapper >> Just
|
||||
}
|
||||
, content =
|
||||
\s ->
|
||||
(case s 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
|
||||
}
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
120
example/src/Example/TextInput.elm
Normal file
120
example/src/Example/TextInput.elm
Normal file
@ -0,0 +1,120 @@
|
||||
module Example.TextInput exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Set exposing (Set)
|
||||
import Widget
|
||||
import Widget.Style exposing (ColumnStyle, TextInputStyle)
|
||||
import Widget.Style.Material as Material
|
||||
import Browser
|
||||
|
||||
type alias Style style msg =
|
||||
{ style
|
||||
| textInput : TextInputStyle msg
|
||||
, column : ColumnStyle msg
|
||||
}
|
||||
|
||||
materialStyle : Style {} msg
|
||||
materialStyle =
|
||||
{ textInput = Material.textInput Material.defaultPalette
|
||||
, column = Material.column
|
||||
}
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ chipTextInput : Set String
|
||||
, textInput : String
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= ToggleTextInputChip String
|
||||
| SetTextInput String
|
||||
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( { chipTextInput = Set.empty
|
||||
, textInput = ""
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
ToggleTextInputChip string ->
|
||||
( { model
|
||||
| chipTextInput =
|
||||
model.chipTextInput
|
||||
|> (if model.chipTextInput |> Set.member string then
|
||||
Set.remove string
|
||||
|
||||
else
|
||||
Set.insert string
|
||||
)
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
SetTextInput string ->
|
||||
( { model | textInput = string }, Cmd.none )
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Style style msg -> Model -> Element msg
|
||||
view msgMapper style model =
|
||||
[ { chips =
|
||||
model.chipTextInput
|
||||
|> Set.toList
|
||||
|> List.map
|
||||
(\string ->
|
||||
{ icon = Element.none
|
||||
, text = string
|
||||
, onPress =
|
||||
string
|
||||
|> ToggleTextInputChip
|
||||
|> msgMapper
|
||||
|> Just
|
||||
}
|
||||
)
|
||||
, text = model.textInput
|
||||
, placeholder = Nothing
|
||||
, label = "Chips"
|
||||
, onChange = SetTextInput >> msgMapper
|
||||
}
|
||||
|> Widget.textInput style.textInput
|
||||
, model.chipTextInput
|
||||
|> Set.diff
|
||||
([ "A", "B", "C" ]
|
||||
|> Set.fromList
|
||||
)
|
||||
|> Set.toList
|
||||
|> List.map
|
||||
(\string ->
|
||||
Widget.button style.textInput.chipButton
|
||||
{ onPress =
|
||||
string
|
||||
|> ToggleTextInputChip
|
||||
|> msgMapper
|
||||
|> Just
|
||||
, text = string
|
||||
, icon = Element.none
|
||||
}
|
||||
)
|
||||
|> Element.wrappedRow [ Element.spacing 10 ]
|
||||
]
|
||||
|> Widget.column style.column
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, view = view identity materialStyle >> Element.layout []
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
@ -1,78 +1,207 @@
|
||||
module Icons exposing
|
||||
( book
|
||||
, chevronDown
|
||||
, chevronLeft
|
||||
, chevronRight
|
||||
, chevronUp
|
||||
, circle
|
||||
, github
|
||||
, menu
|
||||
, moreVertical
|
||||
, circle
|
||||
, triangle
|
||||
, penTool
|
||||
, repeat
|
||||
, search
|
||||
, slash
|
||||
, square
|
||||
, triangle
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Svg exposing (Svg, svg)
|
||||
import Svg.Attributes exposing (..)
|
||||
import Svg.Attributes as Attributes
|
||||
|
||||
|
||||
svgFeatherIcon : String -> List (Svg msg) -> Html msg
|
||||
svgFeatherIcon className =
|
||||
svg
|
||||
[ class <| "feather feather-" ++ className
|
||||
, fill "none"
|
||||
, height "16"
|
||||
, stroke "currentColor"
|
||||
, strokeLinecap "round"
|
||||
, strokeLinejoin "round"
|
||||
, strokeWidth "2"
|
||||
, viewBox "0 0 24 24"
|
||||
, width "16"
|
||||
[ Attributes.class <| "feather feather-" ++ className
|
||||
, Attributes.fill "none"
|
||||
, Attributes.height "16"
|
||||
, Attributes.stroke "currentColor"
|
||||
, Attributes.strokeLinecap "round"
|
||||
, Attributes.strokeLinejoin "round"
|
||||
, Attributes.strokeWidth "2"
|
||||
, Attributes.viewBox "0 0 24 24"
|
||||
, Attributes.width "16"
|
||||
]
|
||||
|
||||
|
||||
chevronDown : Html msg
|
||||
chevronDown =
|
||||
svgFeatherIcon "chevron-down"
|
||||
[ Svg.polyline [ Attributes.points "6 9 12 15 18 9" ] []
|
||||
]
|
||||
|
||||
|
||||
chevronRight : Html msg
|
||||
chevronRight =
|
||||
svgFeatherIcon "chevron-right"
|
||||
[ Svg.polyline [ Attributes.points "9 18 15 12 9 6" ] []
|
||||
]
|
||||
|
||||
|
||||
chevronLeft : Html msg
|
||||
chevronLeft =
|
||||
svgFeatherIcon "chevron-left"
|
||||
[ Svg.polyline [ Attributes.points "15 18 9 12 15 6" ] []
|
||||
]
|
||||
|
||||
|
||||
chevronUp : Html msg
|
||||
chevronUp =
|
||||
svgFeatherIcon "chevron-up"
|
||||
[ Svg.polyline [ Attributes.points "18 15 12 9 6 15" ] []
|
||||
]
|
||||
|
||||
|
||||
repeat : Html msg
|
||||
repeat =
|
||||
svgFeatherIcon "repeat"
|
||||
[ Svg.polyline [ Attributes.points "17 1 21 5 17 9" ] []
|
||||
, Svg.path [ Attributes.d "M3 11V9a4 4 0 0 1 4-4h14" ] []
|
||||
, Svg.polyline [ Attributes.points "7 23 3 19 7 15" ] []
|
||||
, Svg.path [ Attributes.d "M21 13v2a4 4 0 0 1-4 4H3" ] []
|
||||
]
|
||||
|
||||
|
||||
penTool : Html msg
|
||||
penTool =
|
||||
svgFeatherIcon "pen-tool"
|
||||
[ Svg.path [ Attributes.d "M12 19l7-7 3 3-7 7-3-3z" ] []
|
||||
, Svg.path [ Attributes.d "M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z" ] []
|
||||
, Svg.path [ Attributes.d "M2 2l7.586 7.586" ] []
|
||||
, Svg.circle
|
||||
[ Attributes.cx "11"
|
||||
, Attributes.cy "11"
|
||||
, Attributes.r "2"
|
||||
]
|
||||
[]
|
||||
]
|
||||
|
||||
|
||||
book : Html msg
|
||||
book =
|
||||
svgFeatherIcon "book"
|
||||
[ Svg.path [ d "M4 19.5A2.5 2.5 0 0 1 6.5 17H20" ] []
|
||||
, Svg.path [ d "M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" ] []
|
||||
[ Svg.path [ Attributes.d "M4 19.5A2.5 2.5 0 0 1 6.5 17H20" ] []
|
||||
, Svg.path [ Attributes.d "M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" ] []
|
||||
]
|
||||
|
||||
|
||||
github : Html msg
|
||||
github =
|
||||
svgFeatherIcon "github"
|
||||
[ Svg.path [ d "M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" ] []
|
||||
[ Svg.path [ Attributes.d "M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" ] []
|
||||
]
|
||||
|
||||
|
||||
menu : Html msg
|
||||
menu =
|
||||
svgFeatherIcon "menu"
|
||||
[ Svg.line [ x1 "3", y1 "12", x2 "21", y2 "12" ] []
|
||||
, Svg.line [ x1 "3", y1 "6", x2 "21", y2 "6" ] []
|
||||
, Svg.line [ x1 "3", y1 "18", x2 "21", y2 "18" ] []
|
||||
[ Svg.line
|
||||
[ Attributes.x1 "3"
|
||||
, Attributes.y1 "12"
|
||||
, Attributes.x2 "21"
|
||||
, Attributes.y2 "12"
|
||||
]
|
||||
[]
|
||||
, Svg.line
|
||||
[ Attributes.x1 "3"
|
||||
, Attributes.y1 "6"
|
||||
, Attributes.x2 "21"
|
||||
, Attributes.y2 "6"
|
||||
]
|
||||
[]
|
||||
, Svg.line
|
||||
[ Attributes.x1 "3"
|
||||
, Attributes.y1 "18"
|
||||
, Attributes.x2 "21"
|
||||
, Attributes.y2 "18"
|
||||
]
|
||||
[]
|
||||
]
|
||||
|
||||
|
||||
moreVertical : Html msg
|
||||
moreVertical =
|
||||
svgFeatherIcon "more-vertical"
|
||||
[ Svg.circle [ cx "12", cy "12", r "1" ] []
|
||||
, Svg.circle [ cx "12", cy "5", r "1" ] []
|
||||
, Svg.circle [ cx "12", cy "19", r "1" ] []
|
||||
[ Svg.circle [ Attributes.cx "12", Attributes.cy "12", Attributes.r "1" ] []
|
||||
, Svg.circle [ Attributes.cx "12", Attributes.cy "5", Attributes.r "1" ] []
|
||||
, Svg.circle [ Attributes.cx "12", Attributes.cy "19", Attributes.r "1" ] []
|
||||
]
|
||||
|
||||
|
||||
circle : Html msg
|
||||
circle =
|
||||
svgFeatherIcon "circle"
|
||||
[ Svg.circle [ cx "12", cy "12", r "10" ] []
|
||||
[ Svg.circle [ Attributes.cx "12", Attributes.cy "12", Attributes.r "10" ] []
|
||||
]
|
||||
|
||||
|
||||
slash : Html msg
|
||||
slash =
|
||||
svgFeatherIcon "slash"
|
||||
[ Svg.circle
|
||||
[ Attributes.cx "12"
|
||||
, Attributes.cy "12"
|
||||
, Attributes.r "10"
|
||||
]
|
||||
[]
|
||||
, Svg.line
|
||||
[ Attributes.x1 "4.93"
|
||||
, Attributes.y1 "4.93"
|
||||
, Attributes.x2 "19.07"
|
||||
, Attributes.y2 "19.07"
|
||||
]
|
||||
[]
|
||||
]
|
||||
|
||||
|
||||
triangle : Html msg
|
||||
triangle =
|
||||
svgFeatherIcon "triangle"
|
||||
[ Svg.path [ d "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" ] []
|
||||
[ Svg.path [ Attributes.d "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" ] []
|
||||
]
|
||||
|
||||
|
||||
square : Html msg
|
||||
square =
|
||||
svgFeatherIcon "square"
|
||||
[ Svg.rect [ Svg.Attributes.x "3", y "3", width "18", height "18", rx "2", ry "2" ] []
|
||||
[ Svg.rect
|
||||
[ Attributes.x "3"
|
||||
, Attributes.y "3"
|
||||
, Attributes.width "18"
|
||||
, Attributes.height "18"
|
||||
, Attributes.rx "2"
|
||||
, Attributes.ry "2"
|
||||
]
|
||||
[]
|
||||
]
|
||||
|
||||
|
||||
search : Html msg
|
||||
search =
|
||||
svgFeatherIcon "search"
|
||||
[ Svg.circle
|
||||
[ Attributes.cx "11"
|
||||
, Attributes.cy "11"
|
||||
, Attributes.r "8"
|
||||
]
|
||||
[]
|
||||
, Svg.line
|
||||
[ Attributes.x1 "21"
|
||||
, Attributes.y1 "21"
|
||||
, Attributes.x2 "16.65"
|
||||
, Attributes.y2 "16.65"
|
||||
]
|
||||
[]
|
||||
]
|
494
example/src/Main.elm
Normal file
494
example/src/Main.elm
Normal file
@ -0,0 +1,494 @@
|
||||
module Main exposing (main)
|
||||
|
||||
import Array
|
||||
import Browser
|
||||
import Browser.Dom as Dom exposing (Viewport)
|
||||
import Browser.Events as Events
|
||||
import Browser.Navigation as Navigation
|
||||
import Data.Example as Example exposing (Example)
|
||||
import Data.Section as Section exposing (Section(..))
|
||||
import Data.Style exposing (Style)
|
||||
import Data.Theme as Theme exposing (Theme(..))
|
||||
import Element exposing (Element,DeviceClass(..))
|
||||
import Framework
|
||||
import Framework.Grid as Grid
|
||||
import Framework.Heading as Heading
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attributes
|
||||
import Icons
|
||||
import Reusable
|
||||
import Set.Any as AnySet exposing (AnySet)
|
||||
import Stateless
|
||||
import Task
|
||||
import Time
|
||||
import Widget
|
||||
import Widget.Layout as Layout exposing (Layout, Part)
|
||||
import Widget.ScrollingNav as ScrollingNav
|
||||
import FeatherIcons
|
||||
|
||||
type alias LoadedModel =
|
||||
{ stateless : Stateless.Model
|
||||
, scrollingNav : ScrollingNav.ScrollingNav Example
|
||||
, layout : Layout LoadedMsg
|
||||
, displayDialog : Bool
|
||||
, window :
|
||||
{ height : Int
|
||||
, width : Int
|
||||
}
|
||||
, search :
|
||||
{ raw : String
|
||||
, current : String
|
||||
, remaining : Int
|
||||
}
|
||||
, theme : Theme
|
||||
, expanded : AnySet String Example
|
||||
}
|
||||
|
||||
|
||||
type Model
|
||||
= Loading
|
||||
| Loaded LoadedModel
|
||||
|
||||
|
||||
type LoadedMsg
|
||||
= StatelessSpecific Stateless.Msg
|
||||
| UpdateScrollingNav (ScrollingNav.ScrollingNav Example -> ScrollingNav.ScrollingNav Example)
|
||||
| TimePassed Int
|
||||
| AddSnackbar ( String, Bool )
|
||||
| ToggleDialog Bool
|
||||
| ChangedSidebar (Maybe Part)
|
||||
| Resized { width : Int, height : Int }
|
||||
| Load String
|
||||
| JumpTo Example
|
||||
| ChangedSearch String
|
||||
| SetTheme Theme
|
||||
| Idle
|
||||
| ToggledExample Example
|
||||
|
||||
|
||||
type Msg
|
||||
= LoadedSpecific LoadedMsg
|
||||
| GotViewport Viewport
|
||||
|
||||
|
||||
initialModel : Viewport -> ( LoadedModel, Cmd LoadedMsg )
|
||||
initialModel { viewport } =
|
||||
let
|
||||
( scrollingNav, cmd ) =
|
||||
ScrollingNav.init
|
||||
{ toString = Example.toString
|
||||
, fromString = Example.fromString
|
||||
, arrangement = Example.asList
|
||||
, toMsg =
|
||||
\result ->
|
||||
case result of
|
||||
Ok fun ->
|
||||
UpdateScrollingNav fun
|
||||
|
||||
Err _ ->
|
||||
Idle
|
||||
}
|
||||
|
||||
( stateless, statelessCmd ) =
|
||||
Stateless.init
|
||||
in
|
||||
( { stateless = stateless
|
||||
, scrollingNav = scrollingNav
|
||||
, layout = Layout.init
|
||||
, displayDialog = False
|
||||
, window =
|
||||
{ width = viewport.width |> round
|
||||
, height = viewport.height |> round
|
||||
}
|
||||
, search =
|
||||
{ raw = ""
|
||||
, current = ""
|
||||
, remaining = 0
|
||||
}
|
||||
, theme = Material
|
||||
, expanded = AnySet.empty Example.toString
|
||||
}
|
||||
, [ cmd
|
||||
, statelessCmd |> Cmd.map StatelessSpecific
|
||||
]
|
||||
|> Cmd.batch
|
||||
)
|
||||
|
||||
|
||||
init : () -> ( Model, Cmd Msg )
|
||||
init () =
|
||||
( Loading
|
||||
, Task.perform GotViewport Dom.getViewport
|
||||
)
|
||||
|
||||
|
||||
updateLoaded : LoadedMsg -> LoadedModel -> ( LoadedModel, Cmd LoadedMsg )
|
||||
updateLoaded msg model =
|
||||
case msg of
|
||||
StatelessSpecific m ->
|
||||
model.stateless
|
||||
|> Stateless.update m
|
||||
|> Tuple.mapBoth
|
||||
(\stateless ->
|
||||
{ model
|
||||
| stateless = stateless
|
||||
}
|
||||
)
|
||||
(Cmd.map StatelessSpecific)
|
||||
|
||||
UpdateScrollingNav fun ->
|
||||
( { model | scrollingNav = model.scrollingNav |> fun }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
TimePassed int ->
|
||||
let
|
||||
search =
|
||||
model.search
|
||||
in
|
||||
( { model
|
||||
| layout = model.layout |> Layout.timePassed int
|
||||
, search =
|
||||
if search.remaining > 0 then
|
||||
if search.remaining <= int then
|
||||
{ search
|
||||
| current = search.raw
|
||||
, remaining = 0
|
||||
}
|
||||
|
||||
else
|
||||
{ search
|
||||
| remaining = search.remaining - int
|
||||
}
|
||||
|
||||
else
|
||||
model.search
|
||||
}
|
||||
, ScrollingNav.getPos
|
||||
|> Task.perform UpdateScrollingNav
|
||||
)
|
||||
|
||||
AddSnackbar ( string, bool ) ->
|
||||
( { model
|
||||
| layout =
|
||||
model.layout
|
||||
|> Layout.queueMessage
|
||||
{ text = string
|
||||
, button =
|
||||
if bool then
|
||||
Just
|
||||
{ text = "Add"
|
||||
, onPress =
|
||||
Just <|
|
||||
AddSnackbar ( "This is another message", False )
|
||||
}
|
||||
|
||||
else
|
||||
Nothing
|
||||
}
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ToggleDialog bool ->
|
||||
( { model | displayDialog = bool }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Resized window ->
|
||||
( { model | window = window }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ChangedSidebar sidebar ->
|
||||
( { model | layout = model.layout |> Layout.activate sidebar }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Load string ->
|
||||
( model, Navigation.load string )
|
||||
|
||||
JumpTo section ->
|
||||
( model
|
||||
, model.scrollingNav
|
||||
|> ScrollingNav.jumpTo
|
||||
{ section = section
|
||||
, onChange = always Idle
|
||||
}
|
||||
)
|
||||
|
||||
ChangedSearch string ->
|
||||
let
|
||||
search =
|
||||
model.search
|
||||
in
|
||||
( { model
|
||||
| search =
|
||||
{ search
|
||||
| raw = string
|
||||
, remaining = 300
|
||||
}
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
SetTheme theme ->
|
||||
( { model | theme = theme }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ToggledExample example ->
|
||||
( { model
|
||||
| expanded = model.expanded |> AnySet.toggle example
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Idle ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case ( model, msg ) of
|
||||
( Loading, GotViewport viewport ) ->
|
||||
initialModel viewport
|
||||
|> Tuple.mapBoth Loaded (Cmd.map LoadedSpecific)
|
||||
|
||||
( Loaded state, LoadedSpecific m ) ->
|
||||
updateLoaded m state
|
||||
|> Tuple.mapBoth Loaded (Cmd.map LoadedSpecific)
|
||||
|
||||
_ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.batch
|
||||
[ Time.every 50 (always (TimePassed 50))
|
||||
, Events.onResize (\h w -> Resized { height = h, width = w })
|
||||
]
|
||||
|> Sub.map LoadedSpecific
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
case model of
|
||||
Loading ->
|
||||
Element.none |> Framework.responsiveLayout []
|
||||
|
||||
Loaded m ->
|
||||
let
|
||||
style : Style msg
|
||||
style =
|
||||
Theme.toStyle m.theme
|
||||
in
|
||||
Html.map LoadedSpecific <|
|
||||
Layout.view style.layout
|
||||
{ dialog =
|
||||
if m.displayDialog then
|
||||
{ text = "This is a dialog window"
|
||||
, title = Just "Dialog"
|
||||
, accept =
|
||||
Just
|
||||
{ text = "Ok"
|
||||
, onPress = Just <| ToggleDialog False
|
||||
}
|
||||
, dismiss =
|
||||
Just
|
||||
{ text = "Dismiss"
|
||||
, onPress = Just <| ToggleDialog False
|
||||
}
|
||||
}
|
||||
|> Widget.dialog style.dialog
|
||||
|> Just
|
||||
|
||||
else
|
||||
Nothing
|
||||
, layout = m.layout
|
||||
, window = m.window
|
||||
, menu =
|
||||
m.scrollingNav
|
||||
|> ScrollingNav.toSelect
|
||||
(\int ->
|
||||
m.scrollingNav.arrangement
|
||||
|> Array.fromList
|
||||
|> Array.get int
|
||||
|> Maybe.map JumpTo
|
||||
)
|
||||
, actions =
|
||||
[ { onPress = Just <| Load "https://package.elm-lang.org/packages/Orasund/elm-ui-widgets/latest/"
|
||||
, text = "Docs"
|
||||
, icon = Icons.book |> Element.html |> Element.el []
|
||||
}
|
||||
, { onPress = Just <| Load "https://github.com/Orasund/elm-ui-widgets"
|
||||
, text = "Github"
|
||||
, icon = Icons.github |> Element.html |> Element.el []
|
||||
}
|
||||
, { onPress =
|
||||
if m.theme /= Material then
|
||||
Just <| SetTheme <| Material
|
||||
|
||||
else
|
||||
Nothing
|
||||
, text = "Material Theme"
|
||||
, icon = Icons.penTool |> Element.html |> Element.el []
|
||||
}
|
||||
, { onPress =
|
||||
if m.theme /= DarkMaterial then
|
||||
Just <| SetTheme <| DarkMaterial
|
||||
|
||||
else
|
||||
Nothing
|
||||
, text = "Dark Material Theme"
|
||||
, icon = Icons.penTool |> Element.html |> Element.el []
|
||||
}
|
||||
, { onPress =
|
||||
if m.theme /= ElmUiFramework then
|
||||
Just <| SetTheme <| ElmUiFramework
|
||||
|
||||
else
|
||||
Nothing
|
||||
, text = "Elm-Ui-Framework Theme"
|
||||
, icon = Icons.penTool |> Element.html |> Element.el []
|
||||
}
|
||||
, { onPress =
|
||||
if m.theme /= Template then
|
||||
Just <| SetTheme <| Template
|
||||
|
||||
else
|
||||
Nothing
|
||||
, text = "Template Theme"
|
||||
, icon = Icons.penTool |> Element.html |> Element.el []
|
||||
}
|
||||
]
|
||||
, onChangedSidebar = ChangedSidebar
|
||||
, title =
|
||||
"Elm-Ui-Widgets"
|
||||
|> Element.text
|
||||
|> Element.el Heading.h1
|
||||
, search =
|
||||
Just
|
||||
{ text = m.search.raw
|
||||
, onChange = ChangedSearch
|
||||
, label = "Search"
|
||||
}
|
||||
}
|
||||
<|
|
||||
viewLoaded m
|
||||
|
||||
|
||||
viewLoaded : LoadedModel -> Element LoadedMsg
|
||||
viewLoaded m =
|
||||
let
|
||||
style : Style msg
|
||||
style =
|
||||
Theme.toStyle m.theme
|
||||
in
|
||||
[ Element.el [ Element.height <| Element.px <| 42 ] <| Element.none
|
||||
, [ StatelessViews, ReusableViews ]
|
||||
|> List.map
|
||||
(\section ->
|
||||
(case section of
|
||||
ReusableViews ->
|
||||
Reusable.view
|
||||
{ theme = m.theme
|
||||
, addSnackbar = AddSnackbar
|
||||
}
|
||||
|
||||
StatelessViews ->
|
||||
Stateless.view
|
||||
{ theme = m.theme
|
||||
, msgMapper = StatelessSpecific
|
||||
, model = m.stateless
|
||||
}
|
||||
)
|
||||
|> (\{ title, description, items } ->
|
||||
[ title
|
||||
|> Element.text
|
||||
|> Element.el Heading.h2
|
||||
, if m.search.current == "" then
|
||||
description
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
|
||||
else
|
||||
Element.none
|
||||
, items
|
||||
|> (if m.search.current /= "" then
|
||||
List.filter
|
||||
(\( a, _, _ ) ->
|
||||
a
|
||||
|> String.toLower
|
||||
|> String.contains (m.search.current |> String.toLower)
|
||||
)
|
||||
|
||||
else
|
||||
identity
|
||||
)
|
||||
|> List.map
|
||||
(\( name, elem, more ) ->
|
||||
[ [ Element.text name
|
||||
|> Element.el
|
||||
(Heading.h3
|
||||
++ [ Element.height <| Element.shrink
|
||||
, name
|
||||
|> Attributes.id
|
||||
|> Element.htmlAttribute
|
||||
]
|
||||
)
|
||||
, elem
|
||||
]
|
||||
|> Element.column Grid.simple
|
||||
,
|
||||
Widget.expansionPanel style.expansionPanel
|
||||
|
||||
{ onToggle =
|
||||
always
|
||||
(name
|
||||
|> Example.fromString
|
||||
|> Maybe.map ToggledExample
|
||||
|> Maybe.withDefault Idle
|
||||
)
|
||||
, icon = Element.none
|
||||
, text =
|
||||
"States"
|
||||
, content = more
|
||||
, isExpanded =
|
||||
name
|
||||
|> Example.fromString
|
||||
|> Maybe.map
|
||||
(\example ->
|
||||
m.expanded
|
||||
|> AnySet.member example
|
||||
)|> Maybe.withDefault False
|
||||
|
||||
|
||||
}
|
||||
]
|
||||
|> Widget.column style.cardColumn
|
||||
)
|
||||
|> Element.wrappedRow
|
||||
(Grid.simple
|
||||
++ [ Element.height <| Element.shrink
|
||||
]
|
||||
)
|
||||
]
|
||||
|> Element.column (Grid.section ++ [ Element.centerX ])
|
||||
)
|
||||
)
|
||||
|> Element.column (Framework.container ++ style.layout.container)
|
||||
]
|
||||
|> Element.column Grid.compact
|
||||
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = init
|
||||
, view = view
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
@ -1,163 +1,80 @@
|
||||
module Reusable exposing (Model, Msg, init, update, view)
|
||||
module Reusable exposing (view)
|
||||
|
||||
import Browser
|
||||
import Element exposing (Color, Element)
|
||||
import Element.Background as Background
|
||||
import Element.Font as Font
|
||||
import Element.Input as Input
|
||||
import Framework
|
||||
import Framework.Button as Button
|
||||
import Framework.Card as Card
|
||||
import Framework.Color as Color
|
||||
import Data.Style exposing (Style)
|
||||
import Data.Theme as Theme exposing (Theme)
|
||||
import Element exposing (Element)
|
||||
import Framework.Grid as Grid
|
||||
import Framework.Group as Group
|
||||
import Framework.Heading as Heading
|
||||
import Framework.Input as Input
|
||||
import Framework.Tag as Tag
|
||||
import Heroicons.Solid as Heroicons
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attributes
|
||||
import Set exposing (Set)
|
||||
import Time
|
||||
import Widget
|
||||
import Widget.FilterSelect as FilterSelect
|
||||
import Widget.ScrollingNav as ScrollingNav
|
||||
import Widget.Snackbar as Snackbar
|
||||
import Widget.SortTable as SortTable
|
||||
import Widget.ValidatedInput as ValidatedInput
|
||||
|
||||
|
||||
type alias Model =
|
||||
SortTable.Model
|
||||
snackbar : Style msg -> (( String, Bool ) -> msg) -> ( String, Element msg, Element msg )
|
||||
snackbar style addSnackbar =
|
||||
( "Snackbar"
|
||||
, [ Widget.button style.button
|
||||
{ onPress =
|
||||
Just <|
|
||||
addSnackbar <|
|
||||
( "This is a notification. It will disappear after 10 seconds."
|
||||
, False
|
||||
)
|
||||
, text = "Add Notification"
|
||||
, icon = Element.none
|
||||
}
|
||||
, Widget.button style.button
|
||||
{ onPress =
|
||||
Just <|
|
||||
addSnackbar <|
|
||||
( "You can add another notification if you want."
|
||||
, True
|
||||
)
|
||||
, text = "Add Notification with Action"
|
||||
, icon = Element.none
|
||||
}
|
||||
]
|
||||
|> Element.column Grid.simple
|
||||
, Element.none
|
||||
)
|
||||
|
||||
|
||||
type Msg
|
||||
= SortBy { title : String, asc : Bool }
|
||||
scrollingNavCard : Style msg -> ( String, Element msg, Element msg )
|
||||
scrollingNavCard _ =
|
||||
( "Scrolling Nav"
|
||||
, Element.text "Resize the screen and open the side-menu. Then start scrolling to see the scrolling navigation in action."
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
, Element.none
|
||||
)
|
||||
|
||||
|
||||
type alias Item =
|
||||
{ name : String
|
||||
, amount : Int
|
||||
, price : Float
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> Model
|
||||
update msg model =
|
||||
case msg of
|
||||
SortBy m ->
|
||||
m
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
SortTable.sortBy { title = "Name", asc = True }
|
||||
|
||||
|
||||
snackbar : (String -> msg) -> Element msg
|
||||
snackbar addSnackbar =
|
||||
[ Element.el Heading.h3 <| Element.text "Snackbar"
|
||||
, Input.button Button.simple
|
||||
{ onPress = Just <| addSnackbar "This is a notification. It will disappear after 10 seconds."
|
||||
, label =
|
||||
"Add Notification"
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
}
|
||||
]
|
||||
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
|
||||
|
||||
sortTable : SortTable.Model -> Element Msg
|
||||
sortTable model =
|
||||
[ Element.el Heading.h3 <| Element.text "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.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
layout : Style msg -> ( String, Element msg, Element msg )
|
||||
layout _ =
|
||||
( "Layout"
|
||||
, Element.text "The layout combines the menu bar, both side bar, the dialog window and the snackbar. Try using all of them and also try resizing the window to see how they interact with each other."
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
, Element.none
|
||||
)
|
||||
|
||||
|
||||
view :
|
||||
{ addSnackbar : String -> msg
|
||||
, msgMapper : Msg -> msg
|
||||
, model : Model
|
||||
{ theme : Theme
|
||||
, addSnackbar : ( String, Bool ) -> msg
|
||||
}
|
||||
-> Element msg
|
||||
view { addSnackbar, msgMapper, model } =
|
||||
Element.column (Grid.section ++ [ Element.centerX ])
|
||||
[ Element.el Heading.h2 <| Element.text "Reusable Views"
|
||||
, "Reusable views have an internal state but no update function. You will need to do some wiring, but nothing complicated."
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
, Element.wrappedRow (Grid.simple ++ [Element.height <| Element.shrink]) <|
|
||||
[ snackbar addSnackbar
|
||||
, sortTable model |> Element.map msgMapper
|
||||
]
|
||||
->
|
||||
{ title : String
|
||||
, description : String
|
||||
, items : List ( String, Element msg, Element msg )
|
||||
}
|
||||
view { theme, addSnackbar } =
|
||||
let
|
||||
style =
|
||||
Theme.toStyle theme
|
||||
in
|
||||
{ title = "Reusable Views"
|
||||
, description = "Reusable views have an internal state but no update function. You will need to do some wiring, but nothing complicated."
|
||||
, items =
|
||||
[ snackbar style addSnackbar
|
||||
, scrollingNavCard style
|
||||
, layout style
|
||||
]
|
||||
}
|
||||
|
@ -1,326 +1,73 @@
|
||||
module Stateless exposing (Model, Msg, init, update, view)
|
||||
|
||||
import Array exposing (Array)
|
||||
import Data.Example as Example
|
||||
import Data.Theme as Theme exposing (Theme)
|
||||
import Element exposing (Element)
|
||||
import Element.Background as Background
|
||||
import Element.Border as Border
|
||||
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 Set exposing (Set)
|
||||
import Widget
|
||||
import Layout exposing (Direction(..))
|
||||
import Widget.Layout exposing (Part(..))
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ selected : Maybe Int
|
||||
, multiSelected : Set Int
|
||||
, isCollapsed : Bool
|
||||
, carousel : Int
|
||||
, tab : Int
|
||||
{ carousel : Int
|
||||
, example : Example.Model
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= ChangedSelected Int
|
||||
| ChangedMultiSelected Int
|
||||
| ToggleCollapsable Bool
|
||||
| ChangedTab Int
|
||||
| SetCarousel Int
|
||||
= ExampleSpecific Example.Msg
|
||||
| Idle
|
||||
|
||||
|
||||
init : Model
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
{ selected = Nothing
|
||||
, multiSelected = Set.empty
|
||||
, isCollapsed = False
|
||||
, carousel = 0
|
||||
, tab = 1
|
||||
}
|
||||
let
|
||||
( example, cmd ) =
|
||||
Example.init
|
||||
in
|
||||
( { carousel = 0
|
||||
, 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
|
||||
ExampleSpecific exampleMsg ->
|
||||
let
|
||||
( exampleModel, exampleCmd ) =
|
||||
Example.update exampleMsg model.example
|
||||
in
|
||||
( { model | example = exampleModel }
|
||||
, exampleCmd |> Cmd.map ExampleSpecific
|
||||
)
|
||||
|
||||
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
|
||||
| isCollapsed = bool
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
SetCarousel int ->
|
||||
( if (int < 0) || (int > 3) then
|
||||
model
|
||||
|
||||
else
|
||||
{ model
|
||||
| carousel = int
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ChangedTab int ->
|
||||
( { model | tab = int }, Cmd.none )
|
||||
|
||||
|
||||
select : Model -> Element Msg
|
||||
select model =
|
||||
[ Element.el Heading.h3 <| Element.text "Select"
|
||||
, Widget.select
|
||||
{ selected = model.selected
|
||||
, options = [ 1, 2, 42 ]
|
||||
, label = String.fromInt >> Element.text
|
||||
, onChange = ChangedSelected
|
||||
, attributes =
|
||||
\selected ->
|
||||
Button.simple
|
||||
++ [ Border.width 0
|
||||
, Border.rounded 0
|
||||
]
|
||||
++ (if selected then
|
||||
Color.primary
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
}
|
||||
|> List.indexedMap
|
||||
(\i ->
|
||||
Element.el
|
||||
(Button.simple
|
||||
++ [ Element.padding <| 0 ]
|
||||
++ (if i == 0 then
|
||||
Group.left
|
||||
|
||||
else if i == 2 then
|
||||
Group.right
|
||||
|
||||
else
|
||||
Group.center
|
||||
)
|
||||
)
|
||||
)
|
||||
|> Element.row Grid.compact
|
||||
]
|
||||
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
|
||||
|
||||
multiSelect : Model -> Element Msg
|
||||
multiSelect model =
|
||||
[ Element.el Heading.h3 <| Element.text "Multi Select"
|
||||
, Widget.multiSelect
|
||||
{ selected = model.multiSelected
|
||||
, options = [ 1, 2, 42 ]
|
||||
, label = String.fromInt >> Element.text
|
||||
, onChange = ChangedMultiSelected
|
||||
, attributes =
|
||||
\selected ->
|
||||
Button.simple
|
||||
++ [ Border.width 0
|
||||
, Border.rounded 0
|
||||
]
|
||||
++ (if selected then
|
||||
Color.primary
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
}
|
||||
|> List.indexedMap
|
||||
(\i ->
|
||||
Element.el
|
||||
(Button.simple
|
||||
++ [ Element.padding <| 0 ]
|
||||
++ (if i == 0 then
|
||||
Group.left
|
||||
|
||||
else if i == 2 then
|
||||
Group.right
|
||||
|
||||
else
|
||||
Group.center
|
||||
)
|
||||
)
|
||||
)
|
||||
|> Element.row Grid.compact
|
||||
]
|
||||
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
|
||||
|
||||
collapsable : Model -> Element Msg
|
||||
collapsable model =
|
||||
[ Element.el Heading.h3 <| Element.text "Collapsable"
|
||||
, Widget.collapsable
|
||||
{ onToggle = ToggleCollapsable
|
||||
, isCollapsed = model.isCollapsed
|
||||
, label =
|
||||
Element.row Grid.compact
|
||||
[ Element.html <|
|
||||
if model.isCollapsed then
|
||||
Heroicons.cheveronRight [ Attributes.width 20 ]
|
||||
|
||||
else
|
||||
Heroicons.cheveronDown [ Attributes.width 20 ]
|
||||
, Element.el Heading.h4 <| Element.text <| "Title"
|
||||
]
|
||||
, content = Element.text <| "Hello World"
|
||||
}
|
||||
]
|
||||
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
|
||||
|
||||
tab : Model -> Element Msg
|
||||
tab model =
|
||||
[ Element.el Heading.h3 <| Element.text "Tab"
|
||||
, Widget.tab Grid.simple
|
||||
{ selected = model.tab
|
||||
, options = [ 1, 2, 3 ]
|
||||
, onChange = ChangedTab
|
||||
, label = \int -> "Tab " ++ String.fromInt int |> Element.text
|
||||
, content =
|
||||
\selected ->
|
||||
(case selected of
|
||||
1 ->
|
||||
"This is Tab 1"
|
||||
|
||||
2 ->
|
||||
"This is the second tab"
|
||||
|
||||
3 ->
|
||||
"The thrid and last tab"
|
||||
|
||||
_ ->
|
||||
"Please select a tab"
|
||||
)
|
||||
|> Element.text
|
||||
|> Element.el (Card.small ++ Group.bottom)
|
||||
, attributes =
|
||||
\selected ->
|
||||
Button.simple
|
||||
++ Group.top
|
||||
++ (if selected then
|
||||
Color.primary
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
}
|
||||
]
|
||||
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
|
||||
|
||||
scrim :
|
||||
{ showDialog : msg
|
||||
, changedSheet : Maybe Direction -> msg
|
||||
} -> Model -> Element msg
|
||||
scrim {showDialog,changedSheet} model =
|
||||
[ Element.el Heading.h3 <| Element.text "Scrim"
|
||||
, Input.button Button.simple
|
||||
{ onPress = Just showDialog
|
||||
, label = Element.text <| "Show dialog"
|
||||
}
|
||||
, Input.button Button.simple
|
||||
{ onPress = Just <| changedSheet <| Just Left
|
||||
, label = Element.text <| "show left sheet"
|
||||
}
|
||||
, Input.button Button.simple
|
||||
{ onPress = Just <| changedSheet <| Just Right
|
||||
, label = Element.text <| "show right sheet"
|
||||
}
|
||||
]
|
||||
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
|
||||
|
||||
carousel : Model -> Element Msg
|
||||
carousel model =
|
||||
[ Element.el Heading.h3 <| Element.text "Carousel"
|
||||
, Widget.carousel
|
||||
{ content = ( Color.cyan, [ Color.yellow, Color.green, Color.red ] |> Array.fromList )
|
||||
, current = model.carousel
|
||||
, label =
|
||||
\c ->
|
||||
[ Input.button [ Element.centerY ]
|
||||
{ onPress = Just <| SetCarousel <| model.carousel - 1
|
||||
, label =
|
||||
Heroicons.cheveronLeft [ Attributes.width 20 ]
|
||||
|> Element.html
|
||||
}
|
||||
, Element.el
|
||||
(Card.simple
|
||||
++ [ Background.color <| c
|
||||
, Element.height <| Element.px <| 100
|
||||
, Element.width <| Element.px <| 100
|
||||
]
|
||||
)
|
||||
<|
|
||||
Element.none
|
||||
, Input.button [ Element.centerY ]
|
||||
{ onPress = Just <| SetCarousel <| model.carousel + 1
|
||||
, label =
|
||||
Heroicons.cheveronRight [ Attributes.width 20 ]
|
||||
|> Element.html
|
||||
}
|
||||
]
|
||||
|> Element.row (Grid.simple ++ [ Element.centerX, Element.width <| Element.shrink ])
|
||||
}
|
||||
]
|
||||
|> Element.column (Grid.simple ++ Card.large ++ [Element.height <| Element.fill])
|
||||
Idle ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
view :
|
||||
{ msgMapper : Msg -> msg
|
||||
, showDialog : msg
|
||||
, changedSheet : Maybe Direction -> msg
|
||||
} -> Model -> Element msg
|
||||
view { msgMapper, showDialog, changedSheet } model =
|
||||
Element.column (Grid.section )
|
||||
[ Element.el Heading.h2 <| Element.text "Stateless Views"
|
||||
, "Stateless views are simple functions that view some content. No wiring required."
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.paragraph []
|
||||
, Element.wrappedRow
|
||||
(Grid.simple ++ [Element.height <| Element.shrink])
|
||||
<|
|
||||
[ select model |> Element.map msgMapper
|
||||
, multiSelect model |> Element.map msgMapper
|
||||
, collapsable model |> Element.map msgMapper
|
||||
, scrim
|
||||
{ showDialog = showDialog
|
||||
, changedSheet = changedSheet
|
||||
} model
|
||||
, carousel model |> Element.map msgMapper
|
||||
, tab model |> Element.map msgMapper
|
||||
]
|
||||
]
|
||||
{ theme : Theme
|
||||
, msgMapper : Msg -> msg
|
||||
, model : Model
|
||||
}
|
||||
->
|
||||
{ title : String
|
||||
, description : String
|
||||
, items : List ( String, Element msg, Element msg )
|
||||
}
|
||||
view { theme, msgMapper, model } =
|
||||
let
|
||||
style =
|
||||
Theme.toStyle theme
|
||||
in
|
||||
{ title = "Stateless Views"
|
||||
, description = "Stateless views are simple functions that view some content. No wiring required."
|
||||
, items =
|
||||
Example.toCardList
|
||||
{ idle = Idle |> msgMapper
|
||||
, msgMapper = ExampleSpecific >> msgMapper
|
||||
, style = style
|
||||
, model = model.example
|
||||
}
|
||||
}
|
||||
|
513
example/src/View/Test.elm
Normal file
513
example/src/View/Test.elm
Normal file
@ -0,0 +1,513 @@
|
||||
module View.Test exposing (button, dialog, expansionPanel, list, modal, multiSelect, select, sortTable, tab, textInput)
|
||||
|
||||
import Data.Style exposing (Style)
|
||||
import Element exposing (Element)
|
||||
import Icons
|
||||
import Set
|
||||
import Widget
|
||||
import Widget.Layout exposing (Part(..))
|
||||
|
||||
|
||||
button : msg -> Style msg -> List ( String, Element msg )
|
||||
button idle style =
|
||||
[ ( "Button"
|
||||
, Widget.button style.button
|
||||
{ text = "Button"
|
||||
, icon = Icons.triangle |> Element.html |> Element.el []
|
||||
, onPress = Just idle
|
||||
}
|
||||
)
|
||||
, ( "Text button"
|
||||
, Widget.textButton style.button
|
||||
{ text = "Button"
|
||||
, onPress = Just idle
|
||||
}
|
||||
)
|
||||
, ( "Icon button"
|
||||
, Widget.iconButton style.button
|
||||
{ text = "Button"
|
||||
, icon = Icons.triangle |> Element.html |> Element.el []
|
||||
, onPress = Just idle
|
||||
}
|
||||
)
|
||||
, ( "Disabled button"
|
||||
, Widget.button style.button
|
||||
{ text = "Button"
|
||||
, icon = Icons.triangle |> Element.html |> Element.el []
|
||||
, onPress = Nothing
|
||||
}
|
||||
)
|
||||
, ( "Inactive Select button"
|
||||
, Widget.selectButton style.button
|
||||
( False
|
||||
, { text = "Button"
|
||||
, icon = Icons.triangle |> Element.html |> Element.el []
|
||||
, onPress = Just idle
|
||||
}
|
||||
)
|
||||
)
|
||||
, ( "Active Select button"
|
||||
, Widget.selectButton style.button
|
||||
( True
|
||||
, { text = "Button"
|
||||
, icon = Icons.triangle |> Element.html |> Element.el []
|
||||
, onPress = Just idle
|
||||
}
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
select : msg -> Style msg -> List ( String, Element msg )
|
||||
select idle style =
|
||||
[ ( "First selected"
|
||||
, { 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
, ( "Nothing selected"
|
||||
, { 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
, ( "Invalid selection"
|
||||
, { 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
, ( "Disabled selection"
|
||||
, { 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
, ( "Empty Options"
|
||||
, { selected = Nothing
|
||||
, options =
|
||||
[]
|
||||
|> List.map
|
||||
(\int ->
|
||||
{ text = String.fromInt int
|
||||
, icon = Element.none
|
||||
}
|
||||
)
|
||||
, onSelect = always idle >> Just
|
||||
}
|
||||
|> Widget.select
|
||||
|> Widget.buttonRow
|
||||
{ list = style.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
multiSelect : msg -> Style msg -> List ( String, Element msg )
|
||||
multiSelect idle style =
|
||||
[ ( "Some selected"
|
||||
, { 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
, ( "Nothing selected"
|
||||
, { 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
, ( "Invalid selection"
|
||||
, { 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
, ( "Disabled selection"
|
||||
, { 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.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
, ( "Empty Options"
|
||||
, { selected = Set.empty
|
||||
, options =
|
||||
[]
|
||||
|> List.map
|
||||
(\int ->
|
||||
{ text = String.fromInt int
|
||||
, icon = Element.none
|
||||
}
|
||||
)
|
||||
, onSelect = always idle >> Just
|
||||
}
|
||||
|> Widget.multiSelect
|
||||
|> Widget.buttonRow
|
||||
{ list = style.buttonRow
|
||||
, button = style.selectButton
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
expansionPanel : msg -> Style msg -> List ( String, Element msg )
|
||||
expansionPanel idle style =
|
||||
[ ( "Collapsed"
|
||||
, { onToggle = always idle
|
||||
, isExpanded = False
|
||||
, icon = Icons.triangle |> Element.html |> Element.el []
|
||||
, text = "Button"
|
||||
, content = Element.text <| "Hidden Message"
|
||||
}
|
||||
|> Widget.expansionPanel style.expansionPanel
|
||||
)
|
||||
, ( "Expanded"
|
||||
, { 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 ( String, Element msg )
|
||||
tab idle style =
|
||||
[ ( "Nothing selected"
|
||||
, 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
|
||||
}
|
||||
)
|
||||
, ( "Tab selected"
|
||||
, 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
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
sortTable : msg -> Style msg -> List ( String, Element msg )
|
||||
sortTable idle style =
|
||||
[ ( "Int column"
|
||||
, Widget.sortTable style.sortTable
|
||||
{ content =
|
||||
[ { id = 1, name = "Antonio", rating = 2.456, hash = Nothing }
|
||||
, { id = 2, name = "Ana", rating = 1.34, hash = Just "45jf" }
|
||||
]
|
||||
, 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
|
||||
}
|
||||
]
|
||||
, asc = True
|
||||
, sortBy = "Id"
|
||||
, onChange = always idle
|
||||
}
|
||||
)
|
||||
, ( "Name column"
|
||||
, Widget.sortTable style.sortTable
|
||||
{ content =
|
||||
[ { id = 1, name = "Antonio", rating = 2.456, hash = Nothing }
|
||||
, { id = 2, name = "Ana", rating = 1.34, hash = Just "45jf" }
|
||||
]
|
||||
, columns =
|
||||
[ Widget.stringColumn
|
||||
{ title = "Name"
|
||||
, value = .name
|
||||
, toString = identity
|
||||
, width = Element.fill
|
||||
}
|
||||
, Widget.floatColumn
|
||||
{ title = "Rating"
|
||||
, value = .rating
|
||||
, toString = String.fromFloat
|
||||
, width = Element.fill
|
||||
}
|
||||
]
|
||||
, asc = True
|
||||
, sortBy = "Name"
|
||||
, onChange = always idle
|
||||
}
|
||||
)
|
||||
, ( "Float column"
|
||||
, Widget.sortTable style.sortTable
|
||||
{ content =
|
||||
[ { id = 1, name = "Antonio", rating = 2.456, hash = Nothing }
|
||||
, { id = 2, name = "Ana", rating = 1.34, hash = Just "45jf" }
|
||||
]
|
||||
, columns =
|
||||
[ Widget.floatColumn
|
||||
{ title = "Rating"
|
||||
, value = .rating
|
||||
, toString = String.fromFloat
|
||||
, width = Element.fill
|
||||
}
|
||||
, Widget.unsortableColumn
|
||||
{ title = "Hash"
|
||||
, toString = .hash >> Maybe.withDefault "None"
|
||||
, width = Element.fill
|
||||
}
|
||||
]
|
||||
, asc = False
|
||||
, sortBy = "Rating"
|
||||
, onChange = always idle
|
||||
}
|
||||
)
|
||||
, ( "Unsortable column"
|
||||
, Widget.sortTable style.sortTable
|
||||
{ content =
|
||||
[ { id = 1, name = "Antonio", rating = 2.456, hash = Nothing }
|
||||
, { id = 2, name = "Ana", rating = 1.34, hash = Just "45jf" }
|
||||
]
|
||||
, columns =
|
||||
[ Widget.floatColumn
|
||||
{ title = "Rating"
|
||||
, value = .rating
|
||||
, toString = String.fromFloat
|
||||
, width = Element.fill
|
||||
}
|
||||
, Widget.unsortableColumn
|
||||
{ title = "Hash"
|
||||
, toString = .hash >> Maybe.withDefault "None"
|
||||
, width = Element.fill
|
||||
}
|
||||
]
|
||||
, asc = True
|
||||
, sortBy = "Hash"
|
||||
, onChange = always idle
|
||||
}
|
||||
)
|
||||
, ( "Empty Table"
|
||||
, Widget.sortTable style.sortTable
|
||||
{ content =
|
||||
[ { id = 1, name = "Antonio", rating = 2.456, hash = Nothing }
|
||||
, { id = 2, name = "Ana", rating = 1.34, hash = Just "45jf" }
|
||||
]
|
||||
, columns = []
|
||||
, asc = True
|
||||
, sortBy = ""
|
||||
, onChange = always idle
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
modal : msg -> Style msg -> List ( String, Element msg )
|
||||
modal _ _ =
|
||||
[]
|
||||
|
||||
|
||||
dialog : msg -> Style msg -> List ( String, Element msg )
|
||||
dialog _ _ =
|
||||
[]
|
||||
|
||||
|
||||
textInput : msg -> Style msg -> List ( String, Element msg )
|
||||
textInput idle style =
|
||||
[ ( "Nothing Selected"
|
||||
, { chips = []
|
||||
, text = ""
|
||||
, placeholder = Nothing
|
||||
, label = "Label"
|
||||
, onChange = always idle
|
||||
}
|
||||
|> Widget.textInput style.textInput
|
||||
)
|
||||
, ( "Some chips"
|
||||
, { 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
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
list : msg -> Style msg -> List ( String, Element msg )
|
||||
list _ style =
|
||||
[ ( "Row"
|
||||
, [ Element.text "A"
|
||||
, Element.text "B"
|
||||
, Element.text "C"
|
||||
]
|
||||
|> Widget.row style.row
|
||||
)
|
||||
, ( "Column"
|
||||
, [ Element.text "A"
|
||||
, Element.text "B"
|
||||
, Element.text "C"
|
||||
]
|
||||
|> Widget.column style.cardColumn
|
||||
)
|
||||
, ( "Singleton List"
|
||||
, [ Element.text "A"
|
||||
]
|
||||
|> Widget.column style.cardColumn
|
||||
)
|
||||
, ( "Empty List"
|
||||
, []
|
||||
|> Widget.column style.cardColumn
|
||||
)
|
||||
]
|
@ -1,100 +0,0 @@
|
||||
module Core.Style exposing (Style,menuTabButtonSelected,menuTabButton, menuButton, menuButtonSelected, menuIconButton, sheetButton, sheetButtonSelected)
|
||||
|
||||
import Element exposing (Attribute, Element)
|
||||
import Element.Input as Input
|
||||
import Html exposing (Html)
|
||||
|
||||
|
||||
type alias Style msg =
|
||||
{ snackbar : List (Attribute msg)
|
||||
, layout : List (Attribute msg) -> Element msg -> Html msg
|
||||
, header : List (Attribute msg)
|
||||
, sheet : List (Attribute msg)
|
||||
, menuButton : List (Attribute msg)
|
||||
, menuButtonSelected : List (Attribute msg)
|
||||
, sheetButton : List (Attribute msg)
|
||||
, sheetButtonSelected : List (Attribute msg)
|
||||
, tabButton : List (Attribute msg)
|
||||
, tabButtonSelected : List (Attribute msg)
|
||||
, menuIcon : Element Never
|
||||
, moreVerticalIcon : Element Never
|
||||
, spacing : Int
|
||||
}
|
||||
|
||||
|
||||
menuButtonSelected : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
|
||||
menuButtonSelected config { label, icon, onPress } =
|
||||
Input.button (config.menuButton ++ config.menuButtonSelected)
|
||||
{ onPress = onPress
|
||||
, label =
|
||||
Element.row [ Element.spacing 8 ]
|
||||
[ icon |> Element.map never
|
||||
, Element.text label
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
menuButton : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
|
||||
menuButton config { label, icon, onPress } =
|
||||
Input.button config.menuButton
|
||||
{ onPress = onPress
|
||||
, label =
|
||||
Element.row [ Element.spacing 8 ]
|
||||
[ icon |> Element.map never
|
||||
, Element.text label
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
menuIconButton : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
|
||||
menuIconButton config { label, icon, onPress } =
|
||||
Input.button config.menuButton
|
||||
{ onPress = onPress
|
||||
, label = icon |> Element.map never
|
||||
}
|
||||
|
||||
|
||||
sheetButton : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
|
||||
sheetButton config { label, icon, onPress } =
|
||||
Input.button config.sheetButton
|
||||
{ onPress = onPress
|
||||
, label =
|
||||
Element.row [ Element.spacing 8 ]
|
||||
[ icon |> Element.map never
|
||||
, Element.text label
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
sheetButtonSelected : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
|
||||
sheetButtonSelected config { label, icon, onPress } =
|
||||
Input.button (config.sheetButton ++ config.sheetButtonSelected)
|
||||
{ onPress = onPress
|
||||
, label =
|
||||
Element.row [ Element.spacing 8 ]
|
||||
[ icon |> Element.map never
|
||||
, Element.text label
|
||||
]
|
||||
}
|
||||
|
||||
menuTabButton : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
|
||||
menuTabButton config { label, icon, onPress } =
|
||||
Input.button (config.menuButton ++ config.tabButton)
|
||||
{ onPress = onPress
|
||||
, label =
|
||||
Element.row [ Element.spacing 8 ]
|
||||
[ icon |> Element.map never
|
||||
, Element.text label
|
||||
]
|
||||
}
|
||||
|
||||
menuTabButtonSelected : Style msg -> { label : String, icon : Element Never, onPress : Maybe msg } -> Element msg
|
||||
menuTabButtonSelected config { label, icon, onPress } =
|
||||
Input.button (config.menuButton ++ config.tabButton ++ config.tabButtonSelected)
|
||||
{ onPress = onPress
|
||||
, label =
|
||||
Element.row [ Element.spacing 8 ]
|
||||
[ icon |> Element.map never
|
||||
, Element.text label
|
||||
]
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
module Core.Utillity exposing (responsiveLayout)
|
||||
|
||||
import Element exposing (Attribute, Element)
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attributes
|
||||
|
||||
|
||||
{-| taken from Orasund/elm-ui-framework
|
||||
-}
|
||||
layout : List (Attribute msg) -> Element msg -> Html msg
|
||||
layout attributes =
|
||||
Element.layoutWith
|
||||
{ options = layoutOptions
|
||||
}
|
||||
(layoutAttributes ++ attributes)
|
||||
|
||||
|
||||
{-| taken from Orasund/elm-ui-framework
|
||||
-}
|
||||
responsiveLayout : List (Attribute msg) -> Element msg -> Html msg
|
||||
responsiveLayout attributes view =
|
||||
Html.div []
|
||||
[ Html.node "meta"
|
||||
[ Attributes.attribute "name" "viewport"
|
||||
, Attributes.attribute "content" "width=device-width, initial-scale=1.0"
|
||||
]
|
||||
[]
|
||||
, layout attributes <|
|
||||
view
|
||||
]
|
76
src/Internal/Button.elm
Normal file
76
src/Internal/Button.elm
Normal file
@ -0,0 +1,76 @@
|
||||
module Internal.Button exposing
|
||||
( Button
|
||||
, TextButton
|
||||
, button
|
||||
, iconButton
|
||||
, textButton
|
||||
)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Element.Input as Input
|
||||
import Element.Region as Region
|
||||
import Widget.Style exposing (ButtonStyle)
|
||||
|
||||
|
||||
type alias Button msg =
|
||||
{ text : String
|
||||
, onPress : Maybe msg
|
||||
, icon : Element Never
|
||||
}
|
||||
|
||||
|
||||
type alias TextButton msg =
|
||||
{ text : String
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
|
||||
|
||||
iconButton : ButtonStyle msg -> Button msg -> Element msg
|
||||
iconButton style { onPress, text, icon } =
|
||||
Input.button
|
||||
(style.container
|
||||
++ (if onPress == Nothing then
|
||||
style.ifDisabled
|
||||
|
||||
else
|
||||
style.otherwise
|
||||
)
|
||||
++ [ Region.description text ]
|
||||
)
|
||||
{ onPress = onPress
|
||||
, label =
|
||||
icon |> Element.map never |> Element.el style.labelRow
|
||||
}
|
||||
|
||||
|
||||
textButton : ButtonStyle msg -> TextButton msg -> Element msg
|
||||
textButton style { onPress, text } =
|
||||
button style
|
||||
{ onPress = onPress
|
||||
, text = text
|
||||
, icon = Element.none
|
||||
}
|
||||
|
||||
|
||||
button :
|
||||
ButtonStyle msg
|
||||
-> Button msg
|
||||
-> Element msg
|
||||
button style { onPress, text, icon } =
|
||||
Input.button
|
||||
(style.container
|
||||
++ (if onPress == Nothing then
|
||||
style.ifDisabled
|
||||
|
||||
else
|
||||
style.otherwise
|
||||
)
|
||||
)
|
||||
{ onPress = onPress
|
||||
, label =
|
||||
Element.row style.labelRow
|
||||
[ icon |> Element.map never
|
||||
, Element.text text |> Element.el style.text
|
||||
]
|
||||
}
|
||||
|
93
src/Internal/Dialog.elm
Normal file
93
src/Internal/Dialog.elm
Normal file
@ -0,0 +1,93 @@
|
||||
module Internal.Dialog exposing (Dialog, dialog, modal)
|
||||
|
||||
import Element exposing (Attribute, Element)
|
||||
import Element.Background as Background
|
||||
import Element.Events as Events
|
||||
import Internal.Button as Button exposing (TextButton)
|
||||
import Widget.Style exposing (DialogStyle)
|
||||
|
||||
|
||||
type alias Dialog msg =
|
||||
{ title : Maybe String
|
||||
, text : String
|
||||
, accept : Maybe (TextButton msg)
|
||||
, dismiss : Maybe (TextButton msg)
|
||||
}
|
||||
|
||||
|
||||
modal : { onDismiss : Maybe msg, content : Element msg } -> List (Attribute msg)
|
||||
modal { onDismiss, content } =
|
||||
[ Element.none
|
||||
|> Element.el
|
||||
([ Element.width <| Element.fill
|
||||
, Element.height <| Element.fill
|
||||
, Background.color <| Element.rgba255 0 0 0 0.5
|
||||
]
|
||||
++ (onDismiss
|
||||
|> Maybe.map (Events.onClick >> List.singleton)
|
||||
|> Maybe.withDefault []
|
||||
)
|
||||
)
|
||||
|> Element.inFront
|
||||
, content |> Element.inFront
|
||||
, Element.clip
|
||||
]
|
||||
|
||||
|
||||
dialog :
|
||||
DialogStyle msg
|
||||
-> Dialog msg
|
||||
-> List (Attribute msg)
|
||||
dialog style { title, text, accept, dismiss } =
|
||||
{ onDismiss =
|
||||
case ( accept, dismiss ) of
|
||||
( Nothing, Nothing ) ->
|
||||
Nothing
|
||||
|
||||
( Nothing, Just { onPress } ) ->
|
||||
onPress
|
||||
|
||||
( Just _, _ ) ->
|
||||
Nothing
|
||||
, content =
|
||||
Element.column
|
||||
([ Element.centerX
|
||||
, Element.centerY
|
||||
]
|
||||
++ style.containerColumn
|
||||
)
|
||||
[ title
|
||||
|> Maybe.map
|
||||
(Element.text
|
||||
>> Element.el style.title
|
||||
)
|
||||
|> Maybe.withDefault Element.none
|
||||
, text
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.paragraph style.text
|
||||
, Element.row
|
||||
([ Element.alignRight
|
||||
, Element.width <| Element.shrink
|
||||
]
|
||||
++ style.buttonRow
|
||||
)
|
||||
(case ( accept, dismiss ) of
|
||||
( Just acceptButton, Nothing ) ->
|
||||
acceptButton
|
||||
|> Button.textButton style.acceptButton
|
||||
|> List.singleton
|
||||
|
||||
( Just acceptButton, Just dismissButton ) ->
|
||||
[ dismissButton
|
||||
|> Button.textButton style.dismissButton
|
||||
, acceptButton
|
||||
|> Button.textButton style.acceptButton
|
||||
]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
)
|
||||
]
|
||||
}
|
||||
|> modal
|
46
src/Internal/ExpansionPanel.elm
Normal file
46
src/Internal/ExpansionPanel.elm
Normal file
@ -0,0 +1,46 @@
|
||||
module Internal.ExpansionPanel exposing (ExpansionPanel, expansionPanel)
|
||||
|
||||
{-| Part of Material Design Lists
|
||||
-}
|
||||
|
||||
import Element exposing (Element)
|
||||
import Element.Events as Events
|
||||
import Widget.Style exposing (ExpansionPanelStyle)
|
||||
|
||||
|
||||
type alias ExpansionPanel msg =
|
||||
{ onToggle : Bool -> msg
|
||||
, icon : Element Never
|
||||
, text : String
|
||||
, content : Element msg
|
||||
, isExpanded : Bool
|
||||
}
|
||||
|
||||
|
||||
expansionPanel :
|
||||
ExpansionPanelStyle msg
|
||||
-> ExpansionPanel msg
|
||||
-> Element msg
|
||||
expansionPanel style model =
|
||||
Element.column style.containerColumn <|
|
||||
[ Element.row
|
||||
((Events.onClick <| model.onToggle <| not model.isExpanded)
|
||||
:: style.panelRow
|
||||
)
|
||||
[ Element.row style.labelRow
|
||||
[ model.icon |> Element.map never
|
||||
, model.text |> Element.text
|
||||
]
|
||||
, Element.map never <|
|
||||
if model.isExpanded then
|
||||
style.collapseIcon
|
||||
|
||||
else
|
||||
style.expandIcon
|
||||
]
|
||||
, if model.isExpanded then
|
||||
Element.el style.content <| model.content
|
||||
|
||||
else
|
||||
Element.none
|
||||
]
|
114
src/Internal/List.elm
Normal file
114
src/Internal/List.elm
Normal file
@ -0,0 +1,114 @@
|
||||
module Internal.List exposing (buttonColumn, buttonRow, column, row)
|
||||
|
||||
import Element exposing (Attribute, Element)
|
||||
import Internal.Button exposing (Button)
|
||||
import Internal.Select as Select
|
||||
import Widget.Style exposing (ButtonStyle, ColumnStyle, RowStyle)
|
||||
|
||||
|
||||
internal :
|
||||
{ list
|
||||
| element : List (Attribute msg)
|
||||
, ifFirst : List (Attribute msg)
|
||||
, ifLast : List (Attribute msg)
|
||||
, otherwise : List (Attribute msg)
|
||||
}
|
||||
-> List (Element msg)
|
||||
-> List (Element msg)
|
||||
internal style list =
|
||||
list
|
||||
|> List.indexedMap
|
||||
(\i ->
|
||||
Element.el <|
|
||||
style.element
|
||||
++ (if List.length list == 1 then
|
||||
[]
|
||||
|
||||
else if i == 0 then
|
||||
style.ifFirst
|
||||
|
||||
else if i == (List.length list - 1) then
|
||||
style.ifLast
|
||||
|
||||
else
|
||||
style.otherwise
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
row : RowStyle msg -> List (Element msg) -> Element msg
|
||||
row style =
|
||||
internal style >> Element.row style.containerRow
|
||||
|
||||
|
||||
column : ColumnStyle msg -> List (Element msg) -> Element msg
|
||||
column style =
|
||||
internal style >> Element.column style.containerColumn
|
||||
|
||||
|
||||
internalButton :
|
||||
{ list :
|
||||
{ list
|
||||
| element : List (Attribute msg)
|
||||
, ifFirst : List (Attribute msg)
|
||||
, ifLast : List (Attribute msg)
|
||||
, otherwise : List (Attribute msg)
|
||||
}
|
||||
, button : ButtonStyle msg
|
||||
}
|
||||
-> List ( Bool, Button msg )
|
||||
-> List (Element msg)
|
||||
internalButton style list =
|
||||
list
|
||||
|> List.indexedMap
|
||||
(\i ->
|
||||
Select.selectButton
|
||||
{ container =
|
||||
style.button.container
|
||||
++ style.list.element
|
||||
++ (if List.length list == 1 then
|
||||
[]
|
||||
|
||||
else if i == 0 then
|
||||
style.list.ifFirst
|
||||
|
||||
else if i == (List.length list - 1) then
|
||||
style.list.ifLast
|
||||
|
||||
else
|
||||
style.list.otherwise
|
||||
)
|
||||
, labelRow =
|
||||
style.button.labelRow
|
||||
, text =
|
||||
style.button.text
|
||||
, ifDisabled =
|
||||
style.button.ifDisabled
|
||||
, ifActive =
|
||||
style.button.ifActive
|
||||
, otherwise =
|
||||
style.button.otherwise
|
||||
}
|
||||
--Workaround for https://github.com/mdgriffith/elm-ui/issues/47
|
||||
>> Element.el []
|
||||
)
|
||||
|
||||
|
||||
buttonRow :
|
||||
{ list : RowStyle msg
|
||||
, button : ButtonStyle msg
|
||||
}
|
||||
-> List ( Bool, Button msg )
|
||||
-> Element msg
|
||||
buttonRow style =
|
||||
internalButton style >> Element.row style.list.containerRow
|
||||
|
||||
|
||||
buttonColumn :
|
||||
{ list : ColumnStyle msg
|
||||
, button : ButtonStyle msg
|
||||
}
|
||||
-> List ( Bool, Button msg )
|
||||
-> Element msg
|
||||
buttonColumn style =
|
||||
internalButton style >> Element.column style.list.containerColumn
|
87
src/Internal/Select.elm
Normal file
87
src/Internal/Select.elm
Normal file
@ -0,0 +1,87 @@
|
||||
module Internal.Select exposing (MultiSelect, Select, multiSelect, select, selectButton)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Element.Input as Input
|
||||
import Internal.Button exposing (Button)
|
||||
import Set exposing (Set)
|
||||
import Widget.Style exposing (ButtonStyle)
|
||||
|
||||
|
||||
type alias Select msg =
|
||||
{ selected : Maybe Int
|
||||
, options :
|
||||
List
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
}
|
||||
, onSelect : Int -> Maybe msg
|
||||
}
|
||||
|
||||
|
||||
type alias MultiSelect msg =
|
||||
{ selected : Set Int
|
||||
, options :
|
||||
List
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
}
|
||||
, onSelect : Int -> Maybe msg
|
||||
}
|
||||
|
||||
|
||||
selectButton :
|
||||
ButtonStyle msg
|
||||
-> ( Bool, Button msg )
|
||||
-> Element msg
|
||||
selectButton style ( selected, b ) =
|
||||
Input.button
|
||||
(style.container
|
||||
++ (if b.onPress == Nothing then
|
||||
style.ifDisabled
|
||||
|
||||
else if selected then
|
||||
style.ifActive
|
||||
|
||||
else
|
||||
style.otherwise
|
||||
)
|
||||
)
|
||||
{ onPress = b.onPress
|
||||
, label =
|
||||
Element.row style.labelRow
|
||||
[ b.icon |> Element.map never
|
||||
, Element.text b.text |> Element.el style.text
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
select :
|
||||
Select msg
|
||||
-> List ( Bool, Button msg )
|
||||
select { selected, options, onSelect } =
|
||||
options
|
||||
|> List.indexedMap
|
||||
(\i a ->
|
||||
( selected == Just i
|
||||
, { onPress = i |> onSelect
|
||||
, text = a.text
|
||||
, icon = a.icon
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
multiSelect :
|
||||
MultiSelect msg
|
||||
-> List ( Bool, Button msg )
|
||||
multiSelect { selected, options, onSelect } =
|
||||
options
|
||||
|> List.indexedMap
|
||||
(\i a ->
|
||||
( selected |> Set.member i
|
||||
, { onPress = i |> onSelect
|
||||
, text = a.text
|
||||
, icon = a.icon
|
||||
}
|
||||
)
|
||||
)
|
181
src/Internal/SortTable.elm
Normal file
181
src/Internal/SortTable.elm
Normal file
@ -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 []
|
||||
}
|
||||
)
|
||||
}
|
24
src/Internal/Tab.elm
Normal file
24
src/Internal/Tab.elm
Normal file
@ -0,0 +1,24 @@
|
||||
module Internal.Tab exposing (Tab, tab)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Internal.Select as Select exposing (Select)
|
||||
import Widget.Style exposing (TabStyle)
|
||||
|
||||
|
||||
type alias Tab msg =
|
||||
{ tabs : Select msg
|
||||
, content : Maybe Int -> Element msg
|
||||
}
|
||||
|
||||
|
||||
tab : TabStyle msg -> Tab msg -> Element msg
|
||||
tab style { tabs, content } =
|
||||
[ tabs
|
||||
|> Select.select
|
||||
|> List.map (Select.selectButton style.button)
|
||||
|> Element.row style.optionRow
|
||||
, tabs.selected
|
||||
|> content
|
||||
|> Element.el style.content
|
||||
]
|
||||
|> Element.column style.containerColumn
|
38
src/Internal/TextInput.elm
Normal file
38
src/Internal/TextInput.elm
Normal file
@ -0,0 +1,38 @@
|
||||
module Internal.TextInput exposing (TextInput, textInput)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Element.Input as Input exposing (Placeholder)
|
||||
import Internal.Button as Button exposing (Button)
|
||||
import Widget.Style exposing (TextInputStyle)
|
||||
|
||||
|
||||
type alias TextInput msg =
|
||||
{ chips : List (Button msg)
|
||||
, text : String
|
||||
, placeholder : Maybe (Placeholder msg)
|
||||
, label : String
|
||||
, onChange : String -> msg
|
||||
}
|
||||
|
||||
|
||||
textInput : TextInputStyle msg -> TextInput msg -> Element msg
|
||||
textInput style { chips, placeholder, label, text, onChange } =
|
||||
Element.row style.containerRow
|
||||
[ if chips |> List.isEmpty then
|
||||
Element.none
|
||||
|
||||
else
|
||||
chips
|
||||
|> List.map
|
||||
(Button.button style.chipButton
|
||||
--Workaround for https://github.com/mdgriffith/elm-ui/issues/47
|
||||
>> Element.el []
|
||||
)
|
||||
|> Element.row style.chipsRow
|
||||
, Input.text style.input
|
||||
{ onChange = onChange
|
||||
, text = text
|
||||
, placeholder = placeholder
|
||||
, label = Input.labelHidden label
|
||||
}
|
||||
]
|
256
src/Layout.elm
256
src/Layout.elm
@ -1,256 +0,0 @@
|
||||
module Layout exposing (Direction(..), Layout, init, queueMessage, setSidebar, timePassed, view)
|
||||
|
||||
import Browser.Dom exposing (Viewport)
|
||||
import Core.Style as Style exposing (Style)
|
||||
import Element exposing (Attribute, DeviceClass(..), Element)
|
||||
import Element.Background as Background
|
||||
import Element.Events as Events
|
||||
import Element.Input as Input
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attributes
|
||||
import Widget
|
||||
import Widget.Snackbar as Snackbar
|
||||
|
||||
|
||||
type Direction
|
||||
= Left
|
||||
| Right
|
||||
|
||||
|
||||
type alias Layout =
|
||||
{ snackbar : Snackbar.Model String
|
||||
, sheet : Maybe Direction
|
||||
}
|
||||
|
||||
|
||||
init : Layout
|
||||
init =
|
||||
{ snackbar = Snackbar.init
|
||||
, sheet = Nothing
|
||||
}
|
||||
|
||||
|
||||
queueMessage : String -> Layout -> Layout
|
||||
queueMessage message layout =
|
||||
{ layout
|
||||
| snackbar = layout.snackbar |> Snackbar.insert message
|
||||
}
|
||||
|
||||
|
||||
setSidebar : Maybe Direction -> Layout -> Layout
|
||||
setSidebar direction layout =
|
||||
{ layout
|
||||
| sheet = direction
|
||||
}
|
||||
|
||||
|
||||
timePassed : Int -> Layout -> Layout
|
||||
timePassed sec layout =
|
||||
case layout.sheet of
|
||||
Nothing ->
|
||||
{ layout
|
||||
| snackbar = layout.snackbar |> Snackbar.timePassed sec
|
||||
}
|
||||
|
||||
_ ->
|
||||
layout
|
||||
|
||||
|
||||
view :
|
||||
List (Attribute msg)
|
||||
->
|
||||
{ deviceClass : DeviceClass
|
||||
, dialog : Maybe { onDismiss : Maybe msg, content : Element msg }
|
||||
, content : Element msg
|
||||
, layout : Layout
|
||||
, title : Element msg
|
||||
, menu :
|
||||
{ selected : Int
|
||||
, items : List { label : String, icon : Element Never, onPress : Maybe msg }
|
||||
}
|
||||
, actions : List { label : String, icon : Element Never, onPress : Maybe msg }
|
||||
, onChangedSidebar : Maybe Direction -> msg
|
||||
, style : Style msg
|
||||
}
|
||||
-> Html msg
|
||||
view attributes { title, onChangedSidebar, menu, actions, deviceClass, dialog, content, style, layout } =
|
||||
let
|
||||
( primaryActions, moreActions ) =
|
||||
( if (actions |> List.length) > 4 then
|
||||
actions |> List.take 2
|
||||
|
||||
else if (actions |> List.length) == 4 then
|
||||
actions |> List.take 1
|
||||
|
||||
else if (actions |> List.length) == 3 then
|
||||
[]
|
||||
|
||||
else
|
||||
actions |> List.take 2
|
||||
, if (actions |> List.length) > 4 then
|
||||
actions |> List.drop 2
|
||||
|
||||
else if (actions |> List.length) == 4 then
|
||||
actions |> List.drop 1
|
||||
|
||||
else if (actions |> List.length) == 3 then
|
||||
actions
|
||||
|
||||
else
|
||||
actions |> List.drop 2
|
||||
)
|
||||
|
||||
nav =
|
||||
[ (if
|
||||
(deviceClass == Phone)
|
||||
|| (deviceClass == Tablet)
|
||||
|| ((menu.items |> List.length) > 5)
|
||||
then
|
||||
[ Input.button style.menuButton
|
||||
{ onPress = Just <| onChangedSidebar <| Just Left
|
||||
, label = style.menuIcon |> Element.map never
|
||||
}
|
||||
, title
|
||||
]
|
||||
|
||||
else
|
||||
[ title
|
||||
, menu.items
|
||||
|> List.indexedMap
|
||||
(\i ->
|
||||
if i == menu.selected then
|
||||
Style.menuTabButtonSelected style
|
||||
|
||||
else
|
||||
Style.menuTabButton style
|
||||
)
|
||||
|> Element.row
|
||||
[ Element.width <| Element.shrink
|
||||
]
|
||||
]
|
||||
)
|
||||
|> Element.row
|
||||
[ Element.width <| Element.shrink
|
||||
, Element.spacing 8
|
||||
]
|
||||
, [ primaryActions
|
||||
|> List.map
|
||||
(if deviceClass == Phone then
|
||||
Style.menuIconButton style
|
||||
|
||||
else
|
||||
Style.menuButton style
|
||||
)
|
||||
, if moreActions |> List.isEmpty then
|
||||
[]
|
||||
|
||||
else
|
||||
[ Style.menuButton style
|
||||
{ onPress = Just <| onChangedSidebar <| Just Right
|
||||
, icon = style.moreVerticalIcon
|
||||
, label = ""
|
||||
}
|
||||
]
|
||||
]
|
||||
|> List.concat
|
||||
|> Element.row
|
||||
[ Element.width <| Element.shrink
|
||||
, Element.alignRight
|
||||
]
|
||||
]
|
||||
|> Element.row
|
||||
(style.header
|
||||
++ [ Element.padding 0
|
||||
, Element.centerX
|
||||
, Element.spaceEvenly
|
||||
, Element.alignTop
|
||||
, Element.width <| Element.fill
|
||||
]
|
||||
)
|
||||
|
||||
snackbar =
|
||||
layout.snackbar
|
||||
|> Snackbar.current
|
||||
|> Maybe.map
|
||||
(Element.text
|
||||
>> List.singleton
|
||||
>> Element.paragraph style.snackbar
|
||||
>> Element.el
|
||||
[ Element.padding 8
|
||||
, Element.alignBottom
|
||||
, Element.alignRight
|
||||
]
|
||||
)
|
||||
|> Maybe.withDefault Element.none
|
||||
sheet =
|
||||
case layout.sheet of
|
||||
Just Left ->
|
||||
menu.items
|
||||
|> List.indexedMap
|
||||
(\i ->
|
||||
if i == menu.selected then
|
||||
Style.sheetButtonSelected style
|
||||
|
||||
else
|
||||
Style.sheetButton style
|
||||
)
|
||||
|> Element.column [ Element.width <| Element.fill ]
|
||||
|> Element.el
|
||||
(style.sheet
|
||||
++ [ Element.height <| Element.fill
|
||||
, Element.alignLeft
|
||||
]
|
||||
)
|
||||
|
||||
Just Right ->
|
||||
moreActions
|
||||
|> List.map (Style.sheetButton style)
|
||||
|> Element.column [ Element.width <| Element.fill ]
|
||||
|> Element.el
|
||||
(style.sheet
|
||||
++ [ Element.height <| Element.fill
|
||||
, Element.alignRight
|
||||
]
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
Element.none
|
||||
in
|
||||
content
|
||||
|> style.layout
|
||||
(List.concat
|
||||
[ attributes
|
||||
, [ Element.inFront nav
|
||||
, Element.inFront snackbar
|
||||
]
|
||||
, if (layout.sheet /= Nothing) || (dialog /= Nothing) then
|
||||
Widget.scrim
|
||||
{ onDismiss =
|
||||
Just <|
|
||||
case dialog of
|
||||
Just { onDismiss } ->
|
||||
onDismiss
|
||||
|> Maybe.withDefault
|
||||
(Nothing
|
||||
|> onChangedSidebar
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
Nothing
|
||||
|> onChangedSidebar
|
||||
, content = Element.none
|
||||
}
|
||||
|
||||
else
|
||||
[]
|
||||
, [ Element.inFront sheet
|
||||
, Element.inFront <|
|
||||
case dialog of
|
||||
Just element ->
|
||||
element.content
|
||||
|
||||
Nothing ->
|
||||
Element.none
|
||||
]
|
||||
]
|
||||
)
|
685
src/Widget.elm
685
src/Widget.elm
@ -1,249 +1,508 @@
|
||||
module Widget exposing
|
||||
( select, multiSelect, collapsable, carousel, scrim, tab
|
||||
, dialog
|
||||
( Button, TextButton, iconButton, textButton, button
|
||||
, Select, MultiSelect, selectButton, select, multiSelect
|
||||
, Dialog, modal, dialog
|
||||
, ExpansionPanel, expansionPanel
|
||||
, row, column, buttonRow, buttonColumn
|
||||
, SortTable,Column, sortTable, floatColumn, intColumn, stringColumn, unsortableColumn
|
||||
, TextInput, textInput
|
||||
, Tab, tab
|
||||
)
|
||||
|
||||
{-| This module contains functions for displaying data.
|
||||
{-| This module contains different stateless view functions. No wiring required.
|
||||
|
||||
@docs select, multiSelect, collapsable, carousel, scrim, tab
|
||||
These widgets should be used by defining the styling seperately:
|
||||
|
||||
```
|
||||
Widget.button Material.primaryButton
|
||||
{ text = "disable me"
|
||||
, icon =
|
||||
FeatherIcons.slash
|
||||
|> FeatherIcons.withSize 16
|
||||
|> FeatherIcons.toHtml []
|
||||
|> Element.html
|
||||
|> Element.el []
|
||||
, onPress =
|
||||
if isButtonEnabled then
|
||||
ChangedButtonStatus False
|
||||
|> Just
|
||||
|
||||
else
|
||||
Nothing
|
||||
}
|
||||
```
|
||||
|
||||
Every widgets comes with a type. You can think of the widgets as building blocks.
|
||||
You can create you own widgets by sticking widgets types together.
|
||||
|
||||
# Buttons
|
||||
|
||||
![Button](https://orasund.github.io/elm-ui-widgets/assets/button.png)
|
||||
|
||||
@docs Button, TextButton, iconButton, textButton, button
|
||||
|
||||
|
||||
# DEPRECATED
|
||||
# Select
|
||||
|
||||
@docs dialog
|
||||
![Select](https://orasund.github.io/elm-ui-widgets/assets/select.png)
|
||||
|
||||
@docs Select, selectButton, select
|
||||
|
||||
![multiSelect](https://orasund.github.io/elm-ui-widgets/assets/multiSelect.png)
|
||||
|
||||
@docs MultiSelect, multiSelect
|
||||
|
||||
|
||||
# Dialog
|
||||
|
||||
![dialog](https://orasund.github.io/elm-ui-widgets/assets/dialog.png)
|
||||
|
||||
@docs Dialog, modal, dialog
|
||||
|
||||
|
||||
# Expansion Panel
|
||||
|
||||
![expansionPanel](https://orasund.github.io/elm-ui-widgets/assets/expansionPanel.png)
|
||||
|
||||
@docs ExpansionPanel, expansionPanel
|
||||
|
||||
|
||||
# List
|
||||
|
||||
![list](https://orasund.github.io/elm-ui-widgets/assets/list.png)
|
||||
|
||||
@docs row, column, buttonRow, buttonColumn
|
||||
|
||||
|
||||
# Sort Table
|
||||
|
||||
![sortTable](https://orasund.github.io/elm-ui-widgets/assets/sortTable.png)
|
||||
|
||||
@docs SortTable,Column, sortTable, floatColumn, intColumn, stringColumn, unsortableColumn
|
||||
|
||||
|
||||
# Text Input
|
||||
|
||||
![textInput](https://orasund.github.io/elm-ui-widgets/assets/textInput.png)
|
||||
|
||||
@docs TextInput, textInput
|
||||
|
||||
|
||||
# Tab
|
||||
|
||||
![tab](https://orasund.github.io/elm-ui-widgets/assets/textInput.png)
|
||||
|
||||
@docs Tab, tab
|
||||
|
||||
-}
|
||||
|
||||
import Array exposing (Array)
|
||||
import Element exposing (Attribute, Element)
|
||||
import Element.Background as Background
|
||||
import Element.Events as Events
|
||||
import Element.Input as Input
|
||||
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.Tab as Tab
|
||||
import Internal.TextInput as TextInput
|
||||
import Set exposing (Set)
|
||||
import Widget.Style exposing (ButtonStyle,TextInputStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, RowStyle, SortTableStyle, TabStyle)
|
||||
|
||||
|
||||
|
||||
{----------------------------------------------------------
|
||||
- BUTTON
|
||||
----------------------------------------------------------}
|
||||
|
||||
|
||||
{-| Button widget type
|
||||
-}
|
||||
type alias Button msg =
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
|
||||
|
||||
{-| Button widget type with no icon
|
||||
-}
|
||||
type alias TextButton msg =
|
||||
{ text : String
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
|
||||
|
||||
{-| A button containing only an icon, the text is used for screenreaders.
|
||||
-}
|
||||
iconButton :
|
||||
ButtonStyle msg
|
||||
->
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
-> Element msg
|
||||
iconButton =
|
||||
Button.iconButton
|
||||
|
||||
|
||||
{-| A button with just text and not icon.
|
||||
-}
|
||||
textButton :
|
||||
ButtonStyle msg
|
||||
->
|
||||
{ textButton
|
||||
| text : String
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
-> Element msg
|
||||
textButton style { text, onPress } =
|
||||
Button.textButton style
|
||||
{ text = text
|
||||
, onPress = onPress
|
||||
}
|
||||
|
||||
|
||||
{-| A button containing a text and an icon.
|
||||
-}
|
||||
button :
|
||||
ButtonStyle msg
|
||||
->
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
, onPress : Maybe msg
|
||||
}
|
||||
-> Element msg
|
||||
button =
|
||||
Button.button
|
||||
|
||||
|
||||
|
||||
{----------------------------------------------------------
|
||||
- SELECT
|
||||
----------------------------------------------------------}
|
||||
|
||||
|
||||
{-| Select widget type
|
||||
|
||||
Technical Remark:
|
||||
|
||||
* A more suitable name would be "Choice"
|
||||
|
||||
-}
|
||||
type alias Select msg =
|
||||
{ selected : Maybe Int
|
||||
, options :
|
||||
List
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
}
|
||||
, onSelect : Int -> Maybe msg
|
||||
}
|
||||
|
||||
|
||||
{-| Multi Select widget type
|
||||
|
||||
Technical Remark:
|
||||
|
||||
* A more suitable name would be "Options"
|
||||
|
||||
-}
|
||||
type alias MultiSelect msg =
|
||||
{ selected : Set Int
|
||||
, options :
|
||||
List
|
||||
{ text : String
|
||||
, icon : Element Never
|
||||
}
|
||||
, onSelect : Int -> Maybe msg
|
||||
}
|
||||
|
||||
|
||||
{-| A simple button that can be selected.
|
||||
-}
|
||||
selectButton :
|
||||
ButtonStyle msg
|
||||
-> ( Bool, Button msg )
|
||||
-> Element msg
|
||||
selectButton =
|
||||
Select.selectButton
|
||||
|
||||
|
||||
{-| Selects one out of multiple options. This can be used for radio buttons or Menus.
|
||||
-}
|
||||
select :
|
||||
{ selected : Maybe a
|
||||
, options : List a
|
||||
, label : a -> Element msg
|
||||
, onChange : a -> msg
|
||||
, attributes : Bool -> List (Attribute msg)
|
||||
}
|
||||
-> List (Element msg)
|
||||
select { selected, options, label, onChange, attributes } =
|
||||
options
|
||||
|> List.map
|
||||
(\a ->
|
||||
Input.button (attributes (selected == Just a))
|
||||
{ onPress = a |> onChange |> Just
|
||||
, label = label a
|
||||
}
|
||||
)
|
||||
Select msg
|
||||
-> List ( Bool, Button msg )
|
||||
select =
|
||||
Select.select
|
||||
|
||||
|
||||
{-| Selects multible options. This can be used for checkboxes.
|
||||
-}
|
||||
multiSelect :
|
||||
{ selected : Set comparable
|
||||
, options : List comparable
|
||||
, label : comparable -> Element msg
|
||||
, onChange : comparable -> msg
|
||||
, attributes : Bool -> List (Attribute msg)
|
||||
MultiSelect msg
|
||||
-> List ( Bool, Button msg )
|
||||
multiSelect =
|
||||
Select.multiSelect
|
||||
|
||||
|
||||
|
||||
{----------------------------------------------------------
|
||||
- DIALOG
|
||||
----------------------------------------------------------}
|
||||
|
||||
|
||||
{-| Dialog widget type
|
||||
-}
|
||||
type alias Dialog msg =
|
||||
{ title : Maybe String
|
||||
, body : Element msg
|
||||
, accept : Maybe (TextButton msg)
|
||||
, dismiss : Maybe (TextButton msg)
|
||||
}
|
||||
-> List (Element msg)
|
||||
multiSelect { selected, options, label, onChange, attributes } =
|
||||
options
|
||||
|> List.map
|
||||
(\a ->
|
||||
Input.button (attributes (selected |> Set.member a))
|
||||
{ onPress = a |> onChange |> Just
|
||||
, label =
|
||||
label a
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| Some collapsable content.
|
||||
{-| A modal.
|
||||
|
||||
Widget.collapsable
|
||||
{onToggle = ToggleCollapsable
|
||||
,isCollapsed = model.isCollapsed
|
||||
,label = Element.row Grid.compact
|
||||
[ Element.html <|
|
||||
if model.isCollapsed then
|
||||
Heroicons.cheveronRight [ Attributes.width 20]
|
||||
else
|
||||
Heroicons.cheveronDown [ Attributes.width 20]
|
||||
, Element.el Heading.h4 <|Element.text <| "Title"
|
||||
]
|
||||
,content = Element.text <| "Hello World"
|
||||
}
|
||||
Technical Remark:
|
||||
|
||||
* To stop the screen from scrolling, set the height of the layout to the height of the screen.
|
||||
|
||||
-}
|
||||
collapsable :
|
||||
{ onToggle : Bool -> msg
|
||||
, isCollapsed : Bool
|
||||
, label : Element msg
|
||||
, content : Element msg
|
||||
}
|
||||
-> Element msg
|
||||
collapsable { onToggle, isCollapsed, label, content } =
|
||||
Element.column [] <|
|
||||
[ Input.button []
|
||||
{ onPress = Just <| onToggle <| not isCollapsed
|
||||
, label = label
|
||||
}
|
||||
]
|
||||
++ (if isCollapsed then
|
||||
[]
|
||||
modal : { onDismiss : Maybe msg, content : Element msg } -> List (Attribute msg)
|
||||
modal =
|
||||
Dialog.modal
|
||||
|
||||
else
|
||||
[ content ]
|
||||
)
|
||||
|
||||
{-| A Dialog Window.
|
||||
-}
|
||||
dialog :
|
||||
DialogStyle msg
|
||||
->
|
||||
{ title : Maybe String
|
||||
, text : String
|
||||
, accept : Maybe (TextButton msg)
|
||||
, dismiss : Maybe (TextButton msg)
|
||||
}
|
||||
-> List (Attribute msg)
|
||||
dialog =
|
||||
Dialog.dialog
|
||||
|
||||
|
||||
|
||||
{----------------------------------------------------------
|
||||
- EXPANSION PANEL
|
||||
----------------------------------------------------------}
|
||||
|
||||
{-| Expansion Panel widget type
|
||||
-}
|
||||
type alias ExpansionPanel msg =
|
||||
{ onToggle : Bool -> msg
|
||||
, icon : Element Never
|
||||
, text : String
|
||||
, expandIcon : Element Never
|
||||
, collapseIcon : Element Never
|
||||
, content : Element msg
|
||||
, isExpanded : Bool
|
||||
}
|
||||
|
||||
{-| An expansion Panel
|
||||
-}
|
||||
expansionPanel :
|
||||
ExpansionPanelStyle msg
|
||||
->
|
||||
{ onToggle : Bool -> msg
|
||||
, icon : Element Never
|
||||
, text : String
|
||||
, content : Element msg
|
||||
, isExpanded : Bool
|
||||
}
|
||||
-> Element msg
|
||||
expansionPanel =
|
||||
ExpansionPanel.expansionPanel
|
||||
|
||||
|
||||
|
||||
{----------------------------------------------------------
|
||||
- TEXT INPUT
|
||||
----------------------------------------------------------}
|
||||
|
||||
{-| Text Input widget type
|
||||
-}
|
||||
type alias TextInput msg =
|
||||
{ chips : List (Button msg)
|
||||
, text : String
|
||||
, placeholder : Maybe (Placeholder msg)
|
||||
, label : String
|
||||
, onChange : String -> msg
|
||||
}
|
||||
|
||||
{-| A text Input that allows to include chips. -}
|
||||
textInput :
|
||||
TextInputStyle msg
|
||||
->
|
||||
{ chips : List (Button msg)
|
||||
, text : String
|
||||
, placeholder : Maybe (Placeholder msg)
|
||||
, label : String
|
||||
, onChange : String -> msg
|
||||
}
|
||||
-> Element msg
|
||||
textInput =
|
||||
TextInput.textInput
|
||||
|
||||
|
||||
|
||||
{----------------------------------------------------------
|
||||
- LIST
|
||||
----------------------------------------------------------}
|
||||
|
||||
{-| Replacement of `Element.row`
|
||||
-}
|
||||
row : RowStyle msg -> List (Element msg) -> Element msg
|
||||
row =
|
||||
List.row
|
||||
|
||||
{-| Replacement of `Element.column`
|
||||
-}
|
||||
column : ColumnStyle msg -> List (Element msg) -> Element msg
|
||||
column =
|
||||
List.column
|
||||
|
||||
{-| A row of buttons
|
||||
-}
|
||||
buttonRow :
|
||||
{ list : RowStyle msg
|
||||
, button : ButtonStyle msg
|
||||
}
|
||||
-> List ( Bool, Button msg )
|
||||
-> Element msg
|
||||
buttonRow =
|
||||
List.buttonRow
|
||||
|
||||
{-| A column of buttons
|
||||
-}
|
||||
buttonColumn :
|
||||
{ list : ColumnStyle msg
|
||||
, button : ButtonStyle msg
|
||||
}
|
||||
-> List ( Bool, Button msg )
|
||||
-> Element msg
|
||||
buttonColumn =
|
||||
List.buttonColumn
|
||||
|
||||
|
||||
|
||||
{----------------------------------------------------------
|
||||
- SORT TABLE
|
||||
----------------------------------------------------------}
|
||||
|
||||
|
||||
{-| Column for the Sort Table widget type
|
||||
-}
|
||||
type alias Column a =
|
||||
SortTable.Column a
|
||||
|
||||
{-| Sort Table widget type
|
||||
-}
|
||||
type alias SortTable a msg=
|
||||
{ content : List a
|
||||
, columns : List (Column a)
|
||||
, sortBy : String
|
||||
, asc : Bool
|
||||
, onChange : String -> msg
|
||||
}
|
||||
|
||||
{-| An unsortable Column, when trying to sort by this column, nothing will change.
|
||||
-}
|
||||
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
|
||||
|
||||
|
||||
|
||||
{----------------------------------------------------------
|
||||
- TAB
|
||||
----------------------------------------------------------}
|
||||
|
||||
{-| Tab widget type
|
||||
-}
|
||||
type alias Tab msg =
|
||||
{ tabs : Select msg
|
||||
, content : Maybe Int -> Element msg
|
||||
}
|
||||
|
||||
|
||||
{-| Displayes a list of contents in a tab
|
||||
-}
|
||||
tab :
|
||||
List (Attribute msg)
|
||||
TabStyle msg
|
||||
->
|
||||
{ selected : a
|
||||
, options : List a
|
||||
, onChange : a -> msg
|
||||
, label : a -> Element msg
|
||||
, content : a -> Element msg
|
||||
, attributes : Bool -> List (Attribute msg)
|
||||
{ tabs : Select msg
|
||||
, content : Maybe Int -> Element msg
|
||||
}
|
||||
-> Element msg
|
||||
tab atts { selected, options, onChange, label, content, attributes } =
|
||||
[ select
|
||||
{ selected = Just selected
|
||||
, options = options
|
||||
, label = label
|
||||
, onChange = onChange
|
||||
, attributes = attributes
|
||||
}
|
||||
|> Element.row atts
|
||||
, content selected
|
||||
]
|
||||
|> Element.column []
|
||||
|
||||
|
||||
{-| DEPRECATED. Use scrim instead.
|
||||
-}
|
||||
dialog :
|
||||
{ onDismiss : Maybe msg
|
||||
, content : Element msg
|
||||
}
|
||||
-> Element msg
|
||||
dialog { onDismiss, content } =
|
||||
content
|
||||
|> Element.el
|
||||
[ Element.centerX
|
||||
, Element.centerY
|
||||
]
|
||||
|> Element.el
|
||||
([ Element.width <| Element.fill
|
||||
, Element.height <| Element.fill
|
||||
, Background.color <| Element.rgba255 0 0 0 0.5
|
||||
]
|
||||
++ (onDismiss
|
||||
|> Maybe.map (Events.onClick >> List.singleton)
|
||||
|> Maybe.withDefault []
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
{-| A scrim to block the interaction with the site. Usefull for modals and side panels
|
||||
|
||||
If the scrim is clicked a message may be send. Also one can place an element infront.
|
||||
|
||||
Framework.Layout
|
||||
[ Wiget.scrim
|
||||
{ onDismiss = Just <| ToggleDialog False
|
||||
, content =
|
||||
[ "This is a dialog window"
|
||||
|> Element.text
|
||||
, Input.button []
|
||||
{onPress = Just <| ToggleDialog False
|
||||
, label = Element.text "Ok"
|
||||
}
|
||||
]
|
||||
|> Element.column
|
||||
[ Element.centerX
|
||||
, Element.centerY
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
-}
|
||||
scrim : { onDismiss : Maybe msg, content : Element msg } -> List (Attribute msg)
|
||||
scrim { onDismiss, content } =
|
||||
Element.el
|
||||
([ Element.width <| Element.fill
|
||||
, Element.height <| Element.fill
|
||||
, Background.color <| Element.rgba255 0 0 0 0.5
|
||||
]
|
||||
++ (onDismiss
|
||||
|> Maybe.map (Events.onClick >> List.singleton)
|
||||
|> Maybe.withDefault []
|
||||
)
|
||||
)
|
||||
content
|
||||
|> Element.inFront
|
||||
|> List.singleton
|
||||
|
||||
|
||||
{-| A Carousel circles through a non empty list of contents.
|
||||
|
||||
Widget.carousel
|
||||
{content = ("Blue",["Yellow", "Green" , "Red" ]|> Array.fromList)
|
||||
,current = model.carousel
|
||||
, label = \c ->
|
||||
[ Input.button [Element.centerY]
|
||||
{ onPress = Just <|
|
||||
SetCarousel <|
|
||||
(\x -> if x < 0 then 0 else x) <|
|
||||
model.carousel - 1
|
||||
, label = "<" |> Element.text
|
||||
}
|
||||
, c |> Element.text
|
||||
, Input.button [Element.centerY]
|
||||
{ onPress = Just <|
|
||||
SetCarousel <|
|
||||
(\x -> if x > 3 then 3 else x) <|
|
||||
model.carousel + 1
|
||||
, label = ">" |> Element.text
|
||||
}
|
||||
]
|
||||
|> Element.row [Element.centerX, Element.width<| Element.shrink]
|
||||
}
|
||||
|
||||
-}
|
||||
carousel :
|
||||
{ content : ( a, Array a )
|
||||
, current : Int
|
||||
, label : a -> Element msg
|
||||
}
|
||||
-> Element msg
|
||||
carousel { content, current, label } =
|
||||
let
|
||||
( head, tail ) =
|
||||
content
|
||||
in
|
||||
(if current <= 0 then
|
||||
head
|
||||
|
||||
else if current > Array.length tail then
|
||||
tail
|
||||
|> Array.get (Array.length tail - 1)
|
||||
|> Maybe.withDefault head
|
||||
|
||||
else
|
||||
tail
|
||||
|> Array.get (current - 1)
|
||||
|> Maybe.withDefault head
|
||||
)
|
||||
|> label
|
||||
tab =
|
||||
Tab.tab
|
||||
|
@ -1,93 +0,0 @@
|
||||
module Widget.FilterSelect exposing (Model, Msg(..), init, update, viewInput, viewOptions)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Model, Msg, init, update, viewInput, viewOptions
|
||||
|
||||
-}
|
||||
|
||||
import Element exposing (Attribute, Element)
|
||||
import Element.Input as Input exposing (Placeholder)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
{-| The Model containing the raw value, the selected value and all the possible options.
|
||||
-}
|
||||
type alias Model =
|
||||
{ raw : String
|
||||
, selected : Maybe String
|
||||
, options : Set String
|
||||
}
|
||||
|
||||
|
||||
{-| The Msg is exposed by design. You can unselect by sending `Selected Nothing`.
|
||||
-}
|
||||
type Msg
|
||||
= ChangedRaw String
|
||||
| Selected (Maybe String)
|
||||
|
||||
|
||||
{-| The initial state contains the set of possible options.
|
||||
-}
|
||||
init : Set String -> Model
|
||||
init options =
|
||||
{ raw = ""
|
||||
, selected = Nothing
|
||||
, options = options
|
||||
}
|
||||
|
||||
|
||||
{-| Updates the Model
|
||||
-}
|
||||
update : Msg -> Model -> Model
|
||||
update msg model =
|
||||
case msg of
|
||||
ChangedRaw string ->
|
||||
{ model
|
||||
| raw = string
|
||||
}
|
||||
|
||||
Selected maybe ->
|
||||
{ model
|
||||
| selected = maybe
|
||||
}
|
||||
|> (case maybe of
|
||||
Just string ->
|
||||
\m -> { m | raw = string }
|
||||
|
||||
Nothing ->
|
||||
identity
|
||||
)
|
||||
|
||||
|
||||
{-| A wrapper around Input.text.
|
||||
-}
|
||||
viewInput :
|
||||
List (Attribute msg)
|
||||
-> Model
|
||||
->
|
||||
{ msgMapper : Msg -> msg
|
||||
, placeholder : Maybe (Placeholder msg)
|
||||
, label : String
|
||||
}
|
||||
-> Element msg
|
||||
viewInput attributes model { msgMapper, placeholder, label } =
|
||||
Input.text attributes
|
||||
{ onChange = ChangedRaw >> msgMapper
|
||||
, text = model.raw
|
||||
, placeholder = placeholder
|
||||
, label = Input.labelHidden label
|
||||
}
|
||||
|
||||
|
||||
{-| Returns a List of all options that matches the filter.
|
||||
-}
|
||||
viewOptions : Model -> List String
|
||||
viewOptions { raw, options } =
|
||||
if raw == "" then
|
||||
[]
|
||||
|
||||
else
|
||||
options
|
||||
|> Set.filter (String.toUpper >> String.contains (raw |> String.toUpper))
|
||||
|> Set.toList
|
349
src/Widget/Layout.elm
Normal file
349
src/Widget/Layout.elm
Normal file
@ -0,0 +1,349 @@
|
||||
module Widget.Layout exposing (Layout, Part(..), activate, init, queueMessage, timePassed, view)
|
||||
|
||||
{-| Combines multiple concepts from the [material design specification](https://material.io/components/), namely:
|
||||
|
||||
* Top App Bar
|
||||
* Navigation Draw
|
||||
* Side Panel
|
||||
* Dialog
|
||||
* Snackbar
|
||||
|
||||
It is responsive and changes view to apply to the [material design guidelines](https://material.io/components/app-bars-top).
|
||||
|
||||
# Basics
|
||||
|
||||
@docs Layout, Part, init, timePassed, view
|
||||
|
||||
# Actions
|
||||
|
||||
@docs activate, queueMessage
|
||||
|
||||
|
||||
-}
|
||||
|
||||
import Array
|
||||
import Element exposing (Attribute, DeviceClass(..), Element)
|
||||
import Element.Input as Input
|
||||
import Html exposing (Html)
|
||||
import Widget exposing (Button, Select)
|
||||
import Widget.Snackbar as Snackbar exposing (Message)
|
||||
import Widget.Style exposing (LayoutStyle)
|
||||
|
||||
{-| The currently visible part: either the left sheet, right sheet or the search bar
|
||||
-}
|
||||
type Part
|
||||
= LeftSheet
|
||||
| RightSheet
|
||||
| Search
|
||||
|
||||
{-| The model of the layout containing the snackbar and the currently active side sheet (or search bar)
|
||||
-}
|
||||
type alias Layout msg =
|
||||
{ snackbar : Snackbar.Snackbar (Message msg)
|
||||
, active : Maybe Part
|
||||
}
|
||||
|
||||
{-| The initial state of the layout
|
||||
-}
|
||||
init : Layout msg
|
||||
init =
|
||||
{ snackbar = Snackbar.init
|
||||
, active = Nothing
|
||||
}
|
||||
|
||||
{-| Queues a message and displayes it as a snackbar once no other snackbar is visible.
|
||||
-}
|
||||
queueMessage : Message msg -> Layout msg -> Layout msg
|
||||
queueMessage message layout =
|
||||
{ layout
|
||||
| snackbar = layout.snackbar |> Snackbar.insert message
|
||||
}
|
||||
|
||||
{-| Open either a side sheet or the search bar.
|
||||
-}
|
||||
activate : Maybe Part -> Layout msg -> Layout msg
|
||||
activate part layout =
|
||||
{ layout
|
||||
| active = part
|
||||
}
|
||||
|
||||
{-| Update the model, put this function into your subscription.
|
||||
The first argument is the seconds that have passed sice the function was called last.
|
||||
-}
|
||||
timePassed : Int -> Layout msg -> Layout msg
|
||||
timePassed sec layout =
|
||||
case layout.active of
|
||||
Just LeftSheet ->
|
||||
layout
|
||||
|
||||
Just RightSheet ->
|
||||
layout
|
||||
|
||||
_ ->
|
||||
{ layout
|
||||
| snackbar = layout.snackbar |> Snackbar.timePassed sec
|
||||
}
|
||||
|
||||
{-| View the layout. Replacement of `Element.layout`.
|
||||
-}
|
||||
view :
|
||||
LayoutStyle msg
|
||||
->
|
||||
{ window : { height : Int, width : Int }
|
||||
, dialog : Maybe (List (Attribute msg))
|
||||
, layout : Layout msg
|
||||
, title : Element msg
|
||||
, menu : Select msg
|
||||
, search :
|
||||
Maybe
|
||||
{ onChange : String -> msg
|
||||
, text : String
|
||||
, label : String
|
||||
}
|
||||
, actions : List (Button msg)
|
||||
, onChangedSidebar : Maybe Part -> msg
|
||||
}
|
||||
-> Element msg
|
||||
-> Html msg
|
||||
view style { search, title, onChangedSidebar, menu, actions, window, dialog, layout } content =
|
||||
let
|
||||
deviceClass : DeviceClass
|
||||
deviceClass =
|
||||
window
|
||||
|> Element.classifyDevice
|
||||
|> .class
|
||||
|
||||
( primaryActions, moreActions ) =
|
||||
( if (actions |> List.length) > 4 then
|
||||
actions |> List.take 2
|
||||
|
||||
else if (actions |> List.length) == 4 then
|
||||
actions |> List.take 1
|
||||
|
||||
else if (actions |> List.length) == 3 then
|
||||
[]
|
||||
|
||||
else
|
||||
actions |> List.take 2
|
||||
, if (actions |> List.length) > 4 then
|
||||
actions |> List.drop 2
|
||||
|
||||
else if (actions |> List.length) == 4 then
|
||||
actions |> List.drop 1
|
||||
|
||||
else if (actions |> List.length) == 3 then
|
||||
actions
|
||||
|
||||
else
|
||||
actions |> List.drop 2
|
||||
)
|
||||
|
||||
nav =
|
||||
[ (if
|
||||
(deviceClass == Phone)
|
||||
|| (deviceClass == Tablet)
|
||||
|| ((menu.options |> List.length) > 5)
|
||||
then
|
||||
[ Widget.iconButton style.menuButton
|
||||
{ onPress = Just <| onChangedSidebar <| Just LeftSheet
|
||||
, icon = style.menuIcon |> Element.map never
|
||||
, text = "Menu"
|
||||
}
|
||||
, menu.selected
|
||||
|> Maybe.andThen
|
||||
(\option ->
|
||||
menu.options
|
||||
|> Array.fromList
|
||||
|> Array.get option
|
||||
)
|
||||
|> Maybe.map (.text >> Element.text)
|
||||
|> Maybe.withDefault title
|
||||
|> Element.el style.title
|
||||
]
|
||||
|
||||
else
|
||||
[ title |> Element.el style.title
|
||||
, menu
|
||||
|> Widget.select
|
||||
|> List.map (Widget.selectButton style.menuTabButton)
|
||||
|> Element.row
|
||||
[ Element.width <| Element.shrink
|
||||
]
|
||||
]
|
||||
)
|
||||
|> Element.row
|
||||
[ Element.width <| Element.shrink
|
||||
, Element.spacing style.spacing
|
||||
]
|
||||
, if deviceClass == Phone || deviceClass == Tablet then
|
||||
Element.none
|
||||
|
||||
else
|
||||
search
|
||||
|> Maybe.map
|
||||
(\{ onChange, text, label } ->
|
||||
Input.text style.search
|
||||
{ onChange = onChange
|
||||
, text = text
|
||||
, placeholder =
|
||||
Just <|
|
||||
Input.placeholder [] <|
|
||||
Element.text label
|
||||
, label = Input.labelHidden label
|
||||
}
|
||||
)
|
||||
|> Maybe.withDefault Element.none
|
||||
, [ search
|
||||
|> Maybe.map
|
||||
(\{ label } ->
|
||||
if deviceClass == Tablet then
|
||||
[ Widget.button style.menuButton
|
||||
{ onPress = Just <| onChangedSidebar <| Just Search
|
||||
, icon = style.searchIcon
|
||||
, text = label
|
||||
}
|
||||
]
|
||||
|
||||
else if deviceClass == Phone then
|
||||
[ Widget.iconButton style.menuButton
|
||||
{ onPress = Just <| onChangedSidebar <| Just Search
|
||||
, icon = style.searchIcon
|
||||
, text = label
|
||||
}
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
|> Maybe.withDefault []
|
||||
, primaryActions
|
||||
|> List.map
|
||||
(if deviceClass == Phone then
|
||||
Widget.iconButton style.menuButton
|
||||
|
||||
else
|
||||
Widget.button style.menuButton
|
||||
)
|
||||
, if moreActions |> List.isEmpty then
|
||||
[]
|
||||
|
||||
else
|
||||
[ Widget.iconButton style.menuButton
|
||||
{ onPress = Just <| onChangedSidebar <| Just RightSheet
|
||||
, icon = style.moreVerticalIcon
|
||||
, text = "More"
|
||||
}
|
||||
]
|
||||
]
|
||||
|> List.concat
|
||||
|> Element.row
|
||||
[ Element.width <| Element.shrink
|
||||
, Element.alignRight
|
||||
]
|
||||
]
|
||||
|> Element.row
|
||||
(style.header
|
||||
++ [ Element.padding 0
|
||||
, Element.centerX
|
||||
, Element.spacing style.spacing
|
||||
, Element.alignTop
|
||||
, Element.width <| Element.fill
|
||||
]
|
||||
)
|
||||
|
||||
snackbar =
|
||||
layout.snackbar
|
||||
|> Snackbar.view style.snackbar identity
|
||||
|> Maybe.map
|
||||
(Element.el
|
||||
[ Element.padding style.spacing
|
||||
, Element.alignBottom
|
||||
, Element.alignRight
|
||||
]
|
||||
)
|
||||
|> Maybe.withDefault Element.none
|
||||
|
||||
sheet =
|
||||
case layout.active of
|
||||
Just LeftSheet ->
|
||||
[ [ title
|
||||
]
|
||||
, menu
|
||||
|> Widget.select
|
||||
|> List.map
|
||||
(Widget.selectButton style.sheetButton)
|
||||
]
|
||||
|> List.concat
|
||||
|> Element.column [ Element.width <| Element.fill ]
|
||||
|> Element.el
|
||||
(style.sheet
|
||||
++ [ Element.height <| Element.fill
|
||||
, Element.alignLeft
|
||||
]
|
||||
)
|
||||
|
||||
Just RightSheet ->
|
||||
moreActions
|
||||
|> List.map (Widget.button style.sheetButton)
|
||||
|> Element.column [ Element.width <| Element.fill ]
|
||||
|> Element.el
|
||||
(style.sheet
|
||||
++ [ Element.height <| Element.fill
|
||||
, Element.alignRight
|
||||
]
|
||||
)
|
||||
|
||||
Just Search ->
|
||||
case search of
|
||||
Just { onChange, text, label } ->
|
||||
Input.text
|
||||
(style.searchFill
|
||||
++ [ Element.width <| Element.fill
|
||||
]
|
||||
)
|
||||
{ onChange = onChange
|
||||
, text = text
|
||||
, placeholder =
|
||||
Just <|
|
||||
Input.placeholder [] <|
|
||||
Element.text label
|
||||
, label = Input.labelHidden label
|
||||
}
|
||||
|> Element.el
|
||||
[ Element.alignTop
|
||||
, Element.width <| Element.fill
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
Element.none
|
||||
|
||||
Nothing ->
|
||||
Element.none
|
||||
in
|
||||
content
|
||||
|> style.layout
|
||||
(List.concat
|
||||
[ style.container
|
||||
, [ Element.inFront nav
|
||||
, Element.inFront snackbar
|
||||
]
|
||||
, if (layout.active /= Nothing) || (dialog /= Nothing) then
|
||||
--(Element.height <| Element.px <| window.height)
|
||||
-- ::
|
||||
case dialog of
|
||||
Just dialogConfig ->
|
||||
dialogConfig
|
||||
|
||||
Nothing ->
|
||||
Widget.modal
|
||||
{ onDismiss =
|
||||
Nothing
|
||||
|> onChangedSidebar
|
||||
|> Just
|
||||
, content = sheet
|
||||
}
|
||||
|
||||
else
|
||||
[]
|
||||
]
|
||||
)
|
@ -1,7 +1,6 @@
|
||||
module Widget.ScrollingNav exposing
|
||||
( Model, Msg, init, update, subscriptions, view, viewSections, current
|
||||
, jumpTo, syncPositions
|
||||
, jumpToWithOffset
|
||||
( ScrollingNav, init, view, current, toSelect
|
||||
, jumpTo, jumpToWithOffset, syncPositions, getPos, setPos
|
||||
)
|
||||
|
||||
{-| The Scrolling Nav is a navigation bar thats updates while you scroll through
|
||||
@ -10,51 +9,45 @@ the page. Clicking on a navigation button will scroll directly to that section.
|
||||
|
||||
# Basics
|
||||
|
||||
@docs Model, Msg, init, update, subscriptions, view, viewSections, current
|
||||
@docs ScrollingNav, init, view, current, toSelect
|
||||
|
||||
|
||||
# Operations
|
||||
|
||||
@docs jumpTo, syncPositions
|
||||
@docs jumpTo, jumpToWithOffset, syncPositions, getPos, setPos
|
||||
|
||||
-}
|
||||
|
||||
import Browser.Dom as Dom
|
||||
import Element exposing (Attribute, Element)
|
||||
import Framework.Grid as Grid
|
||||
import Element exposing (Element)
|
||||
import Html.Attributes as Attributes
|
||||
import IntDict exposing (IntDict)
|
||||
import Task
|
||||
import Time
|
||||
import Task exposing (Task)
|
||||
import Widget exposing (Select)
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Model section =
|
||||
{ labels : section -> String
|
||||
type alias ScrollingNav section =
|
||||
{ toString : section -> String
|
||||
, fromString : String -> Maybe section
|
||||
, positions : IntDict String
|
||||
, arrangement : List section
|
||||
, scrollPos : Int
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type Msg section
|
||||
= GotHeaderPos section (Result Dom.Error Int)
|
||||
| ChangedViewport (Result Dom.Error ())
|
||||
| SyncPosition Int
|
||||
| JumpTo section
|
||||
| TimePassed
|
||||
|
||||
|
||||
{-| The intial state include the labels and the arrangement of the sections
|
||||
-}
|
||||
init :
|
||||
{ labels : section -> String
|
||||
{ toString : section -> String
|
||||
, fromString : String -> Maybe section
|
||||
, arrangement : List section
|
||||
, toMsg : Result Dom.Error (ScrollingNav section -> ScrollingNav section) -> msg
|
||||
}
|
||||
-> ( Model section, Cmd (Msg section) )
|
||||
init { labels, arrangement } =
|
||||
{ labels = labels
|
||||
-> ( ScrollingNav section, Cmd msg )
|
||||
init { toString, fromString, arrangement, toMsg } =
|
||||
{ toString = toString
|
||||
, fromString = fromString
|
||||
, positions = IntDict.empty
|
||||
, arrangement = arrangement
|
||||
, scrollPos = 0
|
||||
@ -62,101 +55,102 @@ init { labels, arrangement } =
|
||||
|> (\a ->
|
||||
( a
|
||||
, syncPositions a
|
||||
|> Task.attempt toMsg
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
update : Msg section -> Model section -> ( Model section, Cmd (Msg section) )
|
||||
update msg model =
|
||||
case msg of
|
||||
GotHeaderPos label result ->
|
||||
( case result of
|
||||
Ok pos ->
|
||||
{ model
|
||||
| positions =
|
||||
model.positions
|
||||
|> IntDict.insert pos
|
||||
(label |> model.labels)
|
||||
}
|
||||
|
||||
Err _ ->
|
||||
model
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ChangedViewport _ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SyncPosition pos ->
|
||||
( { model
|
||||
| scrollPos = pos
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
TimePassed ->
|
||||
( model
|
||||
, Dom.getViewport
|
||||
|> Task.map (.viewport >> .y >> round)
|
||||
|> Task.perform SyncPosition
|
||||
)
|
||||
|
||||
JumpTo elem ->
|
||||
( model
|
||||
, model
|
||||
|> jumpTo elem
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
subscriptions : Sub (Msg msg)
|
||||
subscriptions =
|
||||
Time.every 100 (always TimePassed)
|
||||
|
||||
|
||||
{-| scrolls the screen to the respective section
|
||||
{-| Syncs the position of of the viewport
|
||||
-}
|
||||
jumpTo : section -> Model section -> Cmd (Msg msg)
|
||||
jumpTo section { labels } =
|
||||
Dom.getElement (section |> labels)
|
||||
getPos : Task x (ScrollingNav selection -> ScrollingNav selection)
|
||||
getPos =
|
||||
Dom.getViewport
|
||||
|> Task.map
|
||||
(\int model ->
|
||||
{ model
|
||||
| scrollPos = int.viewport.y |> round
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| sets the position of the viewport to show a specific section
|
||||
-}
|
||||
setPos : Int -> ScrollingNav section -> ScrollingNav section
|
||||
setPos pos model =
|
||||
{ model | scrollPos = pos }
|
||||
|
||||
|
||||
{-| Scrolls the screen to the respective section
|
||||
-}
|
||||
jumpTo :
|
||||
{ section : section
|
||||
, onChange : Result Dom.Error () -> msg
|
||||
}
|
||||
-> ScrollingNav section
|
||||
-> Cmd msg
|
||||
jumpTo { section, onChange } { toString } =
|
||||
Dom.getElement (section |> toString)
|
||||
|> Task.andThen
|
||||
(\{ element } ->
|
||||
Dom.setViewport 0 (element.y)
|
||||
Dom.setViewport 0 element.y
|
||||
)
|
||||
|> Task.attempt ChangedViewport
|
||||
|> Task.attempt onChange
|
||||
|
||||
{-| scrolls the screen to the respective section with some offset
|
||||
|
||||
{-| Scrolls the screen to the respective section with some offset
|
||||
-}
|
||||
jumpToWithOffset : Float -> section -> Model section -> Cmd (Msg msg)
|
||||
jumpToWithOffset offset section { labels } =
|
||||
Dom.getElement (section |> labels)
|
||||
jumpToWithOffset :
|
||||
{ offset : Float
|
||||
, section : section
|
||||
, onChange : Result Dom.Error () -> msg
|
||||
}
|
||||
-> ScrollingNav section
|
||||
-> Cmd msg
|
||||
jumpToWithOffset { offset, section, onChange } { toString } =
|
||||
Dom.getElement (section |> toString)
|
||||
|> Task.andThen
|
||||
(\{ element } ->
|
||||
Dom.setViewport 0 (element.y - offset)
|
||||
)
|
||||
|> Task.attempt ChangedViewport
|
||||
|> Task.attempt onChange
|
||||
|
||||
{-| -}
|
||||
syncPositions : Model section -> Cmd (Msg section)
|
||||
syncPositions { labels, arrangement } =
|
||||
|
||||
{-| Updates the positions of all sections.
|
||||
This functions should be called regularly if the height of elements on your page can change during time.
|
||||
-}
|
||||
syncPositions : ScrollingNav section -> Task Dom.Error (ScrollingNav section -> ScrollingNav section)
|
||||
syncPositions { toString, arrangement } =
|
||||
arrangement
|
||||
|> List.map
|
||||
(\label ->
|
||||
Dom.getElement (labels label)
|
||||
Dom.getElement (toString label)
|
||||
|> Task.map
|
||||
(.element
|
||||
>> .y
|
||||
>> round
|
||||
(\x ->
|
||||
( x.element.y |> round
|
||||
, label
|
||||
)
|
||||
)
|
||||
|> Task.attempt
|
||||
(GotHeaderPos label)
|
||||
)
|
||||
|> Cmd.batch
|
||||
|> Task.sequence
|
||||
|> Task.map
|
||||
(\list m ->
|
||||
list
|
||||
|> List.foldl
|
||||
(\( pos, label ) model ->
|
||||
{ model
|
||||
| positions =
|
||||
model.positions
|
||||
|> IntDict.insert pos
|
||||
(label |> model.toString)
|
||||
}
|
||||
)
|
||||
m
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
current : (String -> Maybe section) -> Model section -> Maybe section
|
||||
{-| Returns the current section
|
||||
-}
|
||||
current : (String -> Maybe section) -> ScrollingNav section -> Maybe section
|
||||
current fromString { positions, scrollPos } =
|
||||
positions
|
||||
|> IntDict.before (scrollPos + 1)
|
||||
@ -166,42 +160,72 @@ current fromString { positions, scrollPos } =
|
||||
|> Maybe.andThen fromString
|
||||
|
||||
|
||||
{-| -}
|
||||
viewSections :
|
||||
{ label : String -> Element msg
|
||||
, fromString : String -> Maybe section
|
||||
, msgMapper : Msg section -> msg
|
||||
, attributes : Bool -> List (Attribute msg)
|
||||
}
|
||||
-> Model section
|
||||
->
|
||||
{ selected : Maybe section
|
||||
, options : List section
|
||||
, label : section -> Element msg
|
||||
, onChange : section -> msg
|
||||
, attributes : Bool -> List (Attribute msg)
|
||||
}
|
||||
viewSections { label, fromString, msgMapper, attributes } ({ arrangement, scrollPos, labels, positions } as model) =
|
||||
{ selected = model |> current fromString
|
||||
, options = arrangement
|
||||
, label = \elem -> label (elem |> labels)
|
||||
, onChange = JumpTo >> msgMapper
|
||||
, attributes = attributes
|
||||
{-| Returns a select widget containing all section, with the current section selected.
|
||||
-}
|
||||
toSelect : (Int -> Maybe msg) -> ScrollingNav section -> Select msg
|
||||
toSelect onSelect ({ arrangement, toString, fromString } as model) =
|
||||
{ selected =
|
||||
arrangement
|
||||
|> List.indexedMap (\i s -> ( i, s ))
|
||||
|> List.filterMap
|
||||
(\( i, s ) ->
|
||||
if Just s == current fromString model then
|
||||
Just i
|
||||
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
|> List.head
|
||||
, options =
|
||||
arrangement
|
||||
|> List.map
|
||||
(\s ->
|
||||
{ text = toString s
|
||||
, icon = Element.none
|
||||
}
|
||||
)
|
||||
, onSelect = onSelect
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
{-| Opinionated way of viewing the section.
|
||||
|
||||
This might be useful at first, but you should consider writing your own view function.
|
||||
|
||||
```
|
||||
view :
|
||||
(section -> Element msg)
|
||||
-> Model section
|
||||
-> Element msg
|
||||
view asElement { labels, arrangement } =
|
||||
-> List (Element msg)
|
||||
view asElement { toString, arrangement } =
|
||||
arrangement
|
||||
|> List.map
|
||||
(\header ->
|
||||
Element.el
|
||||
[ header
|
||||
|> labels
|
||||
|> toString
|
||||
|> Attributes.id
|
||||
|> Element.htmlAttribute
|
||||
, Element.width <| Element.fill
|
||||
]
|
||||
<|
|
||||
asElement <|
|
||||
header
|
||||
)
|
||||
```
|
||||
|
||||
-}
|
||||
view :
|
||||
(section -> Element msg)
|
||||
-> ScrollingNav section
|
||||
-> List (Element msg)
|
||||
view asElement { toString, arrangement } =
|
||||
arrangement
|
||||
|> List.map
|
||||
(\header ->
|
||||
Element.el
|
||||
[ header
|
||||
|> toString
|
||||
|> Attributes.id
|
||||
|> Element.htmlAttribute
|
||||
, Element.width <| Element.fill
|
||||
@ -210,4 +234,3 @@ view asElement { labels, arrangement } =
|
||||
asElement <|
|
||||
header
|
||||
)
|
||||
|> Element.column Grid.simple
|
||||
|
@ -1,14 +1,16 @@
|
||||
module Widget.Snackbar exposing
|
||||
( Model, init, current, timePassed
|
||||
( Snackbar, Message, init, current, timePassed, view
|
||||
, insert, insertFor, dismiss
|
||||
)
|
||||
|
||||
{-| A [snackbar](https://material.io/components/snackbars/) shows notification, one at a time.
|
||||
{-| ![Snackbar](https://orasund.github.io/elm-ui-widgets/assets/snackbar.png)
|
||||
|
||||
A [snackbar](https://material.io/components/snackbars/) shows notification, one at a time.
|
||||
|
||||
|
||||
# Basics
|
||||
|
||||
@docs Model, init, current, timePassed
|
||||
@docs Snackbar, Message, init, current, timePassed, view
|
||||
|
||||
|
||||
# Operations
|
||||
@ -17,12 +19,22 @@ module Widget.Snackbar exposing
|
||||
|
||||
-}
|
||||
|
||||
import Element exposing (Element)
|
||||
import Queue exposing (Queue)
|
||||
import Widget exposing (TextButton)
|
||||
import Widget.Style exposing (SnackbarStyle)
|
||||
|
||||
{-| A message with maybe some action button
|
||||
-}
|
||||
type alias Message msg =
|
||||
{ text : String
|
||||
, button : Maybe (TextButton msg)
|
||||
}
|
||||
|
||||
|
||||
{-| A snackbar has a queue of Notifications, each with the amount of ms the message should be displayed
|
||||
-}
|
||||
type alias Model a =
|
||||
type alias Snackbar a =
|
||||
{ queue : Queue ( a, Int )
|
||||
, current : Maybe ( a, Int )
|
||||
}
|
||||
@ -30,7 +42,7 @@ type alias Model a =
|
||||
|
||||
{-| Inital state
|
||||
-}
|
||||
init : Model a
|
||||
init : Snackbar a
|
||||
init =
|
||||
{ queue = Queue.empty
|
||||
, current = Nothing
|
||||
@ -39,14 +51,14 @@ init =
|
||||
|
||||
{-| Insert a message that will last for 10 seconds.
|
||||
-}
|
||||
insert : a -> Model a -> Model a
|
||||
insert : a -> Snackbar a -> Snackbar a
|
||||
insert =
|
||||
insertFor 10000
|
||||
|
||||
|
||||
{-| Insert a message for a specific amount of milli seconds.
|
||||
-}
|
||||
insertFor : Int -> a -> Model a -> Model a
|
||||
insertFor : Int -> a -> Snackbar a -> Snackbar a
|
||||
insertFor removeIn a model =
|
||||
case model.current of
|
||||
Nothing ->
|
||||
@ -58,7 +70,7 @@ insertFor removeIn a model =
|
||||
|
||||
{-| Dismiss the current message.
|
||||
-}
|
||||
dismiss : Model a -> Model a
|
||||
dismiss : Snackbar a -> Snackbar a
|
||||
dismiss model =
|
||||
{ model | current = Nothing }
|
||||
|
||||
@ -66,7 +78,7 @@ dismiss model =
|
||||
{-| Updates the model. This functions should be called regularly.
|
||||
The first argument is the milli seconds that passed since the last time the function was called.
|
||||
-}
|
||||
timePassed : Int -> Model a -> Model a
|
||||
timePassed : Int -> Snackbar a -> Snackbar a
|
||||
timePassed ms model =
|
||||
case model.current of
|
||||
Nothing ->
|
||||
@ -89,6 +101,33 @@ timePassed ms model =
|
||||
|
||||
{-| Returns the current element.
|
||||
-}
|
||||
current : Model a -> Maybe a
|
||||
current : Snackbar a -> Maybe a
|
||||
current model =
|
||||
model.current |> Maybe.map Tuple.first
|
||||
|
||||
|
||||
{-| Views the current Message. (only one at a time)
|
||||
-}
|
||||
view :
|
||||
SnackbarStyle msg
|
||||
-> (a -> Message msg)
|
||||
-> Snackbar a
|
||||
-> Maybe (Element msg)
|
||||
view style toMessage model =
|
||||
model
|
||||
|> current
|
||||
|> Maybe.map
|
||||
(toMessage
|
||||
>> (\{ text, button } ->
|
||||
[ text
|
||||
|> Element.text
|
||||
|> List.singleton
|
||||
|> Element.paragraph style.text
|
||||
, button
|
||||
|> Maybe.map
|
||||
(Widget.textButton style.button)
|
||||
|> Maybe.withDefault Element.none
|
||||
]
|
||||
|> Element.row style.containerRow
|
||||
)
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
)
|
||||
}
|
134
src/Widget/Style.elm
Normal file
134
src/Widget/Style.elm
Normal file
@ -0,0 +1,134 @@
|
||||
module Widget.Style exposing (ButtonStyle,ColumnStyle, DialogStyle, ExpansionPanelStyle, LayoutStyle, RowStyle, SnackbarStyle, SortTableStyle, TabStyle, TextInputStyle)
|
||||
|
||||
{-| This module contains style types for every widget.
|
||||
|
||||
@docs ButtonStyle, ColumnStyle, DialogStyle, ExpansionPanelStyle, LayoutStyle, RowStyle, SnackbarStyle, SortTableStyle, TabStyle, TextInputStyle
|
||||
|
||||
-}
|
||||
|
||||
import Element exposing (Attribute, Element)
|
||||
import Html exposing (Html)
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias ButtonStyle msg =
|
||||
{ container : List (Attribute msg)
|
||||
, labelRow : List (Attribute msg)
|
||||
, text : List (Attribute msg)
|
||||
, ifDisabled : List (Attribute msg)
|
||||
, ifActive : List (Attribute msg)
|
||||
, otherwise : List (Attribute msg)
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias DialogStyle msg =
|
||||
{ containerColumn : List (Attribute msg)
|
||||
, title : List (Attribute msg)
|
||||
, buttonRow : List (Attribute msg)
|
||||
, acceptButton : ButtonStyle msg
|
||||
, dismissButton : ButtonStyle msg
|
||||
, text : List (Attribute msg)
|
||||
}
|
||||
|
||||
|
||||
{-| Technical Remark:
|
||||
|
||||
- If icons are defined in Svg, they might not display correctly.
|
||||
To avoid that, make sure to wrap them in `Element.html >> Element.el []`
|
||||
|
||||
-}
|
||||
type alias ExpansionPanelStyle msg =
|
||||
{ containerColumn : List (Attribute msg)
|
||||
, panelRow : List (Attribute msg)
|
||||
, labelRow : List (Attribute msg)
|
||||
, content : List (Attribute msg)
|
||||
, expandIcon : Element Never
|
||||
, collapseIcon : Element Never
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias SnackbarStyle msg =
|
||||
{ containerRow : List (Attribute msg)
|
||||
, text : List (Attribute msg)
|
||||
, button : ButtonStyle msg
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias TextInputStyle msg =
|
||||
{ chipButton : ButtonStyle msg
|
||||
, containerRow : List (Attribute msg)
|
||||
, chipsRow : List (Attribute msg)
|
||||
, input : List (Attribute msg)
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias TabStyle msg =
|
||||
{ button : ButtonStyle msg
|
||||
, optionRow : List (Attribute msg)
|
||||
, containerColumn : List (Attribute msg)
|
||||
, content : List (Attribute msg)
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias RowStyle msg =
|
||||
{ containerRow : List (Attribute msg)
|
||||
, element : List (Attribute msg)
|
||||
, ifFirst : List (Attribute msg)
|
||||
, ifLast : List (Attribute msg)
|
||||
, otherwise : List (Attribute msg)
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias ColumnStyle msg =
|
||||
{ containerColumn : List (Attribute msg)
|
||||
, element : List (Attribute msg)
|
||||
, ifFirst : List (Attribute msg)
|
||||
, ifLast : List (Attribute msg)
|
||||
, otherwise : List (Attribute msg)
|
||||
}
|
||||
|
||||
|
||||
{-| Technical Remark:
|
||||
|
||||
- If icons are defined in Svg, they might not display correctly.
|
||||
To avoid that, make sure to wrap them in `Element.html >> Element.el []`
|
||||
|
||||
-}
|
||||
type alias SortTableStyle msg =
|
||||
{ containerTable : List (Attribute msg)
|
||||
, headerButton : ButtonStyle msg
|
||||
, ascIcon : Element Never
|
||||
, descIcon : Element Never
|
||||
, defaultIcon : Element Never
|
||||
}
|
||||
|
||||
|
||||
{-| Technical Remark:
|
||||
|
||||
- If icons are defined in Svg, they might not display correctly.
|
||||
To avoid that, make sure to wrap them in `Element.html >> Element.el []`
|
||||
|
||||
-}
|
||||
type alias LayoutStyle msg =
|
||||
{ container : List (Attribute msg)
|
||||
, snackbar : SnackbarStyle msg
|
||||
, layout : List (Attribute msg) -> Element msg -> Html msg
|
||||
, header : List (Attribute msg)
|
||||
, sheet : List (Attribute msg)
|
||||
, sheetButton : ButtonStyle msg
|
||||
, menuButton : ButtonStyle msg
|
||||
, menuTabButton : ButtonStyle msg
|
||||
, menuIcon : Element Never
|
||||
, moreVerticalIcon : Element Never
|
||||
, spacing : Int
|
||||
, title : List (Attribute msg)
|
||||
, searchIcon : Element Never
|
||||
, search : List (Attribute msg)
|
||||
, searchFill : List (Attribute msg)
|
||||
}
|
1569
src/Widget/Style/Material.elm
Normal file
1569
src/Widget/Style/Material.elm
Normal file
File diff suppressed because it is too large
Load Diff
383
src/Widget/Style/Template.elm
Normal file
383
src/Widget/Style/Template.elm
Normal file
@ -0,0 +1,383 @@
|
||||
module Widget.Style.Template exposing
|
||||
( box, decoration, icon
|
||||
, button, column, dialog, expansionPanel, layout, row, snackbar, sortTable, tab, textInput
|
||||
)
|
||||
|
||||
{-| ![Example using the Template style](https://orasund.github.io/elm-ui-widgets/assets/template-style.png)
|
||||
|
||||
This package contains mockups designed for writing your own style.
|
||||
|
||||
Start by copying the following code and then replace the fields one by one.
|
||||
|
||||
```
|
||||
type alias Style msg =
|
||||
{ dialog : DialogStyle msg
|
||||
, expansionPanel : ExpansionPanelStyle msg
|
||||
, button : ButtonStyle msg
|
||||
, primaryButton : ButtonStyle msg
|
||||
, tab : TabStyle msg
|
||||
, textInput : TextInputStyle msg
|
||||
, chipButton : ButtonStyle msg
|
||||
, row : RowStyle msg
|
||||
, buttonRow : RowStyle msg
|
||||
, column : ColumnStyle msg
|
||||
, cardColumn : ColumnStyle msg
|
||||
, sortTable : SortTableStyle msg
|
||||
, selectButton : ButtonStyle msg
|
||||
, layout : LayoutStyle msg
|
||||
}
|
||||
|
||||
style : Style msg
|
||||
style =
|
||||
{ sortTable = Template.sortTable <| "sortTable"
|
||||
, row = Template.row <| "row"
|
||||
, buttonRow = Template.row <| "buttonRow"
|
||||
, cardColumn = Template.column <| "cardRow"
|
||||
, column = Template.column <| "column"
|
||||
, button = Template.button <| "button"
|
||||
, primaryButton = Template.button <| "primaryButton"
|
||||
, tab = Template.tab <| "tab"
|
||||
, textInput = Template.textInput <| "textInput"
|
||||
, chipButton = Template.button <| "chipButton"
|
||||
, expansionPanel = Template.expansionPanel "expansionPanel"
|
||||
, selectButton = Template.button "selectButton"
|
||||
, dialog = Template.dialog "dialog"
|
||||
, layout = Template.layout "layout"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Base Elements
|
||||
|
||||
@docs box, decoration, icon
|
||||
|
||||
|
||||
# Mockups
|
||||
|
||||
@docs button, column, dialog, expansionPanel, layout, row, snackbar, sortTable, tab, textInput
|
||||
|
||||
-}
|
||||
|
||||
import Element exposing (Attribute, Element)
|
||||
import Element.Background as Background
|
||||
import Element.Border as Border
|
||||
import Element.Font as Font
|
||||
import Widget.Style
|
||||
exposing
|
||||
( ButtonStyle
|
||||
, ColumnStyle
|
||||
, DialogStyle
|
||||
, ExpansionPanelStyle
|
||||
, LayoutStyle
|
||||
, RowStyle
|
||||
, SnackbarStyle
|
||||
, SortTableStyle
|
||||
, TabStyle
|
||||
, TextInputStyle
|
||||
)
|
||||
|
||||
|
||||
fontSize : Int
|
||||
fontSize =
|
||||
10
|
||||
|
||||
|
||||
{-| A box representing an element
|
||||
-}
|
||||
box : String -> List (Attribute msg)
|
||||
box string =
|
||||
[ Border.width 1
|
||||
, Background.color <| Element.rgba 1 1 1 0.5
|
||||
, Element.padding 10
|
||||
, Element.spacing 10
|
||||
, Element.above <|
|
||||
Element.el [ Font.size <| fontSize ] <|
|
||||
Element.text string
|
||||
]
|
||||
|
||||
|
||||
{-| An additional attribute representing a state change.
|
||||
-}
|
||||
decoration : String -> List (Attribute msg)
|
||||
decoration string =
|
||||
[ Element.below <|
|
||||
Element.el [ Font.size <| fontSize ] <|
|
||||
Element.text string
|
||||
, Background.color <| Element.rgb 0.66 0.66 0.66
|
||||
]
|
||||
|
||||
|
||||
{-| A circle representing an icon
|
||||
-}
|
||||
icon : String -> Element msg
|
||||
icon string =
|
||||
Element.none
|
||||
|> Element.el
|
||||
[ Element.width <| Element.px 12
|
||||
, Element.height <| Element.px 12
|
||||
, Border.rounded 6
|
||||
, Border.width 1
|
||||
, Element.above <|
|
||||
Element.el [ Font.size <| fontSize ] <|
|
||||
Element.text string
|
||||
]
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
button : String -> ButtonStyle msg
|
||||
button string =
|
||||
{ container = box <| string ++ ":container"
|
||||
, labelRow = box <| string ++ ":labelRow"
|
||||
, text = box <| string ++ ":text"
|
||||
, ifDisabled = decoration <| string ++ ":ifDisabled"
|
||||
, ifActive = decoration <| string ++ ":ifActive"
|
||||
, otherwise = decoration <| string ++ ":otherwise"
|
||||
}
|
||||
```
|
||||
|
||||
-}
|
||||
button : String -> ButtonStyle msg
|
||||
button string =
|
||||
{ container = box <| string ++ ":container"
|
||||
, labelRow = box <| string ++ ":labelRow"
|
||||
, text = box <| string ++ ":text"
|
||||
, ifDisabled = decoration <| string ++ ":ifDisabled"
|
||||
, ifActive = decoration <| string ++ ":ifActive"
|
||||
, otherwise = decoration <| string ++ ":otherwise"
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
snackbar : String -> SnackbarStyle msg
|
||||
snackbar string =
|
||||
{ containerRow = box <| string ++ ":containerRow"
|
||||
, button = button <| string ++ ":button"
|
||||
, text = box <| string ++ ":text"
|
||||
}
|
||||
```
|
||||
|
||||
-}
|
||||
snackbar : String -> SnackbarStyle msg
|
||||
snackbar string =
|
||||
{ containerRow = box <| string ++ ":containerRow"
|
||||
, button = button <| string ++ ":button"
|
||||
, text = box <| string ++ ":text"
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
dialog : String -> DialogStyle msg
|
||||
dialog string =
|
||||
{ containerColumn = box <| string ++ ":containerColumn"
|
||||
, title = box <| string ++ ":title"
|
||||
, text = box <| string ++ ":text"
|
||||
, buttonRow = box <| string ++ ":buttonRow"
|
||||
, acceptButton = button <| string ++ ":acceptButton"
|
||||
, dismissButton = button <| string ++ ":dismissButton"
|
||||
}
|
||||
```
|
||||
|
||||
-}
|
||||
dialog : String -> DialogStyle msg
|
||||
dialog string =
|
||||
{ containerColumn = box <| string ++ ":containerColumn"
|
||||
, title = box <| string ++ ":title"
|
||||
, text = box <| string ++ ":text"
|
||||
, buttonRow = box <| string ++ ":buttonRow"
|
||||
, acceptButton = button <| string ++ ":acceptButton"
|
||||
, dismissButton = button <| string ++ ":dismissButton"
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
expansionPanel : String -> ExpansionPanelStyle msg
|
||||
expansionPanel string =
|
||||
{ containerColumn = box <| string ++ ":containerColumn"
|
||||
, panelRow = box <| string ++ ":panelRow"
|
||||
, labelRow = box <| string ++ ":labelRow"
|
||||
, content = box <| string ++ ":content"
|
||||
, expandIcon = icon <| string ++ ":expandIcon"
|
||||
, collapseIcon = icon <| string ++ ":collapseIcon"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
-}
|
||||
expansionPanel : String -> ExpansionPanelStyle msg
|
||||
expansionPanel string =
|
||||
{ containerColumn = box <| string ++ ":containerColumn"
|
||||
, panelRow = box <| string ++ ":panelRow"
|
||||
, labelRow = box <| string ++ ":labelRow"
|
||||
, content = box <| string ++ ":content"
|
||||
, expandIcon = icon <| string ++ ":expandIcon"
|
||||
, collapseIcon = icon <| string ++ ":collapseIcon"
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
textInput : String -> TextInputStyle msg
|
||||
textInput string =
|
||||
{ chipButton = button <| string ++ ":chipButton"
|
||||
, chipsRow = box <| string ++ ":chipsRow"
|
||||
, containerRow = box <| string ++ ":containerRow"
|
||||
, input = box <| string ++ ":input"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
-}
|
||||
textInput : String -> TextInputStyle msg
|
||||
textInput string =
|
||||
{ chipButton = button <| string ++ ":chipButton"
|
||||
, chipsRow = box <| string ++ ":chipsRow"
|
||||
, containerRow = box <| string ++ ":containerRow"
|
||||
, input = box <| string ++ ":input"
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
tab : String -> TabStyle msg
|
||||
tab string =
|
||||
{ button = button <| string ++ ":button"
|
||||
, optionRow = box <| string ++ ":optionRow"
|
||||
, containerColumn = box <| string ++ ":containerColumn"
|
||||
, content = box <| string ++ ":content"
|
||||
}
|
||||
```
|
||||
|
||||
-}
|
||||
tab : String -> TabStyle msg
|
||||
tab string =
|
||||
{ button = button <| string ++ ":button"
|
||||
, optionRow = box <| string ++ ":optionRow"
|
||||
, containerColumn = box <| string ++ ":containerColumn"
|
||||
, content = box <| string ++ ":content"
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
row : String -> RowStyle msg
|
||||
row string =
|
||||
{ containerRow = box <| string ++ ":containerRow"
|
||||
, element = box <| string ++ ":element"
|
||||
, ifFirst = decoration <| string ++ ":ifFirst"
|
||||
, ifLast = decoration <| string ++ ":ifLast"
|
||||
, otherwise = decoration <| string ++ ":otherwise"
|
||||
}
|
||||
```
|
||||
|
||||
-}
|
||||
row : String -> RowStyle msg
|
||||
row string =
|
||||
{ containerRow = box <| string ++ ":containerRow"
|
||||
, element = box <| string ++ ":element"
|
||||
, ifFirst = decoration <| string ++ ":ifFirst"
|
||||
, ifLast = decoration <| string ++ ":ifLast"
|
||||
, otherwise = decoration <| string ++ ":otherwise"
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
column : String -> ColumnStyle msg
|
||||
column string =
|
||||
{ containerColumn = box <| string ++ ":containerColumn"
|
||||
, element = box <| string ++ ":element"
|
||||
, ifFirst = decoration <| string ++ ":ifFirst"
|
||||
, ifLast = decoration <| string ++ ":ifLast"
|
||||
, otherwise = decoration <| string ++ ":otherwise"
|
||||
}
|
||||
```
|
||||
|
||||
-}
|
||||
column : String -> ColumnStyle msg
|
||||
column string =
|
||||
{ containerColumn = box <| string ++ ":containerColumn"
|
||||
, element = box <| string ++ ":element"
|
||||
, ifFirst = decoration <| string ++ ":ifFirst"
|
||||
, ifLast = decoration <| string ++ ":ifLast"
|
||||
, otherwise = decoration <| string ++ ":otherwise"
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
sortTable : String -> SortTableStyle msg
|
||||
sortTable string =
|
||||
{ containerTable = box <| string ++ ":containerTable"
|
||||
, headerButton = button <| string ++ ":headerButton"
|
||||
, ascIcon = icon <| string ++ ":ascIcon"
|
||||
, descIcon = icon <| string ++ ":descIcon"
|
||||
, defaultIcon = icon <| string ++ ":defaultIcon"
|
||||
}
|
||||
```
|
||||
|
||||
-}
|
||||
sortTable : String -> SortTableStyle msg
|
||||
sortTable string =
|
||||
{ containerTable = box <| string ++ ":containerTable"
|
||||
, headerButton = button <| string ++ ":headerButton"
|
||||
, ascIcon = icon <| string ++ ":ascIcon"
|
||||
, descIcon = icon <| string ++ ":descIcon"
|
||||
, defaultIcon = icon <| string ++ ":defaultIcon"
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
```
|
||||
layout : String -> LayoutStyle msg
|
||||
layout string =
|
||||
{ container = box <| string ++ ":container"
|
||||
, snackbar = snackbar <| string ++ ":snackbar"
|
||||
, layout = Element.layout
|
||||
, header = box <| string ++ ":header"
|
||||
, menuButton = button <| string ++ ":menuButton"
|
||||
, sheetButton = button <| string ++ ":sheetButton"
|
||||
, menuTabButton = button <| string ++ ":menuTabButton"
|
||||
, sheet = box <| string ++ ":sheet"
|
||||
, menuIcon = icon <| string ++ ":menuIcon"
|
||||
, moreVerticalIcon = icon <| string ++ ":moreVerticalIcon"
|
||||
, spacing = 8
|
||||
, title = box <| string ++ ":title"
|
||||
, searchIcon = icon <| string ++ ":searchIcon"
|
||||
, search = box <| string ++ ":search"
|
||||
, searchFill = box <| string ++ ":searchFill"
|
||||
}
|
||||
```
|
||||
|
||||
-}
|
||||
layout : String -> LayoutStyle msg
|
||||
layout string =
|
||||
{ container = box <| string ++ ":container"
|
||||
, snackbar = snackbar <| string ++ ":snackbar"
|
||||
, layout = Element.layout
|
||||
, header = box <| string ++ ":header"
|
||||
, menuButton = button <| string ++ ":menuButton"
|
||||
, sheetButton = button <| string ++ ":sheetButton"
|
||||
, menuTabButton = button <| string ++ ":menuTabButton"
|
||||
, sheet = box <| string ++ ":sheet"
|
||||
, menuIcon = icon <| string ++ ":menuIcon"
|
||||
, moreVerticalIcon = icon <| string ++ ":moreVerticalIcon"
|
||||
, spacing = 8
|
||||
, title = box <| string ++ ":title"
|
||||
, searchIcon = icon <| string ++ ":searchIcon"
|
||||
, search = box <| string ++ ":search"
|
||||
, searchFill = box <| string ++ ":searchFill"
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
module Widget.ValidatedInput exposing
|
||||
( Model, Msg, init, update, view
|
||||
, getError, getRaw, getValue
|
||||
)
|
||||
|
||||
{-| The validated Input is a wrapper around `Input.text`.
|
||||
They can validate the input and return an error if nessarry.
|
||||
|
||||
|
||||
# Basics
|
||||
|
||||
@docs Model, Msg, init, update, view
|
||||
|
||||
|
||||
# Access the Model
|
||||
|
||||
@docs getError, getRaw, getValue
|
||||
|
||||
-}
|
||||
|
||||
import Element exposing (Attribute, Element)
|
||||
import Element.Events as Events
|
||||
import Element.Input as Input exposing (Placeholder)
|
||||
|
||||
|
||||
{-| -}
|
||||
type Model err a
|
||||
= Model
|
||||
{ raw : Maybe String
|
||||
, value : a
|
||||
, err : Maybe err
|
||||
, validator : String -> Result err a
|
||||
, toString : a -> String
|
||||
}
|
||||
|
||||
|
||||
{-| returns the raw value (the value that the user currently sees)
|
||||
-}
|
||||
getRaw : Model err a -> String
|
||||
getRaw (Model { raw, value, toString }) =
|
||||
case raw of
|
||||
Just string ->
|
||||
string
|
||||
|
||||
Nothing ->
|
||||
value |> toString
|
||||
|
||||
|
||||
{-| returns the value (the value that has been last successfully validated)
|
||||
-}
|
||||
getValue : Model err a -> a
|
||||
getValue (Model { value }) =
|
||||
value
|
||||
|
||||
|
||||
{-| returns the error (if one exists)
|
||||
-}
|
||||
getError : Model err a -> Maybe err
|
||||
getError (Model { err }) =
|
||||
err
|
||||
|
||||
|
||||
{-| -}
|
||||
type Msg
|
||||
= ChangedRaw String
|
||||
| LostFocus
|
||||
| StartEditing
|
||||
|
||||
|
||||
{-| The initial state contains
|
||||
|
||||
- `value`: starting value
|
||||
- `validator`: a vaidation function (a decoder)
|
||||
- `toString`: a function that returns a string representation
|
||||
|
||||
-}
|
||||
init : { value : a, validator : String -> Result err a, toString : a -> String } -> Model err a
|
||||
init { validator, toString, value } =
|
||||
Model
|
||||
{ raw = Nothing
|
||||
, value = value
|
||||
, err = Nothing
|
||||
, validator = validator
|
||||
, toString = toString
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
update : Msg -> Model err a -> Model err a
|
||||
update msg (Model model) =
|
||||
case msg of
|
||||
StartEditing ->
|
||||
Model
|
||||
{ model
|
||||
| raw = model.value |> model.toString |> Just
|
||||
}
|
||||
|
||||
ChangedRaw string ->
|
||||
Model
|
||||
{ model
|
||||
| raw = Just string
|
||||
, err = Nothing
|
||||
}
|
||||
|
||||
LostFocus ->
|
||||
case model.raw of
|
||||
Just string ->
|
||||
case model.validator string of
|
||||
Ok value ->
|
||||
Model
|
||||
{ model
|
||||
| value = value
|
||||
, raw = Nothing
|
||||
, err = Nothing
|
||||
}
|
||||
|
||||
Err err ->
|
||||
Model
|
||||
{ model
|
||||
| raw = Nothing
|
||||
, err = Just err
|
||||
}
|
||||
|
||||
Nothing ->
|
||||
Model model
|
||||
|
||||
|
||||
{-| the view function, the parameters include
|
||||
|
||||
- `msgMapper`: A function wrapping the `Msg` into a `msg`
|
||||
- `placeholder`: See Element.text for more information
|
||||
- `label`: The (hidden) label of the input (needed for screen readers)
|
||||
- `readOnly`: a representation of the validated value
|
||||
(clicking on the element will turn on edit mode)
|
||||
|
||||
-}
|
||||
view :
|
||||
List (Attribute msg)
|
||||
-> Model err a
|
||||
->
|
||||
{ msgMapper : Msg -> msg
|
||||
, placeholder : Maybe (Placeholder msg)
|
||||
, label : String
|
||||
, readOnly : a -> Element msg
|
||||
}
|
||||
-> Element msg
|
||||
view attributes (Model model) { msgMapper, placeholder, label, readOnly } =
|
||||
case model.raw of
|
||||
Just string ->
|
||||
Input.text (attributes ++ [ Events.onLoseFocus <| msgMapper <| LostFocus ])
|
||||
{ onChange = ChangedRaw >> msgMapper
|
||||
, text = string
|
||||
, placeholder = placeholder
|
||||
, label = Input.labelHidden label
|
||||
}
|
||||
|
||||
Nothing ->
|
||||
Input.button []
|
||||
{ onPress = Just (StartEditing |> msgMapper)
|
||||
, label = model.value |> readOnly
|
||||
}
|
Loading…
Reference in New Issue
Block a user