fix slider
This commit is contained in:
24
src/assets/icons/ArrowIcon.tsx
Normal file
24
src/assets/icons/ArrowIcon.tsx
Normal 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>
|
||||
)
|
||||
})
|
||||
@@ -15,23 +15,13 @@ const t = useTranslations(lang)
|
||||
---
|
||||
|
||||
<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">
|
||||
<script
|
||||
transition:persist
|
||||
script-src:unsafe-inline
|
||||
is:inline
|
||||
type="text/javascript"
|
||||
charset="utf-8"
|
||||
async
|
||||
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`
|
||||
></script>
|
||||
<div
|
||||
id="ymap-loader"
|
||||
class="h-16 w-16 animate-spin rounded-full border-6 border-brand border-t-transparent"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<iframe
|
||||
transition:persist
|
||||
src=`https://yandex.ru/map-widget/v1/?um=constructor%3A069b17526dc83cbf87c488f13ae089eb3af0f8f74af57e1be82e183434693562&source=constructor&scroll=false&lang=${lang}`
|
||||
width="100%"
|
||||
height="400"
|
||||
onload="document.getElementById('ymap-loader').style.display = 'none';"
|
||||
frameborder="0"></iframe>
|
||||
<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" />
|
||||
<p class="text-light text-sm">
|
||||
|
||||
@@ -29,6 +29,7 @@ export const Reviews: React.FC<ReviewsProps> = ({ reviews, lang }) => {
|
||||
<SimpleSwiper
|
||||
slidesPerView={1}
|
||||
freeMode={isMd}
|
||||
pagination={{type: 'fraction', enabled: !isMd}}
|
||||
breakpoints={{
|
||||
[TW_SCREENS.MD]: {
|
||||
slidesPerView: 'auto',
|
||||
|
||||
41
src/components/swiper/FractionPagination.tsx
Normal file
41
src/components/swiper/FractionPagination.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
import React from 'react'
|
||||
import { Swiper, type SwiperProps, type SwiperRef, SwiperSlide } from 'swiper/react'
|
||||
import { A11y, Navigation, Pagination } from 'swiper/modules'
|
||||
import { type SwiperProps, type SwiperRef, SwiperSlide } from 'swiper/react'
|
||||
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 & {
|
||||
images: ImportAttributes[]
|
||||
@@ -16,19 +13,20 @@ export type MediaSwiperProps = SwiperProps & {
|
||||
const isMd = isAboveBreakpoint('MD')
|
||||
|
||||
export const MediaSwiper = React.forwardRef<SwiperRef, MediaSwiperProps>(
|
||||
({ images, className, modules = [], slideClassName, imageClassName, ...otherProps }, ref) => {
|
||||
({ images, className, slideClassName, imageClassName, ...otherProps }, ref) => {
|
||||
return (
|
||||
<Swiper
|
||||
<SimpleSwiper
|
||||
ref={ref}
|
||||
modules={[Navigation, Pagination, A11y, ...modules]}
|
||||
slidesPerView={1}
|
||||
freeMode={false}
|
||||
mousewheel={false}
|
||||
navigation={isMd}
|
||||
spaceBetween={24}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
type: 'fraction',
|
||||
}}
|
||||
loop={true}
|
||||
watchSlidesProgress={true}
|
||||
style={{ overflow: 'hidden' }}
|
||||
className={cn('h-full w-full', className)}
|
||||
{...otherProps}
|
||||
>
|
||||
@@ -47,7 +45,7 @@ export const MediaSwiper = React.forwardRef<SwiperRef, MediaSwiperProps>(
|
||||
<div className="swiper-lazy-preloader border-brand"></div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</SimpleSwiper>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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} />
|
||||
}
|
||||
@@ -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/css'
|
||||
import 'swiper/css/free-mode'
|
||||
import 'swiper/css/pagination'
|
||||
import 'swiper/css/navigation'
|
||||
import './simple-swiper.css'
|
||||
import { cn } from '@/libs/cn'
|
||||
import React from 'react'
|
||||
import { FractionPagination } from './FractionPagination'
|
||||
import { isEnabledPagination, makeCustomPaginationOptions } from './utils'
|
||||
|
||||
export const SimpleSwiper = React.forwardRef<SwiperRef, SwiperProps>(
|
||||
({ children, className, ...props }, ref) => (
|
||||
<Swiper
|
||||
direction="horizontal"
|
||||
slidesPerView="auto"
|
||||
freeMode={true}
|
||||
spaceBetween={24}
|
||||
mousewheel={{ enabled: true, forceToAxis: true }}
|
||||
modules={[FreeMode, Mousewheel]}
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={cn('simple-swiper', className)}
|
||||
>
|
||||
{children}
|
||||
</Swiper>
|
||||
)
|
||||
({ children, className, pagination, modules, ...props }, ref) => {
|
||||
const paginationOptions = makeCustomPaginationOptions(pagination)
|
||||
|
||||
return (
|
||||
<Swiper
|
||||
direction="horizontal"
|
||||
slidesPerView="auto"
|
||||
freeMode={true}
|
||||
spaceBetween={24}
|
||||
mousewheel={{ enabled: true, forceToAxis: true }}
|
||||
modules={[Navigation, Pagination, FreeMode, Mousewheel, A11y, ...(modules || [])]}
|
||||
{...props}
|
||||
{...paginationOptions}
|
||||
ref={ref}
|
||||
className={cn('simple-swiper', className)}
|
||||
>
|
||||
{children}
|
||||
{isEnabledPagination(pagination) && <FractionPagination />}
|
||||
</Swiper>
|
||||
)
|
||||
}
|
||||
)
|
||||
SimpleSwiper.displayName = 'SimpleSwiper'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -2,3 +2,15 @@
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.swiper-pagination-fraction {
|
||||
@apply text-xl w-auto;
|
||||
}
|
||||
|
||||
.swiper-pagination-current {
|
||||
@apply text-text-light;
|
||||
}
|
||||
|
||||
.swiper-button-disabled {
|
||||
@apply opacity-0;
|
||||
}
|
||||
|
||||
37
src/components/swiper/utils.ts
Normal file
37
src/components/swiper/utils.ts
Normal 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user