From ff91d0d10ecb84e12a81ecb4db07567cda86971e Mon Sep 17 00:00:00 2001 From: Sarthak-Bhagat2006 Date: Thu, 11 Jun 2026 19:26:21 +0530 Subject: [PATCH] feat: add keyboard navigation support to CircularGallery --- .../CircularGallery/CircularGallery.css | 5 +++ .../CircularGallery/CircularGallery.jsx | 44 ++++++++++++++++++- .../CircularGallery/CircularGallery.jsx | 33 +++++++++++++- .../CircularGallery/CircularGallery.css | 5 +++ .../CircularGallery/CircularGallery.tsx | 37 +++++++++++++++- .../CircularGallery/CircularGallery.tsx | 42 +++++++++++++++++- 6 files changed, 162 insertions(+), 4 deletions(-) diff --git a/src/content/Components/CircularGallery/CircularGallery.css b/src/content/Components/CircularGallery/CircularGallery.css index 9adb61f34..2df493def 100644 --- a/src/content/Components/CircularGallery/CircularGallery.css +++ b/src/content/Components/CircularGallery/CircularGallery.css @@ -8,3 +8,8 @@ .circular-gallery:active { cursor: grabbing; } + +.circular-gallery:focus-visible { + outline: 2px solid #fff; + outline-offset: 4px; +} diff --git a/src/content/Components/CircularGallery/CircularGallery.jsx b/src/content/Components/CircularGallery/CircularGallery.jsx index 5c96a362b..3486dbc60 100644 --- a/src/content/Components/CircularGallery/CircularGallery.jsx +++ b/src/content/Components/CircularGallery/CircularGallery.jsx @@ -488,6 +488,31 @@ class App { this.scroll.target += (delta > 0 ? this.scrollSpeed : -this.scrollSpeed) * 0.2; this.onCheckDebounce(); } + onKeyDown(e) { + switch (e.key) { + case 'ArrowRight': + e.preventDefault(); + this.scroll.target += this.scrollSpeed * 5; + this.onCheckDebounce(); + break; + + case 'ArrowLeft': + e.preventDefault(); + this.scroll.target -= this.scrollSpeed * 5; + this.onCheckDebounce(); + break; + + case 'Home': + e.preventDefault(); + this.scroll.target = 0; + this.onCheckDebounce(); + break; + + default: + break; + } + } + onCheck() { if (!this.medias || !this.medias[0]) return; const width = this.medias[0].width; @@ -528,6 +553,8 @@ class App { this.boundOnTouchDown = this.onTouchDown.bind(this); this.boundOnTouchMove = this.onTouchMove.bind(this); this.boundOnTouchUp = this.onTouchUp.bind(this); + this.boundOnKeyDown = this.onKeyDown.bind(this); + window.addEventListener('resize', this.boundOnResize); window.addEventListener('mousewheel', this.boundOnWheel); window.addEventListener('wheel', this.boundOnWheel); @@ -537,6 +564,8 @@ class App { window.addEventListener('touchstart', this.boundOnTouchDown); window.addEventListener('touchmove', this.boundOnTouchMove); window.addEventListener('touchend', this.boundOnTouchUp); + + this.container?.addEventListener('keydown', this.boundOnKeyDown); } destroy() { window.cancelAnimationFrame(this.raf); @@ -552,6 +581,10 @@ class App { if (this.renderer && this.renderer.gl && this.renderer.gl.canvas.parentNode) { this.renderer.gl.canvas.parentNode.removeChild(this.renderer.gl.canvas); } + + if (this.container) { + this.container.removeEventListener('keydown', this.boundOnKeyDown); + } } } @@ -582,10 +615,19 @@ export default function CircularGallery({ scrollEase }); }); + return () => { isMounted = false; if (app) app.destroy(); }; }, [items, bend, textColor, borderRadius, font, fontUrl, scrollSpeed, scrollEase]); - return
; + return ( +
+ ); } diff --git a/src/tailwind/Components/CircularGallery/CircularGallery.jsx b/src/tailwind/Components/CircularGallery/CircularGallery.jsx index 86124b091..ac10eae8e 100644 --- a/src/tailwind/Components/CircularGallery/CircularGallery.jsx +++ b/src/tailwind/Components/CircularGallery/CircularGallery.jsx @@ -486,6 +486,22 @@ class App { this.scroll.target += (delta > 0 ? this.scrollSpeed : -this.scrollSpeed) * 0.2; this.onCheckDebounce(); } + onKeyDown(e) { + switch (e.key) { + case 'ArrowRight': + e.preventDefault(); + this.scroll.target += this.scrollSpeed * 5; + this.onCheckDebounce(); + break; + case 'ArrowLeft': + e.preventDefault(); + this.scroll.target -= this.scrollSpeed * 5; + this.onCheckDebounce(); + break; + default: + break; + } + } onCheck() { if (!this.medias || !this.medias[0]) return; const width = this.medias[0].width; @@ -526,6 +542,7 @@ class App { this.boundOnTouchDown = this.onTouchDown.bind(this); this.boundOnTouchMove = this.onTouchMove.bind(this); this.boundOnTouchUp = this.onTouchUp.bind(this); + this.boundOnKeyDown = this.onKeyDown.bind(this); window.addEventListener('resize', this.boundOnResize); window.addEventListener('mousewheel', this.boundOnWheel); window.addEventListener('wheel', this.boundOnWheel); @@ -535,6 +552,8 @@ class App { window.addEventListener('touchstart', this.boundOnTouchDown); window.addEventListener('touchmove', this.boundOnTouchMove); window.addEventListener('touchend', this.boundOnTouchUp); + + this.container?.addEventListener('keydown', this.boundOnKeyDown); } destroy() { window.cancelAnimationFrame(this.raf); @@ -550,6 +569,10 @@ class App { if (this.renderer && this.renderer.gl && this.renderer.gl.canvas.parentNode) { this.renderer.gl.canvas.parentNode.removeChild(this.renderer.gl.canvas); } + + if (this.container) { + this.container.removeEventListener('keydown', this.boundOnKeyDown); + } } } @@ -585,5 +608,13 @@ export default function CircularGallery({ if (app) app.destroy(); }; }, [items, bend, textColor, borderRadius, font, fontUrl, scrollSpeed, scrollEase]); - return
; + return ( +
+ ); } diff --git a/src/ts-default/Components/CircularGallery/CircularGallery.css b/src/ts-default/Components/CircularGallery/CircularGallery.css index 9adb61f34..2df493def 100644 --- a/src/ts-default/Components/CircularGallery/CircularGallery.css +++ b/src/ts-default/Components/CircularGallery/CircularGallery.css @@ -8,3 +8,8 @@ .circular-gallery:active { cursor: grabbing; } + +.circular-gallery:focus-visible { + outline: 2px solid #fff; + outline-offset: 4px; +} diff --git a/src/ts-default/Components/CircularGallery/CircularGallery.tsx b/src/ts-default/Components/CircularGallery/CircularGallery.tsx index 2b5cfb284..1275349a4 100644 --- a/src/ts-default/Components/CircularGallery/CircularGallery.tsx +++ b/src/ts-default/Components/CircularGallery/CircularGallery.tsx @@ -506,6 +506,7 @@ class App { boundOnTouchDown!: (e: MouseEvent | TouchEvent) => void; boundOnTouchMove!: (e: MouseEvent | TouchEvent) => void; boundOnTouchUp!: () => void; + boundOnKeyDown!: (e: KeyboardEvent) => void; isDown: boolean = false; start: number = 0; @@ -669,6 +670,25 @@ class App { this.onCheckDebounce(); } + onKeyDown(e: KeyboardEvent) { + switch (e.key) { + case 'ArrowRight': + e.preventDefault(); + this.scroll.target += this.scrollSpeed * 5; + this.onCheckDebounce(); + break; + + case 'ArrowLeft': + e.preventDefault(); + this.scroll.target -= this.scrollSpeed * 5; + this.onCheckDebounce(); + break; + + default: + break; + } + } + onCheck() { if (!this.medias || !this.medias[0]) return; const width = this.medias[0].width; @@ -712,6 +732,8 @@ class App { this.boundOnTouchDown = this.onTouchDown.bind(this); this.boundOnTouchMove = this.onTouchMove.bind(this); this.boundOnTouchUp = this.onTouchUp.bind(this); + this.boundOnKeyDown = this.onKeyDown.bind(this); + window.addEventListener('resize', this.boundOnResize); window.addEventListener('mousewheel', this.boundOnWheel); window.addEventListener('wheel', this.boundOnWheel); @@ -721,6 +743,8 @@ class App { window.addEventListener('touchstart', this.boundOnTouchDown); window.addEventListener('touchmove', this.boundOnTouchMove); window.addEventListener('touchend', this.boundOnTouchUp); + + this.container?.addEventListener('keydown', this.boundOnKeyDown); } destroy() { @@ -737,6 +761,9 @@ class App { if (this.renderer && this.renderer.gl && this.renderer.gl.canvas.parentNode) { this.renderer.gl.canvas.parentNode.removeChild(this.renderer.gl.canvas as HTMLCanvasElement); } + if (this.container) { + this.container.removeEventListener('keydown', this.boundOnKeyDown); + } } } @@ -783,5 +810,13 @@ export default function CircularGallery({ if (app) app.destroy(); }; }, [items, bend, textColor, borderRadius, font, fontUrl, scrollSpeed, scrollEase]); - return
; + return ( +
+ ); } diff --git a/src/ts-tailwind/Components/CircularGallery/CircularGallery.tsx b/src/ts-tailwind/Components/CircularGallery/CircularGallery.tsx index f955269c5..88cc3cb71 100644 --- a/src/ts-tailwind/Components/CircularGallery/CircularGallery.tsx +++ b/src/ts-tailwind/Components/CircularGallery/CircularGallery.tsx @@ -505,6 +505,7 @@ class App { boundOnTouchDown!: (e: MouseEvent | TouchEvent) => void; boundOnTouchMove!: (e: MouseEvent | TouchEvent) => void; boundOnTouchUp!: () => void; + boundOnKeyDown!: (e: KeyboardEvent) => void; isDown: boolean = false; start: number = 0; @@ -668,6 +669,22 @@ class App { this.onCheckDebounce(); } + onKeyDown(e: KeyboardEvent) { + switch (e.key) { + case 'ArrowRight': + e.preventDefault(); + this.scroll.target += this.scrollSpeed * 5; + this.onCheckDebounce(); + break; + + case 'ArrowLeft': + e.preventDefault(); + this.scroll.target -= this.scrollSpeed * 5; + this.onCheckDebounce(); + break; + } + } + onCheck() { if (!this.medias || !this.medias[0]) return; const width = this.medias[0].width; @@ -711,6 +728,8 @@ class App { this.boundOnTouchDown = this.onTouchDown.bind(this); this.boundOnTouchMove = this.onTouchMove.bind(this); this.boundOnTouchUp = this.onTouchUp.bind(this); + this.boundOnKeyDown = this.onKeyDown.bind(this); + window.addEventListener('resize', this.boundOnResize); window.addEventListener('mousewheel', this.boundOnWheel); window.addEventListener('wheel', this.boundOnWheel); @@ -720,6 +739,12 @@ class App { window.addEventListener('touchstart', this.boundOnTouchDown); window.addEventListener('touchmove', this.boundOnTouchMove); window.addEventListener('touchend', this.boundOnTouchUp); + + this.container?.addEventListener( + 'keydown', + + this.boundOnKeyDown + ); } destroy() { @@ -736,6 +761,13 @@ class App { if (this.renderer && this.renderer.gl && this.renderer.gl.canvas.parentNode) { this.renderer.gl.canvas.parentNode.removeChild(this.renderer.gl.canvas as HTMLCanvasElement); } + if (this.container) { + this.container.removeEventListener( + 'keydown', + + this.boundOnKeyDown + ); + } } } @@ -782,5 +814,13 @@ export default function CircularGallery({ if (app) app.destroy(); }; }, [items, bend, textColor, borderRadius, font, fontUrl, scrollSpeed, scrollEase]); - return
; + return ( +
+ ); }