mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-26 09:11:01 +03:00
Merge remote-tracking branch 'origin/master' into bat/overlapping-highlighter
This commit is contained in:
commit
61f1389bc7
@ -6,6 +6,7 @@ module Examples.HighlighterToolbar exposing (Msg, State, example)
|
||||
|
||||
-}
|
||||
|
||||
import Browser.Dom as Dom
|
||||
import Category exposing (Category(..))
|
||||
import Code
|
||||
import Css exposing (Color)
|
||||
@ -13,13 +14,15 @@ import Debug.Control as Control exposing (Control)
|
||||
import Debug.Control.View as ControlView
|
||||
import Example exposing (Example)
|
||||
import Html.Styled exposing (..)
|
||||
import Html.Styled.Attributes exposing (css)
|
||||
import Html.Styled.Attributes exposing (css, id)
|
||||
import KeyboardSupport exposing (Key(..))
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Heading.V3 as Heading
|
||||
import Nri.Ui.HighlighterToolbar.V1 as HighlighterToolbar
|
||||
import Nri.Ui.HighlighterToolbar.V2 as HighlighterToolbar
|
||||
import Nri.Ui.Svg.V1 as Svg
|
||||
import Nri.Ui.Text.V6 as Text
|
||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||
import Task
|
||||
|
||||
|
||||
moduleName : String
|
||||
@ -29,7 +32,7 @@ moduleName =
|
||||
|
||||
version : Int
|
||||
version =
|
||||
1
|
||||
2
|
||||
|
||||
|
||||
{-| -}
|
||||
@ -61,17 +64,25 @@ example =
|
||||
}
|
||||
, Heading.h2 [ Heading.plaintext "Example" ]
|
||||
, HighlighterToolbar.view
|
||||
{ onSetEraser = SetTool Nothing
|
||||
, onChangeTag = SetTool << Just
|
||||
{ focusAndSelect = FocusAndSelectTag
|
||||
, getColor = getColor
|
||||
, getName = getName
|
||||
, highlighterId = "highlighter"
|
||||
}
|
||||
{ currentTool = state.currentTool
|
||||
, tags = tags
|
||||
}
|
||||
, div [ id "highlighter" ] []
|
||||
]
|
||||
, categories = [ Instructional ]
|
||||
, keyboardSupport = []
|
||||
, keyboardSupport =
|
||||
[ { keys = [ Arrow KeyboardSupport.Left ]
|
||||
, result = "Select the tool to the left of the currently-selected tool. If the first tool is selected, select the last tool."
|
||||
}
|
||||
, { keys = [ Arrow KeyboardSupport.Right ]
|
||||
, result = "Select the tool to the right of the currently-selected tool. If the last tool is selected, select the first tool."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@ -174,7 +185,8 @@ controlSettings =
|
||||
{-| -}
|
||||
type Msg
|
||||
= UpdateControls (Control Settings)
|
||||
| SetTool (Maybe Tag)
|
||||
| FocusAndSelectTag { select : Maybe Tag, focus : Maybe String }
|
||||
| Focused (Result Dom.Error ())
|
||||
|
||||
|
||||
{-| -}
|
||||
@ -184,5 +196,12 @@ update msg state =
|
||||
UpdateControls settings ->
|
||||
( { state | settings = settings }, Cmd.none )
|
||||
|
||||
SetTool tag ->
|
||||
( { state | currentTool = tag }, Cmd.none )
|
||||
FocusAndSelectTag { select, focus } ->
|
||||
( { state | currentTool = select }
|
||||
, focus
|
||||
|> Maybe.map (Dom.focus >> Task.attempt Focused)
|
||||
|> Maybe.withDefault Cmd.none
|
||||
)
|
||||
|
||||
Focused error ->
|
||||
( state, Cmd.none )
|
||||
|
@ -17,8 +17,13 @@ import Example exposing (Example)
|
||||
import Html.Styled exposing (..)
|
||||
import Html.Styled.Attributes exposing (css)
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Container.V2 as Container
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.Heading.V3 as Heading
|
||||
import Nri.Ui.Spacing.V1 as Spacing
|
||||
import Nri.Ui.Table.V6 as Table
|
||||
import Svg.Styled
|
||||
import Svg.Styled.Attributes
|
||||
|
||||
|
||||
moduleName : String
|
||||
@ -41,11 +46,163 @@ example =
|
||||
, state = init
|
||||
, update = update
|
||||
, subscriptions = \_ -> Sub.none
|
||||
, preview = []
|
||||
, preview = preview
|
||||
, view = view
|
||||
}
|
||||
|
||||
|
||||
preview : List (Html msg)
|
||||
preview =
|
||||
[ Svg.Styled.svg
|
||||
[ Svg.Styled.Attributes.viewBox "0 0 100 100"
|
||||
]
|
||||
[ Svg.Styled.rect
|
||||
[ Svg.Styled.Attributes.width "100"
|
||||
, Svg.Styled.Attributes.height "100"
|
||||
, Svg.Styled.Attributes.fill Colors.white.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.rect
|
||||
[ Svg.Styled.Attributes.x "15"
|
||||
, Svg.Styled.Attributes.y "30"
|
||||
, Svg.Styled.Attributes.width "70"
|
||||
, Svg.Styled.Attributes.height "20"
|
||||
, Svg.Styled.Attributes.fill Colors.gray96.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.text_
|
||||
[ Svg.Styled.Attributes.fill Colors.gray20.value
|
||||
, Svg.Styled.Attributes.css [ Fonts.baseFont, Css.fontSize (Css.px 8) ]
|
||||
, Svg.Styled.Attributes.x "20"
|
||||
, Svg.Styled.Attributes.y "43"
|
||||
]
|
||||
[ Svg.Styled.text "Content" ]
|
||||
, -- Top red line indicator
|
||||
Svg.Styled.g []
|
||||
[ Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "50"
|
||||
, Svg.Styled.Attributes.x2 "50"
|
||||
, Svg.Styled.Attributes.y1 "1"
|
||||
, Svg.Styled.Attributes.y2 "29"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "45"
|
||||
, Svg.Styled.Attributes.x2 "55"
|
||||
, Svg.Styled.Attributes.y1 "1"
|
||||
, Svg.Styled.Attributes.y2 "1"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "45"
|
||||
, Svg.Styled.Attributes.x2 "55"
|
||||
, Svg.Styled.Attributes.y1 "29"
|
||||
, Svg.Styled.Attributes.y2 "29"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.text_
|
||||
[ Svg.Styled.Attributes.fill Colors.red.value
|
||||
, Svg.Styled.Attributes.css [ Fonts.baseFont, Css.fontSize (Css.px 8) ]
|
||||
, Svg.Styled.Attributes.x "53"
|
||||
, Svg.Styled.Attributes.y "20"
|
||||
]
|
||||
[ Svg.Styled.text "30px" ]
|
||||
]
|
||||
, -- Bottom red line indicator
|
||||
Svg.Styled.g []
|
||||
[ Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "50"
|
||||
, Svg.Styled.Attributes.x2 "50"
|
||||
, Svg.Styled.Attributes.y1 "51"
|
||||
, Svg.Styled.Attributes.y2 "99"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "45"
|
||||
, Svg.Styled.Attributes.x2 "55"
|
||||
, Svg.Styled.Attributes.y1 "51"
|
||||
, Svg.Styled.Attributes.y2 "51"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "45"
|
||||
, Svg.Styled.Attributes.x2 "55"
|
||||
, Svg.Styled.Attributes.y1 "99"
|
||||
, Svg.Styled.Attributes.y2 "99"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.text_
|
||||
[ Svg.Styled.Attributes.fill Colors.red.value
|
||||
, Svg.Styled.Attributes.css [ Fonts.baseFont, Css.fontSize (Css.px 8) ]
|
||||
, Svg.Styled.Attributes.x "53"
|
||||
, Svg.Styled.Attributes.y "75"
|
||||
]
|
||||
[ Svg.Styled.text "50px" ]
|
||||
]
|
||||
, -- Right red line indicator
|
||||
Svg.Styled.g []
|
||||
[ Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "86"
|
||||
, Svg.Styled.Attributes.x2 "99"
|
||||
, Svg.Styled.Attributes.y1 "40"
|
||||
, Svg.Styled.Attributes.y2 "40"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "86"
|
||||
, Svg.Styled.Attributes.x2 "86"
|
||||
, Svg.Styled.Attributes.y1 "38"
|
||||
, Svg.Styled.Attributes.y2 "42"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "99"
|
||||
, Svg.Styled.Attributes.x2 "99"
|
||||
, Svg.Styled.Attributes.y1 "38"
|
||||
, Svg.Styled.Attributes.y2 "42"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
]
|
||||
, -- Left red line indicator
|
||||
Svg.Styled.g []
|
||||
[ Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "1"
|
||||
, Svg.Styled.Attributes.x2 "14"
|
||||
, Svg.Styled.Attributes.y1 "40"
|
||||
, Svg.Styled.Attributes.y2 "40"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "1"
|
||||
, Svg.Styled.Attributes.x2 "1"
|
||||
, Svg.Styled.Attributes.y1 "38"
|
||||
, Svg.Styled.Attributes.y2 "42"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
, Svg.Styled.line
|
||||
[ Svg.Styled.Attributes.x1 "14"
|
||||
, Svg.Styled.Attributes.x2 "14"
|
||||
, Svg.Styled.Attributes.y1 "38"
|
||||
, Svg.Styled.Attributes.y2 "42"
|
||||
, Svg.Styled.Attributes.stroke Colors.red.value
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
view : EllieLink.Config -> State -> List (Html Msg)
|
||||
view ellieLinkConfig state =
|
||||
let
|
||||
@ -58,25 +215,8 @@ view ellieLinkConfig state =
|
||||
[ settings.topContainerStyle
|
||||
, settings.horizontalContainerStyle
|
||||
, settings.bottomContainerStyle
|
||||
, if settings.childVerticalSpace then
|
||||
Just
|
||||
( "Css.property \"row-gap\" (.value Spacing.verticalSpacerPx)"
|
||||
, Css.property "row-gap" (.value Spacing.verticalSpacerPx)
|
||||
)
|
||||
|
||||
else
|
||||
Nothing
|
||||
, if settings.childHorizontalSpace then
|
||||
Just
|
||||
( "Css.property \"column-gap\" (.value Spacing.horizontalSpacerPx"
|
||||
, Css.property "column-gap" (.value Spacing.horizontalSpacerPx)
|
||||
)
|
||||
|
||||
else
|
||||
Nothing
|
||||
]
|
||||
)
|
||||
(List.repeat settings.childCount child)
|
||||
in
|
||||
[ ControlView.view
|
||||
{ ellieLinkConfig = ellieLinkConfig
|
||||
@ -94,8 +234,67 @@ view ellieLinkConfig state =
|
||||
}
|
||||
]
|
||||
}
|
||||
, Heading.h2 [ Heading.plaintext "Example" ]
|
||||
, Heading.h2 [ Heading.plaintext "Example", Heading.css [ Css.marginTop Spacing.verticalSpacerPx ] ]
|
||||
, fakePage [ exampleView ]
|
||||
, Heading.h2 [ Heading.plaintext "Content alignment", Heading.css [ Css.marginTop Spacing.verticalSpacerPx ] ]
|
||||
, Table.view
|
||||
[ Table.string
|
||||
{ header = "Name"
|
||||
, value = .name
|
||||
, width = Css.pct 10
|
||||
, cellStyles = always [ Css.padding2 (Css.px 14) (Css.px 7), Css.verticalAlign Css.middle ]
|
||||
, sort = Nothing
|
||||
}
|
||||
, Table.string
|
||||
{ header = "Content alignment"
|
||||
, value = .alignment
|
||||
, width = Css.pct 10
|
||||
, cellStyles = always [ Css.padding2 (Css.px 14) (Css.px 7), Css.verticalAlign Css.middle ]
|
||||
, sort = Nothing
|
||||
}
|
||||
, Table.string
|
||||
{ header = "Content max-width"
|
||||
, value = .maxWidth
|
||||
, width = Css.pct 10
|
||||
, cellStyles = always [ Css.padding2 (Css.px 14) (Css.px 7), Css.verticalAlign Css.middle ]
|
||||
, sort = Nothing
|
||||
}
|
||||
, Table.string
|
||||
{ header = "Side padding"
|
||||
, value = .sidePadding
|
||||
, width = Css.pct 10
|
||||
, cellStyles = always [ Css.padding2 (Css.px 14) (Css.px 7), Css.verticalAlign Css.middle ]
|
||||
, sort = Nothing
|
||||
}
|
||||
]
|
||||
[ { name = "centeredContentWithSidePadding", alignment = "Centered", maxWidth = "1000px", sidePadding = "when viewport <= 970px" }
|
||||
, { name = "centeredContent", alignment = "Centered", maxWidth = "1000px", sidePadding = "0px" }
|
||||
, { name = "centeredQuizEngineContentWithSidePadding", alignment = "Centered", maxWidth = "750px", sidePadding = "when viewport <= 720px" }
|
||||
, { name = "centeredQuizEngineContent", alignment = "Centered", maxWidth = "750px", sidePadding = "0px" }
|
||||
, { name = "centeredContentWithSidePaddingAndCustomWidth", alignment = "Centered", maxWidth = "(customizable)", sidePadding = "when viewport <= (custom breakpoint value - 30)" }
|
||||
, { name = "centeredContentWithCustomWidth", alignment = "Centered", maxWidth = "(customizable)", sidePadding = "0px" }
|
||||
]
|
||||
, Heading.h2 [ Heading.plaintext "Constants", Heading.css [ Css.marginTop Spacing.verticalSpacerPx ] ]
|
||||
, Table.view
|
||||
[ Table.string
|
||||
{ header = "Name"
|
||||
, value = .name
|
||||
, width = Css.pct 10
|
||||
, cellStyles = always [ Css.padding2 (Css.px 14) (Css.px 7), Css.verticalAlign Css.middle ]
|
||||
, sort = Nothing
|
||||
}
|
||||
, Table.string
|
||||
{ header = "Value"
|
||||
, value = .value
|
||||
, width = Css.pct 10
|
||||
, cellStyles = always [ Css.padding2 (Css.px 14) (Css.px 7), Css.verticalAlign Css.middle ]
|
||||
, sort = Nothing
|
||||
}
|
||||
]
|
||||
[ { name = "pageTopWhitespacePx", value = "30px" }
|
||||
, { name = "pageBottomWhitespacePx", value = "50px" }
|
||||
, { name = "pageSideWhitespacePx", value = "15px" }
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
@ -109,53 +308,16 @@ fakePage =
|
||||
]
|
||||
|
||||
|
||||
container : List ( String, Css.Style ) -> List ( String, Html msg ) -> ( String, Html msg )
|
||||
container styles children =
|
||||
( [ "div"
|
||||
, "[ css"
|
||||
, " [ Css.border3 (Css.px 2) Css.dashed Colors.greenDarkest"
|
||||
, " , Css.backgroundColor Colors.greenLightest"
|
||||
, " , Css.property " ++ Code.string "display" ++ " " ++ Code.string "grid"
|
||||
, " , Css.property " ++ Code.string "grid-template-columns" ++ " " ++ Code.string "1fr 1fr 1fr 1fr 1fr"
|
||||
, " , Css.batch " ++ Code.listMultiline (List.map Tuple.first styles) 3
|
||||
, " ]"
|
||||
container : List ( String, Css.Style ) -> ( String, Html msg )
|
||||
container styles =
|
||||
( [ "div [ css " ++ Code.listMultiline (List.map Tuple.first styles) 2
|
||||
, "]"
|
||||
, Code.list (List.map Tuple.first children)
|
||||
, "[ Container.view [ Container.paragraph \"Content...\" ]"
|
||||
]
|
||||
|> String.join (Code.newlineWithIndent 1)
|
||||
, div
|
||||
[ css
|
||||
[ Css.border3 (Css.px 2) Css.dashed Colors.greenDarkest
|
||||
, Css.backgroundColor Colors.greenLightest
|
||||
, Css.property "display" "grid"
|
||||
, Css.property "grid-template-columns" "1fr 1fr 1fr 1fr 1fr"
|
||||
, Css.batch (List.map Tuple.second styles)
|
||||
]
|
||||
, div [ css (List.map Tuple.second styles) ]
|
||||
[ Container.view [ Container.paragraph "Content..." ]
|
||||
]
|
||||
(List.map Tuple.second children)
|
||||
)
|
||||
|
||||
|
||||
child : ( String, Html msg )
|
||||
child =
|
||||
( [ "div"
|
||||
, "[ css"
|
||||
, " [ Css.border3 (Css.px 1) Css.solid Colors.ochreDark"
|
||||
, " , Css.backgroundColor Colors.sunshine"
|
||||
, " , Css.height (Css.px 150)"
|
||||
, " ]"
|
||||
, "]"
|
||||
, "[]"
|
||||
]
|
||||
|> String.join (Code.newlineWithIndent 2)
|
||||
, div
|
||||
[ css
|
||||
[ Css.border3 (Css.px 1) Css.solid Colors.ochreDark
|
||||
, Css.backgroundColor Colors.sunshine
|
||||
, Css.height (Css.px 150)
|
||||
]
|
||||
]
|
||||
[]
|
||||
)
|
||||
|
||||
|
||||
@ -173,9 +335,6 @@ type alias Settings =
|
||||
{ topContainerStyle : Maybe ( String, Style )
|
||||
, horizontalContainerStyle : Maybe ( String, Style )
|
||||
, bottomContainerStyle : Maybe ( String, Style )
|
||||
, childCount : Int
|
||||
, childVerticalSpace : Bool
|
||||
, childHorizontalSpace : Bool
|
||||
}
|
||||
|
||||
|
||||
@ -236,9 +395,6 @@ controlSettings =
|
||||
)
|
||||
)
|
||||
)
|
||||
|> Control.field "Child count" (ControlExtra.int 10)
|
||||
|> Control.field "Separate children vertically" (Control.bool True)
|
||||
|> Control.field "Separate children horizontally" (Control.bool True)
|
||||
|
||||
|
||||
asChoice : ( String, Style ) -> ( String, Control ( String, Style ) )
|
||||
|
@ -1,6 +1,7 @@
|
||||
Nri.Ui.Block.V3,upgrade to V4
|
||||
Nri.Ui.Highlightable.V1,upgrade to V2
|
||||
Nri.Ui.Highlighter.V2,upgrade to V3
|
||||
Nri.Ui.HighlighterToolbar.V1,upgrade to V2
|
||||
Nri.Ui.QuestionBox.V2,upgrade to V4
|
||||
Nri.Ui.QuestionBox.V3,upgrade to V4
|
||||
Nri.Ui.Select.V8,upgrade to V9
|
||||
|
|
1
elm.json
1
elm.json
@ -40,6 +40,7 @@
|
||||
"Nri.Ui.Highlighter.V3",
|
||||
"Nri.Ui.HighlighterTool.V1",
|
||||
"Nri.Ui.HighlighterToolbar.V1",
|
||||
"Nri.Ui.HighlighterToolbar.V2",
|
||||
"Nri.Ui.Html.Attributes.V2",
|
||||
"Nri.Ui.Html.V3",
|
||||
"Nri.Ui.InputStyles.V4",
|
||||
|
@ -87,6 +87,9 @@ hint = 'upgrade to V2'
|
||||
[forbidden."Nri.Ui.Highlighter.V2"]
|
||||
hint = 'upgrade to V3'
|
||||
|
||||
[forbidden."Nri.Ui.HighlighterToolbar.V1"]
|
||||
hint = 'upgrade to V2'
|
||||
|
||||
[forbidden."Nri.Ui.Icon.V3"]
|
||||
hint = 'upgrade to V5'
|
||||
usages = ['component-catalog-app/../src/Nri/Ui/Modal/V3.elm']
|
||||
|
@ -7,6 +7,7 @@ module Nri.Ui.HighlighterToolbar.V1 exposing (view)
|
||||
-}
|
||||
|
||||
import Accessibility.Styled.Aria as Aria
|
||||
import Accessibility.Styled.Role as Role
|
||||
import Css exposing (Color)
|
||||
import EventExtras exposing (onClickPreventDefaultAndStopPropagation)
|
||||
import Html.Styled exposing (..)
|
||||
@ -19,10 +20,13 @@ import Nri.Ui.Svg.V1 as Svg
|
||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||
|
||||
|
||||
toolbar : List (Html msg) -> Html msg
|
||||
toolbar =
|
||||
toolbar : String -> List (Html msg) -> Html msg
|
||||
toolbar highlighterId =
|
||||
ul
|
||||
[ nriDescription "tools"
|
||||
, Role.toolBar
|
||||
, Aria.label "Highlighter options"
|
||||
, Aria.controls [ highlighterId ]
|
||||
, css
|
||||
[ Css.displayFlex
|
||||
, Css.listStyle Css.none
|
||||
@ -46,6 +50,7 @@ view :
|
||||
, onChangeTag : tag -> msg
|
||||
, getColor : tag -> { extras | colorSolid : Color, colorLight : Color }
|
||||
, getName : tag -> String
|
||||
, highlighterId : String
|
||||
}
|
||||
-> { model | currentTool : Maybe tag, tags : List tag }
|
||||
-> Html msg
|
||||
@ -59,7 +64,7 @@ view config model =
|
||||
eraserSelected =
|
||||
model.currentTool == Nothing
|
||||
in
|
||||
toolbar
|
||||
toolbar config.highlighterId
|
||||
(List.map viewTagWithConfig model.tags
|
||||
++ [ viewEraser config.onSetEraser eraserSelected ]
|
||||
)
|
||||
@ -70,6 +75,7 @@ viewTag :
|
||||
, onChangeTag : tag -> msg
|
||||
, getColor : tag -> { extras | colorSolid : Color, colorLight : Color }
|
||||
, getName : tag -> String
|
||||
, highlighterId : String
|
||||
}
|
||||
-> Bool
|
||||
-> tag
|
||||
|
276
src/Nri/Ui/HighlighterToolbar/V2.elm
Normal file
276
src/Nri/Ui/HighlighterToolbar/V2.elm
Normal file
@ -0,0 +1,276 @@
|
||||
module Nri.Ui.HighlighterToolbar.V2 exposing (view)
|
||||
|
||||
{-| Bar with markers for choosing how text will be highlighted in a highlighter.
|
||||
|
||||
@docs view
|
||||
|
||||
Changes from V1:
|
||||
|
||||
- replaces `onChangeTag` and `onSetEraser` with `focusAndSelect`.
|
||||
- adds `highlighterId` to config
|
||||
- adds keyboard navigation
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled.Aria as Aria
|
||||
import Accessibility.Styled.Key as Key
|
||||
import Accessibility.Styled.Role as Role
|
||||
import Css exposing (Color)
|
||||
import EventExtras exposing (onClickPreventDefaultAndStopPropagation)
|
||||
import Html.Styled exposing (..)
|
||||
import Html.Styled.Attributes exposing (css, id, tabindex)
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.Html.Attributes.V2 exposing (nriDescription)
|
||||
import Nri.Ui.Html.V3 exposing (viewIf)
|
||||
import Nri.Ui.Svg.V1 as Svg
|
||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||
|
||||
|
||||
toolbar : String -> List (Html msg) -> Html msg
|
||||
toolbar highlighterId =
|
||||
div
|
||||
[ nriDescription "tools"
|
||||
, Role.toolBar
|
||||
, Aria.label "Highlighter options"
|
||||
, Aria.controls [ highlighterId ]
|
||||
, css
|
||||
[ Css.displayFlex
|
||||
, Css.listStyle Css.none
|
||||
, Css.padding (Css.px 0)
|
||||
, Css.margin (Css.px 0)
|
||||
, Css.marginTop (Css.px 10)
|
||||
, Css.flexWrap Css.wrap
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
toolContainer : String -> Html msg -> Html msg
|
||||
toolContainer toolName tool =
|
||||
div [ nriDescription toolName ] [ tool ]
|
||||
|
||||
|
||||
{-| View renders each marker and an eraser. This is usually used with a Highlighter.
|
||||
-}
|
||||
view :
|
||||
{ focusAndSelect : { select : Maybe tag, focus : Maybe String } -> msg
|
||||
, getColor : tag -> { extras | colorSolid : Color, colorLight : Color }
|
||||
, getName : tag -> String
|
||||
, highlighterId : String
|
||||
}
|
||||
-> { model | currentTool : Maybe tag, tags : List tag }
|
||||
-> Html msg
|
||||
view config model =
|
||||
let
|
||||
tools =
|
||||
List.map Just model.tags ++ [ Nothing ]
|
||||
|
||||
viewTagWithConfig : tag -> Html msg
|
||||
viewTagWithConfig tag =
|
||||
viewTag config (model.currentTool == Just tag) tag tools model.currentTool
|
||||
|
||||
eraserSelected : Bool
|
||||
eraserSelected =
|
||||
model.currentTool == Nothing
|
||||
in
|
||||
toolbar config.highlighterId
|
||||
(List.map viewTagWithConfig model.tags
|
||||
++ [ viewEraser config.focusAndSelect eraserSelected tools model.currentTool config.getName ]
|
||||
)
|
||||
|
||||
|
||||
viewTag :
|
||||
{ focusAndSelect : { select : Maybe tag, focus : Maybe String } -> msg
|
||||
, getColor : tag -> { extras | colorSolid : Color, colorLight : Color }
|
||||
, getName : tag -> String
|
||||
, highlighterId : String
|
||||
}
|
||||
-> Bool
|
||||
-> tag
|
||||
-> List (Maybe tag)
|
||||
-> Maybe tag
|
||||
-> Html msg
|
||||
viewTag { focusAndSelect, getColor, getName } selected tag tools currentTool =
|
||||
toolContainer ("tag-" ++ getName tag)
|
||||
(viewTool (getName tag) focusAndSelect (getColor tag) selected (Just tag) tools currentTool getName)
|
||||
|
||||
|
||||
viewEraser :
|
||||
({ select : Maybe tag, focus : Maybe String } -> msg)
|
||||
-> Bool
|
||||
-> List (Maybe tag)
|
||||
-> Maybe tag
|
||||
-> (tag -> String)
|
||||
-> Html msg
|
||||
viewEraser focusAndSelect selected tools currentTool getName =
|
||||
toolContainer "eraser"
|
||||
(viewTool "Remove highlight"
|
||||
focusAndSelect
|
||||
{ colorLight = Colors.gray75, colorSolid = Colors.white }
|
||||
selected
|
||||
Nothing
|
||||
tools
|
||||
currentTool
|
||||
getName
|
||||
)
|
||||
|
||||
|
||||
viewTool :
|
||||
String
|
||||
-> ({ select : Maybe tag, focus : Maybe String } -> msg)
|
||||
-> { extras | colorSolid : Color, colorLight : Color }
|
||||
-> Bool
|
||||
-> Maybe tag
|
||||
-> List (Maybe tag)
|
||||
-> Maybe tag
|
||||
-> (tag -> String)
|
||||
-> Html msg
|
||||
viewTool name focusAndSelect theme selected tag tools currentTool getName =
|
||||
button
|
||||
[ id ("tag-" ++ name)
|
||||
, css
|
||||
[ Css.backgroundColor Css.transparent
|
||||
, Css.borderRadius (Css.px 0)
|
||||
, Css.border (Css.px 0)
|
||||
, Css.active [ Css.outlineStyle Css.none ]
|
||||
, Css.focus [ Css.outlineStyle Css.none ]
|
||||
, Css.cursor Css.pointer
|
||||
]
|
||||
, onClickPreventDefaultAndStopPropagation (focusAndSelect { select = tag, focus = Nothing })
|
||||
, Aria.pressed (Just selected)
|
||||
, tabindex
|
||||
(if selected then
|
||||
0
|
||||
|
||||
else
|
||||
-1
|
||||
)
|
||||
, Key.onKeyDownPreventDefault (keyEvents focusAndSelect currentTool tools getName)
|
||||
]
|
||||
[ toolContent name theme tag
|
||||
, viewIf (\() -> active theme) selected
|
||||
]
|
||||
|
||||
|
||||
keyEvents : ({ select : Maybe tag, focus : Maybe String } -> msg) -> Maybe tag -> List (Maybe tag) -> (tag -> String) -> List (Key.Event msg)
|
||||
keyEvents focusAndSelect tag tools getName =
|
||||
let
|
||||
onFocus tag_ =
|
||||
focusAndSelect
|
||||
{ select = tag_
|
||||
, focus =
|
||||
case tag_ of
|
||||
Just tag__ ->
|
||||
Just ("tag-" ++ getName tag__)
|
||||
|
||||
Nothing ->
|
||||
Just "tag-Remove highlight"
|
||||
}
|
||||
|
||||
findAdjacentTag tag_ ( isAdjacentTab, acc ) =
|
||||
if isAdjacentTab then
|
||||
( False, Just (onFocus tag_) )
|
||||
|
||||
else
|
||||
( tag_ == tag, acc )
|
||||
|
||||
goToNextTag : Maybe msg
|
||||
goToNextTag =
|
||||
List.foldl findAdjacentTag
|
||||
( False
|
||||
, -- if there is no adjacent tag, default to the first tag
|
||||
Maybe.map onFocus (List.head tools)
|
||||
)
|
||||
tools
|
||||
|> Tuple.second
|
||||
|
||||
goToPreviousTag : Maybe msg
|
||||
goToPreviousTag =
|
||||
List.foldr findAdjacentTag
|
||||
( False
|
||||
, -- if there is no adjacent tag, default to the last tag
|
||||
Maybe.map onFocus (List.head (List.reverse tools))
|
||||
)
|
||||
tools
|
||||
|> Tuple.second
|
||||
in
|
||||
List.filterMap identity
|
||||
[ Maybe.map Key.right goToNextTag
|
||||
, Maybe.map Key.left goToPreviousTag
|
||||
]
|
||||
|
||||
|
||||
active :
|
||||
{ extras | colorLight : Color }
|
||||
-> Html msg
|
||||
active palette_ =
|
||||
div
|
||||
[ nriDescription "active-tool"
|
||||
, css
|
||||
[ Css.width (Css.px 38)
|
||||
, Css.height (Css.px 4)
|
||||
, Css.backgroundColor palette_.colorLight
|
||||
]
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
toolContent :
|
||||
String
|
||||
-> { extras | colorSolid : Color, colorLight : Color }
|
||||
-> Maybe tag
|
||||
-> Html msg
|
||||
toolContent name palette_ tool =
|
||||
span
|
||||
[ nriDescription "tool-content"
|
||||
, css
|
||||
[ Css.position Css.relative
|
||||
, Css.height (Css.pct 100)
|
||||
, Css.padding (Css.px 0)
|
||||
, Css.paddingRight (Css.px 15)
|
||||
, Css.display Css.inlineFlex
|
||||
, Css.alignItems Css.center
|
||||
]
|
||||
]
|
||||
[ case tool of
|
||||
Just _ ->
|
||||
toolIcon
|
||||
{ background = palette_.colorSolid
|
||||
, border = palette_.colorSolid
|
||||
, icon = Svg.withColor Colors.white UiIcon.highlighter
|
||||
}
|
||||
|
||||
Nothing ->
|
||||
toolIcon
|
||||
{ background = palette_.colorSolid
|
||||
, border = Colors.gray75
|
||||
, icon = Svg.withColor Colors.gray20 UiIcon.eraser
|
||||
}
|
||||
, span
|
||||
[ nriDescription "tool-label"
|
||||
, css
|
||||
[ Css.color Colors.navy
|
||||
, Css.fontSize (Css.px 15)
|
||||
, Css.marginLeft (Css.px 5)
|
||||
, Css.fontWeight (Css.int 600)
|
||||
, Fonts.baseFont
|
||||
]
|
||||
]
|
||||
[ text name ]
|
||||
]
|
||||
|
||||
|
||||
toolIcon : { background : Color, border : Color, icon : Svg.Svg } -> Html msg
|
||||
toolIcon config =
|
||||
span
|
||||
[ css
|
||||
[ Css.backgroundColor config.background
|
||||
, Css.width (Css.px 38)
|
||||
, Css.height (Css.px 38)
|
||||
, Css.borderRadius (Css.pct 50)
|
||||
, Css.padding (Css.px 7)
|
||||
, Css.border3 (Css.px 1) Css.solid config.border
|
||||
]
|
||||
]
|
||||
[ Svg.toHtml config.icon
|
||||
]
|
@ -23,6 +23,10 @@ module Nri.Ui.Spacing.V1 exposing
|
||||
@docs pageTopWhitespace, pageTopWhitespacePx
|
||||
@docs pageSideWhitespace, pageSideWhitespacePx
|
||||
@docs pageBottomWhitespace, pageBottomWhitespacePx
|
||||
|
||||
|
||||
## Deprecated:
|
||||
|
||||
@docs verticalSpacerPx, horizontalSpacerPx
|
||||
|
||||
-}
|
||||
@ -168,20 +172,14 @@ pageTopWhitespacePx =
|
||||
Css.px 30
|
||||
|
||||
|
||||
{-| Most elements should have 20px of whitespace separating them vertically.
|
||||
|
||||
See [the UI Style Guide and Caveats' Spacing section](https://paper.dropbox.com/doc/UI-Style-Guide-and-Caveats--BobQllelpdS56NBITiRcrO6gAg-PvOLxeX3oyujYEzdJx5pu#:uid=905917270049954035442315&h2=:under-construction:-Spacing) for more details.
|
||||
|
||||
{-| DEPRECATED -- do not use. A future version of noredink-ui will remove.
|
||||
-}
|
||||
verticalSpacerPx : Css.Px
|
||||
verticalSpacerPx =
|
||||
Css.px 20
|
||||
|
||||
|
||||
{-| Most elements should have 10px of whitespace separating them horizontally.
|
||||
|
||||
See [the UI Style Guide and Caveats' Spacing section](https://paper.dropbox.com/doc/UI-Style-Guide-and-Caveats--BobQllelpdS56NBITiRcrO6gAg-PvOLxeX3oyujYEzdJx5pu#:uid=905917270049954035442315&h2=:under-construction:-Spacing) for more details.
|
||||
|
||||
{-| DEPRECATED -- do not use. A future version of noredink-ui will remove.
|
||||
-}
|
||||
horizontalSpacerPx : Css.Px
|
||||
horizontalSpacerPx =
|
||||
|
@ -1,13 +1,15 @@
|
||||
module Spec.Nri.Ui.HighlighterToolbar exposing (..)
|
||||
|
||||
import Accessibility.Aria as Aria
|
||||
import Accessibility.Key as Key
|
||||
import Css exposing (Color)
|
||||
import Expect
|
||||
import Html.Attributes as Attributes
|
||||
import Html.Styled as Html exposing (..)
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.HighlighterToolbar.V1 as HighlighterToolbar
|
||||
import Nri.Ui.HighlighterToolbar.V2 as HighlighterToolbar
|
||||
import ProgramTest exposing (..)
|
||||
import Spec.KeyboardHelpers as KeyboardHelpers
|
||||
import Test exposing (..)
|
||||
import Test.Html.Event as Event
|
||||
import Test.Html.Query as Query
|
||||
@ -16,8 +18,9 @@ import Test.Html.Selector as Selector
|
||||
|
||||
spec : Test
|
||||
spec =
|
||||
describe "Nri.Ui.HighlighterToolbar.V1"
|
||||
describe "Nri.Ui.HighlighterToolbar.V2"
|
||||
[ describe "tool selection" selectionTests
|
||||
, describe "keyboard behavior" keyboardTests
|
||||
]
|
||||
|
||||
|
||||
@ -43,6 +46,83 @@ selectionTests =
|
||||
]
|
||||
|
||||
|
||||
keyboardTests : List Test
|
||||
keyboardTests =
|
||||
[ test "has a focusable tool" <|
|
||||
\() ->
|
||||
program
|
||||
|> ensureTabbable "Remove highlight"
|
||||
|> done
|
||||
, test "has only one tool included in the tab sequence" <|
|
||||
\() ->
|
||||
program
|
||||
|> ensureOnlyOneInTabSequence [ "Claim", "Evidence", "Reasoning", "Remove highlight" ]
|
||||
|> done
|
||||
, test "moves focus right on right arrow key. Should wrap focus on last element." <|
|
||||
\() ->
|
||||
program
|
||||
|> ensureTabbable "Remove highlight"
|
||||
|> rightArrow
|
||||
|> ensureTabbable "Claim"
|
||||
|> ensureOnlyOneInTabSequence [ "Claim", "Evidence", "Reasoning", "Remove highlight" ]
|
||||
|> rightArrow
|
||||
|> ensureTabbable "Evidence"
|
||||
|> rightArrow
|
||||
|> ensureTabbable "Reasoning"
|
||||
|> rightArrow
|
||||
|> ensureTabbable "Remove highlight"
|
||||
|> done
|
||||
, test "moves focus left on left arrow key. Should wrap focus on first element." <|
|
||||
\() ->
|
||||
program
|
||||
|> ensureTabbable "Remove highlight"
|
||||
|> leftArrow
|
||||
|> ensureTabbable "Reasoning"
|
||||
|> leftArrow
|
||||
|> ensureTabbable "Evidence"
|
||||
|> leftArrow
|
||||
|> ensureTabbable "Claim"
|
||||
|> leftArrow
|
||||
|> ensureTabbable "Remove highlight"
|
||||
|> ensureOnlyOneInTabSequence [ "Claim", "Evidence", "Reasoning", "Remove highlight" ]
|
||||
|> done
|
||||
]
|
||||
|
||||
|
||||
ensureTabbable : String -> TestContext -> TestContext
|
||||
ensureTabbable word testContext =
|
||||
testContext
|
||||
|> ensureView
|
||||
(Query.find [ Selector.attribute (Key.tabbable True) ]
|
||||
>> Query.has [ Selector.text word ]
|
||||
)
|
||||
|
||||
|
||||
ensureOnlyOneInTabSequence : List String -> TestContext -> TestContext
|
||||
ensureOnlyOneInTabSequence words testContext =
|
||||
testContext
|
||||
|> ensureView
|
||||
(Query.findAll [ Selector.attribute (Key.tabbable True) ]
|
||||
>> Query.count (Expect.equal 1)
|
||||
)
|
||||
|> ensureView
|
||||
(Query.findAll [ Selector.attribute (Key.tabbable False) ]
|
||||
>> Query.count (Expect.equal (List.length words - 1))
|
||||
)
|
||||
|
||||
|
||||
rightArrow : TestContext -> TestContext
|
||||
rightArrow =
|
||||
KeyboardHelpers.pressRightArrow { targetDetails = [] }
|
||||
[ Selector.attribute (Key.tabbable True) ]
|
||||
|
||||
|
||||
leftArrow : TestContext -> TestContext
|
||||
leftArrow =
|
||||
KeyboardHelpers.pressLeftArrow { targetDetails = [] }
|
||||
[ Selector.attribute (Key.tabbable True) ]
|
||||
|
||||
|
||||
clickTool : String -> ProgramTest model msg effect -> ProgramTest model msg effect
|
||||
clickTool label =
|
||||
ProgramTest.simulateDomEvent
|
||||
@ -152,24 +232,24 @@ init =
|
||||
|
||||
{-| -}
|
||||
type Msg
|
||||
= SetTool (Maybe Tag)
|
||||
= FocusAndSelectTag { select : Maybe Tag, focus : Maybe String }
|
||||
|
||||
|
||||
{-| -}
|
||||
update : Msg -> State -> State
|
||||
update msg state =
|
||||
case msg of
|
||||
SetTool tag ->
|
||||
{ state | currentTool = tag }
|
||||
FocusAndSelectTag { select } ->
|
||||
{ state | currentTool = select }
|
||||
|
||||
|
||||
view : State -> Html Msg
|
||||
view model =
|
||||
HighlighterToolbar.view
|
||||
{ onSetEraser = SetTool Nothing
|
||||
, onChangeTag = SetTool << Just
|
||||
{ focusAndSelect = FocusAndSelectTag
|
||||
, getColor = getColor
|
||||
, getName = getName
|
||||
, highlighterId = "highlighter"
|
||||
}
|
||||
{ currentTool = model.currentTool
|
||||
, tags = tags
|
||||
|
@ -91,10 +91,6 @@ keyboardTests =
|
||||
]
|
||||
|
||||
|
||||
type alias TestContext =
|
||||
ProgramTest State Msg ()
|
||||
|
||||
|
||||
update : Msg -> State -> State
|
||||
update msg model =
|
||||
case msg of
|
||||
@ -126,6 +122,10 @@ view model =
|
||||
}
|
||||
|
||||
|
||||
type alias TestContext =
|
||||
ProgramTest State Msg ()
|
||||
|
||||
|
||||
program : TestContext
|
||||
program =
|
||||
ProgramTest.createSandbox
|
||||
|
@ -36,6 +36,7 @@
|
||||
"Nri.Ui.Highlighter.V3",
|
||||
"Nri.Ui.HighlighterTool.V1",
|
||||
"Nri.Ui.HighlighterToolbar.V1",
|
||||
"Nri.Ui.HighlighterToolbar.V2",
|
||||
"Nri.Ui.Html.Attributes.V2",
|
||||
"Nri.Ui.Html.V3",
|
||||
"Nri.Ui.InputStyles.V4",
|
||||
|
Loading…
Reference in New Issue
Block a user