Skip to content

Instantly share code, notes, and snippets.

@xamedow
Created October 4, 2022 14:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xamedow/2d7e698f4e9b54b108284ddf654ef1d1 to your computer and use it in GitHub Desktop.
Save xamedow/2d7e698f4e9b54b108284ddf654ef1d1 to your computer and use it in GitHub Desktop.
Slider implementation
import React, {
Children,
cloneElement,
ReactElement,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import classNames from 'classnames/bind';
import SwiperCore, { Navigation, Swiper as SwiperProps } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/react';
import { RTLContext } from 'utils/context';
import {
DEFAULT_SLIDER_PADDING_FOR_ARROWS,
DEFAULT_SLIDER_SLIDES_OFFSET,
DEFAULT_SLIDER_SPACE_BETWEEN,
} from '../../../const';
import Button from '../Button';
import Icon from '../Icon';
import styles from './styles.module.scss';
SwiperCore.use([Navigation]);
const cx = classNames.bind(styles);
interface EnhancedSwiperProps extends SwiperProps {
size?: number;
slidesSizesGrid?: Array<number>;
}
interface Props {
spaceBetween?: number;
children: React.ReactElement[];
hideArrows?: boolean;
slidesOffsetBefore?: number;
slidesOffsetAfter?: number;
hideGradients?: boolean;
centerInsufficientSlides?: boolean;
slidesPerView?: number | 'auto';
}
interface ArrowProps {
isNext?: boolean;
isPrev?: boolean;
}
const Slider: React.FC<Props> = ({
children,
spaceBetween = DEFAULT_SLIDER_SPACE_BETWEEN,
hideArrows,
slidesOffsetBefore = DEFAULT_SLIDER_SLIDES_OFFSET,
slidesOffsetAfter = DEFAULT_SLIDER_SLIDES_OFFSET,
hideGradients,
centerInsufficientSlides = false,
slidesPerView = 'auto',
}) => {
const [slider, defineSlider] = useState<EnhancedSwiperProps | null>(null);
const [isInsufficientSlides, setIsInsufficientSlides] = useState(false);
const [showArrows, setShowArrows] = useState(!hideArrows);
const [disableScroll, setDisableScroll] = useState(false);
const { isRTL } = useContext(RTLContext);
const swiperRef = useRef<HTMLDivElement>(null);
const slideRef = useRef<HTMLDivElement>(null);
const [isBeginning, setIsBeginning] = useState(true);
const [isEnd, setIsEnd] = useState(false);
const { navigation }: Record<string, any> = slider ?? ({} as SwiperProps);
const Arrow = ({ isNext, isPrev }: ArrowProps) => {
const classNameButton = cx('button', {
right: isNext,
left: isPrev,
});
const navigationMethod = isRTL
? `on${isNext ? 'Prev' : 'Next'}Click`
: `on${isNext ? 'Next' : 'Prev'}Click`;
return (
<Button
className={classNameButton}
accent="text"
onClick={navigation?.[navigationMethod]}
>
<Icon icon="arrowBold" />
</Button>
);
};
useEffect(() => {
if (swiperRef?.current && slideRef?.current) {
const contentWidth =
slideRef?.current.offsetWidth * React.Children.count(children);
const sliderWidth =
swiperRef?.current.offsetWidth - DEFAULT_SLIDER_PADDING_FOR_ARROWS;
const isNotFit = contentWidth > sliderWidth;
setDisableScroll(!isNotFit);
if (!hideArrows) {
setShowArrows(isNotFit);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [swiperRef, slideRef, children.length, hideArrows]);
// Check insufficient slides
useEffect(() => {
const slidesSize = slider?.slidesSizesGrid?.reduce(
(acc: number, size: number) => acc + size,
0,
);
if (slider?.size && slidesSize && slider.size > slidesSize) {
setIsInsufficientSlides(true);
}
}, [slider, slider?.size, slider?.slidesSizesGrid]);
const renderSlides = Children.map(children, (child: ReactElement) => (
<SwiperSlide>{cloneElement(child, { ref: slideRef })}</SwiperSlide>
));
const classNameSlider = cx('slider', {
hideGradients,
});
const showBack = isRTL ? !isEnd : !isBeginning;
const showNext = isRTL ? !isBeginning : !isEnd;
const slidesOffsetBeforeParam =
isInsufficientSlides && centerInsufficientSlides ? 0 : slidesOffsetBefore;
const slidesOffsetAfterParam =
isInsufficientSlides && centerInsufficientSlides ? 0 : slidesOffsetAfter;
return (
<div className={classNameSlider} ref={swiperRef}>
{showArrows && showBack && <Arrow isPrev />}
<Swiper
noSwiping={disableScroll}
className="swiper-no-swiping"
spaceBetween={spaceBetween}
slidesPerView={slidesPerView}
centerInsufficientSlides={centerInsufficientSlides}
onSwiper={defineSlider}
slidesOffsetBefore={slidesOffsetBeforeParam}
slidesOffsetAfter={slidesOffsetAfterParam}
onSlideChange={(swiper) => {
setIsBeginning(swiper.isBeginning);
setIsEnd(swiper.isEnd);
}}
>
{renderSlides}
</Swiper>
{showArrows && showNext && <Arrow isNext />}
</div>
);
};
export default Slider;
@import 'stylesheets/mixins';
.slider {
position: relative;
width: 100%;
font-family: 'Cairo';
user-select: none;
&:after,
&:before {
position: absolute;
top: 0;
z-index: 2;
width: 15px;
height: 100%;
content: '';
}
&:not(&.hideGradients):after {
right: 0;
background: linear-gradient(270deg, #121620 0%, rgba(18, 22, 32, 0) 100%);
}
&:not(&.hideGradients):before {
left: 0;
background: linear-gradient(270deg, #121620 0%, rgba(18, 22, 32, 0) 100%);
transform: rotate(180deg);
}
}
.button {
@include normalizedButton;
@include color('gray', '-1');
position: absolute;
top: 50%;
left: 0;
display: block;
width: 40px;
height: 40px;
transform: translateY(-50%);
transition: color ease-in-out 0.3s;
&:hover,
&:focus {
@include color('white');
}
&.right {
right: -62px;
left: auto;
transform: translateY(-50%) rotate(180deg);
}
&.left {
left: -62px;
transform: translateY(-50%);
}
svg {
width: 40px;
height: 40px;
}
@media (max-width: 1279px) {
display: none;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment