feat: keyboard navigation in carousel

This commit is contained in:
2024-10-24 03:57:26 +03:00
parent dbba6dcbb1
commit 9f3af6c476
3 changed files with 32 additions and 72 deletions

View File

@@ -1 +1 @@
0245f6e720b08c65d698b6a896bdaf7a
96a8176a62058aaf100c8e8139826079

View File

@@ -2,14 +2,6 @@ import { HtmlProps } from '@/common/types'
import { cn } from '@/common/utils'
import { screenshots } from '$models'
import { DialogContent, DialogTitle } from '@/components/ui/dialog'
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
useCarousel,
} from '../ui/carousel'
import { ScreenshotImg } from '../ScreenshotImg/ScreenshotImg'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Button } from '../ui/button'
@@ -40,17 +32,36 @@ export function ScreenshotsCarouselModal(
return vis
}, [idx, screenshots])
const prevScreenshot = useCallback(
() => setIdx((i) => (i === 0 ? screenshots.length - 1 : --i)),
[screenshots.length],
)
const nextScreenshot = useCallback(
() => setIdx((i) => (i === screenshots.length ? 0 : ++i)),
[screenshots.length],
)
const handleKeyUp = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault()
if (e.key === 'ArrowRight') {
e.preventDefault()
nextScreenshot()
} else if (e.key === 'ArrowLeft') {
e.preventDefault()
prevScreenshot()
}
},
[nextScreenshot, prevScreenshot],
)
return (
<div className={cn('', className)} {...rest}>
<div className={cn('', className)} {...rest} onKeyUp={handleKeyUp}>
<DialogContent className="max-w-[calc(100%_-_128px)] max-h-[calc(100%_-_64px)]">
<DialogTitle>Screenshots</DialogTitle>
<div className="flex gap-4 items-center w-full">
<Button
className="flex-shrink-0"
variant="outline"
size="icon"
onClick={() => setIdx((i) => (i === 0 ? screenshots.length - 1 : --i))}
>
<Button className="flex-shrink-0" variant="outline" size="icon" onClick={prevScreenshot}>
<FaAngleLeft />
</Button>
<div className="flex-grow flex place-content-center">
@@ -62,12 +73,7 @@ export function ScreenshotsCarouselModal(
/>
))}
</div>
<Button
className="flex-shrink-0"
variant="outline"
size="icon"
onClick={() => setIdx((i) => (i === screenshots.length ? 0 : ++i))}
>
<Button className="flex-shrink-0" variant="outline" size="icon" onClick={nextScreenshot}>
<FaAngleRight />
</Button>
</div>
@@ -75,50 +81,3 @@ export function ScreenshotsCarouselModal(
</div>
)
}
// <Carousel opts={{ startIndex: activeIndex ?? undefined, loop: false }}>
// <CarouselInner {...props} />
// <CarouselPrevious />
// <CarouselNext />
// </Carousel>
// function CarouselInner({
// activeIndex,
// screenshots,
// }: React.ComponentProps<typeof ScreenshotsCarouselModal>) {
// const carousel = useCarousel()
// const [inView, setInView] = useState(() => carousel.api?.slidesInView() ?? [])
// const isInView = useCallback(
// (idx: number) =>
// [-2, -1, 0, 1, 2].map((x) => idx + x).some((x) => activeIndex === x || inView.includes(x)),
// [activeIndex, inView],
// )
//
// useEffect(() => {
// if (!carousel.api) {
// return
// }
// const { api } = carousel
// const cb = () => {
// setInView(api.slidesInView() ?? [])
// }
// api.on('slidesInView', cb)
// return () => {
// api.off('slidesInView', cb)
// }
// }, [carousel])
//
// return (
// <CarouselContent className="max-h-full">
// {screenshots.map((scr, i) => (
// <CarouselItem key={scr.path} className="flex items-center justify-center">
// <ScreenshotImg
// screenshot={scr}
// load={isInView(i)}
// className="max-h-[calc(100vh_-_160px)] object-cover"
// />
// </CarouselItem>
// ))}
// </CarouselContent>
// )
// }

View File

@@ -4,7 +4,7 @@ import { LoadingContainer } from '@/components/Loader/LoadingContainer'
import { Button } from '@/components/ui/button'
import { FaAngleLeft } from 'react-icons/fa6'
import { ScreenshotImg } from '@/components/ScreenshotImg/ScreenshotImg'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { ScreenshotsCarouselModal } from '@/components/ScreenshotsCarouselModal/ScreenshotsCarouselModal'
import { Dialog } from '@/components/ui/dialog'
import { useGameScreenshots } from '@/common/hooks/useScreenshots'
@@ -28,6 +28,7 @@ function ScreenshotsGamePage() {
setModalIndex(null)
}, [])
const [headerRef, setHeaderRef] = useStateRef<HTMLDivElement | null>(null)
const [gridRef, setGridRef] = useStateRef<HTMLDivElement | null>(null)
const getColCount = useCallback(
() => Math.floor((gridRef?.clientWidth ?? window.innerWidth) / thumbSize),
@@ -77,7 +78,7 @@ function ScreenshotsGamePage() {
return (
<div className="relative">
<div className="sticky top-0 p-4 bg-background flex flex-col gap-2 z-10">
<div className="sticky top-0 p-4 bg-background flex flex-col gap-2 z-10" ref={setHeaderRef}>
<div>
<Button variant="outline" size="sm" asChild>
<Link to="/screenshots">
@@ -109,7 +110,7 @@ function ScreenshotsGamePage() {
<FixedSizeGrid
columnCount={colCount}
columnWidth={thumbSize}
height={window.innerHeight - 200}
height={window.innerHeight - (headerRef?.clientHeight ?? 200)}
rowCount={Math.ceil((dir.screenshots?.length ?? 0) / colCount)}
rowHeight={168}
width={colCount * (thumbSize + 4)}