Merge branch 'unstable'

This commit is contained in:
Unknown 2020-05-25 14:09:30 +02:00
commit d076c94886
52 changed files with 13896 additions and 25918 deletions

154
README.md
View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 933 B

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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": {

View File

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

View 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)

View File

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

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

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

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

View 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"
}

View 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

View File

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

@ -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
View 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
)
]

View File

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

View File

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

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

View 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
}
]

View File

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

View File

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

View File

@ -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
View 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
[]
]
)

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View 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"
}

View File

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