diff --git a/deprecated-modules.csv b/deprecated-modules.csv index 83109e55..ba732310 100644 --- a/deprecated-modules.csv +++ b/deprecated-modules.csv @@ -1,5 +1,6 @@ Nri.Ui.Accordion.V1,upgrade to V3 Nri.Ui.Button.V8,upgrade to V10 +Nri.Ui.Container.V1,upgrade to V2 Nri.Ui.Menu.V1,upgrade to V3 Nri.Ui.Menu.V2,upgrade to V3 Nri.Ui.Modal.V10,upgrade to V11 diff --git a/elm.json b/elm.json index 31150e28..4330c758 100644 --- a/elm.json +++ b/elm.json @@ -17,6 +17,7 @@ "Nri.Ui.ClickableSvg.V2", "Nri.Ui.ClickableText.V3", "Nri.Ui.Container.V1", + "Nri.Ui.Container.V2", "Nri.Ui.Colors.Extra", "Nri.Ui.Colors.V1", "Nri.Ui.Confetti.V2", diff --git a/forbidden-imports.toml b/forbidden-imports.toml index d49a0eb0..41066dc9 100644 --- a/forbidden-imports.toml +++ b/forbidden-imports.toml @@ -50,6 +50,9 @@ usages = ['styleguide-app/../src/Nri/Ui/SlideModal/V2.elm'] hint = 'upgrade to V2' usages = ['styleguide-app/Examples/Tooltip.elm'] +[forbidden."Nri.Ui.Container.V1"] +hint = 'upgrade to V2' + [forbidden."Nri.Ui.Icon.V3"] hint = 'upgrade to V5' usages = ['styleguide-app/../src/Nri/Ui/Modal/V3.elm'] diff --git a/src/Nri/Ui/Container/V2.elm b/src/Nri/Ui/Container/V2.elm new file mode 100644 index 00000000..1033cdcb --- /dev/null +++ b/src/Nri/Ui/Container/V2.elm @@ -0,0 +1,301 @@ +module Nri.Ui.Container.V2 exposing + ( view, Attribute + , paddingPx, custom, css, testId, id + , plaintext, markdown, html + , gray, default, disabled, invalid, pillow, buttony + ) + +{-| Common NoRedInk Containers + + +# Changelog + + +## Changes from V1 + + - removes fullHeight + - changes the API from providing many themed functions that create HTML (`alternate`, `general`, etc.) to having a single `view` helper that takes a list of attributes (including themed attributes and content attributes) + - adds `custom`, `testId`, `id` + - adds `plaintext` helper and `markdown` helper + - renames themes from: + - `alternate` -> `gray` + - `general` -> `default` + - `interactable` -> `pillow` + - removes `interactableWithLabel` theme + - adds `buttony` theme + + +# Documentation + + +## View + +@docs view, Attribute +@docs paddingPx, custom, css, testId, id + + +## Content + +@docs plaintext, markdown, html + + +## Themes + +@docs gray, default, disabled, invalid, pillow, buttony + +-} + +import Css exposing (..) +import Css.Media exposing (withMedia) +import Html.Styled as Html exposing (..) +import Html.Styled.Attributes +import Markdown +import Nri.Ui +import Nri.Ui.Colors.V1 as Colors +import Nri.Ui.Html.Attributes.V2 as ExtraAttributes +import Nri.Ui.MediaQuery.V1 exposing (mobile) +import Nri.Ui.Text.V5 as Text + + +{-| -} +type Attribute msg + = Attribute (Settings msg -> Settings msg) + + +{-| PRIVATE +-} +type alias Settings msg = + { containerType : String + , padding : Float + , css : List Css.Style + , content : List (Html msg) + , attributes : List (Html.Attribute msg) + } + + +{-| Changes the padding inside the container border around the content. +-} +paddingPx : Float -> Attribute msg +paddingPx padding = + Attribute <| \config -> { config | padding = padding } + + +{-| Use this helper to add custom attributes. + +Do NOT use this helper to add css styles, as they may not be applied the way +you want/expect if underlying styles change. +Instead, please use the `css` helper. + +-} +custom : List (Html.Attribute msg) -> Attribute msg +custom attributes = + Attribute <| + \config -> + { config + | attributes = + config.attributes ++ attributes + } + + +{-| -} +testId : String -> Attribute msg +testId id_ = + custom [ ExtraAttributes.testId id_ ] + + +{-| -} +id : String -> Attribute msg +id id_ = + custom [ Html.Styled.Attributes.id id_ ] + + +{-| -} +css : List Css.Style -> Attribute msg +css css_ = + Attribute <| \config -> { config | css = config.css ++ css_ } + + +{-| -} +view : List (Attribute msg) -> Html msg +view attributes = + let + settings : Settings msg + settings = + List.foldl (\(Attribute set) -> set) + defaultSettings + attributes + in + Nri.Ui.styled div + settings.containerType + (padding (px settings.padding) :: settings.css) + settings.attributes + settings.content + + +{-| Used for the default container case. +-} +default : Attribute msg +default = + Attribute identity + + +defaultSettings : Settings msg +defaultSettings = + { containerType = "default-container" + , padding = 20 + , css = defaultStyles + , content = [] + , attributes = [] + } + + +defaultStyles : List Css.Style +defaultStyles = + [ borderRadius (px 8) + , border3 (px 1) solid Colors.gray92 + , boxShadow5 zero (px 1) (px 1) zero (rgba 0 0 0 0.25) + , backgroundColor Colors.white + ] + + +{-| Used when there are a lot of containers. +-} +gray : Attribute msg +gray = + Attribute <| + \config -> + { config + | containerType = "gray-container" + , padding = 20 + , css = grayStyles + } + + +grayStyles : List Css.Style +grayStyles = + [ borderRadius (px 8) + , backgroundColor Colors.gray96 + ] + + +{-| -} +disabled : Attribute msg +disabled = + Attribute <| + \config -> + { config + | containerType = "disabled-container" + , padding = 20 + , css = disabledStyles + } + + +disabledStyles : List Css.Style +disabledStyles = + [ borderRadius (px 8) + , border3 (px 1) solid Colors.gray92 + , backgroundColor Colors.white + , color Colors.gray45 + ] + + +{-| -} +invalid : Attribute msg +invalid = + Attribute <| + \config -> + { config + | containerType = "invalid-container" + , padding = 20 + , css = invalidStyles + } + + +invalidStyles : List Css.Style +invalidStyles = + [ borderRadius (px 8) + , border3 (px 1) solid Colors.purpleLight + , boxShadow5 zero (px 1) (px 1) zero Colors.purple + , backgroundColor Colors.purpleLight + ] + + +{-| Used for containers of interactive elements. +-} +pillow : Attribute msg +pillow = + Attribute <| + \config -> + { config + | containerType = "pillow-container" + , padding = 40 + , css = pillowStyles + } + + +pillowStyles : List Style +pillowStyles = + [ borderRadius (px 20) + , border3 (px 1) solid Colors.gray92 + , boxShadow5 zero (px 2) (px 4) zero (rgba 0 0 0 0.25) + , backgroundColor Colors.white + , withMedia [ mobile ] + [ borderRadius (px 8) + , padding (px 20) + ] + ] + + +{-| Used for clickable cards +-} +buttony : Attribute msg +buttony = + Attribute <| + \config -> + { config + | containerType = "buttony-container" + , css = buttonyStyles + } + + +buttonyStyles : List Style +buttonyStyles = + [ borderRadius (px 20) + , border3 (px 1) solid Colors.gray85 + , borderBottom3 (px 4) solid Colors.gray85 + , backgroundColor Colors.white + , withMedia [ mobile ] + [ borderRadius (px 8) + ] + ] + + +{-| Provide a list of custom HTML. +-} +html : List (Html msg) -> Attribute msg +html content = + Attribute <| \config -> { config | content = content } + + +{-| Provide a plain-text string. +-} +plaintext : String -> Attribute msg +plaintext content = + Attribute <| \config -> { config | content = [ text content ] } + + +{-| Provide a string that will be rendered as markdown. + +Note that you may need to remove extra margin added by default +to `p` tags by user agents. + +-} +markdown : String -> Attribute msg +markdown content = + Attribute <| + \config -> + { config + | content = + Markdown.toHtml Nothing content + |> List.map fromUnstyled + } diff --git a/styleguide-app/Debug/Control/Extra.elm b/styleguide-app/Debug/Control/Extra.elm new file mode 100644 index 00000000..3003a474 --- /dev/null +++ b/styleguide-app/Debug/Control/Extra.elm @@ -0,0 +1,43 @@ +module Debug.Control.Extra exposing + ( float + , list, listItem, optionalListItem + ) + +{-| + +@docs float +@docs list, listItem, optionalListItem + +-} + +import Debug.Control as Control exposing (Control) + + +{-| -} +float : Float -> Control Float +float default = + Control.map (String.toFloat >> Maybe.withDefault default) + (Control.string (String.fromFloat default)) + + +{-| Use with `listItem` and `optionalListItem` +-} +list : Control (List a) +list = + Control.record [] + + +{-| -} +listItem : String -> Control a -> Control (List a) -> Control (List a) +listItem name accessor accumulator = + Control.field name + (Control.map List.singleton accessor) + (Control.map (++) accumulator) + + +{-| -} +optionalListItem : String -> Control a -> Control (List a) -> Control (List a) +optionalListItem name accessor accumulator = + Control.field name + (Control.map (List.singleton >> List.filterMap identity) (Control.maybe False accessor)) + (Control.map (++) accumulator) diff --git a/styleguide-app/Examples/Balloon.elm b/styleguide-app/Examples/Balloon.elm index 528d210c..5c44dde2 100644 --- a/styleguide-app/Examples/Balloon.elm +++ b/styleguide-app/Examples/Balloon.elm @@ -9,6 +9,7 @@ module Examples.Balloon exposing (example, State, Msg) import Category exposing (Category(..)) import Css import Debug.Control as Control exposing (Control) +import Debug.Control.Extra as ControlExtra import Example exposing (Example) import Examples.IconExamples as IconExamples import Html.Styled exposing (Html, div, fromUnstyled, text) @@ -83,12 +84,12 @@ widthOptions = [ ( "px" , Control.map (\w -> ( "Balloon.widthPx " ++ String.fromFloat w, Balloon.widthPx w )) - (controlFloat 50) + (ControlExtra.float 50) ) , ( "%" , Control.map (\w -> ( "Balloon.widthPct " ++ String.fromFloat w, Balloon.widthPct w )) - (controlFloat 50) + (ControlExtra.float 50) ) ] @@ -97,13 +98,7 @@ paddingOptions : Control ( String, Balloon.Attribute ) paddingOptions = Control.map (\w -> ( "Balloon.paddingPx " ++ String.fromFloat w, Balloon.paddingPx w )) - (controlFloat 10) - - -controlFloat : Float -> Control Float -controlFloat default = - Control.map (String.toFloat >> Maybe.withDefault default) - (Control.string (String.fromFloat default)) + (ControlExtra.float 10) {-| -} diff --git a/styleguide-app/Examples/Container.elm b/styleguide-app/Examples/Container.elm index eb2b319e..52214750 100644 --- a/styleguide-app/Examples/Container.elm +++ b/styleguide-app/Examples/Container.elm @@ -8,23 +8,27 @@ module Examples.Container exposing (Msg, State, example) import Category exposing (Category(..)) import Css +import Debug.Control as Control exposing (Control) +import Debug.Control.Extra as ControlExtra import Example exposing (Example) -import Html.Styled as Html +import Html.Styled as Html exposing (Html) import Html.Styled.Attributes exposing (css) import Html.Styled.Events exposing (onClick) import KeyboardSupport exposing (Direction(..), Key(..)) import Nri.Ui.Button.V10 as Button import Nri.Ui.Colors.V1 as Colors -import Nri.Ui.Container.V1 as Container +import Nri.Ui.Container.V2 as Container import Nri.Ui.Heading.V2 as Heading +import Nri.Ui.Svg.V1 as Svg import Nri.Ui.Text.V5 as Text +import Nri.Ui.UiIcon.V1 as UiIcon {-| -} example : Example State Msg example = { name = "Container" - , version = 1 + , version = 2 , categories = [ Layout ] , keyboardSupport = [] , state = init @@ -32,52 +36,147 @@ example = , subscriptions = \_ -> Sub.none , view = \state -> - [ Heading.h3 [ Heading.css [ Css.marginTop (Css.px 8) ] ] - [ Html.text "General Container" ] - , Html.text "Used for the general container case." - , Container.general [] (Html.text "Content, content...") - , Heading.h3 [ Heading.css [ Css.marginTop (Css.px 8) ] ] - [ Html.text "Alternate Container" ] - , Html.text "Used when there are a lot of containers." - , Container.alternate [] (Html.text "Content, content...") - , Heading.h3 [ Heading.css [ Css.marginTop (Css.px 8) ] ] - [ Html.text "Interactable Container" ] - , Html.text "Usually used for larger containers with many elements inside." - , Container.interactable [] (Html.text "Content, content...") - , Heading.h3 [ Heading.css [ Css.marginTop (Css.px 8) ] ] - [ Html.text "Disabled Container" ] - , Html.text "Used to indicate content is locked/inaccessible" - , Container.disabled [] (Html.text "Content, content...") - , Heading.h3 [ Heading.css [ Css.marginTop (Css.px 8) ] ] - [ Html.text "Invalid Container" ] - , Html.text "Used to indicate content is invalid" - , Container.invalid [] (Html.text "Content, content...") - , Heading.h3 [ Heading.css [ Css.marginTop (Css.px 8) ] ] - [ Html.text "Interactable container with a label" ] - , Html.text "Used for helpful tidbits." - , Container.interactableWithLabel "The label" <| - Html.text "Content, content..." + let + attributes = + Control.currentValue state.control + in + [ Control.view UpdateControl state.control + |> Html.fromUnstyled + , viewExample + { name = "Default Container" + , description = "Your go-to container." + } + (Container.default :: attributes) + , viewExample + { name = "Gray Container" + , description = "A container that doesn’t draw too much attention to itself." + } + (Container.gray :: attributes) + , viewExample + { name = "Pillow Container" + , description = "When you want something big and soft." + } + (Container.pillow :: attributes) + , viewExample + { name = "Buttony Container" + , description = "Used for clickable button card things." + } + (Container.buttony :: attributes) + , viewExample + { name = "Disabled Container" + , description = "Used to indicate content is locked/inaccessible" + } + (Container.disabled :: attributes) + , viewExample + { name = "Invalid Container" + , description = "Used to indicate content is invalid" + } + (Container.invalid :: attributes) ] } +viewExample : { name : String, description : String } -> List (Container.Attribute msg) -> Html msg +viewExample { name, description } attributes = + Html.section + [ css + [ Css.marginTop (Css.px 20) + ] + ] + [ Heading.h3 [] [ Html.text name ] + , Html.text description + , Container.view attributes + ] + + +{-| -} +type alias State = + { control : Control (List (Container.Attribute Msg)) + } + + {-| -} init : State init = - {} + { control = + ControlExtra.list + |> ControlExtra.optionalListItem "paddingPx" controlPaddingPx + |> ControlExtra.optionalListItem "css" controlCss + |> ControlExtra.listItem "content" controlContent + } + + +controlPaddingPx : Control (Container.Attribute msg) +controlPaddingPx = + Control.map Container.paddingPx (ControlExtra.float 20) + + +controlCss : Control (Container.Attribute msg) +controlCss = + Control.map Container.css + (Control.value + [ Css.minHeight (Css.px 100) + , Css.hover [ Css.backgroundColor Colors.glacier ] + ] + ) + + +controlContent : Control (Container.Attribute msg) +controlContent = + Control.choice + [ ( "plain text (short)" + , Control.string "Content, content..." + |> Control.map Container.plaintext + ) + , ( "plain text (long)" + , Control.stringTextarea romeoAndJulietQuotation + |> Control.map Container.plaintext + ) + , ( "markdown" + , Control.string romeoAndJulietQuotation + |> Control.map Container.markdown + ) + , ( "HTML (short)" + , Control.value + (Container.html + [ UiIcon.footsteps + |> Svg.withHeight (Css.px 200) + |> Svg.withColor Colors.grassland + |> Svg.toHtml + ] + ) + ) + ] + + +romeoAndJulietQuotation : String +romeoAndJulietQuotation = + """ + Two households, both alike in dignity, + In fair Verona, where we lay our scene, + From ancient grudge break to new mutiny, + Where civil blood makes civil hands unclean. + From forth the fatal loins of these two foes + A pair of star-cross’d lovers take their life; + Whose misadventured piteous overthrows + Do with their death bury their parents’ strife. + The fearful passage of their death-mark’d love, + And the continuance of their parents’ rage, + Which, but their children’s end, nought could remove, + Is now the two hours’ traffic of our stage; + The which if you with patient ears attend, + What here shall miss, our toil shall strive to mend. + """ {-| -} -type alias State = - {} - - -{-| -} -type alias Msg = - () +type Msg + = UpdateControl (Control (List (Container.Attribute Msg))) {-| -} update : Msg -> State -> ( State, Cmd Msg ) update msg state = - ( state, Cmd.none ) + case msg of + UpdateControl newControl -> + ( { state | control = newControl }, Cmd.none ) diff --git a/tests/elm-verify-examples.json b/tests/elm-verify-examples.json index db6f42cd..0576b25b 100644 --- a/tests/elm-verify-examples.json +++ b/tests/elm-verify-examples.json @@ -13,6 +13,7 @@ "Nri.Ui.ClickableSvg.V2", "Nri.Ui.ClickableText.V3", "Nri.Ui.Container.V1", + "Nri.Ui.Container.V2", "Nri.Ui.Colors.Extra", "Nri.Ui.Colors.V1", "Nri.Ui.Confetti.V2",