mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-05 00:29:17 +03:00
Merge remote-tracking branch 'origin/master' into bat/hover-highlight-styles
This commit is contained in:
commit
a31cc0ff55
@ -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
|
||||
|
4
elm.json
4
elm.json
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user