-
Notifications
You must be signed in to change notification settings - Fork 307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: ThreadList a11y improvements #1221
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@assistant-ui/react": patch | ||
--- | ||
|
||
fix: ThreadList a11y improvements | ||
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -31,7 +31,7 @@ export const ThreadListPrimitiveNew = forwardRef< | |||||
return ( | ||||||
<Primitive.button | ||||||
type="button" | ||||||
{...(isMain ? { "data-active": "true" } : null)} | ||||||
{...(isMain ? { "data-active": "true", "aria-current": "true" } : null)} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Consider using a more specific aria-current value. While - {...(isMain ? { "data-active": "true", "aria-current": "true" } : null)}
+ {...(isMain ? { "aria-current": "page" } : null)} 📝 Committable suggestion
Suggested change
|
||||||
{...props} | ||||||
ref={forwardedRef} | ||||||
disabled={disabled || !callback} | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -19,7 +19,7 @@ export const ThreadListItemPrimitiveRoot = forwardRef< | |||||
|
||||||
return ( | ||||||
<Primitive.div | ||||||
{...(isMain ? { "data-active": "true" } : null)} | ||||||
{...(isMain ? { "data-active": "true", "aria-current": "true" } : null)} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Adjust aria-current value for better accessibility While adding For a thread list item, "page" would be more appropriate as it indicates the current page or screen in a set. - {...(isMain ? { "data-active": "true", "aria-current": "true" } : null)}
+ {...(isMain ? { "data-active": "true", "aria-current": "page" } : null)} 📝 Committable suggestion
Suggested change
|
||||||
{...props} | ||||||
ref={ref} | ||||||
/> | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,8 @@ export { default as BranchPicker } from "./branch-picker"; | |
|
||
export { default as Composer } from "./composer"; | ||
|
||
export { default as ContentPart } from "./content-part"; | ||
|
||
Comment on lines
+23
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Accessibility implementation is present but could be enhanced The components show some accessibility considerations:
However, some improvements could be made:
🔗 Analysis chainVerify accessibility implementation in new components. Since this PR focuses on a11y improvements, please ensure that the newly added components (
Let's check the accessibility implementation: Also applies to: 41-41 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check for accessibility-related code in the new components
# Expected: Find ARIA attributes, role declarations, and keyboard event handlers
# Search for accessibility-related patterns in ContentPart
echo "Checking ContentPart for accessibility implementations:"
rg -i "aria-|role=|onKey|tabIndex" packages/react/src/ui/content-part
# Search for accessibility-related patterns in UserActionBar
echo "Checking UserActionBar for accessibility implementations:"
rg -i "aria-|role=|onKey|tabIndex" packages/react/src/ui/user-action-bar
Length of output: 545 Script: #!/bin/bash
# Let's first find the correct paths for these components
echo "Finding ContentPart component:"
fd -e tsx -e ts -e js -e jsx "content-part" packages/
echo "Finding UserActionBar component:"
fd -e tsx -e ts -e jsx -e js "user-action-bar" packages/
# After finding the paths, let's check for accessibility patterns
echo "Checking for accessibility implementations:"
fd -e tsx -e ts -e jsx -e js "(content-part|user-action-bar)" packages/ --exec rg -i "aria-|role=|onKey|tabIndex|focus|keyboard|screen.?reader|accessibility" {}
Length of output: 680 Script: #!/bin/bash
# Let's examine the content of these components directly
echo "Content of ContentPart component:"
cat packages/react/src/ui/content-part.tsx
echo -e "\nContent of UserActionBar component:"
cat packages/react/src/ui/user-action-bar.tsx
Length of output: 2747 Script: #!/bin/bash
# Let's check the primitives these components are based on
echo "Checking ContentPartPrimitive implementation:"
cat packages/react/src/primitives/content-part.tsx
echo -e "\nChecking ActionBarPrimitive implementation:"
cat packages/react/src/primitives/action-bar.tsx
echo -e "\nChecking TooltipIconButton implementation:"
cat packages/react/src/ui/base/tooltip-icon-button.tsx
Length of output: 1476 |
||
export { | ||
default as AttachmentUI, // TODO name collision with Attachment | ||
} from "./attachment-ui"; | ||
|
@@ -28,14 +30,12 @@ export { default as EditComposer } from "./edit-composer"; | |
|
||
export { default as Thread } from "./thread"; | ||
|
||
export { default as UserMessage } from "./user-message"; | ||
export { default as ThreadList } from "./thread-list"; | ||
|
||
export { default as UserActionBar } from "./user-action-bar"; | ||
export { default as ThreadListItem } from "./thread-list-item"; | ||
|
||
export { default as ThreadWelcome } from "./thread-welcome"; | ||
|
||
export { default as ContentPart } from "./content-part"; | ||
|
||
export { default as ThreadList } from "./thread-list"; | ||
export { default as UserMessage } from "./user-message"; | ||
|
||
export { default as ThreadListItem } from "./thread-list-item"; | ||
export { default as UserActionBar } from "./user-action-bar"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,11 +39,9 @@ const ThreadListItemTitle = forwardRef< | |
ThreadListItemPrimitiveTitle.Element, | ||
ThreadListItemPrimitiveTitle.Props | ||
>(({ className, ...props }, ref) => { | ||
const { | ||
strings: { | ||
threadList: { item: { title: { fallback = "New Chat" } = {} } = {} } = {}, | ||
} = {}, | ||
} = useThreadConfig(); | ||
const config = useThreadConfig(); | ||
const fallback = | ||
config.strings?.threadList?.item?.title?.fallback ?? "New Chat"; | ||
Comment on lines
+42
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Consider adding ARIA attributes for better accessibility. While the config and fallback handling is good, consider enhancing accessibility by:
Example enhancement: return (
<p
ref={ref}
className={classNames("aui-thread-list-item-title", className)}
+ aria-label={props['aria-label'] ?? fallback}
{...props}
>
<ThreadListItemPrimitive.Title fallback={fallback} />
</p>
);
|
||
|
||
return ( | ||
<p | ||
|
@@ -58,34 +56,28 @@ const ThreadListItemTitle = forwardRef< | |
|
||
ThreadListItemTitle.displayName = "ThreadListItemTitle"; | ||
|
||
const ThreadListItemArchive = withDefaults( | ||
forwardRef<HTMLButtonElement, TooltipIconButton.Props>( | ||
({ className, ...props }, ref) => { | ||
const { | ||
strings: { | ||
threadList: { | ||
item: { archive: { label = "Archive thread" } = {} } = {}, | ||
} = {}, | ||
} = {}, | ||
} = useThreadConfig(); | ||
const ThreadListItemArchive = forwardRef< | ||
HTMLButtonElement, | ||
Partial<TooltipIconButton.Props> | ||
>(({ className, ...props }, ref) => { | ||
const config = useThreadConfig(); | ||
const tooltip = | ||
config.strings?.threadList?.item?.archive?.tooltip ?? "Archive thread"; | ||
|
||
return ( | ||
<ThreadListItemPrimitive.Archive asChild> | ||
<TooltipIconButton | ||
{...props} | ||
ref={ref} | ||
className={classNames("aui-thread-list-item-archive", className)} | ||
variant="ghost" | ||
tooltip={label} | ||
> | ||
<ArchiveIcon /> | ||
</TooltipIconButton> | ||
</ThreadListItemPrimitive.Archive> | ||
); | ||
}, | ||
), | ||
{}, | ||
); | ||
return ( | ||
<ThreadListItemPrimitive.Archive asChild> | ||
<TooltipIconButton | ||
ref={ref} | ||
className={classNames("aui-thread-list-item-archive", className)} | ||
variant="ghost" | ||
tooltip={tooltip} | ||
{...props} | ||
> | ||
<ArchiveIcon /> | ||
</TooltipIconButton> | ||
</ThreadListItemPrimitive.Archive> | ||
); | ||
}); | ||
|
||
ThreadListItemArchive.displayName = "ThreadListItemArchive"; | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -27,17 +27,16 @@ const ThreadListNew = forwardRef< | |||||||||||||||||||||||||||||||||||||||||||||
HTMLButtonElement, | ||||||||||||||||||||||||||||||||||||||||||||||
ThreadListPrimitive.New.Props & ButtonProps | ||||||||||||||||||||||||||||||||||||||||||||||
>((props, ref) => { | ||||||||||||||||||||||||||||||||||||||||||||||
const { | ||||||||||||||||||||||||||||||||||||||||||||||
strings: { threadList: { new: { label = "New Thread" } = {} } = {} } = {}, | ||||||||||||||||||||||||||||||||||||||||||||||
} = useThreadConfig(); | ||||||||||||||||||||||||||||||||||||||||||||||
const config = useThreadConfig(); | ||||||||||||||||||||||||||||||||||||||||||||||
const label = config.strings?.threadList?.new?.label ?? "New Thread"; | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Consider adding aria-label for better accessibility While the label extraction logic is good, consider adding an explicit <Button
ref={ref}
className="aui-thread-list-new"
variant="ghost"
+ aria-label={label}
{...props}
>
|
||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||
<ThreadListPrimitive.New asChild> | ||||||||||||||||||||||||||||||||||||||||||||||
<Button | ||||||||||||||||||||||||||||||||||||||||||||||
{...props} | ||||||||||||||||||||||||||||||||||||||||||||||
ref={ref} | ||||||||||||||||||||||||||||||||||||||||||||||
className="aui-thread-list-new" | ||||||||||||||||||||||||||||||||||||||||||||||
variant="ghost" | ||||||||||||||||||||||||||||||||||||||||||||||
{...props} | ||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||
<PlusIcon /> | ||||||||||||||||||||||||||||||||||||||||||||||
{label} | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -47,9 +46,16 @@ const ThreadListNew = forwardRef< | |||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||
ThreadListNew.displayName = "ThreadListNew"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
const ThreadListItems: FC<{ | ||||||||||||||||||||||||||||||||||||||||||||||
components?: Partial<ThreadListPrimitive.Items.Props["components"]>; | ||||||||||||||||||||||||||||||||||||||||||||||
}> = ({ components }) => { | ||||||||||||||||||||||||||||||||||||||||||||||
namespace ThreadListItems { | ||||||||||||||||||||||||||||||||||||||||||||||
export type Props = { | ||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||
* Optional custom components to override default thread list items | ||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||
components?: Partial<ThreadListPrimitive.Items.Props["components"]>; | ||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+49
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Enhance JSDoc documentation with accessibility requirements The components prop documentation could be more detailed about accessibility requirements that custom components should meet. /**
* Optional custom components to override default thread list items
+ * @remarks
+ * Custom components should maintain accessibility features:
+ * - Proper heading structure
+ * - Keyboard navigation support
+ * - ARIA attributes (aria-current, aria-label)
+ * - Focus management
*/ 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
const ThreadListItems: FC<ThreadListItems.Props> = ({ components }) => { | ||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||
<ThreadListPrimitive.Items | ||||||||||||||||||||||||||||||||||||||||||||||
components={{ | ||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider adding more details to the commit message.
While the message follows the conventional commit format, it would be helpful to specify which accessibility improvements were made (e.g., "fix: Add aria-current and focus styles to ThreadList").
📝 Committable suggestion
🧰 Tools
🪛 Markdownlint (0.35.0)
5-5: null
First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)