mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-29 21:52:59 +03:00
Merge pull request #1277 from NoRedInk/bat/tooltips-with-menu
🔧 Menu tooltips
This commit is contained in:
commit
fef894cd2c
@ -31,6 +31,7 @@ import Nri.Ui.Menu.V4 as Menu
|
||||
import Nri.Ui.Spacing.V1 as Spacing
|
||||
import Nri.Ui.Table.V6 as Table
|
||||
import Nri.Ui.TextInput.V7 as TextInput
|
||||
import Nri.Ui.Tooltip.V3 as Tooltip
|
||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||
import Set exposing (Set)
|
||||
import Svg.Styled as Svg
|
||||
@ -123,6 +124,7 @@ view ellieLinkConfig state =
|
||||
let
|
||||
menuAttributes =
|
||||
Control.currentValue state.settings
|
||||
|> .attributes
|
||||
|> List.map Tuple.second
|
||||
|
||||
isOpen name =
|
||||
@ -139,28 +141,55 @@ view ellieLinkConfig state =
|
||||
, version = version
|
||||
, update = UpdateControls
|
||||
, settings = state.settings
|
||||
, mainType = Just "RootHtml.Html { focus : Maybe String, isOpen : Bool }"
|
||||
, mainType = Just "RootHtml.Html Msg"
|
||||
, extraCode =
|
||||
[ "import Nri.Ui.Button.V10 as Button"
|
||||
, "import Nri.Ui.ClickableSvg.V2 as ClickableSvg"
|
||||
, "import Nri.Ui.ClickableText.V3 as ClickableText"
|
||||
, "import Nri.Ui.Tooltip.V3 as Tooltip"
|
||||
, "\ntype Msg = ToggleMenu { focus : Maybe String, isOpen : Bool } | ToggleTooltip Bool"
|
||||
]
|
||||
, renderExample = Code.unstyledView
|
||||
, toExampleCode =
|
||||
\settings ->
|
||||
\{ attributes } ->
|
||||
let
|
||||
code : String
|
||||
code =
|
||||
moduleName
|
||||
++ ".view "
|
||||
++ "identity -- TODO: you will need a real msg type here"
|
||||
++ ".view ToggleMenu"
|
||||
++ Code.listMultiline
|
||||
(("Menu.isOpen " ++ Code.bool (isOpen "interactiveExample"))
|
||||
:: List.map Tuple.first settings
|
||||
((if (Control.currentValue state.settings).withTooltip then
|
||||
[ Code.fromModule "Menu" "withTooltip"
|
||||
++ Code.listMultiline
|
||||
[ Code.fromModule "Tooltip" "open " ++ (Code.bool <| Set.member "tooltip-0" state.openTooltips)
|
||||
, Code.fromModule "Tooltip" "onToggle " ++ "ToggleTooltip"
|
||||
, Code.fromModule "Tooltip" "plaintext " ++ Code.string "Tooltip content"
|
||||
, Code.fromModule "Tooltip" "fitToContent"
|
||||
]
|
||||
2
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
++ ("Menu.isOpen " ++ Code.bool (isOpen "interactiveExample"))
|
||||
:: List.map Tuple.first attributes
|
||||
)
|
||||
1
|
||||
++ Code.newlineWithIndent 1
|
||||
++ "[]"
|
||||
++ Code.listMultiline
|
||||
[ (Code.fromModule moduleName "entry " ++ Code.string "unique-button-id" ++ " <|")
|
||||
++ Code.newlineWithIndent 2
|
||||
++ Code.anonymousFunction "attributes"
|
||||
(Code.newlineWithIndent 2
|
||||
++ (Code.fromModule "ClickableText" "button " ++ Code.string "Button")
|
||||
++ Code.listMultiline
|
||||
[ Code.fromModule "ClickableText" "small"
|
||||
, Code.fromModule "ClickableText" "custom" ++ " attributes"
|
||||
]
|
||||
3
|
||||
)
|
||||
]
|
||||
1
|
||||
in
|
||||
[ { sectionName = "Example"
|
||||
, code = code
|
||||
@ -173,8 +202,29 @@ view ellieLinkConfig state =
|
||||
]
|
||||
, div [ css [ Css.displayFlex, Css.justifyContent Css.center ] ]
|
||||
[ Menu.view (FocusAndToggle "interactiveExample")
|
||||
(Menu.isOpen (isOpen "interactiveExample") :: menuAttributes)
|
||||
[]
|
||||
((if (Control.currentValue state.settings).withTooltip then
|
||||
[ Menu.withTooltip
|
||||
[ Tooltip.open (Set.member "tooltip-0" state.openTooltips)
|
||||
, Tooltip.onToggle (ToggleTooltip "tooltip-0")
|
||||
, Tooltip.plaintext "Tooltip content"
|
||||
, Tooltip.fitToContent
|
||||
]
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
++ Menu.isOpen (isOpen "interactiveExample")
|
||||
:: menuAttributes
|
||||
)
|
||||
[ Menu.entry "customizable-example" <|
|
||||
\attrs ->
|
||||
ClickableText.button "Button"
|
||||
[ ClickableText.small
|
||||
, ClickableText.onClick (ConsoleLog "Interactive example")
|
||||
, ClickableText.custom attrs
|
||||
]
|
||||
]
|
||||
]
|
||||
, Heading.h2
|
||||
[ Heading.plaintext "Menu types"
|
||||
@ -357,11 +407,20 @@ type alias State =
|
||||
|
||||
|
||||
type alias Settings =
|
||||
List ( String, Menu.Attribute Msg )
|
||||
{ attributes : List ( String, Menu.Attribute Msg )
|
||||
, withTooltip : Bool
|
||||
}
|
||||
|
||||
|
||||
initSettings : Control Settings
|
||||
initSettings =
|
||||
Control.record Settings
|
||||
|> Control.field "attributes" initSettingAttributes
|
||||
|> Control.field "withTooltip" (Control.bool False)
|
||||
|
||||
|
||||
initSettingAttributes : Control (List ( String, Menu.Attribute Msg ))
|
||||
initSettingAttributes =
|
||||
ControlExtra.list
|
||||
|> ControlExtra.optionalListItem "alignment" controlAlignment
|
||||
|> ControlExtra.optionalBoolListItem "isDisabled" ( "Menu.isDisabled True", Menu.isDisabled True )
|
||||
@ -503,6 +562,7 @@ controlMenuWidth =
|
||||
type Msg
|
||||
= ConsoleLog String
|
||||
| UpdateControls (Control Settings)
|
||||
| ToggleTooltip String Bool
|
||||
| FocusAndToggle String { isOpen : Bool, focus : Maybe String }
|
||||
| Focused (Result Dom.Error ())
|
||||
|
||||
@ -517,6 +577,12 @@ update msg state =
|
||||
UpdateControls configuration ->
|
||||
( { state | settings = configuration }, Cmd.none )
|
||||
|
||||
ToggleTooltip id True ->
|
||||
( { state | openTooltips = Set.insert id state.openTooltips }, Cmd.none )
|
||||
|
||||
ToggleTooltip id False ->
|
||||
( { state | openTooltips = Set.remove id state.openTooltips }, Cmd.none )
|
||||
|
||||
FocusAndToggle id { isOpen, focus } ->
|
||||
( { state
|
||||
| openMenu =
|
||||
|
@ -3,6 +3,7 @@ module Nri.Ui.Menu.V4 exposing
|
||||
, isOpen, isDisabled
|
||||
, opensOnHover
|
||||
, defaultTrigger, button, clickableText, clickableSvg, clickableSvgWithoutIndicator
|
||||
, withTooltip
|
||||
, buttonId
|
||||
, navMenuList, disclosure, dialog
|
||||
, menuWidth, menuId, menuZIndex
|
||||
@ -10,7 +11,11 @@ module Nri.Ui.Menu.V4 exposing
|
||||
, Entry, group, entry
|
||||
)
|
||||
|
||||
{-| Changes from V3:
|
||||
{-| Patch changes:
|
||||
|
||||
- improve interoperability with Tooltip (Note that tooltip keyboard events are not fully supported!)
|
||||
|
||||
Changes from V3:
|
||||
|
||||
- improve composability with Button, ClickableText, and ClickableSvg
|
||||
|
||||
@ -33,6 +38,7 @@ A togglable menu view and related buttons.
|
||||
## Triggering button options
|
||||
|
||||
@docs defaultTrigger, button, clickableText, clickableSvg, clickableSvgWithoutIndicator
|
||||
@docs withTooltip
|
||||
@docs buttonId
|
||||
|
||||
|
||||
@ -66,6 +72,7 @@ import Nri.Ui.Fonts.V1
|
||||
import Nri.Ui.Html.Attributes.V2 as AttributesExtra
|
||||
import Nri.Ui.Shadows.V1 as Shadows
|
||||
import Nri.Ui.Svg.V1 as Svg
|
||||
import Nri.Ui.Tooltip.V3 as Tooltip
|
||||
import Nri.Ui.WhenFocusLeaves.V1 as WhenFocusLeaves
|
||||
|
||||
|
||||
@ -85,6 +92,7 @@ type alias MenuConfig msg =
|
||||
, zIndex : Int
|
||||
, opensOnHover : Bool
|
||||
, purpose : Purpose
|
||||
, tooltipAttributes : List (Tooltip.Attribute msg)
|
||||
}
|
||||
|
||||
|
||||
@ -100,6 +108,7 @@ defaultConfig =
|
||||
, zIndex = 1
|
||||
, opensOnHover = False
|
||||
, purpose = NavMenu
|
||||
, tooltipAttributes = []
|
||||
}
|
||||
|
||||
|
||||
@ -290,42 +299,22 @@ setButton button_ =
|
||||
|
||||
{-| -}
|
||||
type Button msg
|
||||
= Button (MenuConfig msg -> List (Html.Attribute msg) -> Html msg)
|
||||
= DefaultTrigger String (List (Button.Attribute msg))
|
||||
| Button String (List (Button.Attribute msg))
|
||||
| ClickableText String (List (ClickableText.Attribute msg))
|
||||
| ClickableSvg { includeIndicator : Bool } String Svg.Svg (List (ClickableSvg.Attribute msg))
|
||||
|
||||
|
||||
defaultButton : String -> List (Button.Attribute msg) -> Button msg
|
||||
defaultButton title attributes =
|
||||
Button
|
||||
(\menuConfig buttonAttributes ->
|
||||
Button.button title
|
||||
([ Button.tertiary
|
||||
, Button.css
|
||||
[ Css.justifyContent Css.spaceBetween
|
||||
, Css.paddingLeft (Css.px 15)
|
||||
, Css.paddingRight (Css.px 15)
|
||||
, Css.color Colors.gray20 |> Css.important
|
||||
, Css.fontWeight (Css.int 600)
|
||||
, Css.hover [ Css.backgroundColor Colors.white ]
|
||||
]
|
||||
, Button.custom buttonAttributes
|
||||
, if menuConfig.isDisabled then
|
||||
Button.disabled
|
||||
DefaultTrigger title attributes
|
||||
|
||||
else
|
||||
Button.css []
|
||||
, Button.rightIcon
|
||||
(AnimatedIcon.arrowDownUp menuConfig.isOpen
|
||||
|> (if menuConfig.isDisabled then
|
||||
identity
|
||||
|
||||
else
|
||||
Svg.withColor Colors.azure
|
||||
)
|
||||
)
|
||||
]
|
||||
++ attributes
|
||||
)
|
||||
)
|
||||
{-| Warning: Tooltip keyboard events are not fully supported!
|
||||
-}
|
||||
withTooltip : List (Tooltip.Attribute msg) -> Attribute msg
|
||||
withTooltip tooltipAttributes =
|
||||
Attribute <| \config -> { config | tooltipAttributes = tooltipAttributes }
|
||||
|
||||
|
||||
{-| Use Button with default styles as the triggering element for the Menu.
|
||||
@ -339,73 +328,28 @@ defaultTrigger title attributes =
|
||||
-}
|
||||
button : String -> List (Button.Attribute msg) -> Attribute msg
|
||||
button title attributes =
|
||||
Button
|
||||
(\menuConfig buttonAttributes ->
|
||||
Button.button title
|
||||
([ Button.custom buttonAttributes
|
||||
, if menuConfig.isDisabled then
|
||||
Button.disabled
|
||||
|
||||
else
|
||||
Button.css []
|
||||
, Button.rightIcon (AnimatedIcon.arrowDownUp menuConfig.isOpen)
|
||||
]
|
||||
++ attributes
|
||||
)
|
||||
)
|
||||
|> setButton
|
||||
setButton (Button title attributes)
|
||||
|
||||
|
||||
{-| Use ClickableText as the triggering element for the Menu.
|
||||
-}
|
||||
clickableText : String -> List (ClickableText.Attribute msg) -> Attribute msg
|
||||
clickableText title additionalAttributes =
|
||||
Button
|
||||
(\menuConfig buttonAttributes ->
|
||||
ClickableText.button title
|
||||
([ ClickableText.custom buttonAttributes
|
||||
, ClickableText.disabled menuConfig.isDisabled
|
||||
, ClickableText.rightIcon (AnimatedIcon.arrowDownUp menuConfig.isOpen)
|
||||
]
|
||||
++ additionalAttributes
|
||||
)
|
||||
)
|
||||
|> setButton
|
||||
setButton (ClickableText title additionalAttributes)
|
||||
|
||||
|
||||
{-| Use ClickableSvg as the triggering element for the Menu.
|
||||
-}
|
||||
clickableSvg : String -> Svg.Svg -> List (ClickableSvg.Attribute msg) -> Attribute msg
|
||||
clickableSvg title icon additionalAttributes =
|
||||
Button
|
||||
(\menuConfig buttonAttributes ->
|
||||
ClickableSvg.button title
|
||||
icon
|
||||
([ ClickableSvg.custom buttonAttributes
|
||||
, ClickableSvg.disabled menuConfig.isDisabled
|
||||
, ClickableSvg.rightIcon (AnimatedIcon.arrowDownUp menuConfig.isOpen)
|
||||
]
|
||||
++ additionalAttributes
|
||||
)
|
||||
)
|
||||
|> setButton
|
||||
setButton (ClickableSvg { includeIndicator = True } title icon additionalAttributes)
|
||||
|
||||
|
||||
{-| Use ClickableSvg as the triggering element for the Menu without a dropdown indicator
|
||||
-}
|
||||
clickableSvgWithoutIndicator : String -> Svg.Svg -> List (ClickableSvg.Attribute msg) -> Attribute msg
|
||||
clickableSvgWithoutIndicator title icon additionalAttributes =
|
||||
Button
|
||||
(\menuConfig buttonAttributes ->
|
||||
ClickableSvg.button title
|
||||
icon
|
||||
([ ClickableSvg.custom buttonAttributes
|
||||
, ClickableSvg.disabled menuConfig.isDisabled
|
||||
]
|
||||
++ additionalAttributes
|
||||
)
|
||||
)
|
||||
|> setButton
|
||||
setButton (ClickableSvg { includeIndicator = False } title icon additionalAttributes)
|
||||
|
||||
|
||||
viewCustom :
|
||||
@ -551,7 +495,7 @@ viewCustom focusAndToggle config entries =
|
||||
AttributesExtra.none
|
||||
]
|
||||
[ let
|
||||
buttonAttributes =
|
||||
triggerAttributes =
|
||||
[ Aria.disabled config.isDisabled
|
||||
, -- Whether the menu is open or closed, move to the
|
||||
-- first menu item if the "down" arrow is pressed
|
||||
@ -631,10 +575,36 @@ viewCustom focusAndToggle config entries =
|
||||
Dialog _ ->
|
||||
[ Aria.hasDialogPopUp ]
|
||||
)
|
||||
|
||||
trigger tooltipAttributes =
|
||||
case config.button of
|
||||
DefaultTrigger title buttonAttributes ->
|
||||
viewDefaultTrigger title config buttonAttributes (tooltipAttributes ++ triggerAttributes)
|
||||
|
||||
Button title buttonAttributes ->
|
||||
viewButton title config buttonAttributes (tooltipAttributes ++ triggerAttributes)
|
||||
|
||||
ClickableText title clickableTextAttributes ->
|
||||
viewClickableText title config clickableTextAttributes (tooltipAttributes ++ triggerAttributes)
|
||||
|
||||
ClickableSvg includeIndicator title icon clickableSvgAttributes ->
|
||||
viewClickableSvg includeIndicator
|
||||
title
|
||||
icon
|
||||
config
|
||||
clickableSvgAttributes
|
||||
(tooltipAttributes ++ triggerAttributes)
|
||||
in
|
||||
case config.button of
|
||||
Button standardButton ->
|
||||
standardButton config buttonAttributes
|
||||
case config.tooltipAttributes of
|
||||
[] ->
|
||||
trigger []
|
||||
|
||||
_ ->
|
||||
Tooltip.view
|
||||
{ id = config.menuId ++ "-tooltip"
|
||||
, trigger = trigger
|
||||
}
|
||||
config.tooltipAttributes
|
||||
, div [ styleOuterContent contentVisible config ]
|
||||
[ div
|
||||
[ AttributesExtra.nriDescription "menu-hover-bridge"
|
||||
@ -926,3 +896,82 @@ styleContainer =
|
||||
, display inlineBlock
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- Trigger views
|
||||
|
||||
|
||||
viewDefaultTrigger : String -> MenuConfig msg -> List (Button.Attribute msg) -> List (Html.Attribute msg) -> Html msg
|
||||
viewDefaultTrigger title menuConfig buttonAttributes attributes =
|
||||
Button.button title
|
||||
([ Button.tertiary
|
||||
, Button.css
|
||||
[ Css.justifyContent Css.spaceBetween
|
||||
, Css.paddingLeft (Css.px 15)
|
||||
, Css.paddingRight (Css.px 15)
|
||||
, Css.color Colors.gray20 |> Css.important
|
||||
, Css.fontWeight (Css.int 600)
|
||||
, Css.hover [ Css.backgroundColor Colors.white ]
|
||||
]
|
||||
, Button.custom attributes
|
||||
, if menuConfig.isDisabled then
|
||||
Button.disabled
|
||||
|
||||
else
|
||||
Button.css []
|
||||
, Button.rightIcon
|
||||
(AnimatedIcon.arrowDownUp menuConfig.isOpen
|
||||
|> (if menuConfig.isDisabled then
|
||||
identity
|
||||
|
||||
else
|
||||
Svg.withColor Colors.azure
|
||||
)
|
||||
)
|
||||
]
|
||||
++ buttonAttributes
|
||||
)
|
||||
|
||||
|
||||
viewButton : String -> MenuConfig msg -> List (Button.Attribute msg) -> List (Html.Attribute msg) -> Html msg
|
||||
viewButton title menuConfig buttonAttributes attributes =
|
||||
Button.button title
|
||||
([ Button.custom attributes
|
||||
, if menuConfig.isDisabled then
|
||||
Button.disabled
|
||||
|
||||
else
|
||||
Button.css []
|
||||
, Button.rightIcon (AnimatedIcon.arrowDownUp menuConfig.isOpen)
|
||||
]
|
||||
++ buttonAttributes
|
||||
)
|
||||
|
||||
|
||||
viewClickableText : String -> MenuConfig msg -> List (ClickableText.Attribute msg) -> List (Html.Attribute msg) -> Html msg
|
||||
viewClickableText title menuConfig clickableTextAttributes attributes =
|
||||
ClickableText.button title
|
||||
([ ClickableText.custom attributes
|
||||
, ClickableText.disabled menuConfig.isDisabled
|
||||
, ClickableText.rightIcon (AnimatedIcon.arrowDownUp menuConfig.isOpen)
|
||||
]
|
||||
++ clickableTextAttributes
|
||||
)
|
||||
|
||||
|
||||
viewClickableSvg : { includeIndicator : Bool } -> String -> Svg.Svg -> MenuConfig msg -> List (ClickableSvg.Attribute msg) -> List (Html.Attribute msg) -> Html msg
|
||||
viewClickableSvg { includeIndicator } title icon menuConfig clickableSvgAttributes attributes =
|
||||
ClickableSvg.button title
|
||||
icon
|
||||
([ ClickableSvg.custom attributes
|
||||
, ClickableSvg.disabled menuConfig.isDisabled
|
||||
]
|
||||
++ (if includeIndicator then
|
||||
[ ClickableSvg.rightIcon (AnimatedIcon.arrowDownUp menuConfig.isOpen) ]
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
++ clickableSvgAttributes
|
||||
)
|
||||
|
@ -91,6 +91,15 @@ pressSpaceKey { targetDetails } =
|
||||
pressKey { targetDetails = targetDetails, keyCode = 32, shiftKey = False }
|
||||
|
||||
|
||||
pressDownArrow :
|
||||
{ targetDetails : List ( String, Encode.Value ) }
|
||||
-> List Selector
|
||||
-> ProgramTest model msg effect
|
||||
-> ProgramTest model msg effect
|
||||
pressDownArrow { targetDetails } =
|
||||
pressKey { targetDetails = targetDetails, keyCode = 40, shiftKey = False }
|
||||
|
||||
|
||||
pressRightArrow :
|
||||
{ targetDetails : List ( String, Encode.Value ) }
|
||||
-> List Selector
|
||||
|
@ -5,6 +5,7 @@ import Html.Styled as HtmlStyled
|
||||
import Json.Encode as Encode
|
||||
import Nri.Ui.ClickableText.V3 as ClickableText
|
||||
import Nri.Ui.Menu.V4 as Menu
|
||||
import Nri.Ui.Tooltip.V3 as Tooltip
|
||||
import ProgramTest exposing (ProgramTest, ensureViewHas, ensureViewHasNot)
|
||||
import Spec.KeyboardHelpers as KeyboardHelpers
|
||||
import Test exposing (..)
|
||||
@ -52,6 +53,20 @@ spec =
|
||||
|> pressEscKey { targetId = "some-random-id" }
|
||||
|> ensureViewHasNot (menuContentSelector menuContent)
|
||||
|> ProgramTest.done
|
||||
, test "Opens on down arrow" <|
|
||||
\() ->
|
||||
program []
|
||||
|> KeyboardHelpers.pressDownArrow { targetDetails = targetDetails "hello-button" }
|
||||
[ Selector.tag "button", Selector.id "hello-button" ]
|
||||
|> ensureViewHas (menuContentSelector menuContent)
|
||||
|> ProgramTest.done
|
||||
, test "Opens on down arrow when there's a tooltip attached" <|
|
||||
\() ->
|
||||
program [ Menu.withTooltip [ Tooltip.onToggle ToggleTooltip ] ]
|
||||
|> KeyboardHelpers.pressDownArrow { targetDetails = targetDetails "hello-button" }
|
||||
[ Selector.tag "button", Selector.id "hello-button" ]
|
||||
|> ensureViewHas (menuContentSelector menuContent)
|
||||
|> ProgramTest.done
|
||||
, describe "disclosure" <|
|
||||
[ test "Close on esc key" <|
|
||||
\() ->
|
||||
@ -108,19 +123,27 @@ type alias Model =
|
||||
{ isOpen : Bool }
|
||||
|
||||
|
||||
type alias Msg =
|
||||
Bool
|
||||
type Msg
|
||||
= ToggleMenu Bool
|
||||
| ToggleTooltip Bool
|
||||
|
||||
|
||||
program : List (Menu.Attribute Msg) -> ProgramTest Model Msg ()
|
||||
program attributes =
|
||||
ProgramTest.createSandbox
|
||||
{ init = { isOpen = False }
|
||||
, update = \msg _ -> { isOpen = msg }
|
||||
, update =
|
||||
\msg model ->
|
||||
case msg of
|
||||
ToggleMenu m ->
|
||||
{ isOpen = m }
|
||||
|
||||
ToggleTooltip _ ->
|
||||
model
|
||||
, view =
|
||||
\model ->
|
||||
HtmlStyled.div []
|
||||
[ Menu.view (\{ isOpen } -> isOpen)
|
||||
[ Menu.view (\{ isOpen } -> ToggleMenu isOpen)
|
||||
([ Menu.defaultTrigger menuButton []
|
||||
, Menu.isOpen model.isOpen
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user