Skip to content

Commit 0d629ca

Browse files
Implement core atom HTML element component Heading
This commit implements the core atom(s) `H1`-`H6` which represent the content sectioning (1) HTML element `<h1>`-`<h6>` (2). It uses custom styles instead of browser defaults and allows to disable the bottom margin via the `noMargin` prop. The font styles like size, modular scale and family adhere to the "Typography" design concept (3). References: (1) https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Content_sectioning (2) https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements (3) #2 Associated epics: GH-69, GH-2 GH-81
1 parent ad995b0 commit 0d629ca

File tree

9 files changed

+373
-2
lines changed

9 files changed

+373
-2
lines changed

src/components/atoms/core/HTMLElements/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { A } from "./inline-text-semantics";
19-
import { P } from "./text-content";
19+
import { H1, H2, H3, H4, H5, H6 } from "./sectioning";
20+
import { P } from "./text";
2021

21-
export { A, P };
22+
export { A, H1, H2, H3, H4, H5, H6, P };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
3+
* Copyright (C) 2018-present Sven Greb <[email protected]>
4+
*
5+
* Project: Nord Docs
6+
* Repository: https://github.com/arcticicestudio/nord-docs
7+
* License: MIT
8+
*/
9+
10+
/**
11+
* @author Arctic Ice Studio <[email protected]>
12+
* @author Sven Greb <[email protected]>
13+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements
14+
* @since 0.3.0
15+
*/
16+
17+
import PropTypes from "prop-types";
18+
import styled, { css } from "styled-components";
19+
20+
import { ms } from "styles/theme";
21+
22+
const baseHeadingStyles = css`
23+
margin-top: 0;
24+
margin-bottom: ${({ noMargin }) => (noMargin ? 0 : "0.5rem")};
25+
font-weight: 500;
26+
`;
27+
28+
/**
29+
* A base HTML component that represents a level 1 section heading.
30+
*/
31+
const H1 = styled.h1`
32+
${baseHeadingStyles};
33+
font-size: ${ms(6)};
34+
`;
35+
36+
/**
37+
* A base HTML component that represents a level 2 section heading.
38+
*/
39+
const H2 = styled.h2`
40+
${baseHeadingStyles};
41+
font-size: ${ms(5)};
42+
`;
43+
44+
/**
45+
* A base HTML component that represents a level 3 section heading.
46+
*/
47+
const H3 = styled.h3`
48+
${baseHeadingStyles};
49+
font-size: ${ms(4)};
50+
`;
51+
52+
/**
53+
* A base HTML component that represents a level 4 section heading.
54+
*/
55+
const H4 = styled.h4`
56+
${baseHeadingStyles};
57+
font-size: ${ms(3)};
58+
`;
59+
60+
/**
61+
* A base HTML component that represents a level 5 section heading.
62+
*/
63+
const H5 = styled.h5`
64+
${baseHeadingStyles};
65+
font-size: ${ms(2)};
66+
`;
67+
68+
/**
69+
* A base HTML component that represents a level 6 section heading.
70+
*/
71+
const H6 = styled.h6`
72+
${baseHeadingStyles};
73+
font-size: ${ms(1)};
74+
`;
75+
76+
const propTypes = {
77+
hasBottomMargin: PropTypes.bool
78+
};
79+
80+
const defaultProps = {
81+
hasBottomMargin: false
82+
};
83+
84+
H1.propTypes = propTypes;
85+
H1.defaultProps = defaultProps;
86+
87+
H2.propTypes = propTypes;
88+
H2.defaultProps = defaultProps;
89+
90+
H3.propTypes = propTypes;
91+
H3.defaultProps = defaultProps;
92+
93+
H4.propTypes = propTypes;
94+
H4.defaultProps = defaultProps;
95+
96+
H5.propTypes = propTypes;
97+
H5.defaultProps = defaultProps;
98+
99+
H6.propTypes = propTypes;
100+
H6.defaultProps = defaultProps;
101+
102+
export { H1, H2, H3, H4, H5, H6 };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
3+
* Copyright (C) 2018-present Sven Greb <[email protected]>
4+
*
5+
* Project: Nord Docs
6+
* Repository: https://github.com/arcticicestudio/nord-docs
7+
* License: MIT
8+
*/
9+
10+
/**
11+
* @file Provides components that represent basic HTML elements with content sectioning functionality.
12+
* @author Arctic Ice Studio <[email protected]>
13+
* @author Sven Greb <[email protected]>
14+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Content_sectioning
15+
* @since 0.3.0
16+
*/
17+
18+
import { H1, H2, H3, H4, H5, H6 } from "./Heading";
19+
20+
export { H1, H2, H3, H4, H5, H6 };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
3+
* Copyright (C) 2018-present Sven Greb <[email protected]>
4+
*
5+
* Project: Nord Docs
6+
* Repository: https://github.com/arcticicestudio/nord-docs
7+
* License: MIT
8+
*/
9+
10+
import React, { Fragment } from "react";
11+
import { stripUnit } from "polished";
12+
13+
import { renderWithTheme } from "nord-docs-test-utils";
14+
import { H1, H2, H3, H4, H5, H6 } from "atoms/core/HTMLElements";
15+
16+
describe("theme styles", () => {
17+
test("matches the snapshot", () => {
18+
const { container } = renderWithTheme(
19+
<Fragment>
20+
<H1>Heading Level 1</H1>
21+
<H2>Heading Level 2</H2>
22+
<H3>Heading Level 3</H3>
23+
<H4>Heading Level 4</H4>
24+
<H5>Heading Level 5</H5>
25+
<H6>Heading Level 6</H6>
26+
</Fragment>
27+
);
28+
expect(container).toMatchSnapshot();
29+
});
30+
31+
test("matches the snapshot with adjusted margin", () => {
32+
const { container } = renderWithTheme(
33+
<Fragment>
34+
<H1 noMargin>Heading Level 1</H1>
35+
<H2 noMargin>Heading Level 2</H2>
36+
<H3 noMargin>Heading Level 3</H3>
37+
<H4 noMargin>Heading Level 4</H4>
38+
<H5 noMargin>Heading Level 5</H5>
39+
<H6 noMargin>Heading Level 6</H6>
40+
</Fragment>
41+
);
42+
expect(container).toMatchSnapshot();
43+
});
44+
45+
test("has explicit font size definitions", () => {
46+
const { container } = renderWithTheme(
47+
<Fragment>
48+
<H1 noMargin>Heading Level 1</H1>
49+
<H2 noMargin>Heading Level 2</H2>
50+
<H3 noMargin>Heading Level 3</H3>
51+
<H4 noMargin>Heading Level 4</H4>
52+
<H5 noMargin>Heading Level 5</H5>
53+
<H6 noMargin>Heading Level 6</H6>
54+
</Fragment>
55+
);
56+
expect(
57+
Object.values(container.children)
58+
.map(headingElement => getComputedStyle(headingElement).fontSize)
59+
.filter(Boolean).length
60+
).toEqual(container.children.length);
61+
});
62+
63+
test("has no top margin", () => {
64+
const { container } = renderWithTheme(<H1>Nord</H1>);
65+
expect(container.firstChild).toHaveStyleRule("margin-top", "0");
66+
});
67+
68+
test("adjusts bottom margin based on passed props", () => {
69+
const { container } = renderWithTheme(<H1 noMargin>Nord</H1>);
70+
expect(container.firstChild).toHaveStyleRule("margin-bottom", "0");
71+
});
72+
73+
test("Ensure descending font sizes between all heading levels", () => {
74+
const { container } = renderWithTheme(
75+
<Fragment>
76+
<H1 noMargin>Heading Level 1</H1>
77+
<H2 noMargin>Heading Level 2</H2>
78+
<H3 noMargin>Heading Level 3</H3>
79+
<H4 noMargin>Heading Level 4</H4>
80+
<H5 noMargin>Heading Level 5</H5>
81+
<H6 noMargin>Heading Level 6</H6>
82+
</Fragment>
83+
);
84+
85+
expect(
86+
/* Get the font sizes as numbers of all heading components in rendered order. */
87+
Object.values(container.children)
88+
.map(headingElement => stripUnit(getComputedStyle(headingElement).fontSize))
89+
/* Ensure descending font sizes by comparing a higher level heading with the next lower one. */
90+
.reduce((acc, cur) => (acc > cur ? cur : 0))
91+
).toBeGreaterThan(0);
92+
});
93+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`theme styles matches the snapshot 1`] = `
4+
.c0 {
5+
margin-top: 0;
6+
margin-bottom: 0.5rem;
7+
font-weight: 500;
8+
font-size: 2.0272865295410156em;
9+
}
10+
11+
.c1 {
12+
margin-top: 0;
13+
margin-bottom: 0.5rem;
14+
font-weight: 500;
15+
font-size: 1.802032470703125em;
16+
}
17+
18+
.c2 {
19+
margin-top: 0;
20+
margin-bottom: 0.5rem;
21+
font-weight: 500;
22+
font-size: 1.601806640625em;
23+
}
24+
25+
.c3 {
26+
margin-top: 0;
27+
margin-bottom: 0.5rem;
28+
font-weight: 500;
29+
font-size: 1.423828125em;
30+
}
31+
32+
.c4 {
33+
margin-top: 0;
34+
margin-bottom: 0.5rem;
35+
font-weight: 500;
36+
font-size: 1.265625em;
37+
}
38+
39+
.c5 {
40+
margin-top: 0;
41+
margin-bottom: 0.5rem;
42+
font-weight: 500;
43+
font-size: 1.125em;
44+
}
45+
46+
<div>
47+
<h1
48+
class="c0"
49+
>
50+
Heading Level 1
51+
</h1>
52+
<h2
53+
class="c1"
54+
>
55+
Heading Level 2
56+
</h2>
57+
<h3
58+
class="c2"
59+
>
60+
Heading Level 3
61+
</h3>
62+
<h4
63+
class="c3"
64+
>
65+
Heading Level 4
66+
</h4>
67+
<h5
68+
class="c4"
69+
>
70+
Heading Level 5
71+
</h5>
72+
<h6
73+
class="c5"
74+
>
75+
Heading Level 6
76+
</h6>
77+
</div>
78+
`;
79+
80+
exports[`theme styles matches the snapshot with adjusted margin 1`] = `
81+
.c0 {
82+
margin-top: 0;
83+
margin-bottom: 0;
84+
font-weight: 500;
85+
font-size: 2.0272865295410156em;
86+
}
87+
88+
.c1 {
89+
margin-top: 0;
90+
margin-bottom: 0;
91+
font-weight: 500;
92+
font-size: 1.802032470703125em;
93+
}
94+
95+
.c2 {
96+
margin-top: 0;
97+
margin-bottom: 0;
98+
font-weight: 500;
99+
font-size: 1.601806640625em;
100+
}
101+
102+
.c3 {
103+
margin-top: 0;
104+
margin-bottom: 0;
105+
font-weight: 500;
106+
font-size: 1.423828125em;
107+
}
108+
109+
.c4 {
110+
margin-top: 0;
111+
margin-bottom: 0;
112+
font-weight: 500;
113+
font-size: 1.265625em;
114+
}
115+
116+
.c5 {
117+
margin-top: 0;
118+
margin-bottom: 0;
119+
font-weight: 500;
120+
font-size: 1.125em;
121+
}
122+
123+
<div>
124+
<h1
125+
class="c0"
126+
>
127+
Heading Level 1
128+
</h1>
129+
<h2
130+
class="c1"
131+
>
132+
Heading Level 2
133+
</h2>
134+
<h3
135+
class="c2"
136+
>
137+
Heading Level 3
138+
</h3>
139+
<h4
140+
class="c3"
141+
>
142+
Heading Level 4
143+
</h4>
144+
<h5
145+
class="c4"
146+
>
147+
Heading Level 5
148+
</h5>
149+
<h6
150+
class="c5"
151+
>
152+
Heading Level 6
153+
</h6>
154+
</div>
155+
`;

0 commit comments

Comments
 (0)