mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-24 00:42:29 +03:00
copy v11 -> v12
This commit is contained in:
parent
f4a4c9aad5
commit
85bcfbe424
1
elm.json
1
elm.json
@ -44,6 +44,7 @@
|
|||||||
"Nri.Ui.PremiumCheckbox.V6",
|
"Nri.Ui.PremiumCheckbox.V6",
|
||||||
"Nri.Ui.RadioButton.V1",
|
"Nri.Ui.RadioButton.V1",
|
||||||
"Nri.Ui.SegmentedControl.V11",
|
"Nri.Ui.SegmentedControl.V11",
|
||||||
|
"Nri.Ui.SegmentedControl.V12",
|
||||||
"Nri.Ui.Select.V5",
|
"Nri.Ui.Select.V5",
|
||||||
"Nri.Ui.Select.V7",
|
"Nri.Ui.Select.V7",
|
||||||
"Nri.Ui.Slide.V1",
|
"Nri.Ui.Slide.V1",
|
||||||
|
276
src/Nri/Ui/SegmentedControl/V12.elm
Normal file
276
src/Nri/Ui/SegmentedControl/V12.elm
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
module Nri.Ui.SegmentedControl.V12 exposing
|
||||||
|
( Option, view
|
||||||
|
, Radio, viewRadioGroup
|
||||||
|
, Width(..)
|
||||||
|
)
|
||||||
|
|
||||||
|
{-| Changes from V10:
|
||||||
|
|
||||||
|
- change selection using left/right arrow keys
|
||||||
|
- only currently-selected or first control is tabbable
|
||||||
|
- tabpanel is tabbable
|
||||||
|
- Uses TabsInternal under the hood
|
||||||
|
- `viewSelect` renamed to `viewRadioGroup`, `SelectOption` renamed to `Radio`
|
||||||
|
- `viewRadioGroup` uses native HTML radio input internally
|
||||||
|
|
||||||
|
@docs Option, view
|
||||||
|
@docs Radio, viewRadioGroup
|
||||||
|
@docs Width
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Accessibility.Styled exposing (..)
|
||||||
|
import Accessibility.Styled.Aria as Aria
|
||||||
|
import Accessibility.Styled.Role as Role
|
||||||
|
import Accessibility.Styled.Style as Style
|
||||||
|
import Accessibility.Styled.Widget as Widget
|
||||||
|
import Css exposing (..)
|
||||||
|
import EventExtras
|
||||||
|
import Html.Styled
|
||||||
|
import Html.Styled.Attributes as Attributes exposing (css, href)
|
||||||
|
import Html.Styled.Events as Events
|
||||||
|
import Json.Encode as Encode
|
||||||
|
import Nri.Ui
|
||||||
|
import Nri.Ui.Colors.Extra exposing (withAlpha)
|
||||||
|
import Nri.Ui.Colors.V1 as Colors
|
||||||
|
import Nri.Ui.Fonts.V1 as Fonts
|
||||||
|
import Nri.Ui.Html.Attributes.V2 as AttributesExtra
|
||||||
|
import Nri.Ui.Svg.V1 as Svg exposing (Svg)
|
||||||
|
import Nri.Ui.Util exposing (dashify)
|
||||||
|
import TabsInternal
|
||||||
|
|
||||||
|
|
||||||
|
{-| -}
|
||||||
|
type Width
|
||||||
|
= FitContent
|
||||||
|
| FillContainer
|
||||||
|
|
||||||
|
|
||||||
|
{-| -}
|
||||||
|
type alias Radio value msg =
|
||||||
|
{ value : value
|
||||||
|
, label : String
|
||||||
|
, attributes : List (Attribute msg)
|
||||||
|
, icon : Maybe Svg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-| Creates a set of radio buttons styled to look like a segmented control.
|
||||||
|
|
||||||
|
- `onSelect`: the message to produce when an option is selected (clicked) by the user
|
||||||
|
- `toString`: function to get the radio value as a string
|
||||||
|
- `options`: the list of options available
|
||||||
|
- `selected`: if present, the value of the currently-selected option
|
||||||
|
- `width`: how to size the segmented control
|
||||||
|
- `legend`:
|
||||||
|
- value read to screenreader users to explain the radio group's purpose <https://dequeuniversity.com/rules/axe/3.3/radiogroup?application=axeAPI>
|
||||||
|
- after lowercasing & dashifying, this value is used to group the radio buttons together
|
||||||
|
|
||||||
|
-}
|
||||||
|
viewRadioGroup :
|
||||||
|
{ onSelect : a -> msg
|
||||||
|
, toString : a -> String
|
||||||
|
, options : List (Radio a msg)
|
||||||
|
, selected : Maybe a
|
||||||
|
, width : Width
|
||||||
|
, legend : String
|
||||||
|
}
|
||||||
|
-> Html msg
|
||||||
|
viewRadioGroup config =
|
||||||
|
let
|
||||||
|
viewRadio option =
|
||||||
|
let
|
||||||
|
isSelected =
|
||||||
|
Just option.value == config.selected
|
||||||
|
in
|
||||||
|
labelAfter
|
||||||
|
[ 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.width isSelected
|
||||||
|
)
|
||||||
|
]
|
||||||
|
(div [] [ viewIcon option.icon, text option.label ])
|
||||||
|
(radio name (config.toString option.value) isSelected <|
|
||||||
|
(Events.onCheck (\_ -> config.onSelect option.value)
|
||||||
|
:: css [ Css.opacity Css.zero ]
|
||||||
|
:: Attributes.attribute "data-nri-checked"
|
||||||
|
(if isSelected then
|
||||||
|
"true"
|
||||||
|
|
||||||
|
else
|
||||||
|
"false"
|
||||||
|
)
|
||||||
|
:: Style.invisible
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
name =
|
||||||
|
dashify (String.toLower config.legend)
|
||||||
|
|
||||||
|
legendId =
|
||||||
|
"legend-" ++ name
|
||||||
|
in
|
||||||
|
div
|
||||||
|
[ Role.radioGroup
|
||||||
|
, Aria.labelledBy legendId
|
||||||
|
, css [ displayFlex, cursor pointer ]
|
||||||
|
]
|
||||||
|
(p (Attributes.id legendId :: Style.invisible) [ text config.legend ]
|
||||||
|
:: List.map viewRadio config.options
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
{-| -}
|
||||||
|
type alias Option value msg =
|
||||||
|
{ value : value
|
||||||
|
, label : String
|
||||||
|
, attributes : List (Attribute msg)
|
||||||
|
, icon : Maybe Svg
|
||||||
|
, content : Html msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-|
|
||||||
|
|
||||||
|
- `onSelect` : the message to produce when an option is selected by the user
|
||||||
|
- `onFocus` : the message to focus an element by id string
|
||||||
|
- `toString` : function to get the option value as a string
|
||||||
|
- `options`: the list of options available
|
||||||
|
- `selected`: the value of the currently-selected option
|
||||||
|
- `width`: how to size the segmented control
|
||||||
|
- `toUrl`: a optional function that takes a `route` and returns the URL of that route. You should always use pass a `toUrl` function when the segmented control options correspond to routes in your SPA.
|
||||||
|
|
||||||
|
-}
|
||||||
|
view :
|
||||||
|
{ onSelect : a -> msg
|
||||||
|
, onFocus : String -> msg
|
||||||
|
, toString : a -> String
|
||||||
|
, options : List (Option a msg)
|
||||||
|
, selected : a
|
||||||
|
, width : Width
|
||||||
|
, toUrl : Maybe (a -> String)
|
||||||
|
}
|
||||||
|
-> Html msg
|
||||||
|
view config =
|
||||||
|
let
|
||||||
|
toInternalTab : Option a msg -> TabsInternal.Tab a msg
|
||||||
|
toInternalTab option =
|
||||||
|
{ id = option.value
|
||||||
|
, idString = config.toString option.value
|
||||||
|
, tabAttributes = option.attributes
|
||||||
|
, tabView = [ viewIcon option.icon, text option.label ]
|
||||||
|
, panelView = option.content
|
||||||
|
, spaHref = Maybe.map (\toUrl -> toUrl option.value) config.toUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
{ tabList, tabPanels } =
|
||||||
|
TabsInternal.views
|
||||||
|
{ onSelect = config.onSelect
|
||||||
|
, onFocus = config.onFocus
|
||||||
|
, selected = config.selected
|
||||||
|
, tabs = List.map toInternalTab config.options
|
||||||
|
, tabListStyles = [ displayFlex, cursor pointer, marginBottom (px 10) ]
|
||||||
|
, tabStyles = styles config.width
|
||||||
|
}
|
||||||
|
in
|
||||||
|
div []
|
||||||
|
[ tabList
|
||||||
|
, tabPanels
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewIcon : Maybe Svg.Svg -> Html msg
|
||||||
|
viewIcon icon =
|
||||||
|
case icon of
|
||||||
|
Nothing ->
|
||||||
|
text ""
|
||||||
|
|
||||||
|
Just svg ->
|
||||||
|
svg
|
||||||
|
|> Svg.withWidth (px 18)
|
||||||
|
|> Svg.withHeight (px 18)
|
||||||
|
|> Svg.withCss
|
||||||
|
[ display inlineBlock
|
||||||
|
, verticalAlign textTop
|
||||||
|
, lineHeight (px 15)
|
||||||
|
, marginRight (px 8)
|
||||||
|
]
|
||||||
|
|> Svg.toHtml
|
||||||
|
|
||||||
|
|
||||||
|
styles : Width -> Bool -> List Style
|
||||||
|
styles width isSelected =
|
||||||
|
[ sharedSegmentStyles
|
||||||
|
, if isSelected then
|
||||||
|
focusedSegmentStyles
|
||||||
|
|
||||||
|
else
|
||||||
|
unFocusedSegmentStyles
|
||||||
|
, case width of
|
||||||
|
FitContent ->
|
||||||
|
Css.batch []
|
||||||
|
|
||||||
|
FillContainer ->
|
||||||
|
expandingTabStyles
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
sharedSegmentStyles : Style
|
||||||
|
sharedSegmentStyles =
|
||||||
|
[ padding2 (px 6) (px 20)
|
||||||
|
, height (px 45)
|
||||||
|
, Fonts.baseFont
|
||||||
|
, fontSize (px 15)
|
||||||
|
, fontWeight bold
|
||||||
|
, lineHeight (px 30)
|
||||||
|
, margin zero
|
||||||
|
, firstOfType
|
||||||
|
[ borderTopLeftRadius (px 8)
|
||||||
|
, borderBottomLeftRadius (px 8)
|
||||||
|
, borderLeft3 (px 1) solid Colors.azure
|
||||||
|
]
|
||||||
|
, lastOfType
|
||||||
|
[ borderTopRightRadius (px 8)
|
||||||
|
, borderBottomRightRadius (px 8)
|
||||||
|
]
|
||||||
|
, border3 (px 1) solid Colors.azure
|
||||||
|
, borderLeft (px 0)
|
||||||
|
, boxSizing borderBox
|
||||||
|
, cursor pointer
|
||||||
|
, property "transition" "background-color 0.2s, color 0.2s, box-shadow 0.2s, border 0.2s, border-width 0s"
|
||||||
|
, textDecoration none
|
||||||
|
, hover [ textDecoration none ]
|
||||||
|
, focus [ textDecoration none ]
|
||||||
|
]
|
||||||
|
|> Css.batch
|
||||||
|
|
||||||
|
|
||||||
|
focusedSegmentStyles : Style
|
||||||
|
focusedSegmentStyles =
|
||||||
|
[ backgroundColor Colors.glacier
|
||||||
|
, boxShadow5 inset zero (px 3) zero (withAlpha 0.2 Colors.gray20)
|
||||||
|
, color Colors.navy
|
||||||
|
]
|
||||||
|
|> Css.batch
|
||||||
|
|
||||||
|
|
||||||
|
unFocusedSegmentStyles : Style
|
||||||
|
unFocusedSegmentStyles =
|
||||||
|
[ backgroundColor Colors.white
|
||||||
|
, boxShadow5 inset zero (px -2) zero Colors.azure
|
||||||
|
, color Colors.azure
|
||||||
|
, hover [ backgroundColor Colors.frost ]
|
||||||
|
]
|
||||||
|
|> Css.batch
|
||||||
|
|
||||||
|
|
||||||
|
expandingTabStyles : Style
|
||||||
|
expandingTabStyles =
|
||||||
|
[ flexGrow (int 1)
|
||||||
|
, textAlign center
|
||||||
|
]
|
||||||
|
|> Css.batch
|
@ -21,7 +21,7 @@ import Html.Styled.Attributes as Attributes exposing (css)
|
|||||||
import Html.Styled.Events as Events
|
import Html.Styled.Events as Events
|
||||||
import KeyboardSupport exposing (Direction(..), Key(..))
|
import KeyboardSupport exposing (Direction(..), Key(..))
|
||||||
import Nri.Ui.Colors.V1 as Colors
|
import Nri.Ui.Colors.V1 as Colors
|
||||||
import Nri.Ui.SegmentedControl.V11 as SegmentedControl
|
import Nri.Ui.SegmentedControl.V12 as SegmentedControl
|
||||||
import Nri.Ui.Svg.V1 as Svg exposing (Svg)
|
import Nri.Ui.Svg.V1 as Svg exposing (Svg)
|
||||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||||
import String exposing (toLower)
|
import String exposing (toLower)
|
||||||
@ -31,7 +31,7 @@ import Task
|
|||||||
{-| -}
|
{-| -}
|
||||||
example : Example State Msg
|
example : Example State Msg
|
||||||
example =
|
example =
|
||||||
{ name = "Nri.Ui.SegmentedControl.V11"
|
{ name = "Nri.Ui.SegmentedControl.V12"
|
||||||
, state = init
|
, state = init
|
||||||
, update = update
|
, update = update
|
||||||
, subscriptions = \_ -> Sub.none
|
, subscriptions = \_ -> Sub.none
|
||||||
|
@ -4,7 +4,7 @@ import Expect
|
|||||||
import Html.Attributes as Attributes
|
import Html.Attributes as Attributes
|
||||||
import Html.Styled
|
import Html.Styled
|
||||||
import Json.Encode as Encode
|
import Json.Encode as Encode
|
||||||
import Nri.Ui.SegmentedControl.V11 as SegmentedControl
|
import Nri.Ui.SegmentedControl.V12 as SegmentedControl
|
||||||
import Test exposing (..)
|
import Test exposing (..)
|
||||||
import Test.Html.Query as Query
|
import Test.Html.Query as Query
|
||||||
import Test.Html.Selector as Selector
|
import Test.Html.Selector as Selector
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
"Nri.Ui.PremiumCheckbox.V6",
|
"Nri.Ui.PremiumCheckbox.V6",
|
||||||
"Nri.Ui.RadioButton.V1",
|
"Nri.Ui.RadioButton.V1",
|
||||||
"Nri.Ui.SegmentedControl.V11",
|
"Nri.Ui.SegmentedControl.V11",
|
||||||
|
"Nri.Ui.SegmentedControl.V12",
|
||||||
"Nri.Ui.Select.V5",
|
"Nri.Ui.Select.V5",
|
||||||
"Nri.Ui.Select.V7",
|
"Nri.Ui.Select.V7",
|
||||||
"Nri.Ui.Slide.V1",
|
"Nri.Ui.Slide.V1",
|
||||||
|
Loading…
Reference in New Issue
Block a user