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">
|
<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">
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
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 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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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, 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'
|
||||||
|
|||||||
@@ -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%;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
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