react-card-carousel-materialui
Version:
A full-screen card carousel designed in Material UI, drawing inspiration from the Tesla website.
270 lines (239 loc) • 9.66 kB
JavaScript
import React from 'react';
import Box from "@mui/material/Box";
import Dialog from "@mui/material/Dialog";
import Stack from "@mui/material/Stack";
import PropTypes from "prop-types";
import { useEffect, useState } from "react";
import SliderActions from "./components/SliderActions";
import SliderIndicator from "./components/SliderIndicator";
import SliderItem from "./components/SliderItem";
export function ReactCardCarousel({ open, onClose, items }) {
const [sliderItems, setSliderItems] = useState([])
const [currentItem, setCurrentItem] = useState(0)
const [viewedItems, setViewedItems] = useState([])
useEffect(() => {
setSliderItems(items || [])
const itemsDeepCopy = [...items] // Deep copy of items
// Set viewed sliderItems to 3
let currentViewedItems = [
...(items && items.length > 1 ? [{
id: 0,
content: null,
isHidden: true
}] : []),
...itemsDeepCopy.slice(0, 2)
];
setViewedItems(currentViewedItems);
setCurrentItem(0)
}, [])
const moveToNext = () => {
const itemsDeepCopy = [...sliderItems] // Deep copy of items
if (viewedItems && !viewedItems[0].isHidden) {
const currentViewedItems = [
...itemsDeepCopy.slice(currentItem, currentItem + 3)
]
if (currentViewedItems.length < 3) {
currentViewedItems.push({
id: 0,
content: null,
isHidden: true
})
}
setViewedItems(currentViewedItems)
setCurrentItem(currentItem + 1)
} else {
const currentViewedItems = [
...itemsDeepCopy.slice(0, 3)
]
if (currentViewedItems.length < 3) {
currentViewedItems.push({
id: 0,
content: null,
isHidden: true
})
}
setViewedItems(currentViewedItems)
setCurrentItem(1)
}
}
const moveToPrevious = () => {
const itemsDeepCopy = [...sliderItems] // Deep copy of items
if (viewedItems && viewedItems[0]?.isHidden) {
const currentViewedItems = [
...itemsDeepCopy.slice(currentItem - 2, currentItem + 1)
]
if (currentViewedItems.length < 3) {
currentViewedItems.unshift({
id: 0,
content: null,
isHidden: true
})
}
setViewedItems(currentViewedItems)
setCurrentItem(currentItem - 1)
} else {
if (currentItem === 1) {
let currentViewedItems = [
{
id: 0,
content: null,
isHidden: true
},
...itemsDeepCopy.slice(0, 2)
];
if (currentViewedItems.length < 3) {
currentViewedItems.push({
id: 0,
content: null,
isHidden: true
})
}
setViewedItems(currentViewedItems)
setCurrentItem(currentItem - 1)
}
else {
const currentViewedItems = [
...itemsDeepCopy.slice(currentItem - 2, currentItem + 1)
]
if (currentViewedItems.length < 3) {
currentViewedItems.unshift({
id: 0,
content: null,
isHidden: true
})
}
setViewedItems(currentViewedItems)
setCurrentItem(currentItem - 1)
}
}
}
const isNextItemAvailable = () => {
return currentItem < sliderItems.length - 1
}
const isPreviousItemAvailable = () => {
if (viewedItems && viewedItems[0]?.isHidden) {
return currentItem > 1
}
return currentItem > 0
}
const calculateMinWidth = () => {
//currentWidth of screen
let width = window.innerWidth;
//calculate min width by 80% of current width
let minWidth = width * 0.8;
if (minWidth > 885) minWidth = 885;
if (minWidth < 765) minWidth = width - 80;
return `${minWidth}px`;
}
const moveToASpecificItem = (id) => {
//find the index of the item
let index = sliderItems.findIndex(item => item.id === id);
//if the item is not found, return
if (index === -1) return;
//if the item is already in the view, return
if (index === currentItem) return;
//if the item is in the next view
if (index > currentItem && index < currentItem + 3) {
//move to the next item
moveToNext();
return;
}
//if the item is in the previous view
else if (index < currentItem && index > currentItem - 3) {
//move to the previous item
moveToPrevious();
return;
}
else {
const itemsDeepCopy = [...sliderItems] // Deep copy of items
//Get item after the current item
const currentViewedItems = [
itemsDeepCopy[index - 1] ? itemsDeepCopy[index - 1] : {
id: 0,
content: null,
isHidden: true
},
itemsDeepCopy[index],
itemsDeepCopy[index + 1] ? itemsDeepCopy[index + 1] : {
id: 0,
content: null,
isHidden: true
}
]
setViewedItems(currentViewedItems)
setCurrentItem(index)
}
}
return (
<Dialog
fullScreen
open={open}
onClose={onClose}
style={{ backgroundColor: "rgba(255, 255, 255, 0.5)", backdropFilter: "blur( 5px )", padding: 0, margin: 0, }}
PaperProps={{
style: {
backgroundColor: 'transparent',
boxShadow: 'none',
},
}}
>
<Box id="box1" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', overflow: 'hidden', width: '100%', backgroundColor: "transparent" }}>
<Stack direction="column" spacing={2}>
<Box
id="box2"
style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', overflow: 'hidden', width: '100%', backgroundColor: "transparent" }}>
{
viewedItems
.map((item, index) => (
<div key={item.id} id={item.id}>
{
item.isHidden ? (
<div style={{ width: calculateMinWidth(), minWidth: calculateMinWidth(), height: '550px', minHeight: '550px', marginRight: '30px', marginLeft: '30px', }} />
)
:
(
<SliderItem
onlyItem={viewedItems.length === 1}
id={item.id}
index={index}
isHidden={item.isHidden}
video={item.video}
image={item.image}
title={item.title}
description={item.description}
actions={item.actions || null}
calculateMinWidth={calculateMinWidth}
onClose={onClose}
moveToASpecificItem={moveToASpecificItem}
/>
)
}
</div>
))
}
</Box>
<SliderIndicator
sliderItems={sliderItems}
currentItem={currentItem}
moveToASpecificItem={moveToASpecificItem}
/>
</Stack>
<SliderActions
isNextItemAvailable={isNextItemAvailable}
isPreviousItemAvailable={isPreviousItemAvailable}
moveToNext={moveToNext}
moveToPrevious={moveToPrevious}
/>
</Box>
</Dialog >
)
}
ReactCardCarousel.defaultProps = {
open: false,
onClose: () => { }
}
ReactCardCarousel.propTypes = {
open: PropTypes.bool,
onClose: PropTypes.func
}
export default ReactCardCarousel;