diff --git a/src/Nri/Ui/Svg/V1.elm b/src/Nri/Ui/Svg/V1.elm index c218ba62..927e9804 100644 --- a/src/Nri/Ui/Svg/V1.elm +++ b/src/Nri/Ui/Svg/V1.elm @@ -1,42 +1,102 @@ module Nri.Ui.Svg.V1 exposing ( Svg - , withColor + , withColor, withLabel, withWidth, withHeight , fromHtml, toHtml ) {-| @docs Svg -@docs withColor +@docs withColor, withLabel, withWidth, withHeight @docs fromHtml, toHtml -} +import Accessibility.Styled.Widget as Widget import Css exposing (Color) import Html.Styled as Html exposing (Html) import Html.Styled.Attributes as Attributes -{-| -} +{-| Opaque type describing a non-interactable Html element. +-} type Svg - = Svg (Html Never) - - -{-| -} -withColor : Color -> Svg -> Svg -withColor color (Svg svg) = - Svg (Html.span [ Attributes.css [ Css.color color ] ] [ svg ]) + = Svg + { icon : Html Never + , color : Maybe Color + , width : Maybe Css.Px + , height : Maybe Css.Px + , label : Maybe String + } {-| Tag html as being an svg. -} fromHtml : Html Never -> Svg -fromHtml = +fromHtml icon = Svg + { icon = icon + , color = Nothing + , height = Nothing + , width = Nothing + , label = Nothing + } + + +{-| -} +withColor : Color -> Svg -> Svg +withColor color (Svg record) = + Svg { record | color = Just color } + + +{-| Add a string aria-label property to the element. + +See [Using the aria-label attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute) for +guidelines of when and how to use this attribute. + +-} +withLabel : String -> Svg -> Svg +withLabel label (Svg record) = + Svg { record | label = Just label } + + +{-| -} +withWidth : Css.Px -> Svg -> Svg +withWidth width (Svg record) = + Svg { record | width = Just width } + + +{-| -} +withHeight : Css.Px -> Svg -> Svg +withHeight height (Svg record) = + Svg { record | height = Just height } {-| Render an svg. -} toHtml : Svg -> Html msg -toHtml (Svg svg) = - Html.map never svg +toHtml (Svg record) = + let + css = + List.filterMap identity + [ Maybe.map Css.color record.color + , Maybe.map Css.width record.width + , Maybe.map Css.height record.height + ] + + attributes = + List.filterMap identity + [ if List.isEmpty css then + Nothing + + else + Just (Attributes.css (Css.display Css.inlineBlock :: css)) + , Maybe.map Widget.label record.label + ] + in + case attributes of + [] -> + Html.map never record.icon + + _ -> + Html.div attributes [ Html.map never record.icon ] diff --git a/styleguide-app/Examples/Svg.elm b/styleguide-app/Examples/Svg.elm new file mode 100644 index 00000000..ee278612 --- /dev/null +++ b/styleguide-app/Examples/Svg.elm @@ -0,0 +1,195 @@ +module Examples.Svg exposing + ( Msg + , State + , example + , init + , update + ) + +{-| + +@docs Msg +@docs State +@docs example +@docs init +@docs update + +-} + +import Color exposing (Color) +import Css +import Examples.IconExamples as IconExamples +import Html.Styled as Html +import Html.Styled.Attributes as Attributes +import Html.Styled.Events as Events +import ModuleExample exposing (Category(..), ModuleExample) +import Nri.Ui.Colors.Extra exposing (fromCssColor, toCssColor) +import Nri.Ui.Colors.V1 as Colors +import Nri.Ui.Heading.V2 as Heading +import Nri.Ui.Select.V6 as Select +import Nri.Ui.Svg.V1 as Svg +import Nri.Ui.UiIcon.V1 as UiIcon + + +{-| -} +example : (Msg -> msg) -> State -> ModuleExample msg +example parentMessage state = + { name = "Nri.Ui.Svg.V1" + , category = Icons + , content = + [ viewSettings state + |> Html.map parentMessage + , viewResults state + ] + } + + +viewSettings : State -> Html.Html Msg +viewSettings state = + Html.div + [ Attributes.css + [ Css.displayFlex + , Css.justifyContent Css.spaceBetween + ] + ] + [ Html.label [] + [ Html.text "Color: " + , Html.input + [ Attributes.type_ "color" + , Attributes.value (Color.toHex state.color) + , Events.onInput (SetColor << Color.fromHex) + ] + [] + ] + , Html.label [] + [ Html.text "Width: " + , Html.input + [ Attributes.type_ "range" + , Attributes.min "0" + , Attributes.max "200" + , Attributes.value (String.fromFloat state.width) + , Events.onInput (SetWidth << String.toFloat) + ] + [] + ] + , Html.label [] + [ Html.text "Height: " + , Html.input + [ Attributes.type_ "range" + , Attributes.min "0" + , Attributes.max "200" + , Attributes.value (String.fromFloat state.height) + , Events.onInput (SetHeight << String.toFloat) + ] + [] + ] + , Html.label [] + [ Html.text "Aria-label: " + , Html.input + [ Attributes.value state.label + , Events.onInput SetLabel + ] + [] + ] + ] + + +viewResults : State -> Html.Html msg +viewResults state = + let + ( red, green, blue ) = + Color.toRGB state.color + in + Html.div [ Attributes.css [ Css.displayFlex ] ] + [ Html.pre + [ Attributes.css + [ Css.width (Css.px 400) + , Css.marginRight (Css.px 20) + ] + ] + [ [ "color : Css.Color" + , "color =" + , " Css.rgb " ++ String.fromFloat red ++ " " ++ String.fromFloat green ++ " " ++ String.fromFloat blue + , "" + , "" + , "renderedSvg : Svg " + , "renderedSvg = " + , " UiIcon.newspaper" + , " |> Svg.withColor color" + , " |> Svg.withWidth (Css.px " ++ String.fromFloat state.width ++ ")" + , " |> Svg.withHeight (Css.px " ++ String.fromFloat state.height ++ ")" + , " |> Svg.withLabel \"" ++ state.label ++ "\"" + , " |> Svg.toHtml" + ] + |> String.join "\n" + |> Html.text + ] + , Html.div + [ Attributes.css + [ Css.backgroundColor Colors.gray92 + , Css.flexGrow (Css.int 2) + ] + ] + [ UiIcon.newspaper + |> Svg.withColor (toCssColor state.color) + |> Svg.withWidth (Css.px state.width) + |> Svg.withHeight (Css.px state.height) + |> Svg.withLabel state.label + |> Svg.toHtml + ] + ] + + +{-| -} +type alias State = + { color : Color + , width : Float + , height : Float + , label : String + } + + +{-| -} +init : State +init = + { color = fromCssColor Colors.blue + , width = 30 + , height = 30 + , label = "Newspaper" + } + + +{-| -} +type Msg + = SetColor (Result String Color) + | SetWidth (Maybe Float) + | SetHeight (Maybe Float) + | SetLabel String + + +{-| -} +update : Msg -> State -> ( State, Cmd Msg ) +update msg state = + case msg of + SetColor (Ok color) -> + ( { state | color = color } + , Cmd.none + ) + + SetColor (Err err) -> + ( state, Cmd.none ) + + SetWidth (Just width) -> + ( { state | width = width }, Cmd.none ) + + SetWidth Nothing -> + ( state, Cmd.none ) + + SetHeight (Just height) -> + ( { state | height = height }, Cmd.none ) + + SetHeight Nothing -> + ( state, Cmd.none ) + + SetLabel label -> + ( { state | label = label }, Cmd.none ) diff --git a/styleguide-app/NriModules.elm b/styleguide-app/NriModules.elm index 0f63072f..d40215d6 100644 --- a/styleguide-app/NriModules.elm +++ b/styleguide-app/NriModules.elm @@ -24,6 +24,7 @@ import Examples.Select import Examples.Slide import Examples.SlideModal import Examples.SortableTable +import Examples.Svg import Examples.Table import Examples.Tabs import Examples.Text @@ -55,6 +56,7 @@ type alias ModuleStates = , slideModalExampleState : Examples.SlideModal.State , slideExampleState : Examples.Slide.State , sortableTableState : Examples.SortableTable.State + , svgState : Examples.Svg.State , tabsExampleState : Examples.Tabs.Tab , tooltipExampleState : Examples.Tooltip.State } @@ -78,6 +80,7 @@ init = , slideModalExampleState = Examples.SlideModal.init , slideExampleState = Examples.Slide.init , sortableTableState = Examples.SortableTable.init + , svgState = Examples.Svg.init , tabsExampleState = Examples.Tabs.First , tooltipExampleState = Examples.Tooltip.init } @@ -101,6 +104,7 @@ type Msg | SlideModalExampleMsg Examples.SlideModal.Msg | SlideExampleMsg Examples.Slide.Msg | SortableTableMsg Examples.SortableTable.Msg + | SvgMsg Examples.Svg.Msg | TabsExampleMsg Examples.Tabs.Tab | TooltipExampleMsg Examples.Tooltip.Msg | NoOp @@ -184,6 +188,15 @@ update outsideMsg moduleStates = in ( moduleStates, Cmd.none ) + SvgMsg msg -> + let + ( svgState, cmd ) = + Examples.Svg.update msg moduleStates.svgState + in + ( { moduleStates | svgState = svgState } + , Cmd.map SvgMsg cmd + ) + TableExampleMsg msg -> let ( tableExampleState, cmd ) = @@ -321,6 +334,7 @@ nriThemedModules model = , Examples.Slide.example SlideExampleMsg model.slideExampleState , Examples.SlideModal.example SlideModalExampleMsg model.slideModalExampleState , Examples.SortableTable.example SortableTableMsg model.sortableTableState + , Examples.Svg.example SvgMsg model.svgState , Examples.Table.example TableExampleMsg model.tableExampleState , Examples.Tabs.example TabsExampleMsg model.tabsExampleState , Examples.Text.example