diff --git a/jam-ui/src/components/client/JKSessionChatButton.js b/jam-ui/src/components/client/JKSessionChatButton.js index 62f4f1507..adbcf01ec 100644 --- a/jam-ui/src/components/client/JKSessionChatButton.js +++ b/jam-ui/src/components/client/JKSessionChatButton.js @@ -42,6 +42,10 @@ const JKSessionChatButton = ({ sessionId }) => { src={chatIcon} alt="Chat" onClick={handleClick} + role="button" + tabIndex={0} + aria-label={`Open chat${unreadCount > 0 ? ` (${unreadCount} unread)` : ''}`} + aria-pressed={isWindowOpen} style={{ cursor: 'pointer', width: '24px', diff --git a/jam-ui/src/components/client/JKSessionChatWindow.js b/jam-ui/src/components/client/JKSessionChatWindow.js index 667ce3178..7520d78fc 100644 --- a/jam-ui/src/components/client/JKSessionChatWindow.js +++ b/jam-ui/src/components/client/JKSessionChatWindow.js @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import WindowPortal from '../common/WindowPortal.js'; import JKChatHeader from './chat/JKChatHeader.js'; @@ -32,6 +32,7 @@ import { useJamServerContext } from '../../context/JamServerContext'; */ const JKSessionChatWindow = () => { const dispatch = useDispatch(); + const textareaRef = useRef(null); // Redux selectors const isWindowOpen = useSelector(selectIsChatWindowOpen); @@ -54,6 +55,29 @@ const JKSessionChatWindow = () => { return 'Session Chat'; }, [activeChannel]); + // Keyboard shortcut: Escape to close window + useEffect(() => { + const handleKeyDown = (e) => { + if (e.key === 'Escape') { + dispatch(closeChatWindow()); + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [dispatch]); + + // Focus management: Auto-focus textarea when window opens + useEffect(() => { + if (isWindowOpen && textareaRef.current) { + // Delay focus to ensure DOM is ready + setTimeout(() => { + if (textareaRef.current) { + textareaRef.current.focus(); + } + }, 100); + } + }, [isWindowOpen]); + // Conditional render: only show if window is open if (!isWindowOpen) return null; @@ -64,7 +88,12 @@ const JKSessionChatWindow = () => { onClose={handleClose} windowId="jamkazam-chat" > -
+
{
- +
); diff --git a/jam-ui/src/components/client/chat/JKChatComposer.js b/jam-ui/src/components/client/chat/JKChatComposer.js index 65d2596a2..e8bd44734 100644 --- a/jam-ui/src/components/client/chat/JKChatComposer.js +++ b/jam-ui/src/components/client/chat/JKChatComposer.js @@ -20,8 +20,11 @@ import { useJamServerContext } from '../../../context/JamServerContext'; * - Gray: 0-230 characters (normal) * - Yellow: 231-255 characters (approaching limit) * - Red: 256+ characters (over limit) + * + * @param {Object} props - Component props + * @param {React.Ref} props.textareaRef - Ref for textarea (for focus management) */ -const JKChatComposer = () => { +const JKChatComposer = ({ textareaRef }) => { const dispatch = useDispatch(); const [inputText, setInputText] = useState(''); @@ -102,11 +105,15 @@ const JKChatComposer = () => {
{/* Textarea */}