Skip to content

Commit d829257

Browse files
didooaklkv
andauthored
Foundations / Viewport breakpoints - Implementation (#2848)
Co-authored-by: Alexey Kulakov <[email protected]>
1 parent 130b668 commit d829257

File tree

23 files changed

+569
-68
lines changed

23 files changed

+569
-68
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hashicorp/design-system-components": minor
3+
---
4+
5+
`breakpoints` - Added responsive breakpoint values and helpers for responsiveness
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
//
7+
// BREAKPOINTS
8+
//
9+
10+
@use "sass:list";
11+
@use "sass:map";
12+
@use "sass:meta";
13+
14+
// breakpoints
15+
16+
$hds-breakpoints: (
17+
sm: 480px,
18+
md: 768px,
19+
lg: 1088px,
20+
xl: 1440px,
21+
xxl: 1920px,
22+
) !default;
23+
$hds-breakpoints-names: map.keys($hds-breakpoints);
24+
25+
// functions
26+
27+
@function hds-get-breakpoint-next($name) {
28+
$n: list.index($hds-breakpoints-names, $name);
29+
30+
@if $n != null and $n < list.length($hds-breakpoints-names) {
31+
@return list.nth($hds-breakpoints-names, $n + 1);
32+
}
33+
34+
@return null;
35+
}
36+
37+
@function hds-get-breakpoint-prev($name) {
38+
$n: list.index($hds-breakpoints-names, $name);
39+
40+
@if $n != null and $n > 1 {
41+
@return list.nth($hds-breakpoints-names, $n - 1);
42+
}
43+
44+
@return null;
45+
}
46+
47+
// mixins
48+
49+
@mixin hds-breakpoint-above($name) {
50+
@if map.has-key($hds-breakpoints, $name) {
51+
$width: map.get($hds-breakpoints, $name);
52+
53+
@media screen and (min-width: $width) {
54+
@content;
55+
}
56+
} @else {
57+
@error 'Unable to find a breakpoint with name `#{$name}`. Expected one of: (#{$hds-breakpoints-names})';
58+
}
59+
}
60+
61+
@mixin hds-breakpoint-below($name) {
62+
@if map.has-key($hds-breakpoints, $name) {
63+
// We borrow this logic from bootstrap for specifying the value of the
64+
// max-width. The maximum width is calculated by finding the breakpoint and
65+
// subtracting .02 from its value. This value is used instead of .01 to
66+
// avoid rounding issues in Safari
67+
// https://github.com/twbs/bootstrap/blob/c5b1919deaf5393fcca9e9b9d7ce9c338160d99d/scss/mixins/_breakpoints.scss#L34-L46
68+
$width: map.get($hds-breakpoints, $name) - 0.02;
69+
70+
@media screen and (max-width: $width) {
71+
@content;
72+
}
73+
} @else {
74+
@error 'Unable to find a breakpoint with name `#{$name}`. Expected one of: (#{$hds-breakpoints})';
75+
}
76+
}
77+
78+
@mixin hds-breakpoint-between($lower, $upper) {
79+
$min: map.get($hds-breakpoints, $lower);
80+
$max: map.get($hds-breakpoints, $upper);
81+
82+
@if $min and $max {
83+
@media screen and (min-width: $min) and (max-width: $max) {
84+
@content;
85+
}
86+
} @else if $min != null and $max == null {
87+
@include hds-breakpoint-above($lower) {
88+
@content;
89+
}
90+
} @else if $min == null and $max != null {
91+
@include hds-breakpoint-below($upper) {
92+
@content;
93+
}
94+
} @else {
95+
@error 'Unable to find a breakpoint to satisfy: (#{$lower},#{$upper}). Expected both to be one of (#{$hds-breakpoints-names}).';
96+
}
97+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// todo: understand if we can generate this list reading the corresponding Sass file
2+
// see: https://sergiocarracedo.es/2020/07/17/sharing-variables-between-scss-and-typescript/
3+
4+
export const hdsBreakpoints = {
5+
sm: { value: 480, px: '480px', rem: '30rem' },
6+
md: { value: 768, px: '768px', rem: '48rem' },
7+
lg: { value: 1088, px: '1088px', rem: '68rem' },
8+
xl: { value: 1440, px: '1440px', rem: '90rem' },
9+
xxl: { value: 1920, px: ' 1920px', rem: '120rem' },
10+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
import { hdsBreakpoints } from '@hashicorp/design-system-components/utils/hds-breakpoints';
7+
8+
import ShwLabel from '../../shw/label';
9+
10+
const hdsBreakpointsNames = Object.keys(hdsBreakpoints);
11+
12+
<template>
13+
<div class="mock-demo-breakpoints-ruler">
14+
{{#each hdsBreakpointsNames as |breakpoint|}}
15+
<ShwLabel
16+
class="mock-demo-breakpoints-ruler__mark mock-demo-breakpoints-ruler__mark--{{breakpoint}}"
17+
/>
18+
{{/each}}
19+
</div>
20+
</template>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
import Component from '@glimmer/component';
7+
import gt from 'ember-truth-helpers/helpers/gt';
8+
9+
import { hdsBreakpoints } from '@hashicorp/design-system-components/utils/hds-breakpoints';
10+
11+
import ShwFlex from '../../shw/flex';
12+
import ShwGrid from '../../shw/grid';
13+
import ShwPlaceholder from '../../shw/placeholder';
14+
import ShwTextH2 from '../../shw/text/h2';
15+
import ShwTextH3 from '../../shw/text/h3';
16+
17+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
18+
export interface MockDemoBreakpointsVisualizationSignature {}
19+
20+
const hdsBreakpointsNames = Object.keys(hdsBreakpoints);
21+
22+
export default class MockDemoBreakpointsVisualization extends Component<MockDemoBreakpointsVisualizationSignature> {
23+
get breakpointBetweenGridColumns(): number {
24+
return hdsBreakpointsNames.length - 1;
25+
}
26+
27+
<template>
28+
<div class="mock-demo-breakpoints-visualization">
29+
30+
<ShwTextH2>Sass mixins</ShwTextH2>
31+
32+
<ShwTextH3><code>breakpoint-above</code></ShwTextH3>
33+
<ShwFlex @direction="column" as |SF|>
34+
{{#each hdsBreakpointsNames as |breakpoint|}}
35+
<SF.Item>
36+
<ShwPlaceholder
37+
class="mock-demo-breakpoints-mixin__breakpoint-above--{{breakpoint}}"
38+
@text={{breakpoint}}
39+
@height="20"
40+
/>
41+
</SF.Item>
42+
{{/each}}
43+
</ShwFlex>
44+
45+
<ShwTextH3><code>breakpoint-below</code></ShwTextH3>
46+
<ShwFlex @direction="column" as |SF|>
47+
{{#each hdsBreakpointsNames as |breakpoint|}}
48+
<SF.Item>
49+
<ShwPlaceholder
50+
class="mock-demo-breakpoints-mixin__breakpoint-below--{{breakpoint}}"
51+
@text={{breakpoint}}
52+
@height="20"
53+
/>
54+
</SF.Item>
55+
{{/each}}
56+
</ShwFlex>
57+
58+
<ShwTextH3><code>breakpoint-between</code></ShwTextH3>
59+
<ShwFlex @direction="column" as |SF|>
60+
{{#each hdsBreakpointsNames as |breakpointLower indexLower|}}
61+
{{#each hdsBreakpointsNames as |breakpointUpper indexUupper|}}
62+
{{#if (gt indexUupper indexLower)}}
63+
<SF.Item>
64+
<ShwGrid
65+
@columns={{this.breakpointBetweenGridColumns}}
66+
@gap="0"
67+
as |SG|
68+
>
69+
<SG.Item>
70+
<ShwPlaceholder
71+
class="mock-demo-breakpoints-mixin__breakpoint-between--lower-{{breakpointLower}}-upper-{{breakpointUpper}}"
72+
@text="{{breakpointLower}} / {{breakpointUpper}}"
73+
@height="20"
74+
/>
75+
</SG.Item>
76+
</ShwGrid>
77+
</SF.Item>
78+
{{/if}}
79+
{{/each}}
80+
{{/each}}
81+
</ShwFlex>
82+
83+
</div>
84+
</template>
85+
}

showcase/app/components/shw/grid/index.gts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type { ShwGridItemSignature } from './item';
1818

1919
interface ShwGridSignature {
2020
Args: {
21-
columns: 1 | 2 | 3 | 4 | 5 | 6 | 7;
21+
columns: number;
2222
forceMinWidth?: boolean;
2323
gap?: string;
2424
grow?: boolean;
@@ -40,27 +40,23 @@ export default class ShwGrid extends Component<ShwGridSignature> {
4040
const { columns } = this.args;
4141

4242
assert('@columns for "Shw::Grid" must be defined', columns !== undefined);
43+
assert(
44+
'@columns for "Shw::Grid" must be a positive integer greater than zero',
45+
columns > 0
46+
);
4347

4448
return columns;
4549
}
4650

4751
get itemsStyle(): SafeString | undefined {
4852
const styles = [];
4953
styles.push(`gap: ${this.args.gap ? this.args.gap : '1rem'}`);
54+
styles.push(`--shw-grid-columns: ${this.columns}`);
5055
return styles.length > 0 ? htmlSafe(styles.join('; ')) : undefined;
5156
}
5257

53-
get classNames(): string {
54-
const classes = ['shw-grid'];
55-
56-
// add a class based on the @columns argument
57-
classes.push(`shw-grid--cols-${this.columns}`);
58-
59-
return classes.join(' ');
60-
}
61-
6258
<template>
63-
<div class={{this.classNames}}>
59+
<div class="shw-grid">
6460
{{#if @label}}
6561
<ShwLabel>{{@label}}</ShwLabel>
6662
{{/if}}

showcase/app/router.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ Router.map(function () {
1616
this.route('typography');
1717
this.route('elevation');
1818
this.route('focus-ring');
19+
this.route('breakpoints', function () {
20+
this.route('frameless', function () {
21+
this.route('demo-viewport-breakpoints-visualization');
22+
this.route('demo-viewport-breakpoints-visualization-with-ui-shell');
23+
this.route('demo-viewport-breakpoints-page-padding');
24+
});
25+
});
1926
});
2027
this.route('components', function () {
2128
this.route('accordion');

showcase/app/styles/app.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
@use "./showcase-components/outliner";
2525
@use "./showcase-components/placeholder";
2626
@use "./mock-components/app";
27+
@use "./mock-components/demo/breakpoints";
2728

2829

2930
// Notice: this list can be automatically edited by the Ember blueprint, please don't remove the start/end comments

showcase/app/styles/mock-components/app.scss

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@
33
* SPDX-License-Identifier: MPL-2.0
44
*/
55

6+
// Overrides necessary to simulate minimized behavior
7+
.showcase-app.frameless {
8+
.hds-side-nav,
9+
.hds-app-side-nav {
10+
&.hds-side-nav--is-minimized,
11+
&.hds-app-side-nav--is-minimized {
12+
.hds-side-nav__wrapper,
13+
.hds-app-side-nav__wrapper {
14+
overflow: hidden;
15+
}
16+
17+
.hds-side-nav__wrapper-body,
18+
.hds-app-side-nav__wrapper-body {
19+
width: var(--hds-app-sidenav-width-expanded);
20+
opacity: 0;
21+
}
22+
23+
.hds-side-nav__wrapper-footer,
24+
.hds-app-side-nav__wrapper-footer {
25+
opacity: 0;
26+
}
27+
}
28+
}
29+
}
30+
631
.mock-app-layout-main-content-wrapper {
732
padding: 24px 64px;
833

@@ -19,7 +44,7 @@
1944
}
2045

2146
.mock-app-main-generic-advanced-table-wrapper {
22-
// TODO remove after https://hashicorp.atlassian.net/browse/HDS-4751 is done
47+
// TODO remove after https://hashicorp.atlassian.net/browse/HDS-4751 is done
2348
display: grid;
2449
height: 600px;
2550
overflow: auto;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
$mock-demo-breakpoints-colors: (
7+
sm: #f44336,
8+
md: #ff9800,
9+
lg: #ffc107,
10+
xl: #4caf50,
11+
xxl: #03a9f4,
12+
);

0 commit comments

Comments
 (0)