From 891b9ec5f777490a5382478d82f447dbe2b7a8e1 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Thu, 2 Nov 2023 13:34:38 +0300 Subject: [PATCH] Use Solid Swiper (#293) Use Solid Swiper --- src/components/Article/FullArticle.tsx | 4 +- src/components/Views/Edit.tsx | 4 +- src/components/Views/Home.tsx | 36 +-- src/components/Views/Topic.module.scss | 0 src/components/Views/Topic.tsx | 35 +-- src/components/_shared/Slider/Slider.scss | 253 ------------------ src/components/_shared/Slider/Slider.tsx | 157 ----------- src/components/_shared/Slider/index.ts | 1 - .../_shared/SolidSwiper/ArticleCardSwiper.tsx | 97 +++++++ .../{SolidSwiper.tsx => ImageSwiper.tsx} | 5 +- .../_shared/SolidSwiper/Swiper.module.scss | 15 ++ src/components/_shared/SolidSwiper/index.ts | 2 +- .../_shared/SolidSwiper/swiper.d.ts | 7 +- 13 files changed, 131 insertions(+), 485 deletions(-) create mode 100644 src/components/Views/Topic.module.scss delete mode 100644 src/components/_shared/Slider/Slider.scss delete mode 100644 src/components/_shared/Slider/Slider.tsx delete mode 100644 src/components/_shared/Slider/index.ts create mode 100644 src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx rename src/components/_shared/SolidSwiper/{SolidSwiper.tsx => ImageSwiper.tsx} (99%) diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 4986b99b..5e742829 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -20,7 +20,7 @@ import { AudioHeader } from './AudioHeader' import { Popover } from '../_shared/Popover' import { VideoPlayer } from '../_shared/VideoPlayer' import { Icon } from '../_shared/Icon' -import { SolidSwiper } from '../_shared/SolidSwiper' +import { ImageSwiper } from '../_shared/SolidSwiper' import styles from './Article.module.scss' import { CardTopic } from '../Feed/CardTopic' import { createPopper } from '@popperjs/core' @@ -331,7 +331,7 @@ export const FullArticle = (props: Props) => {
- +
diff --git a/src/components/Views/Edit.tsx b/src/components/Views/Edit.tsx index f24e6f26..d9e2f297 100644 --- a/src/components/Views/Edit.tsx +++ b/src/components/Views/Edit.tsx @@ -12,7 +12,7 @@ import { GrowingTextarea } from '../_shared/GrowingTextarea' import { VideoUploader } from '../Editor/VideoUploader' import { AudioUploader } from '../Editor/AudioUploader' import { slugify } from '../../utils/slugify' -import { SolidSwiper } from '../_shared/SolidSwiper' +import { ImageSwiper } from '../_shared/SolidSwiper' import { DropArea } from '../_shared/DropArea' import { LayoutType, MediaItem } from '../../pages/types' import { clone } from '../../utils/clone' @@ -384,7 +384,7 @@ export const EditView = (props: Props) => { - { /> - - - {(a) => ( - - )} - - + @@ -162,21 +146,7 @@ export const HomeView = (props: Props) => { {randomLayout()} - - - {(a: Shout) => ( - - )} - - + { wrapper={'author'} /> - - - {(a: Shout) => ( - - )} - - + { 15}> - - - {(a: Shout) => ( - - )} - - - + diff --git a/src/components/_shared/Slider/Slider.scss b/src/components/_shared/Slider/Slider.scss deleted file mode 100644 index 1aa7d959..00000000 --- a/src/components/_shared/Slider/Slider.scss +++ /dev/null @@ -1,253 +0,0 @@ -.swiper-slide { - min-height: 0 !important; - - .cards-with-cover & { - height: 0 !important; - padding-top: 100%; - - @include media-breakpoint-up(sm) { - padding-top: 56.2% !important; - } - - @include media-breakpoint-up(md) { - padding-top: 35% !important; - } - - img { - height: 100%; - left: 0; - object-fit: cover; - object-position: center; - position: absolute; - top: 0; - width: 100%; - } - } -} - -.slider-arrow-prev, -.slider-arrow-next { - align-items: center; - display: flex; - cursor: pointer; - height: 100%; - position: absolute; - outline: none; - border: 0; - transform: translate(0); - top: 0; - width: 21%; - z-index: 1; - - @include media-breakpoint-down(md) { - width: 8%; - } - - &::after { - color: #fff; - } - - &:hover { - .icon { - opacity: 0.5; - } - } - - .icon { - height: 36px; - opacity: 1; - transition: opacity 0.2s; - width: 22px; - } -} - -.slider-arrow-prev { - background: linear-gradient(to left, rgb(0 0 0 / 0%) 0%, rgb(0 0 0 / 90%) 100%); - justify-content: flex-start; - left: 0; - - &::after { - margin-left: 5rem; - } - - .icon { - margin-left: 5rem; - - @include media-breakpoint-down(md) { - margin-left: 25%; - } - } -} - -.slider-arrow-next { - background: linear-gradient(to left, rgb(0 0 0 / 90%) 0%, rgb(0 0 0 / 0%) 100%); - justify-content: flex-end; - right: 0; - - &::after { - margin-right: 5rem; - } - - .icon { - margin-right: 5rem; - transform: rotate(180deg); - - @include media-breakpoint-down(md) { - margin-right: 25%; - } - } -} - -.swiper--page-gallery { - padding-bottom: 4rem; - - .swiper-wrapper { - align-items: center; - } - - .swiper-slide { - display: flex; - justify-content: center; - } - - .swiper-slide__inner { - display: flex; - flex-direction: column; - justify-content: center; - - img { - display: block; - height: auto; - margin: 0 auto; - max-height: 80vh; - width: auto; - position: relative; - z-index: 11; - } - } - - .slider-arrow-prev, - .slider-arrow-next { - background: rgb(0 0 0 / 20%); - width: 5rem; - } - - .slider-arrow-next .icon { - margin-right: 2rem; - } - - .slider-arrow-prev .icon { - margin-left: 2rem; - } - - .swiper-slide-active { - .swiper-lazy-preloader { - display: none; - } - } -} - -.thumbs-container { - @include media-breakpoint-up(md) { - padding-left: 3.2rem; - } -} - -.swiper--thumbs { - @include media-breakpoint-up(md) { - max-height: 80vh; - min-width: 100px; - padding: 0 !important; - width: 100px !important; - } - - .swiper-slide { - cursor: pointer; - height: 80px; - opacity: 0.5; - width: 100px; - - @include media-breakpoint-up(md) { - height: 52px; - width: auto; - } - - img { - height: 100%; - object-fit: cover; - object-position: center; - width: 100%; - } - } - - .swiper-slide-thumb-active { - opacity: 1; - } - - .swiper-slide__inner { - height: 100%; - flex: 1; - } - - .swiper-lazy-preloader, - .image-description { - display: none; - } -} - -.sliders-container { - @include media-breakpoint-up(md) { - display: flex; - } -} - -.swiper-pagination { - background: #141414; - bottom: 0; - font-size: 1.2rem; - font-weight: bold; - left: auto; - right: 0; - padding: 1rem; - width: auto; -} - -.uploadPreview { - background: unset; - position: relative; - padding: 0 40px; - - .sliders-container { - position: relative; - } - - .swiper { - background: unset; - - .swiper-wrapper { - min-height: 400px; - } - } - - .slider-arrow-next, - .slider-arrow-prev { - background: none; - filter: invert(1); - width: 40px; - max-height: 540px; - - .icon { - margin: auto; - width: 12px; - height: 20px; - } - } - - // - //.slider-arrow-prev { - // margin-left: -40px; - //} - //.slider-arrow-next { - // margin-right: -40px; - //} -} diff --git a/src/components/_shared/Slider/Slider.tsx b/src/components/_shared/Slider/Slider.tsx deleted file mode 100644 index 6410940b..00000000 --- a/src/components/_shared/Slider/Slider.tsx +++ /dev/null @@ -1,157 +0,0 @@ -//TODO: Replace with SolidSwiper.tsx - -import { Swiper, Navigation, Pagination, Thumbs } from 'swiper' -import type { SwiperOptions } from 'swiper' -import 'swiper/scss' -import 'swiper/scss/navigation' -import 'swiper/scss/pagination' -import 'swiper/scss/thumbs' -import './Slider.scss' -import { createEffect, createSignal, JSX, on, Show } from 'solid-js' -import { Icon } from '../Icon' -import { clsx } from 'clsx' - -interface Props { - title?: string - slidesPerView?: number - isCardsWithCover?: boolean - children?: JSX.Element - isPageGallery?: boolean - hasThumbs?: boolean - variant?: 'uploadPreview' - slideIndex?: (value: number) => void -} - -export const Slider = (props: Props) => { - let el: HTMLDivElement | undefined - let thumbsEl: HTMLDivElement | undefined - let nextEl: HTMLDivElement | undefined - let prevEl: HTMLDivElement | undefined - - const isCardsWithCover = typeof props.isCardsWithCover === 'boolean' ? props.isCardsWithCover : true - - const [swiper, setSwiper] = createSignal() - const [swiperThumbs, setSwiperThumbs] = createSignal() - const opts: SwiperOptions = { - observer: true, - observeParents: true, - roundLengths: true, - loop: true, - centeredSlides: true, - slidesPerView: 1, - modules: [Navigation, Pagination, Thumbs], - speed: 500, - on: { - slideChange: () => { - if (swiper()) { - props.slideIndex(swiper().realIndex || 0) - } - } - }, - navigation: { nextEl, prevEl }, - breakpoints: { - 768: { - slidesPerView: props.slidesPerView > 0 ? props.slidesPerView : 1.66666, - spaceBetween: isCardsWithCover ? 8 : 26 - }, - 992: { - slidesPerView: props.slidesPerView > 0 ? props.slidesPerView : 1.66666, - spaceBetween: isCardsWithCover ? 8 : 52 - } - }, - thumbs: { - swiper: swiperThumbs() - } - } - - createEffect(() => { - if (props.hasThumbs && !!thumbsEl) { - setSwiperThumbs( - new Swiper(thumbsEl, { - slidesPerView: 'auto', - modules: [Thumbs], - roundLengths: true, - spaceBetween: 20, - freeMode: true, - breakpoints: { - 768: { - direction: 'vertical' - } - } - }) - ) - } - }) - - createEffect(() => { - if (!swiper() && !!el) { - if (swiperThumbs()) { - opts.thumbs = { - swiper: swiperThumbs() - } - - opts.pagination = { - el: '.swiper-pagination', - type: 'fraction' - } - } - - setSwiper(new Swiper(el, opts)) - swiper().update() - } - }) - - createEffect(() => { - swiper().update() - }) - - return ( -
-
-
- -

{props.title}

-
- -
-
-
{props.children}
- -
swiper()?.slideNext()}> - -
-
swiper()?.slidePrev()}> - -
-
- {/*
*/} -
- - -
-
-
{props.children}
-
-
-
-
-
-
- -
swiper()?.slideNext()}> - -
-
swiper()?.slidePrev()}> - -
-
-
- ) -} diff --git a/src/components/_shared/Slider/index.ts b/src/components/_shared/Slider/index.ts deleted file mode 100644 index 6d43da3c..00000000 --- a/src/components/_shared/Slider/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Slider } from './Slider' diff --git a/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx b/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx new file mode 100644 index 00000000..ed732d51 --- /dev/null +++ b/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx @@ -0,0 +1,97 @@ +import { createSignal, For, Show } from 'solid-js' +import { Icon } from '../Icon' +import { register } from 'swiper/element/bundle' +import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper' +import { SwiperRef } from './swiper' +import { clsx } from 'clsx' +import styles from './Swiper.module.scss' +import { Shout } from '../../../graphql/types.gen' +import { ArticleCard } from '../../Feed/ArticleCard' + +type Props = { + slides: Shout[] + slidesPerView?: number + title?: string +} + +register() + +SwiperCore.use([Pagination, Navigation, Manipulation]) + +export const ArticleCardSwiper = (props: Props) => { + const [slideIndex, setSlideIndex] = createSignal(0) + + const mainSwipeRef: { current: SwiperRef } = { current: null } + + const handleSlideChange = () => { + setSlideIndex(mainSwipeRef.current.swiper.activeIndex) + } + + return ( +
+ +

{props.title}

+
+
+ 0}> +
+ (mainSwipeRef.current = el)} + centered-slides={true} + thumbs-swiper={'.thumbSwiper'} + observer={true} + onSlideChange={handleSlideChange} + slides-per-view={props.slidesPerView ?? 1.5} + space-between={52} + breakpoints={{ 768: { spaceBetween: 26 }, 992: { spaceBetween: 52 } }} + loop={true} + speed={800} + autoplay={{ + disableOnInteraction: false, + delay: 3000, + pauseOnMouseEnter: true + }} + > + + {(slide, index) => ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + + + + )} + + +
mainSwipeRef.current.swiper.slidePrev()} + > + +
+
mainSwipeRef.current.swiper.slideNext()} + > + +
+
+ {slideIndex() + 1} / {props.slides.length} +
+
+
+
+
+ ) +} diff --git a/src/components/_shared/SolidSwiper/SolidSwiper.tsx b/src/components/_shared/SolidSwiper/ImageSwiper.tsx similarity index 99% rename from src/components/_shared/SolidSwiper/SolidSwiper.tsx rename to src/components/_shared/SolidSwiper/ImageSwiper.tsx index faad65d5..1146dca2 100644 --- a/src/components/_shared/SolidSwiper/SolidSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ImageSwiper.tsx @@ -1,4 +1,4 @@ -import { createEffect, createSignal, For, Show, on } from 'solid-js' +import { createEffect, createSignal, For, Show, on, JSXElement } from 'solid-js' import { MediaItem, UploadedFile } from '../../../pages/types' import { Icon } from '../Icon' import { Popover } from '../Popover' @@ -32,7 +32,7 @@ register() SwiperCore.use([Pagination, Navigation, Manipulation]) -export const SolidSwiper = (props: Props) => { +export const ImageSwiper = (props: Props) => { const { t } = useLocalize() const [loading, setLoading] = createSignal(false) const [slideIndex, setSlideIndex] = createSignal(0) @@ -68,7 +68,6 @@ export const SolidSwiper = (props: Props) => { { defer: true } ) ) - const handleDropAreaUpload = (value: UploadedFile[]) => { props.onImagesAdd(composeMediaItems(value)) swipeToUploaded() diff --git a/src/components/_shared/SolidSwiper/Swiper.module.scss b/src/components/_shared/SolidSwiper/Swiper.module.scss index 44ecd210..4260a522 100644 --- a/src/components/_shared/SolidSwiper/Swiper.module.scss +++ b/src/components/_shared/SolidSwiper/Swiper.module.scss @@ -13,6 +13,17 @@ $navigation-reserve: 32px; margin: 2rem 0; flex-direction: column; + &.ArticleCardSwiper { + margin-bottom: 6rem; + } + + .sliderTitle { + @include font-size(4.5rem); + + text-align: center; + padding: 4rem 0 0; + } + &.articleMode { background: var(--background-color-invert); color: var(--default-color-invert); @@ -114,6 +125,10 @@ $navigation-reserve: 32px; overflow: hidden; width: calc(100% - 130px); + @include media-breakpoint-down(sm) { + width: 100%; + } + .counter { @include font-size(1.2rem); diff --git a/src/components/_shared/SolidSwiper/index.ts b/src/components/_shared/SolidSwiper/index.ts index fbd847e0..54c84efc 100644 --- a/src/components/_shared/SolidSwiper/index.ts +++ b/src/components/_shared/SolidSwiper/index.ts @@ -1 +1 @@ -export { SolidSwiper } from './SolidSwiper' +export { ImageSwiper } from './ImageSwiper' diff --git a/src/components/_shared/SolidSwiper/swiper.d.ts b/src/components/_shared/SolidSwiper/swiper.d.ts index 1da88ad9..d94ccd21 100644 --- a/src/components/_shared/SolidSwiper/swiper.d.ts +++ b/src/components/_shared/SolidSwiper/swiper.d.ts @@ -1,5 +1,5 @@ import 'solid-js' -import { SwiperOptions } from 'swiper' +import { SwiperOptions, AutoplayOptions } from 'swiper' import { SwiperSlideProps } from 'swiper/react' type Kebab = T extends `${infer F}${infer R}` @@ -37,6 +37,11 @@ declare module 'solid-js' { onSlideChange?: () => void onBeforeSlideChangeStart?: () => void class?: string + breakpoints?: { + [width: number]: SwiperOptions + [ratio: string]: SwiperOptions + } + autoplay?: AutoplayOptions | boolean } // eslint-disable-next-line @typescript-eslint/no-empty-interface interface SwiperSlideAttributes extends KebabObjectKeys {