Skip to content

Instantly share code, notes, and snippets.

@saltnpixels
Last active May 7, 2024 19:40
Show Gist options
  • Save saltnpixels/e3c1c82383e28bd34812aa06151b04f4 to your computer and use it in GitHub Desktop.
Save saltnpixels/e3c1c82383e28bd34812aa06151b04f4 to your computer and use it in GitHub Desktop.
Swiper with react customizable
import { useRef, useEffect, useState } from 'react';
import Swiper from 'swiper';
import { SwiperOptions } from 'swiper/types';
import { Pagination, Navigation } from 'swiper/modules';
import { Box, BoxProps, MotionBox } from '@components';
import { Chevronleft, Chevronright } from '@/icons';
import { Variants } from 'framer-motion';
// now importing all thee manually with emotion
// import 'swiper/css';
// import 'swiper/css/navigation';
// import 'swiper/css/pagination';
export interface CarouselProps {
swiperOptions?: SwiperOptions;
navigation?: boolean | 'outside';
pagination?: boolean | 'outside';
navigationSize?: number;
navigationButtonSize?: number;
children: React.ReactElement<CarouselSlideProps>[] | React.ReactElement<CarouselSlideProps>;
navProps?: BoxProps;
arrowBg?: string;
variant?: 'filters' | 'normal';
hideNavigationOnDrag?: boolean;
}
interface CarouselSlideProps extends BoxProps {}
export const CarouselSlide = ({ className = '', children, ...props }: CarouselSlideProps) => {
return (
<Box className={`swiper-slide ${className}`} width="auto" {...props}>
{children}
</Box>
);
};
export const Carousel = ({
swiperOptions,
navigation,
navigationSize = 14,
navigationButtonSize = 40,
pagination,
children,
navProps,
arrowBg = 'transparent',
variant = 'normal',
hideNavigationOnDrag = false,
...props
}: CarouselProps) => {
// Ref for the Swiper instance
const swiperRef = useRef<HTMLDivElement>(null);
const nextButtonRef = useRef<HTMLDivElement>(null);
const prevButtonRef = useRef<HTMLDivElement>(null);
const paginationRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
const [swiperInstance, setSwiperInstance] = useState<Swiper | null>(null);
const [isInitialized, setIsInitialized] = useState(false);
const defaultOptions: SwiperOptions = {
// Swiper options
modules: [Navigation, Pagination],
slidesPerView: 1,
focusableElements: 'input',
spaceBetween: 16,
loop: false,
watchOverflow: false,
// Add other Swiper configurations as needed
};
const options = { ...defaultOptions, ...swiperOptions };
let styles = {};
if (variant === 'filters') {
arrowBg = 'white';
styles = {
...styles,
'.swiper-button-prev': {
height: '100%',
left: 0,
borderRadius: 0,
},
'.swiper-button-next': {
height: '100%',
right: 0,
borderRadius: 0,
},
};
}
// Variants for the Framer Motion animation
const navigationVariants: Variants = {
visible: { opacity: 1, transition: { duration: 0.5 }, pointerEvents: 'auto' },
hidden: { opacity: 0, transition: { duration: 0.5 }, pointerEvents: 'auto' },
};
useEffect(() => {
if (swiperRef.current) {
if (
(pagination && !paginationRef.current) ||
(navigation && !prevButtonRef.current && !nextButtonRef.current)
) {
setIsInitialized(false);
} else {
setIsInitialized(true);
}
}
}, [pagination, navigation]);
useEffect(() => {
// Initialize Swiper only after we have all the refs
if (isInitialized && swiperRef.current) {
if (pagination) {
options.pagination = { el: paginationRef.current, clickable: true };
}
if (navigation) {
options.navigation = {
nextEl: nextButtonRef.current,
prevEl: prevButtonRef.current,
};
}
options.on = {
init: (swiperInstance) => {
setSwiperInstance(swiperInstance);
},
};
const swiper = new Swiper(swiperRef.current, options);
// Variable to keep track of the timeout
if (hideNavigationOnDrag) {
let timeoutId: NodeJS.Timeout;
// Detect when the user starts dragging
swiper.on('touchStart', function () {
clearTimeout(timeoutId);
setIsDragging(true);
});
// Detect when the user stops dragging
swiper.on('touchEnd', function () {
timeoutId = setTimeout(() => setIsDragging(false), 1000);
});
}
// Cleanup on component unmount
return () => {
swiper.destroy(true, true);
setSwiperInstance(null);
};
}
}, [variant, hideNavigationOnDrag, swiperOptions, children, isInitialized]);
useEffect(() => {
if (swiperInstance) {
// This forces Swiper to recalculate slides and pagination
swiperInstance.update();
}
}, [children]);
return (
<Box
className="swiper-container"
sx={styles}
position={'relative'}
userSelect={'none'}
px={navigation === 'outside' ? '60px' : ''}
mb={pagination === 'outside' ? '35px' : 0}
{...props}
>
<Box ref={swiperRef} className="swiper">
<Box
className="swiper-wrapper"
gap={!swiperInstance ? `${options.spaceBetween}px` : 0}
sx={
!swiperInstance
? {
'& .swiper-slide': {
width:
!swiperInstance && options.slidesPerView !== 'auto'
? `${100 / options.slidesPerView!}%`
: '',
},
}
: {}
}
>
{children}
</Box>
</Box>
{pagination && (
<Box opacity={swiperInstance ? '1' : '0'}>
<Box
transform={pagination === 'outside' ? 'translateY(35px)' : 'none'}
className="swiper-pagination"
ref={paginationRef}
sx={{
'.swiper-pagination-bullet-active': {
backgroundColor: 'primary.main',
},
}}
/>
</Box>
)}
{navigation && (
<Box
className="swiper-navigation"
opacity={swiperInstance ? '1' : '0'}
color="black"
{...navProps}
>
<Box
className="swiper-button-prev"
background={arrowBg}
color="inherit"
_after={{ content: 'none' }}
width={`${navigationButtonSize}px`}
aspectRatio="1/1"
borderRadius="100%"
height="auto"
margin="auto"
bottom="50%"
ref={prevButtonRef}
>
<MotionBox
variants={navigationVariants}
initial={false}
animate={isDragging ? 'hidden' : ''}
>
<Chevronleft
style={{
width: `${navigationSize}px`,
position: 'relative',
left: '1px',
height: 'auto',
}}
/>
</MotionBox>
</Box>
<Box
className="swiper-button-next"
background={arrowBg}
color="inherit"
_after={{ content: 'none' }}
width={`${navigationButtonSize}px`}
aspectRatio="1/1"
borderRadius="100%"
height="auto"
margin="auto"
bottom="50%"
padding="10px"
ref={nextButtonRef}
>
<MotionBox
variants={navigationVariants}
initial={false}
animate={isDragging ? 'hidden' : ''}
>
<Chevronright
style={{
width: `${navigationSize}px`,
position: 'relative',
left: '1px',
height: 'auto',
}}
/>
</MotionBox>
</Box>
</Box>
)}
</Box>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment