PaackEng/paack-ui/6.8.2

NOTE: the latest version is 7.23.0

UI.NavigationContainer

The UI.NavigationContainer (abbreviated as Nav) is a page presenter. Depending on the situation, it applies the sidebar menu, dialogs, and mobile's navbar over the current page.

For this, a page must provide some required data through the Nav.Container record. That aggregated with the current rendering configuration provided by UI.RenderConfig and the Nav.State, multiplex between possible viewing layouts.

Example of usage:

view : RenderConfig -> Model.Model -> { body : List (Html Msg.Msg), title : String }
view renderConfig { navState, currentPage } =
    Nav.navigator Msg.NavMsg
        navState
        (getPageContainer >> Nav.containerMap Msg.PageMsg)
        |> Nav.withMenuPages
            [ Nav.menuPage (Icon.packages "Packages")
                (Link.link "/packages")
                (currentPage == Page.Packages)
            ]
        |> Nav.withMenuActions
            [ Nav.menuAction
                (Icon.logout "Logout")
                Msg.SessionLogout
            ]
        |> Nav.withMenuLogo "My company's logo" someLogoElement
        |> Nav.toBrowserDocument renderConfig currentPage


getPageContainer : Page.Page -> Nav.Container Page.Msg
getPageContainer page =
    case page of
        Page.Packages ->
            { title = "Packages"
            , content = Nav.contentSingle Packages.view
            , dialog = Nothing
            , hasMenu = False
            }

Model & Update

type Msg

The Nav.Msg handles menu related messages.

type State

Keep this one in your Model, it holds the current navigation state.

stateInit : RenderConfig -> State

The default way of instantiating a Nav.State.

Nav.stateInit renderConfig

Force the menu to be closed on the Nav.State.

Nav.stateInitWithClosedMenu renderConfig
stateUpdate : Msg -> State -> ( State, Cmd Msg )

Given a message, apply an update to the Nav.State.

Building

Page

type alias Container msg =
{ content : Content msg
, title : String
, dialog : Maybe (Dialog msg)
, hasMenu : Bool
}

The Nav.Container msg describes the current page in its current state.

The title field is exposed as to the browser and reused on the mobile's navbar.

When it is Just someElement, the dialog field is shown over the page.

The hasMenu field can hide the menu when undesired, e.g., login page.

The content field must be the element holding the page's view.

{ content = Nav.contentSingle <| Element.el [] [ Element.text "Element body" ]
, title = "Example page"
, dialog = Nothing -- or Just <| Nav.dialog <| ...
, hasMenu = True
}
containerMap : (a -> b) -> Container a -> Container b

Transform the messages produced by a container.

Content

type alias Content msg =
Content msg

The Nav.Content msg manages different kinds of pages' body. By now, the content is either a typical single page or a stacked child of mobile's views.

The typical single page renders the way they come. The stacked child has a different header on mobile, where a back button replaces the sandwich button.

contentSingle : Element msg -> Content msg

Nav.contentSingle indicates that the current page is a simple single page. It expects the final page's view in the only parameter.

Nav.contentSingle <| view renderConfig model
type alias StackChild msg =
StackChild msg

Stacked children are typical on mobile. The most significant difference is they have a customizable navbar where a back button replaces the sandwich menu, allowing the user to return to a higher scope. Besides that, they can add custom buttons to the right side of the title.

This record holds a stack child's configuration. That includes the back button's message, a title which overwrites the main page's title, and the customized buttons.

{ title = "Edit: Card " ++ selectedCard.number
, buttons =
    [ Icon.print "Print card"
        |> Button.fromIcon
        |> Button.cmd (Msg.PrintCard selectedCard) Button.light
    ]
, goBackMsg = Msg.DiscardCardChanges
}
contentStackChild : StackChild msg -> Element msg -> Content msg

Nav.contentStackChild indicates that the current page is a stack child's page. It expects the child's configuration and the final page's view as its parameters.

Nav.contentStackChild
    { title = "Edit: Card " ++ selectedCard.number
    , buttons =
        [ Icon.print "Print card"
            |> Button.fromIcon
            |> Button.cmd (Msg.PrintCard selectedCard) Button.light
        ]
    , goBackMsg = Msg.DiscardCardChanges
    }
<|
    cardEditView renderConfig selectedCard

Menu

withMenuActions : List (MenuAction msg) -> Navigator page msg -> Navigator page msg

Nav.withMenuActions replaces the list of the menu's actions. Therefore changing the list of items showed at the bottom of the sidebar.

Nav.withMenuActions
    [ Nav.menuAction
        (Icon.language "Change to English")
        (Msg.SetLanguage Lang.English)
    , Nav.menuAction
        (Icon.logout "Logout")
        Msg.SessionLogout
    ]
    someNav
withMenuPages : List MenuPage -> Navigator page msg -> Navigator page msg

Nav.withMenuPages replaces the list of the menu's navigable pages. Therefore changing the list of items showed at the top of the sidebar.

Nav.withMenuPages
    [ Nav.menuPage (Icon.edit "Manage cards")
        (Link.link "/edit-cards")
        (currentPage == Page.CardsEdit)
    , Nav.menuPage (Icon.add "New manager")
        (Link.link "/add-manager")
        (currentPage == Page.ManagerAdd)
    ]
    someNav
withSidebarStyle : SidebarStyle -> Navigator page msg -> Navigator page msg

Nav.withSidebarStyle takes a SidebarStyle setting the appearance/behavior of the sidebar when it is enabled by the hasMenu flag.

sidebarPersistent : SidebarStyle

Persistent style of the sidebar. It occupies more space when open pushing the content right.

sidebarNonPersistent : SidebarStyle

Non-persistent style of the sidebar. Like the mobile sidebar, this style makes the sidebar open over the content with an overlay behind it.

Message to force the exhibition of the sidebar/menu.

Message to force hiding the sidebar/menu.

Dialog

type Dialog msg

The Nav.Dialog msg is a record holding the description of a dialog. See Nav.dialog to see how to create a dialog.

dialog : String -> msg -> Element msg -> Dialog msg

Nav.dialog constructs a Nav.Dialog from a title, a close message, and the dialog's view. Note that the title renders inside the dialog's decoration, with a close button on the opposite side.

This dialog and its decoration render over the current page's view, adding a transparent black layer between them. Clicking on the black layer also activates the closing message.

Nav.dialog
    "Create a new card"
    Msg.NewCardDiscard
    (cardNewView model)

Note: This is deprecated, use 'Nav.dialogV2' instead

dialogV2 : Dialog msg -> Dialog msg
`Nav.dialogV2` constructs a [`Nav.Dialog`](#Dialog) from dialog v2.
This variant of the dialog does not require you to specify body at the time
of construction and you can specify it alongside buttons as an option
like this:


Dialogv2.dialog "An example dialog" Icon.warning closeMsg
    |> Dialogv2.withBody (Element.text "Dialog body")
    |> Dialogv2.withButtons [submitButton, cancleButton]
    |> dialogV2

Rendering

RenderConfig
-> page
-> Navigator page msg
-> { body : List (Html msg)
, title : String
}

End of the builder's life. The result of this function is a ready-to-use Browser.Document.

There is an additional parameter that is the page identifier, used to obtain the current container.

Nav.toBrowserDocument renderConfig currentPage navigator