From 7d7444c2a5d3c28aff6086a19683cdabda6e3ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sat, 21 Feb 2026 16:40:18 +0100 Subject: [PATCH] nicolium: icon picker accessibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../components/icon-picker-menu.tsx | 80 +++++++++++++++---- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/packages/pl-fe/src/features/pl-fe-config/components/icon-picker-menu.tsx b/packages/pl-fe/src/features/pl-fe-config/components/icon-picker-menu.tsx index b8ac1f185..479d6e098 100644 --- a/packages/pl-fe/src/features/pl-fe-config/components/icon-picker-menu.tsx +++ b/packages/pl-fe/src/features/pl-fe-config/components/icon-picker-menu.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import React from 'react'; +import React, { useEffect } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import Text from '@/components/ui/text'; @@ -14,25 +14,72 @@ interface IIconPickerMenu { style?: React.CSSProperties; } -const IconPickerMenu: React.FC = ({ icons, onPick, style }) => { +const IconPickerMenu: React.FC = ({ icons, onPick }) => { const intl = useIntl(); + const containerNode = React.useRef(null); - const setRef = (c: HTMLDivElement) => { - if (!c) return; - - // Nice and dirty hack to display the icons - c.querySelectorAll('button.emoji-mart-emoji > img').forEach((elem) => { - const newIcon = document.createElement('span'); - newIcon.innerHTML = ``; - (elem.parentNode as any).replaceChild(newIcon, elem); - }); - }; + useEffect(() => { + const firstButton = containerNode.current?.querySelector('button') as HTMLButtonElement; + firstButton?.focus(); + }, [containerNode.current]); const handleClick = (icon: string) => { onPick(icon); }; - const renderIcon = (icon: string) => { + const handleKeyDown = (e: React.KeyboardEvent) => { + const target = e.target as HTMLButtonElement; + const icon = target.dataset.index; + + const focus = (index: number) => { + if (index < 0) { + index = Object.values(icons).flat().length - 1; + } else if (index >= Object.values(icons).flat().length) { + index = 0; + } + const button = target.parentElement?.parentElement?.querySelector( + `button[data-index="${index}"]`, + ) as HTMLButtonElement; + button?.focus(); + + e.preventDefault(); + e.stopPropagation(); + }; + switch (e.key) { + case 'Enter': + if (icon) { + handleClick(icon); + e.preventDefault(); + e.stopPropagation(); + } + break; + case 'ArrowLeft': + focus(Number(target.dataset.index) - 1); + break; + case 'ArrowRight': + focus(Number(target.dataset.index) + 1); + break; + case 'ArrowUp': + focus(Number(target.dataset.index) - 8); + break; + case 'ArrowDown': + focus(Number(target.dataset.index) + 8); + break; + case 'Tab': + focus(Number(target.dataset.index) + (e.shiftKey ? -1 : 1)); + break; + case 'Home': + focus(0); + break; + case 'End': + focus(Object.values(icons).flat().length - 1); + break; + default: + break; + } + }; + + const renderIcon = (icon: string, index: number) => { const name = icon.replace('fa fa-', ''); return ( @@ -44,6 +91,7 @@ const IconPickerMenu: React.FC = ({ icons, onPick, style }) => onClick={() => { handleClick(name); }} + data-index={index} > @@ -57,15 +105,15 @@ const IconPickerMenu: React.FC = ({ icons, onPick, style }) =>
-
    +
      {Object.values(icons) .flat() - .map((icon) => renderIcon(icon))} + .map((icon, index) => renderIcon(icon, index))}
);