Skip to content

Commit 72862e5

Browse files
authored
Merge pull request #166 from 2a-webteam/feat/component-block
Implement Content Block, Card, and Card List Components
2 parents 542971d + 6a51d70 commit 72862e5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+12096
-8874
lines changed

client/package-lock.json

Lines changed: 3041 additions & 831 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
"client": "file:",
2727
"clsx": "^2.0.0",
2828
"prism-react-renderer": "^2.3.0",
29-
"react": "^19.0.0",
30-
"react-dom": "^19.0.0",
29+
"react": "^18.3.1",
30+
"react-dom": "^18.3.1",
3131
"react-helmet": "^6.1.0",
3232
"react-icons": "^5.4.0",
3333
"react-markdown": "^10.1.0",
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* eslint-disable @docusaurus/no-html-links */
2+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
3+
// @ts-nocheck
4+
// External Components
5+
6+
import React, { useEffect, useState } from "react";
7+
import styles from "./styles.module.css";
8+
import useBaseUrl from "@docusaurus/useBaseUrl";
9+
10+
export default function ContentBlock() {
11+
const [isDarkMode, setIsDarkMode] = useState(false);
12+
13+
useEffect(() => {
14+
const theme = document.documentElement.getAttribute("data-theme");
15+
setIsDarkMode(theme === "dark");
16+
const observer = new MutationObserver(() => {
17+
const newTheme = document.documentElement.getAttribute("data-theme");
18+
setIsDarkMode(newTheme === "dark");
19+
});
20+
21+
observer.observe(document.documentElement, {
22+
attributes: true,
23+
attributeFilter: ["data-theme"],
24+
});
25+
26+
return () => observer.disconnect();
27+
}, []);
28+
return (
29+
<div className={`${styles.msf} ${styles["card"]}`}>
30+
<a href="#" title="This is a headline. It can be two lines max.">
31+
This is a headline. It can be two lines max
32+
</a>
33+
<div className={styles.card__wrapper}>
34+
<header>
35+
<figure className={`${styles["aspect-box"]}`}>
36+
<img
37+
src={
38+
isDarkMode
39+
? useBaseUrl("/img/icons/card-icon_dark_40x36.svg")
40+
: useBaseUrl("/img/icons/card-icon_40x36.svg")
41+
}
42+
onError={({ currentTarget }) => {
43+
currentTarget.style.display = "none";
44+
}}
45+
alt="card icon"
46+
width="40"
47+
height="36"
48+
/>
49+
</figure>
50+
<h2>This is a headline. It can be two lines max.</h2>
51+
</header>
52+
<article>
53+
<p>
54+
Lorem ipsum dolor sit amet consectetur. Turpis vulputate gravida ut
55+
id dictum aliquam aliquam. Amet fermentum vivamus vestibulum
56+
pellentesque.
57+
</p>
58+
</article>
59+
</div>
60+
</div>
61+
);
62+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
.aspect-box {
2+
position: relative;
3+
display: block;
4+
width: 100%;
5+
}
6+
.aspect-box canvas,
7+
.aspect-box iframe,
8+
.aspect-box img,
9+
.aspect-box svg,
10+
.aspect-box video {
11+
position: absolute;
12+
top: 0;
13+
left: 0;
14+
width: 100%;
15+
height: 100%;
16+
object-fit: cover;
17+
}
18+
.aspect-box:before {
19+
display: block;
20+
padding-top: calc(var(--aspect-ratio) * 100%);
21+
content: "";
22+
}
23+
.msf .container {
24+
max-width: 90rem;
25+
padding: 0 1.5rem;
26+
margin: 0 auto;
27+
}
28+
@media screen and (min-width: 1320px) {
29+
.msf .container {
30+
padding: 0 8.5rem;
31+
}
32+
}
33+
@media screen and (min-width: 1441px) {
34+
.msf .container {
35+
max-width: 100vw;
36+
padding: 0 9.4444444444vw;
37+
}
38+
}
39+
/* Common style ends */
40+
41+
.msf.card {
42+
position: relative;
43+
padding: 1.8125rem 2.1875rem;
44+
border: 0.0625rem solid #d1d5db;
45+
border-radius: 0.5rem;
46+
background-color: #fff;
47+
box-shadow: 0rem 0.125rem 0.5rem 0rem rgba(0, 0, 0, 0.1);
48+
transition: all 0.3s ease-in-out;
49+
}
50+
.msf.card:hover {
51+
background-color: rgba(0, 120, 212, 0.1);
52+
}
53+
[data-theme="dark"] .msf.card {
54+
border-color: #2a2a2a;
55+
background: rgba(0, 120, 212, 0.1);
56+
}
57+
[data-theme="dark"] .msf.card:hover {
58+
background-color: #2a2a2a;
59+
}
60+
.msf.card a {
61+
position: absolute;
62+
width: 100%;
63+
height: 100%;
64+
font-size: 0;
65+
inset: 0;
66+
z-index: 1;
67+
}
68+
.msf.card .card__wrapper header {
69+
display: flex;
70+
align-items: center;
71+
margin-bottom: 0.9375rem;
72+
gap: 1.125rem;
73+
}
74+
@media screen and (min-width: 1441px) {
75+
.msf.card .card__wrapper header {
76+
gap: 1.25vw;
77+
}
78+
}
79+
.msf.card .card__wrapper header figure {
80+
max-width: 2.5rem;
81+
margin: 0;
82+
}
83+
@media screen and (min-width: 1441px) {
84+
.msf.card .card__wrapper header figure {
85+
max-width: 2.7777777778vw;
86+
}
87+
}
88+
.msf.card .card__wrapper header figure.aspect-box {
89+
--aspect-ratio: 36/40;
90+
}
91+
.msf.card .card__wrapper header figure img {
92+
width: 2.5rem;
93+
height: 2.25rem;
94+
}
95+
@media screen and (min-width: 1441px) {
96+
.msf.card .card__wrapper header figure img {
97+
width: 2.7777777778vw;
98+
height: 2.5vw;
99+
}
100+
}
101+
.msf.card .card__wrapper header h2 {
102+
position: relative;
103+
top: 0.0625rem;
104+
margin-bottom: 0;
105+
color: #0078d4;
106+
font-family:
107+
Segoe Sans,
108+
sans-serif;
109+
font-size: 1.25rem;
110+
line-height: 120%;
111+
}
112+
[data-theme="dark"] .msf.card .card__wrapper header h2 {
113+
color: #5ea0ef;
114+
}
115+
@media screen and (min-width: 1540px) {
116+
.msf.card .card__wrapper header h2 {
117+
font-size: 1.3888888889vw;
118+
}
119+
}
120+
.msf.card .card__wrapper article p {
121+
color: #000;
122+
font-family:
123+
Segoe Sans,
124+
sans-serif;
125+
font-size: 1rem;
126+
line-height: 137.5%;
127+
}
128+
[data-theme="dark"] .msf.card .card__wrapper article p {
129+
color: #fff;
130+
}
131+
@media screen and (min-width: 1540px) {
132+
.msf.card .card__wrapper article p {
133+
font-size: 1.1111111111vw;
134+
}
135+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* eslint-disable @docusaurus/no-html-links */
2+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
3+
// @ts-nocheck
4+
// External Components
5+
6+
import React, { useEffect, useState } from "react";
7+
import styles from "./styles.module.css";
8+
import Card from "../../components/Card";
9+
10+
export default function ContentBlock() {
11+
const [isDarkMode, setIsDarkMode] = useState(false);
12+
13+
useEffect(() => {
14+
const theme = document.documentElement.getAttribute("data-theme");
15+
setIsDarkMode(theme === "dark");
16+
const observer = new MutationObserver(() => {
17+
const newTheme = document.documentElement.getAttribute("data-theme");
18+
setIsDarkMode(newTheme === "dark");
19+
});
20+
21+
observer.observe(document.documentElement, {
22+
attributes: true,
23+
attributeFilter: ["data-theme"],
24+
});
25+
26+
return () => observer.disconnect();
27+
}, []);
28+
return (
29+
<section className={`${styles.section} ${styles["section--card-list"]}`}>
30+
<div className={styles.container}>
31+
<div className={`${styles.msf} ${styles["card-list"]}`}>
32+
<Card></Card>
33+
<Card></Card>
34+
<Card></Card>
35+
<Card></Card>
36+
<Card></Card>
37+
<Card></Card>
38+
</div>
39+
</div>
40+
</section>
41+
);
42+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
.container {
2+
max-width: 90rem;
3+
padding: 0 1.5rem;
4+
margin: 0 auto;
5+
}
6+
@media screen and (min-width: 1320px) {
7+
.container {
8+
padding: 0 8.5rem;
9+
}
10+
}
11+
@media screen and (min-width: 1441px) {
12+
.container {
13+
max-width: 100vw;
14+
padding: 0 9.4444444444vw;
15+
}
16+
}
17+
18+
.section.section--card-list {
19+
padding: 1.5rem 0 2.375rem;
20+
}
21+
@media screen and (min-width: 991px) {
22+
.section.section--card-list {
23+
padding: 3rem 0;
24+
}
25+
}
26+
@media screen and (min-width: 1441px) {
27+
.section.section--card-list {
28+
padding: 3.3333333333vw 0;
29+
}
30+
}
31+
.msf.card-list {
32+
display: grid;
33+
gap: 1.614375rem;
34+
}
35+
@media screen and (min-width: 768px) {
36+
.msf.card-list {
37+
grid-template-columns: repeat(2, 1fr);
38+
}
39+
}
40+
@media screen and (min-width: 991px) {
41+
.msf.card-list {
42+
gap: 2.4375rem 2.25rem;
43+
grid-template-columns: repeat(3, 1fr);
44+
}
45+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/* eslint-disable @docusaurus/no-html-links */
2+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
3+
// @ts-nocheck
4+
// External Components
5+
6+
import React, { useEffect, useState } from "react";
7+
import styles from "./styles.module.css";
8+
import useBaseUrl from "@docusaurus/useBaseUrl";
9+
10+
export default function ContentBlock() {
11+
const [isDarkMode, setIsDarkMode] = useState(false);
12+
13+
useEffect(() => {
14+
const theme = document.documentElement.getAttribute("data-theme");
15+
setIsDarkMode(theme === "dark");
16+
const observer = new MutationObserver(() => {
17+
const newTheme = document.documentElement.getAttribute("data-theme");
18+
setIsDarkMode(newTheme === "dark");
19+
});
20+
21+
observer.observe(document.documentElement, {
22+
attributes: true,
23+
attributeFilter: ["data-theme"],
24+
});
25+
26+
return () => observer.disconnect();
27+
}, []);
28+
return (
29+
<section
30+
className={`${styles.section} ${styles["section--content-block"]}`}
31+
>
32+
<div className={`${styles.msf} ${styles["content-block"]}`}>
33+
<div className={styles.container}>
34+
<div className={styles["content-block__wrapper"]}>
35+
<article className={styles["l-title"]}>
36+
<h2>This is a headline. It should be a max of two lines.</h2>
37+
<p>
38+
Lorem ipsum dolor sit amet consectetur. Turpis vulputate gravida
39+
ut id dictum aliquam aliquam. Amet fermentum vivamus vestibulum
40+
pellentesque. Nec ultricies in fusce pulvinar integer diam
41+
tincidunt massa tincidunt.
42+
</p>
43+
<a className={styles["l-btn"]} href="#" title="Optional button">
44+
Optional button
45+
</a>
46+
</article>
47+
<figure className={styles["aspect-box"]}>
48+
<img
49+
src={
50+
isDarkMode
51+
? useBaseUrl("/img/placeholder_dark.png")
52+
: useBaseUrl("/img/placeholder.png")
53+
}
54+
onError={({ currentTarget }) => {
55+
currentTarget.style.display = "none";
56+
}}
57+
alt="Placeholder image"
58+
/>
59+
</figure>
60+
</div>
61+
</div>
62+
</div>
63+
64+
{/* content block inverse */}
65+
<div
66+
className={`${styles.msf} ${styles["content-block"]} ${styles["content-block--inverse"]}`}
67+
>
68+
<div className={styles.container}>
69+
<div className={styles["content-block__wrapper"]}>
70+
<article className={styles["l-title"]}>
71+
<h2>This is a headline. It should be a max of two lines.</h2>
72+
<p>
73+
Lorem ipsum dolor sit amet consectetur. Turpis vulputate gravida
74+
ut id dictum aliquam aliquam. Amet fermentum vivamus vestibulum
75+
pellentesque. Nec ultricies in fusce pulvinar integer diam
76+
tincidunt massa tincidunt.
77+
</p>
78+
<a className={styles["l-btn"]} href="#" title="Optional button">
79+
Optional button
80+
</a>
81+
</article>
82+
<figure className={styles["aspect-box"]}>
83+
<img
84+
src={
85+
isDarkMode
86+
? useBaseUrl("/img/placeholder_dark.png")
87+
: useBaseUrl("/img/placeholder.png")
88+
}
89+
onError={({ currentTarget }) => {
90+
currentTarget.style.display = "none";
91+
}}
92+
alt="Placeholder image"
93+
/>
94+
</figure>
95+
</div>
96+
</div>
97+
</div>
98+
</section>
99+
);
100+
}

0 commit comments

Comments
 (0)