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 { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useFocusTrap } from '../hooks/useFocusTrap';
|
||||||
|
|
||||||
interface OnboardingStep {
|
interface OnboardingStep {
|
||||||
target: string; // data-onboarding attribute value
|
target: string; // data-onboarding attribute value
|
||||||
@@ -126,6 +127,17 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp
|
|||||||
onSkip();
|
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
|
// Calculate tooltip position
|
||||||
const getTooltipStyle = (): React.CSSProperties => {
|
const getTooltipStyle = (): React.CSSProperties => {
|
||||||
if (!targetRect) return { opacity: 0 };
|
if (!targetRect) return { opacity: 0 };
|
||||||
@@ -205,7 +217,11 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp
|
|||||||
|
|
||||||
{/* Tooltip */}
|
{/* Tooltip */}
|
||||||
<div
|
<div
|
||||||
ref={tooltipRef}
|
ref={setTooltipNode}
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="onboarding-title"
|
||||||
|
aria-describedby="onboarding-desc"
|
||||||
className={`onboarding-tooltip tooltip-${step.placement}`}
|
className={`onboarding-tooltip tooltip-${step.placement}`}
|
||||||
style={{
|
style={{
|
||||||
...getTooltipStyle(),
|
...getTooltipStyle(),
|
||||||
@@ -229,8 +245,12 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<h3 className="mb-2 text-lg font-semibold text-dark-50">{step.title}</h3>
|
<h3 id="onboarding-title" className="mb-2 text-lg font-semibold text-dark-50">
|
||||||
<p className="mb-5 text-sm text-dark-400">{step.description}</p>
|
{step.title}
|
||||||
|
</h3>
|
||||||
|
<p id="onboarding-desc" className="mb-5 text-sm text-dark-400">
|
||||||
|
{step.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center justify-between">
|
<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 */}
|
{/* Click handler to advance on target click — only when overlay is fully visible */}
|
||||||
{targetRect && isVisible && (
|
{targetRect && isVisible && (
|
||||||
<div
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
className="absolute cursor-pointer"
|
className="absolute cursor-pointer"
|
||||||
style={{
|
style={{
|
||||||
top: targetRect.top,
|
top: targetRect.top,
|
||||||
|
|||||||
Reference in New Issue
Block a user