CSS: change multiple image position when hover - javascript

I have one large image on the top and three smaller images on the bottom of my page. I want to make it so when you hover the small image (it's also a link which goes to different place) the large image changes to this small image. So clearly explained, two images change/swap places. And when I unhover the large image changes back. I tried a couple solution but it didn't work so I'm in trouble, can you please help me?
I'm looking for CSS/Javascript-solution NOT JQUERY!
Thanks and sorry for bothering!
import React from "react";
import { Link } from "react-router-dom";
import "../screens/StyleName.css";
import { Card } from "react-bootstrap";
import Categories from "../components/Categories.json";
import { useLocation } from "react-router-dom";
// images:
import LargeImage from "../images/largeimage.jpg;
import SmallImage1 from "../images/small_image1.jpg";
import SmallImage2 from "../images/small_image2.jpg";
import SmallImage3 from "../images/small_image3.jpg";
import Placeholder from "../images/placeholder.jpg";
function FunctionName() {
let location = useLocation();
const ShowImage = (ImageName) => {
if (ImageName === "SmallImage1") {
return SmallImage1;
} else if (ImageName === "SmallImage2") {
return SmallImage2;
} else if (ImageName === "SmallImage3") {
return SmallImage3;
} else {
return Placeholder;
}
};
const AllCategories = () => {
return Categories.map((category, index) => {
const data = () => {
if (category.children) {
return { data: category.children };
} else {
return { data: undefined };
}
};
return (
<div key={index} className="category-card">
<Card style={{ border: "none" }}>
<Link
to={{
pathname: location.pathname + "/" + category.name,
state: data(),
}}
className="category-link link"
>
<div className="img__wrap">
<Card.Img
variant="top"
alt="card-img-top"
className="small-img-down img-responsive img__img"
src={ShowImage(category.name)}
/>
<p className="img__description">{category.name}</p>
</div>
</Link>
</Card>
</div>
);
});
};
return (
<div className="maincategory-top">
<div className="category-container">
<div className="col-sm-8">
<Link to="/product" className="category-link link">
<Card.Img
alt="card-img-top"
className="big-img img-responsive"
src={LargeImage}
/>
</Link>
<div className="row">{AllCategories()}</div>
</div>
</div>
</div>
);
}
export default FunctionName;
CSS:
.big-img {
display: block;
margin-left: auto;
margin-right: auto;
margin-bottom: 3%;
overflow: auto;
width: 470px;
height: 300px;
}
.category-container {
padding-left: 8%;
padding-bottom: 10%;
margin-top: 10%;
}
.card-title-main {
font-size: 10px;
letter-spacing: 1.2px;
font-weight: normal;
text-transform: uppercase;
}
a.category-link:link {
color: black;
background-color: transparent;
text-decoration: none;
}
a.category-link:visited {
color: black;
background-color: transparent;
text-decoration: none;
}
a.category-link:hover {
color: black;
background-color: transparent;
text-decoration: none;
}
a.category-link:active {
color: black;
background-color: transparent;
text-decoration: none;
}
.category2 {
margin-top: 1%;
text-align: center;
}
.small-img-down {
width: 120px;
height: 80px;
margin-right: 13px;
margin-bottom: 13px;
display: inline-block;
}
.small-img-down:hover {
transform: scale(0.9);
}
.category-card {
display: block;
margin-left: auto;
margin-right: auto;
}
.img__wrap:hover .img__description {
visibility: visible;
opacity: 1;
padding: 20px;
}
.img__description {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
text-transform: uppercase;
background: rgba(245, 245, 245, 0.363);
color: black;
letter-spacing: 1.2px;
text-align: center;
visibility: hidden;
font-size: 16px;
opacity: 0;
transition: opacity 0.2s, visibility 0.2s;
}

() => {
const default_large = "image_one"
const [largeImage, setLargeImage] = useState(default_large);
return(
<div>
<img
src={image_one}
onMouseEnter={() => setLargeImage("image_one")}
onMouseOut={() => setLargeImage(default_large)}
className={largeImage === "image_one" ? "large" : "small"}
/>
<img
src={image_two}
onMouseEnter={() => setLargeImage("image_two")}
onMouseOut={() => setLargeImage(default_large)}
className={largeImage === "image_two" ? "large" : "small"}
/>
<img
src={image_three}
onMouseEnter={() => setLargeImage("image_three")}
onMouseOut={() => setLargeImage(default_large)}
className={largeImage === "image_three" ? "large" : "small"}
/>
</div>
)
}
In your css.
img.large{
width: 500px
}
img.small{
width: 100px
}
Explanation:
Keep track of what image is large in your component state. Whenever an image is hovered on, update the state accordingly. After the hover event, return the state to its default state. The image will have the "large" class while its being hovered on, and when no image is being hovered on, the default large image will be large.

Related

How can I handle the state separatley when I use map function?

I'm making a menu bar and I've already made it. And This is the code I made. codesandbox
But, as you can see too many states were used. So I want to do refactoring the code by using map function.
I succeeded in loading each data. But don't know how to handle state separately.
I only declared one state and want it to be changed separately when it is hovered.
Question
How can I handle state separately when I use map function??
Originally, the code has a click function, but now I'll ask it to work only when it is hovered.
I think you'll know what I'm asking if you look at the codesandbox below. Thanks
code
import { useState } from "react";
import styled from "styled-components";
import dummy from "./database/DB.json";
export default function App() {
const [data, setData] = useState(dummy.products);
const [hoverCharacter, setHoverCharacter] = useState(false);
return (
<Wrap>
<div className="characterList">
<div className="word">
<h2 className="word__font">Like Pick!</h2>
</div>
<div className="list">
<ul className="list__ul">
{data.map((item) => {
return (
<div className="list__box">
<Card
hoverCharacter={hoverCharacter}
setHoverCharacter={setHoverCharacter}
key={item.id}
item={item}
/>
</div>
);
})}
</ul>
</div>
</div>
</Wrap>
);
}
function Card({ item, hoverCharacter, setHoverCharacter }) {
return (
<CardWrap item={item}>
<div
onMouseEnter={() => {
setHoverCharacter(true);
}}
onMouseLeave={() => {
setHoverCharacter(false);
}}
className={`list__wrap ${hoverCharacter ? "active" : ""}`}
>
<div className={`list__img ${hoverCharacter ? "active" : ""}`} />
{item.nameKr}
</div>
</CardWrap>
);
}
const Wrap = styled.div`
position: relative;
top: calc(50vh - 100px);
width: 1200px;
display: inline-flex;
.characterList {
}
.word {
margin: 0 auto;
width: 1200px;
}
.word__font {
font-family: "SFCompactDisplay-Bold", sans-serif;
font-weight: bold;
font-size: 38px;
margin-bottom: 25px;
text-align: center;
}
.list {
margin: 0 auto;
width: 1200px;
padding-bottom: 20px;
}
.list__ul {
display: inline-flex;
list-style: none;
}
.list__box {
margin: 0 9px;
text-align: center;
font-size: 17px;
}
`;
const CardWrap = styled.div`
.list__wrap {
color: #999;
font-size: 13px;
display: inline-block;
text-align: center;
}
.list__wrap.active {
color: #333;
font-size: 13px;
display: inline-block;
text-align: center;
}
.list__img {
background-image: url(https://www.kakaofriendsgolf.com/img/v3_img_sprites_friends#3x.png);
background-repeat: no-repeat;
width: 70px;
height: 70px;
background-size: 100%;
margin-bottom: 5px;
background-position: 0 ${(props) => props.item.greyed}%;
}
.list__img.active {
background-image: url(https://www.kakaofriendsgolf.com/img/v3_img_sprites_friends#3x.png);
background-repeat: no-repeat;
width: 70px;
height: 70px;
background-size: 100%;
margin-bottom: 5px;
background-position: 0 ${(props) => props.item.blacked}%;
}
`;
json file
{
"products": [
{
"id": "1",
"name": "choosik",
"nameKr": "춘식이",
"greyed": 17.647059,
"blacked": 11.764706
},
{
"id": "2",
"name": "ryan",
"nameKr": "라이언",
"greyed": 88.235295,
"blacked": 82.352941
},
{
"id": "3",
"name": "apeach",
"nameKr": "어피치",
"greyed": 5.882353,
"blacked": 0
}
]
}
codesandbox
https://codesandbox.io/s/characterselectmap-t2mqef?file=/src/App.js:0-2857

React component overlap each other

component Publisher.js and other child components projectStatus.js are overlapping each other when Render the Child component. I don't know what's going wrong and how to fix this. You can see the image
this is my Publisher.js
//import useState hook to create menu collapse state
import React, { useState } from "react";
import {NavLink, Outlet} from "react-router-dom"
//import react pro sidebar components
import {
ProSidebar,
Menu,
MenuItem,
SidebarHeader,
SidebarFooter,
SidebarContent,
} from "react-pro-sidebar";
//import icons from react icons
import { FaFileContract } from "react-icons/fa";
import { FiLogOut} from "react-icons/fi";
import { HiDocumentReport } from "react-icons/hi";
import { BiCog } from "react-icons/bi";
import { GiHamburgerMenu } from "react-icons/gi";
//import sidebar css from react-pro-sidebar module and our custom css
import "react-pro-sidebar/dist/css/styles.css";
import "./publisherCss.css";
const Publisher = () => {
//create initial menuCollapse state using useState hook
const [menuCollapse, setMenuCollapse] = useState(false)
//create a custom function that will change menucollapse state from false to true and true to false
const menuIconClick = () => {
//condition checking to change state from true to false and vice versa
menuCollapse ? setMenuCollapse(false) : setMenuCollapse(true);
};
return (
<>
<div id="sidebarHeader">
{/* collapsed props to change menu size using menucollapse state */}
<ProSidebar collapsed={menuCollapse}>
<SidebarHeader>
<div className="logotext">
{/* small and big change using menucollapse state */}
<p>{menuCollapse ? "Evc" : "Publisher "}</p>
</div>
<div className="closemenu" onClick={menuIconClick}>
{/* changing menu collapse icon on click */}
{menuCollapse ? (
<GiHamburgerMenu/>
) : (
<GiHamburgerMenu/>
)}
</div>
</SidebarHeader>
<SidebarContent>
<Menu iconShape="square">
<NavLink to="/publisher/projectstatus"> <MenuItem icon={<FaFileContract />}>Project status</MenuItem> </NavLink>
<MenuItem icon={<HiDocumentReport />}>All project</MenuItem>
<MenuItem icon={<BiCog />}>Settings</MenuItem>
</Menu>
</SidebarContent>
<SidebarFooter>
<NavLink to="/login">
<Menu iconShape="square">
<MenuItem icon={<FiLogOut />}>Logout</MenuItem>
</Menu>
</NavLink>
</SidebarFooter>
</ProSidebar>
</div>
<Outlet />
</>
)
}
export default Publisher;
Publisher.css
#sidebarHeader {
position: absolute;
width: 220px;
display: flex;
}
#sidebarHeader .pro-sidebar {
height: 100vh;
/* position: absolute; */
}
#sidebarHeader .closemenu {
color: rgb(0,7,61);
position: absolute;
right: 0;
z-index: 9999;
line-height: 20px;
border-radius: 50%;
font-weight: bold;
font-size: 22px;
top: 55px;
cursor: pointer;
}
#sidebarHeader .pro-sidebar {
width: 100%;
min-width: 100%;
}
#sidebarHeader .pro-sidebar.collapsed {
width: 80px;
min-width: 80px;
}
#sidebarHeader .pro-sidebar-inner {
background-color: white;
box-shadow: 0.5px 0.866px 2px 0px rgba(0, 0, 0, 0.15);
}
#sidebarHeader .pro-sidebar-inner .pro-sidebar-layout {
overflow-y: hidden;
}
#sidebarHeader .pro-sidebar-inner .pro-sidebar-layout .logotext p {
font-size: 20px;
padding: 10px 20px;
color: #000;
font-weight: bold;
}
#sidebarHeader .pro-sidebar-inner .pro-sidebar-layout ul {
padding: 0 5px;
}
#sidebarHeader .pro-sidebar-inner .pro-sidebar-layout ul .pro-inner-item {
color: #000;
margin: 10px 0px;
font-weight: bold;
}
#sidebarHeader .pro-sidebar-inner .pro-sidebar-layout ul .pro-inner-item .pro-icon-wrapper {
background-color: #fbf4cd;
color: #000;
border-radius: 3px;
}
#sidebarHeader .pro-sidebar-inner .pro-sidebar-layout ul .pro-inner-item .pro-icon-wrapper .pro-item-content {
color: #000;
}
#sidebarHeader .pro-sidebar-inner .pro-sidebar-layout .active {
background-image: linear-gradient(0deg, #fece00 0%, #ffe172 100%);
}
#sidebarHeader .logo {
padding: 20px;
}
#media only screen and (max-width: 720px) {
html {
overflow: hidden;
}
}
.nav-link .active{
background-color: #ffe172;
}
I think I am doing some wrong CSS override property but I am unable to understand what's wrong I am doing. if anyone know please correct me
if anyone knows how to fix this please tell me. it's appreciated
update:
After updating the CSS display: flex it show the child content in flex but the problem is, I specified width: 220px for the sidebar but the child content not go above the 220px width. you can see the image.
Now how can I fix this to a child can use width?
Your picture is not complete, most likely it's styles or wrong location of "Outlet". I made simplified example, I hope it helps.

custom css are applied randomly

I use scss and styled-components together. Recently I don't know why I started to see a race situation over which styles get applied on the final render. Sometimes, usually, when the app is loaded for the first/second time, styles from scss gets applied and when I do a couple of hard refreshes my custom styles written with styled-components get applied. Do you have any idea why is this happening? I don't want to put it! important everywhere to fix it. I would like to understand it.
The below screenshot shows that _sidebar.scss overrode .sidebar styles I wrote.
after some hard refreshes:
after some refreshes, it is another way around:
Here is a component
/* eslint-disable jsx-a11y/anchor-is-valid */
import React from 'react';
import SidebarLinks from './SidebarLinks';
import FormSelectPrinter from "../printer/FormSelectPrinter"
import sidebarData from "../data/";
import {ModalConsumer} from "../components/ModalContext";
import Modal from "../components/Modal";
import { createGlobalStyle } from "styled-components";
const GlobalSidebarStyle = createGlobalStyle`
.sidebar {
background-color: #303b4a;
padding-left: 0;
padding-right: 0;
padding-bottom:40px;
width: 240px;
}
.user__info {
background: #607D8B;
}
.user__info:hover {
background: #607D8B;
}
.user__name {
color: #fff;
}
.user__email {
color: #303b4a;
}
.dropdown-user-menu {
background: #607D8B;
position: relative;
width: 100%;
float: none;
}
.dropdown-select-language {
background: rgba(96, 125, 139, 0.6);
box-shadow: 0 4px 20px rgba(0,0,0,0.9);
right: -26px;
top: 53px;
user-select: none;
}
.dropdown-user-item:hover, .dropdown-user-item:focus {
color: black !important;
}
.dropdown-user-item {
color: white !important;
}
.navigation {
li {
a {
color: #adb5bd;
padding-left: 15px;
&.active {
color: white !important;
}
}
}
}
.navigation li:not(.navigation__active):not(.navigation__sub--active) a:hover {
background-color: rgba(0, 0, 0, 0.19);
color: white;
}
.id-green {
background-color: #73983F;
}
.language-text-color {
color: white !important;
}
.profile_image_style {
border-radius: 100%;
width: 80px;
height: 80px;
}
`
function Sidebar(props) {
const ModalSelectPrinter = ({onClose, ...otherProps}) => {
return (
<Modal>
<FormSelectPrinter onClose={onClose} md={12}></FormSelectPrinter>
</Modal>
)
};
return (
<>
<GlobalSidebarStyle />
<aside className={`sidebar ${props.sidebarToggled? 'toggled' : ''}`}>
<div className="scrollbar-inner">
<div style={{margin: '50px 0 30px 0'}} onClick={() => props.toggleUserInfo()}>
<div className={`user__info ${props.userInfoShown? 'show' : ''}`}>
<div style={{width: '100%', textAlign: 'center'}}>
<div className="user__name">{user.person === null ? user.name : user.person.full_name}</div>
<div className="user__email">{user.email}</div>
</div>
</div>
<div
className={`dropdown-menu dropdown-user-menu ${props.userInfoShown? 'show' : ''}`}>
<ModalConsumer>
{({showModal}) => (
<a className="dropdown-item dropdown-user-item" onClick = {()=>showModal(ModalSelectPrinter, {})}>Select</a>
)}
</ModalConsumer>
<a className="dropdown-item dropdown-user-item" onClick={() => props.logout()}>Log out</a>
</div>
</div>
<SidebarLinks sidebarData={sidebarData} toggleSidebar={props.toggleSidebar}></SidebarLinks>
</div>
</aside>
</>
)
}
export default Sidebar;

Custom elements in iteration require 'v-bind:key' directives in Vue.js

I have question about Vue.js. How can i fix this? I didn't find anything in documentation. I've got this error: "[vue/require-v-for-key]
Elements in iteration expect to have 'v-bind:key' directives."
And this: "Elements in iteration expect to have 'v-bind:key' directives."
I have this i my Roulette.vue
<template>
<div class="roulette">
<h1>Roulette</h1>
<div class="radio" v-for="genre in genres"> **here**
<input
#change="onGenrePick"
type="radio"
name="genre"
v-bind:id="genre.id"
v-bind:value="genre.id">
<label v-bind:for="genre.id">{{genre.name}}</label>
</div>
<Button class="btn" :onClick="roll">Roll</Button>
<MovieCard
v-if="!!movie"
v-bind:image="serviceGetImagePath(movie.poster_path)"
v-bind:title="movie.title"
v-bind:releaseDate="serviceFormatYear(movie.release_date)"
v-bind:id="movie.id"
v-bind:voteAverage="movie.vote_average"/>
</div>
</template>
<script>
import MovieCard from '../components/MovieCard'
import Button from '../components/Button'
import {movieService} from '../mixins/movieService'
export default {
name: 'Roulette',
components: {Button, MovieCard},
mixins: [movieService],
mounted: async function () {
this.genres = await this.serviceGetGenres()
},
data: () => ({
genres: [],
pickedGenres: new Set(),
movie: null
}),
methods: {
async roll() {
const genreIds = Array.from(this.pickedGenres)
const movies = await this.serviceGetMoviesByGenre(genreIds)
this.movie = movies[this.getRandom(movies.length)]
},
onGenrePick(e) {
this.pickedGenres.add(e.target.value)
},
getRandom(max) {
return Math.floor(Math.random() * max - 1)
}
}
}
</script>
<style scoped lang="scss">
.roulette {
margin: 40px;
}
.btn {
display: block;
min-width: 220px;
}
.radio {
display: inline-block;
margin: 20px 10px;
> label {
margin-left: 5px;
}
}
</style>
And in my UpcomingMovies.vue also
<template>
<div class="wrapper" v-if="movies.length">
<MovieCard
v-for="movie in movies" **here**
v-bind:image="serviceGetImagePath(movie.poster_path)"**here**
v-bind:title="movie.title"**here**
v-bind:releaseDate="serviceFormatYear(movie.release_date)"**here**
v-bind:id="movie.id"**here**
v-bind:voteAverage="movie.vote_average"/>**here**
<div class="loader">
<Button class="loader__btn" :onClick="loadNextPage">Load</Button>
</div>
<router-link :to="routes.roulette.path">
<div class="roulette">
<img src="../assets/roulette.png" alt="Roulette">
</div>
</router-link>
</div>
<Loader v-else/>
</template>
<script>
import Button from '../components/Button'
import MovieCard from '../components/MovieCard'
import Loader from '../components/Loader'
import { movieService } from '../mixins/movieService'
import routes from '../router/routes'
export default {
name: 'UpcomingMovies',
mixins: [movieService],
components: { Button, MovieCard, Loader },
data: () => ({
movies: [],
page: 1,
routes
}),
mounted() {
this.getMovies(this.page)
},
methods: {
getMovies: async function (page) {
const movies = await this.serviceGetMovies(page)
this.movies.push(...movies)
},
loadNextPage() {
this.getMovies(++this.page)
}
}
}
</script>
<style scoped lang="scss">
.wrapper {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
margin-top: 40px;
margin-bottom: 40px;
}
.loader {
width: 100%;
text-align: center;
margin-top: 40px;
margin-bottom: 40px;
&__btn {
border: 5px dashed white;
background-color: transparent;
border-radius: 50%;
width: 80px;
height: 80px;
font-weight: bold;
text-transform: uppercase;
transition: border-radius 100ms ease-in-out, width 120ms ease-in-out 120ms;
&:hover {
border-radius: 0;
background-color: rgba(white, 0.1);
width: 200px;
}
}
}
.roulette {
cursor: pointer;
position: fixed;
right: 25px;
bottom: 25px;
> img {
opacity: .8;
animation: rotate 5s infinite;
width: 70px;
height: auto;
transition: opacity 220ms linear;
&:hover {
opacity: 1;
}
}
}
#keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style>
Vue internally uses unique keys for each loop to determine which element to update at which point during an update process. Therefore every v-for needs a v-bind:key attribute to work properly. In your special case this would be as the following:
<div class="radio" v-for="genre in genres" v-bind:key="someUniqueId"> **here**
You can use the current loop index as ID or anything else.

Why won't my React accordion animation work?

I've implemented my own responsive accordion in React, and I can't get it to animate the opening of a fold.
This is especially odd because I can get the icon before the title to animate up and down, and, other than the icon being a pseudo-element, I can't seem to see the difference between the two.
JS:
class Accordion extends React.Component {
constructor(props) {
super(props);
this.state = {
active: -1
};
}
/***
* Selects the given fold, and deselects if it has been clicked again by setting "active to -1"
* */
selectFold = foldNum => {
const current = this.state.active === foldNum ? -1 : foldNum;
this.setState(() => ({ active: current }));
};
render() {
return (
<div className="accordion">
{this.props.contents.map((content, i) => {
return (
<Fold
key={`${i}-${content.title}`}
content={content}
handle={() => this.selectFold(i)}
active={i === this.state.active}
/>
);
})}
</div>
);
}
}
class Fold extends React.Component {
render() {
return (
<div className="fold">
<button
className={`fold_trigger ${this.props.active ? "open" : ""}`}
onClick={this.props.handle}
>
{this.props.content.title}
</button>
<div
key="content"
className={`fold_content ${this.props.active ? "open" : ""}`}
>
{this.props.active ? this.props.content.inner : null}
</div>
</div>
);
}
}
CSS:
$line-color: rgba(34, 36, 38, 0.35);
.accordion {
width: 100%;
padding: 1rem 2rem;
display: flex;
flex-direction: column;
border-radius: 10%;
overflow-y: auto;
}
.fold {
.fold_trigger {
&:before {
font-family: FontAwesome;
content: "\f107";
display: block;
float: left;
padding-right: 1rem;
transition: transform 400ms;
transform-origin: 20%;
color: $line-color;
}
text-align: start;
width: 100%;
padding: 1rem;
border: none;
outline: none;
background: none;
cursor: pointer;
border-bottom: 1px solid $line-color;
&.open {
&:before {
transform: rotateZ(-180deg);
}
}
}
.fold_content {
display: none;
max-height: 0;
opacity: 0;
transition: max-height 400ms linear;
&.open {
display: block;
max-height: 400px;
opacity: 1;
}
}
border-bottom: 1px solid $line-color;
}
Here's the CodePen: https://codepen.io/renzyq19/pen/bovZKj
I wouldn't conditionally render the content if you want a smooth transition. It will make animating a slide-up especially tricky.
I would change this:
{this.props.active ? this.props.content.inner : null}
to this:
{this.props.content.inner}
and use this scss:
.fold_content {
max-height: 0;
overflow: hidden;
transition: max-height 400ms ease;
&.open {
max-height: 400px;
}
}
Try the snippet below or see the forked CodePen Demo.
class Accordion extends React.Component {
constructor(props) {
super(props);
this.state = {
active: -1
};
}
/***
* Selects the given fold, and deselects if it has been clicked again by setting "active to -1"
* */
selectFold = foldNum => {
const current = this.state.active === foldNum ? -1 : foldNum;
this.setState(() => ({ active: current }));
};
render() {
return (
<div className="accordion">
{this.props.contents.map((content, i) => {
return (
<Fold
key={`${i}-${content.title}`}
content={content}
handle={() => this.selectFold(i)}
active={i === this.state.active}
/>
);
})}
</div>
);
}
}
class Fold extends React.Component {
render() {
return (
<div className="fold">
<button
className={`fold_trigger ${this.props.active ? "open" : ""}`}
onClick={this.props.handle}
>
{this.props.content.title}
</button>
<div
key="content"
className={`fold_content ${this.props.active ? "open" : ""}`}
>
{this.props.content.inner}
</div>
</div>
);
}
}
const pictures = [
"http://unsplash.it/200",
"http://unsplash.it/200",
"http://unsplash.it/200",
];
var test = (title, text, imageURLs) => {
const images=
<div className='test-images' >
{imageURLs.map((url,i) => <img key={i} src={url} />)}
</div>;
const inner =
<div className='test-content' >
<p>{text} </p>
{images}
</div>;
return {title, inner};
};
const testData = [
test('Title', 'Content',pictures ),
test('Title', 'Content',pictures ),
test('Title', 'Content',pictures ),
test('Title', 'Content',pictures ),
test('Title', 'Content',pictures ),
];
ReactDOM.render(<Accordion contents={testData} />, document.getElementById('root'));
.accordion {
width: 100%;
padding: 1rem 2rem;
display: flex;
flex-direction: column;
border-radius: 10%;
overflow-y: auto;
}
.fold {
border-bottom: 1px solid rgba(34, 36, 38, 0.35);
}
.fold .fold_trigger {
text-align: start;
width: 100%;
padding: 1rem;
border: none;
outline: none;
background: none;
cursor: pointer;
border-bottom: 1px solid rgba(34, 36, 38, 0.35);
}
.fold .fold_trigger:before {
font-family: FontAwesome;
content: "\f107";
display: block;
float: left;
padding-right: 1rem;
transition: transform 400ms;
transform-origin: 20%;
color: rgba(34, 36, 38, 0.35);
}
.fold .fold_trigger.open:before {
transform: rotateZ(-180deg);
}
.fold .fold_content {
max-height: 0;
overflow: hidden;
transition: max-height 400ms ease;
}
.fold .fold_content.open {
max-height: 400px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet" />
<div id='root'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Note:
I used ease instead of linear on the transition because I think it's a nicer effect. But that's just personal taste. linear will work as well.
Also, you can continue to conditionally render the content. A slide-down animation is possible, but a slide-up can't be easily achieved. There are some transition libraries that you could explore as well.
However, I think it's easiest to use the state just for conditional classes (as you are doing with the open class). I think conditionally rendering content to the DOM makes your life difficult if you're trying to do CSS animations.

Categories