ContaSystemer / elm-menu / Menu.Styled

This library helps you create a menu. Your data is stored separately; keep it in whatever shape makes the most sense for your application. A menu has a lot of uses: form input, mentions, search, etc.

I have (hopefully!) given the users of this library a large amount of customizability.

I recommend looking at the examples before diving into the API or source code.

View

view : ViewConfig data -> Basics.Int -> State -> List data -> Html.Styled.Html Msg

Take a list of data and turn it into a menu. The ViewConfig argument is the configuration for the menu view. ViewConfig describes the HTML we want to show for each item and the list. The Int argument is how many results you would like to show. The State argument describes what is selected via mouse and keyboard.

Note: The State and List data should live in your Model. The ViewConfig for the menu belongs in your view code. ViewConfig should never exist in your model. Describe any potential menu configurations statically. This pattern has been inspired by Elm Sortable Table.

Update

update : UpdateConfig msg data -> Msg -> Basics.Int -> State -> List data -> ( State, Maybe msg )

Use this function to update the menu's State. Provide the same data as your view. The Int argument is how many results you would like to show.

subscription : Platform.Sub.Sub Msg

Add this to your program's subscriptions so the menu will respond to keyboard input.

Configuration

viewConfig : { toId : data -> String, ul : List (Html.Styled.Attribute Basics.Never), li : KeySelected -> MouseSelected -> data -> HtmlDetails Basics.Never } -> ViewConfig data

Create the configuration for your view function (ViewConfig). Say we have a List Person that we want to show as a series of options. We would create a ViewConfig like so:

import Menu

viewConfig : Menu.ViewConfig Person
viewConfig =
    let
        customizedLi keySelected mouseSelected person =
            { attributes =
                [ classList
                    [ ( "menu-item", True )
                    , ( "key-selected", keySelected )
                    , ( "mouse-selected", mouseSelected )
                    ]
                ]
            , children = [ Html.text person.name ]
            }
    in
    Menu.viewConfig
        { toId = .name
        , ul = [ class "menu-list" ]
        , li = customizedLi
        }

You provide the following information in your menu configuration:

updateConfig : { toId : data -> String, onKeyDown : Basics.Int -> Maybe String -> Maybe msg, onTooLow : Maybe msg, onTooHigh : Maybe msg, onMouseEnter : String -> Maybe msg, onMouseLeave : String -> Maybe msg, onMouseClick : String -> Maybe msg, separateSelections : Basics.Bool } -> UpdateConfig msg data

Create the configuration for your update function (UpdateConfig). Say we have a List Person that we want to show as a series of options. We would create an UpdateConfig like so:

import Menu

updateConfig : Menu.UpdateConfig Msg Person
updateConfig =
    Menu.updateConfig
        { toId = .name
        , onKeyDown =
            \code maybeId ->
                if code == 38 || code == 40 then
                    Nothing

                else if code == 13 then
                    Maybe.map SelectPerson maybeId

                else
                    Just Reset
        , onTooLow = Nothing
        , onTooHigh = Nothing
        , onMouseEnter = \_ -> Nothing
        , onMouseLeave = \_ -> Nothing
        , onMouseClick = \id -> Just (SelectPerson id)
        , separateSelections = False
        }

You provide the following information in your menu configuration:

State


type State

Tracks keyboard and mouse selection within the menu.

current : State -> ( Maybe String, Maybe String )

Current State.

empty : State

A State with nothing selected.

reset : UpdateConfig msg data -> State -> State

Reset the keyboard navigation but leave the mouse state alone. Convenient when the two selections are represented separately.

resetToFirstItem : UpdateConfig msg data -> List data -> Basics.Int -> State -> State

Like reset but defaults to a keyboard selection of the first item.

resetToLastItem : UpdateConfig msg data -> List data -> Basics.Int -> State -> State

Like reset but defaults to a keyboard selection of the last item.


type alias KeySelected =
Basics.Bool

True if the element has been selected via keyboard navigation.


type alias MouseSelected =
Basics.Bool

True if the element has been selected via mouse hover.

Definitions


type Msg

A message type for the menu to update.


type ViewConfig data

Configuration for your menu, describing your menu and its items.

Note: Your ViewConfig should never be held in your model. It should only appear in view code.


type UpdateConfig msg data

Configuration for updates


type alias HtmlDetails msg =
{ attributes : List (Html.Styled.Attribute msg)
, children : List (Html.Styled.Html msg) 
}

HTML lists require li tags as children, so we allow you to specify everything about li HTML node except the nodeType.

Sections

Sections require a separate view and configuration since another type of data must be provided: sections.

Note: Section data can have any shape: your static configuration will just tell the menu how to grab an ID for a section and its related data.

View

viewWithSections : ViewWithSectionsConfig data sectionData -> Basics.Int -> State -> List sectionData -> Html.Styled.Html Msg

Presents a menu with sections. You can follow the same instructions as described for view, providing a more advanced configuration and different data shape. ViewWithSectionsConfig sets up your menu to handle sectioned data. The sectioned data becomes the new data argument for viewWithSections.

Configuration

sectionConfig : { toId : sectionData -> String, getData : sectionData -> List data, ul : List (Html.Styled.Attribute Basics.Never), li : sectionData -> SectionNode Basics.Never } -> SectionConfig data sectionData

Create the SectionConfig for your view function. Say we have a List Century that we want to show as a series of sections. We would create a SectionConfig like so:

type alias Century =
  { title : String
  , people : List Person
  }

import Menu

sectionConfig : Menu.SectionConfig Person Century
sectionConfig =
    Menu.sectionConfig
        { toId = .title
        , getData = .people
        , ul = [ class "menu-section-list" ]
        , li =
            \section ->
                { nodeType = "div"
                , attributes = [ class "menu-section-item" ]
                , children =
                    [ div [ class "menu-section-box" ]
                        [ strong [ class "menu-section-text" ] [ text section.title ]
                        ]
                    ]
                }
        }

You provide the following information in your menu configuration:

viewWithSectionsConfig : { toId : data -> String, ul : List (Html.Styled.Attribute Basics.Never), li : KeySelected -> MouseSelected -> data -> HtmlDetails Basics.Never, section : SectionConfig data sectionData } -> ViewWithSectionsConfig data sectionData

The same configuration as viewConfig, but provide a section configuration as well.

Definitions


type alias SectionNode msg =
{ nodeType : String
, attributes : List (Html.Styled.Attribute msg)
, children : List (Html.Styled.Html msg) 
}

Describe everything about a Section HTML node.


type SectionConfig data sectionData

The configuration for a section of the menu.

Note: This should never live in your model.


type ViewWithSectionsConfig data sectionData

Configuration for your menu, describing your menu, its sections, and its items.

Note: This should never live in your model.