Merge pull request #1063 from NoRedInk/hack/tessa/layout

Hack day project: adding Layout
This commit is contained in:
Tessa 2022-09-12 10:00:01 -06:00 committed by GitHub
commit 82c8415d7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 497 additions and 18 deletions

View File

@ -56,6 +56,7 @@
"Nri.Ui.Shadows.V1",
"Nri.Ui.SideNav.V4",
"Nri.Ui.SortableTable.V3",
"Nri.Ui.Spacing.V1",
"Nri.Ui.Sprite.V1",
"Nri.Ui.Svg.V1",
"Nri.Ui.Switch.V2",

188
src/Nri/Ui/Spacing/V1.elm Normal file
View File

@ -0,0 +1,188 @@
module Nri.Ui.Spacing.V1 exposing
( centeredContentWithSidePadding, centeredContent
, centeredQuizEngineContentWithSidePadding, quizEngineCenteredContent
, centeredContentWithSidePaddingAndCustomWidth, centeredContentWithCustomWidth
, pageTopWhitespace, pageTopWhitespacePx
, pageSideWhitespace, pageSideWhitespacePx
, pageBottomWhitespace, pageBottomWhitespacePx
, verticalSpacerPx, horizontalSpacerPx
)
{-|
## Center a container on the page:
@docs centeredContentWithSidePadding, centeredContent
@docs centeredQuizEngineContentWithSidePadding, quizEngineCenteredContent
@docs centeredContentWithSidePaddingAndCustomWidth, centeredContentWithCustomWidth
## Whitespace constants and helpers:
@docs pageTopWhitespace, pageTopWhitespacePx
@docs pageSideWhitespace, pageSideWhitespacePx
@docs pageBottomWhitespace, pageBottomWhitespacePx
@docs verticalSpacerPx, horizontalSpacerPx
-}
import Css exposing (Style)
import Css.Media as Media
import Nri.Ui.MediaQuery.V1 as MediaQuery
{-| Advanced use only: center content up to a custom page width, with side padding when narrower.
-}
centeredContentWithSidePaddingAndCustomWidth : Css.Px -> Style
centeredContentWithSidePaddingAndCustomWidth breakpoint =
Css.batch
[ centeredContentWithCustomWidth breakpoint
, -- this media query is narrower to prevent the page from "snapping" into the
-- narrow viewport when resizing. Visual shifts should be as minimal as possible
-- when resizing.
Media.withMediaQuery
[ "screen and (max-width: "
++ .value
(Css.calc breakpoint
Css.minus
(Css.calc pageSideWhitespacePx Css.plus pageSideWhitespacePx)
)
++ ")"
]
[ pageSideWhitespace ]
]
{-| Advanced use only: center content up to a custom page width.
-}
centeredContentWithCustomWidth : Css.Px -> Style
centeredContentWithCustomWidth maxWidth =
Css.batch
[ Css.maxWidth maxWidth
, Css.width (Css.pct 100)
, Css.marginLeft Css.auto
, Css.marginRight Css.auto
]
{-| This is meant to be reusable for any area that:
- should be centered
- on wide viewports, should fill the width of the screen up to a max width of the mobile breakpoint
- on narrow viewports, should have standard side padding
If you have a container that should snap flush to the edges on mobile, this isn't the right style to use.
-}
centeredContentWithSidePadding : Style
centeredContentWithSidePadding =
centeredContentWithSidePaddingAndCustomWidth MediaQuery.mobileBreakpoint
{-| Center content with a max width of the mobile breakpoint.
This style does not add side padding on mobile, which means that this can be used for containers that should snap flush to the edges of the mobile viewport.
-}
centeredContent : Style
centeredContent =
centeredContentWithCustomWidth MediaQuery.mobileBreakpoint
{-| Use this style on Quiz Engine pages.
This is identical to `content`, except that it uses the quizEngineMobile breakpoint instead of the mobile breakpoint.
If you have a container that should snap flush to the edges on mobile, this isn't the right style to use.
-}
centeredQuizEngineContentWithSidePadding : Style
centeredQuizEngineContentWithSidePadding =
centeredContentWithSidePaddingAndCustomWidth MediaQuery.quizEngineBreakpoint
{-| Use this style on Quiz Engine pages.
This is identical to `centeredContent`, except that it uses the quizEngineMobile breakpoint instead of the mobile breakpoint.
This style does not add side padding on mobile, which means that this can be used for containers that should snap flush to the edges of the mobile viewport.
-}
quizEngineCenteredContent : Style
quizEngineCenteredContent =
centeredContentWithCustomWidth MediaQuery.quizEngineBreakpoint
{-| 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.
-}
pageSideWhitespace : Style
pageSideWhitespace =
Css.batch
[ Css.paddingLeft pageSideWhitespacePx
, Css.paddingRight pageSideWhitespacePx
]
{-| Unless content is flush with the edges of the viewport, there should be 15px of left/right spacing between the content and the viewport edge.
See [the UI Style Guide and Caveats' Spacing section](https://paper.dropbox.com/doc/UI-Style-Guide-and-Caveats--BobQllelpdS56NBITiRcrO6gAg-PvOLxeX3oyujYEzdJx5pu#:uid=905917270049954035442315&h2=:under-construction:-Spacing) for more details.
-}
pageSideWhitespacePx : Css.Px
pageSideWhitespacePx =
Css.px 15
{-| Convenience for adding the appriopriate amount of whitespace at the end of the page with margin.
-}
pageBottomWhitespace : Style
pageBottomWhitespace =
Css.marginBottom pageBottomWhitespacePx
{-| Every page should have 50px of whitespace at the end, so that footers don't end up spanning the middle of the page, and for consistency's sake.
See [the UI Style Guide and Caveats' Spacing section](https://paper.dropbox.com/doc/UI-Style-Guide-and-Caveats--BobQllelpdS56NBITiRcrO6gAg-PvOLxeX3oyujYEzdJx5pu#:uid=905917270049954035442315&h2=:under-construction:-Spacing) for more details.
-}
pageBottomWhitespacePx : Css.Px
pageBottomWhitespacePx =
Css.px 50
{-| Convenience for adding the appriopriate amount of whitespace at the end of the page with margin.
-}
pageTopWhitespace : Style
pageTopWhitespace =
Css.marginTop pageTopWhitespacePx
{-| Every page should have 30px of whitespace separating the header nav and the page content, as well as before any secondary headers.
See [the UI Style Guide and Caveats' Spacing section](https://paper.dropbox.com/doc/UI-Style-Guide-and-Caveats--BobQllelpdS56NBITiRcrO6gAg-PvOLxeX3oyujYEzdJx5pu#:uid=905917270049954035442315&h2=:under-construction:-Spacing) for more details.
-}
pageTopWhitespacePx : Css.Px
pageTopWhitespacePx =
Css.px 30
{-| Most elements should have 20px of whitespace separating them vertically.
See [the UI Style Guide and Caveats' Spacing section](https://paper.dropbox.com/doc/UI-Style-Guide-and-Caveats--BobQllelpdS56NBITiRcrO6gAg-PvOLxeX3oyujYEzdJx5pu#:uid=905917270049954035442315&h2=:under-construction:-Spacing) for more details.
-}
verticalSpacerPx : Css.Px
verticalSpacerPx =
Css.px 20
{-| Most elements should have 10px of whitespace separating them horizontally.
See [the UI Style Guide and Caveats' Spacing section](https://paper.dropbox.com/doc/UI-Style-Guide-and-Caveats--BobQllelpdS56NBITiRcrO6gAg-PvOLxeX3oyujYEzdJx5pu#:uid=905917270049954035442315&h2=:under-construction:-Spacing) for more details.
-}
horizontalSpacerPx : Css.Px
horizontalSpacerPx =
Css.px 10

View File

@ -21,6 +21,7 @@ import Nri.Ui.FocusRing.V1 as FocusRing
import Nri.Ui.MediaQuery.V1 exposing (mobile)
import Nri.Ui.Page.V3 as Page
import Nri.Ui.SideNav.V4 as SideNav
import Nri.Ui.Spacing.V1 as Spacing
import Nri.Ui.Sprite.V1 as Sprite
import Nri.Ui.UiIcon.V1 as UiIcon
import Routes
@ -56,7 +57,7 @@ init () url key =
( { route = Routes.fromLocation moduleStates url
, previousRoute = Nothing
, moduleStates = moduleStates
, isSideNavOpen = True
, isSideNavOpen = False
, openTooltip = Nothing
, navigationKey = key
, elliePackageDependencies = Ok Dict.empty
@ -239,6 +240,7 @@ view model =
[ view_
, Html.map never Sprite.attach
, Css.Global.global (InputMethod.styles model.inputMethod)
, Css.Global.global [ Css.Global.everything [ Css.boxSizing Css.borderBox ] ]
]
in
case model.route of
@ -318,23 +320,17 @@ withSideNav model content =
[ displayFlex
, withMedia [ mobile ] [ flexDirection column, alignItems stretch ]
, alignItems flexStart
, maxWidth (Css.px 1400)
, margin auto
, Spacing.centeredContentWithSidePaddingAndCustomWidth (Css.px 1400)
, Spacing.pageBottomWhitespace
]
]
[ navigation model
, Html.main_
[ css
[ flexGrow (int 1)
, margin2 (px 40) zero
, Css.minHeight (Css.vh 100)
]
[ css [ flexGrow (int 1) ]
, id "maincontent"
, Key.tabbable False
]
[ Html.div [ css [ Css.marginBottom (Css.px 30) ] ]
[ Routes.viewBreadCrumbs model.route
]
[ Routes.viewBreadCrumbs model.route
, content
]
]
@ -356,7 +352,9 @@ viewPreviews containerId navConfig examples =
, css
[ Css.displayFlex
, Css.flexWrap Css.wrap
, Css.property "gap" "10px"
, Css.property "row-gap" (.value Spacing.verticalSpacerPx)
, Css.property "column-gap" (.value Spacing.horizontalSpacerPx)
, Spacing.pageTopWhitespace
]
]
@ -394,7 +392,7 @@ navigation { moduleStates, route, isSideNavOpen, openTooltip } =
}
[ SideNav.navNotMobileCss
[ VendorPrefixed.value "position" "sticky"
, top (px 55)
, top (px 8)
]
, SideNav.collapsible
{ isOpen = isSideNavOpen

View File

@ -9,6 +9,7 @@ module Code exposing
, newlineWithIndent
, withParens
, always
, fromModule
)
{-|
@ -23,6 +24,7 @@ module Code exposing
@docs newlineWithIndent
@docs withParens
@docs always
@docs fromModule
-}
@ -146,3 +148,9 @@ withParens val =
always : String -> String
always val =
"\\_ -> " ++ val
{-| -}
fromModule : String -> String -> String
fromModule moduleName name =
moduleName ++ "." ++ name

View File

@ -18,6 +18,7 @@ import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Heading.V3 as Heading
import Nri.Ui.Html.V3 exposing (viewIf)
import Nri.Ui.MediaQuery.V1 exposing (mobile)
import Nri.Ui.Spacing.V1 as Spacing
import Nri.Ui.Text.V6 as Text
@ -50,6 +51,7 @@ view config =
, Css.flexWrap Css.wrap
, Css.property "gap" "10px"
, withMedia [ mobile ] [ flexDirection column, alignItems stretch ]
, Spacing.pageTopWhitespace
]
]
[ viewSection "Settings"

View File

@ -12,6 +12,7 @@ import KeyboardSupport exposing (KeyboardSupport)
import Nri.Ui.ClickableText.V3 as ClickableText
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Container.V2 as Container
import Nri.Ui.Spacing.V1 as Spacing
type alias Example state msg =
@ -113,7 +114,8 @@ preview_ { navigate, exampleHref } example =
Container.view
[ Container.gray
, Container.css
[ Css.flexBasis (Css.px 150)
[ Css.flexBasis (Css.px 200)
, Css.flexShrink Css.zero
, Css.hover
[ Css.backgroundColor Colors.glacier
, Css.cursor Css.pointer
@ -174,8 +176,7 @@ view_ ellieLinkConfig example =
in
[ Html.div
[ Attributes.css
[ Css.paddingBottom (Css.px 10)
, Css.marginBottom (Css.px 20)
[ Css.padding2 Spacing.verticalSpacerPx Css.zero
, Css.borderBottom3 (Css.px 1) Css.solid Colors.gray92
]
]

View File

@ -34,6 +34,7 @@ import Examples.Select as Select
import Examples.Shadows as Shadows
import Examples.SideNav as SideNav
import Examples.SortableTable as SortableTable
import Examples.Spacing as Spacing
import Examples.Sprite as Sprite
import Examples.Switch as Switch
import Examples.Table as Table
@ -671,6 +672,25 @@ all =
SortableTableState childState ->
Just childState
_ ->
Nothing
)
, Spacing.example
|> Example.wrapMsg SpacingMsg
(\msg ->
case msg of
SpacingMsg childMsg ->
Just childMsg
_ ->
Nothing
)
|> Example.wrapState SpacingState
(\msg ->
case msg of
SpacingState childState ->
Just childState
_ ->
Nothing
)
@ -882,6 +902,7 @@ type State
| ShadowsState Shadows.State
| SideNavState SideNav.State
| SortableTableState SortableTable.State
| SpacingState Spacing.State
| SpriteState Sprite.State
| SwitchState Switch.State
| TableState Table.State
@ -927,6 +948,7 @@ type Msg
| ShadowsMsg Shadows.Msg
| SideNavMsg SideNav.Msg
| SortableTableMsg SortableTable.Msg
| SpacingMsg Spacing.Msg
| SpriteMsg Sprite.Msg
| SwitchMsg Switch.Msg
| TableMsg Table.Msg

View File

@ -0,0 +1,259 @@
module Examples.Spacing exposing (example, State, Msg)
{-|
@docs example, State, Msg
-}
import Category exposing (Category(..))
import Code
import Css exposing (Style)
import Debug.Control as Control exposing (Control)
import Debug.Control.Extra as ControlExtra
import Debug.Control.View as ControlView
import EllieLink
import Example exposing (Example)
import Html.Styled exposing (..)
import Html.Styled.Attributes exposing (css)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Heading.V3 as Heading
import Nri.Ui.Spacing.V1 as Spacing
moduleName : String
moduleName =
"Spacing"
version : Int
version =
1
{-| -}
example : Example State Msg
example =
{ name = moduleName
, version = version
, categories = [ Layout ]
, keyboardSupport = []
, state = init
, update = update
, subscriptions = \_ -> Sub.none
, preview = []
, view = view
}
view : EllieLink.Config -> State -> List (Html Msg)
view ellieLinkConfig state =
let
settings =
Control.currentValue state.settings
( exampleCode, exampleView ) =
container
(List.filterMap identity
[ settings.topContainerStyle
, settings.horizontalContainerStyle
, settings.bottomContainerStyle
, if settings.childVerticalSpace then
Just
( "Css.property \"row-gap\" (.value Spacing.verticalSpacerPx)"
, Css.property "row-gap" (.value Spacing.verticalSpacerPx)
)
else
Nothing
, if settings.childHorizontalSpace then
Just
( "Css.property \"column-gap\" (.value Spacing.horizontalSpacerPx"
, Css.property "column-gap" (.value Spacing.horizontalSpacerPx)
)
else
Nothing
]
)
(List.repeat settings.childCount child)
in
[ ControlView.view
{ ellieLinkConfig = ellieLinkConfig
, name = moduleName
, version = version
, update = UpdateControl
, settings = state.settings
, mainType = Just "RootHtml.Html msg"
, extraCode = []
, toExampleCode =
\_ ->
[ { sectionName = "Example"
, code = exampleCode
}
]
}
, Heading.h2 [ Heading.plaintext "Example" ]
, fakePage [ exampleView ]
]
fakePage : List (Html msg) -> Html msg
fakePage =
div
[ css
[ Css.border3 (Css.px 4) Css.dotted Colors.gray20
, Css.backgroundColor Colors.gray96
]
]
container : List ( String, Css.Style ) -> List ( String, Html msg ) -> ( String, Html msg )
container styles children =
( [ "div"
, "[ css"
, " [ Css.border3 (Css.px 2) Css.dashed Colors.greenDarkest"
, " , Css.backgroundColor Colors.greenLightest"
, " , Css.property " ++ Code.string "display" ++ " " ++ Code.string "grid"
, " , Css.property " ++ Code.string "grid-template-columns" ++ " " ++ Code.string "1fr 1fr 1fr 1fr 1fr"
, " , Css.batch " ++ Code.listMultiline (List.map Tuple.first styles) 3
, " ]"
, "]"
, Code.list (List.map Tuple.first children)
]
|> String.join (Code.newlineWithIndent 1)
, div
[ css
[ Css.border3 (Css.px 2) Css.dashed Colors.greenDarkest
, Css.backgroundColor Colors.greenLightest
, Css.property "display" "grid"
, Css.property "grid-template-columns" "1fr 1fr 1fr 1fr 1fr"
, Css.batch (List.map Tuple.second styles)
]
]
(List.map Tuple.second children)
)
child : ( String, Html msg )
child =
( [ "div"
, "[ css"
, " [ Css.border3 (Css.px 1) Css.solid Colors.ochreDark"
, " , Css.backgroundColor Colors.sunshine"
, " , Css.height (Css.px 150)"
, " ]"
, "]"
, "[]"
]
|> String.join (Code.newlineWithIndent 2)
, div
[ css
[ Css.border3 (Css.px 1) Css.solid Colors.ochreDark
, Css.backgroundColor Colors.sunshine
, Css.height (Css.px 150)
]
]
[]
)
{-| -}
type alias State =
{ settings : Control Settings }
init : State
init =
{ settings = controlSettings }
type alias Settings =
{ topContainerStyle : Maybe ( String, Style )
, horizontalContainerStyle : Maybe ( String, Style )
, bottomContainerStyle : Maybe ( String, Style )
, childCount : Int
, childVerticalSpace : Bool
, childHorizontalSpace : Bool
}
controlSettings : Control Settings
controlSettings =
Control.record Settings
|> Control.field "pageTopWhitespace"
(Control.maybe True
(Control.value
( Code.fromModule moduleName "pageTopWhitespace"
, Spacing.pageTopWhitespace
)
)
)
|> Control.field "Horizontal container styling"
(Control.maybe True
([ ( "centeredContentWithSidePadding", Spacing.centeredContentWithSidePadding )
|> asChoice
, ( "centeredContent", Spacing.centeredContent )
|> asChoice
, ( "centeredQuizEngineContentWithSidePadding", Spacing.centeredQuizEngineContentWithSidePadding )
|> asChoice
, ( "quizEngineCenteredContent", Spacing.quizEngineCenteredContent )
|> asChoice
, ( "centeredContentWithSidePaddingAndCustomWidth"
, Control.map
(\value ->
( Code.fromModule moduleName "centeredContentWithSidePaddingAndCustomWidth"
++ " (Css.px "
++ String.fromFloat value
++ ")"
, Spacing.centeredContentWithSidePaddingAndCustomWidth (Css.px value)
)
)
(ControlExtra.float 400)
)
, ( "centeredContentWithCustomWidth"
, Control.map
(\value ->
( Code.fromModule moduleName "centeredContentWithCustomWidth"
++ " (Css.px "
++ String.fromFloat value
++ ")"
, Spacing.centeredContentWithCustomWidth (Css.px value)
)
)
(ControlExtra.float 400)
)
]
|> Control.choice
)
)
|> Control.field "pageBottomWhitespace"
(Control.maybe True
(Control.value
( Code.fromModule moduleName "pageBottomWhitespace"
, Spacing.pageBottomWhitespace
)
)
)
|> Control.field "Child count" (ControlExtra.int 10)
|> Control.field "Separate children vertically" (Control.bool True)
|> Control.field "Separate children horizontally" (Control.bool True)
asChoice : ( String, Style ) -> ( String, Control ( String, Style ) )
asChoice ( name, value ) =
( name, Control.value ( Code.fromModule moduleName name, value ) )
{-| -}
type Msg
= UpdateControl (Control Settings)
update : Msg -> State -> ( State, Cmd Msg )
update msg model =
case msg of
UpdateControl settings ->
( { model | settings = settings }
, Cmd.none
)

View File

@ -51,8 +51,7 @@ example =
attributes =
List.map Tuple.second (Control.currentValue state.control)
in
[ Text.caption [ Text.plaintext "NOTE: When using these styles, please read the documentation in the Elm module about \"Understanding spacing\"" ]
, ControlView.view
[ ControlView.view
{ ellieLinkConfig = ellieLinkConfig
, name = moduleName
, version = version
@ -84,6 +83,7 @@ example =
]
}
, Heading.h2 [ Heading.plaintext "Examples" ]
, Text.caption [ Text.plaintext "NOTE: When using these styles, please read the documentation in the Elm module about \"Understanding spacing\"" ]
, Heading.h3 [ Heading.plaintext "Paragraph styles" ]
, viewExamples
[ ( "mediumBody", Text.mediumBody )