diff --git a/src/components/tiles/ProjectsTile.tsx b/src/components/tiles/ProjectsTile.tsx
index 47062e3..b8f21b5 100644
--- a/src/components/tiles/ProjectsTile.tsx
+++ b/src/components/tiles/ProjectsTile.tsx
@@ -13,6 +13,12 @@ interface ProjectItemProps {
slideWidth: string
cardMinHeight: number
onClick: () => void
+ index: number
+ total: number
+ cardRef?: (el: HTMLDivElement | null) => void
+ onArrowKey?: (direction: -1 | 1) => void
+ onEscape?: () => void
+ isInert?: boolean
}
function ProjectItem({
@@ -20,6 +26,12 @@ function ProjectItem({
slideWidth,
cardMinHeight,
onClick,
+ index,
+ total,
+ cardRef,
+ onArrowKey,
+ onEscape,
+ isInert,
}: ProjectItemProps) {
const dotColor = PROJECT_STATUS_COLORS[project.status]
const livePillLabel = project.demoUrl ? 'Live Demo' : project.externalUrl ? 'Live' : null
@@ -30,15 +42,28 @@ function ProjectItem({
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
onClick()
+ } else if (e.key === 'ArrowLeft') {
+ e.preventDefault()
+ onArrowKey?.(-1)
+ } else if (e.key === 'ArrowRight') {
+ e.preventDefault()
+ onArrowKey?.(1)
+ } else if (e.key === 'Escape') {
+ e.preventDefault()
+ onEscape?.()
}
},
- [onClick],
+ [onClick, onArrowKey, onEscape],
)
const maxVisibleResults = 4
return (
>(new Map())
+
const [emblaRef, emblaApi] = useEmblaCarousel(
{ loop: true, align: 'start' },
[Autoplay({ delay: 4000, stopOnInteraction: false, stopOnMouseEnter: true })],
@@ -435,17 +463,41 @@ function EmblaProjectsCarousel() {
}
}, [emblaApi, onSelect])
+ const handleArrowKey = useCallback((currentIndex: number, direction: -1 | 1) => {
+ const nextIndex = (currentIndex + direction + investigations.length) % investigations.length
+ cardRefs.current.get(nextIndex)?.focus()
+ emblaApi?.scrollTo(nextIndex)
+ }, [emblaApi])
+
+ const handleEscape = useCallback(() => {
+ wrapperRef.current?.focus()
+ }, [])
+
return (
-
+
- {investigations.map((project) => (
+ {investigations.map((project, i) => (
openPanel({ type: 'project', investigation: project })}
+ index={i}
+ total={investigations.length}
+ cardRef={(el) => {
+ if (el) cardRefs.current.set(i, el)
+ else cardRefs.current.delete(i)
+ }}
+ onArrowKey={(dir) => handleArrowKey(i, dir)}
+ onEscape={handleEscape}
/>
))}
@@ -502,6 +554,8 @@ function ContinuousScrollCarousel() {
: false,
)
const resumeTimeoutRef = useRef
(0)
+ const containerRef = useRef(null)
+ const cardRefs = useRef