diff --git a/docs/unstable/index.html b/docs/unstable/index.html
new file mode 100644
index 0000000..7346cb7
--- /dev/null
+++ b/docs/unstable/index.html
@@ -0,0 +1,17994 @@
+
+
+
+
+ Example
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/src/Component.elm b/example/src/Component.elm
index ea6dab8..f1a527f 100644
--- a/example/src/Component.elm
+++ b/example/src/Component.elm
@@ -4,6 +4,8 @@ import Browser
import Element exposing (Color, Element)
import Element.Background as Background
import Element.Input as Input
+import Element.Border as Border
+import Element.Font as Font
import Framework
import Framework.Button as Button
import Framework.Card as Card
@@ -19,22 +21,32 @@ import Html.Attributes as Attributes
import Set exposing (Set)
import Time
import Widget
+import Widget.Button as Button exposing (ButtonStyle)
import Widget.FilterSelect as FilterSelect
+import Widget.FilterMultiSelect as FilterMultiSelect
import Widget.ScrollingNav as ScrollingNav
import Widget.Snackbar as Snackbar
import Widget.ValidatedInput as ValidatedInput
-
type alias Model =
{ filterSelect : FilterSelect.Model
+ , filterMultiSelect : FilterMultiSelect.Model
, validatedInput : ValidatedInput.Model () ( String, String )
}
type Msg
= FilterSelectSpecific FilterSelect.Msg
+ | FilterMultiSelectSpecific FilterMultiSelect.Msg
| ValidatedInputSpecific ValidatedInput.Msg
+chipButton : ButtonStyle msg
+chipButton =
+ { container = Tag.simple
+ , disabled = []
+ , label = Grid.simple
+ , active = Color.primary
+ }
init : Model
init =
@@ -56,6 +68,24 @@ init =
]
|> Set.fromList
|> FilterSelect.init
+ , filterMultiSelect =
+ [ "Apple"
+ , "Kiwi"
+ , "Strawberry"
+ , "Pineapple"
+ , "Mango"
+ , "Grapes"
+ , "Watermelon"
+ , "Orange"
+ , "Lemon"
+ , "Blueberry"
+ , "Grapefruit"
+ , "Coconut"
+ , "Cherry"
+ , "Banana"
+ ]
+ |> Set.fromList
+ |> FilterMultiSelect.init
, validatedInput =
ValidatedInput.init
{ value = ( "John", "Doe" )
@@ -83,6 +113,13 @@ update msg model =
, Cmd.none
)
+ FilterMultiSelectSpecific m ->
+ ( { model
+ | filterMultiSelect = model.filterMultiSelect |> FilterMultiSelect.update m
+ }
+ , Cmd.none
+ )
+
ValidatedInputSpecific m ->
( { model
| validatedInput = model.validatedInput |> ValidatedInput.update m
@@ -129,6 +166,55 @@ filterSelect model =
]
)
+filterMultiSelect : FilterMultiSelect.Model -> (String,Element Msg)
+filterMultiSelect model =
+ ( "Filter Multi Select"
+ , [ FilterMultiSelect.viewInput model
+ { msgMapper = FilterMultiSelectSpecific
+ , placeholder =
+ Just <|
+ Input.placeholder [] <|
+ Element.text <|
+ "Fruit"
+ , label = "Fruit"
+ , toChip = \string ->
+ { text = string
+ , onPress = Just <| FilterMultiSelectSpecific <| FilterMultiSelect.ToggleSelected <| string
+ , icon = Element.none
+ }
+ }
+ |> Widget.textInput
+ { chip = chipButton
+ , chipsRow =
+ [ Element.width <| Element.shrink
+ , Element.spacing <| 4 ]
+ , containerRow =
+ Button.simple
+ ++ Color.light
+ ++ [ Border.color <| Element.rgb255 186 189 182
+ , Font.alignLeft
+ , Element.padding 8
+ , Element.height <| Element.px <|42
+ ]
+ ++ Grid.simple
+ , input =
+ Color.light
+ ++ [Element.padding 0]
+ }
+
+ , model
+ |> FilterMultiSelect.viewOptions
+ |> List.map
+ (\string ->
+ Input.button (Button.simple ++ Tag.simple)
+ { onPress = Just <| FilterMultiSelectSpecific <| FilterMultiSelect.ToggleSelected <| string
+ , label = Element.text string
+ }
+ )
+ |> Element.wrappedRow [ Element.spacing 10 ]
+ ]
+ |> Element.column Grid.simple
+ )
validatedInput : ValidatedInput.Model () ( String, String ) -> (String,Element Msg)
validatedInput model =
@@ -162,6 +248,7 @@ view msgMapper model =
, description = "Components have a Model, an Update- and sometimes even a Subscription-function. It takes some time to set them up correctly."
, items =
[ filterSelect model.filterSelect
+ , filterMultiSelect model.filterMultiSelect
, validatedInput model.validatedInput
]
|> List.map (Tuple.mapSecond (Element.map msgMapper) )
diff --git a/example/src/Example.elm b/example/src/Example.elm
index 332b723..0a0b58a 100644
--- a/example/src/Example.elm
+++ b/example/src/Example.elm
@@ -127,7 +127,8 @@ style =
Card.simple
++ Color.dark
++ Grid.simple
- ++ [ Element.paddingXY 8 6]
+ ++ [ Element.paddingXY 8 6
+ , Element.height <| Element.px <|54]
, button =
{ label = Grid.simple
, container = Button.simple ++ Color.dark
@@ -137,6 +138,23 @@ style =
, text = [Element.paddingXY 8 0]
}
, layout = Framework.responsiveLayout
+ {--\a w ->
+ Html.div []
+ [ Html.node "meta"
+ [ Attributes.attribute "name" "viewport"
+ , Attributes.attribute "content" "width=device-width, initial-scale=1.0"
+ ]
+ []
+ , Element.layoutWith
+ {options = (Element.focusStyle
+ { borderColor = Nothing
+ , backgroundColor = Nothing
+ , shadow = Nothing
+ }
+ |> List.singleton)
+ }
+ (Framework.layoutAttributes ++ a) <| w
+ ]--}
, header =
Framework.container
++ Color.dark
diff --git a/example/src/Stateless.elm b/example/src/Stateless.elm
index d946a8c..3dd9347 100644
--- a/example/src/Stateless.elm
+++ b/example/src/Stateless.elm
@@ -21,6 +21,7 @@ import Widget.Button as Button exposing (ButtonStyle)
import Layout exposing (Part(..))
import Icons
import Widget
+import Element.Font as Font
buttonStyle : ButtonStyle msg
buttonStyle =
@@ -38,13 +39,23 @@ tabButtonStyle=
, active = Color.primary
}
+chipButton : ButtonStyle msg
+chipButton =
+ { container = Tag.simple
+ , disabled = []
+ , label = Grid.simple
+ , active = Color.primary
+ }
+
type alias Model =
{ selected : Maybe Int
, multiSelected : Set Int
+ , chipTextInput : Set String
, isCollapsed : Bool
, carousel : Int
, tab : Maybe Int
, button : Bool
+ , textInput : String
}
@@ -52,19 +63,24 @@ type Msg
= ChangedSelected Int
| ChangedMultiSelected Int
| ToggleCollapsable Bool
+ | ToggleTextInputChip String
| ChangedTab Int
| SetCarousel Int
| ToggleButton Bool
+ | SetTextInput String
+ | Idle
init : Model
init =
{ selected = Nothing
, multiSelected = Set.empty
+ , chipTextInput = Set.empty
, isCollapsed = False
, carousel = 0
, tab = Just 1
, button = True
+ , textInput = ""
}
@@ -98,6 +114,18 @@ update msg model =
}
, Cmd.none
)
+
+ ToggleTextInputChip string ->
+ ( { model
+ | chipTextInput =
+ model.chipTextInput |>
+ if model.chipTextInput |> Set.member string then
+ Set.remove string
+ else
+ Set.insert string
+ }
+ , Cmd.none
+ )
SetCarousel int ->
( if (int < 0) || (int > 3) then
@@ -115,6 +143,12 @@ update msg model =
ToggleButton bool ->
( { model | button = bool }, Cmd.none )
+
+ SetTextInput string ->
+ ( {model | textInput = string },Cmd.none)
+
+ Idle ->
+ ( model, Cmd.none)
select : Model -> (String,Element Msg)
@@ -187,29 +221,35 @@ multiSelect model =
collapsable : Model -> (String,Element Msg)
collapsable model =
( "Collapsable"
- , Widget.collapsable
- { onToggle = ToggleCollapsable
+ , { onToggle = ToggleCollapsable
, isCollapsed = model.isCollapsed
, label =
- Element.row Grid.compact
+ Element.row (Grid.simple ++ [Element.width<| Element.fill])
[ Element.html <|
if model.isCollapsed then
Heroicons.cheveronRight [ Attributes.width 20 ]
else
Heroicons.cheveronDown [ Attributes.width 20 ]
- , Element.el Heading.h4 <| Element.text <| "Title"
+ , Element.text <| "Title"
]
, content = Element.text <| "Hello World"
}
+ |>Widget.collapsable
+ { containerColumn = Card.simple ++ Grid.simple
+ ++ [ Element.padding 8 ]
+ , button = []
+ }
+
)
tab : Model -> (String,Element Msg)
tab model =
( "Tab"
, Widget.tab
- { tabButton = tabButtonStyle
- , tabRow = Grid.simple
+ { button = tabButtonStyle
+ , optionRow = Grid.simple
+ , containerColumn = Grid.compact
}
{ selected = model.tab
, options = [ 1, 2, 3 ]
@@ -315,6 +355,64 @@ iconButton model =
] |> Element.column Grid.simple
)
+textInput : Model -> (String,Element Msg)
+textInput model =
+ ( "Chip Text Input"
+ , [ { chips =
+ model.chipTextInput
+ |> Set.toList
+ |> List.map (\string ->
+ { icon = Element.none
+ , text = string
+ , onPress =
+ string
+ |> ToggleTextInputChip
+ |> Just
+ }
+ )
+ , text = model.textInput
+ , placeholder = Nothing
+ , label = "Chips"
+ , onChange = SetTextInput
+ }
+ |> Widget.textInput
+ { chip = chipButton
+ , chipsRow =
+ [ Element.width <| Element.shrink
+ , Element.spacing <| 4 ]
+ , containerRow =
+ Button.simple
+ ++ Color.light
+ ++ [ Border.color <| Element.rgb255 186 189 182
+ , Font.alignLeft
+ , Element.padding 8
+ , Element.height <| Element.px <|42
+ ]
+ ++ Grid.simple
+ , input =
+ Color.light
+ ++ [Element.padding 0]
+ }
+ , model.chipTextInput
+ |> Set.diff
+ (["A","B","C"]
+ |> Set.fromList
+ )
+ |> Set.toList
+ |> List.map
+ (\string ->
+ Input.button (Button.simple ++ Tag.simple)
+ { onPress =
+ string
+ |> ToggleTextInputChip
+ |> Just
+ , label = Element.text string
+ }
+ )
+ |> Element.wrappedRow [ Element.spacing 10 ]
+ ] |> Element.column Grid.simple
+ )
+
view :
{ msgMapper : Msg -> msg
, showDialog : msg
@@ -336,5 +434,6 @@ view { msgMapper, showDialog, changedSheet } model =
, carousel model |> Tuple.mapSecond (Element.map msgMapper)
, tab model |> Tuple.mapSecond (Element.map msgMapper)
, dialog showDialog model
+ , textInput model |> Tuple.mapSecond (Element.map msgMapper)
]
}
diff --git a/src/Widget.elm b/src/Widget.elm
index 05227c6..53e52f6 100644
--- a/src/Widget.elm
+++ b/src/Widget.elm
@@ -1,24 +1,19 @@
module Widget exposing
( select, multiSelect, collapsable, carousel, modal, tab, dialog
- , Dialog, MultiSelect, Select, selectButton
+ , Dialog, Select, selectButton, textInput
)
{-| This module contains functions for displaying data.
@docs select, multiSelect, collapsable, carousel, modal, tab, dialog
-
-# DEPRECATED
-
-@docs dialog
-
-}
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.Input as Input exposing (Placeholder)
import Set exposing (Set)
import Widget.Button as Button exposing (Button, ButtonStyle, TextButton)
@@ -34,17 +29,6 @@ type alias Select msg =
}
-type alias MultiSelect msg =
- { selected : Set Int
- , options :
- List
- { text : String
- , icon : Element Never
- }
- , onSelect : Int -> Maybe msg
- }
-
-
type alias Dialog msg =
{ title : Maybe String
, body : Element msg
@@ -95,7 +79,14 @@ select { selected, options, onSelect } =
{-| Selects multible options. This can be used for checkboxes.
-}
multiSelect :
- MultiSelect msg
+ { selected : Set Int
+ , options :
+ List
+ { text : String
+ , icon : Element Never
+ }
+ , onSelect : Int -> Maybe msg
+ }
-> List ( Bool, Button msg )
multiSelect { selected, options, onSelect } =
options
@@ -110,18 +101,51 @@ multiSelect { selected, options, onSelect } =
)
+{-| -}
+textInput :
+ { chip : ButtonStyle msg
+ , containerRow : List (Attribute msg)
+ , chipsRow : List (Attribute msg)
+ , input : List (Attribute msg)
+ }
+ ->
+ { chips : List (Button msg)
+ , text : String
+ , placeholder : Maybe (Placeholder msg)
+ , label : String
+ , onChange : String -> msg
+ }
+ -> Element msg
+textInput style { chips, placeholder, label, text, onChange } =
+ Element.row style.containerRow
+ [ chips
+ |> List.map (Button.view style.chip)
+ |> Element.row style.chipsRow
+ , Input.text style.input
+ { onChange = onChange
+ , text = text
+ , placeholder = placeholder
+ , label = Input.labelHidden label
+ }
+ ]
+
+
{-| Some collapsable content.
-}
collapsable :
- { onToggle : Bool -> msg
- , isCollapsed : Bool
- , label : Element msg
- , content : Element msg
+ { containerColumn : List (Attribute msg)
+ , button : List (Attribute msg)
}
+ ->
+ { onToggle : Bool -> msg
+ , isCollapsed : Bool
+ , label : Element msg
+ , content : Element msg
+ }
-> Element msg
-collapsable { onToggle, isCollapsed, label, content } =
- Element.column [] <|
- [ Input.button []
+collapsable style { onToggle, isCollapsed, label, content } =
+ Element.column style.containerColumn <|
+ [ Input.button style.button
{ onPress = Just <| onToggle <| not isCollapsed
, label = label
}
@@ -137,9 +161,9 @@ collapsable { onToggle, isCollapsed, label, content } =
{-| Displayes a list of contents in a tab
-}
tab :
- { style
- | tabButton : ButtonStyle msg
- , tabRow : List (Attribute msg)
+ { button : ButtonStyle msg
+ , optionRow : List (Attribute msg)
+ , containerColumn : List (Attribute msg)
}
-> Select msg
-> (Maybe Int -> Element msg)
@@ -147,12 +171,12 @@ tab :
tab style options content =
[ options
|> select
- |> List.map (selectButton style.tabButton)
- |> Element.row style.tabRow
+ |> List.map (selectButton style.button)
+ |> Element.row style.optionRow
, options.selected
|> content
]
- |> Element.column []
+ |> Element.column style.containerColumn
dialog :
diff --git a/src/Widget/FilterMultiSelect.elm b/src/Widget/FilterMultiSelect.elm
new file mode 100644
index 0000000..da4f13e
--- /dev/null
+++ b/src/Widget/FilterMultiSelect.elm
@@ -0,0 +1,108 @@
+module Widget.FilterMultiSelect exposing (Model, Msg(..), init, update, viewInput, viewOptions)
+
+{-|
+
+@docs Model, Msg, init, update, viewInput, viewOptions
+
+-}
+
+import Element.Input exposing (Placeholder)
+import Set exposing (Set)
+import Widget.Button exposing (Button)
+
+
+{-| The Model containing the raw value, the selected value and all the possible options.
+-}
+type alias Model =
+ { raw : String
+ , selected : Set String
+ , options : Set String
+ }
+
+
+{-| The Msg is exposed by design. You can unselect by sending `Selected Nothing`.
+-}
+type Msg
+ = ChangedRaw String
+ | ToggleSelected String
+
+
+{-| The initial state contains the set of possible options.
+-}
+init : Set String -> Model
+init options =
+ { raw = ""
+ , selected = Set.empty
+ , options = options
+ }
+
+
+{-| Updates the Model
+-}
+update : Msg -> Model -> Model
+update msg model =
+ case msg of
+ ChangedRaw string ->
+ { model
+ | raw = string
+ }
+
+ ToggleSelected string ->
+ if model.selected |> Set.member string then
+ { model
+ | selected = model.selected |> Set.remove string
+ }
+
+ else
+ { model
+ | selected = model.selected |> Set.insert string
+ , raw = ""
+ }
+
+
+{-| A wrapper around Input.text.
+-}
+viewInput :
+ Model
+ ->
+ { msgMapper : Msg -> msg
+ , placeholder : Maybe (Placeholder msg)
+ , label : String
+ , toChip : String -> Button msg
+ }
+ ->
+ { chips : List (Button msg)
+ , text : String
+ , placeholder : Maybe (Placeholder msg)
+ , label : String
+ , onChange : String -> msg
+ }
+viewInput model { msgMapper, placeholder, label, toChip } =
+ { chips =
+ model.selected
+ |> Set.toList
+ |> List.map toChip
+ , text = model.raw
+ , placeholder = placeholder
+ , label = label
+ , onChange = ChangedRaw >> msgMapper
+ }
+
+
+{-| Returns a List of all options that matches the filter.
+-}
+viewOptions : Model -> List String
+viewOptions { raw, options, selected } =
+ if raw == "" then
+ []
+
+ else
+ options
+ |> Set.filter (String.toUpper >> String.contains (raw |> String.toUpper))
+ |> Set.filter
+ (\string ->
+ selected
+ |> Set.member string
+ |> not
+ )
+ |> Set.toList
diff --git a/src/Widget/Snackbar.elm b/src/Widget/Snackbar.elm
index 464f3fd..a4d90d0 100644
--- a/src/Widget/Snackbar.elm
+++ b/src/Widget/Snackbar.elm
@@ -20,7 +20,6 @@ module Widget.Snackbar exposing
import Element exposing (Attribute, Element)
import Queue exposing (Queue)
-import Widget
import Widget.Button as Button exposing (ButtonStyle, TextButton)