Merge remote-tracking branch 'origin/master' into bat/remove-old-components-18

This commit is contained in:
Tessa Kelly 2022-08-16 15:37:07 -07:00
commit 3f38576338
23 changed files with 503 additions and 87 deletions

View File

@ -27,6 +27,7 @@
"Nri.Ui.Divider.V2",
"Nri.Ui.Effects.V1",
"Nri.Ui.WhenFocusLeaves.V1",
"Nri.Ui.FocusRing.V1",
"Nri.Ui.FocusTrap.V1",
"Nri.Ui.Fonts.V1",
"Nri.Ui.Heading.V3",

View File

@ -99,6 +99,7 @@ import Markdown.Inline
import Nri.Ui
import Nri.Ui.Colors.Extra as ColorsExtra
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Fonts.V1
import Nri.Ui.Html.Attributes.V2 as ExtraAttributes
import Nri.Ui.MediaQuery.V1 as MediaQuery
@ -605,10 +606,14 @@ renderButton ((ButtonOrLink config) as button_) =
in
Nri.Ui.styled Html.button
(styledName "customButton")
[ buttonStyles config.size config.width buttonStyle_ config.customStyles ]
[ buttonStyles config.size config.width buttonStyle_ config.customStyles
, Css.pseudoClass "focus-visible"
[ Css.outline Css.none, FocusRing.boxShadows [] ]
]
(ClickableAttributes.toButtonAttributes config.clickableAttributes
++ Attributes.disabled (isDisabled config.state)
:: Attributes.type_ "button"
:: Attributes.class FocusRing.customClass
:: config.customAttributes
)
[ viewLabel config.size config.icon config.label ]
@ -629,8 +634,14 @@ renderLink ((ButtonOrLink config) as link_) =
in
Nri.Ui.styled Styled.a
(styledName linkFunctionName)
[ buttonStyles config.size config.width colorPalette config.customStyles ]
(attributes ++ config.customAttributes)
[ buttonStyles config.size config.width colorPalette config.customStyles
, Css.pseudoClass "focus-visible"
[ Css.outline Css.none, FocusRing.boxShadows [] ]
]
(Attributes.class FocusRing.customClass
:: attributes
++ config.customAttributes
)
[ viewLabel config.size config.icon config.label ]
@ -689,19 +700,34 @@ toggleButton :
-> Html msg
toggleButton config =
let
pressedShadowColor =
ColorsExtra.withAlpha 0.2 Colors.gray20
toggledBoxShadow =
"inset 0 3px 0 "
++ ColorsExtra.toCssString pressedShadowColor
toggledStyles =
if config.pressed then
Css.batch
[ Css.color Colors.navy
, Css.backgroundColor Colors.glacier
, Css.boxShadow5 Css.inset Css.zero (Css.px 3) Css.zero (ColorsExtra.withAlpha 0.2 Colors.gray20)
, Css.boxShadow5 Css.inset Css.zero (Css.px 3) Css.zero pressedShadowColor
, Css.pseudoClass "focus-visible"
[ Css.outline Css.none
, FocusRing.boxShadows [ toggledBoxShadow ]
]
, Css.border3 (Css.px 1) Css.solid Colors.azure
, Css.fontWeight Css.bold
]
else
Css.batch
[]
[ Css.pseudoClass "focus-visible"
[ Css.outline Css.none
, FocusRing.boxShadows []
]
]
in
Nri.Ui.styled Html.button
(styledName "toggleButton")
@ -729,6 +755,7 @@ toggleButton config =
-- equivalent to preventDefaultBehavior = false
-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-name
, Attributes.type_ "button"
, Attributes.class FocusRing.customClass
]
[ viewLabel Medium Nothing config.label ]

View File

@ -41,6 +41,7 @@ import Html.Styled.Attributes as Attributes exposing (css)
import Html.Styled.Events as Events
import Json.Decode
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Svg.V1 exposing (Svg)
@ -165,13 +166,17 @@ checkboxContainer model =
, marginLeft (px -4)
, pseudoClass "focus-within"
[ Css.Global.descendants
[ Css.Global.class "checkbox-icon-container"
[ borderColor (rgb 0 95 204)
]
[ Css.Global.class "checkbox-icon-container" FocusRing.styles
]
]
, Css.Global.descendants
[ Css.Global.input [ position absolute, top (calc (pct 50) minus (px 10)), left (px 10) ]
[ Css.Global.input
[ position absolute
, top (calc (pct 50) minus (px 10))
, left (px 10)
, outline none |> Css.important
, boxShadow none |> Css.important
]
]
]
, Attributes.id (model.identifier ++ "-container")
@ -298,7 +303,7 @@ viewIcon styles icon =
[ position absolute
, left zero
, top (calc (pct 50) minus (px 18))
, border3 (px 2) solid transparent
, margin (px 2)
, padding (px 2)
, borderRadius (px 3)
, height (Css.px 27)

View File

@ -73,6 +73,7 @@ import Css.Media
import Html.Styled as Html exposing (Html)
import Html.Styled.Attributes as Attributes
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Html.Attributes.V2 as ExtraAttributes
import Nri.Ui.MediaQuery.V1 as MediaQuery
import Nri.Ui.Svg.V1 as Svg exposing (Svg)
@ -521,6 +522,7 @@ renderButton ((ButtonOrLink config) as button_) =
in
Html.button
([ Attributes.class "Nri-Ui-Clickable-Svg-V1__button"
, Attributes.class FocusRing.customClass
, Attributes.type_ "button"
, Attributes.css (buttonOrLinkStyles config theme ++ config.customStyles)
, Attributes.disabled config.disabled
@ -551,6 +553,7 @@ renderLink ((ButtonOrLink config) as link_) =
in
Html.a
([ Attributes.class ("Nri-Ui-Clickable-Svg-" ++ linkFunctionName)
, Attributes.class FocusRing.customClass
, Attributes.css (buttonOrLinkStyles config theme ++ config.customStyles)
, Aria.disabled config.disabled
, Aria.label config.label
@ -700,6 +703,15 @@ buttonOrLinkStyles config { main_, mainHovered, background, backgroundHovered, b
, Css.boxSizing Css.borderBox
, Css.width (Css.px (Maybe.withDefault (getSize config.size) config.width))
, Css.height (Css.px (Maybe.withDefault (getSize config.size) config.height))
-- Focus
, Css.pseudoClass "focus-visible"
(if config.hasBorder then
[ Css.outline Css.none, FocusRing.boxShadows [] ]
else
FocusRing.styles
)
]

View File

@ -96,6 +96,7 @@ import Html.Styled as Html exposing (..)
import Html.Styled.Attributes as Attributes
import Nri.Ui
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Fonts.V1
import Nri.Ui.Html.Attributes.V2 as ExtraAttributes
import Nri.Ui.MediaQuery.V1 as MediaQuery
@ -479,8 +480,8 @@ defaults =
, size = Medium
, label = ""
, icon = Nothing
, customAttributes = []
, customStyles = []
, customAttributes = [ Attributes.class FocusRing.customClass ]
, customStyles = [ Css.pseudoClass "focus-visible" (Css.borderRadius (Css.px 4) :: FocusRing.tightStyles) ]
}

View File

@ -17,6 +17,7 @@ module Nri.Ui.Colors.Extra exposing
import Css
import SolidColor exposing (SolidColor)
import TransparentColor
{-| -}
@ -50,5 +51,11 @@ withAlpha alpha { red, green, blue } =
{-| -}
toCssString : Css.Color -> String
toCssString =
SolidColor.toRGBString << fromCssColor
toCssString color =
TransparentColor.fromRGBA
{ red = toFloat color.red
, green = toFloat color.green
, blue = toFloat color.blue
, alpha = TransparentColor.customOpacity color.alpha
}
|> TransparentColor.toRGBAString

166
src/Nri/Ui/FocusRing/V1.elm Normal file
View File

@ -0,0 +1,166 @@
module Nri.Ui.FocusRing.V1 exposing
( forKeyboardUsers, forMouseUsers
, styles, tightStyles
, boxShadows, outerBoxShadow, insetBoxShadow
, customClass
, outerColor, innerColor
)
{-|
@docs forKeyboardUsers, forMouseUsers
@docs styles, tightStyles
@docs boxShadows, outerBoxShadow, insetBoxShadow
@docs customClass
@docs outerColor, innerColor
-}
import Css exposing (Color)
import Css.Global exposing (Snippet)
import Nri.Ui.Colors.Extra exposing (toCssString)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.InputStyles.V3 as InputStyles exposing (focusedErrorInputBoxShadow, focusedInputBoxShadow)
{-| When :focus-visible, add the two-tone focus ring.
Hides default focus ring from elements that are tagged as having a custom focus ring.
-}
forKeyboardUsers : List Css.Global.Snippet
forKeyboardUsers =
[ Css.Global.class customClass [ Css.outline Css.none ]
, Css.Global.selector (":not(." ++ customClass ++ "):focus-visible") styles
, Css.Global.selector "p a:focus-visible" [ Css.important (Css.batch tightStyles) ]
, Css.Global.class InputStyles.inputClass
[ Css.pseudoClass "focus-visible"
[ boxShadows [ focusedInputBoxShadow ]
|> Css.important
, Css.Global.withClass "error"
[ boxShadows [ focusedErrorInputBoxShadow ]
|> Css.important
]
]
]
]
{-| -}
forMouseUsers : List Snippet
forMouseUsers =
[ Css.Global.everything [ Css.outline Css.none ]
, Css.Global.selector ":focus-within .checkbox-icon-container"
[ Css.important (Css.boxShadow Css.none)
]
, Css.Global.selector ":focus-within .Nri-RadioButton-RadioButtonIcon"
[ Css.important (Css.boxShadow Css.none)
]
, Css.Global.selector ".nri-ui-input:focus"
[ applyBoxShadows [ InputStyles.focusedInputBoxShadow ]
]
, Css.Global.selector ".switch-track"
[ Css.important (Css.boxShadow Css.none)
]
]
{-| Add this class to remove global focus styles. Only do this
if you'll be adding the two-tone focus ring styles another way.
-}
customClass : String
customClass =
"custom-focus-ring"
{-| A two-tone focus ring that will be visually apparent for any background/element combination.
NOTE: use `boxShadows` instead if your focusable element:
- already has a box shadow
- has an explicit border radius set
-}
styles : List Css.Style
styles =
[ boxShadows []
, Css.outline Css.none
, Css.borderRadius (Css.px 4)
]
{-|
focus
[ FocusRing.boxShadows [ "inset 0 3px 0 0 " ++ ColorsExtra.toCssString glacier ]
, outline none
]
-}
boxShadows : List String -> Css.Style
boxShadows existingBoxShadows =
existingBoxShadows
++ [ "0 0 0 3px " ++ innerColorString
, "0 0 0 6px " ++ outerColorString
]
|> applyBoxShadows
{-| Prefer `styles` over tightStyles, except in cases where line spacing/font size will otherwise cause obscured content.
-}
tightStyles : List Css.Style
tightStyles =
[ Css.outline Css.none
, applyBoxShadows
[ "inset 0 0 0 2px " ++ innerColorString
, "0 0 0 2px " ++ outerColorString
]
]
{-| In special cases, we don't use a two-tone focus ring, and an outset focus ring would be obscured.
Be very sure this is what you need before using this!
-}
insetBoxShadow : Css.Style
insetBoxShadow =
applyBoxShadows [ "inset 0 0 0 3px " ++ outerColorString ]
{-| In special cases, we don't use a two-tone focus ring.
Be very sure this is what you need before using this!
-}
outerBoxShadow : Css.Style
outerBoxShadow =
applyBoxShadows [ "0 0 0 3px " ++ outerColorString ]
applyBoxShadows : List String -> Css.Style
applyBoxShadows =
-- using `property` due to https://github.com/rtfeldman/elm-css/issues/265
String.join "," >> Css.property "box-shadow"
innerColorString : String
innerColorString =
toCssString innerColor
outerColorString : String
outerColorString =
toCssString outerColor
{-| -}
innerColor : Color
innerColor =
Colors.white
{-| -}
outerColor : Color
outerColor =
Colors.red

View File

@ -1,6 +1,7 @@
module Nri.Ui.InputStyles.V3 exposing
( label, Theme(..), input
, inputPaddingVertical, inputLineHeight, textAreaHeight, writingLineHeight, writingPadding, writingPaddingTop, writingMinHeight, defaultMarginTop
, focusedInputBoxShadow, focusedErrorInputBoxShadow, errorClass, inputClass
)
{-| InputStyles used by the TextInput and TextArea widgets.
@ -11,6 +12,7 @@ module Nri.Ui.InputStyles.V3 exposing
## Shared hardcoded values
@docs inputPaddingVertical, inputLineHeight, textAreaHeight, writingLineHeight, writingPadding, writingPaddingTop, writingMinHeight, defaultMarginTop
@docs focusedInputBoxShadow, focusedErrorInputBoxShadow, errorClass, inputClass
## Changelog
@ -23,6 +25,7 @@ module Nri.Ui.InputStyles.V3 exposing
import Css exposing (..)
import Css.Global
import Nri.Ui.Colors.Extra as ColorsExtra
import Nri.Ui.Colors.V1 exposing (..)
import Nri.Ui.Fonts.V1
@ -116,10 +119,34 @@ defaultMarginTop =
9
{-| -}
focusedInputBoxShadow : String
focusedInputBoxShadow =
"inset 0 3px 0 0 " ++ ColorsExtra.toCssString glacier
{-| -}
focusedErrorInputBoxShadow : String
focusedErrorInputBoxShadow =
"inset 0 3px 0 0 " ++ ColorsExtra.toCssString purpleLight
{-| -}
inputClass : String
inputClass =
"nri-input"
{-| -}
errorClass : String
errorClass =
"nri-input-error"
{-| In order to use these styles in an input module, you will need to add the class "override-sass-styles". This is because sass styles in the monolith have higher precendence than the class styles here.
-}
input : Theme -> Bool -> Style
input theme isInError =
input : Theme -> Style
input theme =
let
sharedStyles =
batch
@ -142,20 +169,16 @@ input theme isInError =
, focus
[ borderColor azure
, outline none
, boxShadow6 inset zero (px 3) zero zero glacier
, property "box-shadow" focusedInputBoxShadow
]
, if isInError then
batch
, Css.Global.withClass errorClass
[ borderColor purple
, boxShadow6 inset zero (px 3) zero zero purpleLight
, focus
[ borderColor purple
, boxShadow6 inset zero (px 3) zero zero purpleLight
, focus
[ borderColor purple
, boxShadow6 inset zero (px 3) zero zero purpleLight
]
, property "box-shadow" focusedErrorInputBoxShadow
]
else
batch []
]
]
in
batch
@ -191,15 +214,11 @@ input theme isInError =
[ backgroundColor azure
, color white
, borderColor azure
, if isInError then
batch
[ backgroundColor purple
, color white
, borderColor purple
]
else
batch []
, Css.Global.withClass errorClass
[ backgroundColor purple
, color white
, borderColor purple
]
]
]
]

View File

@ -57,6 +57,7 @@ import Html.Styled.Attributes as Attributes exposing (class, classList, css)
import Html.Styled.Events as Events
import Json.Decode
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Fonts.V1
import Nri.Ui.Html.Attributes.V2 as AttributesExtra
import Nri.Ui.Html.V3 exposing (viewJust)
@ -323,7 +324,11 @@ button attributes title =
StandardButton
(\menuConfig buttonAttributes ->
Html.button
([ classList [ ( "ToggleButton", True ), ( "WithBorder", buttonConfig.hasBorder ) ]
([ classList
[ ( "ToggleButton", True )
, ( "WithBorder", buttonConfig.hasBorder )
, ( FocusRing.customClass, True )
]
, css
[ Nri.Ui.Fonts.V1.baseFont
, fontSize (px 15)
@ -334,6 +339,10 @@ button attributes title =
, height (pct 100)
, fontWeight (int 600)
, cursor pointer
, pseudoClass "focus-visible"
[ outline none
, FocusRing.boxShadows []
]
, if menuConfig.isDisabled then
Css.batch
[ opacity (num 0.4)

View File

@ -48,6 +48,7 @@ import Html.Styled.Events exposing (onClick)
import InputErrorAndGuidanceInternal exposing (ErrorState, Guidance)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Data.PremiumDisplay as PremiumDisplay exposing (PremiumDisplay)
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Html.Attributes.V2 as Extra
import Nri.Ui.Pennant.V2 as Pennant
@ -301,16 +302,15 @@ view { label, name, value, valueToString, selectedValue } attributes =
[ Attributes.id (idValue ++ "-container")
, css
[ position relative
, marginLeft (px -4)
, Css.paddingLeft (Css.px 40)
, Css.marginLeft (Css.px -2)
, Css.paddingLeft (Css.px 38)
, Css.paddingTop (px 6)
, Css.paddingBottom (px 4)
, display inlineBlock
, pseudoClass "focus-within"
[ Css.Global.descendants
[ Css.Global.class "Nri-RadioButton-RadioButtonIcon"
[ borderColor (rgb 0 95 204)
]
FocusRing.tightStyles
]
]
, Css.batch config.containerCss
@ -339,17 +339,6 @@ view { label, name, value, valueToString, selectedValue } attributes =
, top (pct 50)
, left (px 4)
, opacity zero
, pseudoClass "focus"
[ Css.Global.adjacentSiblings
[ Css.Global.everything
[ Css.Global.descendants
[ Css.Global.class "Nri-RadioButton-RadioButtonIcon"
[ borderColor (rgb 0 95 204)
]
]
]
]
]
]
]
++ List.map (Attributes.map never) config.custom
@ -439,8 +428,8 @@ viewLockedButton { idValue, label } config =
[ Attributes.id (idValue ++ "-container")
, css
[ position relative
, marginLeft (px -4)
, Css.paddingLeft (Css.px 40)
, marginLeft (px -2)
, Css.paddingLeft (Css.px 38)
, Css.paddingTop (px 6)
, Css.paddingBottom (px 4)
, display inlineBlock
@ -542,9 +531,6 @@ radioInputIcon config =
iconHeight =
26
borderWidth =
2
iconPadding =
2
in
@ -562,9 +548,8 @@ radioInputIcon config =
[]
, position absolute
, left zero
, top (calc (pct 50) Css.minus (Css.px ((iconHeight + borderWidth + iconPadding) / 2)))
, top (calc (pct 50) Css.minus (Css.px ((iconHeight - 2 + iconPadding) / 2)))
, Css.property "transition" ".3s all"
, border3 (px borderWidth) solid transparent
, borderRadius (px 50)
, padding (px iconPadding)
, displayFlex

View File

@ -26,8 +26,9 @@ import Css exposing (..)
import Html.Styled
import Html.Styled.Attributes as Attributes exposing (css)
import Html.Styled.Events as Events
import Nri.Ui.Colors.Extra exposing (withAlpha)
import Nri.Ui.Colors.Extra as ColorsExtra
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Svg.V1 as Svg exposing (Svg)
import Nri.Ui.Tooltip.V3 as Tooltip
@ -92,13 +93,8 @@ viewRadioGroup config =
inner extraAttrs =
Html.Styled.label
(css
-- ensure that the focus state is visible, even
-- though the radio button that technically has focus
-- is not
(Css.pseudoClass "focus-within"
[ Css.property "outline-style" "auto" ]
:: styles config.positioning numOptions index isSelected
)
(styles config.positioning numOptions index isSelected)
:: Attributes.class FocusRing.customClass
:: extraAttrs
)
[ radio name option.idString isSelected <|
@ -194,7 +190,7 @@ view config =
toInternalTab option =
{ id = option.value
, idString = option.idString
, tabAttributes = option.attributes
, tabAttributes = Attributes.class FocusRing.customClass :: option.attributes
, tabTooltip =
case config.positioning of
Left FillContainer ->
@ -272,6 +268,11 @@ styles positioning numEntries index isSelected =
_ ->
[]
, -- ensure that the focus state is visible & looks nice
Css.pseudoClass "focus-within"
[ FocusRing.boxShadows [ focusedSegmentBoxShadowValue ]
, outline none
]
]
@ -312,12 +313,22 @@ sharedSegmentStyles numEntries index =
focusedSegmentStyles : Style
focusedSegmentStyles =
[ backgroundColor Colors.glacier
, boxShadow5 inset zero (px 3) zero (withAlpha 0.2 Colors.gray20)
, Css.property "box-shadow" focusedSegmentBoxShadowValue
, color Colors.navy
]
|> Css.batch
focusedSegmentBoxShadowValue : String
focusedSegmentBoxShadowValue =
let
colorStr =
ColorsExtra.withAlpha 0.2 Colors.gray20
|> ColorsExtra.toCssString
in
"inset 0 3px 0 " ++ colorStr
unFocusedSegmentStyles : Style
unFocusedSegmentStyles =
[ backgroundColor Colors.white

View File

@ -417,7 +417,10 @@ viewSelect config =
)
, Css.borderBottomWidth (Css.px 3)
, Css.borderRadius (Css.px 8)
, Css.focus [ Css.borderColor Colors.azure ]
, Css.focus
[ Css.borderColor Colors.azure
, Css.borderRadius (Css.px 8) |> Css.important
]
-- Font and color
, Css.color Colors.gray20

View File

@ -61,6 +61,7 @@ import Nri.Ui.ClickableSvg.V2 as ClickableSvg
import Nri.Ui.ClickableText.V3 as ClickableText
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Data.PremiumDisplay as PremiumDisplay exposing (PremiumDisplay)
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Html.Attributes.V2 as ExtraAttributes
import Nri.Ui.Html.V3 exposing (viewJust)
@ -333,10 +334,17 @@ viewSkipLink onSkip =
ClickableText.button "Skip to main content"
[ ClickableText.icon UiIcon.arrowPointingRight
, ClickableText.small
, ClickableText.custom [ Attributes.class FocusRing.customClass ]
, ClickableText.css
[ Css.pseudoClass "not(:focus)"
[ Style.invisibleStyle
]
, Css.pseudoClass "focus-visible"
[ outline none
, FocusRing.outerBoxShadow
]
, Css.padding (Css.px 2)
, Css.borderRadius (Css.px 4)
]
, ClickableText.onClick onSkip
]
@ -422,7 +430,10 @@ viewSidebarLeaf config extraStyles entryConfig =
)
++ entryConfig.customStyles
)
(attributes ++ entryConfig.customAttributes)
(Attributes.class FocusRing.customClass
:: attributes
++ entryConfig.customAttributes
)
[ viewJust
(\icon_ ->
icon_
@ -446,7 +457,9 @@ viewLockedEntry extraStyles entryConfig =
]
(case entryConfig.onLockedContent of
Just event ->
Events.onClick event :: entryConfig.customAttributes
Events.onClick event
:: Attributes.class FocusRing.customClass
:: entryConfig.customAttributes
Nothing ->
entryConfig.customAttributes
@ -463,6 +476,7 @@ viewLockedEntry extraStyles entryConfig =
sharedEntryStyles : List Style
sharedEntryStyles =
[ padding2 (px 13) (px 20)
, Css.pseudoClass "focus-visible" [ outline none, FocusRing.insetBoxShadow ]
, Css.property "word-break" "normal"
, Css.property "overflow-wrap" "anywhere"
, displayFlex

View File

@ -42,6 +42,7 @@ import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Nri.Ui.Colors.Extra exposing (toCssString)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Html.Attributes.V2 as Extra
import Nri.Ui.MediaQuery.V1 as MediaQuery
@ -160,9 +161,9 @@ view { label, id } attrs =
, Css.position Css.relative
, Css.pseudoClass "focus-within"
[ Global.descendants
[ Global.class "switch-slider"
[ stroke Colors.azure
, Css.property "stroke-width" "3px"
[ Global.class "switch-track"
[ FocusRing.boxShadows []
, Css.borderRadius (Css.px 16)
]
]
]
@ -367,6 +368,7 @@ viewSwitch config =
else
Css.opacity (Css.num 1)
]
|> Nri.Ui.Svg.V1.withCustom [ SvgAttributes.class "switch-track" ]
stroke : Color -> Style

View File

@ -32,6 +32,7 @@ import Html.Styled.Attributes as Attributes
import Nri.Ui
import Nri.Ui.Colors.Extra exposing (withAlpha)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Tooltip.V3 as Tooltip
import TabsInternal.V2 as TabsInternal
@ -105,10 +106,23 @@ spaHref url =
Attribute (\tab -> { tab | spaHref = Just url })
{-| -}
tabAttributes : List (Html.Attribute msg) -> Attribute id msg
tabAttributes attrs =
Attribute (\tab -> { tab | tabAttributes = tab.tabAttributes ++ attrs })
{-| -}
build : { id : id, idString : String } -> List (Attribute id msg) -> Tab id msg
build config attributes =
Tab (TabsInternal.fromList config (List.map (\(Attribute f) -> f) attributes))
Tab
(TabsInternal.fromList config
(List.map (\(Attribute f) -> f)
(tabAttributes [ Attributes.class FocusRing.customClass ]
:: attributes
)
)
)
{-| Determines whether tabs are centered or floating to the left or right.
@ -272,6 +286,10 @@ tabStyles customSpacing index isSelected =
, borderRightColor Colors.azure
, borderLeftColor Colors.azure
]
, focus
[ FocusRing.outerBoxShadow
, outline none
]
]
margin =

View File

@ -117,7 +117,7 @@ view_ theme model =
[ Css.display Css.block ]
autoresizeAttrs
[ Html.styled Html.textarea
[ InputStyles.input theme model.isInError
[ InputStyles.input theme
, Css.boxSizing Css.borderBox
, case model.height of
AutoResize minimumHeight ->
@ -133,7 +133,11 @@ view_ theme model =
, Attributes.autofocus model.autofocus
, Attributes.placeholder model.placeholder
, Attributes.attribute "data-gramm" "false" -- disables grammarly to prevent https://github.com/NoRedInk/NoRedInk/issues/14859
, Attributes.class "override-sass-styles"
, Attributes.class "override-sass-styles custom-focus-ring"
, Attributes.classList
[ ( InputStyles.inputClass, True )
, ( InputStyles.errorClass, model.isInError )
]
, Attributes.attribute "aria-invalid" <|
if model.isInError then
"true"

View File

@ -808,7 +808,7 @@ view label attributes =
++ [ Attributes.id idValue
, InputErrorAndGuidanceInternal.describedBy idValue config
, Attributes.css
[ InputStyles.input config.inputStyle isInError
[ InputStyles.input config.inputStyle
, if config.inputStyle == InputStyles.Writing then
Css.Global.withClass "override-sass-styles"
[ textAlign center
@ -840,7 +840,11 @@ view label attributes =
, maybeAttr (attribute "inputmode") config.inputMode
, maybeAttr (attribute "autocomplete") config.autocomplete
, maybeAttr onEnter_ eventsAndValues.onEnter
, class "override-sass-styles"
, class "nri-ui-textinput override-sass-styles custom-focus-ring"
, classList
[ ( InputStyles.inputClass, True )
, ( InputStyles.errorClass, isInError )
]
, Attributes.attribute "aria-invalid" <|
if isInError then
"true"

View File

@ -851,8 +851,18 @@ viewTooltip tooltipId config =
, Css.fontWeight (Css.int 600)
, Css.color Colors.white
, Shadows.high
, Global.descendants [ Global.a [ Css.textDecoration Css.underline ] ]
, Global.descendants [ Global.a [ Css.color Colors.white ] ]
, Global.descendants
[ Global.a
[ Css.textDecoration Css.underline
, Css.color Colors.white
, Css.visited [ Css.color Colors.white ]
, Css.hover [ Css.color Colors.white ]
, Css.pseudoClass "focus-visible"
[ Css.outline Css.none
, Css.property "box-shadow" "0 0 0 2px #FFF"
]
]
]
]
++ config.tooltipStyleOverrides
)

View File

@ -7,15 +7,17 @@ import Browser.Dom
import Browser.Navigation exposing (Key)
import Category exposing (Category)
import Css exposing (..)
import Css.Global
import Css.Media exposing (withMedia)
import Dict exposing (Dict)
import Example exposing (Example)
import Examples
import Html.Styled.Attributes exposing (..)
import Http
import InputMethod exposing (InputMethod)
import Json.Decode as Decode
import Nri.Ui.CssVendorPrefix.V1 as VendorPrefixed
import Nri.Ui.Heading.V3 as Heading
import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.MediaQuery.V1 exposing (mobile)
import Nri.Ui.Page.V3 as Page
import Nri.Ui.SideNav.V4 as SideNav
@ -40,6 +42,7 @@ type alias Model key =
, openTooltip : Maybe TooltipId
, navigationKey : key
, elliePackageDependencies : Result Http.Error (Dict String String)
, inputMethod : InputMethod
}
@ -57,6 +60,7 @@ init () url key =
, openTooltip = Nothing
, navigationKey = key
, elliePackageDependencies = Ok Dict.empty
, inputMethod = InputMethod.init
}
, Cmd.batch
[ loadPackage
@ -80,6 +84,7 @@ type Msg
| ToggleTooltip TooltipId Bool
| LoadedPackages (Result Http.Error (Dict String String))
| Focused (Result Browser.Dom.Error ())
| NewInputMethod InputMethod
update : Msg -> Model key -> ( Model key, Effect )
@ -181,6 +186,9 @@ update action model =
Focused _ ->
( model, None )
NewInputMethod inputMethod ->
( { model | inputMethod = inputMethod }, None )
type Effect
= GoToRoute Route
@ -215,9 +223,12 @@ perform navigationKey effect =
subscriptions : Model key -> Sub Msg
subscriptions model =
Dict.values model.moduleStates
|> List.map (\example -> Sub.map (UpdateModuleStates example.name) (example.subscriptions example.state))
|> Sub.batch
Sub.batch
[ Dict.values model.moduleStates
|> List.map (\example -> Sub.map (UpdateModuleStates example.name) (example.subscriptions example.state))
|> Sub.batch
, Sub.map NewInputMethod InputMethod.subscriptions
]
view : Model key -> Document Msg
@ -227,6 +238,7 @@ view model =
List.map Html.toUnstyled
[ view_
, Html.map never Sprite.attach
, Css.Global.global (InputMethod.styles model.inputMethod)
]
in
case model.route of

View File

@ -12,6 +12,7 @@ import KeyboardSupport exposing (KeyboardSupport)
import Nri.Ui.ClickableText.V3 as ClickableText
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Container.V2 as Container
import Nri.Ui.FocusRing.V1 as FocusRing
type alias Example state msg =
@ -118,6 +119,7 @@ preview_ { navigate, exampleHref } example =
[ Css.backgroundColor Colors.glacier
, Css.cursor Css.pointer
]
, Css.pseudoClass "focus-within" [ FocusRing.boxShadows [] ]
]
, Container.custom [ Events.onClick (navigate example) ]
, Container.html
@ -125,6 +127,7 @@ preview_ { navigate, exampleHref } example =
[ ClickableText.href (exampleHref example)
, ClickableText.css [ Css.marginBottom (Css.px 10) ]
, ClickableText.nriDescription "doodad-link"
, ClickableText.custom [ Attributes.class FocusRing.customClass ]
]
:: [ Html.div
[ Attributes.css

View File

@ -50,7 +50,7 @@ example =
[ Html.div [ css [ Css.position Css.relative ] ]
[ Html.textarea
[ css
[ InputStyles.input Standard False
[ InputStyles.input Standard
, Css.minHeight (Css.px 100)
, Css.maxWidth (Css.px 140)
, Css.backgroundColor Colors.white |> Css.important
@ -123,6 +123,7 @@ example =
, placeholder = "Placeholder"
, showLabel = state.showLabel == Checkbox.Selected
}
, Html.br [ css [ Css.marginBottom (Css.px 10) ] ] []
, TextArea.writing
{ value = Maybe.withDefault "" <| Dict.get 2 state.textValues
, autofocus = False
@ -139,6 +140,7 @@ example =
, placeholder = "Placeholder"
, showLabel = state.showLabel == Checkbox.Selected
}
, Html.br [ css [ Css.marginBottom (Css.px 10) ] ] []
, TextArea.contentCreation
{ value = Maybe.withDefault "" <| Dict.get 3 state.textValues
, autofocus = False
@ -155,6 +157,7 @@ example =
, placeholder = "Placeholder"
, showLabel = state.showLabel == Checkbox.Selected
}
, Html.br [ css [ Css.marginBottom (Css.px 10) ] ] []
, TextArea.writing
{ value = Maybe.withDefault "" <| Dict.get 4 state.textValues
, autofocus = False

View File

@ -0,0 +1,99 @@
module InputMethod exposing (InputMethod(..), init, subscriptions, styles)
{-| If in the NRI monolith, please see Nri.InputMethod for the equivalent module.
Utilities for detecting input method and hiding focus rings when
appropriate. Inspired by a blog post from [David Gilbertson](https://medium.com/hackernoon/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2).
@docs InputMethod, init, subscriptions, styles
-}
import Browser.Events
import Css.Global exposing (Snippet)
import Json.Decode as Decode exposing (Decoder)
import Nri.Ui.FocusRing.V1 as FocusRing
{-| Represents the method of input the user is currently using to
iteract with our app.
-}
type InputMethod
= Keyboard
| Mouse
{-| Even though most users will probably be using a mouse, setting the initial
value to Keyboard makes it so that we display the focus ring when we do care
about which element is focused initially for users that rely on the keyboard.
There dont't seem to be any downsides of doing this for mouse users, since as
soon as they interact with the page the input method will be changed.
-}
init : InputMethod
init =
Keyboard
{-| A subscription of the input method the user is currently using.
-}
subscriptions : Sub InputMethod
subscriptions =
Sub.batch
[ Browser.Events.onKeyDown
(Decode.map2 (\k t -> ( k, t ))
(Decode.field "key" Decode.string)
(Decode.at [ "target", "tagName" ] Decode.string)
|> Decode.andThen
(\( key, tagName ) ->
case key of
"ArrowUp" ->
unlessInInput tagName
"ArrowDown" ->
unlessInInput tagName
"ArrowRight" ->
unlessInInput tagName
"ArrowLeft" ->
unlessInInput tagName
"Tab" ->
Decode.succeed Keyboard
"Escape" ->
Decode.succeed Keyboard
" " ->
unlessInInput tagName
_ ->
Decode.fail "Not a navigation key. Discarding event."
)
)
, Browser.Events.onMouseDown (Decode.succeed Mouse)
]
unlessInInput : String -> Decoder InputMethod
unlessInInput tagName =
if tagName == "TEXTAREA" || tagName == "INPUT" then
Decode.fail "In an input. Discarding event."
else
Decode.succeed Keyboard
{-| A collection of global styles that will hide or show the focus ring if keyboard
navigation is detected from the user.
-}
styles : InputMethod -> List Snippet
styles inputMethod =
case inputMethod of
Keyboard ->
FocusRing.forKeyboardUsers
Mouse ->
FocusRing.forMouseUsers

View File

@ -23,6 +23,7 @@
"Nri.Ui.Divider.V2",
"Nri.Ui.Effects.V1",
"Nri.Ui.WhenFocusLeaves.V1",
"Nri.Ui.FocusRing.V1",
"Nri.Ui.FocusTrap.V1",
"Nri.Ui.Fonts.V1",
"Nri.Ui.Heading.V3",