diff --git a/docs/ACCESSIBILITY.md b/docs/ACCESSIBILITY.md index f9bd7816..4bde6783 100644 --- a/docs/ACCESSIBILITY.md +++ b/docs/ACCESSIBILITY.md @@ -38,6 +38,7 @@ Use **assertive** only for urgent errors or time-sensitive status. - Give the primary `
` a stable id such as `main-content` so skip links and **Alt+M** work everywhere. There should be **exactly one** `
` (or `role="main"`) per view. - For horizontal toolbars, add `data-roving-root` on the toolbar container. **Left/Right arrow** moves among buttons, links, tabs, and elements marked with `data-roving-item` (including those using `tabindex="-1"` for roving patterns). +- Rich post editors should expose a named multiline textbox, a named formatting toolbar with pressed states, and helper text connected through `aria-describedby`. Post composer message lists should use `role="log"` with polite updates so new discussion activity is announced without interrupting the current task. ## What automation does _not_ prove diff --git a/src/app/components/messaging/MessageComposer.tsx b/src/app/components/messaging/MessageComposer.tsx index 2a64e86f..8baef1e5 100644 --- a/src/app/components/messaging/MessageComposer.tsx +++ b/src/app/components/messaging/MessageComposer.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useRef, useCallback } from 'react'; +import { useState, useRef, useCallback, useId } from 'react'; import { FiSend, FiPaperclip, FiX, FiFile, FiImage, FiFileText } from 'react-icons/fi'; import RichTextEditor from '@/app/components/ui/RichTextEditor'; @@ -39,6 +39,7 @@ export default function MessageComposer({ }: MessageComposerProps) { const [content, setContent] = useState(''); const fileInputRef = useRef(null); + const editorHelpId = useId(); const handleContentChange = useCallback( (newContent: string) => { @@ -161,6 +162,8 @@ export default function MessageComposer({ content={content} onChange={handleContentChange} placeholder="Type your message..." + ariaLabel="Message content" + describedBy={editorHelpId} /> @@ -182,7 +185,7 @@ export default function MessageComposer({ {/* Helper Text */} -

+

Press{' '} Enter diff --git a/src/app/components/social/GroupDiscussionThread.tsx b/src/app/components/social/GroupDiscussionThread.tsx index de92ff7d..56e4fe49 100644 --- a/src/app/components/social/GroupDiscussionThread.tsx +++ b/src/app/components/social/GroupDiscussionThread.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useMemo, useState, useEffect, useRef } from 'react'; +import React, { useMemo, useState, useEffect, useRef, useId } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Paperclip, Send } from 'lucide-react'; import RichTextEditor from '@/app/components/ui/RichTextEditor'; @@ -26,6 +26,7 @@ export default function GroupDiscussionThread({ messages, onPost }: GroupDiscuss const [files, setFiles] = useState([]); const messagesEndRef = useRef(null); const fileInputRef = useRef(null); + const editorHelpId = useId(); const attachments = useMemo( () => @@ -56,37 +57,60 @@ export default function GroupDiscussionThread({ messages, onPost }: GroupDiscuss } }; + const handleEditorKeyDown = (event: React.KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') { + event.preventDefault(); + handlePost(); + } + }; + return (

-
+
{messages.length === 0 ? ( -
+

No messages yet. Start the conversation!

) : ( {messages.map((m, index) => ( - {/* Avatar */} -
+ {/* Message Content */}
- + {m.senderName} - + +
)}
- + ))} )} @@ -117,8 +141,23 @@ export default function GroupDiscussionThread({ messages, onPost }: GroupDiscuss
{/* Message Input */} -
- +
{ + event.preventDefault(); + handlePost(); + }} + aria-label="Create discussion post" + > +
+ +
@@ -164,8 +206,10 @@ export default function GroupDiscussionThread({ messages, onPost }: GroupDiscuss ))}
)} -

Press Cmd/Ctrl + Enter to post

-
+

+ Press Cmd/Ctrl + Enter to post +

+
); } diff --git a/src/app/components/social/__tests__/GroupDiscussionThread.test.tsx b/src/app/components/social/__tests__/GroupDiscussionThread.test.tsx index e9a92ecb..fae40846 100644 --- a/src/app/components/social/__tests__/GroupDiscussionThread.test.tsx +++ b/src/app/components/social/__tests__/GroupDiscussionThread.test.tsx @@ -3,8 +3,24 @@ import { describe, it, expect, vi } from 'vitest'; // Mock the RichTextEditor to avoid TipTap dependency in tests vi.mock('@/app/components/ui/RichTextEditor', () => ({ - default: ({ content, onChange }: { content: string; onChange: (v: string) => void }) => ( -