1- import React , { useState } from "react" ;
1+ import React , { useState , useCallback } from "react" ;
22import { Button } from "@/components/ui/button" ;
33import { Sparkles , Lock , Shield } from "lucide-react" ;
44
@@ -17,6 +17,7 @@ const getRarityColor = (rarity) =>
1717 RARITY_GRADIENTS [ rarity ] || "from-gray-400 to-gray-600" ;
1818
1919// Displays a single NFT with rarity gradient, lock state, and claim action.
20+ const NFTCard = ( { nft, onClaim } ) => {
2021// Wrapped in React.memo so that when a parent re-renders (e.g. the wallet
2122// store updating) and passes the same `nft` reference to every card in
2223// a gallery, the heavy gradient DOM tree doesn't get re-reconciled for
@@ -25,10 +26,21 @@ const getRarityColor = (rarity) =>
2526const NFTCard = ( { nft } ) => {
2627 const [ isHovered , setIsHovered ] = useState ( false ) ;
2728
29+ const handleKeyDown = useCallback (
30+ ( e ) => {
31+ if ( ! nft . locked && ( e . key === "Enter" || e . key === " " ) ) {
32+ e . preventDefault ( ) ;
33+ onClaim ?. ( nft ) ;
34+ }
35+ } ,
36+ [ nft , onClaim ]
37+ ) ;
38+
2839 return (
2940 < div
30- aria-label = { nft ?. name ? `NFT: ${ nft . name } ` : "NFT card" }
31- className = "relative overflow-hidden transition-transform duration-300 group rounded-xl hover:scale-105"
41+ role = "article"
42+ aria-label = { nft ?. name ? `NFT: ${ nft . name } ${ nft . locked ? " (locked)" : "" } ` : "NFT card" }
43+ className = "relative overflow-hidden transition-transform duration-300 group rounded-xl hover:scale-105 focus-within:ring-2 focus-within:ring-purple-500 focus-within:ring-offset-2 focus-within:ring-offset-black"
3244 onMouseEnter = { ( ) => setIsHovered ( true ) }
3345 onMouseLeave = { ( ) => setIsHovered ( false ) }
3446 >
@@ -55,8 +67,12 @@ const NFTCard = ({ nft }) => {
5567
5668 { /* Locked Overlay */ }
5769 { nft . locked && (
58- < div className = "absolute inset-0 flex items-center justify-center bg-black/60" >
59- < Lock className = "w-12 h-12 text-white/50" />
70+ < div
71+ role = "status"
72+ aria-label = "This NFT is locked"
73+ className = "absolute inset-0 flex items-center justify-center bg-black/60"
74+ >
75+ < Lock className = "w-12 h-12 text-white/50" aria-hidden = "true" />
6076 </ div >
6177 ) }
6278
@@ -96,15 +112,20 @@ const NFTCard = ({ nft }) => {
96112 : "bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600"
97113 } `}
98114 disabled = { nft . locked }
115+ aria-disabled = { nft . locked }
116+ aria-pressed = { ! nft . locked ? undefined : undefined }
117+ aria-label = { nft . locked ? `${ nft . name } is locked. Complete challenges to unlock.` : `Claim ${ nft . name } NFT` }
118+ onKeyDown = { handleKeyDown }
119+ tabIndex = { 0 }
99120 >
100121 { nft . locked ? (
101122 < span className = "flex items-center" >
102- < Lock className = "w-4 h-4 mr-2" />
123+ < Lock className = "w-4 h-4 mr-2" aria-hidden = "true" />
103124 Complete Challenges to Unlock
104125 </ span >
105126 ) : (
106127 < span className = "flex items-center" >
107- < Sparkles className = "w-4 h-4 mr-2" />
128+ < Sparkles className = "w-4 h-4 mr-2" aria-hidden = "true" />
108129 Claim NFT
109130 </ span >
110131 ) }
0 commit comments