@@ -6,17 +6,34 @@ import useMediaQuery from '../hooks/useMediaQuery';
66import { t } from '../i18n/t' ;
77
88// An (i) button that holds the explanation a page used to print inline.
9- // Mouse: hover/focus previews it. Touch, click, Enter and Space: toggles a
10- // pinned popover that closes on outside tap or Escape. Narrow screens get a
9+ // Mouse: hover/focus previews it. Touch and click toggle a pinned popover that
10+ // closes on outside tap or Escape. Keyboard: Enter/Space on an unpinned preview
11+ // pins it open; on a pinned tip they toggle it closed. Narrow screens get a
1112// bottom sheet instead, so the text never sits on top of the content it
1213// explains. Both are portalled (no button-in-button) and above Modal/Sheet.
1314// `trigger` replaces the (i) glyph (e.g. a whole step pill); `title` heads the sheet.
1415export default function InfoTip ( { children, label, title, trigger, side = 'top' , size = 'sm' , className = '' } ) {
1516 const [ open , setOpen ] = useState ( false ) ;
1617 const narrow = useMediaQuery ( '(max-width: 640px)' ) ;
1718 const pinned = useRef ( false ) ;
19+ const pinnedByKey = useRef ( false ) ;
1820 const hoverable = ( e ) => e . pointerType === 'mouse' ;
1921
22+ // preventDefault skips Radix's toggle (it honours defaultPrevented); Space
23+ // also clicks on keyup, so that one is swallowed too.
24+ const onKeyDown = ( e ) => {
25+ if ( ( e . key !== 'Enter' && e . key !== ' ' ) || ! open || pinned . current ) return ;
26+ e . preventDefault ( ) ;
27+ pinned . current = true ;
28+ pinnedByKey . current = e . key === ' ' ;
29+ } ;
30+ const onKeyUp = ( e ) => {
31+ if ( e . key === ' ' && pinnedByKey . current ) {
32+ e . preventDefault ( ) ;
33+ pinnedByKey . current = false ;
34+ }
35+ } ;
36+
2037 const onOpenChange = ( next ) => {
2138 pinned . current = next ;
2239 setOpen ( next ) ;
@@ -31,6 +48,8 @@ export default function InfoTip({ children, label, title, trigger, side = 'top',
3148 className = { ( trigger ? className : `info-tip info-tip--${ size } ${ className } ` ) . trim ( ) }
3249 aria-label = { trigger ? label : ( label || t ( 'common.moreInfo' , 'More info' ) ) }
3350 onClick = { stop }
51+ onKeyDown = { onKeyDown }
52+ onKeyUp = { onKeyUp }
3453 onPointerEnter = { ( e ) => { if ( hoverable ( e ) ) setOpen ( true ) ; } }
3554 onPointerLeave = { ( e ) => { if ( hoverable ( e ) && ! pinned . current ) setOpen ( false ) ; } }
3655 onFocus = { ( e ) => { if ( e . currentTarget . matches ( ':focus-visible' ) ) setOpen ( true ) ; } }
0 commit comments