1- import { memo , type RefObject } from 'react'
1+ import { memo , useEffect , type RefObject } from 'react'
22
33// Split helper component; parent card owns useCardLoadingState.
44import { AlertCircle , AlertTriangle , BellOff , Clock , List , MoreVertical , RefreshCw } from 'lucide-react'
55import { useTranslation } from 'react-i18next'
66import { CardControlsRow , CardSearchInput } from '../../lib/cards/CardComponents'
77import { SNOOZE_DURATIONS , type SnoozeDuration } from '../../hooks/useSnoozedAlerts'
8+ import { useKeyboardNav , useTabKeyboardNav } from '../../hooks/useKeyboardNav'
89import { cn } from '../../lib/cn'
910import { StatusBadge } from '../ui/StatusBadge'
1011import { CARD_UI_STRINGS } from './strings'
@@ -92,6 +93,13 @@ export const HardwareHealthCardHeader = memo(function HardwareHealthCardHeader({
9293 isDemoData,
9394} : HardwareHealthCardHeaderProps ) {
9495 const { t } = useTranslation ( [ 'cards' , 'common' ] )
96+ const snoozeMenuNav = useKeyboardNav ( { selector : '[role="menuitem"]:not([disabled])' , orientation : 'vertical' , onEscape : onToggleSnoozeAllMenu } )
97+ const { tabListProps, getTabProps } = useTabKeyboardNav < ViewMode > ( { tabs : [ 'inventory' , 'alerts' ] , activeTab : viewMode , onChange : onViewModeChange } )
98+
99+ useEffect ( ( ) => {
100+ if ( ! snoozeAllMenuOpen ) return
101+ snoozeMenuNav . focusMatchingItem ( { fallbackSelector : '[role="menuitem"]:not([disabled])' } )
102+ } , [ snoozeAllMenuOpen , snoozeMenuNav ] )
95103
96104 return (
97105 < >
@@ -139,12 +147,11 @@ export const HardwareHealthCardHeader = memo(function HardwareHealthCardHeader({
139147 </ div >
140148
141149 < div className = "flex flex-wrap gap-2 mb-3" >
142- < div className = "flex flex-1 min-w-0 bg-muted/30 rounded-lg p-0.5" >
150+ < div { ... tabListProps } className = "flex flex-1 min-w-0 bg-muted/30 rounded-lg p-0.5" >
143151 < button
144- onClick = { ( ) => onViewModeChange ( 'inventory' ) }
152+ { ... getTabProps ( 'inventory' ) }
145153 className = { cn ( 'flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors' , viewMode === 'inventory' ? 'bg-background text-foreground shadow-xs' : 'text-muted-foreground hover:text-foreground' ) }
146154 aria-label = { t ( 'cards:hardwareHealth.switchToInventoryAria' ) }
147- aria-pressed = { viewMode === 'inventory' }
148155 >
149156 < List className = "w-3.5 h-3.5" />
150157 { t ( 'cards:hardwareHealth.inventory' , 'Inventory' ) }
@@ -153,10 +160,9 @@ export const HardwareHealthCardHeader = memo(function HardwareHealthCardHeader({
153160 ) }
154161 </ button >
155162 < button
156- onClick = { ( ) => onViewModeChange ( 'alerts' ) }
163+ { ... getTabProps ( 'alerts' ) }
157164 className = { cn ( 'flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors' , viewMode === 'alerts' ? 'bg-background text-foreground shadow-xs' : 'text-muted-foreground hover:text-foreground' ) }
158165 aria-label = { t ( 'cards:hardwareHealth.switchToAlertsAria' ) }
159- aria-pressed = { viewMode === 'alerts' }
160166 >
161167 < AlertCircle className = "w-3.5 h-3.5" />
162168 { t ( 'cards:hardwareHealth.alerts' , 'Alerts' ) }
@@ -194,11 +200,19 @@ export const HardwareHealthCardHeader = memo(function HardwareHealthCardHeader({
194200 < MoreVertical className = "w-4 h-4" />
195201 </ button >
196202 { snoozeAllMenuOpen && (
197- < div className = "absolute right-0 top-full mt-1 z-50 bg-popover border border-border rounded-lg shadow-lg py-1 min-w-[160px]" >
203+ < div
204+ ref = { ( node ) => {
205+ snoozeMenuNav . containerRef . current = node
206+ } }
207+ role = "menu"
208+ className = "absolute right-0 top-full mt-1 z-50 bg-popover border border-border rounded-lg shadow-lg py-1 min-w-[160px]"
209+ onKeyDown = { snoozeMenuNav . handleKeyDown }
210+ >
198211 < div className = "px-3 py-1.5 text-xs font-medium text-muted-foreground border-b border-border mb-1" > { t ( 'cards:hardwareHealth.snoozeAllVisibleCount' , 'Snooze All ({{count}})' , { count : visibleAlertIds . length } ) } </ div >
199212 { ( Object . keys ( SNOOZE_DURATIONS ) as SnoozeDuration [ ] ) . map ( duration => (
200213 < button
201214 key = { duration }
215+ role = "menuitem"
202216 onClick = { ( ) => onSnoozeAll ( duration ) }
203217 className = "w-full px-3 py-1.5 text-xs text-left hover:bg-muted/50 transition-colors flex items-center gap-2"
204218 aria-label = { t ( 'cards:hardwareHealth.snoozeAllForAria' , { duration } ) }
@@ -211,6 +225,7 @@ export const HardwareHealthCardHeader = memo(function HardwareHealthCardHeader({
211225 < >
212226 < div className = "border-t border-border my-1" />
213227 < button
228+ role = "menuitem"
214229 onClick = { onClearAllSnoozed }
215230 className = "w-full px-3 py-1.5 text-xs text-left text-yellow-400 hover:bg-muted/50 transition-colors"
216231 aria-label = { t ( 'cards:hardwareHealth.clearAllSnoozesAria' ) }
0 commit comments