fix slider

This commit is contained in:
Salam-vsem
2025-05-20 16:58:44 +03:00
parent 8a8583fc8f
commit d774f0345f
11 changed files with 158 additions and 112 deletions

View File

@@ -0,0 +1,24 @@
import { forwardRef } from 'react'
import type { IconProps } from './types'
export const ArrowIcon = forwardRef<SVGSVGElement, IconProps>(function ArrowIcon(props, ref) {
return (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
ref={ref}
{...props}
>
<path
d="M6 12H18M6 12L11 7M6 12L11 17"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
})

View File

@@ -15,23 +15,13 @@ const t = useTranslations(lang)
--- ---
<footer id="contacts" class="flex flex-col items-center gap-4 text-center md:gap-6"> <footer id="contacts" class="flex flex-col items-center gap-4 text-center md:gap-6">
<div class="flex h-[400px] w-full items-center justify-center bg-bg-light"> <iframe
<script
transition:persist transition:persist
script-src:unsafe-inline src=`https://yandex.ru/map-widget/v1/?um=constructor%3A069b17526dc83cbf87c488f13ae089eb3af0f8f74af57e1be82e183434693562&source=constructor&scroll=false&lang=${lang}`
is:inline width="100%"
type="text/javascript" height="400"
charset="utf-8"
async
onload="document.getElementById('ymap-loader').style.display = 'none';" onload="document.getElementById('ymap-loader').style.display = 'none';"
src=`https://api-maps.yandex.ru/services/constructor/1.0/js/?um=constructor%3A069b17526dc83cbf87c488f13ae089eb3af0f8f74af57e1be82e183434693562&width=100%25&height=400&lang=${lang}&scroll=false` frameborder="0"></iframe>
></script>
<div
id="ymap-loader"
class="h-16 w-16 animate-spin rounded-full border-6 border-brand border-t-transparent"
>
</div>
</div>
<div class="flex flex-col items-center gap-3 px-4 py-6 text-center md:gap-4 md:px-6 md:py-8"> <div class="flex flex-col items-center gap-3 px-4 py-6 text-center md:gap-4 md:px-6 md:py-8">
<Image src={logoSrc} alt="logo" class="h-32 w-auto md:h-48" /> <Image src={logoSrc} alt="logo" class="h-32 w-auto md:h-48" />
<p class="text-light text-sm"> <p class="text-light text-sm">

View File

@@ -29,6 +29,7 @@ export const Reviews: React.FC<ReviewsProps> = ({ reviews, lang }) => {
<SimpleSwiper <SimpleSwiper
slidesPerView={1} slidesPerView={1}
freeMode={isMd} freeMode={isMd}
pagination={{type: 'fraction', enabled: !isMd}}
breakpoints={{ breakpoints={{
[TW_SCREENS.MD]: { [TW_SCREENS.MD]: {
slidesPerView: 'auto', slidesPerView: 'auto',

View File

@@ -0,0 +1,41 @@
import React from 'react'
import { cn } from '@/libs/cn'
import type { PaginationOptions } from 'swiper/types'
import type { SwiperProps } from 'swiper/react'
import { ArrowIcon } from '@/assets/icons/ArrowIcon'
type FractionPaginationProps = React.HTMLAttributes<HTMLDivElement>
export const isFranctionPagination = (
options?: Pick<SwiperProps, 'pagination'>['pagination']
): options is PaginationOptions => typeof options === 'object' && options.type === 'fraction'
export const makeCustomFranctionOptions = (
options: PaginationOptions
): Pick<SwiperProps, 'pagination' | 'navigation'> => ({
pagination: {
...options,
type: 'fraction',
el: '#pagination-container',
},
navigation: {
prevEl: '#swiper-button-prev',
nextEl: '#swiper-button-next',
},
})
export const FractionPagination: React.FC<FractionPaginationProps> = ({ className, ...props }) => {
return (
<div
className={cn('mt-4 flex w-full items-center justify-center gap-6 lg:mt-8', className)}
{...props}
>
<button id="swiper-button-prev" className="transition-opacity duration-300">
<ArrowIcon className="stroke-text" height={32} width={32} />
</button>
<div className="font-semibold text-text" id="pagination-container"></div>
<button id="swiper-button-next" className="transition-opacity duration-300">
<ArrowIcon className="rotate-180 stroke-text" height={32} width={32} />
</button>
</div>
)
}

View File

@@ -1,11 +1,8 @@
import React from 'react' import React from 'react'
import { Swiper, type SwiperProps, type SwiperRef, SwiperSlide } from 'swiper/react' import { type SwiperProps, type SwiperRef, SwiperSlide } from 'swiper/react'
import { A11y, Navigation, Pagination } from 'swiper/modules'
import { cn } from '@/libs/cn' import { cn } from '@/libs/cn'
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
import { isAboveBreakpoint } from '@/libs/breakpoint' import { isAboveBreakpoint } from '@/libs/breakpoint'
import { SimpleSwiper } from './SimpleSwiper'
export type MediaSwiperProps = SwiperProps & { export type MediaSwiperProps = SwiperProps & {
images: ImportAttributes[] images: ImportAttributes[]
@@ -16,19 +13,20 @@ export type MediaSwiperProps = SwiperProps & {
const isMd = isAboveBreakpoint('MD') const isMd = isAboveBreakpoint('MD')
export const MediaSwiper = React.forwardRef<SwiperRef, MediaSwiperProps>( export const MediaSwiper = React.forwardRef<SwiperRef, MediaSwiperProps>(
({ images, className, modules = [], slideClassName, imageClassName, ...otherProps }, ref) => { ({ images, className, slideClassName, imageClassName, ...otherProps }, ref) => {
return ( return (
<Swiper <SimpleSwiper
ref={ref} ref={ref}
modules={[Navigation, Pagination, A11y, ...modules]}
slidesPerView={1} slidesPerView={1}
freeMode={false}
mousewheel={false}
navigation={isMd} navigation={isMd}
spaceBetween={24}
pagination={{ pagination={{
clickable: true, type: 'fraction',
}} }}
loop={true} loop={true}
watchSlidesProgress={true} watchSlidesProgress={true}
style={{ overflow: 'hidden' }}
className={cn('h-full w-full', className)} className={cn('h-full w-full', className)}
{...otherProps} {...otherProps}
> >
@@ -47,7 +45,7 @@ export const MediaSwiper = React.forwardRef<SwiperRef, MediaSwiperProps>(
<div className="swiper-lazy-preloader border-brand"></div> <div className="swiper-lazy-preloader border-brand"></div>
</SwiperSlide> </SwiperSlide>
))} ))}
</Swiper> </SimpleSwiper>
) )
} }
) )

View File

@@ -1,21 +0,0 @@
import React, { useRef } from 'react'
import { type SwiperRef } from 'swiper/react'
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
import { NextButton, PrevButton } from './NavButtons'
import { useNavHandlers } from './hooks'
import { MediaSwiper, type MediaSwiperProps } from './MediaSwiper'
export const MediaSwiperWithNav: React.FC<MediaSwiperProps> = (props) => {
const swiperRef = useRef<SwiperRef | null>(null)
const { handleNext, handlePrev } = useNavHandlers(swiperRef)
return (
<div className="flex h-full w-full items-center gap-10">
<PrevButton className="hidden md:block" onClick={handlePrev} />
<MediaSwiper {...props} ref={swiperRef} />
<NextButton className="hidden md:block" onClick={handleNext} />
</div>
)
}

View File

@@ -1,30 +0,0 @@
import type { ImageMetadata } from 'astro'
import React, { type ButtonHTMLAttributes } from 'react'
import ChevronLeft from '@/assets/icons/LeftArrowIcon.svg'
import ChevronRight from '@/assets/icons/RightArrowIcon.svg'
import { cn } from '@/libs/cn'
type Props = ButtonHTMLAttributes<HTMLButtonElement> & { iconSrc: ImageMetadata }
export const NavButton: React.FC<Props> = ({ iconSrc, ...buttonProps }) => {
return (
<button
{...buttonProps}
className={cn(
'rounded-full border border-solid border-border-light',
buttonProps.disabled && 'border-none',
buttonProps.className
)}
>
<img {...iconSrc} width={50} height={50} />
</button>
)
}
export const PrevButton: React.FC<ButtonHTMLAttributes<HTMLButtonElement>> = (props) => {
return <NavButton {...props} iconSrc={ChevronLeft} />
}
export const NextButton: React.FC<ButtonHTMLAttributes<HTMLButtonElement>> = (props) => {
return <NavButton {...props} iconSrc={ChevronRight} />
}

View File

@@ -1,26 +1,36 @@
import { FreeMode, Mousewheel } from 'swiper/modules' import { A11y, FreeMode, Mousewheel, Navigation, Pagination } from 'swiper/modules'
import { Swiper, type SwiperProps, type SwiperRef } from 'swiper/react' import { Swiper, type SwiperProps, type SwiperRef } from 'swiper/react'
import 'swiper/css' import 'swiper/css'
import 'swiper/css/free-mode' import 'swiper/css/free-mode'
import 'swiper/css/pagination'
import 'swiper/css/navigation'
import './simple-swiper.css' import './simple-swiper.css'
import { cn } from '@/libs/cn' import { cn } from '@/libs/cn'
import React from 'react' import React from 'react'
import { FractionPagination } from './FractionPagination'
import { isEnabledPagination, makeCustomPaginationOptions } from './utils'
export const SimpleSwiper = React.forwardRef<SwiperRef, SwiperProps>( export const SimpleSwiper = React.forwardRef<SwiperRef, SwiperProps>(
({ children, className, ...props }, ref) => ( ({ children, className, pagination, modules, ...props }, ref) => {
const paginationOptions = makeCustomPaginationOptions(pagination)
return (
<Swiper <Swiper
direction="horizontal" direction="horizontal"
slidesPerView="auto" slidesPerView="auto"
freeMode={true} freeMode={true}
spaceBetween={24} spaceBetween={24}
mousewheel={{ enabled: true, forceToAxis: true }} mousewheel={{ enabled: true, forceToAxis: true }}
modules={[FreeMode, Mousewheel]} modules={[Navigation, Pagination, FreeMode, Mousewheel, A11y, ...(modules || [])]}
{...props} {...props}
{...paginationOptions}
ref={ref} ref={ref}
className={cn('simple-swiper', className)} className={cn('simple-swiper', className)}
> >
{children} {children}
{isEnabledPagination(pagination) && <FractionPagination />}
</Swiper> </Swiper>
) )
}
) )
SimpleSwiper.displayName = 'SimpleSwiper' SimpleSwiper.displayName = 'SimpleSwiper'

View File

@@ -1,16 +0,0 @@
import { type RefObject, useCallback } from 'react'
import type { SwiperRef } from 'swiper/react'
export const useNavHandlers = (swiperRef: RefObject<SwiperRef | null>) => {
const handlePrev = useCallback(() => {
if (!swiperRef.current) return
swiperRef.current.swiper.slidePrev()
}, [])
const handleNext = useCallback(() => {
if (!swiperRef.current) return
swiperRef.current.swiper.slideNext()
}, [])
return { handlePrev, handleNext } as const
}

View File

@@ -2,3 +2,15 @@
width: 100%; width: 100%;
overflow: visible; overflow: visible;
} }
.swiper-pagination-fraction {
@apply text-xl w-auto;
}
.swiper-pagination-current {
@apply text-text-light;
}
.swiper-button-disabled {
@apply opacity-0;
}

View File

@@ -0,0 +1,37 @@
import type { SwiperProps } from 'swiper/react'
import type { PaginationOptions } from 'swiper/types'
export const isEnabledPagination = (
pagination?: Pick<SwiperProps, 'pagination'>['pagination']
): boolean =>
(typeof pagination === 'object' && pagination.enabled !== false) ||
(typeof pagination === 'boolean' && pagination)
export const isFranctionPagination = (
options?: Pick<SwiperProps, 'pagination'>['pagination']
): options is PaginationOptions => typeof options === 'object' && options.type === 'fraction'
export const makeCustomFranctionOptions = (
options: PaginationOptions
): Pick<SwiperProps, 'pagination' | 'navigation'> => ({
pagination: {
...options,
type: 'fraction',
el: '#pagination-container',
},
navigation: {
enabled: options.enabled,
prevEl: '#swiper-button-prev',
nextEl: '#swiper-button-next',
},
})
export const makeCustomPaginationOptions = (
pagination: Pick<SwiperProps, 'pagination'>['pagination']
): SwiperProps => {
if (!pagination) return { pagination: { enabled: Boolean(pagination) } }
if (isFranctionPagination(pagination)) return makeCustomFranctionOptions(pagination)
return { pagination }
}