diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9a64573..32ce19b02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +* **component:** Add Split component (#326) * **component:** New picto component, deprecating the previous picto card (#382) * **template:** New multi-column layout container with up to four even columns (#233) * **css:** Add a theme class for alt background colors diff --git a/src/assets/sass/demos/split.scss b/src/assets/sass/demos/split.scss new file mode 100644 index 000000000..cae701e87 --- /dev/null +++ b/src/assets/sass/demos/split.scss @@ -0,0 +1,87 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/* -------------------------------------------------------------------------- */ +// Split demo + +@import '../protocol/includes/lib'; +@import '../docs/protocol'; +@import '../docs/protocol-components'; + +#config { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-column-gap: 80px; + grid-row-gap: $layout-md; + + fieldset fieldset { + margin-bottom: $layout-sm; + } + + legend { + @include font-size(16px); + } +} + +#config-preset { + grid-column-start: 1; + grid-column-end: 4; +} + +.demo { + .mzp-c-split, + .mzp-c-split-bg, + .mzp-c-split-container, + .mzp-c-split-media, + .mzp-c-split-body { + position: relative; + + &:before { + content: attr(class); + background-color: #fff; + color: #000; + display: block; + font-family: monospace; + font-size: 12px; + left: 0; + padding: 0 2px; + position: absolute; + bottom: 100%; + z-index: 1; + } + } + + .mzp-c-split:before { + top: 0; + bottom: auto; + } + .mzp-c-split { + outline: 1px solid red; + } + + .mzp-c-split-container { + background-color: rgba(0, 0, 255, 0.1); + } + + .mzp-c-split-media { + background-color: rgba(0, 255, 0, 0.1); + } + + .mzp-c-split-body { + background-color: rgba(255, 0, 0, 0.1); + } +} + +.spacer { + align-items: center; + background-color: rgba(0, 0, 0, 0.2); + display: flex; + justify-content: center; + height: 200px; + margin: 20px; +} + +.mzp-l-split-h-center select { + margin: 0 auto; +} diff --git a/src/assets/sass/protocol/components/_split.scss b/src/assets/sass/protocol/components/_split.scss new file mode 100644 index 000000000..5d0573306 --- /dev/null +++ b/src/assets/sass/protocol/components/_split.scss @@ -0,0 +1,467 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@import '../includes/lib'; + + +/* -------------------------------------------------------------------------- */ +// Basic vertical alignment + +.mzp-c-split { + overflow-x: hidden; + position: relative; + + // vertical spacing should match mzp-l-content's vertical spacing + padding: get-theme('v-grid-xs') 0; + + @media #{$mq-md} { + padding: get-theme('v-grid-md') 0; + } + + @media #{$mq-xl} { + padding: get-theme('v-grid-xl') 0; + } +} + +.mzp-c-split-container { + @include clearfix(); + @include border-box; + margin: 0 auto; + max-width: $content-max; + min-width: $content-xs; + + // horizontal spacing should match mzp-l-content's horizontal spacing + padding: 0 get-theme('h-grid-xs'); + + @media #{$mq-md} { + padding: 0 get-theme('h-grid-md'); + } + + @media #{$mq-xl} { + padding: 0 get-theme('h-grid-xl'); + } +} + + +.mzp-c-split-body { + position: relative; + + > :last-child { + margin-bottom: 0; + + > :last-child { + margin-bottom: 0; + } + } +} + +.mzp-c-split-media { + position: relative; +} + +.mzp-c-split-media-asset { + display: block; + max-width: 100%; +} + +.mzp-c-split-bg { + // vertical spacing should match mzp-l-content's vertical spacing + padding: get-theme('v-grid-xs') 0; + + @media #{$mq-md} { + padding: get-theme('v-grid-md') 0; + } + + @media #{$mq-xl} { + padding: get-theme('v-grid-xl') 0; + } +} + + +/* -------------------------------------------------------------------------- */ +// Mobile only rules + +@media all and (max-width:#{$screen-lg - 1}) { + + .mzp-c-split-body + .mzp-c-split-media, + .mzp-c-split-media + .mzp-c-split-body { + margin-top: get-theme('v-grid-xs'); + } + + .mzp-c-split-media { + max-width: $content-sm; + } + + .mzp-l-split-center-on-sm-md { + .mzp-c-split-body, + .mzp-c-split-media, + .mzp-c-split-media-asset { + text-align: center; + margin-left: auto; + margin-right: auto; + } + } + + .mzp-l-split-hide-media-on-sm-md { + .mzp-c-split-body { + margin-top: 0; + } + + .mzp-c-split-media { + display: none; + } + } +} + + +/* -------------------------------------------------------------------------- */ +// side by side layout, float based fall back + +@media #{$mq-lg} { + .mzp-c-split-body { + @include border-box; + @include bidi(((float, left, right),)); + width: 50%; + padding: 0 (get-theme('h-grid-lg') / 2); + + // right-third + .mzp-l-split-body-narrow & { + width: 33.33%; + } + + .mzp-l-split-body-wide & { + width: 66.66%; + } + + .mzp-l-split-reversed & { + @include bidi(((float, right, left),)); + } + } + + .mzp-c-split-media { + @include border-box; + @include bidi(((float, right, left),)); + width: 50%; + padding: 0 (get-theme('h-grid-lg') / 2); + + .mzp-l-split-body-narrow & { + width: 66.66%; + } + + .mzp-l-split-body-wide & { + width: 33.33%; + } + + .mzp-l-split-reversed & { + @include bidi(((float, left, right),)); + } + } + +} + +@media #{$mq-xl} { + .mzp-c-split-body { + padding: 0 (get-theme('h-grid-xl') / 2); + } + + .mzp-c-split-media { + padding: 0 (get-theme('h-grid-xl') / 2); + } +} + + +/* -------------------------------------------------------------------------- */ +// side by side layout, grid based + +@supports(display:grid) { + @media #{$mq-lg} { + .mzp-c-split-container { + // grid is all start/end based we don't need bidi declarations for RTL support \o/ + align-items: center; + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-areas: "body media"; + grid-column-gap: get-theme('h-grid-lg'); + + .mzp-l-split-body-narrow & { + grid-template-columns: 1fr 2fr; + } + + .mzp-l-split-body-wide & { + grid-template-columns: 2fr 1fr; + } + + .mzp-l-split-reversed & { + grid-template-areas: "media body"; + } + + .mzp-l-split-body-narrow.mzp-l-split-reversed & { + grid-template-columns: 2fr 1fr; + } + + .mzp-l-split-body-wide.mzp-l-split-reversed & { + grid-template-columns: 1fr 2fr; + } + } + + .mzp-c-split-body, + .mzp-c-split-media { + .mzp-c-split-container & { // extra specificitly to over-ride the styles that make the float fall-back work + float: none; + min-width: 0; //because of https://stackoverflow.com/questions/43311943/prevent-content-from-expanding-grid-items + padding: 0; + width: auto; + } + } + + .mzp-c-split-body { + grid-area: body; + } + + .mzp-c-split-media { + align-self: center; + grid-area: media; + justify-self: start; + + .mzp-c-split-media-asset { + display: block; + } + + .mzp-l-split-reversed & { + justify-self: end; + } + } + } + + @media #{$mq-xl} { + .mzp-c-split-container { + grid-column-gap: get-theme('h-grid-xl'); + } + } +} + +/* -------------------------------------------------------------------------- */ +// constraints on height or width of media + +@supports(display:grid) { + @media #{$mq-lg} { + .mzp-c-split-media { + &.mzp-l-split-media-overflow { + + .mzp-c-split-media-asset { + max-width: none; + } + } + + &.mzp-l-split-media-constrain-height { + height: 100%; + justify-self: auto !important; /* stylelint-disable-line declaration-no-important */ + + .mzp-c-split-media-asset { + @include bidi(((left, 0, right, auto),)); + bottom: 0; + max-height: 100%; + max-width: none; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: auto; + + .mzp-l-split-reversed & { + @include bidi(( + (left, auto, right, 0), + (right, 0, left, auto), + )); + } + } + } + } + } +} + +/* --------------------------------------------------------------------------- */ +// vertical spacing for body or media + + +@supports(display:grid) { + @media #{$mq-lg} { + .mzp-l-split-pop-top, + .mzp-l-split-pop { + + &.mzp-c-split { + padding-top: get-theme('v-grid-lg') * 2; + } + + .mzp-c-split-media { + margin-top: get-theme('v-grid-lg') * -1; + } + + .mzp-c-split-bg & { + .mzp-c-split-media { + margin-top: get-theme('v-grid-lg') * -2; + } + } + } + + .mzp-l-split-pop-bottom, + .mzp-l-split-pop { + + &.mzp-c-split { + padding-bottom: get-theme('v-grid-lg') * 2; + } + + .mzp-c-split-media { + margin-bottom: get-theme('v-grid-lg') * -1; + } + + .mzp-c-split-bg & { + .mzp-c-split-media { + margin-bottom: get-theme('v-grid-lg') * -2; + } + } + } + + .mzp-l-split-media-constrain-height { + .mzp-l-split-pop-top &, + .mzp-l-split-pop-bottom & { + height: calc(100% + #{get-theme('v-grid-lg')}); + } + .mzp-l-split-pop & { + height: calc(100% + #{get-theme('v-grid-lg') * 2}); + + } + + .mzp-l-split-pop-top .mzp-c-split-bg &, + .mzp-l-split-pop-bottom .mzp-c-split-bg & { + height: calc(100% + #{get-theme('v-grid-lg') * 2}); + } + + .mzp-l-split-pop .mzp-c-split-bg & { + height: calc(100% + #{get-theme('v-grid-lg') * 4}); + + } + } + } + + @media #{$mq-xl} { + .mzp-l-split-pop-top, + .mzp-l-split-pop { + + &.mzp-c-split { + padding-top: get-theme('v-grid-xl') * 2; + } + + .mzp-c-split-media { + margin-top: get-theme('v-grid-xl') * -1; + } + + & .mzp-c-split-bg { + .mzp-c-split-media { + margin-top: get-theme('v-grid-xl') * -2; + } + } + } + + .mzp-l-split-pop-bottom, + .mzp-l-split-pop { + + &.mzp-c-split { + padding-bottom: get-theme('v-grid-xl') * 2; + } + + .mzp-c-split-media { + margin-bottom: get-theme('v-grid-xl') * -1; + } + + & .mzp-c-split-bg { + .mzp-c-split-media { + margin-bottom: get-theme('v-grid-xl') * -2; + } + } + } + + .mzp-l-split-media-constrain-height { + .mzp-l-split-pop-top &, + .mzp-l-split-pop-bottom & { + height: calc(100% + #{get-theme('v-grid-xl')}); + } + .mzp-l-split-pop & { + height: calc(100% + #{get-theme('v-grid-xl') * 2}); + + } + + .mzp-l-split-pop-top .mzp-c-split-bg &, + .mzp-l-split-pop-bottom .mzp-c-split-bg & { + height: calc(100% + #{get-theme('v-grid-xl') * 2}); + } + + .mzp-l-split-pop .mzp-c-split-bg & { + height: calc(100% + #{get-theme('v-grid-xl') * 4}); + + } + } + } +} + +/* --------------------------------------------------------------------------- */ +// vertical and horizontal aligment for body or media + +@media #{$mq-lg} { + .mzp-c-split { + + // horizontal alignment works with float fallbacks. + // horizontal alignment does not apply to constrain-height or overflow because it could expand and obscure the text + // there's an !important in the contrain-height definitions to enforce this + .mzp-l-split-h-start { + @include bidi(((text-align, left, right),)); + justify-self: start; + } + + .mzp-l-split-h-center:not([class*='mzp-l-split-media-']) { + justify-self: center; + text-align: center; + + .mzp-c-split-media-asset { + margin: 0 auto; + } + } + + .mzp-l-split-h-end:not([class*='mzp-l-split-media-']) { + @include bidi(((text-align, right, left),)); + justify-self: end; + + .mzp-c-split-media-asset { + @include bidi(( + (margin-left, auto, margin-right, 0), + (margin-right, 0, margin-left, 0), + )); + } + } + + + // vertical alignment relies on grid so don't work for float fallbacks + .mzp-l-split-v-start { + align-self: start; + + &.mzp-l-split-media-constrain-height .mzp-c-split-media-asset { + bottom: 0; + top: 0; + transform: none; + } + } + + .mzp-l-split-v-center { + align-self: center; + } + + .mzp-l-split-v-end { + align-self: end; + + &.mzp-l-split-media-constrain-height .mzp-c-split-media-asset { + top: auto; + transform: none; + } + } + } +} diff --git a/src/assets/sass/protocol/protocol-components.scss b/src/assets/sass/protocol/protocol-components.scss index c53973ebf..6a50827c6 100644 --- a/src/assets/sass/protocol/protocol-components.scss +++ b/src/assets/sass/protocol/protocol-components.scss @@ -14,6 +14,7 @@ $image-path: '../img' !default; @import 'components/article'; +@import 'components/split'; @import 'components/billboard'; @import 'components/button'; @import 'components/call-out'; diff --git a/src/pages/demos/split-visual.hbs b/src/pages/demos/split-visual.hbs new file mode 100644 index 000000000..d2bcf2774 --- /dev/null +++ b/src/pages/demos/split-visual.hbs @@ -0,0 +1,275 @@ +--- +title: Split Outlines +layout: blank +styles: + - split +--- + + +
In this demo background colours have been applied to the different parts of the component to show how they work together.
+Looking to configure something specific? Skip to that section:
+ +This is the default layout.
+The media is constrained to the width of its container.
+Text is vertically and horizontally centered.
+ {{/content}} + {{/embed}} + +By default the body is 50% of the available space.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" block_classes="mzp-l-split-body-narrow" body_classes="" media_classes="" }} + {{#content "content"}} +The body can be 33%
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" block_classes="mzp-l-split-body-wide" body_classes="" media_classes="" }} + {{#content "content"}} +The body can be 66%
+ {{/content}} + {{/embed}} + + + {{#embed "patterns.organisms.split.split" block_classes="mzp-l-split-reversed" body_classes="" media_classes="mzp-l-split-media-overflow" }} + {{#content "content"}} +A reversed block will have the media on the left.
+ {{/content}} + {{/embed}} + +The body is vertically centered and horizontally left aligned by default.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" body_classes="mzp-l-split-v-end" media_classes="mzp-l-split-media-overflow" }} + {{#content "content"}} +The text can be aligned to the bottom.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" body_classes="mzp-l-split-v-start" media_classes="mzp-l-split-media-overflow" }} + {{#content "content"}} +Or top.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" body_classes="mzp-l-split-h-end" media_classes="mzp-l-split-media-overflow" }} + {{#content "content"}} +Horizontal alignment can also be set. This is aligned to the other side of the body container.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" body_classes="mzp-l-split-h-center mzp-l-split-v-start" media_classes="mzp-l-split-media-overflow" }} + {{#content "content"}} +Vertical alignment can be combined with horizontal alignment. For example, this is vertically aligned to the top and horizontally centered.
+ {{/content}} + {{/embed}} + +By default the media will be constrained by the width and height of the container.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" body_classes="" media_classes="mzp-l-split-media-constrain-height" }} + {{#content "content"}} +The media and instead be constrained by the height of the media container, to the maximum of the media height.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" body_classes="" media_classes="mzp-l-split-media-constrain-height" }} + {{#content "content"}} +In some cases this means the media will overflow the container if it is a wide media next to tall text.
+Or the media can be it's natural height, pushing the container larger.
+With wide media this means the media will overflow the container.
+ {{/content}} + {{/embed}} + +Media is vertically centered by default.
+The media can also be aligned to the bottom.
+Or top.
+If `mzp-l-media constrain-width` or `mzp-l-split-media-overflow` are not set, the media can also be horizontally aligned.
+Horizontally centered.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" media_classes="mzp-l-split-h-end" img_src="add-ons.png" }} + {{#content "content"}} +Or horizontally aligned to the other side of the body container.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" media_classes="mzp-l-split-v-end mzp-l-split-h-end" img_src="add-ons.png" }} + {{#content "content"}} +Here is mzp-l-split-v-end and mzp-l-split-h-end.
+Both the body and media will be inside the block container with no padding between them and the edges.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" block_classes="mzp-l-split-pop" }} + {{#content "content"}} +Adding a "pop" will make the media pop out of the container. It can be set on both the top and bottom with mzp-l-split-pop or just the top or bottom.
Popping the media makes it more likely to overflow when `mzp-l-split-media-overflow` or `mzp-l-split-media-constrain-height` are set.
+ {{/content}} + {{/embed}} + +Spooky.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" has_bg="mzp-t-dark mzp-t-background-alt" media_classes="mzp-l-split-h-center" img_src="add-ons.png" }} + {{#content "content"}} +Nice.
+ {{/content}} + {{/embed}} + + {{#embed "patterns.organisms.split.split" has_bg="mzp-t-background-alt" media_classes="mzp-l-split-media-constrain-height" block_classes="mzp-l-split-pop" }} + {{#content "content"}} +With mzp-l-split-pop
+ {{/content}} + {{/embed}} + +
+ By default on mobile everything is left aligned and the media is given a max width.
+The media will appear above or below the text based on its order in the source code.
+You can centre the content on mobile only with `mzp-l-split-center-on-sm-md`.
+
+ Some fake text so you can see how it looks. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vitae finibus elit. Maecenas maximus sodales finibus. In at eros sit amet eros placerat vestibulum sed et nisl.
++ Call to action +
+ {{/content}} +{{/embed}} + +{{#embed "patterns.organisms.split.split" block_classes="mzp-l-split-body-wide" body_classes="" media_classes="" }} + {{#content "content"}} +{{#embed "patterns.atoms.logo.wordmark" size="sm" product="mozilla" label="Mozilla"}}{{/embed}}
+Some fake text so you can see how it looks. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vitae finibus elit. Maecenas maximus sodales finibus. In at eros sit amet eros placerat vestibulum sed et nisl.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vitae finibus elit. Maecenas maximus sodales finibus. In at eros sit amet eros placerat vestibulum sed et nisl.
+ {{/content}} +{{/embed}} + +{{#embed "patterns.organisms.split.split" block_classes="mzp-l-split-reversed mzp-l-split-center-on-sm-md" body_classes="mzp-l-split-v-start" media_classes="" }} + {{#content "content"}} +Some fake text so you can see how it looks. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vitae finibus elit. Maecenas maximus sodales finibus. In at eros sit amet eros placerat vestibulum sed et nisl.
++ Call to action +
+Some fake text so you can see how it looks. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vitae finibus elit. Maecenas maximus sodales finibus. In at eros sit amet eros placerat vestibulum sed et nisl.
++ Call to action +
+ {{/content}} +{{/embed}} + + +{{#embed "patterns.organisms.split.split" has_bg="mzp-t-dark" media_classes="mzp-l-split-media-constrain-height" }} + {{#content "content"}} +Firefox fights for you.
++ Download Firefox +
+ {{/content}} +{{/embed}} + +No more getting locked out of apps and websites. Lockbox secures all the passwords you’ve saved in Firefox and gives you easy access across all your iOS devices.
+ + {{/content}} + {{/embed}} +
+ A description with a maximum of 140 characters and a link. That means we usually only have room for one or two sentences. Like this.
++ Call to action +
+ {{/content}} +{{/embed}} diff --git a/src/patterns/organisms/split/split.hbs b/src/patterns/organisms/split/split.hbs new file mode 100644 index 000000000..96a603b05 --- /dev/null +++ b/src/patterns/organisms/split/split.hbs @@ -0,0 +1,44 @@ +--- +name: Split +order: 1 +description: | + Split a row into text and a single piece of media (image or video). +notes: | + - Highly customizable positioning options + - layout sizing classes: (50/50 even by default), `mzp-l-split-body-narrow`, `mzp-l-split-body-wide` + - layout positioning classes: `mzp-l-split-reversed` + - body positioning classes: (horizontally left aligned by default), `mzp-l-split-h-center`, `mzp-l-split-h-end`, `mzp-l-split-v-start`, (vertically centered by default), `mzp-l-split-v-end` + - media sizing classes: `mzp-l-split-media-overflow`, `mzp-l-split-media-constrain-height` + - media positioning classes: (horizontally left aligned by default), `mzp-l-split-h-center`, `mzp-l-split-h-end`, `mzp-l-split-v-start`, (vertically centered by default), `mzp-l-split-v-end` + - note that the media can only be positioned horizontally if it does not have a sizing class added + - "pop" the media out of the container with: `mzp-l-split-pop-top`, `mzp-l-split-pop-bottom`, `mzp-l-split-pop` + - mobile display classes: `mzp-l-split-center-on-sm-md`, `mzp-l-split-hide-media-on-sm-md` + - No default typography styles are applied to the body text; use atoms to achieve the desired content and appearance. + - [See the demo page](/demos/split.html) for more examples in a full window context. +links: + Demo: /demos/split.html + Layout visualization: /demos/split-visual.html +--- + +A description with a maximum of 140 characters and a link. That means we usually only have room for one or two sentences. Like this.
++ Call to action +
+ {{/block}} +
+ {{/if}}
+