diff --git a/packages/pl-fe/src/components/list.tsx b/packages/pl-fe/src/components/list.tsx index 788798bfe..d11bbc74f 100644 --- a/packages/pl-fe/src/components/list.tsx +++ b/packages/pl-fe/src/components/list.tsx @@ -36,6 +36,8 @@ const ListItem: React.FC = ({ ...rest }) => { const [domId] = useState(`list-group-${crypto.randomUUID()}`); + const labelId = `${domId}-label`; + const hintId = `${domId}-hint`; const onKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { @@ -50,10 +52,22 @@ const ListItem: React.FC = ({ React.Children.map(children, (child) => { if (React.isValidElement(child)) { const isSelect = child.type === SelectDropdown || child.type === Select; + const childLabelledBy = child.props['aria-labelledby']; + const childDescribedBy = child.props['aria-describedby']; + const ariaLabelledBy = childLabelledBy ? `${childLabelledBy} ${labelId}` : labelId; + const ariaDescribedBy = hint + ? childDescribedBy + ? `${childDescribedBy} ${hintId}` + : hintId + : childDescribedBy; return React.cloneElement(child, { // @ts-ignore id: domId, + // @ts-ignore + 'aria-labelledby': ariaLabelledBy, + // @ts-ignore + 'aria-describedby': ariaDescribedBy, className: clsx( { 'w-auto': isSelect, @@ -65,7 +79,7 @@ const ListItem: React.FC = ({ return null; }), - [children, domId], + [children, domId, labelId, hint, hintId], ); const classNames = clsx('⁂-list-item', className, { @@ -76,9 +90,15 @@ const ListItem: React.FC = ({ const body = ( <>
- {label} + + {label} + - {hint ? {hint} : null} + {hint ? ( + + {hint} + + ) : null}
{'to' in rest || href || onClick ? ( diff --git a/packages/pl-fe/src/components/ui/slider.tsx b/packages/pl-fe/src/components/ui/slider.tsx index 36fad0f9c..a0c36d696 100644 --- a/packages/pl-fe/src/components/ui/slider.tsx +++ b/packages/pl-fe/src/components/ui/slider.tsx @@ -6,6 +6,8 @@ import { getPointerPosition } from '@/features/video'; interface ISlider { id?: string; + 'aria-labelledby'?: string; + 'aria-describedby'?: string; /** Value between 0 and 1. */ value: number; /** Callback when the value changes. */ @@ -13,7 +15,13 @@ interface ISlider { } /** Draggable slider component. */ -const Slider: React.FC = ({ id, value, onChange }) => { +const Slider: React.FC = ({ + id, + value, + onChange, + 'aria-labelledby': ariaLabelledby, + 'aria-describedby': ariaDescribedby, +}) => { const node = useRef(null); const keyboardAnimationTimeout = useRef(null); const [animateKeyboardInput, setAnimateKeyboardInput] = useState(false); @@ -154,6 +162,8 @@ const Slider: React.FC = ({ id, value, onChange }) => { aria-valuemax={1} aria-valuenow={value} aria-orientation='horizontal' + aria-labelledby={ariaLabelledby} + aria-describedby={ariaDescribedby} onKeyDown={handleKeyDown} /> diff --git a/packages/pl-fe/src/components/ui/step-slider.tsx b/packages/pl-fe/src/components/ui/step-slider.tsx index 9231a4f66..d6e8bdf96 100644 --- a/packages/pl-fe/src/components/ui/step-slider.tsx +++ b/packages/pl-fe/src/components/ui/step-slider.tsx @@ -5,6 +5,8 @@ import { getPointerPosition } from '@/features/video'; interface IStepSlider { id?: string; + 'aria-labelledby'?: string; + 'aria-describedby'?: string; /** Value between 0 and the amount of steps minus one. */ value: number; /** Steps available in the slider. */ @@ -14,7 +16,14 @@ interface IStepSlider { } /** Slider allowing selecting integers in a given range. */ -const StepSlider: React.FC = ({ id, value, steps, onChange }) => { +const StepSlider: React.FC = ({ + id, + value, + steps, + onChange, + 'aria-labelledby': ariaLabelledby, + 'aria-describedby': ariaDescribedby, +}) => { const node = useRef(null); const handleMouseDown: React.MouseEventHandler = (e) => { @@ -121,6 +130,8 @@ const StepSlider: React.FC = ({ id, value, steps, onChange }) => { aria-valuemax={steps - 1} aria-valuenow={value} aria-orientation='horizontal' + aria-labelledby={ariaLabelledby} + aria-describedby={ariaDescribedby} onKeyDown={handleKeyDown} style={{ left: `calc(${(value / (steps - 1)) * 100}% + 0.125rem)` }} />