fix(a11y): batch-fix useButtonType (32→3) and noSvgWithoutTitle (20→7)
- Add type="button" to 29 button elements across 14 components - Add title element to 13 SVGs across 9 components - Remaining a11y: 159 errors (noLabelWithoutControl 37, noStaticElementInteractions 22, useKeyWithClickEvents 21, useSemanticElements 8, noSvgWithoutTitle 7, useButtonType 3, noAssignInExpressions 3)
This commit is contained in:
parent
096a168c03
commit
14fe663f28
23 changed files with 42 additions and 49 deletions
|
|
@ -258,6 +258,7 @@ export function ConsentBanner() {
|
|||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<title>Icon</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
|
@ -293,6 +294,7 @@ export function ConsentBanner() {
|
|||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<title>Icon</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export class ErrorBoundary extends Component<Props, State> {
|
|||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 rounded-xl bg-red-500/20 flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<title>Icon</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@ function SentimentPanel({ sentiment }: { sentiment?: SentimentData }) {
|
|||
<div className="flex items-center gap-5">
|
||||
<div className="relative w-20 h-20">
|
||||
<svg className="w-full h-full transform -rotate-90">
|
||||
<title>Icon</title>
|
||||
<circle cx="40" cy="40" r="34" fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="6" />
|
||||
<circle
|
||||
cx="40"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export default function RiskRing({ score, size = 48, strokeWidth = 4 }: RiskRing
|
|||
return (
|
||||
<div className="relative inline-flex items-center justify-center" style={{ width: size, height: size }}>
|
||||
<svg width={size} height={size} className="transform -rotate-90">
|
||||
<title>Icon</title>
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ export function TraceTab() {
|
|||
</div>
|
||||
<div className="relative">
|
||||
<svg ref={svgRef} viewBox="0 0 800 500" className="w-full h-[500px] bg-[#0a0e1a]">
|
||||
<title>Icon</title>
|
||||
<defs>
|
||||
<marker id="arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#5C6080" />
|
||||
|
|
|
|||
|
|
@ -157,8 +157,7 @@ export default function AISignals({ chain, tokenAddress, initialSignals, classNa
|
|||
const meta = t === 'all' ? null : TYPE_META[t]
|
||||
const count = t === 'all' ? signals.length : signals.filter((s) => s.type === t).length
|
||||
return (
|
||||
<button
|
||||
key={t}
|
||||
<button type="button" key={t}
|
||||
onClick={() => setFilterType(t as any)}
|
||||
className={`flex items-center gap-1 px-2 py-1 rounded text-[10px] uppercase tracking-wider transition-colors ${
|
||||
filterType === t
|
||||
|
|
|
|||
|
|
@ -160,8 +160,7 @@ export default function LiveTradeTape({ chain, tokenAddress, initialTrades = [],
|
|||
<div className="flex items-center justify-between px-3 py-2 border-b border-[rgba(139,92,246,0.08)]">
|
||||
<div className="flex gap-1">
|
||||
{(['all', 'whale', 'bot', 'suspicious'] as const).map((f) => (
|
||||
<button
|
||||
key={f}
|
||||
<button type="button" key={f}
|
||||
onClick={() => setFilter(f)}
|
||||
className={`px-2 py-0.5 text-[10px] uppercase tracking-wider rounded transition-colors ${
|
||||
filter === f
|
||||
|
|
@ -180,8 +179,7 @@ export default function LiveTradeTape({ chain, tokenAddress, initialTrades = [],
|
|||
{stats.suspicious} SUSPICIOUS
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setPaused((p) => !p)}
|
||||
<button type="button" onClick={() => setPaused((p) => !p)}
|
||||
className="px-2 py-0.5 bg-[rgba(139,92,246,0.08)] hover:bg-[rgba(139,92,246,0.2)] rounded text-[10px]"
|
||||
>
|
||||
{paused ? '▶ RESUME' : '⏸ PAUSE'}
|
||||
|
|
|
|||
|
|
@ -210,8 +210,7 @@ export default function MultiChainCompare({ initialTokens = [], className = '' }
|
|||
placeholder="chain:address (e.g. solana:So1111... or just 0x...)"
|
||||
className="flex-1 px-3 py-1.5 bg-[rgba(255,255,255,0.02)] border border-[rgba(139,92,246,0.08)] rounded-lg text-xs text-[#F1F1F6] placeholder:text-[#5C6080] focus:border-[#8B5CF6]/30 focus:outline-none font-mono"
|
||||
/>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
<button type="button" onClick={handleAdd}
|
||||
disabled={loading || !search.trim()}
|
||||
className="px-3 py-1.5 bg-[#8B5CF6] hover:bg-[#A78BFA] disabled:opacity-50 rounded-lg text-xs font-semibold uppercase tracking-wider text-white transition-colors"
|
||||
>
|
||||
|
|
@ -320,8 +319,7 @@ export default function MultiChainCompare({ initialTokens = [], className = '' }
|
|||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 text-right">
|
||||
<button
|
||||
onClick={() => removeToken(idx)}
|
||||
<button type="button" onClick={() => removeToken(idx)}
|
||||
className="text-[10px] text-[#5C6080] hover:text-[#FF3366] transition-colors"
|
||||
title="Remove"
|
||||
>
|
||||
|
|
@ -339,15 +337,13 @@ export default function MultiChainCompare({ initialTokens = [], className = '' }
|
|||
Page {page + 1} of {totalPages} · {ranked.length} tokens
|
||||
</span>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => setPage((p) => Math.max(0, p - 1))}
|
||||
<button type="button" onClick={() => setPage((p) => Math.max(0, p - 1))}
|
||||
disabled={page === 0}
|
||||
className="p-1 bg-[rgba(139,92,246,0.08)] hover:bg-[rgba(139,92,246,0.2)] disabled:opacity-30 rounded"
|
||||
>
|
||||
<ChevronLeft className="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
|
||||
<button type="button" onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
|
||||
disabled={page >= totalPages - 1}
|
||||
className="p-1 bg-[rgba(139,92,246,0.08)] hover:bg-[rgba(139,92,246,0.2)] disabled:opacity-30 rounded"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ function CircularProgress({ value, size = 120, color = '#8b5cf6', label = '' })
|
|||
return (
|
||||
<div className="relative inline-flex items-center justify-center">
|
||||
<svg width={size} height={size} className="-rotate-90">
|
||||
<title>Icon</title>
|
||||
<circle cx={size / 2} cy={size / 2} r={radius} fill="none" stroke="rgba(255,255,255,0.05)" strokeWidth="8" />
|
||||
<circle
|
||||
cx={size / 2}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ function CircularProgress({ value, size = 120, color = '#8b5cf6' }) {
|
|||
return (
|
||||
<div className="relative inline-flex items-center justify-center">
|
||||
<svg width={size} height={size} className="-rotate-90">
|
||||
<title>Icon</title>
|
||||
<circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="rgba(255,255,255,0.05)" strokeWidth="8" />
|
||||
<circle
|
||||
cx={size / 2}
|
||||
|
|
@ -470,8 +471,7 @@ export default function RugChartsPluginViewer({ tokenAddress, chain }) {
|
|||
const _hasData = plugins[meta.name] && !plugins[meta.name].error
|
||||
const error = plugins[meta.name]?.error
|
||||
return (
|
||||
<button
|
||||
key={meta.name}
|
||||
<button type="button" key={meta.name}
|
||||
onClick={() => setActiveTab(meta.name)}
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-xl text-xs font-bold whitespace-nowrap transition-all border ${
|
||||
isActive
|
||||
|
|
@ -510,8 +510,7 @@ export default function RugChartsPluginViewer({ tokenAddress, chain }) {
|
|||
|
||||
{/* Refresh button */}
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={fetchPlugins}
|
||||
<button type="button" onClick={fetchPlugins}
|
||||
className="px-4 py-2 rounded-xl text-xs font-bold text-gray-400 hover:text-white bg-white/5 border border-white/10 hover:border-white/20 transition-all"
|
||||
>
|
||||
↻ Refresh Analysis
|
||||
|
|
|
|||
|
|
@ -195,8 +195,7 @@ export default function RugPullWarning({ chain, tokenAddress, initialWarnings, c
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={runScan}
|
||||
<button type="button" onClick={runScan}
|
||||
disabled={scanning}
|
||||
className="p-1.5 rounded bg-[rgba(139,92,246,0.08)] hover:bg-[rgba(139,92,246,0.2)] disabled:opacity-50"
|
||||
title="Re-scan"
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ export default function ScamRiskMeter({
|
|||
{/* Arc meter */}
|
||||
<div className="relative w-24 h-24 flex-shrink-0">
|
||||
<svg className="w-full h-full -rotate-90" viewBox="0 0 100 100">
|
||||
<title>Icon</title>
|
||||
<circle cx="50" cy="50" r="42" stroke="rgba(139,92,246,0.1)" strokeWidth="8" fill="none" />
|
||||
<circle
|
||||
cx="50"
|
||||
|
|
@ -220,8 +221,7 @@ export default function ScamRiskMeter({
|
|||
{/* Factor bars by category */}
|
||||
{!loading && factors.length > 0 && (
|
||||
<div className="border-t border-[rgba(139,92,246,0.1)]">
|
||||
<button
|
||||
onClick={() => setExpanded((e) => !e)}
|
||||
<button type="button" onClick={() => setExpanded((e) => !e)}
|
||||
className="w-full px-4 py-2 flex items-center justify-between hover:bg-[rgba(139,92,246,0.04)] transition-colors"
|
||||
>
|
||||
<span className="text-[11px] uppercase tracking-wider text-[#F1F1F6] font-semibold">
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export default function BotNetworkPanel({ tokenAddress, chain }: BotNetworkPanel
|
|||
<h3 className="text-[14px] font-semibold text-[#F1F1F6]">Bot Network Detection</h3>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={detectBots} disabled={loading} className="btn btn-primary w-full">
|
||||
<button type="button" onClick={detectBots} disabled={loading} className="btn btn-primary w-full">
|
||||
{loading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : <Bot className="w-4 h-4 mr-2" />}
|
||||
{loading ? 'Analyzing transaction patterns...' : 'Detect Bot Networks'}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export default function ContractDeepDive({ tokenAddress, chain }: ContractDeepDi
|
|||
<h3 className="text-[14px] font-semibold text-[#F1F1F6]">Contract Deep Dive</h3>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={analyze} className="btn btn-primary w-full">
|
||||
<button type="button" onClick={analyze} className="btn btn-primary w-full">
|
||||
<Code className="w-4 h-4 mr-2" />
|
||||
Analyze Contract
|
||||
</button>
|
||||
|
|
@ -181,8 +181,7 @@ export default function ContractDeepDive({ tokenAddress, chain }: ContractDeepDi
|
|||
</div>
|
||||
|
||||
{functions.length > 4 && (
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
<button type="button" onClick={() => setExpanded(!expanded)}
|
||||
className="text-[10px] text-purple-400 mt-2 hover:text-purple-300"
|
||||
>
|
||||
{expanded ? 'Show less' : `Show ${functions.length - 4} more functions`}
|
||||
|
|
|
|||
|
|
@ -180,8 +180,7 @@ export default function DeployerNetwork({
|
|||
<Calendar className="w-3 h-3" />
|
||||
<span>Window:</span>
|
||||
{[30, 60, 90, 180, 365].map((d) => (
|
||||
<button
|
||||
key={d}
|
||||
<button type="button" key={d}
|
||||
onClick={() => setWindowDays(d)}
|
||||
className={`px-2 py-0.5 rounded ${windowDays === d ? 'bg-[#8B5CF6] text-white' : 'bg-[rgba(139,92,246,0.08)] hover:bg-[rgba(139,92,246,0.2)]'}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ export default function ExportPanel({ tokenAddress, chain, scanData, aiResult, s
|
|||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button onClick={generateJSON} className="btn btn-ghost btn-sm flex items-center gap-2">
|
||||
<button type="button" onClick={generateJSON} className="btn btn-ghost btn-sm flex items-center gap-2">
|
||||
<FileJson className="w-4 h-4" />
|
||||
JSON
|
||||
</button>
|
||||
<button onClick={generateShareLink} className="btn btn-ghost btn-sm flex items-center gap-2">
|
||||
<button type="button" onClick={generateShareLink} className="btn btn-ghost btn-sm flex items-center gap-2">
|
||||
<Share2 className="w-4 h-4" />
|
||||
Share Link
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -197,8 +197,7 @@ export default function FundingTracer({ chain, address, initialHops, className =
|
|||
const meta = TYPE_META[t]
|
||||
const stat = sourceStats[t] || { count: 0, total_usd: 0 }
|
||||
return (
|
||||
<button
|
||||
key={t}
|
||||
<button type="button" key={t}
|
||||
onClick={() => setFilter(filter === t ? 'all' : (t as any))}
|
||||
className={`px-2 py-1.5 rounded border transition-colors ${
|
||||
filter === t
|
||||
|
|
@ -227,8 +226,7 @@ export default function FundingTracer({ chain, address, initialHops, className =
|
|||
<div className="flex items-center gap-2 text-[#5C6080]">
|
||||
<span>Depth:</span>
|
||||
{[1, 2, 3, 5].map((d) => (
|
||||
<button
|
||||
key={d}
|
||||
<button type="button" key={d}
|
||||
onClick={() => setDepth(d)}
|
||||
className={`px-2 py-0.5 rounded ${depth === d ? 'bg-[#8B5CF6] text-white' : 'bg-[rgba(139,92,246,0.08)] hover:bg-[rgba(139,92,246,0.2)]'}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ export default function MultiTokenCompare({
|
|||
{/* Radar chart */}
|
||||
<div className="flex items-center justify-center bg-[#0E1525] rounded-lg p-3 border border-white/5">
|
||||
<svg width="220" height="220" viewBox="0 0 220 220" className="overflow-visible">
|
||||
<title>Icon</title>
|
||||
{/* Concentric rings */}
|
||||
{[0.25, 0.5, 0.75, 1].map((s) => (
|
||||
<circle
|
||||
|
|
|
|||
|
|
@ -124,19 +124,19 @@ export function RugMapsCanvas({ data, chain, width = 800, height = 500 }: RugMap
|
|||
return (
|
||||
<div className={`relative ${isFullscreen ? 'fixed inset-0 z-50 bg-[#0a0a1a]' : ''}`}>
|
||||
<div className="absolute top-2 right-2 z-10 flex gap-1">
|
||||
<button onClick={zoomIn} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-gray-300">
|
||||
<button type="button" onClick={zoomIn} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-gray-300">
|
||||
<ZoomIn size={16} />
|
||||
</button>
|
||||
<button onClick={zoomOut} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-gray-300">
|
||||
<button type="button" onClick={zoomOut} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-gray-300">
|
||||
<ZoomOut size={16} />
|
||||
</button>
|
||||
<button onClick={fitAll} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-gray-300">
|
||||
<button type="button" onClick={fitAll} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-gray-300">
|
||||
<RotateCcw size={16} />
|
||||
</button>
|
||||
<button onClick={toggleFullscreen} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-gray-300">
|
||||
<button type="button" onClick={toggleFullscreen} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-gray-300">
|
||||
{isFullscreen ? <Minimize2 size={16} /> : <Maximize2 size={16} />}
|
||||
</button>
|
||||
<button onClick={exportPng} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-green-400">
|
||||
<button type="button" onClick={exportPng} className="p-1.5 rounded bg-[#1a1a2e] hover:bg-[#2a2a3e] text-green-400">
|
||||
<Download size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -99,28 +99,24 @@ export default function TemporalPlayback({ frames, onFrameChange }: TemporalPlay
|
|||
|
||||
{/* Controls */}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<button
|
||||
onClick={() => setCurrentFrame(0)}
|
||||
<button type="button" onClick={() => setCurrentFrame(0)}
|
||||
className="p-2 rounded-[8px] hover:bg-[rgba(255,255,255,0.05)] text-[#5C6080]"
|
||||
>
|
||||
<SkipBack className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsPlaying(!isPlaying)}
|
||||
<button type="button" onClick={() => setIsPlaying(!isPlaying)}
|
||||
className="p-2.5 rounded-[10px] bg-cyan-500/10 border border-cyan-500/20 text-cyan-400 hover:bg-cyan-500/20"
|
||||
>
|
||||
{isPlaying ? <Pause className="w-5 h-5" /> : <Play className="w-5 h-5" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentFrame(Math.min(frames.length - 1, currentFrame + 10))}
|
||||
<button type="button" onClick={() => setCurrentFrame(Math.min(frames.length - 1, currentFrame + 10))}
|
||||
className="p-2 rounded-[8px] hover:bg-[rgba(255,255,255,0.05)] text-[#5C6080]"
|
||||
>
|
||||
<SkipForward className="w-4 h-4" />
|
||||
</button>
|
||||
<div className="flex items-center gap-1 ml-auto">
|
||||
{[0.5, 1, 2, 4].map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
<button type="button" key={s}
|
||||
onClick={() => setSpeed(s)}
|
||||
className={`px-2 py-1 rounded-[6px] text-[10px] ${speed === s ? 'bg-cyan-500/20 text-cyan-400' : 'text-[#5C6080] hover:text-[#9DA0B0]'}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -125,8 +125,7 @@ export default function X402PaymentGate({ onUpgrade, isConnected, walletAddress,
|
|||
})}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => selectedTier && handlePay(TIERS.find((t) => t.id === selectedTier)!)}
|
||||
<button type="button" onClick={() => selectedTier && handlePay(TIERS.find((t) => t.id === selectedTier)!)}
|
||||
disabled={!(selectedTier && isConnected) || processing}
|
||||
className="btn btn-primary w-full mt-3"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export function WalletClusterGraph({
|
|||
|
||||
return (
|
||||
<svg viewBox="0 0 800 600" className="w-full h-auto">
|
||||
<title>Icon</title>
|
||||
<defs>
|
||||
<filter id="glow-tc">
|
||||
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export default function Sparkline({
|
|||
if (!data || data.length < 2) {
|
||||
return (
|
||||
<svg width={width} height={height} className="opacity-30">
|
||||
<title>Icon</title>
|
||||
<line
|
||||
x1="0"
|
||||
y1={height / 2}
|
||||
|
|
@ -64,6 +65,7 @@ export default function Sparkline({
|
|||
|
||||
return (
|
||||
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} className="overflow-visible">
|
||||
<title>Icon</title>
|
||||
<defs>
|
||||
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor={color} stopOpacity="0.25" />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue