Skip to content

Commit 956ebf8

Browse files
authored
Hype Group layout object (#2170)
* Hype Group layout object * Cloud Cover layout constraint
1 parent 5924120 commit 956ebf8

File tree

9 files changed

+460
-4
lines changed

9 files changed

+460
-4
lines changed

.changeset/khaki-ants-doubt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cloudfour/patterns': patch
3+
---
4+
5+
Constrain Cloud Cover cloud z-indices to their component to avoid unintended overlap

.changeset/unlucky-rockets-hug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cloudfour/patterns': minor
3+
---
4+
5+
Add new Hype Group layout object for sections of promotional or marketing content

src/components/cloud-cover/cloud-cover.scss

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,17 @@ $_column_gap: spacing.$fluid-gap;
5757
* The topmost containing element for this component. We keep most layout logic
5858
* off of this so it can be used with `o-container`.
5959
*
60-
* 1. This is only required to prevent Safari from showing subpixel artifacts
60+
* 1. Allows elements after this to overlap the clouds without having to use
61+
* increasingly larger `z-index` values.
62+
* 2. This is only required to prevent Safari from showing subpixel artifacts
6163
* at the bottom of bottom-aligned imagery (specifically the clouds).
62-
* 2. Allows us to position clouds absolutely relative to this container.
64+
* 3. Allows us to position clouds absolutely relative to this container.
6365
*/
6466

6567
.c-cloud-cover {
66-
overflow: hidden; /* 1 */
67-
position: relative; /* 2 */
68+
contain: layout; /* 1 */
69+
overflow: hidden; /* 2 */
70+
position: relative; /* 3 */
6871
}
6972

7073
/**

src/mixins/_spacing.scss

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ $fluid-spacing-block: fluid.fluid-clamp(
1717
$fluid-spacing-breakpoint-min,
1818
$fluid-spacing-breakpoint-max
1919
);
20+
$fluid-spacing-block-negative: fluid.fluid-clamp(
21+
$fluid-spacing-block-min * -1,
22+
$fluid-spacing-block-max * -1,
23+
$fluid-spacing-breakpoint-min,
24+
$fluid-spacing-breakpoint-max
25+
);
26+
$fluid-spacing-block-negative-overlap: fluid.fluid-clamp(
27+
$fluid-spacing-block-min * -1.25,
28+
$fluid-spacing-block-max * -1.25,
29+
$fluid-spacing-breakpoint-min,
30+
$fluid-spacing-breakpoint-max
31+
);
32+
$fluid-spacing-block-negative-overlap-partial: fluid.fluid-clamp(
33+
$fluid-spacing-block-min * -0.5,
34+
$fluid-spacing-block-max * -0.5,
35+
$fluid-spacing-breakpoint-min,
36+
$fluid-spacing-breakpoint-max
37+
);
2038
$fluid-spacing-inline: fluid.fluid-clamp(
2139
$fluid-spacing-inline-min,
2240
$fluid-spacing-inline-max,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{% for i in 0..2 %}
2+
{% include '@cloudfour/objects/hype-group/demo/single.twig' with {
3+
container_class: cycle(['o-container--pad', 'o-container--pad t-alternate'], loop.index0),
4+
reverse: cycle([reverse, not reverse], loop.index0),
5+
} %}
6+
{% endfor %}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{% set _hype_group %}
2+
{% embed '@cloudfour/objects/hype-group/hype-group.twig' %}
3+
{% block intro %}
4+
{% include '@cloudfour/components/heading/heading.twig' with {
5+
content: "Let’s get hyped for this content!",
6+
level: 0,
7+
tag_name: 'h2',
8+
} only %}
9+
{% endblock %}
10+
{% block object %}
11+
<img src="{{example_object_img_src}}" alt="Example object image" width="960" height="960">
12+
{% endblock %}
13+
{% block content %}
14+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nulla nulla, vestibulum vel pharetra ut, suscipit sit amet orci. Aliquam interdum tristique tincidunt.</p>
15+
{% if example_paragraphs > 1 %}
16+
<p>Nunc pellentesque eu purus vel aliquet. Vestibulum sed nulla tellus. Sed sed ante varius, facilisis dui in, lacinia dui. Sed convallis aliquam dolor, sit amet pretium nibh dignissim quis.</p>
17+
{% endif %}
18+
{% if example_paragraphs > 2 %}
19+
<p>Donec sit amet odio eget eros cursus pellentesque et in justo. Aliquam vel tristique diam. Donec vehicula dolor vitae turpis condimentum fermentum sit amet et est.</p>
20+
{% endif %}
21+
{% endblock %}
22+
{% endembed %}
23+
{% endset %}
24+
25+
{% embed '@cloudfour/objects/container/container.twig' with {
26+
class: container_class|default('o-container--pad'),
27+
_hype_group: _hype_group,
28+
} only %}
29+
{% block content %}
30+
{{_hype_group}}
31+
{% endblock %}
32+
{% endembed %}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
@use '../../compiled/tokens/scss/breakpoint';
2+
@use '../../compiled/tokens/scss/color';
3+
@use '../../compiled/tokens/scss/size';
4+
@use '../../mixins/spacing';
5+
@use '../../mixins/theme';
6+
@use 'sass:color' as sasscolor;
7+
8+
/// Vary image outline colors based on theme. This is the only real style that
9+
/// IMO stretches the definition of "object" a bit, but it's a nice way to
10+
/// support different sorts of image content.
11+
12+
@include theme.props {
13+
--theme-color-border-hype-group-object: #{sasscolor.change(
14+
color.$brand-primary-darker,
15+
$alpha: 0.1
16+
)};
17+
}
18+
19+
@include theme.props(dark) {
20+
--theme-color-border-hype-group-object: #{sasscolor.change(
21+
color.$text-light-emphasis,
22+
$alpha: 0.1
23+
)};
24+
}
25+
26+
/// Hype Group container. This assumes quite a bit of padding: For best results,
27+
/// include this within a padded Container object!
28+
///
29+
/// 1. Clear any floats without hiding overflowing content (which we need for
30+
/// the image effect).
31+
32+
.o-hype-group {
33+
contain: layout; // 1
34+
35+
/// Above a certain breakpoint, we switch to using Grid for layout. We can't
36+
/// do this from the beginning since the smallest layouts use floats.
37+
38+
@media (width >= breakpoint.$m) {
39+
column-gap: spacing.$fluid-gap;
40+
display: grid;
41+
grid-template-areas:
42+
'. object'
43+
'intro object'
44+
'content object'
45+
'. object';
46+
grid-template-columns: repeat(2, 1fr);
47+
grid-template-rows: minmax(0, 1fr) repeat(2, minmax(0, auto)) minmax(0, 1fr);
48+
}
49+
50+
@media (width >= breakpoint.$l) {
51+
grid-template-areas:
52+
'. . object'
53+
'intro intro object'
54+
'content content object'
55+
'. . object';
56+
grid-template-columns: repeat(3, 1fr);
57+
}
58+
}
59+
60+
/// To display the visual object on the opposite side, we re-use the "reverse"
61+
/// term from the Media Object and derivative patterns. Though this is a bit of
62+
/// a misnomer since the change only applies to larger breakpoints: Floating
63+
/// the object left at smaller viewports would greatly disrupt the reading line.
64+
65+
.o-hype-group--reverse {
66+
@media (width >= breakpoint.$m) {
67+
grid-template-areas:
68+
'object .'
69+
'object intro'
70+
'object content'
71+
'object .';
72+
}
73+
74+
@media (width >= breakpoint.$l) {
75+
grid-template-areas:
76+
'object . .'
77+
'object intro intro'
78+
'object content content'
79+
'object . .';
80+
}
81+
}
82+
83+
/// Intro container (heading, etc.)
84+
85+
.o-hype-group__intro {
86+
grid-area: intro;
87+
}
88+
89+
/// The first of two containers for the visual object. The first reserves the
90+
/// space for the object within the container, the second allows the object to
91+
/// break out of that space for a more dynamic layout.
92+
///
93+
/// 1. Where supported, we are going to use container query units for some
94+
/// sizing, so we need to set that up here.
95+
/// 2. We will need to use absolute positioning to control exactly how the
96+
/// inner element is positioned. This style makes sure that will be relative
97+
/// to this element.
98+
99+
.o-hype-group__object {
100+
container-type: size; // 1
101+
grid-area: object;
102+
position: relative; // 2
103+
104+
/// At smaller breakpoints, we float the object, which breaks out of its
105+
/// container off the right edge.
106+
///
107+
/// 1. The aspect ratio defines how much taller this element is than its
108+
/// width. The taller the ratio, the more the inner object will break out
109+
/// of its container.
110+
/// 2. We subtract a bit of gap space so the visual object does not appear to
111+
/// be taking up _more_ space than the content (they should appear equal).
112+
113+
@media (width < breakpoint.$m) {
114+
aspect-ratio: 2/3; // 1
115+
float: right;
116+
inline-size: calc(50% - #{size.$spacing-gap-inline-small}); // 2
117+
margin: size.$spacing-gap-inline-small;
118+
margin-inline-end: 0;
119+
120+
/// Defining a circular shape allows content to flow around the circle
121+
/// instead of the outer box, which is more visually pleasing. We have to
122+
/// size and position the circle a little oddly since we're mapping it to
123+
/// the inner element, _not_ this container (which is narrower).
124+
125+
.o-hype-group--circle-object & {
126+
shape-outside: circle(58% at 66% 50%);
127+
}
128+
129+
@media (width >= breakpoint.$s) {
130+
aspect-ratio: 4/5;
131+
inline-size: calc(100% / 3 - #{size.$spacing-gap-inline-small});
132+
133+
.o-hype-group--circle-object & {
134+
shape-outside: circle(65% at 80% 50%);
135+
}
136+
}
137+
}
138+
139+
/// At larger breakpoints, we use negative margins to allow the object to
140+
/// break outside of the outer margins... first just a little bit, but
141+
/// eventually by quite a lot.
142+
143+
@media (width >= breakpoint.$l) {
144+
margin-block: spacing.$fluid-spacing-block-negative-overlap-partial;
145+
}
146+
147+
@media (width >= breakpoint.$xl) {
148+
margin-block: spacing.$fluid-spacing-block-negative-overlap;
149+
}
150+
}
151+
152+
/// This inner object defines the visual shape of the object. There's some
153+
/// overlap with our Embed object, but because there's so much to coordinate
154+
/// between this and other elements, it seemed cleaner to embrace a bit of
155+
/// repetition to avoid a lot of complex restrictions on the Embed object in
156+
/// this particular context.
157+
///
158+
/// 1. Keeps the image a square, regardless of its container size. Really
159+
/// important for the effect!
160+
/// 2. By default, we fill the available height, which allows this element to
161+
/// fill its container and overflow the horizontal axis on small screens or
162+
/// the negative margin on large screens.
163+
164+
.o-hype-group__object-inner {
165+
aspect-ratio: 1; // 1
166+
block-size: 100%; // 2
167+
border-radius: size.$border-radius-large;
168+
display: flex;
169+
inline-size: auto;
170+
overflow: hidden;
171+
position: relative;
172+
173+
.o-hype-group--circle-object & {
174+
border-radius: size.$border-radius-full;
175+
}
176+
177+
/// 1. If the container is very short, `100%` height will be too small. So
178+
/// where supported, we can use `cqmax` to size based on the largest
179+
/// container axis instead.
180+
/// 2. At larger breakpoints, we need to use absolute positioning to make sure
181+
/// the object is centered on both axes. Without this, it might overflow
182+
/// over the wrong edges depending on the `--reverse` modifier and the
183+
/// length of adjacent content.
184+
185+
@media (width >= breakpoint.$m) {
186+
block-size: 100cqmax; // 1
187+
inset-block-start: 50%; // 2
188+
position: absolute; // 2
189+
transform: translateY(-50%); // 2
190+
191+
.o-hype-group--reverse & {
192+
inset-inline-end: 0;
193+
}
194+
}
195+
196+
/// Here is where the optional object outline is applied, allowing this
197+
/// element to showcase content like interface screenshots where the
198+
/// background could unintentionally bleed into the background. (Again, this
199+
/// is the one touch that really stretches this being an "Object," but the
200+
/// fact that the pattern is, by default, devoid of any content, pushed me
201+
/// into categorizing it as such.)
202+
///
203+
/// 1. Just in case the end user wants to copy the image URL or view the
204+
/// image properties or something, this prevents this overlay from
205+
/// interrupting that.
206+
207+
.o-hype-group--outline-object &::after {
208+
border: size.$edge-medium solid var(--theme-color-border-hype-group-object);
209+
border-radius: inherit;
210+
content: '';
211+
inset: 0;
212+
pointer-events: none; // 1
213+
position: absolute;
214+
}
215+
216+
/// Prevents non-square images from stretching to fit the square container.
217+
218+
> img,
219+
> picture > img {
220+
object-fit: cover;
221+
}
222+
}
223+
224+
/// We use margin instead of gap on the content container because we don't want
225+
/// extra space above and below the intro and content (where there are empty
226+
/// "fill" rows for the purposes of centering those elements at larger
227+
/// viewports).
228+
229+
.o-hype-group__content {
230+
grid-area: content;
231+
margin-block-start: var(--rhythm, #{size.$rhythm-default});
232+
max-inline-size: size.$max-width-prose;
233+
}

0 commit comments

Comments
 (0)