🚹 feat: Miscellaneous Accessibility Improvements (#10913)

* 🔱 fix: Fork Menu Accessibility Improvements (#10910)

* feat: more accessible aria-label for fork button

* fix: alignment between text and checkbox

* feat: add text change on focus for parity with on hover for keyboard accessibility

* 🤔 fix: Programmatic Expansion State for Thinking Button (#10912)

* 🙋‍♂️ feat: Accessible Default User Icon Colors (#10909)

* fix: downshift values for all non-compliant default bg-colors for user icons to achieve 4.5:1 contrast threshold minimums with text

* 🚪 feat: Open Sidebar Label Accessibility (#10893)

* feat: more accessible labelling on open / close sidebar
This commit is contained in:
Dustin Healy 2025-12-11 07:42:28 -08:00 committed by GitHub
parent c9005c41a8
commit 7ea9cc06f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 88 additions and 34 deletions

View file

@ -65,6 +65,7 @@ export const ThinkingButton = memo(
<button
type="button"
onClick={onClick}
aria-expanded={isExpanded}
className={cn(
'group/button flex flex-1 items-center justify-start rounded-lg leading-[18px]',
fontSize,

View file

@ -75,6 +75,21 @@ const PopoverButton: React.FC<PopoverButtonProps> = ({
setActiveSetting(optionLabels[ForkOptions.DEFAULT]);
}, 175);
}}
onFocus={() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
setActiveSetting(optionLabels[setting]);
}}
onBlur={() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
setActiveSetting(optionLabels[ForkOptions.DEFAULT]);
}, 175);
}}
className="mx-0.5 w-14 flex-1 rounded-xl border-2 border-border-medium bg-surface-secondary text-text-secondary transition duration-200 ease-in-out hover:bg-surface-hover hover:text-text-primary"
aria-label={label}
>
@ -134,35 +149,36 @@ const CheckboxOption: React.FC<CheckboxOptionProps> = ({
const { showToast } = useToastContext();
return (
<Ariakit.HovercardProvider placement="right-start">
<div className="flex items-center">
<div className="flex h-6 w-full select-none items-center justify-start rounded-md text-sm text-text-secondary hover:text-text-primary">
<Ariakit.HovercardAnchor
render={
<div>
<Ariakit.Checkbox
id={id}
checked={checked}
onChange={(e) => {
const value = e.target.checked;
if (value && showToastOnCheck) {
showToast({
message: localize('com_ui_fork_remember_checked'),
status: 'info',
});
}
onToggle(value);
}}
className="h-4 w-4 rounded-sm border border-primary ring-offset-background transition duration-300 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
aria-label={localize(labelKey)}
/>
<label htmlFor={id} className="ml-2 cursor-pointer">
{localize(labelKey)}
</label>
</div>
}
/>
</div>
<Ariakit.HovercardDisclosure className="rounded-full text-text-secondary focus:outline-none focus:ring-2 focus:ring-ring">
<div className="flex items-center justify-between">
<Ariakit.HovercardAnchor
render={
<div className="flex items-center">
<Ariakit.Checkbox
id={id}
checked={checked}
onChange={(e) => {
const value = e.target.checked;
if (value && showToastOnCheck) {
showToast({
message: localize('com_ui_fork_remember_checked'),
status: 'info',
});
}
onToggle(value);
}}
className="h-4 w-4 rounded-sm border border-primary ring-offset-background transition duration-300 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
aria-label={localize(labelKey)}
/>
<label
htmlFor={id}
className="ml-2 cursor-pointer select-none text-sm text-text-secondary hover:text-text-primary"
>
{localize(labelKey)}
</label>
</div>
}
/>
<Ariakit.HovercardDisclosure className="ml-1 rounded-full text-text-secondary focus:outline-none focus:ring-2 focus:ring-ring">
<VisuallyHidden>{localize(infoKey)}</VisuallyHidden>
{chevronDown}
</Ariakit.HovercardDisclosure>
@ -331,7 +347,7 @@ export default function Fork({
}
}}
type="button"
aria-label={localize('com_ui_fork')}
aria-label={localize('com_ui_fork_open_menu')}
>
<GitFork size="19" />
</button>

View file

@ -29,6 +29,23 @@ export default function NavToggle({
const topBarRotation = side === 'right' ? `-${rotation}` : rotation;
const bottomBarRotation = side === 'right' ? rotation : `-${rotation}`;
let sidebarLabel;
let actionKey;
if (side === 'left') {
sidebarLabel = localize('com_ui_chat_history');
} else {
sidebarLabel = localize('com_nav_control_panel');
}
if (navVisible) {
actionKey = 'com_ui_close_var';
} else {
actionKey = 'com_ui_open_var';
}
const ariaDescription = localize(actionKey, { 0: sidebarLabel });
return (
<div
className={cn(
@ -42,15 +59,13 @@ export default function NavToggle({
>
<TooltipAnchor
side={side === 'right' ? 'left' : 'right'}
aria-label={side === 'left' ? localize('com_ui_chat_history') : localize('com_ui_controls')}
aria-label={ariaDescription}
aria-expanded={navVisible}
aria-controls={side === 'left' ? 'chat-history-nav' : 'controls-nav'}
id={`toggle-${side}-nav`}
onClick={onToggle}
role="button"
description={
navVisible ? localize('com_nav_close_sidebar') : localize('com_nav_open_sidebar')
}
description={ariaDescription}
className="flex items-center justify-center"
tabIndex={0}
>

View file

@ -440,6 +440,7 @@
"com_nav_clear_conversation_confirm_message": "Are you sure you want to clear all conversations? This is irreversible.",
"com_nav_close_sidebar": "Close sidebar",
"com_nav_commands": "Commands",
"com_nav_control_panel": "Control Panel",
"com_nav_confirm_clear": "Confirm Clear",
"com_nav_conversation_mode": "Conversation Mode",
"com_nav_convo_menu_options": "Conversation Menu Options",
@ -788,6 +789,7 @@
"com_ui_client_secret": "Client Secret",
"com_ui_close": "Close",
"com_ui_close_menu": "Close Menu",
"com_ui_close_var": "Close {{0}}",
"com_ui_close_settings": "Close Settings",
"com_ui_close_window": "Close Window",
"com_ui_code": "Code",
@ -979,6 +981,7 @@
"com_ui_fork_info_visible": "This option forks only the visible messages; in other words, the direct path to the target message, without any branches.",
"com_ui_fork_more_details_about": "View additional information and details about the \"{{0}}\" fork option",
"com_ui_fork_more_info_options": "View detailed explanation of all fork options and their behaviors",
"com_ui_fork_open_menu": "Open Fork Menu",
"com_ui_fork_processing": "Forking conversation...",
"com_ui_fork_remember": "Remember",
"com_ui_fork_remember_checked": "Your selection will be remembered after usage. Change this at any time in the settings.",
@ -1268,6 +1271,7 @@
"com_ui_share_var": "Share {{0}}",
"com_ui_shared_link_delete_success": "Successfully deleted shared link",
"com_ui_shared_link_not_found": "Shared link not found",
"com_ui_open_var": "Open {{0}}",
"com_ui_shared_prompts": "Shared Prompts",
"com_ui_shop": "Shopping",
"com_ui_show_all": "Show All",

View file

@ -25,6 +25,24 @@ const useAvatar = (user: TUser | undefined) => {
seed,
fontFamily: ['Verdana'],
fontSize: 36,
backgroundType: ['solid'],
backgroundColor: [
'd81b60',
'8e24aa',
'5e35b1',
'3949ab',
'DB3733',
'1B79CC',
'027CB8',
'008291',
'008577',
'58802F',
'8A761D',
'9C6D00',
'B06200',
'D1451A',
],
textColor: ['ffffff'],
});
let avatarDataUri = '';