Compare commits

...

5 Commits

Author SHA1 Message Date
Shaheer Kochai
fd4916cfb5 Merge branch 'develop' into fix/logs-new-column-keyboard-nav 2024-12-18 12:44:57 +04:30
ahmadshaheer
f76c50500c fix(LogsFormatOptionsMenu): handle the incorrect navigation due to previously selected option 2024-12-05 13:57:25 +04:30
ahmadshaheer
94b53f1512 fix(LogsFormatOptionsMenu): prevent action on Enter key when no option is selected
- Add a condition to ignore Enter key presses if no option is currently selected
2024-12-05 13:34:47 +04:30
ahmadshaheer
b94c58f0b7 fix(LogsFormatOptionsMenu): enhance accessibility and focus handling
- Add focus-visible styles to the new column container
- Update the new column container to include role and tabIndex for better keyboard navigation
2024-12-05 13:09:20 +04:30
ahmadshaheer
7158c65282 refactor(LogsFormatOptionsMenu): improve keyboard navigation and event handling
- Replace window.addEventListener with React's onKeyDown handler
- Add arrow key navigation to select first/last dropdown options if there is no selected option
- Fix edge cases in option selection logic (don't allow selection options if options array is empty)
2024-12-05 12:21:09 +04:30
2 changed files with 87 additions and 65 deletions

View File

@@ -646,3 +646,8 @@
} }
} }
} }
.add-new-column-container {
&:focus-visible {
outline: none;
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/click-events-have-key-events */
import './LogsFormatOptionsMenu.styles.scss'; import './LogsFormatOptionsMenu.styles.scss';
@@ -100,82 +99,101 @@ export default function LogsFormatOptionsMenu({
if (maxLinesPerRow && config && config.maxLines?.onChange) { if (maxLinesPerRow && config && config.maxLines?.onChange) {
config.maxLines.onChange(maxLinesPerRow); config.maxLines.onChange(maxLinesPerRow);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [maxLinesPerRow]); }, [maxLinesPerRow]);
useEffect(() => { useEffect(() => {
if (fontSizeValue && config && config.fontSize?.onChange) { if (fontSizeValue && config && config.fontSize?.onChange) {
config.fontSize.onChange(fontSizeValue); config.fontSize.onChange(fontSizeValue);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fontSizeValue]); }, [fontSizeValue]);
function handleColumnSelection( const handleColumnSelection = useCallback(
currentIndex: number, (currentIndex: number, optionsData: DefaultOptionType[]): void => {
optionsData: DefaultOptionType[], const currentItem = optionsData[currentIndex];
): void { const itemLength = optionsData.length;
const currentItem = optionsData[currentIndex]; if (addColumn && addColumn?.onSelect) {
const itemLength = optionsData.length; addColumn?.onSelect(selectedValue, {
if (addColumn && addColumn?.onSelect) { label: currentItem.label,
addColumn?.onSelect(selectedValue, { disabled: false,
label: currentItem.label, });
disabled: false,
});
// if the last element is selected then select the previous one // if the last element is selected then select the previous one
if (currentIndex === itemLength - 1) { if (currentIndex === itemLength - 1) {
// there should be more than 1 element in the list // there should be more than 1 element in the list
if (currentIndex - 1 >= 0) { if (currentIndex - 1 >= 0) {
const prevValue = optionsData[currentIndex - 1]?.value || null; const prevValue = optionsData[currentIndex - 1]?.value || null;
setSelectedValue(prevValue as string | null); setSelectedValue(prevValue as string | null);
} else {
// if there is only one element then just select and do nothing
setSelectedValue(null);
}
} else { } else {
// if there is only one element then just select and do nothing // selecting any random element from the list except the last one
setSelectedValue(null); const nextIndex = currentIndex + 1;
const nextValue = optionsData[nextIndex]?.value || null;
setSelectedValue(nextValue as string | null);
} }
} else {
// selecting any random element from the list except the last one
const nextIndex = currentIndex + 1;
const nextValue = optionsData[nextIndex]?.value || null;
setSelectedValue(nextValue as string | null);
} }
} },
} [addColumn, selectedValue],
);
const handleKeyDown = (e: KeyboardEvent): void => { const handleKeyDown = useCallback(
if (!selectedValue) return; (e: React.KeyboardEvent): void => {
if (!addColumn?.options) return;
const optionsData = addColumn.options;
const optionsData = addColumn?.options || []; const currentIndex = optionsData.findIndex(
(item) => item?.value === selectedValue,
);
const currentIndex = optionsData.findIndex( const itemLength = optionsData.length;
(item) => item?.value === selectedValue,
);
const itemLength = optionsData.length; if (currentIndex === -1) {
// if there is not option selected, and the user presses enter, do nothing
if (e.key === 'Enter') return;
switch (e.key) { if (e.key === 'ArrowDown' && optionsData.length > 0) {
case 'ArrowUp': { setSelectedValue(optionsData[0].value as string);
const newValue = optionsData[Math.max(0, currentIndex - 1)]?.value; e.preventDefault();
return;
}
setSelectedValue(newValue as string | null); if (e.key === 'ArrowUp' && optionsData.length > 0) {
e.preventDefault(); setSelectedValue(optionsData[optionsData.length - 1].value as string);
break; e.preventDefault();
return;
}
} }
case 'ArrowDown': {
const newValue =
optionsData[Math.min(itemLength - 1, currentIndex + 1)]?.value;
setSelectedValue(newValue as string | null); switch (e.key) {
e.preventDefault(); case 'ArrowUp': {
break; const newValue = optionsData[Math.max(0, currentIndex - 1)]?.value;
setSelectedValue(newValue as string | null);
e.preventDefault();
break;
}
case 'ArrowDown': {
const newValue =
optionsData[Math.min(itemLength - 1, currentIndex + 1)]?.value;
setSelectedValue(newValue as string | null);
e.preventDefault();
break;
}
case 'Enter':
e.preventDefault();
handleColumnSelection(currentIndex, optionsData);
break;
default:
break;
} }
case 'Enter': },
e.preventDefault(); [addColumn?.options, selectedValue, handleColumnSelection],
handleColumnSelection(currentIndex, optionsData); );
break;
default:
break;
}
};
useEffect(() => { useEffect(() => {
// Scroll the selected item into view // Scroll the selected item into view
@@ -193,14 +211,7 @@ export default function LogsFormatOptionsMenu({
}); });
} }
} }
}, [selectedValue]); }, [addColumn?.options, selectedValue]);
useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return (): void => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [selectedValue]);
return ( return (
<div <div
@@ -266,7 +277,13 @@ export default function LogsFormatOptionsMenu({
) : null} ) : null}
{showAddNewColumnContainer && ( {showAddNewColumnContainer && (
<div className="add-new-column-container"> // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<div
className="add-new-column-container"
onKeyDown={handleKeyDown}
role="dialog"
tabIndex={-1}
>
<div className="add-new-column-header"> <div className="add-new-column-header">
<div className="title"> <div className="title">
<div className="periscope-btn ghost" onClick={handleToggleAddNewColumn}> <div className="periscope-btn ghost" onClick={handleToggleAddNewColumn}>