Skip to content

Commit

Permalink
Feat(web): Introduce responsive layouts of Card #DS-1559
Browse files Browse the repository at this point in the history
  • Loading branch information
adamkudrna committed Dec 6, 2024
1 parent 47c76d1 commit ace462c
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 172 deletions.
11 changes: 11 additions & 0 deletions packages/web/src/scss/components/Card/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ Card can be displayed in a vertical, horizontal, or reversed horizontal layout.
👉 Keep in mind that, no matter the layout, the Card subcomponents must be arranged in the order
[specified above](#card-1).

### Responsive Card Layout

Card layout can be adjusted based on the breakpoint. To create a responsive layout, use the `tablet` and `desktop`
infixes, e.g. `Card--tablet--horizontal` or `Card--desktop--vertical`.

```html
<article class="Card Card--vertical Card--tablet--horizontal Card--desktop--horizontalReversed">
<!---->
</article>
```

### Boxed Cards

Card can be displayed with a border and a box shadow on hover.
Expand Down
205 changes: 100 additions & 105 deletions packages/web/src/scss/components/Card/_Card.scss
Original file line number Diff line number Diff line change
@@ -1,67 +1,42 @@
// 1. Get ready for card link overlay.
// 2. If there is a expanded CardMedia in a boxed vertical card, replace card padding with empty grid columns/rows.
// 3. Allow shrinking in grid layouts.
// 4. Make sure links and buttons are clickable.
// 5. Make text content selectable when there is a stretched CardLink.

// 2. Allow shrinking in grid layouts.
// 3. Make sure links and buttons are clickable.
// 4. Make text content selectable when there is a stretched CardLink.
// 5. Prepare card layouts for different breakpoints.
// a) Layouts are common for all breakpoints, so they are defined once in the theme file.
// b) Layout settings are applied to the grid via custom properties.
// c) If there is an expanded CardMedia in a boxed card, replace card padding with empty grid columns/rows.
// d) Padding is adjusted for different breakpoints.

@use 'sass:map';
@use '@tokens' as tokens;
@use '../../tools/breakpoint';
@use '../../tools/typography';
@use 'theme';

.Card {
--#{tokens.$css-variable-prefix}card-padding: #{theme.$padding};

@include typography.generate(theme.$typography);

position: relative; // 1.
display: grid;
min-width: 0; // 3.
grid-template-columns: var(--#{tokens.$css-variable-prefix}card-columns); // 5.b
grid-template-rows: var(--#{tokens.$css-variable-prefix}card-rows); // 5.b
grid-template-areas: var(--#{tokens.$css-variable-prefix}card-areas); // 5.b
column-gap: var(--#{tokens.$css-variable-prefix}card-column-gap); // 5.b
min-width: 0; // 2.
color: theme.$color;

@include breakpoint.up(tokens.$breakpoint-tablet) {
--#{tokens.$css-variable-prefix}card-padding: #{theme.$padding-tablet};
}
}

// 4., 5.
// 3., 4.
.Card:has(.CardLink) :where(a:not(.CardLink), button),
.Card:has(.CardLink):has(.CardBody--selectable) :where(p, ul, ol, dl) {
position: relative;
z-index: 1;
}

.Card--vertical {
grid-template-rows: auto 1fr auto;
grid-template-areas:
'media'
'body'
'footer';
}

.Card--horizontal {
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
'media logo'
'media body'
'media footer';
}

.Card--horizontalReversed {
grid-template-columns: 1fr auto;
grid-template-rows: auto 1fr auto;
grid-template-areas:
'logo media'
'body media'
'footer media';
}

:is(.Card--horizontal, .Card--horizontalReversed):has(.CardArtwork:not(:only-child), .CardMedia:not(:only-child)) {
column-gap: theme.$gap;
}

.Card--boxed {
--#{tokens.$css-variable-prefix}card-padding: #{theme.$padding}; // 5.d

border: theme.$border-width theme.$border-style theme.$border-color;
border-radius: theme.$border-radius;
background-color: theme.$background-color;
Expand All @@ -76,77 +51,97 @@
}
}

&:not(:has(.CardMedia--expanded)) {
padding: var(--#{tokens.$css-variable-prefix}card-padding);
&:has(.CardMedia--expanded) {
padding: var(--#{tokens.$css-variable-prefix}card-padding-shorthand); // 5.b
}
}

.Card--boxed.Card--vertical {
&:has(.CardMedia--expanded) {
grid-template-columns:
var(--#{tokens.$css-variable-prefix}card-padding)
1fr
var(--#{tokens.$css-variable-prefix}card-padding); // 2.

grid-template-rows:
var(--#{tokens.$css-variable-prefix}card-padding)
auto
1fr
auto;
grid-template-areas:
'media media media'
'media media media'
'. body .'
'. footer .';
&:not(:has(.CardMedia--expanded)) {
padding: var(--#{tokens.$css-variable-prefix}card-padding);
}

&:has(.CardMedia:not(:only-child)) {
padding-bottom: var(--#{tokens.$css-variable-prefix}card-padding);
@include breakpoint.up(tokens.$breakpoint-tablet) {
--#{tokens.$css-variable-prefix}card-padding: #{theme.$padding-tablet}; // 5.d
}
}

.Card--boxed.Card--horizontal {
&:has(.CardMedia--expanded) {
grid-template-columns: auto 1fr;
grid-template-rows:
var(--#{tokens.$css-variable-prefix}card-padding)
auto
1fr
auto
var(--#{tokens.$css-variable-prefix}card-padding); // 2.

grid-template-areas:
'media .'
'media logo'
'media body'
'media footer'
'media .';
}
// 5.
@each $breakpoint-name, $breakpoint-value in theme.$breakpoints {
$infix: breakpoint.get-modifier(infix, $breakpoint-name, $breakpoint-value);

&:has(.CardMedia:not(:only-child)) {
padding-right: var(--#{tokens.$css-variable-prefix}card-padding);
}
}
@include breakpoint.up($breakpoint-value) {
.Card--#{$infix}vertical {
--#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts, vertical, columns)};
--#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts, vertical, rows)};
--#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts, vertical, areas)};
}

.Card--boxed.Card--horizontalReversed {
&:has(.CardMedia--expanded) {
grid-template-columns: 1fr auto;
grid-template-rows:
var(--#{tokens.$css-variable-prefix}card-padding)
auto
1fr
auto
var(--#{tokens.$css-variable-prefix}card-padding); // 2.

grid-template-areas:
'. media'
'logo media'
'body media'
'footer media'
'. media';
}
.Card--#{$infix}horizontal {
--#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts, horizontal, columns)};
--#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts, horizontal, rows)};
--#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts, horizontal, areas)};
}

.Card--#{$infix}horizontalReversed {
--#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts, horizontal-reversed, columns)};
--#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts, horizontal-reversed, rows)};
--#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts, horizontal-reversed, areas)};
}

.Card--#{$infix}vertical:has(.CardArtwork:not(:only-child), .CardMedia:not(:only-child)) {
--#{tokens.$css-variable-prefix}card-column-gap: 0;
}

:is(.Card--#{$infix}horizontal, .Card--#{$infix}horizontalReversed):has(
.CardArtwork:not(:only-child),
.CardMedia:not(:only-child)
) {
--#{tokens.$css-variable-prefix}card-column-gap: #{theme.$gap};
}

// 5.c
.Card--boxed.Card--#{$infix}vertical {
&:has(.CardMedia--expanded) {
--#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts-boxed, vertical, columns)};
--#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts-boxed, vertical, rows)};
--#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts-boxed, vertical, areas)};
}

&:has(.CardMedia:not(:only-child)) {
padding-left: var(--#{tokens.$css-variable-prefix}card-padding);
&:has(.CardMedia:not(:only-child)) {
--#{tokens.$css-variable-prefix}card-padding-shorthand: 0 0
var(--#{tokens.$css-variable-prefix}card-padding);
}
}

// 5.c
.Card--boxed.Card--#{$infix}horizontal {
&:has(.CardMedia--expanded) {
--#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts-boxed, horizontal, columns)};
--#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts-boxed, horizontal, rows)};
--#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts-boxed, horizontal, areas)};
}

&:has(.CardMedia:not(:only-child)) {
--#{tokens.$css-variable-prefix}card-padding-shorthand: 0
var(--#{tokens.$css-variable-prefix}card-padding) 0 0;
}
}

// 5.c
.Card--boxed.Card--#{$infix}horizontalReversed {
&:has(.CardMedia--expanded) {
--#{tokens.$css-variable-prefix}card-columns: #{map.get(
theme.$layouts-boxed,
horizontal-reversed,
columns
)};
--#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts-boxed, horizontal-reversed, rows)};
--#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts-boxed, horizontal-reversed, areas)};
}

&:has(.CardMedia:not(:only-child)) {
--#{tokens.$css-variable-prefix}card-padding-shorthand: 0 0 0
var(--#{tokens.$css-variable-prefix}card-padding);
}
}
}
}
20 changes: 18 additions & 2 deletions packages/web/src/scss/components/Card/_CardArtwork.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
@use '@tokens' as tokens;
@use '../../tools/breakpoint';
@use '../../tools/dictionaries';
@use 'theme';

.CardArtwork {
display: grid;
grid-area: media;

&:not(:last-child) {
margin-bottom: var(--#{tokens.$css-variable-prefix}card-artwork-margin-bottom);
}
}

@include dictionaries.generate-alignments(
Expand All @@ -12,6 +18,16 @@
$axis: 'x'
);

.Card--vertical > .CardArtwork:not(:last-child) {
margin-bottom: theme.$gap;
@each $breakpoint-name, $breakpoint-value in theme.$breakpoints {
@include breakpoint.up($breakpoint-value) {
$infix: breakpoint.get-modifier(infix, $breakpoint-name, $breakpoint-value);

.Card--#{$infix}vertical > .CardArtwork {
--#{tokens.$css-variable-prefix}card-artwork-margin-bottom: #{theme.$gap};
}

:is(.Card--#{$infix}horizontal, .Card--#{$infix}horizontalReversed) > .CardArtwork {
--#{tokens.$css-variable-prefix}card-artwork-margin-bottom: 0;
}
}
}
34 changes: 24 additions & 10 deletions packages/web/src/scss/components/Card/_CardLogo.scss
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
@use '@tokens' as tokens;
@use '../../tools/breakpoint';
@use 'theme';

.CardLogo {
display: inline-flex;
grid-area: var(--#{tokens.$css-variable-prefix}card-logo-grid-area);
place-self: var(--#{tokens.$css-variable-prefix}card-logo-place-self);
align-items: center;
justify-content: center;
margin-right: var(--#{tokens.$css-variable-prefix}card-logo-margin-right);
border: theme.$logo-border-width theme.$logo-border-style theme.$logo-border-color;
border-radius: theme.$logo-border-radius;
background-color: theme.$logo-background-color;
}

.Card--vertical > .CardLogo {
grid-area: media;
place-self: end;
margin-right: theme.$logo-offset-horizontal;
&:not(:last-child) {
margin-bottom: var(--#{tokens.$css-variable-prefix}card-logo-margin-bottom);
}
}

:is(.Card--horizontal, .Card--horizontalReversed) > .CardLogo {
grid-area: logo;
justify-self: start;
@each $breakpoint-name, $breakpoint-value in theme.$breakpoints {
@include breakpoint.up($breakpoint-value) {
$infix: breakpoint.get-modifier(infix, $breakpoint-name, $breakpoint-value);

&:not(:last-child) {
margin-bottom: theme.$gap;
.Card--#{$infix}vertical > .CardLogo {
--#{tokens.$css-variable-prefix}card-logo-grid-area: media;
--#{tokens.$css-variable-prefix}card-logo-place-self: end;
--#{tokens.$css-variable-prefix}card-logo-margin-right: #{theme.$logo-offset-horizontal};
--#{tokens.$css-variable-prefix}card-logo-margin-bottom: 0;
}

:is(.Card--#{$infix}horizontal, .Card--#{$infix}horizontalReversed) > .CardLogo {
--#{tokens.$css-variable-prefix}card-logo-grid-area: logo;
--#{tokens.$css-variable-prefix}card-logo-place-self: start;
--#{tokens.$css-variable-prefix}card-logo-margin-right: 0;
--#{tokens.$css-variable-prefix}card-logo-margin-bottom: #{theme.$gap};
}
}
}
Loading

0 comments on commit ace462c

Please sign in to comment.