mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-12-25 06:33:09 +03:00
Adds new version of the checkbox
This commit is contained in:
parent
8766a17f4d
commit
e869f064ff
@ -14,6 +14,7 @@
|
||||
"Nri.Ui.Colors.V1",
|
||||
"Nri.Ui.Colors.Extra",
|
||||
"Nri.Ui.Checkbox.V1",
|
||||
"Nri.Ui.Checkbox.V2",
|
||||
"Nri.Ui.Divider.V1",
|
||||
"Nri.Ui.Effects.V1",
|
||||
"Nri.Ui.Fonts.V1",
|
||||
|
633
src/Nri/Ui/Checkbox/V2.elm
Normal file
633
src/Nri/Ui/Checkbox/V2.elm
Normal file
@ -0,0 +1,633 @@
|
||||
module Nri.Ui.Checkbox.V2
|
||||
exposing
|
||||
( ColorTheme(..)
|
||||
, IsSelected(..)
|
||||
, Model
|
||||
, PremiumConfig
|
||||
, Theme(..)
|
||||
, disabled
|
||||
, keyframeCss
|
||||
, premium
|
||||
, styles
|
||||
, view
|
||||
, viewAttention
|
||||
, viewWithLabel
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Model, Theme, ColorTheme
|
||||
|
||||
@docs view, viewWithLabel, viewAttention, disabled
|
||||
@docs IsSelected
|
||||
@docs keyframeCss, styles
|
||||
|
||||
|
||||
## Premium
|
||||
|
||||
@docs PremiumConfig, premium
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility exposing (..)
|
||||
import Accessibility.Aria exposing (controls)
|
||||
import Accessibility.Style
|
||||
import Accessibility.Widget as Widget
|
||||
import Css exposing (..)
|
||||
import Css.Foreign exposing (Snippet, children, descendants, everything, selector)
|
||||
import Html
|
||||
import Html.Attributes as Attributes
|
||||
import Html.Events as Events exposing (defaultOptions)
|
||||
import Json.Decode
|
||||
import Json.Encode
|
||||
import Nri.Ui.AssetPath exposing (Asset(..))
|
||||
import Nri.Ui.AssetPath.Css
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Data.PremiumLevel as PremiumLevel exposing (PremiumLevel(..))
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.Html.Attributes.Extra as Attributes
|
||||
import Nri.Ui.Html.Extra exposing (onEnter, onKeyUp)
|
||||
import Nri.Ui.Styles.V1
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
- isChecked : Maybe Bool
|
||||
- Just True == Checked (rendered checkmark)
|
||||
- Just False == Not Checked (rendered blank)
|
||||
- Nothing == Indeterminate (rendered dash)
|
||||
|
||||
-}
|
||||
type alias Model msg =
|
||||
{ identifier : String
|
||||
, label : String
|
||||
, setterMsg : Bool -> msg
|
||||
, isChecked : Maybe Bool
|
||||
, disabled : Bool
|
||||
, theme : Theme
|
||||
, noOpMsg : msg
|
||||
}
|
||||
|
||||
|
||||
customView : List CssClasses -> Bool -> Model msg -> Html msg
|
||||
customView modifierClasses showLabels model =
|
||||
let
|
||||
containerClasses =
|
||||
List.concat
|
||||
[ [ Container ]
|
||||
, modifierClasses
|
||||
, case model.theme of
|
||||
Square Gray ->
|
||||
[ SquareClass, GrayClass ]
|
||||
|
||||
Square Orange ->
|
||||
[ SquareClass, OrangeClass ]
|
||||
|
||||
Square Default ->
|
||||
[ SquareClass ]
|
||||
|
||||
Locked ->
|
||||
[ LockedClass ]
|
||||
|
||||
LockOnInside ->
|
||||
[ LockOnInsideClass ]
|
||||
|
||||
Unlockable ->
|
||||
[ UnlockableClass ]
|
||||
|
||||
Round ->
|
||||
[ RoundClass ]
|
||||
|
||||
Disabled ->
|
||||
[ SquareClass, Opacified ]
|
||||
|
||||
Premium ->
|
||||
[ SquareClass, PremiumClass ]
|
||||
]
|
||||
in
|
||||
Html.span
|
||||
[ styles.class 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.onWithOptions "click"
|
||||
{ defaultOptions | stopPropagation = True }
|
||||
(Json.Decode.succeed "stop click propagation")
|
||||
]
|
||||
[ checkbox model.identifier
|
||||
model.isChecked
|
||||
[ Widget.label model.label
|
||||
, styles.class [ Checkbox ]
|
||||
, Events.onCheck model.setterMsg
|
||||
, Attributes.id model.identifier
|
||||
, Attributes.disabled model.disabled
|
||||
]
|
||||
, Html.label
|
||||
[ Attributes.for model.identifier
|
||||
, getLabelClass model.isChecked
|
||||
, controls model.identifier
|
||||
, Widget.disabled model.disabled
|
||||
, Widget.checked model.isChecked
|
||||
, if not model.disabled then
|
||||
Attributes.tabindex 0
|
||||
else
|
||||
Attributes.none
|
||||
, if not model.disabled then
|
||||
Nri.Ui.Html.Extra.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 model.isChecked |> Maybe.withDefault True)
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
else
|
||||
Attributes.none
|
||||
]
|
||||
[ span
|
||||
(if showLabels then
|
||||
[]
|
||||
else
|
||||
[ Accessibility.Style.invisible ]
|
||||
)
|
||||
[ Html.text model.label ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
{-| Shows a checkbox (the label is only used for accessibility hints)
|
||||
-}
|
||||
view : Model msg -> Html msg
|
||||
view model =
|
||||
customView [] False model
|
||||
|
||||
|
||||
{-| Shows a checkbox and its label text
|
||||
-}
|
||||
viewWithLabel : Model msg -> Html msg
|
||||
viewWithLabel model =
|
||||
customView [] True model
|
||||
|
||||
|
||||
{-| Show a disabled checkbox.
|
||||
-}
|
||||
disabled : String -> String -> Html msg
|
||||
disabled identifier labelText =
|
||||
span
|
||||
[ styles.class [ Container, SquareClass, Opacified ]
|
||||
, Attributes.id <| identifier ++ "-container"
|
||||
]
|
||||
[ checkbox identifier
|
||||
(Just False)
|
||||
[ Widget.label labelText
|
||||
, styles.class [ Checkbox ]
|
||||
, Attributes.id identifier
|
||||
, Attributes.disabled True
|
||||
]
|
||||
, label
|
||||
[ Attributes.for identifier
|
||||
, getLabelClass (Just False)
|
||||
]
|
||||
[ Html.text labelText
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
type IsSelected
|
||||
= Selected
|
||||
| NotSelected
|
||||
| PartiallySelected
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
- `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 : IsSelected
|
||||
, disabled : Bool
|
||||
, teacherPremiumLevel : PremiumLevel
|
||||
, contentPremiumLevel : PremiumLevel
|
||||
, showFlagWhenLocked : Bool
|
||||
, onChange : Bool -> msg
|
||||
, onLockedClick : msg
|
||||
, noOpMsg : msg
|
||||
}
|
||||
|
||||
|
||||
{-| A checkbox that should be used for premium content
|
||||
|
||||
This checkbox is locked when the premium level of the content is greater than the premium level of the teacher
|
||||
|
||||
-}
|
||||
premium : PremiumConfig msg -> Html msg
|
||||
premium config =
|
||||
let
|
||||
isLocked =
|
||||
not <|
|
||||
PremiumLevel.allowedFor
|
||||
config.contentPremiumLevel
|
||||
config.teacherPremiumLevel
|
||||
|
||||
isChecked =
|
||||
case config.selected of
|
||||
Selected ->
|
||||
Just True
|
||||
|
||||
NotSelected ->
|
||||
Just False
|
||||
|
||||
PartiallySelected ->
|
||||
Nothing
|
||||
|
||||
modifierClasses =
|
||||
List.concat
|
||||
[ if config.showFlagWhenLocked && config.contentPremiumLevel /= Free then
|
||||
[ PremiumClass ]
|
||||
else
|
||||
[]
|
||||
, if config.disabled then
|
||||
[ Opacified ]
|
||||
else
|
||||
[]
|
||||
]
|
||||
|
||||
theme =
|
||||
if isLocked then
|
||||
LockOnInside
|
||||
else if config.contentPremiumLevel /= Free then
|
||||
Premium
|
||||
else
|
||||
Square Default
|
||||
in
|
||||
customView modifierClasses
|
||||
True
|
||||
{ identifier = config.id
|
||||
, label = config.label
|
||||
, setterMsg =
|
||||
if isLocked then
|
||||
\_ -> config.onLockedClick
|
||||
else
|
||||
config.onChange
|
||||
, isChecked = isChecked
|
||||
, disabled = config.disabled
|
||||
, theme = theme
|
||||
, noOpMsg = config.noOpMsg
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
viewAttention : Model msg -> Html msg
|
||||
viewAttention model =
|
||||
customView [ WithPulsing ] False model
|
||||
|
||||
|
||||
getLabelClass : Maybe Bool -> Html.Attribute msg
|
||||
getLabelClass maybeChecked =
|
||||
styles.class
|
||||
[ Label
|
||||
, case maybeChecked of
|
||||
Just True ->
|
||||
Checked
|
||||
|
||||
Just False ->
|
||||
Unchecked
|
||||
|
||||
Nothing ->
|
||||
Indeterminate
|
||||
]
|
||||
|
||||
|
||||
indeterminateAttr : Html.Attribute msg
|
||||
indeterminateAttr =
|
||||
Attributes.property "indeterminate" (Json.Encode.bool True)
|
||||
|
||||
|
||||
type CssClasses
|
||||
= Container
|
||||
| Checkbox
|
||||
| Unchecked
|
||||
| Checked
|
||||
| Indeterminate
|
||||
| SquareClass
|
||||
| RoundClass
|
||||
| GrayClass
|
||||
| OrangeClass
|
||||
| LockedClass
|
||||
| LockOnInsideClass
|
||||
| UnlockableClass
|
||||
| Label
|
||||
| WithPulsing
|
||||
| Opacified
|
||||
| PremiumClass
|
||||
|
||||
|
||||
type CheckboxImage
|
||||
= CheckboxUnchecked
|
||||
| CheckboxChecked
|
||||
| CheckboxCheckedPartially
|
||||
| PremiumUnlocked
|
||||
| PremiumFlag
|
||||
| CheckWhite
|
||||
| PremiumLocked
|
||||
| PremiumKey
|
||||
| CheckboxLockOnInside
|
||||
|
||||
|
||||
{-| -}
|
||||
type Theme
|
||||
= Square ColorTheme
|
||||
| Round
|
||||
| Locked
|
||||
| LockOnInside
|
||||
| Unlockable
|
||||
| Disabled
|
||||
| Premium
|
||||
|
||||
|
||||
{-| -}
|
||||
type ColorTheme
|
||||
= Default
|
||||
| Gray
|
||||
| Orange
|
||||
|
||||
|
||||
mainSnippet : List Snippet
|
||||
mainSnippet =
|
||||
[ Css.Foreign.class Container
|
||||
[ display block
|
||||
, height inherit
|
||||
, descendants
|
||||
[ Css.Foreign.label
|
||||
[ display inlineBlock
|
||||
, verticalAlign middle
|
||||
, minHeight (px 42) -- container height
|
||||
, padding2 (px 13) zero
|
||||
, fontSize (px 16)
|
||||
, Fonts.baseFont
|
||||
, color Colors.gray20
|
||||
, property "background-position" "left center"
|
||||
, property "background-repeat" "no-repeat"
|
||||
]
|
||||
, Css.Foreign.input [ display none ]
|
||||
, selector ":disabled + label"
|
||||
[ cursor auto
|
||||
]
|
||||
]
|
||||
]
|
||||
, Css.Foreign.class Checkbox
|
||||
[ cursor pointer ]
|
||||
, Css.Foreign.class Label
|
||||
[ cursor pointer
|
||||
, outline none
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
square : Assets r -> List Snippet
|
||||
square assets =
|
||||
[ Css.Foreign.class SquareClass
|
||||
[ children
|
||||
[ Css.Foreign.label
|
||||
[ paddingLeft (px (29 + 6)) -- checkbox width + padding
|
||||
]
|
||||
, Css.Foreign.class Unchecked [ backgroundImage assets CheckboxUnchecked ]
|
||||
, Css.Foreign.class Checked [ backgroundImage assets CheckboxChecked ]
|
||||
, Css.Foreign.class Indeterminate [ backgroundImage assets CheckboxCheckedPartially ]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
gray : List Snippet
|
||||
gray =
|
||||
[ Css.Foreign.class GrayClass
|
||||
[ children
|
||||
[ Css.Foreign.label [ color Colors.gray45 ] ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
orange : Assets r -> List Snippet
|
||||
orange assets =
|
||||
[ Css.Foreign.class OrangeClass
|
||||
[ children
|
||||
[ Css.Foreign.label
|
||||
[ color Colors.ochre
|
||||
, displayFlex
|
||||
, alignItems center
|
||||
]
|
||||
, selector "label::after"
|
||||
[ property "content" "''"
|
||||
, width (px 26)
|
||||
, height (px 24)
|
||||
, marginLeft (px 8)
|
||||
, backgroundImage assets PremiumUnlocked
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
round : Assets r -> List Snippet
|
||||
round assets =
|
||||
[ Css.Foreign.class RoundClass
|
||||
[ children
|
||||
[ Css.Foreign.label
|
||||
[ displayFlex
|
||||
, alignItems center
|
||||
, property "cursor" "pointer"
|
||||
]
|
||||
, selector "label::before"
|
||||
[ property "content" "''"
|
||||
, width (px 24)
|
||||
, height (px 24)
|
||||
, marginRight (px 8)
|
||||
, borderRadius (pct 100)
|
||||
]
|
||||
, selector ".checkbox-Unchecked::before"
|
||||
[ border3 (px 2) solid Colors.blue
|
||||
, backgroundColor Colors.white
|
||||
]
|
||||
, selector ".checkbox-Checked::before"
|
||||
[ backgroundColor Colors.green
|
||||
, border3 (px 2) solid Colors.green
|
||||
, backgroundImage assets CheckWhite
|
||||
, property "background-repeat" "no-repeat"
|
||||
, property "background-position" "center center"
|
||||
]
|
||||
, selector ":disabled + label"
|
||||
[ property "cursor" "auto"
|
||||
]
|
||||
]
|
||||
]
|
||||
, Css.Foreign.class WithPulsing
|
||||
[ property "-webkit-animation" "pulsate 1s infinite"
|
||||
, property "-moz-animation" "pulsate 1s infinite"
|
||||
, property "animation" "pulsate 1s infinite"
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
locked : Assets r -> List Snippet
|
||||
locked assets =
|
||||
[ Css.Foreign.class LockedClass
|
||||
[ descendants
|
||||
[ Css.Foreign.label
|
||||
[ paddingLeft (px (29 + 6)) -- checkbox width + padding
|
||||
, backgroundImage assets PremiumLocked
|
||||
, property "cursor" "auto"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
lockOnInside : Assets r -> List Snippet
|
||||
lockOnInside assets =
|
||||
[ Css.Foreign.class LockOnInsideClass
|
||||
[ descendants
|
||||
[ Css.Foreign.label
|
||||
[ paddingLeft (px 35)
|
||||
, backgroundImage assets CheckboxLockOnInside
|
||||
, backgroundSize (px 24)
|
||||
, backgroundRepeat noRepeat
|
||||
, property "cursor" "pointer"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
unlockable : Assets r -> List Snippet
|
||||
unlockable assets =
|
||||
[ Css.Foreign.class UnlockableClass
|
||||
[ descendants
|
||||
[ Css.Foreign.label
|
||||
[ paddingLeft (px (29 + 6)) -- checkbox width + padding
|
||||
, backgroundImage assets PremiumKey
|
||||
, property "cursor" "auto"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
premiumStyles : Assets r -> List Snippet
|
||||
premiumStyles assets =
|
||||
[ Css.Foreign.class PremiumClass
|
||||
[ children
|
||||
[ Css.Foreign.label
|
||||
[ displayFlex
|
||||
, alignItems center
|
||||
]
|
||||
, selector "label::after"
|
||||
[ property "content" "''"
|
||||
, display inlineBlock
|
||||
, width (px 26)
|
||||
, height (px 24)
|
||||
, marginLeft (px 8)
|
||||
, backgroundImage assets PremiumFlag
|
||||
, backgroundRepeat noRepeat
|
||||
, backgroundPosition Css.center
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
opacified : List Snippet
|
||||
opacified =
|
||||
[ Css.Foreign.class Opacified
|
||||
[ descendants [ everything [ opacity (num 0.4) ] ] ]
|
||||
]
|
||||
|
||||
|
||||
backgroundImage : Assets r -> CheckboxImage -> Css.Style
|
||||
backgroundImage assets checkboxImage =
|
||||
property "background-image" (Nri.Ui.AssetPath.Css.url <| checkboxAssetPath assets checkboxImage)
|
||||
|
||||
|
||||
checkboxAssetPath : Assets r -> CheckboxImage -> Asset
|
||||
checkboxAssetPath assets checkboxImage =
|
||||
case checkboxImage of
|
||||
CheckboxUnchecked ->
|
||||
assets.checkboxUnchecked_svg
|
||||
|
||||
CheckboxChecked ->
|
||||
assets.checkboxChecked_svg
|
||||
|
||||
CheckboxCheckedPartially ->
|
||||
assets.checkboxCheckedPartially_svg
|
||||
|
||||
PremiumUnlocked ->
|
||||
assets.iconPremiumUnlocked_png
|
||||
|
||||
CheckWhite ->
|
||||
assets.iconCheck_png
|
||||
|
||||
PremiumLocked ->
|
||||
assets.iconPremiumLocked_png
|
||||
|
||||
CheckboxLockOnInside ->
|
||||
assets.checkboxLockOnInside_svg
|
||||
|
||||
PremiumKey ->
|
||||
assets.iconPremiumKey_png
|
||||
|
||||
PremiumFlag ->
|
||||
assets.iconPremiumFlag_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
keyframeCss : Nri.Ui.Styles.V1.Keyframe
|
||||
keyframeCss =
|
||||
Nri.Ui.Styles.V1.keyframes "pulsate"
|
||||
[ ( "0%", "transform: scale(1, 1);" )
|
||||
, ( "50%", "transform: scale(1.2);" )
|
||||
, ( "100%", "transform: scale(1, 1);" )
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
styles : Nri.Ui.Styles.V1.StylesWithAssets Never CssClasses msg (Assets r)
|
||||
styles =
|
||||
(\assets ->
|
||||
[ mainSnippet
|
||||
, square assets
|
||||
, gray
|
||||
, orange assets
|
||||
, round assets
|
||||
, locked assets
|
||||
, lockOnInside assets
|
||||
, unlockable assets
|
||||
, opacified
|
||||
, premiumStyles assets
|
||||
]
|
||||
|> List.concat
|
||||
)
|
||||
|> Nri.Ui.Styles.V1.stylesWithAssets "checkbox-"
|
||||
|
||||
|
||||
{-| The assets used in this module.
|
||||
-}
|
||||
type alias Assets r =
|
||||
{ r
|
||||
| checkboxUnchecked_svg : Asset
|
||||
, checkboxChecked_svg : Asset
|
||||
, checkboxCheckedPartially_svg : Asset
|
||||
, iconPremiumUnlocked_png : Asset
|
||||
, iconCheck_png : Asset
|
||||
, iconPremiumLocked_png : Asset
|
||||
, checkboxLockOnInside_svg : Asset
|
||||
, iconPremiumKey_png : Asset
|
||||
, iconPremiumFlag_svg : Asset
|
||||
}
|
@ -7,7 +7,7 @@ module Examples.TextArea exposing (Msg, State, example, init, update)
|
||||
import Dict exposing (Dict)
|
||||
import Html
|
||||
import ModuleExample as ModuleExample exposing (Category(..), ModuleExample)
|
||||
import Nri.Ui.Checkbox.V1 as Checkbox
|
||||
import Nri.Ui.Checkbox.V2 as Checkbox
|
||||
import Nri.Ui.Text.V1 as Text
|
||||
import Nri.Ui.TextArea.V1 as TextArea
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user