I'm a beginner in programming, and I'm trying to build a page layout using the FLIP technique in React, but I'm stuck, so I'd like to ask for your help. For more information about the FLIP technique, please refer to the reference URL.
Quickly, what I would like to build is a page layout that consists of one main content and multiple sub-contents, as shown in the image below, and that can be repositioned with the main content by clicking on the sub-content.
I found a layout similar to the one I want to create by surfing the net, and I've included it below as a reference URL.
・What I tried.
I thought I could achieve this using hooks, CSS, and the react-flip-toolkit -library-. In fact, the code to swap the position of the two elements was easy to implement.
https://codesandbox.io/s/flip-l4o04?file=/src/index.js
However, I don't know how to write the code to control the behavior when the number of contents is increased.
The react-flip-toolkit uses the flipkey as a key, so if I just add more containers, onclick will fire all the containers in the flipper tag and it won't work.
https://codesandbox.io/s/flip-test-f3pqb?file=/src/index.js
Next, I rewrote some of the code.
const AnimatedSquare = () => {
const [active, setActive] = useState(false);
const toggleActive = () => setActive(prevState => !prevState);
const [active1, setActive1] = useState(false);
const toggleActive1 = () => setActive1(prevState => !prevState);
return (
<div>
<Flipper flipKey={active,active1}>
<div className="box">
<Flipped flipId="square">
<div
className={active ? "square" : "square-active"}
onClick={toggleActive}/>
</Flipped>
<Flipped flipId="square1">
<div
className={active1 ? "square-active-1" : "square1"}
onClick={toggleActive1}/>
</Flipped>
Only the part of the code that was rewritten now behaves independently, but this is not good enough because it cannot be repositioned with the main content.
・Question
What code should I write to make each container behave independently and swap its position with the main content as shown in the reference URL? I would appreciate your help.
~Reference URL~
・About the FLIP technique
・What I want to build like this
I think this is what you want
I just divide active state on squares and accumulate active square classes
If someday the link stops opening, I attach the code below
import React, { useCallback, useState } from "react";
import ReactDOM from "react-dom";
import { Flipper, Flipped } from "react-flip-toolkit";
import "./styles.css";
const AnimatedSquare = () => {
const [active, setActive] = useState({
square1: true,
square2: false,
square3: false,
square4: false,
square5: false
});
const handleActive = useCallback(
(id) => () =>
setActive({
square1: false,
square2: false,
square3: false,
square4: false,
square5: false,
[id]: true
}),
[]
);
return (
<div>
<Flipper flipKey={true}>
<div className="box">
<Flipped flipId="square1">
<div
className={`square1${active.square1 ? " active" : ""}`}
onClick={handleActive("square1")}
></div>
</Flipped>
<Flipped flipId="square2">
<div
className={`square2${active.square2 ? " active" : ""}`}
onClick={handleActive("square2")}
/>
</Flipped>
</div>
<Flipped flipId="square3">
<div
className={`square3${active.square3 ? " active" : ""}`}
onClick={handleActive("square3")}
/>
</Flipped>
<Flipped flipId="square4">
<div
className={`square4${active.square4 ? " active" : ""}`}
onClick={handleActive("square4")}
/>
</Flipped>
<Flipped flipId="square5">
<div
className={`square5${active.square5 ? " active" : ""}`}
onClick={handleActive("square5")}
/>
</Flipped>
</Flipper>
</div>
);
};
ReactDOM.render(<AnimatedSquare />, document.querySelector("#root"));
* {
box-sizing: border-box;
}
body {
justify-content: left;
align-items: left;
min-height: 100vh;
}
.box {
position: relative;
}
.square1 {
transition: all 1s;
margin-top: 10px;
margin-left: 5px;
margin-right: 5px;
width: 17rem;
height: 12rem;
cursor: pointer;
background-image: linear-gradient(45deg, rgb(0, 255, 0), rgb(71, 182, 108));
}
.square2 {
transition: all 1s;
margin-top: 10px;
margin-left: 5px;
margin-right: 5px;
width: 17rem;
height: 12rem;
cursor: pointer;
background-image: linear-gradient(45deg, rgb(255, 0, 255), rgb(182, 71, 158));
}
.square3 {
transition: all 1s;
margin-top: 10px;
margin-left: 5px;
margin-right: 5px;
width: 17rem;
height: 12rem;
cursor: pointer;
background-image: linear-gradient(
45deg,
rgb(113, 206, 234),
rgb(60, 107, 170)
);
}
.square4 {
transition: all 1s;
margin-top: 10px;
margin-left: 5px;
margin-right: 5px;
width: 17rem;
height: 12rem;
cursor: pointer;
background-image: linear-gradient(45deg, rgb(253, 72, 0), rgb(170, 110, 60));
}
.square5 {
transition: all 1s;
margin-top: 10px;
margin-left: 5px;
margin-right: 5px;
width: 17rem;
height: 12rem;
cursor: pointer;
background-image: linear-gradient(45deg, rgb(255, 0, 0), rgb(182, 71, 71));
}
.active {
position: fixed;
top: 0;
transform: translateX(300px);
width: 75rem;
height: 50rem;
cursor: pointer;
}
Related
I saw it on the Airbnb website. The skeleton would show, then the icons and images will be displayed individually. I would like to know how this is done.
Can anyone explain how this can be done? Code examples are fine too. Is there any library that makes this easy?
Curious in particular how to do this in React.
What I have in mind is to have a setTimeout function that shows each component per tick.
This is mostly a CSS challenge. The logic part is simply to pass from a loader to actual data. I created a simple working example below so you get a general idea:
function App() {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
fetch("https://randomuser.me/api/")
.then((res) => res.json())
// The setTimeout is there so you see the animation longer.
.then((data) => setTimeout(() => setUser(data.results[0]), 2000));
}, []);
return (
<div>
{!user ? (
<div className="user skeleton">
<div className="avatar"></div>
<div>
<div className="line"> </div>
<div className="line"></div>
</div>
</div>
) : (
<div className="user">
<img className="avatar" src={user.picture.medium} alt="" />
<div>
<div className="line"> {user.name.first + " " + user.name.last}</div>
<div className="line">{user.email}</div>
</div>
</div>
)}
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
#root{
min-height:100vh;
display:grid;
place-items:center;
}
.user {
background:rgb(250, 250, 250);
border-radius:1rem;
display: flex;
width:fit-content;
margin:auto;
align-items: center;
gap: 1rem;
padding: 1rem;
}
.user .avatar {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 50%;
}
.user.skeleton .avatar{
background-image: linear-gradient(90deg, #ddd 0px, #e8e8e8 40px, #ddd 80px);
}
.user .line {
margin-block: 0.5rem;
padding: 0 0.5rem;
}
.user.skeleton .line {
background-image: linear-gradient(90deg, #ddd 0px, #e8e8e8 40px, #ddd 80px);
animation: bg-lines 2s infinite linear;
border-radius: 1rem;
min-width: 200px;
min-height: 1rem;
}
#keyframes bg-lines {
0% {
background-position: -100px;
}
100% {
background-position: 140px;
}
}
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
I am building my own SpeedDial which is inspired by the Material UI one, but can't currently figure out how I can make the hidden buttons transition smoothly like they do in the example link above when the SpeedDial is hovered. It's not like they are sliding out, but each button appears one after the other smoothly. If you go to the link above, you will see what I mean. Select the "left" direction, since it is most like my example code.
Here is a quick screenshot of what the SpeedDial looks like, just in case you are wondering.
I have a working CodeSandbox where I have the entire Speed Dial built, but it's lacking the animation I am looking for. My initial thought was to not conditionally render the components, and have them always visible, but use visibility: hidden. However, I don't think doing that solves my problem, and won't allow for any transitions to be set.
For sake of completeness, I will include all of the code, but I highly recommend that you just mess around with the CodeSandbox, as it already works.
App.js
import { useState } from "react";
import SpeedDial from "./components/SpeedDial";
import SpeedDialAction from "./components/SpeedDialAction";
import "./styles.css";
const actions = [
{ label: "share", icon: "share " },
{ label: "print", icon: "print" },
{ label: "save", icon: "floppy-o" },
{ label: "copy", icon: "copy" }
];
export default function App() {
const [activeAction, setActiveAction] = useState("");
return (
<div className="App">
<SpeedDial
direction="left"
style={{
position: "absolute",
bottom: 40,
right: 40
}}
>
{actions.map((action) => (
<SpeedDialAction
key={action.label}
direction="left"
setActiveAction={setActiveAction}
hovered={action.label === activeAction}
label={action.label}
icon={action.icon}
/>
))}
</SpeedDial>
</div>
);
}
Basically, the SpeedDial component accepts children, specifically an array that will create several SpeedDialAction components. When the SpeedDial button is hovered, the SpeedDialAction components become visible next to the main SpeedDial button (the blue button). When the mouse is moved out of the SpeedDial, they disappear.
SpeedDial.js
import React, { useState } from "react";
import Icon from "./Icon";
import "./SpeedDial.css";
export default function SpeedDial({ children, direction, style }) {
const [hovered, setHovered] = useState(false);
return (
<div
className={`speed-dial ${direction}`}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => {
setHovered(false);
}}
onClick={() => {
setHovered(!hovered);
}}
style={style}
>
<button className="main-btn">
<Icon
name="plus"
style={{
transform: hovered ? "rotate(45deg" : "none",
transition: "transform ease .25s"
}}
/>
</button>
<div className={`action-wrapper ${direction}`}>{hovered && children}</div>
</div>
);
}
SpeedDial.defaultProps = {
direction: "right",
onClick: () => {}
};
Notice how in the action-wrapper that this is where we are conditionally rendering the SpeedDialAction components via the children prop. I am aware that conditionally rendering like this works pretty much like display: none, and won't allow me to animate it. That's why I hope you can help!
SpeedDial.css
.speed-dial {
display: inline-flex;
position: relative;
align-items: center;
width: auto;
}
.speed-dial.top {
flex-direction: column-reverse;
align-items: flex-start;
justify-content: center;
}
.speed-dial.bottom {
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
.speed-dial.left {
flex-direction: row-reverse;
}
.speed-dial > .main-btn {
background: rgb(25, 118, 210);
height: 60px;
width: 60px;
border-radius: 50%;
color: white;
font-size: 18px;
cursor: pointer;
box-shadow: rgb(0 0 0 / 20%) 0px 3px 5px -1px,
rgb(0 0 0 / 14%) 0px 6px 10px 0px, rgb(0 0 0 / 12%) 0px 1px 18px 0px;
}
.speed-dial > .action-wrapper {
display: flex;
}
.speed-dial > .action-wrapper.top {
flex-direction: column;
align-items: flex-start;
transform: translateX(7px);
}
.speed-dial > .action-wrapper.bottom {
flex-direction: column;
align-items: flex-start;
transform: translateX(7px);
}
SpeedDialAction.js
import React from "react";
import Icon from "./Icon";
import "./SpeedDialAction.css";
export default function SpeedDialAction({
icon,
label,
hovered,
setActiveAction,
direction,
onClick
}) {
return (
<div className="speed-dial-action" onClick={onClick}>
{hovered && <div className={`label ${direction}`}>{label}</div>}
<button
onMouseEnter={() => setActiveAction(label)}
onMouseLeave={() => setActiveAction("")}
className={direction}
>
<Icon name={icon} />
</button>
</div>
);
}
Nothing crazy going on here, I just added it for sake of thoroughness.
SpeedDialAction.css
.speed-dial-action {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.speed-dial-action button {
box-shadow: rgb(0 0 0 / 20%) 0px 3px 5px -1px,
rgb(0 0 0 / 14%) 0px 6px 10px 0px, rgb(0 0 0 / 12%) 0px 1px 18px 0px;
background: white;
height: 45px;
width: 45px;
border-radius: 50%;
font-size: 20px;
cursor: pointer;
}
.speed-dial-action button:hover {
background: #d8d8d8;
}
.speed-dial-action button.left {
margin-right: 15px;
}
.speed-dial-action button.right {
margin-left: 15px;
}
.speed-dial-action button.bottom {
margin-top: 15px;
}
.speed-dial-action button.top {
margin-bottom: 15px;
}
.speed-dial-action .label {
background: #565656;
color: white;
padding: 5px 8px;
position: absolute;
border-radius: 4px;
text-transform: capitalize;
}
.speed-dial-action .label.left {
top: -40px;
}
.speed-dial-action .label.right {
top: -40px;
}
I have a navbar with a hamburger button and i want to change the style (the bar color and the hamburger button style) on tap. I mention that i've tried using &:active, &.activeBar on the scss file.
For some reason the background color doesn t change and the hamburger button doesn't turn into an X
This is my SCSS file:
.topbar {
width: 100%;
background-color: $secondaryColor;
color: $mainColor;
height: 70px;
position: fixed;
top: 0;
z-index: 3;
transition: all 1s ease;
overflow: hidden;
.wrapper {
padding: 10px 30px;
display: flex;
align-items: center;
justify-content: space-between;
}
.left {
display: flex;
align-items: center;
margin: 10;
.itemContainer {
display: flex;
align-items: center;
.icon {
font-size: 36px;
margin-right: 5px;
&:hover {
color: blue;
}
}
span {
font-size: 15px;
font-weight: 700;
}
}
.logo {
font-size: 40px;
font-weight: 700;
text-decoration: none;
color: inherit;
margin-right: 40px;
}
}
.right {
.hamburger {
width: 32px;
height: 25px;
display: flex;
flex-direction: column;
justify-content: space-between;
span {
width: 100%;
height: 3px;
background-color: $mainColor;
transform-origin: left;
transition: all 0.5s ease;
}
}
}
&.activeBar {
background-color: $mainColor;
color: $secondaryColor;
.hamburger span {
&:first-child {
background-color: $secondaryColor;
transform: rotate(45deg);
}
&:nth-child(2) {
opacity: 0;
}
&:last-child {
background-color: $secondaryColor;
transform: rotate(-45deg);
}
}
}
}
And this is where i try to change the classname in my js file:
import React from 'react';
import styles from './topbar.module.scss';
import { Facebook } from '#material-ui/icons';
const Topbar = ({ open, toggle }) => {
function menuToggled() {
toggle(!open);
console.log('menuToggled', open);
}
return (
<div className={`${styles.topbar} ${open ? 'active' : ''}`}>
<div className={styles.wrapper}>
<div className={styles.left}>
<a href='#slider' className={styles.logo}>
Muntenia cu gust
</a>
<div className={styles.itemContainer}>
<Facebook
className={styles.icon}
onClick={() => window.open('https://stackoverflow.com/')}
/>
</div>
</div>
<div className={styles.right}>
<div className={styles.hamburger} onClick={menuToggled}>
<span className={styles.line1}></span>
<span className={styles.line2}></span>
<span className={styles.line3}></span>
</div>
</div>
</div>
</div>
);
};
export default Topbar;
In your code, you're adding active as a class in the "global" scope. By default with css-modules, all classes are output in "local" scope. So your .topbar class, after compilation, would look something like: .Topbar_topbar__abc12 ("abc12" would be a hash).
So, the .active class is also being compiled to local scope, looking something like: .Topbar_active_def34
In your markup, you're toggling the .active class as a string rather than as styles.active. So, on output, you're expecting your "active" class to be .Topbar_topbar__abc12.active when in actuality, your stylesheet is declaring .Topbar_topbar_abc12.Topbar_active_def32 as the "active" class.
By declaring .active as being in the global scope, the output would then be as expected. Try adding the :global flag to your stylesheet:
...
&:global(.active) {
background-color: $mainColor;
color: $secondaryColor;
.hamburger span {
&:first-child {
background-color: $secondaryColor;
transform: rotate(45deg);
}
&:nth-child(2) {
opacity: 0;
}
&:last-child {
background-color: $secondaryColor;
transform: rotate(-45deg);
}
}
}
...
use classnames package (https://www.npmjs.com/package/classnames) to combine classes that are on the same element, the rest is handled the same as in vanilla html/css/js
In your code you use active it also should come from styles and be combined using:
classnames('styles.ElementClassName', active && styles.active)
Adding the active selector on the regular .hamburger class should trigger on touch. Also the classname in you scss is activeBar but in your react app is only active.
&.clicked {
background-color: $mainColor;
color: $secondaryColor;
.hamburger span {
&:first-child {
background-color: $secondaryColor;
transform: rotate(45deg);
}
&:nth-child(2) {
opacity: 0;
}
&:last-child {
background-color: $secondaryColor;
transform: rotate(-45deg);
}
}
}
If you want to change the topBar also then you'll need to change your TopBar component a bit
import React, { useState } from 'react';
import styles from './topbar.module.scss';
import { Facebook } from '#material-ui/icons';
const Topbar = ({ open, toggle }) => {
const [isClicked, setIsClicked] = useState(false);
function menuToggled() {
toggle(!open);
console.log('menuToggled', open);
}
return (
<div className={`${styles.topbar} ${open ? 'active' : ''} ${ isClicked && ' clicked'}`}>
<div className={styles.wrapper}>
<div className={styles.left}>
<a href='#slider' className={styles.logo}>
Muntenia cu gust
</a>
<div className={styles.itemContainer}>
<Facebook
className={styles.icon}
onClick={() => window.open('https://stackoverflow.com/')}
/>
</div>
</div>
<div className={styles.right}>
<div className={styles.hamburger} onClick={menuToggled} onTouchStart={(e) => setIsClicked(true)}>
<span className={styles.line1}></span>
<span className={styles.line2}></span>
<span className={styles.line3}></span>
</div>
</div>
</div>
</div>
);
};
I am working on a React application and I am using Redux to store the state. I have the following code:
request.component.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Loading from '../loading/loading.component';
import { changeRequestStatus } from '../../redux/requests/requests.actions';
import { RESOLVED, AWAITING_WAIT_STAFF } from '../../redux/requests/requests.status-types'
import './request.styles.scss';
class Request extends Component {
state = { isLoading: false }
render() {
const { _id, table_no, timestamp, description, status } = this.props.request;
const { user, changeRequestStatus } = this.props;
return (
<>
{this.state.isLoading ? <Loading /> : null}
<div className="request-box">
<div className="request-details">
<div>
<h1 style={{ color: status === AWAITING_WAIT_STAFF ? "#28bfa6" : "#f5a953" }}>Table {table_no}, {new Date(timestamp).toLocaleString()}</h1>
<h2>{description}</h2>
</div>
<div className="status-button">
<button
className="request-button"
onClick={async () => {
this.setState({ isLoading: true })
await changeRequestStatus(_id, status === AWAITING_WAIT_STAFF ? user.username : RESOLVED)
this.setState({ isLoading: false })
}} style={{ background: status === AWAITING_WAIT_STAFF ? "linear-gradient(to right, rgba(141,227,227,1) 0%, rgba(114,240,218,1) 100%)" : "linear-gradient(to right, rgba(255,213,94,1) 0%, rgba(246,170,123,1) 100%)" }}>
{status}
</button>
</div>
</div>
</div>
</>
)
}
}
const mapStateToProps = (state) => {
return {
requests: state.requests.requests,
user: state.user.currentUser
}
}
export default connect(mapStateToProps, { changeRequestStatus })(Request);
request.styles.scss:
.request-box {
border: 1px solid #c3c9c8;
height: 200px;
max-width: 100%;
border-radius: 5px;
position: relative;
background-color: white;
font-family: Helvetica;
box-shadow: 0 10px 6px -6px #ededed;
margin: 10px;
}
.request-details {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 0 30px;
height: 100%;
h1 {
font-size: 30px;
color: #28bfa6;
text-align: left;
}
h2 {
font-size: 22px;
text-align: left;
}
}
.status-button {
padding-bottom: 25px;
width: 100%;
#media (min-width: 1000px) {
width: auto;
padding-right: 20px;
padding-left: 100px;
}
}
.request-button {
height: 50px;
font-size: 19px;
font-weight: 600;
border: none;
border-radius: 5px;
padding: 10px 25px;
background-size: 150% auto;
background: linear-gradient(to right, rgba(141,227,227,1) 0%, rgba(114,240,218,1) 100%);
cursor: pointer;
&:hover {
background: #2de1c2;
}
}
In my Request component, I am changing the background property of my request-button div depending on the value of the status variable.
However, I would like to change the request-buttton:hover property depending on the value of status variable in my Request component.
I am not sure what the correct syntax would be to achieve this. Any insights are appreciated.
Create different CSS classes for each button color/status. Then use a ternary operator to apply the CSS class to the button when status changes. Check the status via Redux and then apply a CSS className like this.
I can't seem to get the hideMoreDetails() function to work on this component. Nothing gets logged in the console and state remains unchanged when clicking on the 'close-more-info-cross' div.
Any clue? Could it be a stacking issue?
import { useState } from "react";
import TracklistAndPlayer from "./TracklistAndPlayer";
function ReleaseEntry(props) {
const [playerDisplayId, setPlayerDisplayId] = useState(null);
const [showMoreDetails, setShowMoreDetails] = useState(true);
function showPlayer(event) {
setPlayerDisplayId(event.target.getAttribute("data-tag"));
}
function hideMoreDetails() {
console.log("hide more details");
setShowMoreDetails(false);
}
function renderSection(section) {
return (
<div className="section">
{section[0] && section.map(paragraph => <p>{paragraph.text}</p>)}
</div>
);
}
return (
props.releases &&
props.releases.map(function(release, index) {
let tracklist = Object.values(release.data.tracks[0]);
return (
<div>
<div className="release-entry-wrapper">
<div onClick={showPlayer}>
<img
className="release-cover"
key={`cover${index}`}
src={release.data.cover.url}
alt="release-cover"
data-tag={index}
/>
<div className="release-details">
<div
key={`artist${index}`}
className="artist-name"
data-tag={index}
>
{release.data.artist[0].text}
</div>
<div
key={`title${index}`}
className="release-name"
data-tag={index}
>
<img
className="mini-play"
src="/img/play-song.png"
alt="mini-play"
data-tag={index}
/>
{release.data.title[0].text}
</div>
</div>
</div>
{parseInt(playerDisplayId) === index && showMoreDetails && (
<div>
{
<div className="more-info-about-release">
<img
className="close-more-info-cross"
src="/img/cross.png"
alt="cross"
onCLick={hideMoreDetails}
/>
<div className="tracklist-details">
{renderSection(release.data.tracklist)}
</div>
<div className="about-release">
{renderSection(release.data.about)}
</div>
<div className="credits">
{renderSection(release.data.credits)}
</div>
</div>
}
<TracklistAndPlayer
tracklist={tracklist}
setPlayerDisplayId={setPlayerDisplayId}
/>
</div>
)}
</div>
<style jsx>{`
.release-entry-wrapper {
padding-left: var(--global-margin);
padding-right: var(--global-margin);
font-family: var(--font1);
font-size: var(--standard-font-size);
text-transform: uppercase;
}
.release-cover {
cursor: pointer;
width: 100%;
transition: transform 0.5s;
}
.release-cover:hover {
transform: scale(1.005);
}
.release-details {
padding-top: 1rem;
padding-bottom: 1rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
text-align: center;
letter-spacing: 0.05rem;
transition: opacity 0.3s;
transition: transform 0.5s;
cursor: pointer;
}
.release-details:hover {
opacity: 0.85;
transform: scale(1.01);
}
.mini-play {
width: 0.5rem;
margin-right: 0.7rem;
}
.artist-name,
.release-name {
padding-top: 0.5rem;
padding-bottom: 0.3rem;
}
.tracklist-details {
margin-bottom: 2rem;
}
.close-more-info-cross {
width: 0.6rem;
position: absolute;
right: 0;
top: 0;
transition: transform 0.3s;
opacity: 0.7;
cursor: pointer;
}
.close-more-info-cross:hover {
width: 0.7rem;
opacity: 1;
}
.more-info-about-release {
text-transform: none;
margin-bottom: 2rem;
position: relative;
}
.more-info-section-title {
margin-bottom: 1rem;
margin-top: 1rem;
}
.about-release {
margin-bottom: 2rem;
}
`}</style>
</div>
);
})
);
}
export default ReleaseEntry;
PS : link to the entire project: https://github.com/jeremieemk/elis-records-website
You have a small typo on your onClick method:
<img
className="close-more-info-cross"
src="/img/cross.png"
alt="cross"
onCLick={hideMoreDetails}
^~~~~~ 👻
/>
It should be onClick, not onCLick.
If you open the browser's console, you would probably see a red warning such as onCLick method is not a dom property or such. I recommend keeping the console open when working with React, it will show many useful warnings.
In my case, I had to rename the file name of the component in lowercase. After that, all click listeners worked. Ex: Footer.js (old file name) -> footer.js
Before that, I couldn't able to see changes in real-time (not only click listeners, UI changes didn't appear). I had to restart the server to see changes.