Add Checkbox.V4/PremiumCheckbox.V3 with correct text styling

Previous versions relied on the nri2015 sass stylesheet for label style.
This commit is contained in:
mavnn 2018-12-14 12:18:49 +00:00
parent a97ddb4e4e
commit ae7aa6047c
No known key found for this signature in database
GPG Key ID: 5F95FCB1A05442B1
7 changed files with 619 additions and 2 deletions

View File

@ -16,6 +16,7 @@
"Nri.Ui.Button.V5",
"Nri.Ui.Button.V6",
"Nri.Ui.Checkbox.V3",
"Nri.Ui.Checkbox.V4",
"Nri.Ui.Colors.Extra",
"Nri.Ui.Colors.V1",
"Nri.Ui.DisclosureIndicator.V1",
@ -35,6 +36,7 @@
"Nri.Ui.Palette.V1",
"Nri.Ui.PremiumCheckbox.V1",
"Nri.Ui.PremiumCheckbox.V2",
"Nri.Ui.PremiumCheckbox.V3",
"Nri.Ui.SegmentedControl.V6",
"Nri.Ui.Select.V5",
"Nri.Ui.Table.V3",

View File

@ -13,6 +13,7 @@
"Nri.Ui.Button.V5",
"Nri.Ui.Button.V6",
"Nri.Ui.Checkbox.V3",
"Nri.Ui.Checkbox.V4",
"Nri.Ui.Colors.Extra",
"Nri.Ui.Colors.V1",
"Nri.Ui.DisclosureIndicator.V1",
@ -32,6 +33,7 @@
"Nri.Ui.Palette.V1",
"Nri.Ui.PremiumCheckbox.V1",
"Nri.Ui.PremiumCheckbox.V2",
"Nri.Ui.PremiumCheckbox.V3",
"Nri.Ui.SegmentedControl.V6",
"Nri.Ui.Select.V5",
"Nri.Ui.Table.V3",

332
src/Nri/Ui/Checkbox/V4.elm Normal file
View File

@ -0,0 +1,332 @@
module Nri.Ui.Checkbox.V4 exposing
( Model, Theme(..), IsSelected(..)
, view, viewWithLabel, Assets
, selectedFromBool
)
{-|
@docs Model, Theme, IsSelected
@docs view, viewWithLabel, Assets
@docs selectedFromBool
-}
import Accessibility.Styled as Html
import Accessibility.Styled.Aria as Aria
import Accessibility.Styled.Style
import Accessibility.Styled.Widget as Widget
import Css exposing (..)
import Css.Global exposing (Snippet, children, descendants, everything, selector)
import Html.Events
import Html.Styled
import Html.Styled.Attributes as Attributes exposing (css)
import Html.Styled.Events as Events
import Json.Decode
import Nri.Ui.AssetPath exposing (Asset(..))
import Nri.Ui.AssetPath.Css
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Html.Attributes.V2 as ExtraAttributes
import Nri.Ui.Html.V3 as HtmlExtra exposing (defaultOptions)
{-| -}
type alias Model msg =
{ identifier : String
, label : String
, setterMsg : Bool -> msg
, selected : IsSelected
, disabled : Bool
, theme : Theme
, noOpMsg : msg
}
{-|
= Selected -- Checked (rendered with a checkmark)
| NotSelected -- Not Checked (rendered blank)
| PartiallySelected -- Indeterminate (rendered dash)
-}
type IsSelected
= Selected
| NotSelected
| PartiallySelected
{-| -}
type Theme
= Square
| Locked
{-| If your selectedness is always selected or not selected,
you will likely store that state as a `Bool` in your model.
`selectedFromBool` lets you easily convert that into an `IsSelected` value
for use with `Nri.Ui.Checkbox`.
-}
selectedFromBool : Bool -> IsSelected
selectedFromBool isSelected =
case isSelected of
True ->
Selected
False ->
NotSelected
selectedToMaybe : IsSelected -> Maybe Bool
selectedToMaybe selected =
case selected of
Selected ->
Just True
NotSelected ->
Just False
PartiallySelected ->
Nothing
{-| Shows a checkbox (the label is only used for accessibility hints)
-}
view : Assets a -> Model msg -> Html.Html msg
view assets model =
buildCheckbox assets model <|
Html.span Accessibility.Styled.Style.invisible
[ Html.text model.label ]
{-| Shows a checkbox and its label text
-}
viewWithLabel : Assets a -> Model msg -> Html.Html msg
viewWithLabel assets model =
buildCheckbox assets model <|
Html.span [] [ Html.text model.label ]
buildCheckbox : Assets a -> Model msg -> Html.Html msg -> Html.Html msg
buildCheckbox assets model labelContent =
viewCheckbox model <|
case model.theme of
Square ->
{ containerClasses = toClassList [ "SquareClass" ]
, labelStyles =
squareLabelStyles model <|
case model.selected of
Selected ->
assets.checkboxChecked_svg
NotSelected ->
assets.checkboxUnchecked_svg
PartiallySelected ->
assets.checkboxCheckedPartially_svg
, labelClasses = labelClass model.selected
, labelContent = labelContent
}
Locked ->
{ containerClasses = toClassList [ "Locked" ]
, labelStyles = lockLabelStyles model assets.checkboxLockOnInside_svg
, labelClasses = labelClass model.selected
, labelContent = labelContent
}
squareLabelStyles : { b | disabled : Bool } -> Asset -> Html.Styled.Attribute msg
squareLabelStyles model image =
let
baseStyles =
[ positioning
, textStyle
, outline none
, addIcon image
]
in
css
(baseStyles
++ (if model.disabled then
[ cursor auto, checkboxImageSelector [ opacity (num 0.4) ] ]
else
[ cursor pointer ]
)
)
lockLabelStyles : { b | disabled : Bool } -> Asset -> Html.Styled.Attribute msg
lockLabelStyles model image =
let
baseStyles =
[ positioning
, textStyle
, outline none
, addIcon image
]
in
css
(baseStyles
++ (if model.disabled then
[ cursor auto
, checkboxImageSelector [ opacity (num 0.4) ]
]
else
[ cursor pointer ]
)
)
positioning : Style
positioning =
batch
[ display inlineBlock
, padding4 (px 13) zero (px 13) (px 35)
]
textStyle : Style
textStyle =
batch
[ Fonts.baseFont
, fontSize (px 16)
, fontWeight (int 600)
, color Colors.navy
]
addIcon : Asset -> Style
addIcon icon =
batch
[ position relative
, checkboxImageSelector
[ backgroundImage icon
, backgroundRepeat noRepeat
, backgroundSize (px 24)
, property "content" "''"
, position absolute
, left zero
, top (px 10)
, width (px 24)
, height (px 24)
]
]
checkboxImageSelector : List Style -> Style
checkboxImageSelector =
before
labelClass : IsSelected -> Html.Styled.Attribute msg
labelClass isSelected =
case isSelected of
Selected ->
toClassList [ "Label", "Checked" ]
NotSelected ->
toClassList [ "Label", "Unchecked" ]
PartiallySelected ->
toClassList [ "Label", "Indeterminate" ]
toClassList : List String -> Html.Styled.Attribute msg
toClassList =
List.map (\a -> ( "checkbox-V3__" ++ a, True )) >> Attributes.classList
viewCheckbox :
Model msg
->
{ containerClasses : Html.Attribute msg
, labelStyles : Html.Attribute msg
, labelClasses : Html.Attribute msg
, labelContent : Html.Html msg
}
-> Html.Html msg
viewCheckbox model config =
let
toggledValue =
selectedToMaybe model.selected
|> Maybe.withDefault False
|> not
in
Html.Styled.span
[ css
[ display block
, height inherit
, descendants [ Css.Global.input [ display none ] ]
]
, config.containerClasses
, Attributes.id <| model.identifier ++ "-container"
, -- This is necessary to prevent event propagation.
-- See https://github.com/elm-lang/html/issues/96
Attributes.map (always model.noOpMsg) <|
Events.stopPropagationOn "click"
(Json.Decode.succeed ( "stop click propagation", True ))
]
[ Html.checkbox model.identifier
(selectedToMaybe model.selected)
[ Widget.label model.label
, Events.onCheck (\_ -> model.setterMsg toggledValue)
, Attributes.id model.identifier
, Attributes.disabled model.disabled
]
, viewLabel model config.labelContent config.labelClasses config.labelStyles
]
viewLabel : Model msg -> Html.Html msg -> Html.Attribute msg -> Html.Attribute msg -> Html.Html msg
viewLabel model content class theme =
Html.Styled.label
[ Attributes.for model.identifier
, Aria.controls model.identifier
, Widget.disabled model.disabled
, Widget.checked (selectedToMaybe model.selected)
, if not model.disabled then
Attributes.tabindex 0
else
ExtraAttributes.none
, if not model.disabled then
--TODO: the accessibility keyboard module might make this a tad more readable.
HtmlExtra.onKeyUp
{ defaultOptions | preventDefault = True }
(\keyCode ->
-- 32 is the space bar, 13 is enter
if (keyCode == 32 || keyCode == 13) && not model.disabled then
Just <| model.setterMsg (Maybe.map not (selectedToMaybe model.selected) |> Maybe.withDefault True)
else
Nothing
)
else
ExtraAttributes.none
, class
, theme
]
[ content ]
{-| The assets used in this module.
-}
type alias Assets r =
{ r
| checkboxUnchecked_svg : Asset
, checkboxChecked_svg : Asset
, checkboxCheckedPartially_svg : Asset
, checkboxLockOnInside_svg : Asset
}
backgroundImage : Asset -> Style
backgroundImage =
Nri.Ui.AssetPath.Css.url
>> property "background-image"

View File

@ -0,0 +1,119 @@
module Nri.Ui.PremiumCheckbox.V3 exposing (PremiumConfig, premium, Pennant(..))
{-|
@docs PremiumConfig, premium, Pennant
-}
import Accessibility.Styled as Html
import Css exposing (..)
import Html.Styled.Attributes as Attributes exposing (css)
import Nri.Ui.AssetPath exposing (Asset(..))
import Nri.Ui.AssetPath.Css
import Nri.Ui.Checkbox.V4 as Checkbox
{-|
- `onChange`: A message for when the user toggles the checkbox
- `onLockedClick`: A message for when the user clicks a checkbox they don't have PremiumLevel for.
If you get this message, you should show an `Nri.Ui.Premium.Model.view`
-}
type alias PremiumConfig msg =
{ label : String
, id : String
, selected : Checkbox.IsSelected
, disabled : Bool
, isLocked : Bool
, pennant : Maybe Pennant
, onChange : Bool -> msg
, onLockedClick : msg
, noOpMsg : msg
}
{-| Premium is the yellow "P" pennant
PremiumWithWriting is the yellow "P+" pennant
-}
type Pennant
= Premium
| PremiumWithWriting
{-| A checkbox that should be used for premium content
-}
premium : Assets a -> PremiumConfig msg -> Html.Html msg
premium assets config =
Html.div
[ css
[ displayFlex
, alignItems center
]
]
[ Checkbox.viewWithLabel assets
{ identifier = config.id
, label = config.label
, setterMsg =
if config.isLocked then
\_ -> config.onLockedClick
else
config.onChange
, selected = config.selected
, disabled = config.disabled
, theme =
if config.isLocked then
Checkbox.Locked
else
Checkbox.Square
, noOpMsg = config.noOpMsg
}
, case config.pennant of
Just pennant ->
Html.div
[ Attributes.class "premium-checkbox-V1__PremiumClass"
, css
[ property "content" "''"
, display inlineBlock
, width (px 26)
, height (px 24)
, marginLeft (px 8)
, backgroundImage
(case pennant of
Premium ->
assets.iconPremiumFlag_svg
PremiumWithWriting ->
assets.iconPremiumWithWritingFlag_svg
)
, backgroundRepeat noRepeat
, backgroundPosition center
]
]
[]
Nothing ->
Html.text ""
]
{-| The assets used in this module.
-}
type alias Assets r =
{ r
| checkboxUnchecked_svg : Asset
, checkboxChecked_svg : Asset
, checkboxCheckedPartially_svg : Asset
, checkboxLockOnInside_svg : Asset
, iconPremiumFlag_svg : Asset
, iconPremiumWithWritingFlag_svg : Asset
}
backgroundImage : Asset -> Style
backgroundImage =
Nri.Ui.AssetPath.Css.url
>> property "background-image"

View File

@ -9,9 +9,9 @@ module Examples.Checkbox exposing (Msg, State, example, init, update)
import Assets exposing (assets)
import Html.Styled as Html exposing (..)
import ModuleExample as ModuleExample exposing (Category(..), ModuleExample)
import Nri.Ui.Checkbox.V3 as Checkbox
import Nri.Ui.Checkbox.V4 as Checkbox
import Nri.Ui.Data.PremiumLevel as PremiumLevel exposing (PremiumLevel(..))
import Nri.Ui.PremiumCheckbox.V2 as PremiumCheckbox
import Nri.Ui.PremiumCheckbox.V3 as PremiumCheckbox
import Set exposing (Set)

View File

@ -0,0 +1,21 @@
module Spec.Nri.Ui.Checkbox.V4 exposing (all)
import Expect exposing (Expectation)
import Nri.Ui.Checkbox.V4 as Checkbox
import Test exposing (..)
all : Test
all =
describe "Nri.Ui.Checkbox.V3"
[ describe "selectedFromBool"
[ test "it handles Selected" <|
\() ->
Checkbox.selectedFromBool True
|> Expect.equal Checkbox.Selected
, test "it handles NotSelected" <|
\() ->
Checkbox.selectedFromBool False
|> Expect.equal Checkbox.NotSelected
]
]

View File

@ -0,0 +1,141 @@
module Spec.Nri.Ui.PremiumCheckbox.V3 exposing (spec)
import Html.Attributes
import Html.Styled
import Nri.Ui.AssetPath exposing (Asset(..))
import Nri.Ui.Checkbox.V4 exposing (IsSelected(..))
import Nri.Ui.Data.PremiumLevel as PremiumLevel exposing (PremiumLevel(..))
import Nri.Ui.PremiumCheckbox.V3 as PremiumCheckbox
import Test exposing (..)
import Test.Html.Query as Query
import Test.Html.Selector as Selector
premiumView config =
PremiumCheckbox.premium assets
{ label = "i am label"
, id = "id"
, selected = config.selected
, disabled = config.disabled
, isLocked = config.isLocked
, pennant = config.pennant
, onChange = \_ -> ()
, onLockedClick = ()
, noOpMsg = ()
}
|> Html.Styled.toUnstyled
|> Query.fromHtml
spec : Test
spec =
describe "Nri.Ui.PremiumCheckbox.V2"
[ describe "premium"
[ test "displays the label" <|
\() ->
premiumView
{ selected = Selected
, disabled = False
, isLocked = False
, pennant = Nothing
}
|> Query.has [ Selector.text "i am label" ]
, test "appears selected when Selected is passed in" <|
\() ->
premiumView
{ selected = Selected
, disabled = False
, isLocked = False
, pennant = Nothing
}
|> Query.has [ Selector.class "checkbox-V3__Checked" ]
, test "appears unselected when NotSelected is passed in" <|
\() ->
premiumView
{ selected = NotSelected
, disabled = False
, isLocked = False
, pennant = Nothing
}
|> Query.has [ Selector.class "checkbox-V3__Unchecked" ]
, test "appears partially selected when PartiallySelected is passed in" <|
\() ->
premiumView
{ selected = PartiallySelected
, disabled = False
, isLocked = False
, pennant = Nothing
}
|> Query.has [ Selector.class "checkbox-V3__Indeterminate" ]
, test "appears locked when isLocked = True" <|
\() ->
premiumView
{ selected = Selected
, disabled = False
, isLocked = True
, pennant = Nothing
}
|> Query.has [ Selector.class "checkbox-V3__Locked" ]
, test "appears unlocked when isLocked = False" <|
\() ->
premiumView
{ selected = Selected
, disabled = False
, isLocked = False
, pennant = Nothing
}
|> Query.hasNot [ Selector.class "checkbox-V3__Locked" ]
, test "appears with P flag when Premium pennant is passed in" <|
\() ->
premiumView
{ selected = Selected
, disabled = False
, isLocked = False
, pennant = Just PremiumCheckbox.Premium
}
|> Query.find [ Selector.tag "style" ]
|> Query.has [ Selector.text "iconPremiumFlag reference" ]
, test "appears with P+ flag when Premium pennant is passed in" <|
\() ->
premiumView
{ selected = Selected
, disabled = False
, isLocked = False
, pennant = Just PremiumCheckbox.PremiumWithWriting
}
|> Query.find [ Selector.tag "style" ]
|> Query.has [ Selector.text "iconPremiumWritingFlag reference" ]
, test "is not disabled when disabled = False" <|
\() ->
premiumView
{ selected = Selected
, disabled = False
, isLocked = False
, pennant = Nothing
}
|> Query.has [ Selector.disabled False ]
, test "is disabled when disabled = True" <|
\() ->
premiumView
{ selected = Selected
, disabled = True
, isLocked = False
, pennant = Nothing
}
|> Query.has [ Selector.disabled True ]
]
]
assets =
{ checkboxUnchecked_svg = Asset "checkboxUnchecked reference"
, checkboxChecked_svg = Asset "checkboxChecked reference"
, checkboxCheckedPartially_svg = Asset "checkboxCheckedPartially reference"
, iconPremiumUnlocked_png = Asset "iconPremiumUnlocked reference"
, iconCheck_png = Asset "iconCheck reference"
, iconPremiumLocked_png = Asset "iconPremiumLocked reference"
, checkboxLockOnInside_svg = Asset "checkboxLockOnInside reference"
, iconPremiumKey_png = Asset "iconPremiumKey reference"
, iconPremiumFlag_svg = Asset "iconPremiumFlag reference"
, iconPremiumWithWritingFlag_svg = Asset "iconPremiumWritingFlag reference"
}