From 61daee0f472de9558232bf04f81081a90146eeaf Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Tue, 19 Mar 2024 16:06:58 -0500 Subject: [PATCH 01/58] Add MediaQuery.builder --- component-catalog/src/UsageExample.elm | 12 +- component-catalog/src/UsageExamples.elm | 6 + .../src/UsageExamples/MediaQueryBuilder.elm | 54 ++++++++ .../tests/MediaQueryBuilderSpec.elm | 59 +++++++++ src/Nri/Ui/MediaQuery/V1.elm | 124 ++++++++++++++++++ 5 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 component-catalog/src/UsageExamples/MediaQueryBuilder.elm create mode 100644 component-catalog/tests/MediaQueryBuilderSpec.elm diff --git a/component-catalog/src/UsageExample.elm b/component-catalog/src/UsageExample.elm index b8fdba3fe..f9a76102b 100644 --- a/component-catalog/src/UsageExample.elm +++ b/component-catalog/src/UsageExample.elm @@ -1,4 +1,4 @@ -module UsageExample exposing (UsageExample, fromRouteName, fullName, preview, routeName, view, wrapMsg, wrapState) +module UsageExample exposing (UsageExample, fromRouteName, fullName, noop, preview, routeName, stateless, view, wrapMsg, wrapState) import Category exposing (Category) import Css @@ -42,6 +42,11 @@ fromRouteName name = String.replace "-" " " name +noop : noop -> UsageExample state msg -> UsageExample state noop +noop msg = + wrapMsg (always msg) (always Nothing) + + wrapMsg : (msg -> msg2) -> (msg2 -> Maybe msg) @@ -69,6 +74,11 @@ wrapMsg wrapMsg_ unwrapMsg example = } +stateless : stateless -> UsageExample () msg -> UsageExample stateless msg +stateless state = + wrapState (always state) (always (Just ())) + + wrapState : (state -> state2) -> (state2 -> Maybe state) diff --git a/component-catalog/src/UsageExamples.elm b/component-catalog/src/UsageExamples.elm index a10bb5437..bef1d758d 100644 --- a/component-catalog/src/UsageExamples.elm +++ b/component-catalog/src/UsageExamples.elm @@ -4,6 +4,7 @@ import UsageExample exposing (UsageExample) import UsageExamples.ClickableCardWithTooltip as ClickableCardWithTooltip import UsageExamples.FocusLoop as FocusLoop import UsageExamples.Form as Form +import UsageExamples.MediaQueryBuilder as MediaQueryBuilder all : List (UsageExample State Msg) @@ -65,6 +66,9 @@ all = _ -> Nothing ) + , MediaQueryBuilder.example + |> UsageExample.noop NoOp + |> UsageExample.stateless Stateless ] @@ -72,9 +76,11 @@ type State = ClickableCardWithTooltipState ClickableCardWithTooltip.State | FormState Form.State | FocusLoopState FocusLoop.State + | Stateless type Msg = ClickableCardWithTooltipMsg ClickableCardWithTooltip.Msg | FormMsg Form.Msg | FocusLoopMsg FocusLoop.Msg + | NoOp diff --git a/component-catalog/src/UsageExamples/MediaQueryBuilder.elm b/component-catalog/src/UsageExamples/MediaQueryBuilder.elm new file mode 100644 index 000000000..d0c410657 --- /dev/null +++ b/component-catalog/src/UsageExamples/MediaQueryBuilder.elm @@ -0,0 +1,54 @@ +module UsageExamples.MediaQueryBuilder exposing (example) + +{-| + + @docs example + +-} + +import Category +import Css exposing (backgroundColor, before, color, fontFamily, fontSize, property, px, sansSerif) +import Html.Styled exposing (..) +import Nri.Ui.Container.V2 as Container +import Nri.Ui.MediaQuery.V1 as MediaQuery +import UsageExample exposing (UsageExample) + + +example : UsageExample () () +example = + { name = "MediaQueryBuilder" + , categories = [ Category.Layout ] + , init = () + , update = \_ _ -> ( (), Cmd.none ) + , view = always view + , about = [] + , subscriptions = \_ -> Sub.none + } + + +view : List (Html msg) +view = + let + content str = + before [ property "content" ("'" ++ str ++ "'") ] + in + [ h2 [] [ text "MediaQueryBuilder" ] + , Container.view + [ Container.buttony + , Container.css + (MediaQuery.builder + [ content "I am the base style, visible to all devices" + , fontFamily sansSerif + ] + |> MediaQuery.onNarrowMobile + [ content "I am the narrow mobile style, visible only to screens" ] + |> MediaQuery.onQuizEngineMobile + [ content "I am the quiz engine mobile style, visible only to screens" ] + |> MediaQuery.onMobile + [ content "I am the mobile style, visible only to screens" ] + |> MediaQuery.onDesktop + [ content "I am the desktop style, visible only to screens" ] + |> MediaQuery.toStyles + ) + ] + ] diff --git a/component-catalog/tests/MediaQueryBuilderSpec.elm b/component-catalog/tests/MediaQueryBuilderSpec.elm new file mode 100644 index 000000000..5da399766 --- /dev/null +++ b/component-catalog/tests/MediaQueryBuilderSpec.elm @@ -0,0 +1,59 @@ +module MediaQueryBuilderSpec exposing (suite) + +import Css exposing (fontSize, px) +import Html.Styled exposing (div, toUnstyled) +import Html.Styled.Attributes exposing (css) +import Nri.Ui.MediaQuery.V1 as MediaQuery +import Test exposing (..) +import Test.Html.Query as Query +import Test.Html.Selector as Selector + + +suite : Test +suite = + describe "MediaQuery.builder" + [ test "it puts queries in the correct order" <| + \() -> + div + [ css + (MediaQuery.builder [ fontSize (px 99) ] + |> MediaQuery.onNarrowMobile [ fontSize (px 1) ] + |> MediaQuery.onQuizEngineMobile [ fontSize (px 2) ] + |> MediaQuery.onMobile [ fontSize (px 3) ] + |> MediaQuery.onDesktop [ fontSize (px 4) ] + |> MediaQuery.toStyles + ) + ] + [] + |> toUnstyled + |> Query.fromHtml + |> Query.find [ Selector.tag "style" ] + |> Query.has + [ Selector.text (String.trim """ +._ebc36d6{font-size:99px;} +@media only screen and (max-width: 1000px){._ebc36d6{font-size:3px;}} +@media only screen and (max-width: 750px){._ebc36d6{font-size:2px;}} +@media only screen and (max-width: 500px){._ebc36d6{font-size:1px;}} +@media only screen and (min-width: 1001px){._ebc36d6{font-size:4px;}} + """) + ] + , test "it works with only a single breakpoint" <| + \() -> + div + [ css + (MediaQuery.builder [ fontSize (px 99) ] + |> MediaQuery.onMobile [ fontSize (px 3) ] + |> MediaQuery.toStyles + ) + ] + [] + |> toUnstyled + |> Query.fromHtml + |> Query.find [ Selector.tag "style" ] + |> Query.has + [ Selector.text (String.trim """ +._d0428ccb{font-size:99px;} +@media only screen and (max-width: 1000px){._d0428ccb{font-size:3px;}} + """) + ] + ] diff --git a/src/Nri/Ui/MediaQuery/V1.elm b/src/Nri/Ui/MediaQuery/V1.elm index ad27a768b..232a92b25 100644 --- a/src/Nri/Ui/MediaQuery/V1.elm +++ b/src/Nri/Ui/MediaQuery/V1.elm @@ -2,12 +2,15 @@ module Nri.Ui.MediaQuery.V1 exposing ( anyMotion, prefersReducedMotion , highContrastMode, notHighContrastMode , withViewport + , Builder, builder, toStyles + , onMobile, onNarrowMobile, onDesktop , mobile, notMobile , mobileBreakpoint , quizEngineMobile, notQuizEngineMobile , quizEngineBreakpoint , narrowMobile, notNarrowMobile , narrowMobileBreakpoint + , onQuizEngineMobile ) {-| Major version changes: @@ -20,6 +23,7 @@ Patch changes: - adds narrowMobileBreakpoint and deprecates narrowMobileBreakPoint - adds withViewport for convenience when matching specific viewport size ranges - fix `not` queries to not overlap with the regular breakpoint queries + - adds `Builder` for more efficient and fool-proof media query construction Standard media queries for responsive pages. @@ -40,6 +44,12 @@ Standard media queries for responsive pages. @docs withViewport +### Builder + +@docs Builder, builder, toStyles +@docs onMobile, onQuizEngine, onNarrowMobile, onDesktop + + ### 1000px breakpoint @docs mobile, notMobile @@ -61,6 +71,7 @@ Standard media queries for responsive pages. import Css exposing (Style, px) import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia, withMediaQuery) +import Maybe.Extra as Maybe {-| @@ -114,6 +125,12 @@ mobile = {-| Styles using the `mobileBreakpoint` value as the minWidth. + + Note: This does *not* use the min-width query, rather it uses the max-width query + with `not`. This has implications for screen readers and print–mobile is ignored by + these media types, but included for notMobile. Unfortunately, elm-css does not have + `all` as a MediaType (and no means to use a custom one). + -} notMobile : MediaQuery notMobile = @@ -135,6 +152,12 @@ quizEngineMobile = {-| Styles using the `quizEngineBreakpoint` value as the minWidth. + + Note: This does *not* use the min-width query, rather it uses the max-width query + with `not`. This has implications for screen readers and print–mobile is ignored by + these media types, but included for notMobile. Unfortunately, elm-css does not have + `all` as a MediaType (and no means to use a custom one). + -} notQuizEngineMobile : MediaQuery notQuizEngineMobile = @@ -156,6 +179,12 @@ narrowMobile = {-| Styles using the `quizEngineBreakpoint` value as the minWidth. + + Note: This does *not* use the min-width query, rather it uses the max-width query + with `not`. This has implications for screen readers and print–mobile is ignored by + these media types, but included for notMobile. Unfortunately, elm-css does not have + `all` as a MediaType (and no means to use a custom one). + -} notNarrowMobile : MediaQuery notNarrowMobile = @@ -167,3 +196,98 @@ notNarrowMobile = narrowMobileBreakpoint : Css.Px narrowMobileBreakpoint = px 500 + + +{-| A builder for creating efficient media queries. + + Removes the need to think about the order of media queries or the nuances of + `not` queries with respect to screen readers and print. + +-} +type alias Builder = + { baseStyles : List Style + , mobileStyles : Maybe (List Style) + , quizEngineStyles : Maybe (List Style) + , narrowMobileStyles : Maybe (List Style) + , desktopStyles : Maybe (List Style) + } + + +{-| Initialize a desktop-first media query builder with the given base styles. +-} +builder : List Style -> Builder +builder baseStyles = + { baseStyles = baseStyles + , mobileStyles = Nothing + , quizEngineStyles = Nothing + , narrowMobileStyles = Nothing + , desktopStyles = Nothing + } + + +{-| Add mobile styles to the builder. + + Smaller breakpoints will inherit these styles unless overridden. + +-} +onMobile : List Style -> Builder -> Builder +onMobile mobileStyles b = + { b | mobileStyles = Just mobileStyles } + + +{-| Add quiz engine mobile styles to the builder. + + Smaller breakpoints will inherit these styles unless overridden. + +-} +onQuizEngineMobile : List Style -> Builder -> Builder +onQuizEngineMobile quizEngineStyles b = + { b | quizEngineStyles = Just quizEngineStyles } + + +{-| Add narrow mobile styles to the builder. + + Smaller breakpoints will inherit these styles unless overridden. + +-} +onNarrowMobile : List Style -> Builder -> Builder +onNarrowMobile narrowMobileStyles b = + { b | narrowMobileStyles = Just narrowMobileStyles } + + +{-| Add desktop ONLY styles to the builder. + + Smaller breakpoints will NOT inherit these styles. For styles that should apply across + all breakpoints, use the base styles. + +-} +onDesktop : List Style -> Builder -> Builder +onDesktop desktopStyles b = + { b | desktopStyles = Just desktopStyles } + + +{-| Generate a list of styles from the a builder. +-} +toStyles : Builder -> List Style +toStyles b = + let + maybeAddBreakpoint breakpoint maybeStyles = + -- Yes, cons adds to the front of the list. + -- Yes, these are in the wrong order for a media query when you consider that fact. + -- Yes, this is intentional. + -- + -- elm-css reverses the order of media queries when it generates the CSS. + -- + -- Very confusing, I know. + -- + -- That's why we have a builder. + Maybe.cons (Maybe.map (withMedia [ breakpoint ]) maybeStyles) + in + b.baseStyles + |> maybeAddBreakpoint mobile b.mobileStyles + |> maybeAddBreakpoint quizEngineMobile b.quizEngineStyles + |> maybeAddBreakpoint narrowMobile b.narrowMobileStyles + -- Don't use notMobile here, lest `onDesktop` will behave inconsistently with + -- the other builder functions with respect to screen readers and print. + -- ONLY things in the base styles should be visible to these other media types. + |> maybeAddBreakpoint (only screen [ minWidth (px 1001) ]) b.desktopStyles From 0925c9a02e37ac090cf4e66ab8b7e38593e7f28c Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Tue, 19 Mar 2024 16:29:18 -0500 Subject: [PATCH 02/58] Docs typo --- src/Nri/Ui/MediaQuery/V1.elm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Nri/Ui/MediaQuery/V1.elm b/src/Nri/Ui/MediaQuery/V1.elm index 232a92b25..c636bd4d4 100644 --- a/src/Nri/Ui/MediaQuery/V1.elm +++ b/src/Nri/Ui/MediaQuery/V1.elm @@ -3,14 +3,13 @@ module Nri.Ui.MediaQuery.V1 exposing , highContrastMode, notHighContrastMode , withViewport , Builder, builder, toStyles - , onMobile, onNarrowMobile, onDesktop + , onMobile, onQuizEngineMobile, onNarrowMobile, onDesktop , mobile, notMobile , mobileBreakpoint , quizEngineMobile, notQuizEngineMobile , quizEngineBreakpoint , narrowMobile, notNarrowMobile , narrowMobileBreakpoint - , onQuizEngineMobile ) {-| Major version changes: @@ -47,7 +46,7 @@ Standard media queries for responsive pages. ### Builder @docs Builder, builder, toStyles -@docs onMobile, onQuizEngine, onNarrowMobile, onDesktop +@docs onMobile, onQuizEngineMobile, onNarrowMobile, onDesktop ### 1000px breakpoint From 6a36a92cf2c5cbeba60921d76d2a82eb39fec86a Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Tue, 19 Mar 2024 16:32:35 -0500 Subject: [PATCH 03/58] elm-review --- component-catalog/src/UsageExamples/MediaQueryBuilder.elm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/component-catalog/src/UsageExamples/MediaQueryBuilder.elm b/component-catalog/src/UsageExamples/MediaQueryBuilder.elm index d0c410657..070b7aa9f 100644 --- a/component-catalog/src/UsageExamples/MediaQueryBuilder.elm +++ b/component-catalog/src/UsageExamples/MediaQueryBuilder.elm @@ -7,7 +7,7 @@ module UsageExamples.MediaQueryBuilder exposing (example) -} import Category -import Css exposing (backgroundColor, before, color, fontFamily, fontSize, property, px, sansSerif) +import Css exposing (before, property) import Html.Styled exposing (..) import Nri.Ui.Container.V2 as Container import Nri.Ui.MediaQuery.V1 as MediaQuery @@ -37,9 +37,7 @@ view = [ Container.buttony , Container.css (MediaQuery.builder - [ content "I am the base style, visible to all devices" - , fontFamily sansSerif - ] + [ content "I am the base style, visible to all devices" ] |> MediaQuery.onNarrowMobile [ content "I am the narrow mobile style, visible only to screens" ] |> MediaQuery.onQuizEngineMobile From b7c41fc0da94aace7feba188c3920f9acbd8e6b0 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Tue, 19 Mar 2024 17:06:34 -0500 Subject: [PATCH 04/58] media query percy snapshots --- script/puppeteer-tests.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/script/puppeteer-tests.js b/script/puppeteer-tests.js index 344b8f25c..f2d545134 100644 --- a/script/puppeteer-tests.js +++ b/script/puppeteer-tests.js @@ -321,4 +321,20 @@ describe("UI tests", function () { page.close(); }); + + it("MediaQuery.builder", async function () { + page = await browser.newPage(); + + await page.emulateMediaFeatures([ + { name: "prefers-reduced-motion", value: "reduce" }, + ]); + + handlePageErrors(page); + await page.goto(`http://localhost:${PORT}/#/usage_example/MediaQueryBuilder`); + + await page.$("#maincontent"); + await percySnapshot(page, this.test.fullTitle(), { widths: [500, 750, 1000, 1024] }); + + page.close(); + }) }); From 1738680d65d25e0866e6823185b931505aa0be56 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Tue, 19 Mar 2024 17:08:57 -0500 Subject: [PATCH 05/58] prettier --- script/puppeteer-tests.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/script/puppeteer-tests.js b/script/puppeteer-tests.js index f2d545134..9b0194a5f 100644 --- a/script/puppeteer-tests.js +++ b/script/puppeteer-tests.js @@ -321,7 +321,7 @@ describe("UI tests", function () { page.close(); }); - + it("MediaQuery.builder", async function () { page = await browser.newPage(); @@ -330,11 +330,15 @@ describe("UI tests", function () { ]); handlePageErrors(page); - await page.goto(`http://localhost:${PORT}/#/usage_example/MediaQueryBuilder`); + await page.goto( + `http://localhost:${PORT}/#/usage_example/MediaQueryBuilder` + ); await page.$("#maincontent"); - await percySnapshot(page, this.test.fullTitle(), { widths: [500, 750, 1000, 1024] }); + await percySnapshot(page, this.test.fullTitle(), { + widths: [500, 750, 1000, 1024], + }); page.close(); - }) + }); }); From c0a8362664864820903bb233b754bb2ebe460a08 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Tue, 19 Mar 2024 19:32:19 -0500 Subject: [PATCH 06/58] MediaQuery.V2 --- .../src/UsageExamples/MediaQueryBuilder.elm | 36 +++-- elm.json | 1 + src/Nri/Ui/MediaQuery/V1.elm | 123 ------------------ src/Nri/Ui/MediaQuery/V2.elm | 84 ++++++++++++ 4 files changed, 107 insertions(+), 137 deletions(-) create mode 100644 src/Nri/Ui/MediaQuery/V2.elm diff --git a/component-catalog/src/UsageExamples/MediaQueryBuilder.elm b/component-catalog/src/UsageExamples/MediaQueryBuilder.elm index 070b7aa9f..2065fa6e5 100644 --- a/component-catalog/src/UsageExamples/MediaQueryBuilder.elm +++ b/component-catalog/src/UsageExamples/MediaQueryBuilder.elm @@ -7,10 +7,10 @@ module UsageExamples.MediaQueryBuilder exposing (example) -} import Category -import Css exposing (before, property) +import Css import Html.Styled exposing (..) import Nri.Ui.Container.V2 as Container -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import UsageExample exposing (UsageExample) @@ -30,23 +30,31 @@ view : List (Html msg) view = let content str = - before [ property "content" ("'" ++ str ++ "'") ] + Css.property "content" ("'" ++ str ++ "'") + + before str = + Css.before [ content str ] + + after str = + Css.after [ content str ] in [ h2 [] [ text "MediaQueryBuilder" ] , Container.view [ Container.buttony , Container.css - (MediaQuery.builder - [ content "I am the base style, visible to all devices" ] - |> MediaQuery.onNarrowMobile - [ content "I am the narrow mobile style, visible only to screens" ] - |> MediaQuery.onQuizEngineMobile - [ content "I am the quiz engine mobile style, visible only to screens" ] - |> MediaQuery.onMobile - [ content "I am the mobile style, visible only to screens" ] - |> MediaQuery.onDesktop - [ content "I am the desktop style, visible only to screens" ] - |> MediaQuery.toStyles + (Css.displayFlex + :: Css.property "justify-content" "space-evenly" + :: before "Default" + :: after "Default" + :: MediaQuery.styles + [ MediaQuery.narrowMobile [ before "Narrow Mobile" ] + , MediaQuery.quizEngineMobile [ before "Quiz Engine Mobile" ] + , MediaQuery.mobile [ before "Mobile" ] + , MediaQuery.notNarrowMobile [ after "Not Narrow Mobile" ] + , MediaQuery.notQuizEngineMobile [ after "Not Quiz Engine Mobile" ] + , MediaQuery.notMobile [ after "Not Mobile" ] + ] ) + , Container.plaintext " | " ] ] diff --git a/elm.json b/elm.json index bc36f01b4..291708fc8 100644 --- a/elm.json +++ b/elm.json @@ -53,6 +53,7 @@ "Nri.Ui.Mark.V6", "Nri.Ui.MasteryIcon.V1", "Nri.Ui.MediaQuery.V1", + "Nri.Ui.MediaQuery.V2", "Nri.Ui.Menu.V4", "Nri.Ui.Message.V4", "Nri.Ui.Modal.V12", diff --git a/src/Nri/Ui/MediaQuery/V1.elm b/src/Nri/Ui/MediaQuery/V1.elm index c636bd4d4..ad27a768b 100644 --- a/src/Nri/Ui/MediaQuery/V1.elm +++ b/src/Nri/Ui/MediaQuery/V1.elm @@ -2,8 +2,6 @@ module Nri.Ui.MediaQuery.V1 exposing ( anyMotion, prefersReducedMotion , highContrastMode, notHighContrastMode , withViewport - , Builder, builder, toStyles - , onMobile, onQuizEngineMobile, onNarrowMobile, onDesktop , mobile, notMobile , mobileBreakpoint , quizEngineMobile, notQuizEngineMobile @@ -22,7 +20,6 @@ Patch changes: - adds narrowMobileBreakpoint and deprecates narrowMobileBreakPoint - adds withViewport for convenience when matching specific viewport size ranges - fix `not` queries to not overlap with the regular breakpoint queries - - adds `Builder` for more efficient and fool-proof media query construction Standard media queries for responsive pages. @@ -43,12 +40,6 @@ Standard media queries for responsive pages. @docs withViewport -### Builder - -@docs Builder, builder, toStyles -@docs onMobile, onQuizEngineMobile, onNarrowMobile, onDesktop - - ### 1000px breakpoint @docs mobile, notMobile @@ -70,7 +61,6 @@ Standard media queries for responsive pages. import Css exposing (Style, px) import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia, withMediaQuery) -import Maybe.Extra as Maybe {-| @@ -124,12 +114,6 @@ mobile = {-| Styles using the `mobileBreakpoint` value as the minWidth. - - Note: This does *not* use the min-width query, rather it uses the max-width query - with `not`. This has implications for screen readers and print–mobile is ignored by - these media types, but included for notMobile. Unfortunately, elm-css does not have - `all` as a MediaType (and no means to use a custom one). - -} notMobile : MediaQuery notMobile = @@ -151,12 +135,6 @@ quizEngineMobile = {-| Styles using the `quizEngineBreakpoint` value as the minWidth. - - Note: This does *not* use the min-width query, rather it uses the max-width query - with `not`. This has implications for screen readers and print–mobile is ignored by - these media types, but included for notMobile. Unfortunately, elm-css does not have - `all` as a MediaType (and no means to use a custom one). - -} notQuizEngineMobile : MediaQuery notQuizEngineMobile = @@ -178,12 +156,6 @@ narrowMobile = {-| Styles using the `quizEngineBreakpoint` value as the minWidth. - - Note: This does *not* use the min-width query, rather it uses the max-width query - with `not`. This has implications for screen readers and print–mobile is ignored by - these media types, but included for notMobile. Unfortunately, elm-css does not have - `all` as a MediaType (and no means to use a custom one). - -} notNarrowMobile : MediaQuery notNarrowMobile = @@ -195,98 +167,3 @@ notNarrowMobile = narrowMobileBreakpoint : Css.Px narrowMobileBreakpoint = px 500 - - -{-| A builder for creating efficient media queries. - - Removes the need to think about the order of media queries or the nuances of - `not` queries with respect to screen readers and print. - --} -type alias Builder = - { baseStyles : List Style - , mobileStyles : Maybe (List Style) - , quizEngineStyles : Maybe (List Style) - , narrowMobileStyles : Maybe (List Style) - , desktopStyles : Maybe (List Style) - } - - -{-| Initialize a desktop-first media query builder with the given base styles. --} -builder : List Style -> Builder -builder baseStyles = - { baseStyles = baseStyles - , mobileStyles = Nothing - , quizEngineStyles = Nothing - , narrowMobileStyles = Nothing - , desktopStyles = Nothing - } - - -{-| Add mobile styles to the builder. - - Smaller breakpoints will inherit these styles unless overridden. - --} -onMobile : List Style -> Builder -> Builder -onMobile mobileStyles b = - { b | mobileStyles = Just mobileStyles } - - -{-| Add quiz engine mobile styles to the builder. - - Smaller breakpoints will inherit these styles unless overridden. - --} -onQuizEngineMobile : List Style -> Builder -> Builder -onQuizEngineMobile quizEngineStyles b = - { b | quizEngineStyles = Just quizEngineStyles } - - -{-| Add narrow mobile styles to the builder. - - Smaller breakpoints will inherit these styles unless overridden. - --} -onNarrowMobile : List Style -> Builder -> Builder -onNarrowMobile narrowMobileStyles b = - { b | narrowMobileStyles = Just narrowMobileStyles } - - -{-| Add desktop ONLY styles to the builder. - - Smaller breakpoints will NOT inherit these styles. For styles that should apply across - all breakpoints, use the base styles. - --} -onDesktop : List Style -> Builder -> Builder -onDesktop desktopStyles b = - { b | desktopStyles = Just desktopStyles } - - -{-| Generate a list of styles from the a builder. --} -toStyles : Builder -> List Style -toStyles b = - let - maybeAddBreakpoint breakpoint maybeStyles = - -- Yes, cons adds to the front of the list. - -- Yes, these are in the wrong order for a media query when you consider that fact. - -- Yes, this is intentional. - -- - -- elm-css reverses the order of media queries when it generates the CSS. - -- - -- Very confusing, I know. - -- - -- That's why we have a builder. - Maybe.cons (Maybe.map (withMedia [ breakpoint ]) maybeStyles) - in - b.baseStyles - |> maybeAddBreakpoint mobile b.mobileStyles - |> maybeAddBreakpoint quizEngineMobile b.quizEngineStyles - |> maybeAddBreakpoint narrowMobile b.narrowMobileStyles - -- Don't use notMobile here, lest `onDesktop` will behave inconsistently with - -- the other builder functions with respect to screen readers and print. - -- ONLY things in the base styles should be visible to these other media types. - |> maybeAddBreakpoint (only screen [ minWidth (px 1001) ]) b.desktopStyles diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm new file mode 100644 index 000000000..a2df44361 --- /dev/null +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -0,0 +1,84 @@ +module Nri.Ui.MediaQuery.V2 exposing (..) + +import Css exposing (Style, px) +import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia) +import Maybe.Extra as Maybe + + +type Attribute + = Attribute (MediaQuery -> MediaQuery) + + +type alias MediaQuery = + { mobile : ( Maybe (List Style), Maybe (List Style) ) + , quizEngineMobile : ( Maybe (List Style), Maybe (List Style) ) + , narrowMobile : ( Maybe (List Style), Maybe (List Style) ) + } + + +mobile : List Style -> Attribute +mobile s = + Attribute (\q -> { q | mobile = ( Just s, Tuple.second q.mobile ) }) + + +notMobile : List Style -> Attribute +notMobile s = + Attribute (\q -> { q | mobile = ( Tuple.first q.mobile, Just s ) }) + + +quizEngineMobile : List Style -> Attribute +quizEngineMobile s = + Attribute (\q -> { q | quizEngineMobile = ( Just s, Tuple.second q.quizEngineMobile ) }) + + +notQuizEngineMobile : List Style -> Attribute +notQuizEngineMobile s = + Attribute (\b -> { b | quizEngineMobile = ( Tuple.first b.quizEngineMobile, Just s ) }) + + +narrowMobile : List Style -> Attribute +narrowMobile s = + Attribute (\b -> { b | narrowMobile = ( Just s, Tuple.second b.narrowMobile ) }) + + +notNarrowMobile : List Style -> Attribute +notNarrowMobile s = + Attribute (\b -> { b | narrowMobile = ( Tuple.first b.narrowMobile, Just s ) }) + + +styles : List Attribute -> List Style +styles attributes = + let + config = + List.foldl (\(Attribute f) -> f) + { mobile = ( Nothing, Nothing ) + , quizEngineMobile = ( Nothing, Nothing ) + , narrowMobile = ( Nothing, Nothing ) + } + attributes + + mkStyle rule size = + Maybe.map (withMedia [ only screen [ rule (px size) ] ]) + + prepend = + Maybe.cons + + append maybeItem list = + case maybeItem of + Just item -> + list ++ [ item ] + + Nothing -> + list + + addBreakpointStyles ( size, getStyles ) = + let + ( lteStyles, gtStyles ) = + getStyles config + in + prepend (mkStyle maxWidth size lteStyles) >> append (mkStyle minWidth (size + 1) gtStyles) + in + [] + |> addBreakpointStyles ( 1000, .mobile ) + |> addBreakpointStyles ( 750, .quizEngineMobile ) + |> addBreakpointStyles ( 500, .narrowMobile ) From 2020ab420eac3f45792a579a4fefb2a98dc481a0 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Tue, 19 Mar 2024 20:07:19 -0500 Subject: [PATCH 07/58] docs --- src/Nri/Ui/MediaQuery/V2.elm | 88 +++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index a2df44361..f3787722e 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -1,52 +1,110 @@ -module Nri.Ui.MediaQuery.V2 exposing (..) +module Nri.Ui.MediaQuery.V2 exposing + ( Attribute, styles + , mobile, quizEngineMobile, narrowMobile + , notMobile, notQuizEngineMobile, notNarrowMobile + ) -import Css exposing (Style, px) +{-| Patch changes: + + + +Changes from V1: + + - Use Attribute-style API instead of direct integration with `Css.Media` module. This + abstracts away the ordering of media queries and resolves some footguns related to + the previous way "not" media queries were handled (e.g. `notMobile` and `notNarrowMobile`, + they were using the `not` keyword which caused a discrepancy between the device types + affected. tl;dr: sometimes `content` and `display` rules affected screenreaders and printers, + sometimes they didn't. In this version, media query rules are _strictly_ screen only). + +Build media queries for responsive design. + + import Nri.Ui.MediaQuery.V2 as MediaQuery exposing (Attribute, mobile, narrowMobile, styles) + + MediaQuery.styles + [ MediaQuery.mobile [ Css.paddingTop (Css.px 10) ] + , MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ] + ] + +@docs Attribute, styles + +@docs mobile, quizEngineMobile, narrowMobile +@docs notMobile, notQuizEngineMobile, notNarrowMobile + +-} + +import Css exposing (Style) import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia) import Maybe.Extra as Maybe +{-| Media query attribute, contains CSS styles for a given media query. +-} type Attribute = Attribute (MediaQuery -> MediaQuery) +{-| Represents a pair of styles for a given breakpoint, where the first +element is the styles to apply when the viewport is less than or equal to +the breakpoint, and the second element is the styles to apply when the +viewport is greater than the breakpoint. +-} +type alias BreakpointStyles = + ( Maybe (List Style), Maybe (List Style) ) + + type alias MediaQuery = - { mobile : ( Maybe (List Style), Maybe (List Style) ) - , quizEngineMobile : ( Maybe (List Style), Maybe (List Style) ) - , narrowMobile : ( Maybe (List Style), Maybe (List Style) ) + { mobile : BreakpointStyles + , quizEngineMobile : BreakpointStyles + , narrowMobile : BreakpointStyles } +{-| Set styles for mobile and smaller devices (<= 1000px) +-} mobile : List Style -> Attribute mobile s = Attribute (\q -> { q | mobile = ( Just s, Tuple.second q.mobile ) }) +{-| Set styles for larger-than-mobile devices (> 1000px) +-} notMobile : List Style -> Attribute notMobile s = Attribute (\q -> { q | mobile = ( Tuple.first q.mobile, Just s ) }) +{-| Set styles for quiz engine mobile and smaller devices (<= 750px) +-} quizEngineMobile : List Style -> Attribute quizEngineMobile s = Attribute (\q -> { q | quizEngineMobile = ( Just s, Tuple.second q.quizEngineMobile ) }) +{-| Set styles for larger-than-quiz-engine-mobile devices (> 750px) +-} notQuizEngineMobile : List Style -> Attribute notQuizEngineMobile s = Attribute (\b -> { b | quizEngineMobile = ( Tuple.first b.quizEngineMobile, Just s ) }) +{-| Set styles for narrow mobile and smaller devices (<= 500px) +-} narrowMobile : List Style -> Attribute narrowMobile s = Attribute (\b -> { b | narrowMobile = ( Just s, Tuple.second b.narrowMobile ) }) +{-| Set styles for larger-than-narrow-mobile devices (> 500px) +-} notNarrowMobile : List Style -> Attribute notNarrowMobile s = Attribute (\b -> { b | narrowMobile = ( Tuple.first b.narrowMobile, Just s ) }) -styles : List Attribute -> List Style +{-| Build a `Css.Style` from a list of breakpoints +-} +styles : List Attribute -> Style styles attributes = let config = @@ -57,28 +115,24 @@ styles attributes = } attributes - mkStyle rule size = - Maybe.map (withMedia [ only screen [ rule (px size) ] ]) + mkStyle rule px = + Maybe.map <| withMedia [ only screen [ rule <| Css.px px ] ] prepend = Maybe.cons append maybeItem list = - case maybeItem of - Just item -> - list ++ [ item ] - - Nothing -> - list + Maybe.map (List.singleton >> List.append list) maybeItem |> Maybe.withDefault list - addBreakpointStyles ( size, getStyles ) = + addBreakpointStyles ( px, getStyles ) = let - ( lteStyles, gtStyles ) = + ( desktopFirst, mobileFirst ) = getStyles config in - prepend (mkStyle maxWidth size lteStyles) >> append (mkStyle minWidth (size + 1) gtStyles) + prepend (mkStyle maxWidth px desktopFirst) >> append (mkStyle minWidth (px + 1) mobileFirst) in [] |> addBreakpointStyles ( 1000, .mobile ) |> addBreakpointStyles ( 750, .quizEngineMobile ) |> addBreakpointStyles ( 500, .narrowMobile ) + |> Css.batch From ccd6048654f87f9b99a5f1fcb1c9bb1a9c2a776f Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Tue, 19 Mar 2024 21:41:59 -0500 Subject: [PATCH 08/58] Just version it, redo the API --- component-catalog/src/UsageExamples.elm | 4 +- .../src/UsageExamples/MediaQuery.elm | 88 ++++++++++++++ .../src/UsageExamples/MediaQueryBuilder.elm | 60 ---------- .../tests/MediaQueryBuilderSpec.elm | 51 +++----- src/Nri/Ui/MediaQuery/V2.elm | 113 +++++++++--------- 5 files changed, 165 insertions(+), 151 deletions(-) create mode 100644 component-catalog/src/UsageExamples/MediaQuery.elm delete mode 100644 component-catalog/src/UsageExamples/MediaQueryBuilder.elm diff --git a/component-catalog/src/UsageExamples.elm b/component-catalog/src/UsageExamples.elm index bef1d758d..1aa8ecd2a 100644 --- a/component-catalog/src/UsageExamples.elm +++ b/component-catalog/src/UsageExamples.elm @@ -4,7 +4,7 @@ import UsageExample exposing (UsageExample) import UsageExamples.ClickableCardWithTooltip as ClickableCardWithTooltip import UsageExamples.FocusLoop as FocusLoop import UsageExamples.Form as Form -import UsageExamples.MediaQueryBuilder as MediaQueryBuilder +import UsageExamples.MediaQuery as MediaQuery all : List (UsageExample State Msg) @@ -66,7 +66,7 @@ all = _ -> Nothing ) - , MediaQueryBuilder.example + , MediaQuery.example |> UsageExample.noop NoOp |> UsageExample.stateless Stateless ] diff --git a/component-catalog/src/UsageExamples/MediaQuery.elm b/component-catalog/src/UsageExamples/MediaQuery.elm new file mode 100644 index 000000000..a2f4be74b --- /dev/null +++ b/component-catalog/src/UsageExamples/MediaQuery.elm @@ -0,0 +1,88 @@ +module UsageExamples.MediaQuery exposing (example) + +{-| + + @docs example + +-} + +import Category +import Css +import Html.Styled exposing (..) +import Nri.Ui.Container.V2 as Container +import Nri.Ui.MediaQuery.V2 as MediaQuery +import UsageExample exposing (UsageExample) + + +example : UsageExample () () +example = + { name = "MediaQueryBuilder" + , categories = [ Category.Layout ] + , init = () + , update = \_ _ -> ( (), Cmd.none ) + , view = always view + , about = [] + , subscriptions = \_ -> Sub.none + } + + +view : List (Html msg) +view = + viewCascade :: viewIndividually + + +viewCascade : Html msg +viewCascade = + let + content str = + Css.property "content" ("'" ++ str ++ "'") + + before str = + Css.before [ content str ] + + after str = + Css.after [ content str ] + in + Container.view + [ Container.buttony + , Container.css + (Css.displayFlex + :: Css.property "justify-content" "space-evenly" + :: before "Default" + :: after "Default" + :: MediaQuery.toStyles + [ MediaQuery.narrowMobile [ before "Narrow Mobile" ] + , MediaQuery.quizEngineMobile [ before "Quiz Engine Mobile" ] + , MediaQuery.mobile [ before "Mobile" ] + , MediaQuery.not MediaQuery.narrowMobile [ after "Not Narrow Mobile" ] + , MediaQuery.not MediaQuery.quizEngineMobile [ after "Not Quiz Engine Mobile" ] + , MediaQuery.not MediaQuery.mobile [ after "Not Mobile" ] + ] + ) + , Container.plaintext " | " + ] + + +viewIndividually : List (Html msg) +viewIndividually = + let + hidden = + Css.display Css.none + + visible = + Css.display Css.block + + viewBreakpoint name breakpoint = + Container.view + [ Container.buttony + , Container.css <| hidden :: MediaQuery.toStyles [ breakpoint [ visible ] ] + , Container.plaintext name + ] + in + [ viewBreakpoint "Narrow Mobile" MediaQuery.narrowMobile + , viewBreakpoint "Quiz Engine Mobile" MediaQuery.quizEngineMobile + , viewBreakpoint "Mobile" MediaQuery.mobile + , viewBreakpoint "Not Narrow Mobile" (MediaQuery.not MediaQuery.narrowMobile) + , viewBreakpoint "Not Quiz Engine Mobile" (MediaQuery.not MediaQuery.quizEngineMobile) + , viewBreakpoint "Not Mobile" (MediaQuery.not MediaQuery.mobile) + ] diff --git a/component-catalog/src/UsageExamples/MediaQueryBuilder.elm b/component-catalog/src/UsageExamples/MediaQueryBuilder.elm deleted file mode 100644 index 2065fa6e5..000000000 --- a/component-catalog/src/UsageExamples/MediaQueryBuilder.elm +++ /dev/null @@ -1,60 +0,0 @@ -module UsageExamples.MediaQueryBuilder exposing (example) - -{-| - - @docs example - --} - -import Category -import Css -import Html.Styled exposing (..) -import Nri.Ui.Container.V2 as Container -import Nri.Ui.MediaQuery.V2 as MediaQuery -import UsageExample exposing (UsageExample) - - -example : UsageExample () () -example = - { name = "MediaQueryBuilder" - , categories = [ Category.Layout ] - , init = () - , update = \_ _ -> ( (), Cmd.none ) - , view = always view - , about = [] - , subscriptions = \_ -> Sub.none - } - - -view : List (Html msg) -view = - let - content str = - Css.property "content" ("'" ++ str ++ "'") - - before str = - Css.before [ content str ] - - after str = - Css.after [ content str ] - in - [ h2 [] [ text "MediaQueryBuilder" ] - , Container.view - [ Container.buttony - , Container.css - (Css.displayFlex - :: Css.property "justify-content" "space-evenly" - :: before "Default" - :: after "Default" - :: MediaQuery.styles - [ MediaQuery.narrowMobile [ before "Narrow Mobile" ] - , MediaQuery.quizEngineMobile [ before "Quiz Engine Mobile" ] - , MediaQuery.mobile [ before "Mobile" ] - , MediaQuery.notNarrowMobile [ after "Not Narrow Mobile" ] - , MediaQuery.notQuizEngineMobile [ after "Not Quiz Engine Mobile" ] - , MediaQuery.notMobile [ after "Not Mobile" ] - ] - ) - , Container.plaintext " | " - ] - ] diff --git a/component-catalog/tests/MediaQueryBuilderSpec.elm b/component-catalog/tests/MediaQueryBuilderSpec.elm index 5da399766..1a158d394 100644 --- a/component-catalog/tests/MediaQueryBuilderSpec.elm +++ b/component-catalog/tests/MediaQueryBuilderSpec.elm @@ -1,9 +1,9 @@ module MediaQueryBuilderSpec exposing (suite) -import Css exposing (fontSize, px) +import Css exposing (int, order) import Html.Styled exposing (div, toUnstyled) import Html.Styled.Attributes exposing (css) -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery exposing (mobile, narrowMobile, quizEngineMobile) import Test exposing (..) import Test.Html.Query as Query import Test.Html.Selector as Selector @@ -15,14 +15,15 @@ suite = [ test "it puts queries in the correct order" <| \() -> div - [ css - (MediaQuery.builder [ fontSize (px 99) ] - |> MediaQuery.onNarrowMobile [ fontSize (px 1) ] - |> MediaQuery.onQuizEngineMobile [ fontSize (px 2) ] - |> MediaQuery.onMobile [ fontSize (px 3) ] - |> MediaQuery.onDesktop [ fontSize (px 4) ] - |> MediaQuery.toStyles - ) + [ css <| + MediaQuery.toStyles + [ narrowMobile [ order (int 6) ] + , quizEngineMobile [ order (int 5) ] + , mobile [ order (int 4) ] + , MediaQuery.not narrowMobile [ order (int 1) ] + , MediaQuery.not quizEngineMobile [ order (int 2) ] + , MediaQuery.not mobile [ order (int 3) ] + ] ] [] |> toUnstyled @@ -30,30 +31,12 @@ suite = |> Query.find [ Selector.tag "style" ] |> Query.has [ Selector.text (String.trim """ -._ebc36d6{font-size:99px;} -@media only screen and (max-width: 1000px){._ebc36d6{font-size:3px;}} -@media only screen and (max-width: 750px){._ebc36d6{font-size:2px;}} -@media only screen and (max-width: 500px){._ebc36d6{font-size:1px;}} -@media only screen and (min-width: 1001px){._ebc36d6{font-size:4px;}} - """) - ] - , test "it works with only a single breakpoint" <| - \() -> - div - [ css - (MediaQuery.builder [ fontSize (px 99) ] - |> MediaQuery.onMobile [ fontSize (px 3) ] - |> MediaQuery.toStyles - ) - ] - [] - |> toUnstyled - |> Query.fromHtml - |> Query.find [ Selector.tag "style" ] - |> Query.has - [ Selector.text (String.trim """ -._d0428ccb{font-size:99px;} -@media only screen and (max-width: 1000px){._d0428ccb{font-size:3px;}} +@media only screen and (min-width: 501px){._543c77e6{order:1;}} +@media only screen and (min-width: 751px){._543c77e6{order:2;}} +@media only screen and (min-width: 1001px){._543c77e6{order:3;}} +@media only screen and (max-width: 1000px){._543c77e6{order:4;}} +@media only screen and (max-width: 750px){._543c77e6{order:5;}} +@media only screen and (max-width: 500px){._543c77e6{order:6;}} """) ] ] diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index f3787722e..b9c55400d 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -1,7 +1,10 @@ module Nri.Ui.MediaQuery.V2 exposing - ( Attribute, styles - , mobile, quizEngineMobile, narrowMobile - , notMobile, notQuizEngineMobile, notNarrowMobile + ( MediaQuery + , mobile + , narrowMobile + , not + , quizEngineMobile + , toStyles ) {-| Patch changes: @@ -24,96 +27,97 @@ Build media queries for responsive design. MediaQuery.styles [ MediaQuery.mobile [ Css.paddingTop (Css.px 10) ] , MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ] + , MediaQuery.not MediaQuery.mobile [ Css.fontSize (Css.px 30) ] ] -@docs Attribute, styles - -@docs mobile, quizEngineMobile, narrowMobile -@docs notMobile, notQuizEngineMobile, notNarrowMobile - -} -import Css exposing (Style) +import Css exposing (Style, target) import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia) import Maybe.Extra as Maybe -{-| Media query attribute, contains CSS styles for a given media query. +{-| Type representing a pair of styles for a given target. -} -type Attribute - = Attribute (MediaQuery -> MediaQuery) +type MediaQuery + = MediaQuery Target (Maybe (List Style)) (Maybe (List Style)) -{-| Represents a pair of styles for a given breakpoint, where the first -element is the styles to apply when the viewport is less than or equal to -the breakpoint, and the second element is the styles to apply when the -viewport is greater than the breakpoint. --} -type alias BreakpointStyles = - ( Maybe (List Style), Maybe (List Style) ) +type Target + = Mobile + | QuizEngineMobile + | NarrowMobile -type alias MediaQuery = - { mobile : BreakpointStyles - , quizEngineMobile : BreakpointStyles - , narrowMobile : BreakpointStyles - } +{-| Negate a MediaQuery + Note: This is not a true "not" media query. Rather, this module will use whatever + the logical inverse of the media query target is. For example, `not mobile` will + not do `not screen and (max-width: 1000px)`, but rather `screen and (min-width: 1001px)`. + This is because the `not` keyword in media queries causes some hard-to-track-down + unexpected behavior with screen readers and printers. -{-| Set styles for mobile and smaller devices (<= 1000px) -} -mobile : List Style -> Attribute -mobile s = - Attribute (\q -> { q | mobile = ( Just s, Tuple.second q.mobile ) }) +not : (List Style -> MediaQuery) -> List Style -> MediaQuery +not query s = + let + (MediaQuery target _ _) = + query s + in + MediaQuery target Nothing (Just s) -{-| Set styles for larger-than-mobile devices (> 1000px) +{-| Set styles for mobile and smaller devices (<= 1000px) -} -notMobile : List Style -> Attribute -notMobile s = - Attribute (\q -> { q | mobile = ( Tuple.first q.mobile, Just s ) }) +mobile : List Style -> MediaQuery +mobile s = + MediaQuery Mobile (Just s) Nothing {-| Set styles for quiz engine mobile and smaller devices (<= 750px) -} -quizEngineMobile : List Style -> Attribute +quizEngineMobile : List Style -> MediaQuery quizEngineMobile s = - Attribute (\q -> { q | quizEngineMobile = ( Just s, Tuple.second q.quizEngineMobile ) }) - - -{-| Set styles for larger-than-quiz-engine-mobile devices (> 750px) --} -notQuizEngineMobile : List Style -> Attribute -notQuizEngineMobile s = - Attribute (\b -> { b | quizEngineMobile = ( Tuple.first b.quizEngineMobile, Just s ) }) + MediaQuery QuizEngineMobile (Just s) Nothing {-| Set styles for narrow mobile and smaller devices (<= 500px) -} -narrowMobile : List Style -> Attribute +narrowMobile : List Style -> MediaQuery narrowMobile s = - Attribute (\b -> { b | narrowMobile = ( Just s, Tuple.second b.narrowMobile ) }) - - -{-| Set styles for larger-than-narrow-mobile devices (> 500px) --} -notNarrowMobile : List Style -> Attribute -notNarrowMobile s = - Attribute (\b -> { b | narrowMobile = ( Tuple.first b.narrowMobile, Just s ) }) + MediaQuery NarrowMobile (Just s) Nothing {-| Build a `Css.Style` from a list of breakpoints -} -styles : List Attribute -> Style -styles attributes = +toStyles : List MediaQuery -> List Style +toStyles queries = let config = - List.foldl (\(Attribute f) -> f) + List.foldl + (\(MediaQuery target satisfiesTargetStyles doesNotSatisfyTargetStyles) acc -> + let + ( get, set ) = + case target of + Mobile -> + ( .mobile, \r v -> { r | mobile = v } ) + + QuizEngineMobile -> + ( .quizEngineMobile, \r v -> { r | quizEngineMobile = v } ) + + NarrowMobile -> + ( .narrowMobile, \r v -> { r | narrowMobile = v } ) + in + set acc + ( Maybe.or (Tuple.first (get acc)) satisfiesTargetStyles + , Maybe.or (Tuple.second (get acc)) doesNotSatisfyTargetStyles + ) + ) { mobile = ( Nothing, Nothing ) , quizEngineMobile = ( Nothing, Nothing ) , narrowMobile = ( Nothing, Nothing ) } - attributes + queries mkStyle rule px = Maybe.map <| withMedia [ only screen [ rule <| Css.px px ] ] @@ -135,4 +139,3 @@ styles attributes = |> addBreakpointStyles ( 1000, .mobile ) |> addBreakpointStyles ( 750, .quizEngineMobile ) |> addBreakpointStyles ( 500, .narrowMobile ) - |> Css.batch From 457678dfc7abb5ebdc6cfca364bc16a98fed8e9c Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Tue, 19 Mar 2024 22:19:49 -0500 Subject: [PATCH 09/58] prefers-reduced-motion and high contrast mode --- ...ueryBuilderSpec.elm => MediaQuerySpec.elm} | 31 +++++-- src/Nri/Ui/MediaQuery/V2.elm | 83 +++++++++++++++---- 2 files changed, 90 insertions(+), 24 deletions(-) rename component-catalog/tests/{MediaQueryBuilderSpec.elm => MediaQuerySpec.elm} (50%) diff --git a/component-catalog/tests/MediaQueryBuilderSpec.elm b/component-catalog/tests/MediaQuerySpec.elm similarity index 50% rename from component-catalog/tests/MediaQueryBuilderSpec.elm rename to component-catalog/tests/MediaQuerySpec.elm index 1a158d394..c589fb241 100644 --- a/component-catalog/tests/MediaQueryBuilderSpec.elm +++ b/component-catalog/tests/MediaQuerySpec.elm @@ -1,9 +1,16 @@ -module MediaQueryBuilderSpec exposing (suite) +module MediaQuerySpec exposing (suite) import Css exposing (int, order) import Html.Styled exposing (div, toUnstyled) import Html.Styled.Attributes exposing (css) -import Nri.Ui.MediaQuery.V2 as MediaQuery exposing (mobile, narrowMobile, quizEngineMobile) +import Nri.Ui.MediaQuery.V2 as MediaQuery + exposing + ( highContrastMode + , mobile + , narrowMobile + , prefersReducedMotion + , quizEngineMobile + ) import Test exposing (..) import Test.Html.Query as Query import Test.Html.Selector as Selector @@ -23,6 +30,10 @@ suite = , MediaQuery.not narrowMobile [ order (int 1) ] , MediaQuery.not quizEngineMobile [ order (int 2) ] , MediaQuery.not mobile [ order (int 3) ] + , MediaQuery.highContrastMode [ order (int -1) ] + , MediaQuery.prefersReducedMotion [ order (int -2) ] + , MediaQuery.not MediaQuery.highContrastMode [ order (int -11) ] + , MediaQuery.not MediaQuery.prefersReducedMotion [ order (int -22) ] ] ] [] @@ -31,12 +42,16 @@ suite = |> Query.find [ Selector.tag "style" ] |> Query.has [ Selector.text (String.trim """ -@media only screen and (min-width: 501px){._543c77e6{order:1;}} -@media only screen and (min-width: 751px){._543c77e6{order:2;}} -@media only screen and (min-width: 1001px){._543c77e6{order:3;}} -@media only screen and (max-width: 1000px){._543c77e6{order:4;}} -@media only screen and (max-width: 750px){._543c77e6{order:5;}} -@media only screen and (max-width: 500px){._543c77e6{order:6;}} +@media only screen and (min-width: 501px){._28601c77{order:1;}} +@media only screen and (min-width: 751px){._28601c77{order:2;}} +@media only screen and (min-width: 1001px){._28601c77{order:3;}} +@media (forced-colors: none){._28601c77{order:-11;}} +@media (forced-colors: active){._28601c77{order:-1;}} +@media (prefers-reduced-motion: no-preference){._28601c77{order:-22;}} +@media (prefers-reduced-motion){._28601c77{order:-2;}} +@media only screen and (max-width: 1000px){._28601c77{order:4;}} +@media only screen and (max-width: 750px){._28601c77{order:5;}} +@media only screen and (max-width: 500px){._28601c77{order:6;}} """) ] ] diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index b9c55400d..b05a16a68 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -1,10 +1,8 @@ module Nri.Ui.MediaQuery.V2 exposing - ( MediaQuery - , mobile - , narrowMobile + ( MediaQuery, toStyles , not - , quizEngineMobile - , toStyles + , mobile, narrowMobile, quizEngineMobile + , prefersReducedMotion, highContrastMode ) {-| Patch changes: @@ -30,10 +28,30 @@ Build media queries for responsive design. , MediaQuery.not MediaQuery.mobile [ Css.fontSize (Css.px 30) ] ] + +### Basics + +@docs MediaQuery, toStyles + + +### Utilities + +@docs not + + +### Viewport Media Queries + +@docs mobile, narrowMobile, quizEngineMobile + + +### Accessibility Media Queries + +@docs prefersReducedMotion, highContrastMode + -} import Css exposing (Style, target) -import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia) +import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia, withMediaQuery) import Maybe.Extra as Maybe @@ -47,6 +65,8 @@ type Target = Mobile | QuizEngineMobile | NarrowMobile + | ReducedMotion + | HighContrast {-| Negate a MediaQuery @@ -88,7 +108,21 @@ narrowMobile s = MediaQuery NarrowMobile (Just s) Nothing -{-| Build a `Css.Style` from a list of breakpoints +{-| Set styles for reduced motion +-} +prefersReducedMotion : List Style -> MediaQuery +prefersReducedMotion s = + MediaQuery ReducedMotion (Just s) Nothing + + +{-| Set styles for high contrast mode +-} +highContrastMode : List Style -> MediaQuery +highContrastMode s = + MediaQuery HighContrast (Just s) Nothing + + +{-| Build a `Css.Style` from a list of media queries. -} toStyles : List MediaQuery -> List Style toStyles queries = @@ -107,6 +141,12 @@ toStyles queries = NarrowMobile -> ( .narrowMobile, \r v -> { r | narrowMobile = v } ) + + ReducedMotion -> + ( .reducedMotion, \r v -> { r | reducedMotion = v } ) + + HighContrast -> + ( .highContrast, \r v -> { r | highContrast = v } ) in set acc ( Maybe.or (Tuple.first (get acc)) satisfiesTargetStyles @@ -116,26 +156,37 @@ toStyles queries = { mobile = ( Nothing, Nothing ) , quizEngineMobile = ( Nothing, Nothing ) , narrowMobile = ( Nothing, Nothing ) + , reducedMotion = ( Nothing, Nothing ) + , highContrast = ( Nothing, Nothing ) } queries - mkStyle rule px = - Maybe.map <| withMedia [ only screen [ rule <| Css.px px ] ] - prepend = Maybe.cons append maybeItem list = Maybe.map (List.singleton >> List.append list) maybeItem |> Maybe.withDefault list - addBreakpointStyles ( px, getStyles ) = + mkViewportQuery rule px = + Maybe.map <| withMedia [ only screen [ rule <| Css.px px ] ] + + mkBooleanQuery onQuery offQuery ( onStyles, offStyles ) = + Maybe.values + [ Maybe.map (withMediaQuery [ onQuery ]) onStyles + , Maybe.map (withMediaQuery [ offQuery ]) offStyles + ] + + addViewportQuery ( px, getStyles ) = let ( desktopFirst, mobileFirst ) = getStyles config in - prepend (mkStyle maxWidth px desktopFirst) >> append (mkStyle minWidth (px + 1) mobileFirst) + prepend (mkViewportQuery maxWidth px desktopFirst) >> append (mkViewportQuery minWidth (px + 1) mobileFirst) in - [] - |> addBreakpointStyles ( 1000, .mobile ) - |> addBreakpointStyles ( 750, .quizEngineMobile ) - |> addBreakpointStyles ( 500, .narrowMobile ) + List.concat + [ mkBooleanQuery "(prefers-reduced-motion)" "(prefers-reduced-motion: no-preference)" config.reducedMotion + , mkBooleanQuery "(forced-colors: active)" "(forced-colors: none)" config.highContrast + ] + |> addViewportQuery ( 1000, .mobile ) + |> addViewportQuery ( 750, .quizEngineMobile ) + |> addViewportQuery ( 500, .narrowMobile ) From 6869ab5f5cac5c8a011eb55f17e5d4dde5fd05a3 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 10:30:55 -0500 Subject: [PATCH 10/58] nits --- component-catalog/tests/MediaQuerySpec.elm | 8 +++--- src/Nri/Ui/MediaQuery/V2.elm | 31 +++++++++++++++++++--- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/component-catalog/tests/MediaQuerySpec.elm b/component-catalog/tests/MediaQuerySpec.elm index c589fb241..dfcb50b61 100644 --- a/component-catalog/tests/MediaQuerySpec.elm +++ b/component-catalog/tests/MediaQuerySpec.elm @@ -30,10 +30,10 @@ suite = , MediaQuery.not narrowMobile [ order (int 1) ] , MediaQuery.not quizEngineMobile [ order (int 2) ] , MediaQuery.not mobile [ order (int 3) ] - , MediaQuery.highContrastMode [ order (int -1) ] - , MediaQuery.prefersReducedMotion [ order (int -2) ] - , MediaQuery.not MediaQuery.highContrastMode [ order (int -11) ] - , MediaQuery.not MediaQuery.prefersReducedMotion [ order (int -22) ] + , highContrastMode [ order (int -1) ] + , prefersReducedMotion [ order (int -2) ] + , MediaQuery.not highContrastMode [ order (int -11) ] + , MediaQuery.not prefersReducedMotion [ order (int -22) ] ] ] [] diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index b05a16a68..f751e5238 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -1,5 +1,5 @@ module Nri.Ui.MediaQuery.V2 exposing - ( MediaQuery, toStyles + ( MediaQuery, toStyles, toStyle , not , mobile, narrowMobile, quizEngineMobile , prefersReducedMotion, highContrastMode @@ -25,13 +25,15 @@ Build media queries for responsive design. MediaQuery.styles [ MediaQuery.mobile [ Css.paddingTop (Css.px 10) ] , MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ] + + -- negate a media query with `not` , MediaQuery.not MediaQuery.mobile [ Css.fontSize (Css.px 30) ] ] ### Basics -@docs MediaQuery, toStyles +@docs MediaQuery, toStyles, toStyle ### Utilities @@ -55,7 +57,18 @@ import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMed import Maybe.Extra as Maybe -{-| Type representing a pair of styles for a given target. +{-| Type representing a Media Query in the format + + `(Target, SatisfiesTargetStyles, DoesNotSatisfyTargetStyles)`, where: + + - `Target` is the media this query is targeting (e.g. Mobile, NarrowMobile, etc.) + - `SatisfiesTargetStyles` is the style to apply when the media query is satisfied + - `DoesNotSatisfyTargetStyles` is the style to apply when the media query is NOT satisfied + + Example: + + MediaQuery Mobile (Just [ Css.paddingTop (Css.px 10) ]) (Just [ Css.paddingTop (Css.px 20) ] + -} type MediaQuery = MediaQuery Target (Maybe (List Style)) (Maybe (List Style)) @@ -122,7 +135,7 @@ highContrastMode s = MediaQuery HighContrast (Just s) Nothing -{-| Build a `Css.Style` from a list of media queries. +{-| Build a list of `Css.Style` from a list of media queries. -} toStyles : List MediaQuery -> List Style toStyles queries = @@ -190,3 +203,13 @@ toStyles queries = |> addViewportQuery ( 1000, .mobile ) |> addViewportQuery ( 750, .quizEngineMobile ) |> addViewportQuery ( 500, .narrowMobile ) + + +{-| Build a single `Css.Style` from a list of media queries. + + This is a convenience function for `Css.batch (toStyles queries)`. + +-} +toStyle : List MediaQuery -> Style +toStyle = + toStyles >> Css.batch From 6d03b225bcd3c130bfdcbf31a75aa9d71559538b Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 14:08:52 -0500 Subject: [PATCH 11/58] Refactor Tooltip to use MediaQuery.V2 (and efficient styles) --- src/Nri/Ui/Tooltip/V3.elm | 335 ++++++++++++++++++-------------------- 1 file changed, 161 insertions(+), 174 deletions(-) diff --git a/src/Nri/Ui/Tooltip/V3.elm b/src/Nri/Ui/Tooltip/V3.elm index 8aa86204f..d36ced0c1 100644 --- a/src/Nri/Ui/Tooltip/V3.elm +++ b/src/Nri/Ui/Tooltip/V3.elm @@ -90,18 +90,19 @@ import Accessibility.Styled.Role as Role import Content import Css exposing (Color, Px, Style) import Css.Global as Global -import Css.Media import EventExtras as Events import Html.Styled as Root import Html.Styled.Attributes as Attributes import Html.Styled.Events as Events import Json.Decode +import List.Extra as List +import Maybe.Extra as Maybe import Nri.Ui import Nri.Ui.ClickableSvg.V2 as ClickableSvg 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.MediaQuery.V1 as MediaQuery exposing (mobileBreakpoint, narrowMobileBreakpoint, quizEngineBreakpoint) +import Nri.Ui.MediaQuery.V2 as MediaQuery exposing (MediaQuery) import Nri.Ui.Shadows.V1 as Shadows import Nri.Ui.UiIcon.V1 as UiIcon import Nri.Ui.WhenFocusLeaves.V2 as WhenFocusLeaves @@ -126,6 +127,7 @@ type alias Tooltip msg = , attributes : List (Html.Attribute Never) , containerStyles : List Style , tooltipStyleOverrides : List Style + , responsiveTooltipStyleOverrides : List MediaQuery , width : Width , padding : Padding , trigger : Maybe (Trigger msg) @@ -160,6 +162,7 @@ buildAttributes = , Css.position Css.relative ] , tooltipStyleOverrides = [] + , responsiveTooltipStyleOverrides = [] , width = Exactly 320 , padding = NormalPadding , trigger = Nothing @@ -641,56 +644,61 @@ css tooltipStyleOverrides = Attribute (\config -> { config | tooltipStyleOverrides = config.tooltipStyleOverrides ++ tooltipStyleOverrides }) +{-| Set some conditional custom styles on the tooltip according to a media query. +-} +responsiveCss : List MediaQuery -> Attribute msg +responsiveCss styles = + -- Don't do the `MediaQuery.toStyles` here, because we want calls to multiple + -- `responsiveCss` to be additive and processed by MediaQuery together. + Attribute (\config -> { config | responsiveTooltipStyleOverrides = config.responsiveTooltipStyleOverrides ++ styles }) + + {-| Set styles that will only apply if the viewport is wider than NRI's mobile breakpoint. Equivalent to: - Tooltip.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.notMobile ] styles ] + Tooltip.responsiveCss [ Nri.Ui.MediaQuery.V2.not Nri.Ui.MediaQuery.V2.mobile styles ] -} notMobileCss : List Style -> Attribute msg notMobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.notMobile ] styles ] + responsiveCss [ MediaQuery.not MediaQuery.mobile styles ] {-| Set styles that will only apply if the viewport is narrower than NRI's mobile breakpoint. Equivalent to: - Tooltip.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.mobile ] styles ] + Tooltip.responsiveCss [ Nri.Ui.MediaQuery.V2.mobile styles ] -} mobileCss : List Style -> Attribute msg mobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.mobile ] styles ] + responsiveCss [ MediaQuery.mobile styles ] {-| Set styles that will only apply if the viewport is narrower than NRI's quiz-engine-specific mobile breakpoint. Equivalent to: - Tooltip.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.quizEngineMobile ] styles ] + Tooltip.responsiveCss [ Nri.Ui.MediaQuery.V2.quizEngineMobile styles ] -} quizEngineMobileCss : List Style -> Attribute msg quizEngineMobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.quizEngineMobile ] styles ] + responsiveCss [ MediaQuery.quizEngineMobile styles ] {-| Set styles that will only apply if the viewport is narrower than NRI's narrow mobile breakpoint. Equivalent to: - Tooltip.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.narrowMobile ] styles ] + Tooltip.responsiveCss [ Nri.Ui.MediaQuery.V2.narrowMobile styles ] -} narrowMobileCss : List Style -> Attribute msg narrowMobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.narrowMobile ] styles ] + responsiveCss [ MediaQuery.narrowMobile styles ] {-| Use this helper to add custom attributes. @@ -1043,23 +1051,39 @@ viewTooltip_ { trigger, id } tooltip = viewTooltip : String -> Tooltip msg -> Html msg viewTooltip tooltipId config = let - mobileDirection = - Maybe.withDefault config.direction config.mobileDirection - - quizEngineMobileDirection = - Maybe.withDefault mobileDirection config.quizEngineMobileDirection - - narrowMobileDirection = - Maybe.withDefault quizEngineMobileDirection config.narrowMobileDirection - - mobileAlignment = - Maybe.withDefault config.alignment config.mobileAlignment - - quizEngineMobileAlignment = - Maybe.withDefault mobileAlignment config.quizEngineMobileAlignment - - narrowMobileAlignment = - Maybe.withDefault quizEngineMobileAlignment config.narrowMobileAlignment + ( _, _, mediaQueries ) = + List.foldl + (\( mediaQuery, maybeNextDirection, maybeNextAlignment ) ( prevDirection, prevAlignment, acc ) -> + if Maybe.isJust maybeNextDirection || Maybe.isJust maybeNextAlignment then + let + ( direction, alignment ) = + ( Maybe.withDefault prevDirection maybeNextDirection, Maybe.withDefault prevAlignment maybeNextAlignment ) + in + ( direction + , alignment + , { acc + | positionTooltip = mediaQuery (positionTooltip direction alignment) :: acc.positionTooltip + , hoverAreaForDirection = mediaQuery (hoverAreaForDirection direction) :: acc.hoverAreaForDirection + , positioning = mediaQuery (positioning direction alignment) :: acc.positioning + , applyTail = mediaQuery (applyTail direction) :: acc.applyTail + } + ) + + else + ( prevDirection, prevAlignment, acc ) + ) + ( config.direction + , config.alignment + , { positionTooltip = [] + , hoverAreaForDirection = [] + , positioning = [] + , applyTail = [] + } + ) + [ ( MediaQuery.mobile, config.mobileDirection, config.mobileAlignment ) + , ( MediaQuery.quizEngineMobile, config.quizEngineMobileDirection, config.quizEngineMobileAlignment ) + , ( MediaQuery.narrowMobile, config.narrowMobileDirection, config.narrowMobileAlignment ) + ] applyTail direction = case config.tail of @@ -1067,19 +1091,11 @@ viewTooltip tooltipId config = tailForDirection direction WithoutTail -> - Css.batch [] + [] in Html.div - [ Attributes.css + [ Attributes.css <| [ Css.position Css.absolute - , MediaQuery.withViewport (Just mobileBreakpoint) Nothing <| - positionTooltip config.direction config.alignment - , MediaQuery.withViewport (Just quizEngineBreakpoint) (Just mobileBreakpoint) <| - positionTooltip mobileDirection mobileAlignment - , MediaQuery.withViewport (Just narrowMobileBreakpoint) (Just quizEngineBreakpoint) <| - positionTooltip quizEngineMobileDirection quizEngineMobileAlignment - , MediaQuery.withViewport Nothing (Just narrowMobileBreakpoint) <| - positionTooltip narrowMobileDirection narrowMobileAlignment , Css.boxSizing Css.borderBox , if config.isOpen then Css.batch [] @@ -1087,6 +1103,8 @@ viewTooltip tooltipId config = else Css.display Css.none ] + ++ positionTooltip config.direction config.alignment + ++ MediaQuery.toStyles mediaQueries.positionTooltip , -- Used for tests, since the visibility is controlled via CSS, which elm-program-test cannot account for Attributes.attribute "data-tooltip-visible" <| if config.isOpen then @@ -1114,22 +1132,6 @@ viewTooltip tooltipId config = , Css.zIndex (Css.int 100) , Css.backgroundColor Colors.navy , Css.border3 (Css.px 1) Css.solid outlineColor - , MediaQuery.withViewport (Just mobileBreakpoint) Nothing <| - [ positioning config.direction config.alignment - , applyTail config.direction - ] - , MediaQuery.withViewport (Just quizEngineBreakpoint) (Just mobileBreakpoint) <| - [ positioning mobileDirection mobileAlignment - , applyTail mobileDirection - ] - , MediaQuery.withViewport (Just narrowMobileBreakpoint) (Just quizEngineBreakpoint) <| - [ positioning quizEngineMobileDirection quizEngineMobileAlignment - , applyTail quizEngineMobileDirection - ] - , MediaQuery.withViewport Nothing (Just narrowMobileBreakpoint) <| - [ positioning narrowMobileDirection narrowMobileAlignment - , applyTail narrowMobileDirection - ] , Fonts.baseFont , Css.fontSize (Css.px 15) , Css.fontWeight (Css.int 600) @@ -1150,6 +1152,9 @@ viewTooltip tooltipId config = ] ] ] + ++ positioning config.direction config.alignment + ++ applyTail config.direction + ++ MediaQuery.toStyles (mediaQueries.positioning ++ mediaQueries.applyTail) ++ config.tooltipStyleOverrides ) @@ -1178,16 +1183,10 @@ viewTooltip tooltipId config = ++ [ Html.div [ ExtraAttributes.nriDescription "tooltip-hover-bridge" , Attributes.css - [ Css.position Css.absolute - , MediaQuery.withViewport (Just mobileBreakpoint) Nothing <| - hoverAreaForDirection config.direction - , MediaQuery.withViewport (Just quizEngineBreakpoint) (Just mobileBreakpoint) <| - hoverAreaForDirection mobileDirection - , MediaQuery.withViewport (Just narrowMobileBreakpoint) (Just quizEngineBreakpoint) <| - hoverAreaForDirection quizEngineMobileDirection - , MediaQuery.withViewport Nothing (Just narrowMobileBreakpoint) <| - hoverAreaForDirection narrowMobileDirection - ] + (Css.position Css.absolute + :: hoverAreaForDirection config.direction + ++ MediaQuery.toStyles mediaQueries.hoverAreaForDirection + ) ] [] ] @@ -1223,115 +1222,103 @@ positionTooltip direction alignment = ltrPosition = case alignment of Start customOffset -> - Css.left customOffset + [ Css.left customOffset, Css.right Css.unset ] Middle -> - Css.left (Css.pct 50) + [ Css.left (Css.pct 50), Css.right Css.unset ] End customOffset -> - Css.right customOffset + [ Css.left Css.unset, Css.right customOffset ] topToBottomPosition = case alignment of Start customOffset -> - Css.top customOffset + [ Css.top customOffset, Css.bottom Css.unset ] Middle -> - Css.top (Css.pct 50) + [ Css.top (Css.pct 50), Css.bottom Css.unset ] End customOffset -> - Css.bottom customOffset + [ Css.top Css.unset, Css.bottom customOffset ] in case direction of OnTop -> - [ ltrPosition - , Css.top (Css.calc (Css.px (negate tailSize)) Css.minus (Css.px 2)) - ] + Css.top (Css.calc (Css.px (negate tailSize)) Css.minus (Css.px 2)) :: Css.bottom Css.unset :: ltrPosition OnBottom -> - [ ltrPosition - , Css.bottom (Css.calc (Css.px (negate tailSize)) Css.minus (Css.px 2)) - ] + Css.top Css.unset :: Css.bottom (Css.calc (Css.px (negate tailSize)) Css.minus (Css.px 2)) :: ltrPosition OnLeft -> - [ topToBottomPosition - , Css.left (Css.calc (Css.px (negate tailSize)) Css.minus (Css.px 2)) - ] + Css.left (Css.calc (Css.px (negate tailSize)) Css.minus (Css.px 2)) :: Css.right Css.unset :: topToBottomPosition OnRight -> - [ topToBottomPosition - , Css.right (Css.calc (Css.px (negate tailSize)) Css.minus (Css.px 2)) - ] + Css.left Css.unset :: Css.right (Css.calc (Css.px (negate tailSize)) Css.minus (Css.px 2)) :: topToBottomPosition -- TAILS -positioning : Direction -> Alignment -> Style +positioning : Direction -> Alignment -> List Style positioning direction alignment = let topBottomAlignment = case alignment of Start _ -> - Css.left (Css.px offCenterOffset) + [ Css.left (Css.px offCenterOffset), Css.right Css.unset ] Middle -> - Css.left (Css.pct 50) + [ Css.left (Css.pct 50), Css.right Css.unset ] End _ -> - Css.right (Css.px offCenterOffset) + [ Css.left Css.unset, Css.right (Css.px offCenterOffset) ] rightLeftAlignment = case alignment of Start _ -> - Css.property "top" ("calc(-" ++ String.fromFloat tailSize ++ "px + " ++ String.fromFloat offCenterOffset ++ "px)") + [ Css.top (Css.calc (Css.px offCenterOffset) Css.minus (Css.px tailSize)), Css.bottom Css.unset ] Middle -> - Css.property "top" ("calc(-" ++ String.fromFloat tailSize ++ "px + 50%)") + [ Css.top (Css.calc (Css.pct 50) Css.minus (Css.px tailSize)), Css.bottom Css.unset ] End _ -> - Css.property "bottom" ("calc(-" ++ String.fromFloat tailSize ++ "px + " ++ String.fromFloat offCenterOffset ++ "px)") + [ Css.top Css.unset, Css.bottom (Css.calc (Css.px offCenterOffset) Css.minus (Css.px tailSize)) ] in case direction of OnTop -> - Css.batch - [ Css.property "transform" "translate(-50%, -100%)" - , getTailPositioning - { xAlignment = topBottomAlignment - , yAlignment = Css.top (Css.pct 100) - } - ] + [ Css.property "transform" "translate(-50%, -100%)" + , getTailPositioning + { xAlignment = topBottomAlignment + , yAlignment = [ Css.top (Css.pct 100), Css.bottom Css.unset ] + } + ] OnBottom -> - Css.batch - [ Css.property "transform" "translate(-50%, 0)" - , getTailPositioning - { xAlignment = topBottomAlignment - , yAlignment = Css.bottom (Css.pct 100) - } - ] + [ Css.property "transform" "translate(-50%, 0)" + , getTailPositioning + { xAlignment = topBottomAlignment + , yAlignment = [ Css.top Css.unset, Css.bottom (Css.pct 100) ] + } + ] OnRight -> - Css.batch - [ Css.property "transform" "translate(0, -50%)" - , getTailPositioning - { xAlignment = Css.right (Css.pct 100) - , yAlignment = rightLeftAlignment - } - ] + [ Css.property "transform" "translate(0, -50%)" + , getTailPositioning + { xAlignment = [ Css.left Css.unset, Css.right (Css.pct 100) ] + , yAlignment = rightLeftAlignment + } + ] OnLeft -> - Css.batch - [ Css.property "transform" "translate(-100%, -50%)" - , getTailPositioning - { xAlignment = Css.left (Css.pct 100) - , yAlignment = rightLeftAlignment - } - ] + [ Css.property "transform" "translate(-100%, -50%)" + , getTailPositioning + { xAlignment = [ Css.left (Css.pct 100), Css.right Css.unset ] + , yAlignment = rightLeftAlignment + } + ] -tailForDirection : Direction -> Style +tailForDirection : Direction -> List Style tailForDirection direction = case direction of OnTop -> @@ -1368,6 +1355,7 @@ topHoverArea = [ Css.bottom (Css.pct 100) , Css.left Css.zero , Css.right Css.zero + , Css.top Css.unset , Css.height (Css.px (tailSize + 3)) ] @@ -1377,6 +1365,7 @@ bottomHoverArea = [ Css.top (Css.pct 100) , Css.left Css.zero , Css.right Css.zero + , Css.bottom Css.unset , Css.height (Css.px (tailSize + 3)) ] @@ -1386,6 +1375,7 @@ leftHoverArea = [ Css.right (Css.pct 100) , Css.top Css.zero , Css.bottom Css.zero + , Css.left Css.unset , Css.width (Css.px (tailSize + 3)) ] @@ -1395,75 +1385,72 @@ rightHoverArea = [ Css.left (Css.pct 100) , Css.top Css.zero , Css.bottom Css.zero + , Css.right Css.unset , Css.width (Css.px (tailSize + 3)) ] -bottomTail : Style +bottomTail : List Style bottomTail = - Css.batch - [ Css.before - [ Css.borderTopColor outlineColor - , Css.property "border-width" (String.fromFloat (tailSize + 1) ++ "px") - , Css.marginLeft (Css.px (-tailSize - 1)) - ] - , Css.after - [ Css.borderTopColor tooltipColor - , Css.property "border-width" (String.fromFloat tailSize ++ "px") - , Css.marginLeft (Css.px -tailSize) - ] + [ Css.before + [ Css.property "border-width" (String.fromFloat (tailSize + 1) ++ "px") + , Css.borderColor4 outlineColor Css.transparent Css.transparent Css.transparent + , Css.marginLeft (Css.px (-tailSize - 1)) + ] + , Css.after + [ Css.property "border-width" (String.fromFloat tailSize ++ "px") + , Css.borderColor4 tooltipColor Css.transparent Css.transparent Css.transparent + , Css.margin4 Css.zero Css.zero Css.zero (Css.px -tailSize) ] + ] -topTail : Style +topTail : List Style topTail = - Css.batch - [ Css.before - [ Css.borderBottomColor outlineColor - , Css.property "border-width" (String.fromFloat (tailSize + 1) ++ "px") - , Css.marginLeft (Css.px (-tailSize - 1)) - ] - , Css.after - [ Css.borderBottomColor tooltipColor - , Css.property "border-width" (String.fromFloat tailSize ++ "px") - , Css.marginLeft (Css.px -tailSize) - ] + [ Css.before + [ Css.property "border-width" (String.fromFloat (tailSize + 1) ++ "px") + , Css.borderColor4 Css.transparent Css.transparent outlineColor Css.transparent + , Css.marginLeft (Css.px (-tailSize - 1)) ] + , Css.after + [ Css.property "border-width" (String.fromFloat tailSize ++ "px") + , Css.borderColor4 Css.transparent Css.transparent tooltipColor Css.transparent + , Css.margin4 Css.zero Css.zero Css.zero (Css.px -tailSize) + ] + ] -rightTail : Style +rightTail : List Style rightTail = - Css.batch - [ Css.before - [ Css.borderLeftColor outlineColor - , Css.property "border-width" (String.fromFloat (tailSize + 1) ++ "px") - ] - , Css.after - [ Css.borderLeftColor tooltipColor - , Css.property "border-width" (String.fromFloat tailSize ++ "px") - , Css.marginTop (Css.px 1) - , Css.marginRight (Css.px 2) - ] + [ Css.before + [ Css.property "border-width" (String.fromFloat (tailSize + 1) ++ "px") + , Css.borderColor4 Css.transparent Css.transparent Css.transparent outlineColor + , Css.marginLeft Css.zero + ] + , Css.after + [ Css.property "border-width" (String.fromFloat tailSize ++ "px") + , Css.borderColor4 Css.transparent Css.transparent Css.transparent tooltipColor + , Css.margin4 (Css.px 1) (Css.px 2) Css.zero Css.zero ] + ] -leftTail : Style +leftTail : List Style leftTail = - Css.batch - [ Css.before - [ Css.borderRightColor outlineColor - , Css.property "border-width" (String.fromFloat (tailSize + 1) ++ "px") - ] - , Css.after - [ Css.borderRightColor tooltipColor - , Css.property "border-width" (String.fromFloat tailSize ++ "px") - , Css.marginTop (Css.px 1) - , Css.marginLeft (Css.px 2) - ] + [ Css.before + [ Css.property "border-width" (String.fromFloat (tailSize + 1) ++ "px") + , Css.borderColor4 Css.transparent outlineColor Css.transparent Css.transparent + , Css.marginLeft Css.zero + ] + , Css.after + [ Css.property "border-width" (String.fromFloat tailSize ++ "px") + , Css.borderColor4 Css.transparent tooltipColor Css.transparent Css.transparent + , Css.margin4 (Css.px 1) Css.zero Css.zero (Css.px 2) ] + ] -getTailPositioning : { xAlignment : Style, yAlignment : Style } -> Style +getTailPositioning : { xAlignment : List Style, yAlignment : List Style } -> Style getTailPositioning config = Css.batch [ Css.before (positionTail config) @@ -1471,14 +1458,14 @@ getTailPositioning config = ] -positionTail : { xAlignment : Style, yAlignment : Style } -> List Style +positionTail : { xAlignment : List Style, yAlignment : List Style } -> List Style positionTail { xAlignment, yAlignment } = - [ xAlignment - , yAlignment - , Css.property "border" "solid transparent" + [ Css.property "border" "solid transparent" , Css.property "content" "\" \"" , Css.height Css.zero , Css.width Css.zero , Css.position Css.absolute , Css.pointerEvents Css.none ] + ++ xAlignment + ++ yAlignment From d68660b1376b048b4fffc77bcbc3e7bcf967a29f Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 14:09:29 -0500 Subject: [PATCH 12/58] Fix duplicated media queries bug --- component-catalog/tests/MediaQuerySpec.elm | 19 ++++++++++++++++++- src/Nri/Ui/MediaQuery/V2.elm | 16 +++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/component-catalog/tests/MediaQuerySpec.elm b/component-catalog/tests/MediaQuerySpec.elm index dfcb50b61..7574bdf47 100644 --- a/component-catalog/tests/MediaQuerySpec.elm +++ b/component-catalog/tests/MediaQuerySpec.elm @@ -1,6 +1,6 @@ module MediaQuerySpec exposing (suite) -import Css exposing (int, order) +import Css exposing (borderWidth, fontSize, int, order, px) import Html.Styled exposing (div, toUnstyled) import Html.Styled.Attributes exposing (css) import Nri.Ui.MediaQuery.V2 as MediaQuery @@ -54,4 +54,21 @@ suite = @media only screen and (max-width: 500px){._28601c77{order:6;}} """) ] + , test "it works with duplicated queries" <| + \() -> + div + [ css <| + MediaQuery.toStyles + [ mobile [ borderWidth (px 1) ] + , mobile [ fontSize (px 1) ] + ] + ] + [] + |> toUnstyled + |> Query.fromHtml + |> Query.find [ Selector.tag "style" ] + |> Query.has + [ Selector.text "border-width:1px;" + , Selector.text "font-size:1px;" + ] ] diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index f751e5238..b8442afd9 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -160,10 +160,13 @@ toStyles queries = HighContrast -> ( .highContrast, \r v -> { r | highContrast = v } ) + + ( maybeExistingSatisfies, maybeExistingDoesNotSatisfy ) = + get acc in set acc - ( Maybe.or (Tuple.first (get acc)) satisfiesTargetStyles - , Maybe.or (Tuple.second (get acc)) doesNotSatisfyTargetStyles + ( merge maybeExistingSatisfies satisfiesTargetStyles + , merge maybeExistingDoesNotSatisfy doesNotSatisfyTargetStyles ) ) { mobile = ( Nothing, Nothing ) @@ -174,21 +177,24 @@ toStyles queries = } queries + merge a b = + Maybe.map ((++) (Maybe.withDefault [] a)) b |> Maybe.orElse a + prepend = Maybe.cons append maybeItem list = Maybe.map (List.singleton >> List.append list) maybeItem |> Maybe.withDefault list - mkViewportQuery rule px = - Maybe.map <| withMedia [ only screen [ rule <| Css.px px ] ] - mkBooleanQuery onQuery offQuery ( onStyles, offStyles ) = Maybe.values [ Maybe.map (withMediaQuery [ onQuery ]) onStyles , Maybe.map (withMediaQuery [ offQuery ]) offStyles ] + mkViewportQuery rule px = + Maybe.map <| withMedia [ only screen [ rule <| Css.px px ] ] + addViewportQuery ( px, getStyles ) = let ( desktopFirst, mobileFirst ) = From a27aefd51ea9da3537171d457602c21840cca778 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 14:15:31 -0500 Subject: [PATCH 13/58] Use fuzz test --- component-catalog/tests/MediaQuerySpec.elm | 41 ++++++++++++---------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/component-catalog/tests/MediaQuerySpec.elm b/component-catalog/tests/MediaQuerySpec.elm index 7574bdf47..3aa092c2d 100644 --- a/component-catalog/tests/MediaQuerySpec.elm +++ b/component-catalog/tests/MediaQuerySpec.elm @@ -1,11 +1,13 @@ module MediaQuerySpec exposing (suite) import Css exposing (borderWidth, fontSize, int, order, px) +import Fuzz exposing (Fuzzer) import Html.Styled exposing (div, toUnstyled) import Html.Styled.Attributes exposing (css) import Nri.Ui.MediaQuery.V2 as MediaQuery exposing - ( highContrastMode + ( MediaQuery + , highContrastMode , mobile , narrowMobile , prefersReducedMotion @@ -16,27 +18,28 @@ import Test.Html.Query as Query import Test.Html.Selector as Selector +allQueriesRandomOrderFuzzer : Fuzzer (List MediaQuery) +allQueriesRandomOrderFuzzer = + Fuzz.shuffledList + [ MediaQuery.not narrowMobile [ order (int 1) ] + , MediaQuery.not quizEngineMobile [ order (int 2) ] + , MediaQuery.not mobile [ order (int 3) ] + , mobile [ order (int 4) ] + , quizEngineMobile [ order (int 5) ] + , narrowMobile [ order (int 6) ] + , highContrastMode [ order (int -1) ] + , MediaQuery.not highContrastMode [ order (int -11) ] + , prefersReducedMotion [ order (int -2) ] + , MediaQuery.not prefersReducedMotion [ order (int -22) ] + ] + + suite : Test suite = describe "MediaQuery.builder" - [ test "it puts queries in the correct order" <| - \() -> - div - [ css <| - MediaQuery.toStyles - [ narrowMobile [ order (int 6) ] - , quizEngineMobile [ order (int 5) ] - , mobile [ order (int 4) ] - , MediaQuery.not narrowMobile [ order (int 1) ] - , MediaQuery.not quizEngineMobile [ order (int 2) ] - , MediaQuery.not mobile [ order (int 3) ] - , highContrastMode [ order (int -1) ] - , prefersReducedMotion [ order (int -2) ] - , MediaQuery.not highContrastMode [ order (int -11) ] - , MediaQuery.not prefersReducedMotion [ order (int -22) ] - ] - ] - [] + [ fuzz allQueriesRandomOrderFuzzer "it puts queries in the correct order" <| + \queries -> + div [ css <| MediaQuery.toStyles queries ] [] |> toUnstyled |> Query.fromHtml |> Query.find [ Selector.tag "style" ] From 47d7f7b017c72e2635857a546c580a4f7b70a457 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 14:17:54 -0500 Subject: [PATCH 14/58] elm-review --- src/Nri/Ui/Tooltip/V3.elm | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Nri/Ui/Tooltip/V3.elm b/src/Nri/Ui/Tooltip/V3.elm index d36ced0c1..557db2062 100644 --- a/src/Nri/Ui/Tooltip/V3.elm +++ b/src/Nri/Ui/Tooltip/V3.elm @@ -95,7 +95,6 @@ import Html.Styled as Root import Html.Styled.Attributes as Attributes import Html.Styled.Events as Events import Json.Decode -import List.Extra as List import Maybe.Extra as Maybe import Nri.Ui import Nri.Ui.ClickableSvg.V2 as ClickableSvg From d6f0645080c1212e3213d35f1beedcfa55e91705 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 14:34:59 -0500 Subject: [PATCH 15/58] Refactor Tabs to use MediaQuery.V2 --- src/Nri/Ui/Tabs/V8.elm | 220 ++++++++++++++++++++--------------------- 1 file changed, 105 insertions(+), 115 deletions(-) diff --git a/src/Nri/Ui/Tabs/V8.elm b/src/Nri/Ui/Tabs/V8.elm index 32b78594b..4c49b5cac 100644 --- a/src/Nri/Ui/Tabs/V8.elm +++ b/src/Nri/Ui/Tabs/V8.elm @@ -39,15 +39,15 @@ module Nri.Ui.Tabs.V8 exposing import Css exposing (..) import Css.Global -import Css.Media import Html.Styled as Html exposing (Html) import Html.Styled.Attributes as Attributes +import Maybe.Extra as Maybe import Nri.Ui import Nri.Ui.Colors.Extra exposing (withAlpha) import Nri.Ui.Colors.V1 as Colors import Nri.Ui.FocusRing.V1 as FocusRing import Nri.Ui.Fonts.V1 as Fonts -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import Nri.Ui.Tooltip.V3 as Tooltip import TabsInternal.V2 as TabsInternal @@ -282,28 +282,32 @@ view { focusAndSelect, selected } attrs tabs = , Css.borderBottomStyle Css.solid , Css.borderBottomColor Colors.navy , Fonts.baseFont - , Css.Media.withMedia - [ MediaQuery.narrowMobile ] - [ Css.backgroundColor Colors.gray96 - , Css.padding (Css.px 20) - , Css.borderRadius (Css.px 8) - , Css.borderBottom Css.zero - , Css.flexDirection Css.column - , Css.alignItems flexStart - , Css.Global.children [ Css.Global.div [ Css.width (Css.pct 100) ] ] - ] - , maybeStyle - (\{ topOffset, topPadding, zIndex } -> - Css.Media.withMedia - [ MediaQuery.notMobile ] - [ Css.position Css.sticky - , Css.top (Css.px topOffset) - , Css.paddingTop (Css.px topPadding) - , Css.zIndex (Css.int zIndex) - ] + , MediaQuery.toStyle + (Maybe.values + [ Just + (MediaQuery.narrowMobile + [ Css.backgroundColor Colors.gray96 + , Css.padding (Css.px 20) + , Css.borderRadius (Css.px 8) + , Css.borderBottom Css.zero + , Css.flexDirection Css.column + , Css.alignItems flexStart + , Css.Global.children [ Css.Global.div [ Css.width (Css.pct 100) ] ] + ] + ) + , config.tabListStickyConfig + |> Maybe.map + (\{ topOffset, topPadding, zIndex } -> + MediaQuery.mobile + [ Css.position Css.sticky + , Css.top (Css.px topOffset) + , Css.paddingTop (Css.px topPadding) + , Css.zIndex (Css.int zIndex) + ] + ) + ] ) - config.tabListStickyConfig - , maybeStyle Css.backgroundColor config.pageBackgroundColor + , Maybe.unwrap (Css.batch []) Css.backgroundColor config.pageBackgroundColor ] [] [ config.title @@ -363,67 +367,52 @@ stylesTabsAligned config = , Css.displayFlex , Css.flexGrow (Css.int 1) , Css.padding Css.zero - , Css.Media.withMedia - [ MediaQuery.narrowMobile ] - [ Css.flexDirection Css.column - ] + , MediaQuery.toStyle [ MediaQuery.narrowMobile [ Css.flexDirection Css.column ] ] ] -maybeStyle : (a -> Style) -> Maybe a -> Style -maybeStyle styler maybeValue = - case maybeValue of - Just value -> - styler value - - Nothing -> - Css.batch [] - - tabStyles : Maybe Float -> Css.Color -> Int -> Bool -> List Style tabStyles customSpacing pageBackgroundColor_ index isSelected = let - stylesDynamic = + ( stylesDynamic, narrowMobileStylesDynamic ) = if isSelected then - [ Css.borderBottom (Css.px 1) - , Css.borderBottomStyle Css.solid - , Css.backgroundColor Colors.white - , Css.borderBottomColor pageBackgroundColor_ - , Css.backgroundImage <| - Css.linearGradient2 Css.toTop - (Css.stop2 (withAlpha 1 pageBackgroundColor_) (Css.pct 0)) - (Css.stop2 (withAlpha 0 pageBackgroundColor_) (Css.pct 100)) - [] - , Css.Media.withMedia - [ MediaQuery.narrowMobile ] - [ Css.border Css.zero - , Css.backgroundImage Css.none - , Css.backgroundColor Colors.glacier - , Css.borderRadius (Css.px 8) - , Css.fontSize (Css.px 15) - , Css.fontWeight (Css.int 700) - , Css.width (Css.pct 100) - ] - ] + ( [ Css.borderBottom (Css.px 1) + , Css.borderBottomStyle Css.solid + , Css.backgroundColor Colors.white + , Css.borderBottomColor pageBackgroundColor_ + , Css.backgroundImage <| + Css.linearGradient2 Css.toTop + (Css.stop2 (withAlpha 1 pageBackgroundColor_) (Css.pct 0)) + (Css.stop2 (withAlpha 0 pageBackgroundColor_) (Css.pct 100)) + [] + ] + , [ Css.border Css.zero + , Css.backgroundImage Css.none + , Css.backgroundColor Colors.glacier + , Css.borderRadius (Css.px 8) + , Css.fontSize (Css.px 15) + , Css.fontWeight (Css.int 700) + , Css.width (Css.pct 100) + ] + ) else - [ Css.backgroundColor Colors.frost - , Css.backgroundImage <| - Css.linearGradient2 Css.toTop - (Css.stop2 (withAlpha 0.25 Colors.azure) (Css.pct 0)) - (Css.stop2 (withAlpha 0 Colors.azure) (Css.pct 25)) - [ Css.stop2 (withAlpha 0 Colors.azure) (Css.pct 100) ] - , Css.Media.withMedia - [ MediaQuery.narrowMobile ] - [ Css.backgroundImage Css.none - , Css.backgroundColor Colors.gray96 - , Css.border Css.zero - , Css.borderRadius (Css.px 8) - , Css.fontSize (Css.px 15) - , Css.width (Css.pct 100) - , Css.fontWeight (Css.int 600) - ] - ] + ( [ Css.backgroundColor Colors.frost + , Css.backgroundImage <| + Css.linearGradient2 Css.toTop + (Css.stop2 (withAlpha 0.25 Colors.azure) (Css.pct 0)) + (Css.stop2 (withAlpha 0 Colors.azure) (Css.pct 25)) + [ Css.stop2 (withAlpha 0 Colors.azure) (Css.pct 100) ] + ] + , [ Css.backgroundImage Css.none + , Css.backgroundColor Colors.gray96 + , Css.border Css.zero + , Css.borderRadius (Css.px 8) + , Css.fontSize (Css.px 15) + , Css.width (Css.pct 100) + , Css.fontWeight (Css.int 600) + ] + ) baseStyles = [ Css.color Colors.navy @@ -439,52 +428,53 @@ tabStyles customSpacing pageBackgroundColor_ index isSelected = , Css.height (Css.pct 100) ] - stylesTab = - [ Css.display Css.inlineBlock - , Css.borderTopLeftRadius (Css.px 10) - , Css.borderTopRightRadius (Css.px 10) - , Css.border3 (Css.px 1) Css.solid Colors.navy - , Css.marginTop Css.zero - , Css.marginLeft - (if index == 0 then - Css.px 0 - - else - Css.px margin - ) - , Css.marginRight (Css.px margin) - , Css.padding2 (Css.px 1) (Css.px 6) - , Css.marginBottom (Css.px -1) - , Css.cursor Css.pointer - , property "transition" "background-color 0.2s" - , property "transition" "border-color 0.2s" - , Css.Media.withMedia - [ MediaQuery.narrowMobile ] - [ Css.marginLeft Css.zero - , Css.marginRight Css.zero - , Css.textAlign Css.left - , Css.padding Css.zero - ] - , hover - [ backgroundColor Colors.white - , borderTopColor Colors.azure - , borderRightColor Colors.azure - , borderLeftColor Colors.azure - , Css.Media.withMedia - [ MediaQuery.narrowMobile ] + ( stylesTab, narrowMobileStylesTab ) = + ( [ Css.display Css.inlineBlock + , Css.borderTopLeftRadius (Css.px 10) + , Css.borderTopRightRadius (Css.px 10) + , Css.border3 (Css.px 1) Css.solid Colors.navy + , Css.marginTop Css.zero + , Css.marginLeft + (if index == 0 then + Css.px 0 + + else + Css.px margin + ) + , Css.marginRight (Css.px margin) + , Css.padding2 (Css.px 1) (Css.px 6) + , Css.marginBottom (Css.px -1) + , Css.cursor Css.pointer + , property "transition" "background-color 0.2s" + , property "transition" "border-color 0.2s" + , pseudoClass "focus-visible" + [ FocusRing.outerBoxShadow + , Css.outline3 (Css.px 2) Css.solid Css.transparent + ] + , hover + [ backgroundColor Colors.white + , borderTopColor Colors.azure + , borderRightColor Colors.azure + , borderLeftColor Colors.azure + ] + ] + , [ Css.marginLeft Css.zero + , Css.marginRight Css.zero + , Css.textAlign Css.left + , Css.padding Css.zero + , hover [ backgroundColor Colors.frost , borderTopColor Css.unset , borderRightColor Css.unset , borderLeftColor Css.unset ] - ] - , pseudoClass "focus-visible" - [ FocusRing.outerBoxShadow - , Css.outline3 (Css.px 2) Css.solid Css.transparent - ] - ] + ] + ) margin = Maybe.withDefault 10 customSpacing / 2 in - baseStyles ++ stylesTab ++ stylesDynamic + baseStyles + ++ stylesTab + ++ stylesDynamic + ++ MediaQuery.toStyles [ MediaQuery.narrowMobile <| narrowMobileStylesTab ++ narrowMobileStylesDynamic ] From 12f7e5f28e70da9693dc48f11ff9b43fb610844a Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 14:36:39 -0500 Subject: [PATCH 16/58] Refactor Switch to use MediaQuery.V2 --- src/Nri/Ui/Switch/V3.elm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Nri/Ui/Switch/V3.elm b/src/Nri/Ui/Switch/V3.elm index df4469f90..af2055f9e 100644 --- a/src/Nri/Ui/Switch/V3.elm +++ b/src/Nri/Ui/Switch/V3.elm @@ -39,7 +39,7 @@ import Nri.Ui.Colors.V1 as Colors import Nri.Ui.FocusRing.V1 as FocusRing import Nri.Ui.Fonts.V1 as Fonts import Nri.Ui.Html.Attributes.V2 as Extra -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import Nri.Ui.Svg.V1 exposing (Svg) import Svg.Styled as Svg import Svg.Styled.Attributes as SvgAttributes @@ -395,4 +395,7 @@ stroke color = transition : String -> Css.Style transition transitionRules = - MediaQuery.anyMotion [ Css.property "transition" transitionRules ] + MediaQuery.toStyle + [ MediaQuery.not MediaQuery.prefersReducedMotion + [ Css.property "transition" transitionRules ] + ] From 6af7bc9c15950586486f811c83c6dea717c6e9b5 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 16:15:04 -0500 Subject: [PATCH 17/58] Generalize breakpoints and user preferences --- component-catalog/tests/MediaQuerySpec.elm | 45 ++++--- src/Nri/Ui/MediaQuery/V2.elm | 147 ++++++++++++--------- 2 files changed, 108 insertions(+), 84 deletions(-) diff --git a/component-catalog/tests/MediaQuerySpec.elm b/component-catalog/tests/MediaQuerySpec.elm index 3aa092c2d..8b08fa3ae 100644 --- a/component-catalog/tests/MediaQuerySpec.elm +++ b/component-catalog/tests/MediaQuerySpec.elm @@ -27,17 +27,13 @@ allQueriesRandomOrderFuzzer = , mobile [ order (int 4) ] , quizEngineMobile [ order (int 5) ] , narrowMobile [ order (int 6) ] - , highContrastMode [ order (int -1) ] - , MediaQuery.not highContrastMode [ order (int -11) ] - , prefersReducedMotion [ order (int -2) ] - , MediaQuery.not prefersReducedMotion [ order (int -22) ] ] suite : Test suite = - describe "MediaQuery.builder" - [ fuzz allQueriesRandomOrderFuzzer "it puts queries in the correct order" <| + describe "MediaQuery.V2" + [ fuzz allQueriesRandomOrderFuzzer "it puts breakpoint queries in the correct order" <| \queries -> div [ css <| MediaQuery.toStyles queries ] [] |> toUnstyled @@ -45,25 +41,38 @@ suite = |> Query.find [ Selector.tag "style" ] |> Query.has [ Selector.text (String.trim """ -@media only screen and (min-width: 501px){._28601c77{order:1;}} -@media only screen and (min-width: 751px){._28601c77{order:2;}} -@media only screen and (min-width: 1001px){._28601c77{order:3;}} -@media (forced-colors: none){._28601c77{order:-11;}} -@media (forced-colors: active){._28601c77{order:-1;}} -@media (prefers-reduced-motion: no-preference){._28601c77{order:-22;}} -@media (prefers-reduced-motion){._28601c77{order:-2;}} -@media only screen and (max-width: 1000px){._28601c77{order:4;}} -@media only screen and (max-width: 750px){._28601c77{order:5;}} -@media only screen and (max-width: 500px){._28601c77{order:6;}} +@media only screen and (min-width: 501px){._543c77e6{order:1;}} +@media only screen and (min-width: 751px){._543c77e6{order:2;}} +@media only screen and (min-width: 1001px){._543c77e6{order:3;}} +@media only screen and (max-width: 1000px){._543c77e6{order:4;}} +@media only screen and (max-width: 750px){._543c77e6{order:5;}} +@media only screen and (max-width: 500px){._543c77e6{order:6;}} """) ] + , test "it works with user preference queries" <| + \() -> + div + [ css <| + MediaQuery.toStyles + [ highContrastMode [ borderWidth (px 1) ] + , MediaQuery.not highContrastMode [ borderWidth (px 2) ] + ] + ] + [] + |> toUnstyled + |> Query.fromHtml + |> Query.find [ Selector.tag "style" ] + |> Query.has + [ Selector.text "border-width:1px;" + , Selector.text "border-width:2px;" + ] , test "it works with duplicated queries" <| \() -> div [ css <| MediaQuery.toStyles - [ mobile [ borderWidth (px 1) ] - , mobile [ fontSize (px 1) ] + [ prefersReducedMotion [ borderWidth (px 1) ] + , prefersReducedMotion [ fontSize (px 1) ] ] ] [] diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index b8442afd9..3111fa233 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -1,6 +1,6 @@ module Nri.Ui.MediaQuery.V2 exposing ( MediaQuery, toStyles, toStyle - , not + , not, offset , mobile, narrowMobile, quizEngineMobile , prefersReducedMotion, highContrastMode ) @@ -38,15 +38,15 @@ Build media queries for responsive design. ### Utilities -@docs not +@docs not, offset -### Viewport Media Queries +### Breakpoint @docs mobile, narrowMobile, quizEngineMobile -### Accessibility Media Queries +### User Preferences @docs prefersReducedMotion, highContrastMode @@ -54,6 +54,7 @@ Build media queries for responsive design. import Css exposing (Style, target) import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia, withMediaQuery) +import Dict import Maybe.Extra as Maybe @@ -75,11 +76,8 @@ type MediaQuery type Target - = Mobile - | QuizEngineMobile - | NarrowMobile - | ReducedMotion - | HighContrast + = Breakpoint Float + | UserPreference String String {-| Negate a MediaQuery @@ -100,39 +98,75 @@ not query s = MediaQuery target Nothing (Just s) +{-| Offset a viewport breakpoint + + For example, + + `offset 20 mobile [ fontSize (px 20) ]` + + Will apply the `font-size: 20px` rule at the breakpoint 1020px. + + If used with a non-breakpoint media query or the resulting breakpoing is < 0, + `Nothing` will be returned. + +-} +offset : Float -> (List Style -> MediaQuery) -> List Style -> Maybe MediaQuery +offset px query s = + case query s of + MediaQuery (Breakpoint orig) _ _ -> + if orig + px < 0 then + Nothing + + else + Just <| MediaQuery (Breakpoint (orig + px)) (Just s) Nothing + + _ -> + Nothing + + +breakpoint : Float -> List Style -> MediaQuery +breakpoint px s = + MediaQuery (Breakpoint px) (Just s) Nothing + + +userPreference : String -> String -> List Style -> MediaQuery +userPreference on off s = + MediaQuery (UserPreference on off) (Just s) Nothing + + {-| Set styles for mobile and smaller devices (<= 1000px) -} mobile : List Style -> MediaQuery -mobile s = - MediaQuery Mobile (Just s) Nothing +mobile = + breakpoint 1000 {-| Set styles for quiz engine mobile and smaller devices (<= 750px) -} quizEngineMobile : List Style -> MediaQuery -quizEngineMobile s = - MediaQuery QuizEngineMobile (Just s) Nothing +quizEngineMobile = + breakpoint 750 {-| Set styles for narrow mobile and smaller devices (<= 500px) -} narrowMobile : List Style -> MediaQuery -narrowMobile s = - MediaQuery NarrowMobile (Just s) Nothing +narrowMobile = + breakpoint 500 {-| Set styles for reduced motion -} prefersReducedMotion : List Style -> MediaQuery -prefersReducedMotion s = - MediaQuery ReducedMotion (Just s) Nothing +prefersReducedMotion = + userPreference "(prefers-reduced-motion)" "(prefers-reduced-motion: no-preference)" {-| Set styles for high contrast mode -} highContrastMode : List Style -> MediaQuery -highContrastMode s = - MediaQuery HighContrast (Just s) Nothing +highContrastMode = + userPreference "(forced-colors: active)" "(forced-colors: none)" {-| Build a list of `Css.Style` from a list of media queries. @@ -143,37 +177,30 @@ toStyles queries = config = List.foldl (\(MediaQuery target satisfiesTargetStyles doesNotSatisfyTargetStyles) acc -> - let - ( get, set ) = - case target of - Mobile -> - ( .mobile, \r v -> { r | mobile = v } ) - - QuizEngineMobile -> - ( .quizEngineMobile, \r v -> { r | quizEngineMobile = v } ) - - NarrowMobile -> - ( .narrowMobile, \r v -> { r | narrowMobile = v } ) - - ReducedMotion -> - ( .reducedMotion, \r v -> { r | reducedMotion = v } ) - - HighContrast -> - ( .highContrast, \r v -> { r | highContrast = v } ) - - ( maybeExistingSatisfies, maybeExistingDoesNotSatisfy ) = - get acc - in - set acc - ( merge maybeExistingSatisfies satisfiesTargetStyles - , merge maybeExistingDoesNotSatisfy doesNotSatisfyTargetStyles - ) + case target of + Breakpoint px -> + { acc + | breakpoints = + Dict.update px + (Maybe.withDefault ( Nothing, Nothing ) + >> Tuple.mapBoth + (merge satisfiesTargetStyles) + (merge doesNotSatisfyTargetStyles) + >> Just + ) + acc.breakpoints + } + + UserPreference onQuery offQuery -> + { acc + | userPreferences = + acc.userPreferences + |> Dict.update onQuery (merge satisfiesTargetStyles) + |> Dict.update offQuery (merge doesNotSatisfyTargetStyles) + } ) - { mobile = ( Nothing, Nothing ) - , quizEngineMobile = ( Nothing, Nothing ) - , narrowMobile = ( Nothing, Nothing ) - , reducedMotion = ( Nothing, Nothing ) - , highContrast = ( Nothing, Nothing ) + { breakpoints = Dict.empty + , userPreferences = Dict.empty } queries @@ -186,29 +213,17 @@ toStyles queries = append maybeItem list = Maybe.map (List.singleton >> List.append list) maybeItem |> Maybe.withDefault list - mkBooleanQuery onQuery offQuery ( onStyles, offStyles ) = - Maybe.values - [ Maybe.map (withMediaQuery [ onQuery ]) onStyles - , Maybe.map (withMediaQuery [ offQuery ]) offStyles - ] + mkUserPreferenceQuery ( query, styles ) = + withMediaQuery [ query ] styles mkViewportQuery rule px = Maybe.map <| withMedia [ only screen [ rule <| Css.px px ] ] - addViewportQuery ( px, getStyles ) = - let - ( desktopFirst, mobileFirst ) = - getStyles config - in + addViewportQuery ( px, ( desktopFirst, mobileFirst ) ) = prepend (mkViewportQuery maxWidth px desktopFirst) >> append (mkViewportQuery minWidth (px + 1) mobileFirst) in - List.concat - [ mkBooleanQuery "(prefers-reduced-motion)" "(prefers-reduced-motion: no-preference)" config.reducedMotion - , mkBooleanQuery "(forced-colors: active)" "(forced-colors: none)" config.highContrast - ] - |> addViewportQuery ( 1000, .mobile ) - |> addViewportQuery ( 750, .quizEngineMobile ) - |> addViewportQuery ( 500, .narrowMobile ) + List.map mkUserPreferenceQuery (Dict.toList config.userPreferences) + ++ List.foldr addViewportQuery [] (Dict.toList config.breakpoints |> List.sortBy Tuple.first) {-| Build a single `Css.Style` from a list of media queries. From 304eb6b7eb6d8ffeed99369e0f0defccdbd99919 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 16:55:17 -0500 Subject: [PATCH 18/58] Don't return Maybe from offset --- component-catalog/tests/MediaQuerySpec.elm | 23 +++++++++++++--------- src/Nri/Ui/MediaQuery/V2.elm | 15 +++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/component-catalog/tests/MediaQuerySpec.elm b/component-catalog/tests/MediaQuerySpec.elm index 8b08fa3ae..56d8af549 100644 --- a/component-catalog/tests/MediaQuerySpec.elm +++ b/component-catalog/tests/MediaQuerySpec.elm @@ -10,6 +10,7 @@ import Nri.Ui.MediaQuery.V2 as MediaQuery , highContrastMode , mobile , narrowMobile + , offset , prefersReducedMotion , quizEngineMobile ) @@ -24,9 +25,11 @@ allQueriesRandomOrderFuzzer = [ MediaQuery.not narrowMobile [ order (int 1) ] , MediaQuery.not quizEngineMobile [ order (int 2) ] , MediaQuery.not mobile [ order (int 3) ] - , mobile [ order (int 4) ] - , quizEngineMobile [ order (int 5) ] - , narrowMobile [ order (int 6) ] + , MediaQuery.not (offset 100 mobile) [ order (int 4) ] + , offset -100 mobile [ order (int 6) ] + , mobile [ order (int 5) ] + , quizEngineMobile [ order (int 7) ] + , narrowMobile [ order (int 8) ] ] @@ -41,12 +44,14 @@ suite = |> Query.find [ Selector.tag "style" ] |> Query.has [ Selector.text (String.trim """ -@media only screen and (min-width: 501px){._543c77e6{order:1;}} -@media only screen and (min-width: 751px){._543c77e6{order:2;}} -@media only screen and (min-width: 1001px){._543c77e6{order:3;}} -@media only screen and (max-width: 1000px){._543c77e6{order:4;}} -@media only screen and (max-width: 750px){._543c77e6{order:5;}} -@media only screen and (max-width: 500px){._543c77e6{order:6;}} +@media only screen and (min-width: 501px){._a039f42a{order:1;}} +@media only screen and (min-width: 751px){._a039f42a{order:2;}} +@media only screen and (min-width: 1001px){._a039f42a{order:3;}} +@media only screen and (min-width: 1101px){._a039f42a{order:4;}} +@media only screen and (max-width: 1000px){._a039f42a{order:5;}} +@media only screen and (max-width: 900px){._a039f42a{order:6;}} +@media only screen and (max-width: 750px){._a039f42a{order:7;}} +@media only screen and (max-width: 500px){._a039f42a{order:8;}} """) ] , test "it works with user preference queries" <| diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index 3111fa233..451ef03c8 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -106,22 +106,17 @@ not query s = Will apply the `font-size: 20px` rule at the breakpoint 1020px. - If used with a non-breakpoint media query or the resulting breakpoing is < 0, - `Nothing` will be returned. + No effect if used with a non-breakpoint media query. -} -offset : Float -> (List Style -> MediaQuery) -> List Style -> Maybe MediaQuery +offset : Float -> (List Style -> MediaQuery) -> List Style -> MediaQuery offset px query s = case query s of MediaQuery (Breakpoint orig) _ _ -> - if orig + px < 0 then - Nothing + MediaQuery (Breakpoint (max 0 (orig + px))) (Just s) Nothing - else - Just <| MediaQuery (Breakpoint (orig + px)) (Just s) Nothing - - _ -> - Nothing + other -> + other breakpoint : Float -> List Style -> MediaQuery From beb35449fe2f33ed304fa64ee3d664e72ace55c7 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 20:37:32 -0500 Subject: [PATCH 19/58] Refactor the api, again --- src/Nri/Ui/MediaQuery/V2.elm | 167 ++++++++++++++++++++++------------- 1 file changed, 105 insertions(+), 62 deletions(-) diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index 451ef03c8..27ff110e0 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -1,5 +1,5 @@ module Nri.Ui.MediaQuery.V2 exposing - ( MediaQuery, toStyles, toStyle + ( MediaQuery, fromList, toStyles, toStyle , not, offset , mobile, narrowMobile, quizEngineMobile , prefersReducedMotion, highContrastMode @@ -33,7 +33,7 @@ Build media queries for responsive design. ### Basics -@docs MediaQuery, toStyles, toStyle +@docs MediaQuery, fromList, init, on, toStyles, toStyle ### Utilities @@ -54,7 +54,7 @@ Build media queries for responsive design. import Css exposing (Style, target) import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia, withMediaQuery) -import Dict +import Dict exposing (Dict) import Maybe.Extra as Maybe @@ -71,7 +71,7 @@ import Maybe.Extra as Maybe MediaQuery Mobile (Just [ Css.paddingTop (Css.px 10) ]) (Just [ Css.paddingTop (Css.px 20) ] -} -type MediaQuery +type MediaQuery properties = MediaQuery Target (Maybe (List Style)) (Maybe (List Style)) @@ -89,13 +89,11 @@ type Target unexpected behavior with screen readers and printers. -} -not : (List Style -> MediaQuery) -> List Style -> MediaQuery -not query s = - let - (MediaQuery target _ _) = - query s - in - MediaQuery target Nothing (Just s) +not : (List Style -> MediaQuery properties) -> List Style -> MediaQuery properties +not mq s = + case mq s of + MediaQuery target _ _ -> + MediaQuery target Nothing (Just s) {-| Offset a viewport breakpoint @@ -109,9 +107,9 @@ not query s = No effect if used with a non-breakpoint media query. -} -offset : Float -> (List Style -> MediaQuery) -> List Style -> MediaQuery -offset px query s = - case query s of +offset : Float -> (List Style -> MediaQuery { properties | offsettable : () }) -> List Style -> MediaQuery { properties | offsettable : () } +offset px mq s = + case mq s of MediaQuery (Breakpoint orig) _ _ -> MediaQuery (Breakpoint (max 0 (orig + px))) (Just s) Nothing @@ -119,97 +117,104 @@ offset px query s = other -breakpoint : Float -> List Style -> MediaQuery +breakpoint : Float -> List Style -> MediaQuery { properties | offsettable : () } breakpoint px s = MediaQuery (Breakpoint px) (Just s) Nothing -userPreference : String -> String -> List Style -> MediaQuery -userPreference on off s = - MediaQuery (UserPreference on off) (Just s) Nothing +userPreference : String -> String -> List Style -> MediaQuery properties +userPreference on_ off s = + MediaQuery (UserPreference on_ off) (Just s) Nothing {-| Set styles for mobile and smaller devices (<= 1000px) -} -mobile : List Style -> MediaQuery +mobile : List Style -> MediaQuery { properties | offsettable : () } mobile = breakpoint 1000 {-| Set styles for quiz engine mobile and smaller devices (<= 750px) -} -quizEngineMobile : List Style -> MediaQuery +quizEngineMobile : List Style -> MediaQuery { properties | offsettable : () } quizEngineMobile = breakpoint 750 {-| Set styles for narrow mobile and smaller devices (<= 500px) -} -narrowMobile : List Style -> MediaQuery +narrowMobile : List Style -> MediaQuery { properties | offsettable : () } narrowMobile = breakpoint 500 {-| Set styles for reduced motion -} -prefersReducedMotion : List Style -> MediaQuery +prefersReducedMotion : List Style -> MediaQuery {} prefersReducedMotion = userPreference "(prefers-reduced-motion)" "(prefers-reduced-motion: no-preference)" {-| Set styles for high contrast mode -} -highContrastMode : List Style -> MediaQuery +highContrastMode : List Style -> MediaQuery {} highContrastMode = userPreference "(forced-colors: active)" "(forced-colors: none)" -{-| Build a list of `Css.Style` from a list of media queries. --} -toStyles : List MediaQuery -> List Style -toStyles queries = - let - config = - List.foldl - (\(MediaQuery target satisfiesTargetStyles doesNotSatisfyTargetStyles) acc -> - case target of - Breakpoint px -> - { acc - | breakpoints = - Dict.update px - (Maybe.withDefault ( Nothing, Nothing ) - >> Tuple.mapBoth - (merge satisfiesTargetStyles) - (merge doesNotSatisfyTargetStyles) - >> Just - ) - acc.breakpoints - } - - UserPreference onQuery offQuery -> - { acc - | userPreferences = - acc.userPreferences - |> Dict.update onQuery (merge satisfiesTargetStyles) - |> Dict.update offQuery (merge doesNotSatisfyTargetStyles) - } - ) - { breakpoints = Dict.empty - , userPreferences = Dict.empty - } - queries +type alias ResponsiveStyles = + { breakpoints : Dict Float ( Maybe (List Style), Maybe (List Style) ) + , userPreferences : Dict String (List Style) + } + + +init : ResponsiveStyles +init = + { breakpoints = Dict.empty, userPreferences = Dict.empty } + +on : MediaQuery properties -> ResponsiveStyles -> ResponsiveStyles +on mq internal = + let merge a b = Maybe.map ((++) (Maybe.withDefault [] a)) b |> Maybe.orElse a + in + case mq of + MediaQuery (Breakpoint px) satisfiesTargetStyles doesNotSatisfyTargetStyles -> + { internal + | breakpoints = + Dict.update px + (Maybe.withDefault ( Nothing, Nothing ) + >> Tuple.mapBoth + (merge satisfiesTargetStyles) + (merge doesNotSatisfyTargetStyles) + >> Just + ) + internal.breakpoints + } + + MediaQuery (UserPreference onQuery offQuery) satisfiesTargetStyles doesNotSatisfyTargetStyles -> + { internal + | userPreferences = + internal.userPreferences + |> Dict.update onQuery (merge satisfiesTargetStyles) + |> Dict.update offQuery (merge doesNotSatisfyTargetStyles) + } + +{-| Build a list of `Css.Style` from a list of media queries. +-} +toStyles : ResponsiveStyles -> List Style +toStyles { breakpoints, userPreferences } = + let prepend = Maybe.cons append maybeItem list = Maybe.map (List.singleton >> List.append list) maybeItem |> Maybe.withDefault list - mkUserPreferenceQuery ( query, styles ) = - withMediaQuery [ query ] styles + mkUserPreferenceQuery ( preference, styles ) = + withMediaQuery [ preference ] styles mkViewportQuery rule px = Maybe.map <| withMedia [ only screen [ rule <| Css.px px ] ] @@ -217,8 +222,8 @@ toStyles queries = addViewportQuery ( px, ( desktopFirst, mobileFirst ) ) = prepend (mkViewportQuery maxWidth px desktopFirst) >> append (mkViewportQuery minWidth (px + 1) mobileFirst) in - List.map mkUserPreferenceQuery (Dict.toList config.userPreferences) - ++ List.foldr addViewportQuery [] (Dict.toList config.breakpoints |> List.sortBy Tuple.first) + List.map mkUserPreferenceQuery (Dict.toList userPreferences) + ++ List.foldr addViewportQuery [] (Dict.toList breakpoints |> List.sortBy Tuple.first) {-| Build a single `Css.Style` from a list of media queries. @@ -226,6 +231,44 @@ toStyles queries = This is a convenience function for `Css.batch (toStyles queries)`. -} -toStyle : List MediaQuery -> Style +toStyle : ResponsiveStyles -> Style toStyle = toStyles >> Css.batch + + +{-| Build a single `Css.Style` from a list of media queries. + + This is a convenience function for `List.foldl MediaQuery.on MediaQuery.query >> MediaQuery.toStyle` + + In other words, + + ``` + MediaQuery.fromList + [ MediaQuery.mobile [ Css.paddingTop (Css.px 10) ] + , MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ] + ] + ``` + + is the same as + + ``` + MediaQuery.query + |> MediaQuery.on (MediaQuery.mobile [ Css.paddingTop (Css.px 10) ]) + |> MediaQuery.on (MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ]) + |> MediaQuery.toStyle + ``` + + Why do both of these exist? + + `fromList` is convenient, but restricts the use of phantom types. That is, you can't + create a media query that contains both a breakpoint and a user preference. You can only + create a media query that contains one or the other. + + This usually isn't a concern, but to enforce certain restrictions internally it + makes more sense to design the API around pipeline-style usage and expose this + convenience function for the common case. + +-} +fromList : List (MediaQuery properties) -> Style +fromList = + List.foldl on init >> toStyle From ecaf26dfb07baedf4bd05c310c52c60a56ba6227 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Wed, 20 Mar 2024 21:49:39 -0500 Subject: [PATCH 20/58] docs --- src/Nri/Ui/MediaQuery/Internal.elm | 5 + src/Nri/Ui/MediaQuery/V2.elm | 158 ++++++++++++++++++++++------- 2 files changed, 129 insertions(+), 34 deletions(-) create mode 100644 src/Nri/Ui/MediaQuery/Internal.elm diff --git a/src/Nri/Ui/MediaQuery/Internal.elm b/src/Nri/Ui/MediaQuery/Internal.elm new file mode 100644 index 000000000..041920b95 --- /dev/null +++ b/src/Nri/Ui/MediaQuery/Internal.elm @@ -0,0 +1,5 @@ +module Nri.Ui.MediaQuery.Internal exposing (..) + + +mobile = + 1000 diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index 27ff110e0..ec8b687e9 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -1,6 +1,7 @@ module Nri.Ui.MediaQuery.V2 exposing ( MediaQuery, fromList, toStyles, toStyle , not, offset + , breakpoint , mobile, narrowMobile, quizEngineMobile , prefersReducedMotion, highContrastMode ) @@ -43,6 +44,7 @@ Build media queries for responsive design. ### Breakpoint +@docs breakpoint @docs mobile, narrowMobile, quizEngineMobile @@ -58,17 +60,11 @@ import Dict exposing (Dict) import Maybe.Extra as Maybe -{-| Type representing a Media Query in the format +{-| Type representing a media query. - `(Target, SatisfiesTargetStyles, DoesNotSatisfyTargetStyles)`, where: + There is a lone constructor `MediaQuery` that takes 3 arguments: - - `Target` is the media this query is targeting (e.g. Mobile, NarrowMobile, etc.) - - `SatisfiesTargetStyles` is the style to apply when the media query is satisfied - - `DoesNotSatisfyTargetStyles` is the style to apply when the media query is NOT satisfied - - Example: - - MediaQuery Mobile (Just [ Css.paddingTop (Css.px 10) ]) (Just [ Css.paddingTop (Css.px 20) ] + (Target, SatisfiesTargetStyles, DoesNotSatisfyTargetStyles)` -} type MediaQuery properties @@ -117,11 +113,29 @@ offset px mq s = other +{-| Construct a media query for a viewport breakpoint + + For example, + + `breakpoint 1000 [ fontSize (px 20) ]` + + Will apply the `font-size: 20px` rule at the breakpoint 1000px. + +-} breakpoint : Float -> List Style -> MediaQuery { properties | offsettable : () } breakpoint px s = MediaQuery (Breakpoint px) (Just s) Nothing +{-| Construct a media query for a user preference + + For example, + + `userPreference "(prefers-reduced-motion)" "(prefers-reduced-motion: no-preference)" [ fontSize (px 20) ]` + + Will apply the `font-size: 20px` rule when the user prefers reduced motion. + +-} userPreference : String -> String -> List Style -> MediaQuery properties userPreference on_ off s = MediaQuery (UserPreference on_ off) (Just s) Nothing @@ -148,7 +162,12 @@ narrowMobile = breakpoint 500 -{-| Set styles for reduced motion +{-| Set styles for users who prefer reduced motion + + Generally, you will want to wrap any use of animations/transitions with + + `not (prefersReducedMotion [ ... ])` + -} prefersReducedMotion : List Style -> MediaQuery {} prefersReducedMotion = @@ -156,23 +175,73 @@ prefersReducedMotion = {-| Set styles for high contrast mode + + This media query indicates that the user has FORCED high contrast mode on. + + It is NOT the same as `prefers-color-scheme: high-contrast`. + + The practical difference is that this query targets users who are using high contrast + or inverted colors at the system/browser level (e.g. Windows High Contrast mode, + Mac OS Invert Colors), while `prefers-color-scheme: high-contrast` targets users who + have expressed a preference for high contrast mode, but are otherwise seeing the same + experience as everyone else. + + `prefers-color-scheme: high-contrast` is similar to `prefers-reduced-motion` and + `prefers-color-scheme: dark` in that it is up to us to respect it and provide a + high-contrast experience, while `forced-colors: active` is a signal that the user + is using high contrast mode and intended to be used when there is a need to make + more significant changes or compensate for a suboptimal experience for those users. + -} highContrastMode : List Style -> MediaQuery {} highContrastMode = userPreference "(forced-colors: active)" "(forced-colors: none)" +{-| A record representing the styles to apply at various breakpoints and user preferences. + + This is the internal representation of media queries. It is not recommended to use it directly. + + Instead, use `MediaQuery.fromList` or `MediaQuery.init |> MediaQuery.on ...`. + + `breakpoints` - Dict of breakpoints where the key is the pixel value and the value is a tuple + of (Maybe (List Style), Maybe (List Style)) where the first item is the styles to apply when the + media query is satisfied and the second item is the styles to apply when the media query is NOT + satisfied. + + `userPreferences` - Dict of user preferences where the key is the media expression + (e.g. "(prefers-reduced-motion)") and the value is a list of styles to apply when the + media query is satisfied. + + This is the internal representation of media queries. It is not recommended to use it directly. + +-} type alias ResponsiveStyles = { breakpoints : Dict Float ( Maybe (List Style), Maybe (List Style) ) , userPreferences : Dict String (List Style) } +{-| Initialize a ResponsiveStyles record that can be used to compose media queries. +-} init : ResponsiveStyles init = { breakpoints = Dict.empty, userPreferences = Dict.empty } +{-| Add a MediaQuery to a ResponsiveStyles record. + + This is the primary way to build a ResponsiveStyles record. + + For example, + + ``` + MediaQuery.init + |> MediaQuery.on (MediaQuery.mobile [ Css.paddingTop (Css.px 10) ]) + |> MediaQuery.on (MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ]) + ``` + +-} on : MediaQuery properties -> ResponsiveStyles -> ResponsiveStyles on mq internal = let @@ -202,7 +271,18 @@ on mq internal = } -{-| Build a list of `Css.Style` from a list of media queries. +{-| Build a list of `Css.Style` from a ResponsiveStyles record. + + Styles are output in the following order: + + 1. User preferences + 2. Mobile-first breakpoints (min-width, ascending) + 3. Desktop-first breakpoints (max-width, descending) + + This ordering (at least for the breakpoints) is important to ensure the correct styles are applied. + + CASCADES, BABY! + -} toStyles : ResponsiveStyles -> List Style toStyles { breakpoints, userPreferences } = @@ -226,7 +306,15 @@ toStyles { breakpoints, userPreferences } = ++ List.foldr addViewportQuery [] (Dict.toList breakpoints |> List.sortBy Tuple.first) -{-| Build a single `Css.Style` from a list of media queries. +{-| Build a single `Css.Style` from a ResponsiveStyles record. + + Styles are output in the following order: + + 1. User preferences + 2. Mobile-first breakpoints (min-width, ascending) + 3. Desktop-first breakpoints (max-width, descending) + + This ordering (at least for the breakpoints) is important to ensure the correct styles are applied. This is a convenience function for `Css.batch (toStyles queries)`. @@ -238,35 +326,37 @@ toStyle = {-| Build a single `Css.Style` from a list of media queries. - This is a convenience function for `List.foldl MediaQuery.on MediaQuery.query >> MediaQuery.toStyle` + !!! ARE YOU TRYING TO USE THIS FUNCTION AND GETTING A TYPE ERROR ON YOUR LIST OF MEDIA QUERIES? READ THIS! !!! - In other words, + This module is designed with a pipeline-style API in mind, and uses phantom types to enforce certain restrictions. - ``` - MediaQuery.fromList - [ MediaQuery.mobile [ Css.paddingTop (Css.px 10) ] - , MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ] - ] - ``` + These phantom types prevent using modifiers like `not` and `offset` with media queries that are not designed to accept them. - is the same as + This function is an alias for `List.foldl on init >> toStyle` provided for convenience and the common case. - ``` - MediaQuery.query - |> MediaQuery.on (MediaQuery.mobile [ Css.paddingTop (Css.px 10) ]) - |> MediaQuery.on (MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ]) - |> MediaQuery.toStyle - ``` + If you're stuck, you have 2 options: + + 1. Use 2 independent calls to `MediaQuery.fromList`, one with your breakpoints and one with your user preferences. + + 2. Use the pipeline-style API directly. + + For example, - Why do both of these exist? + ``` + MediaQuery.fromList + [ MediaQuery.mobile [ Css.paddingTop (Css.px 10) ] + , MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ] + ] + ``` - `fromList` is convenient, but restricts the use of phantom types. That is, you can't - create a media query that contains both a breakpoint and a user preference. You can only - create a media query that contains one or the other. + translates to - This usually isn't a concern, but to enforce certain restrictions internally it - makes more sense to design the API around pipeline-style usage and expose this - convenience function for the common case. + ``` + MediaQuery.query + |> MediaQuery.on (MediaQuery.mobile [ Css.paddingTop (Css.px 10) ]) + |> MediaQuery.on (MediaQuery.narrowMobile [ Css.paddingTop (Css.px 20) ]) + |> MediaQuery.toStyle + ``` -} fromList : List (MediaQuery properties) -> Style From 3e5607272551f2d4b158ed2969b3bf6ac90b1157 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 14:53:16 -0500 Subject: [PATCH 21/58] refactor for new api --- .../src/UsageExamples/MediaQuery.elm | 28 +++++------ component-catalog/tests/MediaQuerySpec.elm | 14 +++--- src/Nri/Ui/MediaQuery/V2.elm | 25 +++++++--- src/Nri/Ui/Switch/V3.elm | 2 +- src/Nri/Ui/Tabs/V8.elm | 8 ++-- src/Nri/Ui/Tooltip/V3.elm | 47 +++++++++---------- 6 files changed, 69 insertions(+), 55 deletions(-) diff --git a/component-catalog/src/UsageExamples/MediaQuery.elm b/component-catalog/src/UsageExamples/MediaQuery.elm index a2f4be74b..be82ea7e1 100644 --- a/component-catalog/src/UsageExamples/MediaQuery.elm +++ b/component-catalog/src/UsageExamples/MediaQuery.elm @@ -46,19 +46,19 @@ viewCascade = Container.view [ Container.buttony , Container.css - (Css.displayFlex - :: Css.property "justify-content" "space-evenly" - :: before "Default" - :: after "Default" - :: MediaQuery.toStyles - [ MediaQuery.narrowMobile [ before "Narrow Mobile" ] - , MediaQuery.quizEngineMobile [ before "Quiz Engine Mobile" ] - , MediaQuery.mobile [ before "Mobile" ] - , MediaQuery.not MediaQuery.narrowMobile [ after "Not Narrow Mobile" ] - , MediaQuery.not MediaQuery.quizEngineMobile [ after "Not Quiz Engine Mobile" ] - , MediaQuery.not MediaQuery.mobile [ after "Not Mobile" ] - ] - ) + [ Css.displayFlex + , Css.property "justify-content" "space-evenly" + , before "Default" + , after "Default" + , MediaQuery.fromList + [ MediaQuery.narrowMobile [ before "Narrow Mobile" ] + , MediaQuery.quizEngineMobile [ before "Quiz Engine Mobile" ] + , MediaQuery.mobile [ before "Mobile" ] + , MediaQuery.not MediaQuery.narrowMobile [ after "Not Narrow Mobile" ] + , MediaQuery.not MediaQuery.quizEngineMobile [ after "Not Quiz Engine Mobile" ] + , MediaQuery.not MediaQuery.mobile [ after "Not Mobile" ] + ] + ] , Container.plaintext " | " ] @@ -75,7 +75,7 @@ viewIndividually = viewBreakpoint name breakpoint = Container.view [ Container.buttony - , Container.css <| hidden :: MediaQuery.toStyles [ breakpoint [ visible ] ] + , Container.css [ hidden, MediaQuery.fromList [ breakpoint [ visible ] ] ] , Container.plaintext name ] in diff --git a/component-catalog/tests/MediaQuerySpec.elm b/component-catalog/tests/MediaQuerySpec.elm index 56d8af549..fd41ebc25 100644 --- a/component-catalog/tests/MediaQuerySpec.elm +++ b/component-catalog/tests/MediaQuerySpec.elm @@ -19,7 +19,7 @@ import Test.Html.Query as Query import Test.Html.Selector as Selector -allQueriesRandomOrderFuzzer : Fuzzer (List MediaQuery) +allQueriesRandomOrderFuzzer : Fuzzer (List (MediaQuery { offsettable : () })) allQueriesRandomOrderFuzzer = Fuzz.shuffledList [ MediaQuery.not narrowMobile [ order (int 1) ] @@ -38,7 +38,7 @@ suite = describe "MediaQuery.V2" [ fuzz allQueriesRandomOrderFuzzer "it puts breakpoint queries in the correct order" <| \queries -> - div [ css <| MediaQuery.toStyles queries ] [] + div [ css [ MediaQuery.fromList queries ] ] [] |> toUnstyled |> Query.fromHtml |> Query.find [ Selector.tag "style" ] @@ -57,11 +57,12 @@ suite = , test "it works with user preference queries" <| \() -> div - [ css <| - MediaQuery.toStyles + [ css + [ MediaQuery.fromList [ highContrastMode [ borderWidth (px 1) ] , MediaQuery.not highContrastMode [ borderWidth (px 2) ] ] + ] ] [] |> toUnstyled @@ -74,11 +75,12 @@ suite = , test "it works with duplicated queries" <| \() -> div - [ css <| - MediaQuery.toStyles + [ css + [ MediaQuery.fromList [ prefersReducedMotion [ borderWidth (px 1) ] , prefersReducedMotion [ fontSize (px 1) ] ] + ] ] [] |> toUnstyled diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index ec8b687e9..a45cc3b03 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -1,9 +1,10 @@ module Nri.Ui.MediaQuery.V2 exposing - ( MediaQuery, fromList, toStyles, toStyle + ( MediaQuery, fromList, init, on, toStyles, toStyle , not, offset , breakpoint , mobile, narrowMobile, quizEngineMobile , prefersReducedMotion, highContrastMode + , ResponsiveStyles ) {-| Patch changes: @@ -76,6 +77,18 @@ type Target | UserPreference String String +type alias Properties = + {} + + +type alias Offsettable properties = + { properties | offsettable : () } + + +type alias BreakpointProperties = + Offsettable Properties + + {-| Negate a MediaQuery Note: This is not a true "not" media query. Rather, this module will use whatever @@ -103,7 +116,7 @@ not mq s = No effect if used with a non-breakpoint media query. -} -offset : Float -> (List Style -> MediaQuery { properties | offsettable : () }) -> List Style -> MediaQuery { properties | offsettable : () } +offset : Float -> (List Style -> MediaQuery (Offsettable properties)) -> List Style -> MediaQuery (Offsettable properties) offset px mq s = case mq s of MediaQuery (Breakpoint orig) _ _ -> @@ -122,7 +135,7 @@ offset px mq s = Will apply the `font-size: 20px` rule at the breakpoint 1000px. -} -breakpoint : Float -> List Style -> MediaQuery { properties | offsettable : () } +breakpoint : Float -> List Style -> MediaQuery BreakpointProperties breakpoint px s = MediaQuery (Breakpoint px) (Just s) Nothing @@ -143,21 +156,21 @@ userPreference on_ off s = {-| Set styles for mobile and smaller devices (<= 1000px) -} -mobile : List Style -> MediaQuery { properties | offsettable : () } +mobile : List Style -> MediaQuery BreakpointProperties mobile = breakpoint 1000 {-| Set styles for quiz engine mobile and smaller devices (<= 750px) -} -quizEngineMobile : List Style -> MediaQuery { properties | offsettable : () } +quizEngineMobile : List Style -> MediaQuery BreakpointProperties quizEngineMobile = breakpoint 750 {-| Set styles for narrow mobile and smaller devices (<= 500px) -} -narrowMobile : List Style -> MediaQuery { properties | offsettable : () } +narrowMobile : List Style -> MediaQuery BreakpointProperties narrowMobile = breakpoint 500 diff --git a/src/Nri/Ui/Switch/V3.elm b/src/Nri/Ui/Switch/V3.elm index af2055f9e..dc0ff2e2c 100644 --- a/src/Nri/Ui/Switch/V3.elm +++ b/src/Nri/Ui/Switch/V3.elm @@ -395,7 +395,7 @@ stroke color = transition : String -> Css.Style transition transitionRules = - MediaQuery.toStyle + MediaQuery.fromList [ MediaQuery.not MediaQuery.prefersReducedMotion [ Css.property "transition" transitionRules ] ] diff --git a/src/Nri/Ui/Tabs/V8.elm b/src/Nri/Ui/Tabs/V8.elm index 4c49b5cac..cf8895cd0 100644 --- a/src/Nri/Ui/Tabs/V8.elm +++ b/src/Nri/Ui/Tabs/V8.elm @@ -282,7 +282,7 @@ view { focusAndSelect, selected } attrs tabs = , Css.borderBottomStyle Css.solid , Css.borderBottomColor Colors.navy , Fonts.baseFont - , MediaQuery.toStyle + , MediaQuery.fromList (Maybe.values [ Just (MediaQuery.narrowMobile @@ -367,7 +367,7 @@ stylesTabsAligned config = , Css.displayFlex , Css.flexGrow (Css.int 1) , Css.padding Css.zero - , MediaQuery.toStyle [ MediaQuery.narrowMobile [ Css.flexDirection Css.column ] ] + , MediaQuery.fromList [ MediaQuery.narrowMobile [ Css.flexDirection Css.column ] ] ] @@ -474,7 +474,7 @@ tabStyles customSpacing pageBackgroundColor_ index isSelected = margin = Maybe.withDefault 10 customSpacing / 2 in - baseStyles + MediaQuery.fromList [ MediaQuery.narrowMobile <| narrowMobileStylesTab ++ narrowMobileStylesDynamic ] + :: baseStyles ++ stylesTab ++ stylesDynamic - ++ MediaQuery.toStyles [ MediaQuery.narrowMobile <| narrowMobileStylesTab ++ narrowMobileStylesDynamic ] diff --git a/src/Nri/Ui/Tooltip/V3.elm b/src/Nri/Ui/Tooltip/V3.elm index 557db2062..e54de2b55 100644 --- a/src/Nri/Ui/Tooltip/V3.elm +++ b/src/Nri/Ui/Tooltip/V3.elm @@ -126,7 +126,7 @@ type alias Tooltip msg = , attributes : List (Html.Attribute Never) , containerStyles : List Style , tooltipStyleOverrides : List Style - , responsiveTooltipStyleOverrides : List MediaQuery + , responsiveTooltipStyleOverrides : MediaQuery.ResponsiveStyles , width : Width , padding : Padding , trigger : Maybe (Trigger msg) @@ -161,7 +161,7 @@ buildAttributes = , Css.position Css.relative ] , tooltipStyleOverrides = [] - , responsiveTooltipStyleOverrides = [] + , responsiveTooltipStyleOverrides = MediaQuery.init , width = Exactly 320 , padding = NormalPadding , trigger = Nothing @@ -645,11 +645,9 @@ css tooltipStyleOverrides = {-| Set some conditional custom styles on the tooltip according to a media query. -} -responsiveCss : List MediaQuery -> Attribute msg -responsiveCss styles = - -- Don't do the `MediaQuery.toStyles` here, because we want calls to multiple - -- `responsiveCss` to be additive and processed by MediaQuery together. - Attribute (\config -> { config | responsiveTooltipStyleOverrides = config.responsiveTooltipStyleOverrides ++ styles }) +responsiveCss : MediaQuery properties -> Attribute msg +responsiveCss mq = + Attribute (\config -> { config | responsiveTooltipStyleOverrides = MediaQuery.on mq config.responsiveTooltipStyleOverrides }) {-| Set styles that will only apply if the viewport is wider than NRI's mobile breakpoint. @@ -660,8 +658,8 @@ Equivalent to: -} notMobileCss : List Style -> Attribute msg -notMobileCss styles = - responsiveCss [ MediaQuery.not MediaQuery.mobile styles ] +notMobileCss = + responsiveCss << MediaQuery.not MediaQuery.mobile {-| Set styles that will only apply if the viewport is narrower than NRI's mobile breakpoint. @@ -672,8 +670,8 @@ Equivalent to: -} mobileCss : List Style -> Attribute msg -mobileCss styles = - responsiveCss [ MediaQuery.mobile styles ] +mobileCss = + responsiveCss << MediaQuery.mobile {-| Set styles that will only apply if the viewport is narrower than NRI's quiz-engine-specific mobile breakpoint. @@ -684,8 +682,8 @@ Equivalent to: -} quizEngineMobileCss : List Style -> Attribute msg -quizEngineMobileCss styles = - responsiveCss [ MediaQuery.quizEngineMobile styles ] +quizEngineMobileCss = + responsiveCss << MediaQuery.quizEngineMobile {-| Set styles that will only apply if the viewport is narrower than NRI's narrow mobile breakpoint. @@ -696,8 +694,8 @@ Equivalent to: -} narrowMobileCss : List Style -> Attribute msg -narrowMobileCss styles = - responsiveCss [ MediaQuery.narrowMobile styles ] +narrowMobileCss = + responsiveCss << MediaQuery.narrowMobile {-| Use this helper to add custom attributes. @@ -1061,10 +1059,10 @@ viewTooltip tooltipId config = ( direction , alignment , { acc - | positionTooltip = mediaQuery (positionTooltip direction alignment) :: acc.positionTooltip - , hoverAreaForDirection = mediaQuery (hoverAreaForDirection direction) :: acc.hoverAreaForDirection - , positioning = mediaQuery (positioning direction alignment) :: acc.positioning - , applyTail = mediaQuery (applyTail direction) :: acc.applyTail + | positionTooltip = MediaQuery.on (mediaQuery (positionTooltip direction alignment)) acc.positionTooltip + , hoverAreaForDirection = MediaQuery.on (mediaQuery (hoverAreaForDirection direction)) acc.hoverAreaForDirection + , positioning = MediaQuery.on (mediaQuery (positioning direction alignment)) acc.positioning + , applyTail = MediaQuery.on (mediaQuery (applyTail direction)) acc.applyTail } ) @@ -1073,10 +1071,10 @@ viewTooltip tooltipId config = ) ( config.direction , config.alignment - , { positionTooltip = [] - , hoverAreaForDirection = [] - , positioning = [] - , applyTail = [] + , { positionTooltip = MediaQuery.init + , hoverAreaForDirection = MediaQuery.init + , positioning = MediaQuery.init + , applyTail = MediaQuery.init } ) [ ( MediaQuery.mobile, config.mobileDirection, config.mobileAlignment ) @@ -1153,7 +1151,8 @@ viewTooltip tooltipId config = ] ++ positioning config.direction config.alignment ++ applyTail config.direction - ++ MediaQuery.toStyles (mediaQueries.positioning ++ mediaQueries.applyTail) + ++ MediaQuery.toStyles mediaQueries.positioning + ++ MediaQuery.toStyles mediaQueries.applyTail ++ config.tooltipStyleOverrides ) From 8be17ae735748c8718c0ae4d18f9259c2bf41148 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 15:16:20 -0500 Subject: [PATCH 22/58] Refactor Nri.Ui.SortableTable --- src/Nri/Ui/SortableTable/V4.elm | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Nri/Ui/SortableTable/V4.elm b/src/Nri/Ui/SortableTable/V4.elm index 68eed8e7d..82981628b 100644 --- a/src/Nri/Ui/SortableTable/V4.elm +++ b/src/Nri/Ui/SortableTable/V4.elm @@ -32,7 +32,7 @@ import Nri.Ui.CssVendorPrefix.V1 as CssVendorPrefix import Nri.Ui.Fonts.V1 as Fonts import Nri.Ui.Html.Attributes.V2 exposing (maybe) import Nri.Ui.Html.V3 exposing (viewJust) -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import Nri.Ui.Svg.V1 import Nri.Ui.Table.V7 as Table exposing (SortDirection(..)) import Nri.Ui.UiIcon.V1 @@ -119,17 +119,18 @@ consJust maybe_ list = stickyConfigStyles : StickyConfig -> List Style stickyConfigStyles { topOffset, zIndex, pageBackgroundColor, hoverZIndex } = - [ Css.Media.withMedia - [ MediaQuery.notMobile ] - [ Css.Global.children - [ Css.Global.thead - (consJust (Maybe.map (\index -> Css.hover [ Css.zIndex (Css.int index) ]) hoverZIndex) - [ Css.position Css.sticky - , Css.top (Css.px topOffset) - , Css.zIndex (Css.int zIndex) - , Css.backgroundColor pageBackgroundColor - ] - ) + [ MediaQuery.fromList + [ MediaQuery.not MediaQuery.mobile + [ Css.Global.children + [ Css.Global.thead + (consJust (Maybe.map (\index -> Css.hover [ Css.zIndex (Css.int index) ]) hoverZIndex) + [ Css.position Css.sticky + , Css.top (Css.px topOffset) + , Css.zIndex (Css.int zIndex) + , Css.backgroundColor pageBackgroundColor + ] + ) + ] ] ] ] From 0c242ca45922ea2055a40d011b9c580a4869c521 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 15:24:16 -0500 Subject: [PATCH 23/58] Refactor Nri.Ui.SideNav --- src/Nri/Ui/SideNav/V5.elm | 77 +++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/src/Nri/Ui/SideNav/V5.elm b/src/Nri/Ui/SideNav/V5.elm index 036ebf0d7..54299c766 100644 --- a/src/Nri/Ui/SideNav/V5.elm +++ b/src/Nri/Ui/SideNav/V5.elm @@ -80,7 +80,7 @@ import Nri.Ui.FocusRing.V1 as FocusRing import Nri.Ui.Fonts.V1 as Fonts import Nri.Ui.Html.Attributes.V2 as AttributesExtra import Nri.Ui.Html.V3 exposing (viewJust) -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import Nri.Ui.Pennant.V3 as Pennant import Nri.Ui.Svg.V1 as Svg exposing (Svg) import Nri.Ui.Tooltip.V3 as Tooltip @@ -151,6 +151,7 @@ type alias NavAttributeConfig msg = { navLabel : Maybe String , navId : Maybe String , css : List Style + , responsiveCss : MediaQuery.ResponsiveStyles , collapsible : Maybe (CollapsibleConfig msg) } @@ -160,6 +161,7 @@ defaultNavAttributeConfig = { navLabel = Nothing , navId = Nothing , css = [] + , responsiveCss = MediaQuery.init , collapsible = Nothing } @@ -196,22 +198,27 @@ navCss styles = NavAttribute (\config -> { config | css = List.append config.css styles }) +responsiveNavCss : MediaQuery.MediaQuery properties -> NavAttribute msg +responsiveNavCss mq = + NavAttribute (\config -> { config | responsiveCss = MediaQuery.on mq config.responsiveCss }) + + {-| -} navNotMobileCss : List Style -> NavAttribute msg -navNotMobileCss styles = - navCss [ Css.Media.withMedia [ MediaQuery.notMobile ] styles ] +navNotMobileCss = + responsiveNavCss << MediaQuery.not MediaQuery.mobile {-| -} navMobileCss : List Style -> NavAttribute msg -navMobileCss styles = - navCss [ Css.Media.withMedia [ MediaQuery.mobile ] styles ] +navMobileCss = + responsiveNavCss << MediaQuery.mobile {-| -} navQuizEngineMobileCss : List Style -> NavAttribute msg -navQuizEngineMobileCss styles = - navCss [ Css.Media.withMedia [ MediaQuery.quizEngineMobile ] styles ] +navQuizEngineMobileCss = + responsiveNavCss << MediaQuery.quizEngineMobile {-| -} @@ -267,21 +274,31 @@ view config navAttributes entries = , backgroundColor Colors.gray96 , alignSelf flexStart , width (px 300) - , Css.Media.withMedia [ MediaQuery.mobile ] - [ Css.property "flex-basis" "unset" - , marginRight Css.zero - , marginBottom (Css.px 20) - , width (pct 100) - , case Maybe.map .isOpen appliedNavAttributes.collapsible of - Just _ -> - Css.padding (Css.px 10) - - Nothing -> - Css.batch [] + , MediaQuery.fromList + [ MediaQuery.mobile + [ Css.property "flex-basis" "unset" + , marginRight Css.zero + , marginBottom (Css.px 20) + , width (pct 100) + , case Maybe.map .isOpen appliedNavAttributes.collapsible of + Just _ -> + Css.padding (Css.px 10) + + Nothing -> + Css.batch [] + ] ] ] in - div [ Attributes.css (defaultCss ++ appliedNavAttributes.css) ] + div + [ Attributes.css + (List.concat + [ defaultCss + , appliedNavAttributes.css + , MediaQuery.toStyles appliedNavAttributes.responsiveCss + ] + ) + ] [ viewSkipLink config.onSkipNav , case entries of [] -> @@ -347,15 +364,19 @@ viewOpenCloseButton sidenavId navLabel_ currentEntry { isOpen, toggle, isTooltip Tooltip.onRight , Tooltip.containerCss [ -- Hide the tooltip for mobile. We'll display static text instead - Css.Media.withMedia [ MediaQuery.mobile ] - [ Css.display Css.none ] + MediaQuery.fromList + [ MediaQuery.mobile + [ Css.display Css.none ] + ] ] , Tooltip.containerCss (if isOpen then - [ Css.Media.withMedia [ MediaQuery.notMobile ] - [ Css.position Css.absolute - , Css.top (Css.px 10) - , Css.right (Css.px 10) + [ MediaQuery.fromList + [ MediaQuery.not MediaQuery.mobile + [ Css.position Css.absolute + , Css.top (Css.px 10) + , Css.right (Css.px 10) + ] ] ] @@ -369,8 +390,10 @@ viewOpenCloseButton sidenavId navLabel_ currentEntry { isOpen, toggle, isTooltip [ Attributes.css [ -- Hide the plain button/static text if not on the mobile view Css.display Css.none - , Css.Media.withMedia [ MediaQuery.mobile ] - [ Css.displayFlex ] + , MediaQuery.fromList + [ MediaQuery.mobile + [ Css.displayFlex ] + ] ] ] [ trigger [] From 5372a6c787708b573aefa3a7e34317fd1fb40e21 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 15:24:56 -0500 Subject: [PATCH 24/58] elm-review stuff --- src/Nri/Ui/SideNav/V5.elm | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Nri/Ui/SideNav/V5.elm b/src/Nri/Ui/SideNav/V5.elm index 54299c766..5cfe22bfc 100644 --- a/src/Nri/Ui/SideNav/V5.elm +++ b/src/Nri/Ui/SideNav/V5.elm @@ -65,7 +65,6 @@ import Accessibility.Styled.Style as Style import ClickableAttributes exposing (ClickableAttributes) import Css exposing (..) import Css.Global -import Css.Media import Html.Styled import Html.Styled.Attributes as Attributes import Html.Styled.Events as Events @@ -434,17 +433,16 @@ viewNav sidenavId config appliedNavAttributes entries showNav = ] |> List.filterMap identity ) - (viewJust (viewOpenCloseButton sidenavId appliedNavAttributes.navLabel currentEntry) appliedNavAttributes.collapsible - :: [ ul - [ Attributes.css - [ listStyle none - , padding zero - , margin zero - ] - ] - (List.map (viewSidebarEntry config entryStyles) entries) - ] - ) + [ viewJust (viewOpenCloseButton sidenavId appliedNavAttributes.navLabel currentEntry) appliedNavAttributes.collapsible + , ul + [ Attributes.css + [ listStyle none + , padding zero + , margin zero + ] + ] + (List.map (viewSidebarEntry config entryStyles) entries) + ] viewSkipLink : msg -> Html msg @@ -581,7 +579,7 @@ anyLinkDescendants f children = Html _ -> False - CompactGroup children_ groupConfig -> + CompactGroup children_ _ -> anyLinkDescendants f children_ ) children From bf311383299bf29e210af72499dc4be7cac00862 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 15:59:34 -0500 Subject: [PATCH 25/58] Refactor Spacing.V1 (feels bad, man) --- src/Nri/Ui/MediaQuery/Internal.elm | 13 ++++++++++++- src/Nri/Ui/MediaQuery/V2.elm | 7 ++++--- src/Nri/Ui/Spacing/V1.elm | 14 +++++++------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/Nri/Ui/MediaQuery/Internal.elm b/src/Nri/Ui/MediaQuery/Internal.elm index 041920b95..df044a49c 100644 --- a/src/Nri/Ui/MediaQuery/Internal.elm +++ b/src/Nri/Ui/MediaQuery/Internal.elm @@ -1,5 +1,16 @@ module Nri.Ui.MediaQuery.Internal exposing (..) -mobile = +mobileBreakpoint : Float +mobileBreakpoint = 1000 + + +quizEngineMobileBreakpoint : Float +quizEngineMobileBreakpoint = + 750 + + +narrowMobileBreakpoint : Float +narrowMobileBreakpoint = + 500 diff --git a/src/Nri/Ui/MediaQuery/V2.elm b/src/Nri/Ui/MediaQuery/V2.elm index a45cc3b03..880069d4e 100644 --- a/src/Nri/Ui/MediaQuery/V2.elm +++ b/src/Nri/Ui/MediaQuery/V2.elm @@ -59,6 +59,7 @@ import Css exposing (Style, target) import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMedia, withMediaQuery) import Dict exposing (Dict) import Maybe.Extra as Maybe +import Nri.Ui.MediaQuery.Internal exposing (..) {-| Type representing a media query. @@ -158,21 +159,21 @@ userPreference on_ off s = -} mobile : List Style -> MediaQuery BreakpointProperties mobile = - breakpoint 1000 + breakpoint mobileBreakpoint {-| Set styles for quiz engine mobile and smaller devices (<= 750px) -} quizEngineMobile : List Style -> MediaQuery BreakpointProperties quizEngineMobile = - breakpoint 750 + breakpoint quizEngineMobileBreakpoint {-| Set styles for narrow mobile and smaller devices (<= 500px) -} narrowMobile : List Style -> MediaQuery BreakpointProperties narrowMobile = - breakpoint 500 + breakpoint narrowMobileBreakpoint {-| Set styles for users who prefer reduced motion diff --git a/src/Nri/Ui/Spacing/V1.elm b/src/Nri/Ui/Spacing/V1.elm index 53ea8127d..05be050f7 100644 --- a/src/Nri/Ui/Spacing/V1.elm +++ b/src/Nri/Ui/Spacing/V1.elm @@ -37,7 +37,7 @@ module Nri.Ui.Spacing.V1 exposing import Css exposing (Style) import Css.Media as Media -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.Internal exposing (mobileBreakpoint, narrowMobileBreakpoint, quizEngineMobileBreakpoint) {-| Advanced use only: center content up to a custom page width, with side padding when narrower. @@ -85,7 +85,7 @@ If you have a container that should snap flush to the edges on mobile, this isn' -} centeredContentWithSidePadding : Style centeredContentWithSidePadding = - centeredContentWithSidePaddingAndCustomWidth MediaQuery.mobileBreakpoint + centeredContentWithSidePaddingAndCustomWidth <| Css.px mobileBreakpoint {-| Center content with a max width of the mobile breakpoint. @@ -95,7 +95,7 @@ This style does not add side padding on mobile, which means that this can be use -} centeredContent : Style centeredContent = - centeredContentWithCustomWidth MediaQuery.mobileBreakpoint + centeredContentWithCustomWidth <| Css.px mobileBreakpoint {-| Use this style on Quiz Engine pages. @@ -107,7 +107,7 @@ If you have a container that should snap flush to the edges on mobile, this isn' -} centeredQuizEngineContentWithSidePadding : Style centeredQuizEngineContentWithSidePadding = - centeredContentWithSidePaddingAndCustomWidth MediaQuery.quizEngineBreakpoint + centeredContentWithSidePaddingAndCustomWidth <| Css.px quizEngineMobileBreakpoint {-| Use this style on Quiz Engine pages. @@ -119,7 +119,7 @@ This style does not add side padding on mobile, which means that this can be use -} quizEngineCenteredContent : Style quizEngineCenteredContent = - centeredContentWithCustomWidth MediaQuery.quizEngineBreakpoint + centeredContentWithCustomWidth <| Css.px quizEngineMobileBreakpoint {-| Use this style on pages with a narrow (500px) breakpoint. @@ -131,7 +131,7 @@ If you have a container that should snap flush to the edges on mobile, this isn' -} centeredNarrowContentWithSidePadding : Style centeredNarrowContentWithSidePadding = - centeredContentWithSidePaddingAndCustomWidth MediaQuery.narrowMobileBreakpoint + centeredContentWithSidePaddingAndCustomWidth <| Css.px narrowMobileBreakpoint {-| Use this style on pages with a narrow (500px) breakpoint. @@ -143,7 +143,7 @@ This style does not add side padding on mobile, which means that this can be use -} narrowCenteredContent : Style narrowCenteredContent = - centeredContentWithCustomWidth MediaQuery.narrowMobileBreakpoint + centeredContentWithCustomWidth <| Css.px narrowMobileBreakpoint {-| Convenience for adding the appriopriate amount of whitespace on the sides of a full-width container on the page or on the page with side padding. From f3d2ff43ef535254761a33252344985f45247ff8 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:01:48 -0500 Subject: [PATCH 26/58] Refactor Nri.Ui.Modal --- src/Nri/Ui/Modal/V12.elm | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Nri/Ui/Modal/V12.elm b/src/Nri/Ui/Modal/V12.elm index 01eb441f3..c5c3969ab 100644 --- a/src/Nri/Ui/Modal/V12.elm +++ b/src/Nri/Ui/Modal/V12.elm @@ -176,7 +176,7 @@ 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 exposing (viewJust) -import Nri.Ui.MediaQuery.V1 exposing (mobile) +import Nri.Ui.MediaQuery.V2 as MediaQuery exposing (mobile) import Nri.Ui.Shadows.V1 as Shadows import Nri.Ui.UiIcon.V1 as UiIcon import Nri.Ui.WhenFocusLeaves.V2 as WhenFocusLeaves @@ -436,8 +436,10 @@ modalStyles = -- Border , borderRadius (px 20) , Shadows.high - , Css.Media.withMedia [ mobile ] - [ borderRadius zero + , MediaQuery.fromList + [ mobile + [ borderRadius zero + ] ] -- Spacing @@ -468,8 +470,10 @@ titleStyles config = (Css.px 40) titleSidePadding (Css.px 20) - , Css.Media.withMedia [ mobile ] - [ Css.padding3 (Css.px 20) titleSidePadding Css.zero + , MediaQuery.fromList + [ mobile + [ Css.padding3 (Css.px 20) titleSidePadding Css.zero + ] ] , Css.margin Css.zero , Css.fontSize (Css.px 20) From c8505db20d65e267ab8a65f0d8ae40c916f63043 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:16:18 -0500 Subject: [PATCH 27/58] Copy Message.V4 -> Message.V5 --- src/Nri/Ui/Message/V5.elm | 1018 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1018 insertions(+) create mode 100644 src/Nri/Ui/Message/V5.elm diff --git a/src/Nri/Ui/Message/V5.elm b/src/Nri/Ui/Message/V5.elm new file mode 100644 index 000000000..5514cf381 --- /dev/null +++ b/src/Nri/Ui/Message/V5.elm @@ -0,0 +1,1018 @@ +module Nri.Ui.Message.V4%20copy exposing (..)module Nri.Ui.Message.V5 exposing + ( somethingWentWrong + , view, Attribute + , icon, custom, testId, id + , hideIconForMobile, hideIconFor + , css, notMobileCss, mobileCss, quizEngineMobileCss + , tiny, large, banner + , paragraph, plaintext, markdown, html, httpError, codeDetails + , tip, error, alert, success, customTheme + , alertRole, statusRole + , onDismiss + ) + +{-| Changes from V3: + + - adds `statusRole` + - removes `alertDialogRole` + - moves custom attributes and role to a div containing the icon and text + - makes this div programmatically focusable + +Patch changes: + + - adds `notMobileCss`, `mobileCss`, `quizEngineMobileCss` + - adds `hideIconForMobile` and `hideIconFor` + - use `Shadows` + - use internal `Content` module + - make the tiny Message's icon size smaller + +Changes from V2: + + - adds helpers: `custom`,`css`,`icon`,`testId`,`id` + + +# View + +@docs somethingWentWrong +@docs view, Attribute +@docs icon, custom, testId, id + + +# CSS + +@docs hideIconForMobile, hideIconFor +@docs css, notMobileCss, mobileCss, quizEngineMobileCss + + +## Size + +@docs tiny, large, banner + + +## Content + +@docs paragraph, plaintext, markdown, html, httpError, codeDetails + + +## Theme + +@docs tip, error, alert, success, customTheme + + +## Role + +@docs alertRole, statusRole + + +## Actions + +@docs onDismiss + +-} + +import Accessibility.Styled as Html exposing (..) +import Accessibility.Styled.Key exposing (tabbable) +import Accessibility.Styled.Role as Role +import Accessibility.Styled.Style exposing (invisibleStyle) +import Content +import Css exposing (..) +import Css.Global +import Css.Media exposing (MediaQuery) +import Html.Styled.Attributes as Attributes +import Http +import MarkdownStyles +import Nri.Ui +import Nri.Ui.ClickableSvg.V2 as ClickableSvg +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.MediaQuery.V1 as MediaQuery +import Nri.Ui.Shadows.V1 as Shadows +import Nri.Ui.Svg.V1 as NriSvg exposing (Svg) +import Nri.Ui.UiIcon.V1 as UiIcon + + +{-| + + view = + Message.view + [ Message.tip + , Message.markdown "Don't tip too much, or your waitress will **fall over**!" + ] + +-} +view : List (Attribute msg) -> Html msg +view attributes_ = + let + attributes = + configFromAttributes attributes_ + + role = + getRoleAttribute attributes.role + + backgroundColor_ = + getBackgroundColor attributes.size attributes.theme + + color_ = + getColor attributes.size attributes.theme + + icon_ = + getIcon attributes.icon attributes.size attributes.theme + + baseStyles = + [ Fonts.baseFont + , color color_ + , boxSizing borderBox + , Css.batch attributes.customStyles + , Css.Global.descendants + [ Css.Global.a + [ color Colors.azure + , borderBottom3 (px 1) solid Colors.azure + , textDecoration none + , visited [ color Colors.azure ] + ] + ] + ] + in + case attributes.size of + Tiny -> + div + [ ExtraAttributes.nriDescription "Nri-Ui-Message--tiny" + , Attributes.css + (baseStyles + ++ [ paddingTop (px 6) + , paddingBottom (px 8) + ] + ) + ] + [ div + [ Attributes.css + [ displayFlex + , alignItems center + ] + ] + [ div + ([ Attributes.css + [ displayFlex + , alignItems center + , fontSize (px 13) + ] + , tabbable False + ] + ++ role + ++ attributes.customAttributes + ) + [ Nri.Ui.styled div + "Nri-Ui-Message--icon" + [] + [ Attributes.css + [ displayFlex + , alignItems center + ] + ] + [ icon_ + ] + , div [] attributes.content + ] + , case attributes.onDismiss of + Nothing -> + text "" + + Just msg -> + tinyDismissButton msg + ] + , case attributes.codeDetails of + Just details -> + viewCodeDetails details + + Nothing -> + text "" + ] + + Large -> + div + [ ExtraAttributes.nriDescription "Nri-Ui-Message-large" + , Attributes.css + (baseStyles + ++ [ -- Box + borderRadius (px 8) + , backgroundColor_ + , Shadows.low + ] + ) + ] + [ div + [ Attributes.css + [ fontSize (px 15) + , fontWeight (int 600) + , lineHeight (px 21) + , padding (px 20) + , Css.Media.withMedia + [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] + [ padding (px 15) + ] + ] + ] + [ div + [ Attributes.css + [ displayFlex + , alignItems center + ] + ] + [ div + ([ Attributes.css + [ displayFlex + , alignItems center + , property "width" "fit-content" + ] + , tabbable False + ] + ++ role + ++ attributes.customAttributes + ) + [ icon_ + , div + [ Attributes.css + [ minWidth (px 100) + , flexBasis (px 100) + , flexGrow (int 1) + ] + ] + attributes.content + ] + , div [ Attributes.css [ flexGrow (int 1) ] ] [] + , case attributes.onDismiss of + Nothing -> + text "" + + Just msg -> + largeDismissButton msg + ] + , case attributes.codeDetails of + Just details -> + viewCodeDetails details + + Nothing -> + text "" + ] + ] + + Banner -> + div + [ ExtraAttributes.nriDescription "Nri-Ui-Message-banner" + , Attributes.css + (baseStyles + ++ [ backgroundColor_ + ] + ) + ] + [ div + [ Attributes.css + [ fontSize (px 20) + , fontWeight (int 700) + , lineHeight (num 1.4) + , padding (px 20) + , Css.Media.withMedia + [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] + [ fontSize (px 15) + , fontWeight (int 600) + , padding (px 15) + ] + ] + ] + [ div + [ Attributes.css + [ displayFlex + , alignItems center + ] + ] + [ div [ Attributes.css [ flexGrow (int 1) ] ] [] + , div + ([ Attributes.css + [ displayFlex + , alignItems center + , property "width" "fit-content" + ] + , tabbable False + ] + ++ role + ++ attributes.customAttributes + ) + [ icon_ + , Nri.Ui.styled div + "banner-alert-notification" + [ fontSize (px 20) + , fontWeight (int 700) + , lineHeight (num 1.4) + , maxWidth (px 600) + , minWidth (px 100) + , flexShrink (int 1) + , Fonts.baseFont + , Css.Media.withMedia + [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] + [ fontSize (px 15) + , fontWeight (int 600) + ] + ] + [] + attributes.content + ] + , div [ Attributes.css [ flexGrow (int 1) ] ] [] + , case attributes.onDismiss of + Nothing -> + text "" + + Just msg -> + bannerDismissButton msg + ] + , case attributes.codeDetails of + Just details -> + viewCodeDetails details + + Nothing -> + text "" + ] + ] + + +{-| Shows an appropriate error message for when something unhandled happened. + + view maybeDetailedErrorMessage = + viewMaybe Message.somethingWentWrong maybeDetailedErrorMessage + +-} +somethingWentWrong : String -> Html msg +somethingWentWrong errorMessageForEngineers = + view + [ tiny + , error + , alertRole + , plaintext somethingWentWrongMessage + , codeDetails errorMessageForEngineers + ] + + +somethingWentWrongMessage : String +somethingWentWrongMessage = + "Sorry, something went wrong. Please try again later." + + +viewCodeDetails : String -> Html msg +viewCodeDetails errorMessageForEngineers = + details [] + [ summary + [ Attributes.css + [ Fonts.baseFont + , fontSize (px 14) + , color Colors.gray45 + ] + ] + [ text "Details for NoRedInk engineers" ] + , code + [ Attributes.css + [ display block + , whiteSpace preWrap + , overflowWrap breakWord + , color Colors.gray45 + , backgroundColor Colors.gray96 + , border3 (px 1) solid Colors.gray92 + , borderRadius (px 3) + , padding2 (px 2) (px 4) + , fontSize (px 12) + , fontFamily monospace + ] + ] + [ text errorMessageForEngineers ] + ] + + +{-| Shows a tiny alert message. We commonly use these for validation errors and small hints to users. + + Message.view [ Message.tiny ] + +This is the default size for a Message. + +-} +tiny : Attribute msg +tiny = + Attribute <| \config -> { config | size = Tiny } + + +{-| Shows a large alert or callout message. We commonly use these for highlighted tips, instructions, or asides in page copy. + + Message.view [ Message.large ] + +-} +large : Attribute msg +large = + Attribute <| \config -> { config | size = Large } + + +{-| Shows a banner alert message. This is even more prominent than `Message.large`. +We commonly use these for flash messages at the top of pages. + + Message.view [ Message.banner ] + +-} +banner : Attribute msg +banner = + Attribute <| \config -> { config | size = Banner } + + +{-| Provide a plain-text string. +-} +plaintext : String -> Attribute msg +plaintext = + Attribute << Content.plaintext + + +{-| Provide a plain-text string that will be put into a paragraph tag, with the default margin removed. +-} +paragraph : String -> Attribute msg +paragraph = + Attribute << Content.paragraph + + +{-| Provide a string that will be rendered as markdown. +-} +markdown : String -> Attribute msg +markdown content = + Attribute <| + \config -> + { config + | content = Content.markdownContent content + , customStyles = MarkdownStyles.anchorAndButton ++ config.customStyles + } + + +{-| Provide a list of custom HTML. +-} +html : List (Html msg) -> Attribute msg +html = + Attribute << Content.html + + +{-| Provide an HTTP error, which will be translated to user-friendly text. +-} +httpError : Http.Error -> Attribute msg +httpError error_ = + let + ( codeDetails_, content ) = + case error_ of + Http.BadUrl url -> + ( Just ("Bad url: " ++ url) + , [ text somethingWentWrongMessage ] + ) + + Http.Timeout -> + ( Nothing + , [ text "This request took too long to complete." ] + ) + + Http.NetworkError -> + ( Nothing + , [ text "Something went wrong, and we think the problem is probably with your internet connection." ] + ) + + Http.BadStatus 401 -> + ( Nothing + , [ text "You were logged out. Please log in again to continue working." ] + ) + + Http.BadStatus 404 -> + ( Nothing + , [ text "We couldn’t find that!" ] + ) + + Http.BadStatus status -> + ( Just ("Bad status: " ++ String.fromInt status) + , [ text somethingWentWrongMessage ] + ) + + Http.BadBody body -> + ( Just body + , [ text somethingWentWrongMessage ] + ) + in + Attribute <| \config -> { config | content = content, codeDetails = codeDetails_ } + + +{-| Details for Engineers + +Will be rendered in a closed details box + +-} +codeDetails : String -> Attribute msg +codeDetails code = + Attribute <| \config -> { config | codeDetails = Just code } + + +{-| This is the default theme for a Message. +-} +tip : Attribute msg +tip = + Attribute <| \config -> { config | theme = Tip } + + +{-| -} +error : Attribute msg +error = + Attribute <| \config -> { config | theme = Error } + + +{-| -} +alert : Attribute msg +alert = + Attribute <| \config -> { config | theme = Alert } + + +{-| -} +success : Attribute msg +success = + Attribute <| \config -> { config | theme = Success } + + +{-| -} +customTheme : { color : Color, backgroundColor : Color } -> Attribute msg +customTheme custom_ = + Attribute <| \config -> { config | theme = Custom custom_ } + + +{-| -} +icon : Svg -> Attribute msg +icon icon_ = + Attribute <| \config -> { config | icon = Just icon_ } + + +{-| 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 Never) -> Attribute msg +custom attributes = + Attribute <| + \config -> + { config + | customAttributes = List.append config.customAttributes attributes + } + + +{-| -} +testId : String -> Attribute msg +testId id_ = + custom [ ExtraAttributes.testId id_ ] + + +{-| -} +id : String -> Attribute msg +id id_ = + custom [ Attributes.id id_ ] + + +{-| -} +hideIconForMobile : Attribute msg +hideIconForMobile = + hideIconFor MediaQuery.mobile + + +{-| -} +hideIconFor : MediaQuery -> Attribute msg +hideIconFor mediaQuery = + css + [ Css.Media.withMedia [ mediaQuery ] + [ Css.Global.descendants + [ ExtraAttributes.nriDescriptionSelector messageIconDescription + [ invisibleStyle + ] + ] + ] + ] + + +{-| -} +css : List Style -> Attribute msg +css styles = + Attribute <| + \config -> + { config + | customStyles = List.append config.customStyles styles + } + + +{-| Equivalent to: + + Message.css + [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.notMobile ] styles ] + +-} +notMobileCss : List Style -> Attribute msg +notMobileCss styles = + css [ Css.Media.withMedia [ MediaQuery.notMobile ] styles ] + + +{-| Equivalent to: + + Message.css + [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.mobile ] styles ] + +-} +mobileCss : List Style -> Attribute msg +mobileCss styles = + css [ Css.Media.withMedia [ MediaQuery.mobile ] styles ] + + +{-| Equivalent to: + + Message.css + [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.quizEngineMobile ] styles ] + +-} +quizEngineMobileCss : List Style -> Attribute msg +quizEngineMobileCss styles = + css [ Css.Media.withMedia [ MediaQuery.quizEngineMobile ] styles ] + + +{-| Adds a dismiss ("X" icon) to a message which will produce the given `msg` when clicked. +-} +onDismiss : msg -> Attribute msg +onDismiss msg = + Attribute <| \config -> { config | onDismiss = Just msg } + + +{-| Use this attribute when a user's immediate attention on the Message is required. + +For example, use this attribute when: + +> - An invalid value was entered into a form field +> - The user's login session is about to expire +> - The connection to the server was lost, local changes will not be saved + +-- Excerpted from [Using the alert role MDN docs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_alert_role) + +-} +alertRole : Attribute msg +alertRole = + Attribute <| \config -> { config | role = Just AlertRole } + + +{-| Use this attribute when a user's immediate attention on the Message is NOT required. + +This means that a `status` Message has a lower priority than an `alert` Message. + +-} +statusRole : Attribute msg +statusRole = + Attribute <| \config -> { config | role = Just StatusRole } + + + +-- +-- PRIVATE +-- + + +{-| Construct an `Attribute` using a helper like `onDismiss` or `alert`. +-} +type Attribute msg + = Attribute (BannerConfig msg -> BannerConfig msg) + + +{-| PRIVATE +-} +type alias BannerConfig msg = + { onDismiss : Maybe msg + , role : Maybe Role + , content : List (Html msg) + , codeDetails : Maybe String + , theme : Theme + , size : Size + , icon : Maybe Svg + , customAttributes : List (Html.Attribute Never) + , customStyles : List Style + } + + +{-| PRIVATE +-} +configFromAttributes : List (Attribute msg) -> BannerConfig msg +configFromAttributes attr = + List.foldl (\(Attribute set) -> set) + { onDismiss = Nothing + , role = Nothing + , content = [] + , codeDetails = Nothing + , theme = Tip + , size = Tiny + , icon = Nothing + , customAttributes = [] + , customStyles = [] + } + attr + + + +-- Size + + +type Size + = Tiny + | Large + | Banner + + + +-- Themes + + +{-| `Error` / `Alert` / `Tip` / `Success` +-} +type Theme + = Error + | Alert + | Tip + | Success + | Custom { color : Color, backgroundColor : Color } + + +getColor : Size -> Theme -> Color +getColor size theme = + case theme of + Custom { color } -> + color + + Error -> + case size of + Tiny -> + Colors.purpleDark + + _ -> + Colors.purpleDark + + Alert -> + case size of + Tiny -> + Colors.redDark + + _ -> + Colors.redDark + + Tip -> + Colors.navy + + Success -> + Colors.greenDarkest + + +getBackgroundColor : Size -> Theme -> Style +getBackgroundColor size theme = + case ( size, theme ) of + ( Tiny, _ ) -> + Css.batch [] + + ( Large, Tip ) -> + Css.backgroundColor Colors.sunshine + + ( Banner, Tip ) -> + Css.backgroundColor Colors.sunshine + + ( _, Error ) -> + Css.backgroundColor Colors.purpleLight + + ( _, Alert ) -> + Css.backgroundColor Colors.sunshine + + ( _, Success ) -> + Css.backgroundColor Colors.greenLightest + + ( _, Custom { backgroundColor } ) -> + Css.backgroundColor backgroundColor + + +getIcon : Maybe Svg -> Size -> Theme -> Html msg +getIcon customIcon size theme = + let + ( iconSize, marginRight ) = + case size of + Tiny -> + ( px 18, Css.marginRight (Css.px 5) ) + + Large -> + ( px 35, Css.marginRight (Css.px 10) ) + + Banner -> + ( px 35, Css.marginRight (Css.px 10) ) + in + case ( customIcon, theme ) of + ( Nothing, Error ) -> + UiIcon.exclamation + |> NriSvg.withColor Colors.purple + |> NriSvg.withWidth iconSize + |> NriSvg.withHeight iconSize + |> NriSvg.withCss [ marginRight, Css.flexShrink Css.zero ] + |> NriSvg.withLabel "Error" + |> NriSvg.toHtml + + ( Nothing, Alert ) -> + let + color = + case size of + Tiny -> + Colors.red + + _ -> + Colors.red + in + UiIcon.exclamation + |> NriSvg.withColor color + |> NriSvg.withWidth iconSize + |> NriSvg.withHeight iconSize + |> NriSvg.withCss [ marginRight, Css.flexShrink Css.zero ] + |> NriSvg.withLabel "Alert" + |> NriSvg.toHtml + + ( Nothing, Tip ) -> + case size of + Tiny -> + div + [ Attributes.css + [ borderRadius (pct 50) + , height (px 18) + , width (px 18) + , Css.marginRight (Css.px 5) + , backgroundColor Colors.navy + , displayFlex + , Css.flexShrink Css.zero + , alignItems center + , justifyContent center + ] + , ExtraAttributes.nriDescription messageIconDescription + ] + [ UiIcon.baldBulb + |> NriSvg.withColor Colors.mustard + |> NriSvg.withWidth (Css.px 13) + |> NriSvg.withHeight (Css.px 13) + |> NriSvg.toHtml + ] + + Large -> + div + [ Attributes.css + [ borderRadius (pct 50) + , height (px 35) + , width (px 35) + , Css.marginRight (Css.px 10) + , backgroundColor Colors.navy + , displayFlex + , Css.flexShrink Css.zero + , alignItems center + , justifyContent center + ] + , ExtraAttributes.nriDescription messageIconDescription + ] + [ UiIcon.sparkleBulb + |> NriSvg.withColor Colors.mustard + |> NriSvg.withWidth (Css.px 22) + |> NriSvg.withHeight (Css.px 22) + |> NriSvg.toHtml + ] + + Banner -> + div + [ Attributes.css + [ borderRadius (pct 50) + , height (px 35) + , width (px 35) + , Css.marginRight (Css.px 10) + , backgroundColor Colors.navy + , displayFlex + , Css.flexShrink Css.zero + , alignItems center + , justifyContent center + , Css.Media.withMedia + [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] + [ height (px 35) + , width (px 35) + ] + ] + , ExtraAttributes.nriDescription messageIconDescription + ] + [ UiIcon.sparkleBulb + |> NriSvg.withColor Colors.mustard + |> NriSvg.withWidth (Css.px 22) + |> NriSvg.withHeight (Css.px 22) + |> NriSvg.toHtml + ] + + ( Nothing, Success ) -> + UiIcon.checkmarkInCircle + |> NriSvg.withColor Colors.greenDark + |> NriSvg.withWidth iconSize + |> NriSvg.withHeight iconSize + |> NriSvg.withCss [ marginRight, Css.flexShrink Css.zero ] + |> NriSvg.withLabel "Success" + |> NriSvg.toHtml + + ( Just icon_, _ ) -> + icon_ + |> NriSvg.withWidth iconSize + |> NriSvg.withHeight iconSize + |> NriSvg.withCss [ marginRight, Css.flexShrink Css.zero ] + |> NriSvg.toHtml + + ( Nothing, Custom _ ) -> + Html.text "" + + +messageIconDescription : String +messageIconDescription = + "Nri-Ui-Message-icon" + + + +-- Role + + +type Role + = AlertRole + | StatusRole + + +getRoleAttribute : Maybe Role -> List (Html.Attribute Never) +getRoleAttribute role = + case role of + Just AlertRole -> + [ Role.alert ] + + Just StatusRole -> + [ Role.status ] + + Nothing -> + [] + + + +-- Dismiss buttons + + +tinyDismissButton : msg -> Html msg +tinyDismissButton msg = + Nri.Ui.styled div + "dismiss-button-container" + [] + [] + [ ClickableSvg.button "Dismiss message" + UiIcon.x + [ ClickableSvg.onClick msg + , ClickableSvg.exactWidth 16 + , ClickableSvg.exactHeight 16 + , ClickableSvg.css + [ Css.verticalAlign Css.middle + , Css.marginLeft (Css.px 5) + ] + ] + ] + + +largeDismissButton : msg -> Html msg +largeDismissButton msg = + Nri.Ui.styled div + "dismiss-button-container" + [ padding2 zero (px 20) + , displayFlex + , Css.Media.withMedia + [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] + [ padding4 (px 10) zero (px 10) (px 15) + ] + ] + [] + [ ClickableSvg.button "Dismiss message" + UiIcon.x + [ ClickableSvg.onClick msg + , ClickableSvg.exactWidth 16 + , ClickableSvg.exactHeight 16 + ] + ] + + +bannerDismissButton : msg -> Html msg +bannerDismissButton msg = + Nri.Ui.styled div + "dismiss-button-container" + [ padding2 zero (px 20) + , displayFlex + , Css.Media.withMedia + [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] + [ padding4 (px 10) zero (px 10) (px 15) + ] + ] + [] + [ ClickableSvg.button "Dismiss banner" + UiIcon.x + [ ClickableSvg.onClick msg + , ClickableSvg.exactWidth 16 + , ClickableSvg.exactHeight 16 + ] + ] From e0baea20974346f612de37e0320a40bc7b544eab Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:23:43 -0500 Subject: [PATCH 28/58] Use Nri.Ui.MediaQuery in Message.V5 --- src/Nri/Ui/Message/V5.elm | 110 ++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 40 deletions(-) diff --git a/src/Nri/Ui/Message/V5.elm b/src/Nri/Ui/Message/V5.elm index 5514cf381..8b92d2d80 100644 --- a/src/Nri/Ui/Message/V5.elm +++ b/src/Nri/Ui/Message/V5.elm @@ -1,4 +1,4 @@ -module Nri.Ui.Message.V4%20copy exposing (..)module Nri.Ui.Message.V5 exposing +module Nri.Ui.Message.V5 exposing ( somethingWentWrong , view, Attribute , icon, custom, testId, id @@ -11,7 +11,11 @@ module Nri.Ui.Message.V4%20copy exposing (..)module Nri.Ui.Message.V5 exposing , onDismiss ) -{-| Changes from V3: +{-| Changes from V4: + + - changes signature of `hideIconFor` to accept an `Nri.Ui.MediaQuery` constructor instead of `Css.Media.MediaQuery` + +Changes from V3: - adds `statusRole` - removes `alertDialogRole` @@ -77,7 +81,6 @@ import Accessibility.Styled.Style exposing (invisibleStyle) import Content import Css exposing (..) import Css.Global -import Css.Media exposing (MediaQuery) import Html.Styled.Attributes as Attributes import Http import MarkdownStyles @@ -86,7 +89,7 @@ import Nri.Ui.ClickableSvg.V2 as ClickableSvg 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.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery exposing (MediaQuery) import Nri.Ui.Shadows.V1 as Shadows import Nri.Ui.Svg.V1 as NriSvg exposing (Svg) import Nri.Ui.UiIcon.V1 as UiIcon @@ -207,9 +210,10 @@ view attributes_ = , fontWeight (int 600) , lineHeight (px 21) , padding (px 20) - , Css.Media.withMedia - [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] - [ padding (px 15) + , MediaQuery.fromList + [ MediaQuery.mobile + [ padding (px 15) + ] ] ] ] @@ -272,11 +276,12 @@ view attributes_ = , fontWeight (int 700) , lineHeight (num 1.4) , padding (px 20) - , Css.Media.withMedia - [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] - [ fontSize (px 15) - , fontWeight (int 600) - , padding (px 15) + , MediaQuery.fromList + [ MediaQuery.mobile + [ fontSize (px 15) + , fontWeight (int 600) + , padding (px 15) + ] ] ] ] @@ -308,10 +313,11 @@ view attributes_ = , minWidth (px 100) , flexShrink (int 1) , Fonts.baseFont - , Css.Media.withMedia - [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] - [ fontSize (px 15) - , fontWeight (int 600) + , MediaQuery.fromList + [ MediaQuery.mobile + [ fontSize (px 15) + , fontWeight (int 600) + ] ] ] [] @@ -579,17 +585,17 @@ hideIconForMobile = {-| -} -hideIconFor : MediaQuery -> Attribute msg -hideIconFor mediaQuery = - css - [ Css.Media.withMedia [ mediaQuery ] +hideIconFor : (List Style -> MediaQuery properties) -> Attribute msg +hideIconFor mq = + responsiveCss + (mq [ Css.Global.descendants [ ExtraAttributes.nriDescriptionSelector messageIconDescription [ invisibleStyle ] ] ] - ] + ) {-| -} @@ -602,37 +608,56 @@ css styles = } +{-| -} +responsiveCss : MediaQuery properties -> Attribute msg +responsiveCss mq = + Attribute <| + \config -> + { config + | customResponsiveStyles = MediaQuery.on mq config.customResponsiveStyles + } + + {-| Equivalent to: Message.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.notMobile ] styles ] + [ MediaQuery.fromList + [ MediaQuery.not MediaQuery.mobile [ styles ] + ] + ] -} notMobileCss : List Style -> Attribute msg -notMobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.notMobile ] styles ] +notMobileCss = + responsiveCss << MediaQuery.not MediaQuery.mobile {-| Equivalent to: Message.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.mobile ] styles ] + [ MediaQuery.fromList + [ MediaQuery.mobile [ styles ] + ] + ] -} mobileCss : List Style -> Attribute msg -mobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.mobile ] styles ] +mobileCss = + responsiveCss << MediaQuery.mobile {-| Equivalent to: Message.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.quizEngineMobile ] styles ] + [ MediaQuery.fromList + [ MediaQuery.quizEngineMobile [ styles ] + ] + ] -} quizEngineMobileCss : List Style -> Attribute msg -quizEngineMobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.quizEngineMobile ] styles ] +quizEngineMobileCss = + responsiveCss << MediaQuery.quizEngineMobile {-| Adds a dismiss ("X" icon) to a message which will produce the given `msg` when clicked. @@ -692,6 +717,7 @@ type alias BannerConfig msg = , icon : Maybe Svg , customAttributes : List (Html.Attribute Never) , customStyles : List Style + , customResponsiveStyles : MediaQuery.ResponsiveStyles } @@ -709,6 +735,7 @@ configFromAttributes attr = , icon = Nothing , customAttributes = [] , customStyles = [] + , customResponsiveStyles = MediaQuery.init } attr @@ -891,10 +918,11 @@ getIcon customIcon size theme = , Css.flexShrink Css.zero , alignItems center , justifyContent center - , Css.Media.withMedia - [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] - [ height (px 35) - , width (px 35) + , MediaQuery.fromList + [ MediaQuery.mobile + [ height (px 35) + , width (px 35) + ] ] ] , ExtraAttributes.nriDescription messageIconDescription @@ -982,9 +1010,10 @@ largeDismissButton msg = "dismiss-button-container" [ padding2 zero (px 20) , displayFlex - , Css.Media.withMedia - [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] - [ padding4 (px 10) zero (px 10) (px 15) + , MediaQuery.fromList + [ MediaQuery.mobile + [ padding4 (px 10) zero (px 10) (px 15) + ] ] ] [] @@ -1003,9 +1032,10 @@ bannerDismissButton msg = "dismiss-button-container" [ padding2 zero (px 20) , displayFlex - , Css.Media.withMedia - [ Css.Media.all [ Css.Media.maxWidth (px 1000) ] ] - [ padding4 (px 10) zero (px 10) (px 15) + , MediaQuery.fromList + [ MediaQuery.mobile + [ padding4 (px 10) zero (px 10) (px 15) + ] ] ] [] From 9614213730ace68616e4c233ebd4324d296bae3d Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:25:49 -0500 Subject: [PATCH 29/58] Update elm-verify-examples.json --- tests/elm-verify-examples.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/elm-verify-examples.json b/tests/elm-verify-examples.json index cc38d2afd..591cd3aa2 100644 --- a/tests/elm-verify-examples.json +++ b/tests/elm-verify-examples.json @@ -48,7 +48,7 @@ "Nri.Ui.Mark.V2", "Nri.Ui.Mark.V6", "Nri.Ui.MasteryIcon.V1", - "Nri.Ui.MediaQuery.V1", + "Nri.Ui.MediaQuery.V2", "Nri.Ui.Menu.V4", "Nri.Ui.Message.V4", "Nri.Ui.Modal.V12", From 5d78412f4dc6d545d1646eb1420cdc0d99c75b12 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:34:16 -0500 Subject: [PATCH 30/58] Refactor Mark --- src/Nri/Ui/Mark/V6.elm | 60 ++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/Nri/Ui/Mark/V6.elm b/src/Nri/Ui/Mark/V6.elm index 8a7ddb9e3..4542c35c0 100644 --- a/src/Nri/Ui/Mark/V6.elm +++ b/src/Nri/Ui/Mark/V6.elm @@ -34,7 +34,7 @@ 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.Html.V3 exposing (viewJust) -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import Sort exposing (Sorter) import Sort.Set as Set exposing (Set) import String.Extra @@ -110,10 +110,12 @@ viewWithOverlaps viewSegment segments = [ span [ css startStyles ] [ viewInlineTag [ Css.display Css.none - , MediaQuery.highContrastMode - [ Css.property "forced-color-adjust" "none" - , Css.display Css.inline |> Css.important - , Css.property "color" "initial" |> Css.important + , MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "forced-color-adjust" "none" + , Css.display Css.inline |> Css.important + , Css.property "color" "initial" |> Css.important + ] ] ] (String.Extra.toSentenceOxford names) @@ -337,7 +339,7 @@ viewStartHighlightTag tagStyle marked name = [] else - [ MediaQuery.highContrastMode marked.startStyles ] + [ MediaQuery.fromList [ MediaQuery.highContrastMode marked.startStyles ] ] InlineTags -> if marked.name == Nothing then @@ -369,15 +371,25 @@ markStyles tagStyle index marked = markedWith.startStyles else - [ MediaQuery.notHighContrastMode - (markedWith.startStyles - ++ [ -- override for the left border that's typically - -- added in Nri.Ui.HighlighterTool - MediaQuery.highContrastMode - [ Css.important (Css.borderLeftWidth Css.zero) - ] - ] - ) + [ MediaQuery.fromList + [ MediaQuery.not MediaQuery.highContrastMode + (markedWith.startStyles + ++ [ -- override for the left border that's typically + -- added in Nri.Ui.HighlighterTool + -- + -- Q: should this be nested like this? + -- it seems like it might never apply + -- since the parent media query is contradictory. + -- Leaving it like this for now since this is + -- the way I found it, but it seems wrong. + MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.important (Css.borderLeftWidth Css.zero) + ] + ] + ] + ) + ] ] InlineTags -> @@ -443,19 +455,23 @@ viewTag tagStyle = case tagStyle of InlineTags -> viewInlineTag - [ MediaQuery.highContrastMode - [ Css.property "forced-color-adjust" "none" - , Css.property "color" "initial" |> Css.important + [ MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "forced-color-adjust" "none" + , Css.property "color" "initial" |> Css.important + ] ] ] HiddenTags -> viewInlineTag [ Css.display Css.none - , MediaQuery.highContrastMode - [ Css.property "forced-color-adjust" "none" - , Css.display Css.inline |> Css.important - , Css.property "color" "initial" |> Css.important + , MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "forced-color-adjust" "none" + , Css.display Css.inline |> Css.important + , Css.property "color" "initial" |> Css.important + ] ] ] From fb1b948442ecb19d4b690b08c735aa9852fc2bb8 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:36:14 -0500 Subject: [PATCH 31/58] Refactor Loading --- src/Nri/Ui/Loading/V1.elm | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Nri/Ui/Loading/V1.elm b/src/Nri/Ui/Loading/V1.elm index c953cbed4..89bdf1e27 100644 --- a/src/Nri/Ui/Loading/V1.elm +++ b/src/Nri/Ui/Loading/V1.elm @@ -18,7 +18,7 @@ import Html.Styled as Html exposing (Html) import Html.Styled.Attributes as Attributes import List.Extra import Nri.Ui.Colors.V1 as Colors -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import Nri.Ui.Svg.V1 as Svg exposing (Svg) import Nri.Ui.UiIcon.V1 as UiIcon import Svg.Styled @@ -74,13 +74,13 @@ spinning size color = Html.div [] [ spinningDots - |> Svg.withCss [ MediaQuery.anyMotion [ Css.display Css.none ] ] + |> Svg.withCss [ MediaQuery.fromList [ MediaQuery.not MediaQuery.prefersReducedMotion [ Css.display Css.none ] ] ] |> Svg.withWidth size |> Svg.withHeight size |> Svg.toHtml , spinningPencil |> Svg.withColor color - |> Svg.withCss [ MediaQuery.prefersReducedMotion [ Css.display Css.none ] ] + |> Svg.withCss [ MediaQuery.fromList [ MediaQuery.prefersReducedMotion [ Css.display Css.none ] ] ] |> Svg.withWidth size |> Svg.withHeight size |> Svg.toHtml @@ -152,11 +152,13 @@ spinningDots = circlingCss : List Css.Style circlingCss = - [ MediaQuery.anyMotion - [ Css.property "animation-duration" "1s" - , Css.property "animation-iteration-count" "infinite" - , Css.animationName rotateKeyframes - , Css.property "animation-timing-function" "linear" + [ MediaQuery.fromList + [ MediaQuery.not MediaQuery.prefersReducedMotion + [ Css.property "animation-duration" "1s" + , Css.property "animation-iteration-count" "infinite" + , Css.animationName rotateKeyframes + , Css.property "animation-timing-function" "linear" + ] ] ] From 8f5944d3807006f6a8984b247bf364d281358426 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:38:08 -0500 Subject: [PATCH 32/58] Refactor HighlighterTool --- src/Nri/Ui/HighlighterTool/V1.elm | 62 +++++++++++++++++++------------ 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/Nri/Ui/HighlighterTool/V1.elm b/src/Nri/Ui/HighlighterTool/V1.elm index 5aa1a6b3d..549dea5d9 100644 --- a/src/Nri/Ui/HighlighterTool/V1.elm +++ b/src/Nri/Ui/HighlighterTool/V1.elm @@ -21,7 +21,7 @@ module Nri.Ui.HighlighterTool.V1 exposing import Css import Nri.Ui.Colors.V1 as Colors -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery {-| Tool that can be used on a highlighter @@ -96,8 +96,10 @@ buildMarker { highlightColor, hoverColor, hoverHighlightColor, kind, name } = startGroupStyles : List Css.Style startGroupStyles = - [ MediaQuery.highContrastMode - [ Css.property "border-left" "2px solid Mark" + [ MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "border-left" "2px solid Mark" + ] ] , Css.paddingLeft (Css.px paddingSize) , Css.marginLeft (Css.px -paddingSize) @@ -108,8 +110,10 @@ startGroupStyles = endGroupStyles : List Css.Style endGroupStyles = - [ MediaQuery.highContrastMode - [ Css.property "border-right" "2px solid Mark" + [ MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "border-right" "2px solid Mark" + ] ] , Css.paddingRight (Css.px paddingSize) , Css.marginRight (Css.px -paddingSize) @@ -137,10 +141,12 @@ sharedStyles = [ Css.paddingTop (Css.px 4) , Css.paddingBottom (Css.px 3) , Css.property "transition" "background-color 0.4s, box-shadow 0.4s" - , MediaQuery.highContrastMode - [ Css.property "color" "CanvasText" - , Css.property "border-top" "2px solid Mark" - , Css.property "border-bottom" "2px solid Mark" + , MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "color" "CanvasText" + , Css.property "border-top" "2px solid Mark" + , Css.property "border-bottom" "2px solid Mark" + ] ] ] @@ -151,8 +157,10 @@ hoverStyles color = sharedStyles [ Css.boxShadow5 Css.zero Css.zero (Css.px 10) (Css.px 2) color , Css.important (Css.backgroundColor color) - , MediaQuery.highContrastMode - [ Css.property "border-color" "Highlight" |> Css.important + , MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "border-color" "Highlight" |> Css.important + ] ] ] @@ -171,10 +179,12 @@ buildMarkerWithBorder { highlightColor, kind, name } = Css.batch [ Css.padding2 (Css.px 6) Css.zero , Css.lineHeight (Css.em 2.5) - , MediaQuery.highContrastMode - [ Css.property "border-color" "Mark" - , Css.property "color" "CanvasText" - , Css.borderWidth (Css.px 2) + , MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "border-color" "Mark" + , Css.property "color" "CanvasText" + , Css.borderWidth (Css.px 2) + ] ] ] in @@ -222,13 +232,17 @@ buildMarkerWithoutRounding { highlightColor, hoverColor, hoverHighlightColor, ki { hoverClass = squareHoverStyles hoverColor , hintClass = squareHoverStyles hoverColor , startGroupClass = - [ MediaQuery.highContrastMode - [ Css.property "border-left" "2px solid Mark" + [ MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "border-left" "2px solid Mark" + ] ] ] , endGroupClass = - [ MediaQuery.highContrastMode - [ Css.property "border-right" "2px solid Mark" + [ MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "border-right" "2px solid Mark" + ] ] ] , highlightClass = squareHighlightStyles highlightColor @@ -251,10 +265,12 @@ squareSharedStyles : List Css.Style squareSharedStyles = [ Css.paddingTop (Css.px 4) , Css.paddingBottom (Css.px 3) - , MediaQuery.highContrastMode - [ Css.property "color" "CanvasText" - , Css.property "border-top" "2px solid Mark" - , Css.property "border-bottom" "2px solid Mark" + , MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "color" "CanvasText" + , Css.property "border-top" "2px solid Mark" + , Css.property "border-bottom" "2px solid Mark" + ] ] ] From de5f93fd8ee96c9c2bec49117d03df395cfbe075 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:41:01 -0500 Subject: [PATCH 33/58] Refactor Header --- src/Nri/Ui/Header/V1.elm | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Nri/Ui/Header/V1.elm b/src/Nri/Ui/Header/V1.elm index e566e997e..7ecd29641 100644 --- a/src/Nri/Ui/Header/V1.elm +++ b/src/Nri/Ui/Header/V1.elm @@ -36,13 +36,13 @@ module Nri.Ui.Header.V1 exposing import Accessibility.Styled as Html exposing (Html) import Accessibility.Styled.Aria as Aria import Css -import Css.Media as Media import Html.Styled.Attributes exposing (css) import Nri.Ui.BreadCrumbs.V2 as BreadCrumbs exposing (BreadCrumbs) import Nri.Ui.Colors.V1 as Colors import Nri.Ui.Html.Attributes.V2 as AttributesExtra import Nri.Ui.Html.V3 exposing (viewJust) -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.Internal exposing (mobileBreakpoint) +import Nri.Ui.MediaQuery.V2 as MediaQuery import Nri.Ui.Spacing.V1 as Spacing import Nri.Ui.Text.V6 as Text @@ -121,7 +121,7 @@ customize = , containerAttributes = [] , extraContent = [] , description = Nothing - , pageWidth = MediaQuery.mobileBreakpoint + , pageWidth = Css.px <| mobileBreakpoint , breadCrumbsLabel = "breadcrumbs" , extraNav = Nothing } @@ -165,8 +165,10 @@ view attrs { breadCrumbs, isCurrentRoute } = , Css.borderBottom3 (Css.px 1) Css.solid Colors.gray92 , Css.paddingTop (Css.px 30) , Css.paddingBottom (Css.px 20) - , Media.withMedia [ MediaQuery.mobile ] - [ Css.important (Css.padding2 (Css.px 20) (Css.px 15)) + , MediaQuery.fromList + [ MediaQuery.mobile + [ Css.important (Css.padding2 (Css.px 20) (Css.px 15)) + ] ] ] , AttributesExtra.nriDescription "Nri-Header" @@ -176,7 +178,7 @@ view attrs { breadCrumbs, isCurrentRoute } = [ Spacing.centeredContentWithCustomWidth config.pageWidth , Css.alignItems Css.center , Css.displayFlex - , Media.withMedia [ MediaQuery.mobile ] [ Css.flexDirection Css.column ] + , MediaQuery.fromList [ MediaQuery.mobile [ Css.flexDirection Css.column ] ] ] :: config.containerAttributes ) From 8fc1184d73876df671b55329d303a6b3dbf7011a Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:45:28 -0500 Subject: [PATCH 34/58] Refactor Container --- src/Nri/Ui/Container/V2.elm | 41 +++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/Nri/Ui/Container/V2.elm b/src/Nri/Ui/Container/V2.elm index 38284b9af..7574e9807 100644 --- a/src/Nri/Ui/Container/V2.elm +++ b/src/Nri/Ui/Container/V2.elm @@ -65,14 +65,13 @@ module Nri.Ui.Container.V2 exposing import Content import Css exposing (..) -import Css.Media exposing (withMedia) import Html.Styled as Html exposing (..) import Html.Styled.Attributes import MarkdownStyles import Nri.Ui import Nri.Ui.Colors.V1 as Colors import Nri.Ui.Html.Attributes.V2 as ExtraAttributes -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery exposing (MediaQuery) import Nri.Ui.Shadows.V1 as Shadows @@ -87,6 +86,7 @@ type alias Settings msg = { containerType : String , padding : Float , css : List Css.Style + , responsiveCss : MediaQuery.ResponsiveStyles , content : List (Html msg) , attributes : List (Html.Attribute msg) } @@ -134,6 +134,12 @@ css css_ = Attribute <| \config -> { config | css = config.css ++ css_ } +{-| -} +responsiveCss : MediaQuery properties -> Attribute msg +responsiveCss mq = + Attribute <| \config -> { config | responsiveCss = MediaQuery.on mq config.responsiveCss } + + {-| Set styles that will only apply if the viewport is wider than NRI's mobile breakpoint. Equivalent to: @@ -143,8 +149,8 @@ Equivalent to: -} notMobileCss : List Style -> Attribute msg -notMobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.notMobile ] styles ] +notMobileCss = + responsiveCss << MediaQuery.not MediaQuery.mobile {-| Set styles that will only apply if the viewport is narrower than NRI's mobile breakpoint. @@ -156,8 +162,8 @@ Equivalent to: -} mobileCss : List Style -> Attribute msg -mobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.mobile ] styles ] +mobileCss = + responsiveCss << MediaQuery.mobile {-| Set styles that will only apply if the viewport is narrower than NRI's quiz-engine-specific mobile breakpoint. @@ -169,8 +175,8 @@ Equivalent to: -} quizEngineMobileCss : List Style -> Attribute msg -quizEngineMobileCss styles = - css [ Css.Media.withMedia [ MediaQuery.quizEngineMobile ] styles ] +quizEngineMobileCss = + responsiveCss << MediaQuery.quizEngineMobile {-| -} @@ -185,7 +191,7 @@ view attributes = in Nri.Ui.styled div settings.containerType - (padding (px settings.padding) :: settings.css) + (padding (px settings.padding) :: settings.css ++ MediaQuery.toStyles settings.responsiveCss) settings.attributes settings.content @@ -202,6 +208,7 @@ defaultSettings = { containerType = "default-container" , padding = 20 , css = defaultStyles + , responsiveCss = MediaQuery.init , content = [] , attributes = [] } @@ -298,9 +305,11 @@ pillowStyles = , border3 (px 1) solid Colors.gray92 , Shadows.medium , backgroundColor Colors.white - , withMedia [ MediaQuery.mobile ] - [ borderRadius (px 8) - , padding (px 20) + , MediaQuery.fromList + [ MediaQuery.mobile + [ borderRadius (px 8) + , padding (px 20) + ] ] ] @@ -323,8 +332,10 @@ buttonyStyles = , border3 (px 1) solid Colors.gray85 , borderBottom3 (px 4) solid Colors.gray85 , backgroundColor Colors.white - , withMedia [ MediaQuery.mobile ] - [ borderRadius (px 8) + , MediaQuery.fromList + [ MediaQuery.mobile + [ borderRadius (px 8) + ] ] ] @@ -362,5 +373,5 @@ markdown content = \config -> { config | content = Content.markdownContent content - , css = MarkdownStyles.anchorAndButton ++ config.css + , css = MarkdownStyles.anchorAndButton ++ config.css ++ MediaQuery.toStyles config.responsiveCss } From dcdba93235dfdefe12d656f4ab0c9d521669df08 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:49:27 -0500 Subject: [PATCH 35/58] Message/Tooltip fixup --- src/Nri/Ui/Message/V5.elm | 3 ++- src/Nri/Ui/Tooltip/V3.elm | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Nri/Ui/Message/V5.elm b/src/Nri/Ui/Message/V5.elm index 8b92d2d80..ce87b592c 100644 --- a/src/Nri/Ui/Message/V5.elm +++ b/src/Nri/Ui/Message/V5.elm @@ -127,6 +127,7 @@ view attributes_ = , color color_ , boxSizing borderBox , Css.batch attributes.customStyles + , MediaQuery.toStyle attributes.customResponsiveStyles , Css.Global.descendants [ Css.Global.a [ color Colors.azure @@ -447,7 +448,7 @@ markdown content = \config -> { config | content = Content.markdownContent content - , customStyles = MarkdownStyles.anchorAndButton ++ config.customStyles + , customStyles = MarkdownStyles.anchorAndButton ++ config.customStyles ++ MediaQuery.toStyles config.customResponsiveStyles } diff --git a/src/Nri/Ui/Tooltip/V3.elm b/src/Nri/Ui/Tooltip/V3.elm index e54de2b55..45896245e 100644 --- a/src/Nri/Ui/Tooltip/V3.elm +++ b/src/Nri/Ui/Tooltip/V3.elm @@ -1154,6 +1154,7 @@ viewTooltip tooltipId config = ++ MediaQuery.toStyles mediaQueries.positioning ++ MediaQuery.toStyles mediaQueries.applyTail ++ config.tooltipStyleOverrides + ++ MediaQuery.toStyles config.responsiveTooltipStyleOverrides ) -- We need to keep this animation in tests to make it pass: check out From cbb27f7abd1c809293ac17dfde11c5f09f37b3a2 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:53:11 -0500 Subject: [PATCH 36/58] Update comments --- src/Nri/Ui/Container/V2.elm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Nri/Ui/Container/V2.elm b/src/Nri/Ui/Container/V2.elm index 7574e9807..5756dc556 100644 --- a/src/Nri/Ui/Container/V2.elm +++ b/src/Nri/Ui/Container/V2.elm @@ -145,7 +145,7 @@ responsiveCss mq = Equivalent to: Container.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.notMobile ] styles ] + [ MediaQuery.fromList [ MediaQuery.not MediaQuery.mobile styles ] ] -} notMobileCss : List Style -> Attribute msg @@ -158,7 +158,7 @@ notMobileCss = Equivalent to: Container.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.mobile ] styles ] + [ MediaQuery.fromList [ MediaQuery.mobile styles ] ] -} mobileCss : List Style -> Attribute msg @@ -171,7 +171,7 @@ mobileCss = Equivalent to: Container.css - [ Css.Media.withMedia [ Nri.Ui.MediaQuery.V1.quizEngineMobile ] styles ] + [ MediaQuery.fromList [ MediaQuery.quizEngineMobile styles ] ] -} quizEngineMobileCss : List Style -> Attribute msg From 3131612d298fb16ba6de00ca4154a7b73ed2cd69 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:54:37 -0500 Subject: [PATCH 37/58] Refactor Confetti --- src/Nri/Ui/Confetti/V2.elm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nri/Ui/Confetti/V2.elm b/src/Nri/Ui/Confetti/V2.elm index b0ac996e9..7bd7e5f66 100644 --- a/src/Nri/Ui/Confetti/V2.elm +++ b/src/Nri/Ui/Confetti/V2.elm @@ -24,7 +24,7 @@ import Css exposing (Color) import Html.Styled as Html import Html.Styled.Attributes as Attributes exposing (css) import Nri.Ui.Colors.V1 as Colors -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import Nri.Ui.Svg.V1 as Svg import Nri.Ui.UiIcon.V1 as UiIcon import Particle exposing (Particle) @@ -80,7 +80,7 @@ view (System system _) = , Css.width (Css.pct 100) , Css.height (Css.vh 100) , Css.pointerEvents Css.none - , MediaQuery.prefersReducedMotion [ Css.display Css.none ] + , MediaQuery.fromList [ MediaQuery.prefersReducedMotion [ Css.display Css.none ] ] ] ] ) From 28b586851c44cec5f9766cf8c58bedf25473b69d Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:55:57 -0500 Subject: [PATCH 38/58] Refactor Block --- src/Nri/Ui/Block/V6.elm | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Nri/Ui/Block/V6.elm b/src/Nri/Ui/Block/V6.elm index 2d0b4efe9..dd98a5d65 100644 --- a/src/Nri/Ui/Block/V6.elm +++ b/src/Nri/Ui/Block/V6.elm @@ -73,7 +73,7 @@ import List.Extra import Nri.Ui.Colors.V1 as Colors import Nri.Ui.Html.Attributes.V2 as AttributesExtra exposing (nriDescription) import Nri.Ui.Mark.V6 as Mark exposing (Mark) -import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import Position exposing (xOffsetPx) @@ -547,10 +547,12 @@ toMark config { backgroundColor, borderColor } = , Css.backgroundColor backgroundColor , Css.borderTop3 borderWidth Css.dashed borderColor , Css.borderBottom3 borderWidth Css.dashed borderColor - , MediaQuery.highContrastMode - [ Css.property "background-color" "Mark" - , Css.property "color" "MarkText" - , Css.property "forced-color-adjust" "none" + , MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "background-color" "Mark" + , Css.property "color" "MarkText" + , Css.property "forced-color-adjust" "none" + ] ] ] , endStyles = @@ -738,16 +740,18 @@ viewBlank blankStyle blankHeight (CharacterWidth width) = Underline -> Css.borderBottom2 (Css.rem 0.1) Css.solid - , MediaQuery.highContrastMode - [ Css.property "border-color" "CanvasText" - , Css.batch - (case blankStyle of - Dashed -> - [ Css.property "background-color" "Canvas" ] - - Underline -> - [] - ) + , MediaQuery.fromList + [ MediaQuery.highContrastMode + [ Css.property "border-color" "CanvasText" + , Css.batch + (case blankStyle of + Dashed -> + [ Css.property "background-color" "Canvas" ] + + Underline -> + [] + ) + ] ] , Css.backgroundColor (case blankStyle of From aa1a1e08a080043695703047c0f406553aa28187 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 16:56:58 -0500 Subject: [PATCH 39/58] Refactor BreadCrumbs --- src/Nri/Ui/BreadCrumbs/V2.elm | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Nri/Ui/BreadCrumbs/V2.elm b/src/Nri/Ui/BreadCrumbs/V2.elm index 53c04e6a8..7850a7348 100644 --- a/src/Nri/Ui/BreadCrumbs/V2.elm +++ b/src/Nri/Ui/BreadCrumbs/V2.elm @@ -57,13 +57,12 @@ import Accessibility.Styled exposing (..) import Accessibility.Styled.Aria as Aria import Accessibility.Styled.Style as Style import Css exposing (..) -import Css.Media as Media import Html.Styled import Html.Styled.Attributes as Attributes exposing (css) 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.MediaQuery.V1 as MediaQuery +import Nri.Ui.MediaQuery.V2 as MediaQuery import Nri.Ui.Svg.V1 as Svg exposing (Svg) import Nri.Ui.UiIcon.V1 as UiIcon @@ -221,7 +220,7 @@ fontCss heading = case heading of H1 -> [ fontSize (px 30) - , Media.withMedia [ MediaQuery.mobile ] [ fontSize (px 25) ] + , MediaQuery.fromList [ MediaQuery.mobile [ fontSize (px 25) ] ] , color Colors.navy ] @@ -327,7 +326,7 @@ navContainerStyles : List Css.Style navContainerStyles = [ alignItems center , displayFlex - , Media.withMedia [ MediaQuery.mobile ] [ marginBottom (px 10), flexWrap wrap ] + , MediaQuery.fromList [ MediaQuery.mobile [ marginBottom (px 10), flexWrap wrap ] ] ] @@ -413,8 +412,10 @@ viewHeadingWithIcon { isIconOnly, isLast } title = else [ css [ marginLeft horizontalSpacing - , Media.withMedia [ MediaQuery.mobile ] - [ Style.invisibleStyle + , MediaQuery.fromList + [ MediaQuery.mobile + [ Style.invisibleStyle + ] ] ] ] From 0d11a75e80e95c5d9dc7aff9e20723d3ea6171b5 Mon Sep 17 00:00:00 2001 From: Casey Webb Date: Thu, 21 Mar 2024 17:04:51 -0500 Subject: [PATCH 40/58] Copy ClickableText.V4 to V5 --- src/Nri/Ui/ClickableText/V5.elm | 754 ++++++++++++++++++++++++++++++++ 1 file changed, 754 insertions(+) create mode 100644 src/Nri/Ui/ClickableText/V5.elm diff --git a/src/Nri/Ui/ClickableText/V5.elm b/src/Nri/Ui/ClickableText/V5.elm new file mode 100644 index 000000000..d4884296f --- /dev/null +++ b/src/Nri/Ui/ClickableText/V5.elm @@ -0,0 +1,754 @@ +module Nri.Ui.ClickableText.V5 exposing + ( button + , link + , Attribute + , small, medium, large, modal + , appearsInline + , onClick, submit, opensModal + , href, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking + , disabled + , icon, rightIcon + , hideIconForMobile, hideIconFor + , custom, nriDescription, testId, id + , hideTextForMobile, hideTextFor + , css, notMobileCss, mobileCss, quizEngineMobileCss, rightIconCss + ) + +{-| Patch changes + + - switchs `Medium` size to the default + + +# Changes from V3 + + - removes the `caption` attribute + - removes `Css.display Css.inlineBlock` + - inherits font family and weight from parent + - adjusts font sizes + - uses `Css.display Css.inlineFlex` only on buttons + - removes `"-v2"` from data descriptor + - uses dataDescriptor for `"clickable-text-label"` + - adds `Inherited` size as default + - adjusts icon margins for `Inherited` size + + +# About: + +ClickableText looks different from Nri.Ui.Button in that it displays without margin or padding. +ClickableText has the suave, traditional look of a "link"! + +For accessibility purposes, buttons that perform an action on the current page should be HTML `