Skip to content

Commit efc5044

Browse files
authored
Merge pull request #229 from jhd3197/feat/ux-remote-access-tooltips
fix(ui): make InfoTips work with Enter and easier to tap
2 parents ab9d2b8 + cb26a62 commit efc5044

2 files changed

Lines changed: 33 additions & 2 deletions

File tree

‎frontend/src/components/InfoTip.jsx‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,34 @@ import useMediaQuery from '../hooks/useMediaQuery';
66
import { 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.
1415
export 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); }}

‎frontend/src/styles/components/_info-tip.scss‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@
1313
color: $text-muted;
1414
cursor: pointer;
1515
vertical-align: middle;
16+
position: relative;
17+
18+
// Invisible 32px touch target; the glyph and line box stay as they are.
19+
&::before {
20+
content: '';
21+
position: absolute;
22+
inset-block-start: 50%;
23+
inset-inline-start: 50%;
24+
width: 32px; // geometry: minimum touch target
25+
height: 32px; // geometry: minimum touch target
26+
transform: translate(-50%, -50%);
27+
}
1628

1729
&:hover,
1830
&[data-state='open'] { color: $accent-primary; }

0 commit comments

Comments
 (0)