From b6b41109e5f11360f66f23bef9d0203d40880353 Mon Sep 17 00:00:00 2001 From: Tessa Kelly Date: Wed, 5 Aug 2020 13:14:51 -0700 Subject: [PATCH] cp SegmentedControl/V10.elm SegmentedControl/V11.elm --- elm.json | 1 + src/Nri/Ui/SegmentedControl/V11.elm | 305 +++++++++++++++++++ styleguide-app/Examples/SegmentedControl.elm | 4 +- tests/elm-verify-examples.json | 1 + 4 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 src/Nri/Ui/SegmentedControl/V11.elm diff --git a/elm.json b/elm.json index 9042b739..a6da9a79 100644 --- a/elm.json +++ b/elm.json @@ -45,6 +45,7 @@ "Nri.Ui.RadioButton.V1", "Nri.Ui.SegmentedControl.V9", "Nri.Ui.SegmentedControl.V10", + "Nri.Ui.SegmentedControl.V11", "Nri.Ui.Select.V5", "Nri.Ui.Select.V7", "Nri.Ui.Slide.V1", diff --git a/src/Nri/Ui/SegmentedControl/V11.elm b/src/Nri/Ui/SegmentedControl/V11.elm new file mode 100644 index 00000000..ff7cafab --- /dev/null +++ b/src/Nri/Ui/SegmentedControl/V11.elm @@ -0,0 +1,305 @@ +module Nri.Ui.SegmentedControl.V11 exposing + ( Option, view + , SelectOption, viewSelect + , Width(..) + ) + +{-| Changes from V9: + + - hides non-displayed content rather than fully removing from the DOM, allowing for the content the SegmentedControl controls to have overflowY: auto & maintain scroll position + - 💀 removes NavConfig and SelectConfig + - combines `view` and `viewSpa` (for V9 `view` behavior, be sure `toUrl` is Nothing. for V9 `viewSpa` behavior, pass through a Just as `toUrl`) + - add custom attributes hole to the Option (in order to make SegmentedControls compatible with the Modal component) + - combine `css` attributes into one to prevent class-name-order-change css :bug:s + - :bug: fix overflowing-y svg icon issue + +@docs Option, view +@docs SelectOption, viewSelect +@docs Width + +-} + +import Accessibility.Styled exposing (..) +import Accessibility.Styled.Aria as Aria +import Accessibility.Styled.Role as Role +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 Html.Styled.Keyed as Keyed +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) + + +{-| -} +type Width + = FitContent + | FillContainer + + +{-| -} +type alias SelectOption value msg = + { value : value + , label : String + , attributes : List (Attribute msg) + , icon : Maybe Svg + } + + +{-| Creates _just the segmented select_ when you need the ui element itself and +not a page control + + - `onClick` : the message to produce when an option is selected (clicked) by the user + - `options`: the list of options available + - `selected`: if present, the value of the currently-selected option + - `width`: how to size the segmented control + +-} +viewSelect : + { onClick : a -> msg + , options : List (SelectOption a msg) + , selected : Maybe a + , width : Width + } + -> Html msg +viewSelect config = + let + viewRadio option = + let + isSelected = + Just option.value == config.selected + in + button + ([ Attributes.id (segmentIdFor option) + , css (getStyles { isSelected = isSelected, width = config.width }) + , Role.radio + , if isSelected then + Widget.selected True + + else + Widget.selected False + , Events.onClick (config.onClick option.value) + ] + ++ option.attributes + ) + [ viewIcon option.icon + , text option.label + ] + in + div + [ css + [ displayFlex + , cursor pointer + ] + , Role.radioGroup + ] + (List.map viewRadio config.options) + + +{-| -} +type alias Option value msg = + { value : value + , label : String + , attributes : List (Attribute msg) + , icon : Maybe Svg + , content : Html msg + } + + +{-| + + - `onClick` : the message to produce when an option is selected (clicked) by the user + - `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 : + { onClick : a -> msg + , options : List (Option a msg) + , selected : a + , width : Width + , toUrl : Maybe (a -> String) + } + -> Html msg +view config = + let + isSelected option = + option.value == config.selected + + viewTab option = + case config.toUrl of + Just toUrl -> + -- This is a for a SPA view + Html.Styled.a + (href (toUrl option.value) + :: EventExtras.onClickPreventDefaultForLinkWithHref + (config.onClick option.value) + :: tabAttributes option + ++ option.attributes + ) + [ viewIcon option.icon + , text option.label + ] + + Nothing -> + -- This is for a non-SPA view + button + (Events.onClick (config.onClick option.value) + :: tabAttributes option + ++ option.attributes + ) + [ viewIcon option.icon + , text option.label + ] + + tabAttributes option = + [ Attributes.id (segmentIdFor option) + , css (getStyles { isSelected = isSelected option, width = config.width }) + , Role.tab + , if isSelected option then + Aria.currentPage + + else + AttributesExtra.none + ] + + viewTabPanel option = + tabPanel + [ Aria.labelledBy (segmentIdFor option) + , css + [ paddingTop (px 10) + , if isSelected option then + Css.batch [] + + else + Css.display none + ] + , Widget.hidden (not (isSelected option)) + ] + [ option.content + ] + in + div [] + [ tabList [ css [ displayFlex, cursor pointer ] ] + (List.map viewTab config.options) + , Keyed.node "div" [] <| + List.map (\option -> ( keyedNodeIdFor option, viewTabPanel option )) + config.options + ] + + +segmentIdFor : { option | label : String } -> String +segmentIdFor option = + "Nri-Ui-SegmentedControl-Segment-" ++ dashify option.label + + +keyedNodeIdFor : { option | label : String } -> String +keyedNodeIdFor option = + "Nri-Ui-SegmentedControl-Panel-keyed-node-" ++ dashify option.label + + +panelIdFor : { option | label : String } -> String +panelIdFor option = + "Nri-Ui-SegmentedControl-Panel-" ++ dashify option.label + + +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 + + +getStyles : { isSelected : Bool, width : Width } -> List Style +getStyles { isSelected, width } = + [ 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 diff --git a/styleguide-app/Examples/SegmentedControl.elm b/styleguide-app/Examples/SegmentedControl.elm index 86f7b6ab..089278da 100644 --- a/styleguide-app/Examples/SegmentedControl.elm +++ b/styleguide-app/Examples/SegmentedControl.elm @@ -20,7 +20,7 @@ import Html.Styled.Attributes as Attributes exposing (css) import Html.Styled.Events as Events import KeyboardSupport exposing (Direction(..), Key(..)) import Nri.Ui.Colors.V1 as Colors -import Nri.Ui.SegmentedControl.V10 as SegmentedControl +import Nri.Ui.SegmentedControl.V11 as SegmentedControl import Nri.Ui.Svg.V1 as Svg exposing (Svg) import Nri.Ui.UiIcon.V1 as UiIcon @@ -28,7 +28,7 @@ import Nri.Ui.UiIcon.V1 as UiIcon {-| -} example : Example State Msg example = - { name = "Nri.Ui.SegmentedControl.V10" + { name = "Nri.Ui.SegmentedControl.V11" , state = init , update = update , subscriptions = \_ -> Sub.none diff --git a/tests/elm-verify-examples.json b/tests/elm-verify-examples.json index b4ea92b5..ffd6d4cc 100644 --- a/tests/elm-verify-examples.json +++ b/tests/elm-verify-examples.json @@ -41,6 +41,7 @@ "Nri.Ui.RadioButton.V1", "Nri.Ui.SegmentedControl.V9", "Nri.Ui.SegmentedControl.V10", + "Nri.Ui.SegmentedControl.V11", "Nri.Ui.Select.V5", "Nri.Ui.Select.V7", "Nri.Ui.Slide.V1",