Merge remote-tracking branch 'origin/master' into bat/hover-highlight-styles

This commit is contained in:
Tessa Kelly 2023-04-06 10:32:55 -06:00
commit a31cc0ff55
6 changed files with 258 additions and 54 deletions

View File

@ -21,7 +21,10 @@ import Example exposing (Example)
import Html.Styled as Html
import Html.Styled.Attributes exposing (css)
import KeyboardSupport exposing (Key(..))
import List.Extra
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Message.V3 as Message
import Nri.Ui.Panel.V1 as Panel
import Nri.Ui.Tabs.V8 as Tabs exposing (Alignment(..), Tab)
import Nri.Ui.Text.V6 as Text
import Nri.Ui.Tooltip.V3 as Tooltip
@ -107,7 +110,11 @@ example =
Control.currentValue model.settings
tabs =
allTabs model.openTooltip settings.withTooltips
allTabs
{ openTooltipId = model.openTooltip
, withTooltips = settings.withTooltips
, pageBackgroundColor = settings.pageBackgroundColor
}
in
[ ControlView.view
{ ellieLinkConfig = ellieLinkConfig
@ -131,7 +138,7 @@ example =
[ Just (moduleName ++ ".alignment " ++ moduleName ++ "." ++ Debug.toString settings.alignment)
, Maybe.map (\title -> moduleName ++ ".title " ++ Code.string title) settings.title
, Maybe.map (\spacing -> moduleName ++ ".spacing " ++ String.fromFloat spacing) settings.customSpacing
, Maybe.map (\color -> moduleName ++ ".pageBackgroundColor" ++ colorToCode color) settings.pageBackgroundColor
, Maybe.map (\color -> moduleName ++ ".pageBackgroundColor " ++ colorToCode color) settings.pageBackgroundColor
, Maybe.map
(\sticky ->
case sticky of
@ -161,40 +168,73 @@ example =
}
]
}
, Tabs.view
{ focusAndSelect = FocusAndSelectTab
, selected = model.selected
}
(List.filterMap identity
[ Just (Tabs.alignment settings.alignment)
, Maybe.map Tabs.title settings.title
, Maybe.map Tabs.spacing settings.customSpacing
, Maybe.map (Tabs.pageBackgroundColor << colorToCss) settings.pageBackgroundColor
, Maybe.map
(\stickiness ->
case stickiness of
Default ->
Tabs.tabListSticky
, Html.div
[ css
[ Css.padding (Css.px 20)
, case settings.pageBackgroundColor of
Nothing ->
Css.batch []
Custom stickyConfig ->
Tabs.tabListStickyCustom stickyConfig
)
settings.stickiness
Just color ->
Css.backgroundColor (colorToCss color)
]
)
(List.map Tuple.second tabs)
]
[ case settings.pageBackgroundColor of
Nothing ->
Html.text ""
Just _ ->
Message.view
[ Message.tip
, Message.plaintext "Container and tab panel background adjusted to match pageBackgroundColor. Note that the component won't do this for you automatically!"
, Message.css [ Css.marginBottom (Css.px 20) ]
]
, Tabs.view
{ focusAndSelect = FocusAndSelectTab
, selected = model.selected
}
(List.filterMap identity
[ Just (Tabs.alignment settings.alignment)
, Maybe.map Tabs.title settings.title
, Maybe.map Tabs.spacing settings.customSpacing
, Maybe.map (Tabs.pageBackgroundColor << colorToCss) settings.pageBackgroundColor
, Maybe.map
(\stickiness ->
case stickiness of
Default ->
Tabs.tabListSticky
Custom stickyConfig ->
Tabs.tabListStickyCustom stickyConfig
)
settings.stickiness
]
)
(List.map Tuple.second tabs)
]
]
}
allTabs : Maybe Int -> Bool -> List ( String, Tab Int Msg )
allTabs openTooltipId withTooltips =
allTabs :
{ openTooltipId : Maybe Int
, withTooltips : Bool
, pageBackgroundColor : Maybe Color
}
-> List ( String, Tab Int Msg )
allTabs config =
List.repeat 4 ()
|> List.indexedMap (\i _ -> buildTooltip openTooltipId withTooltips i)
|> List.indexedMap (\i _ -> buildTab config i)
buildTooltip : Maybe Int -> Bool -> Int -> ( String, Tab Int Msg )
buildTooltip openTooltipId withTooltips id =
buildTab :
{ openTooltipId : Maybe Int
, withTooltips : Bool
, pageBackgroundColor : Maybe Color
}
-> Int
-> ( String, Tab Int Msg )
buildTab config id =
let
idString =
String.fromInt (id + 1)
@ -212,13 +252,13 @@ buildTooltip openTooltipId withTooltips id =
[ "Tabs.build { id = " ++ String.fromInt id ++ ", idString = " ++ Code.string tabIdString ++ " }"
, "\n\t [ Tabs.tabString " ++ Code.string tabName
, "\n\t , Tabs.panelHtml (text " ++ Code.string panelName ++ ")"
, if withTooltips then
, if config.withTooltips then
String.join "\n\t "
[ "\n\t , Tabs.withTooltip"
, " [ Tooltip.plaintext " ++ Code.string tabName
, " -- You will need to have a tooltip handler"
, " -- , Tooltip.onToggle ToggleTooltip " ++ ""
, " , Tooltip.open " ++ Code.bool (openTooltipId == Just id)
, " , Tooltip.open " ++ Code.bool (config.openTooltipId == Just id)
, " ]"
]
@ -228,19 +268,13 @@ buildTooltip openTooltipId withTooltips id =
]
, Tabs.build { id = id, idString = tabIdString }
([ Tabs.tabString tabName
, panelName
|> List.repeat 50
|> String.join "\n"
|> Html.text
|> List.singleton
|> Html.pre []
|> Tabs.panelHtml
, Tabs.panelHtml (panelContent config.pageBackgroundColor id panelName)
]
++ (if withTooltips then
++ (if config.withTooltips then
[ Tabs.withTooltip
[ Tooltip.plaintext tabName
, Tooltip.onToggle (ToggleTooltip id)
, Tooltip.open (openTooltipId == Just id)
, Tooltip.open (config.openTooltipId == Just id)
]
]
@ -251,6 +285,43 @@ buildTooltip openTooltipId withTooltips id =
)
panelContent : Maybe Color -> Int -> String -> Html.Html msg
panelContent pageBackgroundColor_ id panelName =
let
pangrams =
-- cycle panels so that panel contents change when changing tabs
-- without getting too creative :-D
[ ( "The one about the fox"
, "The quick brown fox jumps over the lazy dog."
)
, ( "The one about the wizards"
, "The five boxing wizards jump quickly."
)
, ( "The one about the zebras"
, "How quickly daft jumping zebras vex!"
)
, ( "The one about the sphinxes"
, "Sphinx of black quartz, judge my vow."
)
]
|> List.Extra.splitAt id
|> (\( beforeSplit, afterSplit ) -> afterSplit ++ beforeSplit)
in
Html.div []
(List.concat
[ List.map
(\( title, content ) ->
Panel.view
[ Panel.header title
, Panel.paragraph content
, Panel.containerCss [ Css.margin2 (Css.px 10) Css.zero ]
]
)
pangrams
]
)
type alias State =
{ selected : Int
, settings : Control Settings
@ -288,7 +359,7 @@ colorToCss color =
Colors.white
Gray ->
Colors.gray92
Colors.gray96
colorToCode : Color -> String
@ -298,7 +369,7 @@ colorToCode color =
"Colors.white"
Gray ->
"Colors.gray92"
"Colors.gray96"
type Stickiness

View File

@ -3,7 +3,7 @@
"name": "NoRedInk/noredink-ui",
"summary": "UI Widgets we use at NRI",
"license": "BSD-3-Clause",
"version": "22.8.0",
"version": "22.8.1",
"exposed-modules": [
"Browser.Events.Extra",
"Nri.Ui",
@ -118,4 +118,4 @@
"elm-explorations/test": "1.2.2 <= v < 2.0.0",
"tesk9/accessible-html": "5.0.0 <= v < 6.0.0"
}
}
}

View File

@ -9,7 +9,11 @@ module Nri.Ui.Highlighter.V3 exposing
, clickedHighlightable, hoveredHighlightable
)
{-| Changes from V2:
{-| Patch changes:
- fixed :bug: where clicking on a static space would cause the space to be marked
Changes from V2:
- support overlapping highlights (by way of removing the underlying mark element)
- move asFragmentTuples, usedMarkers, and text to the Highlightable module
@ -576,6 +580,7 @@ saveHinted marker =
_ ->
Highlightable.clearHint highlightable
)
>> trimHighlightableGroups
toggleHinted : Int -> Tool.MarkerModel marker -> List (Highlightable marker) -> List (Highlightable marker)
@ -598,6 +603,53 @@ toggleHinted index marker highlightables =
highlightable
in
List.map (toggle >> Highlightable.clearHint) highlightables
|> trimHighlightableGroups
{-| This removes all-static highlights. We need to track events on static elements,
so that we don't miss mouse events if a user starts or ends a highlight on a space, say,
but we should only persist changes to interactive segments.
It is meant to be called as a clean up after the highlightings have been changed.
-}
trimHighlightableGroups : List (Highlightable marker) -> List (Highlightable marker)
trimHighlightableGroups highlightables =
let
apply segment ( lastInteractiveHighlighterMarkers, staticAcc, acc ) =
-- logic largely borrowed from joinAdjacentInteractiveHighlights.
-- TODO in the next version: clean up the implementation!
case segment.type_ of
Highlightable.Interactive ->
let
bracketingHighlightTypes =
List.filterMap (\x -> List.Extra.find ((==) x) lastInteractiveHighlighterMarkers)
segment.marked
static_ =
-- for every static tag, ensure that if it's not between interactive segments
-- that share a mark in common, marks are removed.
List.map
(\s ->
{ s
| marked =
List.filterMap (\x -> List.Extra.find ((==) x) bracketingHighlightTypes)
s.marked
}
)
staticAcc
in
( segment.marked, [], segment :: static_ ++ acc )
Highlightable.Static ->
( lastInteractiveHighlighterMarkers, segment :: staticAcc, acc )
in
highlightables
|> List.foldr apply ( [], [], [] )
|> (\( _, static_, acc ) -> removeHighlights_ static_ ++ acc)
|> List.foldl apply ( [], [], [] )
|> (\( _, static_, acc ) -> removeHighlights_ static_ ++ acc)
|> List.reverse
{-| Finds the group indexes of the groups which are in the same highlighting as the group index
@ -630,7 +682,12 @@ removeHinted =
{-| -}
removeHighlights : Model marker -> Model marker
removeHighlights model =
{ model | highlightables = List.map (Highlightable.set Nothing) model.highlightables }
{ model | highlightables = removeHighlights_ model.highlightables }
removeHighlights_ : List (Highlightable m) -> List (Highlightable m)
removeHighlights_ =
List.map (Highlightable.set Nothing)
{-| You are not likely to need this helper unless you're working with inline commenting.

View File

@ -161,6 +161,7 @@ view { label, id } attrs =
[ Css.display Css.inlineFlex
, Css.alignItems Css.center
, Css.position Css.relative
, Css.fontSize (Css.px 15)
, Css.pseudoClass "focus-within"
[ Global.descendants
[ Global.class "switch-track"

View File

@ -15,6 +15,7 @@ module Nri.Ui.Tabs.V8 exposing
- Uses an HTML-like API
- Adds sticky positioning
- Adds background color in the tab list (for use with sticky positioning)
- Adds the ability to make the background of the active tab fade into the background of the panel below it
### Attributes
@ -171,9 +172,16 @@ spacing spacing_ =
Attribute (\config -> { config | spacing = Just spacing_ })
{-| Tell this tab list about the background color of the page it lievs on. This
is mostly useful when setting up sticky headers, to prevent page content from
showing through the background.
{-| Tell this tab list about the background color of the page it lievs on.
You may want to use this if, for example:
- you are setting up sticky headers, to prevent page content from showing
through the background.
- you are using tabs in a page that has non-white background, so the
background of the active tab fades into the panel below it.
-}
pageBackgroundColor : Css.Color -> Attribute id msg
pageBackgroundColor color =
@ -261,7 +269,10 @@ view { focusAndSelect, selected } attrs tabs =
{ focusAndSelect = focusAndSelect
, selected = selected
, tabs = List.map (\(Tab t) -> t) tabs
, tabStyles = tabStyles config.spacing
, tabStyles =
tabStyles
config.spacing
(Maybe.withDefault Colors.white config.pageBackgroundColor)
, tabListStyles = stylesTabsAligned config
}
in
@ -360,15 +371,20 @@ maybeStyle styler maybeValue =
Css.batch []
tabStyles : Maybe Float -> Int -> Bool -> List Style
tabStyles customSpacing index isSelected =
tabStyles : Maybe Float -> Css.Color -> Int -> Bool -> List Style
tabStyles customSpacing pageBackgroundColor_ index isSelected =
let
stylesDynamic =
if isSelected then
[ Css.backgroundColor Colors.white
, Css.borderBottom (Css.px 1)
[ Css.borderBottom (Css.px 1)
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor Colors.white
, Css.backgroundColor Colors.white
, Css.borderBottomColor pageBackgroundColor_
, Css.backgroundImage <|
Css.linearGradient2 Css.toTop
(Css.stop2 (withAlpha 1 pageBackgroundColor_) (Css.pct 0))
(Css.stop2 (withAlpha 0 pageBackgroundColor_) (Css.pct 100))
[]
]
else

View File

@ -21,7 +21,8 @@ import Test.Html.Selector as Selector exposing (Selector)
spec : Test
spec =
describe "Nri.Ui.Highlighter"
[ describe "keyboard behavior" keyboardTests
[ describe "mouse behavior" mouseTests
, describe "keyboard behavior" keyboardTests
, describe "markdown behavior" markdownTests
, describe "joinAdjacentInteractiveHighlights" joinAdjacentInteractiveHighlightsTests
, describe "selectShortest" selectShortestTests
@ -29,6 +30,54 @@ spec =
]
mouseTests : List Test
mouseTests =
[ test "clicking on a static element does nothing" <|
\() ->
[ Highlightable.init Highlightable.Static [] 0 ( [], "Pothos" )
, Highlightable.init Highlightable.Interactive [] 1 ( [], "Philodendron" )
]
|> program { markerName = Nothing, joinAdjacentInteractiveHighlights = False }
|> click "Pothos"
|> noneMarked
|> done
, test "ending a highlight on a static element works" <|
\() ->
[ Highlightable.init Highlightable.Static [] 0 ( [], "Pothos" )
, Highlightable.init Highlightable.Interactive [] 1 ( [], "Philodendron" )
]
|> program { markerName = Nothing, joinAdjacentInteractiveHighlights = False }
|> mouseDown "Philodendron"
|> mouseUp "Pothos"
|> ensureNotMarked "Pothos"
|> ensureMarked [ "Philodendron" ]
|> done
, test "static element before a highlight cannot be highlighted" <|
\() ->
[ Highlightable.init Highlightable.Static [] 0 ( [], "Pothos" )
, Highlightable.init Highlightable.Interactive [] 1 ( [], "Philodendron" )
]
|> program { markerName = Nothing, joinAdjacentInteractiveHighlights = False }
|> click "Philodendron"
|> click "Pothos"
|> ensureNotMarked "Pothos"
|> ensureMarked [ "Philodendron" ]
|> done
, test "static element after a highlight cannot be highlighted" <|
\() ->
[ Highlightable.init Highlightable.Interactive [] 0 ( [], "Philodendron" )
, Highlightable.init Highlightable.Static [] 1 ( [], "Pothos" )
]
|> program { markerName = Nothing, joinAdjacentInteractiveHighlights = False }
|> click "Philodendron"
|> ensureMarked [ "Philodendron" ]
|> click "Pothos"
|> ensureNotMarked "Pothos"
|> ensureMarked [ "Philodendron" ]
|> done
]
keyboardTests : List Test
keyboardTests =
[ test "has a focusable element when there is one" <|
@ -455,6 +504,16 @@ ensureOnlyOneInTabSequence words testContext =
)
ensureNotMarked : String -> TestContext -> TestContext
ensureNotMarked name =
ensureViewHasNot
[ Selector.all
[ Selector.tag "mark"
, Selector.containing [ Selector.text name ]
]
]
ensureMarked : List String -> TestContext -> TestContext
ensureMarked =
ensureMarkIndex 0