nicolium: use aria-labelledby and describedby

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-02-21 15:45:29 +01:00
parent fb3d6d1388
commit 7731cf867b
3 changed files with 46 additions and 5 deletions

View File

@ -36,6 +36,8 @@ const ListItem: React.FC<IListItem> = ({
...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<IListItem> = ({
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<IListItem> = ({
return null;
}),
[children, domId],
[children, domId, labelId, hint, hintId],
);
const classNames = clsx('⁂-list-item', className, {
@ -76,9 +90,15 @@ const ListItem: React.FC<IListItem> = ({
const body = (
<>
<div className='⁂-list-item__label'>
<LabelComp htmlFor={domId}>{label}</LabelComp>
<LabelComp id={labelId} {...(LabelComp === 'label' ? { htmlFor: domId } : {})}>
{label}
</LabelComp>
{hint ? <span className='⁂-list-item__hint'>{hint}</span> : null}
{hint ? (
<span id={hintId} className='⁂-list-item__hint'>
{hint}
</span>
) : null}
</div>
{'to' in rest || href || onClick ? (

View File

@ -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<ISlider> = ({ id, value, onChange }) => {
const Slider: React.FC<ISlider> = ({
id,
value,
onChange,
'aria-labelledby': ariaLabelledby,
'aria-describedby': ariaDescribedby,
}) => {
const node = useRef<HTMLDivElement>(null);
const keyboardAnimationTimeout = useRef<number | null>(null);
const [animateKeyboardInput, setAnimateKeyboardInput] = useState<boolean>(false);
@ -154,6 +162,8 @@ const Slider: React.FC<ISlider> = ({ id, value, onChange }) => {
aria-valuemax={1}
aria-valuenow={value}
aria-orientation='horizontal'
aria-labelledby={ariaLabelledby}
aria-describedby={ariaDescribedby}
onKeyDown={handleKeyDown}
/>
</div>

View File

@ -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<IStepSlider> = ({ id, value, steps, onChange }) => {
const StepSlider: React.FC<IStepSlider> = ({
id,
value,
steps,
onChange,
'aria-labelledby': ariaLabelledby,
'aria-describedby': ariaDescribedby,
}) => {
const node = useRef<HTMLDivElement>(null);
const handleMouseDown: React.MouseEventHandler = (e) => {
@ -121,6 +130,8 @@ const StepSlider: React.FC<IStepSlider> = ({ 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)` }}
/>