mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2025-12-19 18:18:27 -05:00
feat(curriculum): add reusable footer React lab (#57202)
Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
This commit is contained in:
@@ -3306,7 +3306,10 @@
|
||||
]
|
||||
},
|
||||
"oxiv": { "title": "260", "intro": [] },
|
||||
"fjvx": { "title": "261", "intro": [] },
|
||||
"lab-reusable-footer": {
|
||||
"title": "Build a Reusable Footer",
|
||||
"intro": ["In this lab, you will use React to build a reusable footer."]
|
||||
},
|
||||
"lecture-working-with-data-in-react": {
|
||||
"title": "Working with Data in React",
|
||||
"intro": [
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to the Build a Reusable Footer
|
||||
block: lab-reusable-footer
|
||||
superBlock: full-stack-developer
|
||||
---
|
||||
|
||||
## Introduction to the Build a Reusable Footer
|
||||
|
||||
In this lab, you will use React to build a reusable footer.
|
||||
11
curriculum/challenges/_meta/lab-reusable-footer/meta.json
Normal file
11
curriculum/challenges/_meta/lab-reusable-footer/meta.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Build a Reusable Footer",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"dashedName": "lab-reusable-footer",
|
||||
"superBlock": "full-stack-developer",
|
||||
"challengeOrder": [{ "id": "673b02b03134b04637bf7055", "title": "Build a Reusable Footer" }],
|
||||
"helpCategory": "HTML-CSS",
|
||||
"blockLayout": "link",
|
||||
"blockType": "lab"
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
---
|
||||
id: 673b02b03134b04637bf7055
|
||||
title: Build a Reusable Footer
|
||||
challengeType: 14
|
||||
dashedName: build-a-reusable-footer
|
||||
demoType: onClick
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
|
||||
|
||||
**User Stories:**
|
||||
|
||||
1. You should export a `Footer` component.
|
||||
2. Your `Footer` component should return a `footer` element that contains all the other elements.
|
||||
3. Your `footer` element should have:
|
||||
|
||||
- At least three unordered lists, each with at least two list items.
|
||||
- At least one paragraph element with a copyright `©` symbol.
|
||||
- At least three links with the `href` value set to `#` and the link content set to an icon or text of your choice.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should export a `Footer` component.
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.mount(React.createElement(window.index.Footer));
|
||||
assert.lengthOf(mockedComponent.find('Footer'), 1);
|
||||
```
|
||||
|
||||
Your `Footer` component should return a `footer` element.
|
||||
|
||||
```js
|
||||
assert.equal(Enzyme.shallow(React.createElement(window.index.Footer)).type(), 'footer');
|
||||
```
|
||||
|
||||
Your `footer` should have at least three unordered lists.
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.shallow(React.createElement(window.index.Footer));
|
||||
assert.isAtLeast(mockedComponent.find('ul').length, 3);
|
||||
```
|
||||
|
||||
Each of your unordered lists should have at least two list items.
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.shallow(React.createElement(window.index.Footer));
|
||||
const uls = mockedComponent.find('ul');
|
||||
uls.forEach(ul => {
|
||||
assert.isAtLeast(ul.find('li').length, 2);
|
||||
});
|
||||
|
||||
assert.isAtLeast(uls.length, 1);
|
||||
```
|
||||
|
||||
Your `footer` should have at least one paragraph element.
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.shallow(React.createElement(window.index.Footer));
|
||||
assert.isAtLeast(mockedComponent.find('p').length, 1);
|
||||
```
|
||||
|
||||
You should have a copyright (`©`) symbol within one of your paragraphs.
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.shallow(React.createElement(window.index.Footer));
|
||||
let hasCopyright = false;
|
||||
mockedComponent.find('p').forEach(p => {
|
||||
if(p.text().includes('©')) {
|
||||
hasCopyright = true;
|
||||
}
|
||||
});
|
||||
assert.isTrue(hasCopyright);
|
||||
```
|
||||
|
||||
Your `footer` should have at least three links with the `href` value set to `#`.
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.shallow(React.createElement(window.index.Footer));
|
||||
assert.isAtLeast(mockedComponent.find('a[href="#"]').length, 3);
|
||||
```
|
||||
|
||||
None of your links should be empty.
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.shallow(React.createElement(window.index.Footer));
|
||||
mockedComponent.find('a').forEach(a => {
|
||||
assert.isAtLeast(a.text().length, 1);
|
||||
});
|
||||
assert.isAtLeast(mockedComponent.find('a').length, 1);
|
||||
```
|
||||
|
||||
Your `Footer` component should be rendered to the page's `#root` element.
|
||||
|
||||
```js
|
||||
const mockedComponent = Enzyme.mount(React.createElement(window.index.Footer));
|
||||
assert.equal(mockedComponent.html(), document.getElementById('root').innerHTML);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Reusable Footer Component</title>
|
||||
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
<script
|
||||
data-plugins="transform-modules-umd"
|
||||
type="text/babel"
|
||||
src="index.jsx"
|
||||
></script>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script
|
||||
data-plugins="transform-modules-umd"
|
||||
type="text/babel"
|
||||
data-presets="react"
|
||||
data-type="module"
|
||||
>
|
||||
import { Footer } from './index.jsx';
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<Footer />
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
```jsx
|
||||
export const Footer = () => {
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Reusable Footer Component</title>
|
||||
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
<script
|
||||
data-plugins="transform-modules-umd"
|
||||
type="text/babel"
|
||||
src="index.jsx"
|
||||
></script>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script
|
||||
data-plugins="transform-modules-umd"
|
||||
type="text/babel"
|
||||
data-presets="react"
|
||||
data-type="module"
|
||||
>
|
||||
import { Footer } from './index.jsx';
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<Footer />
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
.footer {
|
||||
background-color: #e3e1eb;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
text-decoration: none;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
color: #6366f1;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
color: #6b7280;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.footer-icons {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer-icons a {
|
||||
margin: 0 10px;
|
||||
font-size: 24px;
|
||||
color: #6b7280;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer-icons a:hover {
|
||||
color: #6366f1;
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
export const Footer = () =>
|
||||
<footer className="footer">
|
||||
<div className="footer-section">
|
||||
<ul>
|
||||
<li><a href="#">Fitness Dashboard</a></li>
|
||||
<li><a href="#">Services</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="#">Watch Videos</a></li>
|
||||
<li><a href="#">Discord</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="#">Privacy Policy</a></li>
|
||||
<li><a href="#">Terms & Conditions</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>© 2024 Fitness Dashboard. All Rights Reserved.</p>
|
||||
<div className="footer-icons">
|
||||
<a href="#">🎮</a>
|
||||
<a href="#">🐦</a>
|
||||
<a href="#">💻</a>
|
||||
<a href="#">🏀</a>
|
||||
</div>
|
||||
</footer>
|
||||
```
|
||||
@@ -548,6 +548,7 @@
|
||||
{
|
||||
"dashedName": "lecture-introduction-to-javascript-libraries-and-frameworks"
|
||||
},
|
||||
{ "dashedName": "lab-reusable-footer" },
|
||||
{ "dashedName": "lecture-working-with-data-in-react" },
|
||||
{ "dashedName": "review-react-basics" },
|
||||
{ "dashedName": "quiz-react-basics" }
|
||||
|
||||
Reference in New Issue
Block a user