Accessible Clickable Area
by _sir
HTML
<div class="actionable" data-click-area-expanded>
<p>
So much content
that it's not funny
</p>
<p>
<a href="#" class="learnMore">Learn More</a>
</p>
<label for="buttonBlue" class="sr-only">View Details for Check 844</label>
<button id="buttonBlue" class="viewDetail" data-click-area-target="true">
→
</button>
</div>
<div class="actionable" data-click-area-expanded>
<p>
So much content
that it's not funny
</p>
<button class="info learnMore">
ℹ️
</button>
<label for="buttonRed" class="sr-only">View Details for Check 123</label>
<button id="buttonRed" class="viewDetail" data-click-area-target="true">
<span>»</span>
</button>
</div>
<div id="container">
</div>
CSS
div.actionable {
margin: 1em;
padding: 1em;
border: solid gray;
border-width: 1px 0;
height: 4em;
position: relative;
}
.actionable button.viewDetail {
background: steelblue;
color: white;
font-weight: bold;
font-size: 2rem;
position: absolute;
border: none;
height: 100%;
top: 0;
right: 0;
}
button.info {
border: none;
background: none;
}
#buttonBlue {
background: steelblue;
color: white;
}
#buttonRed {
background: maroon;
color: white;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: none;
}
React
function ExpandedClickableArea(props) {
const clickableElemTypes = ['a', 'button', 'input'];
const refExpandedArea = useRef();
function handleClick(e) {
const actionTargets = [
...refExpandedArea.current.querySelectorAll('[data-expand-click-area]')
];
if (clickableElems.length !== 1) {
throw new Error(
`Expected one clickable element but found ${clickableElems.length}`
);
}
const [actionTarget] = actionTargets0;
const clickableElements = [...refExpandedArea.current.querySelectorAll('a,button,input')];
const originalIsClickable = clickableElements.some((el) => el === e.target || el.contains(e.target));
if (clickableElem !== e.target && !originalIsClickable) {
clickableElem.click();
}
}
return (
<div
ref={refExpandedArea}
className={`${props.className} ${styles.expandedClickableArea}`}
tabIndex="0"
onClick={handleClick}
>
{props.children}
</div>
);
};
ReactDOM.render(
<ExpandedClickableArea>
</ExpandedClickableArea>,
document.getElementById('container')
);
document.getElementById('buttonBlue').addEventListener('click', () => console.log('We clicked the blue button!'));
document.getElementById('buttonRed').addEventListener('click', () => console.log('We clicked the red button!'));
[...document.querySelectorAll('.learnMore')].forEach(el => el.addEventListener('click', () => console.log('Learn more!')));
const clickableElemTypes = ['a', 'button', 'input'];
[...document.querySelectorAll('[data-click-area-expanded]')].forEach((container) => {
container.addEventListener('click', (e) => {
const targets = [...container.querySelectorAll('[data-click-area-target]')];
if (targets.length !== 1) {
throw new Error('Could not find single click target');
}
const [actionTarget] = targets;
const clickables = [...container.querySelectorAll('a,button,input')];
const inClickable = clickables.some((el) =>...