mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
feat(a11y): dialog semantics + focus trap for the onboarding tour
- role=dialog/aria-modal/aria-labelledby/aria-describedby on the onboarding tooltip - trap focus inside the tooltip while the tour runs; Esc skips (lockScroll off so scrollIntoView can still bring each step's target into view) - merge the focus-trap ref with the existing measurement ref via a callback ref - aria-hidden on the click-to-advance target overlay (redundant with the Next button)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFocusTrap } from '../hooks/useFocusTrap';
|
||||
|
||||
interface OnboardingStep {
|
||||
target: string; // data-onboarding attribute value
|
||||
@@ -126,6 +127,17 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp
|
||||
onSkip();
|
||||
};
|
||||
|
||||
// Trap focus inside the tooltip while the tour runs; Esc skips it.
|
||||
// lockScroll stays off so scrollIntoView can bring each target into view.
|
||||
const trapRef = useFocusTrap<HTMLDivElement>(true, { onEscape: handleSkip, lockScroll: false });
|
||||
const setTooltipNode = useCallback(
|
||||
(node: HTMLDivElement | null) => {
|
||||
tooltipRef.current = node;
|
||||
trapRef.current = node;
|
||||
},
|
||||
[trapRef],
|
||||
);
|
||||
|
||||
// Calculate tooltip position
|
||||
const getTooltipStyle = (): React.CSSProperties => {
|
||||
if (!targetRect) return { opacity: 0 };
|
||||
@@ -205,7 +217,11 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp
|
||||
|
||||
{/* Tooltip */}
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
ref={setTooltipNode}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="onboarding-title"
|
||||
aria-describedby="onboarding-desc"
|
||||
className={`onboarding-tooltip tooltip-${step.placement}`}
|
||||
style={{
|
||||
...getTooltipStyle(),
|
||||
@@ -229,8 +245,12 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<h3 className="mb-2 text-lg font-semibold text-dark-50">{step.title}</h3>
|
||||
<p className="mb-5 text-sm text-dark-400">{step.description}</p>
|
||||
<h3 id="onboarding-title" className="mb-2 text-lg font-semibold text-dark-50">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p id="onboarding-desc" className="mb-5 text-sm text-dark-400">
|
||||
{step.description}
|
||||
</p>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -259,6 +279,7 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp
|
||||
{/* Click handler to advance on target click — only when overlay is fully visible */}
|
||||
{targetRect && isVisible && (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute cursor-pointer"
|
||||
style={{
|
||||
top: targetRect.top,
|
||||
|
||||
Reference in New Issue
Block a user