a11y button (#335)

fixes issue #333
This commit is contained in:
Jing Hua 2023-07-19 20:48:08 -07:00 committed by GitHub
parent 2b0280a479
commit 5b642f043f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 139 additions and 21 deletions

View file

@ -132,6 +132,7 @@ const ApiEndpointSelector = ({
<button
className='btn btn-neutral btn-small flex justify-between w-full'
type='button'
aria-label='expand api menu'
onClick={() => setDropDown((prev) => !prev)}
>
<span className='truncate'>{_apiEndpoint}</span>

View file

@ -44,7 +44,11 @@ const CloneChat = React.memo(() => {
};
return (
<button className='btn btn-neutral flex gap-1' onClick={cloneChat}>
<button
className='btn btn-neutral flex gap-1'
aria-label={t('cloneChat') as string}
onClick={cloneChat}
>
{cloned ? (
<>
<TickIcon /> {t('cloned')}

View file

@ -24,6 +24,7 @@ const DownloadChat = React.memo(
<>
<button
className='btn btn-neutral'
aria-label={t('downloadChat') as string}
onClick={() => {
setIsModalOpen(true);
}}
@ -39,6 +40,7 @@ const DownloadChat = React.memo(
<div className='p-6 border-b border-gray-200 dark:border-gray-600 flex gap-4'>
<button
className='btn btn-neutral gap-2'
aria-label='image'
onClick={async () => {
if (saveRef && saveRef.current) {
const imgData = await htmlToImg(saveRef.current);
@ -82,6 +84,7 @@ const DownloadChat = React.memo(
</button> */}
<button
className='btn btn-neutral gap-2'
aria-label='markdown'
onClick={async () => {
if (saveRef && saveRef.current) {
const chats = useStore.getState().chats;
@ -106,6 +109,7 @@ const DownloadChat = React.memo(
</button>
<button
className='btn btn-neutral gap-2'
aria-label='json'
onClick={async () => {
const chats = useStore.getState().chats;
if (chats) {

View file

@ -38,6 +38,7 @@ const CodeBar = React.memo(
<span className=''>{lang}</span>
<button
className='flex ml-auto gap-2'
aria-label='copy codeblock'
onClick={async () => {
const codeString = codeRef.current?.textContent;
if (codeString)

View file

@ -20,14 +20,13 @@ const CommandPrompt = ({
const [dropDown, setDropDown, dropDownRef] = useHideOnOutsideClick();
useEffect(() => {
if (dropDown && inputRef.current) {
// When dropdown is visible, focus the input
inputRef.current.focus();
}
}, [dropDown]);
useEffect(() => {
const filteredPrompts = matchSorter(useStore.getState().prompts, input, {
keys: ['name'],
@ -44,6 +43,7 @@ const CommandPrompt = ({
<div className='relative max-wd-sm' ref={dropDownRef}>
<button
className='btn btn-neutral btn-small'
aria-label='prompt library'
onClick={() => setDropDown(!dropDown)}
>
/

View file

@ -46,7 +46,11 @@ const NewMessageButton = React.memo(
};
return (
<div className='h-0 w-0 relative' key={messageIndex}>
<div
className='h-0 w-0 relative'
key={messageIndex}
aria-label='insert message'
>
<div
className='absolute top-0 right-0 translate-x-1/2 translate-y-[-50%] text-gray-600 dark:text-white cursor-pointer bg-gray-200 dark:bg-gray-600/80 rounded-full p-1 text-sm hover:bg-gray-300 dark:hover:bg-gray-800/80 transition-bg duration-200'
onClick={addMessage}

View file

@ -28,6 +28,7 @@ const RoleSelector = React.memo(
<div className='prose dark:prose-invert relative'>
<button
className='btn btn-neutral btn-small flex gap-1'
aria-label={t(role) as string}
type='button'
onClick={() => setDropDown((prev) => !prev)}
>

View file

@ -3,15 +3,18 @@ import React from 'react';
const BaseButton = ({
onClick,
icon,
buttonProps,
}: {
onClick: React.MouseEventHandler<HTMLButtonElement>;
icon: React.ReactElement;
buttonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
}) => {
return (
<div className='text-gray-400 flex self-end lg:self-center justify-center gap-3 md:gap-4 visible'>
<button
className='p-1 rounded-md hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible'
onClick={onClick}
{...buttonProps}
>
{icon}
</button>

View file

@ -15,6 +15,7 @@ const CopyButton = ({
return (
<BaseButton
icon={isCopied ? <TickIcon /> : <CopyIcon />}
buttonProps={{ 'aria-label': 'copy message' }}
onClick={(e) => {
onClick(e);
setIsCopied(true);

View file

@ -11,7 +11,11 @@ const DeleteButton = memo(
setIsDelete: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
return (
<BaseButton icon={<DeleteIcon />} onClick={() => setIsDelete(true)} />
<BaseButton
icon={<DeleteIcon />}
buttonProps={{ 'aria-label': 'delete message' }}
onClick={() => setIsDelete(true)}
/>
);
}
);

View file

@ -9,7 +9,13 @@ const DownButton = ({
}: {
onClick: React.MouseEventHandler<HTMLButtonElement>;
}) => {
return <BaseButton icon={<DownChevronArrow />} onClick={onClick} />;
return (
<BaseButton
icon={<DownChevronArrow />}
buttonProps={{ 'aria-label': 'shift message down' }}
onClick={onClick}
/>
);
};
export default DownButton;

View file

@ -10,7 +10,13 @@ const EditButton = memo(
}: {
setIsEdit: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
return <BaseButton icon={<EditIcon2 />} onClick={() => setIsEdit(true)} />;
return (
<BaseButton
icon={<EditIcon2 />}
buttonProps={{ 'aria-label': 'edit message' }}
onClick={() => setIsEdit(true)}
/>
);
}
);

View file

@ -14,6 +14,7 @@ const MarkdownModeButton = () => {
return (
<BaseButton
icon={markdownMode ? <MarkdownIcon /> : <FileTextIcon />}
buttonProps={{ 'aria-label': 'toggle markdown mode' }}
onClick={() => {
setMarkdownMode(!markdownMode);
}}

View file

@ -9,7 +9,13 @@ const RefreshButton = ({
}: {
onClick: React.MouseEventHandler<HTMLButtonElement>;
}) => {
return <BaseButton icon={<RefreshIcon />} onClick={onClick} />;
return (
<BaseButton
icon={<RefreshIcon />}
buttonProps={{ 'aria-label': 'regenerate message' }}
onClick={onClick}
/>
);
};
export default RefreshButton;

View file

@ -12,6 +12,7 @@ const UpButton = ({
return (
<BaseButton
icon={<DownChevronArrow className='rotate-180' />}
buttonProps={{ 'aria-label': 'shift message up' }}
onClick={onClick}
/>
);

View file

@ -158,11 +158,16 @@ const ContentView = memo(
<>
<button
className='p-1 hover:text-white'
aria-label='cancel'
onClick={() => setIsDelete(false)}
>
<CrossIcon />
</button>
<button className='p-1 hover:text-white' onClick={handleDelete}>
<button
className='p-1 hover:text-white'
aria-label='confirm'
onClick={handleDelete}
>
<TickIcon />
</button>
</>

View file

@ -189,6 +189,7 @@ const EditViewButtons = memo(
generating ? 'cursor-not-allowed opacity-40' : ''
}`}
onClick={handleSaveAndSubmit}
aria-label={t('saveAndSubmit') as string}
>
<div className='flex items-center justify-center gap-2'>
{t('saveAndSubmit')}
@ -205,6 +206,7 @@ const EditViewButtons = memo(
: 'btn-primary'
}`}
onClick={handleSave}
aria-label={t('save') as string}
>
<div className='flex items-center justify-center gap-2'>
{t('save')}
@ -217,6 +219,7 @@ const EditViewButtons = memo(
onClick={() => {
!generating && setIsModalOpen(true);
}}
aria-label={t('saveAndSubmit') as string}
>
<div className='flex items-center justify-center gap-2'>
{t('saveAndSubmit')}
@ -228,6 +231,7 @@ const EditViewButtons = memo(
<button
className='btn relative btn-neutral'
onClick={() => setIsEdit(false)}
aria-label={t('cancel') as string}
>
<div className='flex items-center justify-center gap-2'>
{t('cancel')}

View file

@ -12,6 +12,7 @@ const ScrollToBottomButton = React.memo(() => {
className={`cursor-pointer absolute right-6 bottom-[60px] md:bottom-[60px] z-10 rounded-full border border-gray-200 bg-gray-50 text-gray-600 dark:border-white/10 dark:bg-white/10 dark:text-gray-200 ${
atBottom ? 'hidden' : ''
}`}
aria-label='scroll to bottom'
onClick={scrollToBottom}
>
<DownArrow />

View file

@ -24,7 +24,10 @@ const TextField = () => {
className='m-0 w-full resize-none border-0 bg-transparent p-0 pl-2 pr-7 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-0'
style={{ maxHeight: '200px', height: '24px', overflowY: 'hidden' }}
></textarea>
<button className='absolute p-1 rounded-md text-gray-500 bottom-1.5 right-1 md:bottom-2.5 md:right-2 hover:bg-gray-100 dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent'>
<button
className='absolute p-1 rounded-md text-gray-500 bottom-1.5 right-1 md:bottom-2.5 md:right-2 hover:bg-gray-100 dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent'
aria-label='submit'
>
<SendIcon />
</button>
</div>

View file

@ -20,7 +20,11 @@ const ChatConfigMenu = () => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
return (
<div>
<button className='btn btn-neutral' onClick={() => setIsModalOpen(true)}>
<button
className='btn btn-neutral'
onClick={() => setIsModalOpen(true)}
aria-label={t('defaultChatConfig') as string}
>
{t('defaultChatConfig')}
</button>
{isModalOpen && <ChatConfigPopup setIsModalOpen={setIsModalOpen} />}

View file

@ -86,6 +86,7 @@ export const ModelSelector = ({
className='btn btn-neutral btn-small flex gap-1'
type='button'
onClick={() => setDropDown((prev) => !prev)}
aria-label='model'
>
{_model}
<DownChevronArrow />

View file

@ -52,11 +52,19 @@ const GoogleSyncButton = ({ loginHandler }: { loginHandler?: () => void }) => {
return (
<div className='flex gap-4 flex-wrap justify-center'>
<button className='btn btn-primary' onClick={() => login()}>
<button
className='btn btn-primary'
onClick={() => login()}
aria-label={t('button.sync') as string}
>
{t('button.sync')}
</button>
{cloudSync && (
<button className='btn btn-neutral' onClick={logout}>
<button
className='btn btn-neutral'
onClick={logout}
aria-label={t('button.stop') as string}
>
{t('button.stop')}
</button>
)}

View file

@ -26,6 +26,7 @@ const ExportChat = () => {
};
downloadFile(fileData, getToday());
}}
aria-label={t('export') as string}
>
{t('export')}
</button>

View file

@ -152,6 +152,7 @@ const ImportChat = () => {
<button
className='btn btn-small btn-primary mt-3'
onClick={handleFileUpload}
aria-label={t('import') as string}
>
{t('import')}
</button>

View file

@ -68,6 +68,7 @@ const ImportChatOpenAI = ({
<button
className='btn btn-small btn-primary mt-3'
onClick={handleFileUpload}
aria-label={t('import') as string}
>
{t('import')}
</button>

View file

@ -14,6 +14,7 @@ const LanguageSelector = () => {
className='btn btn-neutral btn-small w-36 flex justify-between'
type='button'
onClick={() => setDropDown((prev) => !prev)}
aria-label='language selector'
>
{languageCodeToName[i18n.language as keyof typeof languageCodeToName] ??
i18n.language}

View file

@ -211,10 +211,18 @@ const ChatFolder = ({
>
{isDelete || isEdit ? (
<>
<button className='p-1 hover:text-white' onClick={handleTick}>
<button
className='p-1 hover:text-white'
onClick={handleTick}
aria-label='confirm'
>
<TickIcon />
</button>
<button className='p-1 hover:text-white' onClick={handleCross}>
<button
className='p-1 hover:text-white'
onClick={handleCross}
aria-label='cancel'
>
<CrossIcon />
</button>
</>
@ -229,6 +237,7 @@ const ChatFolder = ({
onClick={() => {
setShowPalette((prev) => !prev);
}}
aria-label='folder color'
>
<ColorPaletteIcon />
</button>
@ -243,12 +252,14 @@ const ChatFolder = ({
onClick={() => {
updateColor(c);
}}
aria-label={c}
/>
))}
<button
onClick={() => {
updateColor();
}}
aria-label='default color'
>
<RefreshIcon />
</button>
@ -260,16 +271,22 @@ const ChatFolder = ({
<button
className='p-1 hover:text-white md:hidden group-hover/folder:md:inline'
onClick={() => setIsEdit(true)}
aria-label='edit folder title'
>
<EditIcon />
</button>
<button
className='p-1 hover:text-white md:hidden group-hover/folder:md:inline'
onClick={() => setIsDelete(true)}
aria-label='delete folder'
>
<DeleteIcon />
</button>
<button className='p-1 hover:text-white' onClick={toggleExpanded}>
<button
className='p-1 hover:text-white'
onClick={toggleExpanded}
aria-label='expand folder'
>
<DownChevronArrow
className={`${
isExpanded ? 'rotate-180' : ''

View file

@ -131,10 +131,18 @@ const ChatHistory = React.memo(
<div className='absolute flex right-1 z-10 text-gray-300 visible'>
{isDelete || isEdit ? (
<>
<button className='p-1 hover:text-white' onClick={handleTick}>
<button
className='p-1 hover:text-white'
onClick={handleTick}
aria-label='confirm'
>
<TickIcon />
</button>
<button className='p-1 hover:text-white' onClick={handleCross}>
<button
className='p-1 hover:text-white'
onClick={handleCross}
aria-label='cancel'
>
<CrossIcon />
</button>
</>
@ -143,12 +151,14 @@ const ChatHistory = React.memo(
<button
className='p-1 hover:text-white'
onClick={() => setIsEdit(true)}
aria-label='edit chat title'
>
<EditIcon />
</button>
<button
className='p-1 hover:text-white'
onClick={() => setIsDelete(true)}
aria-label='delete chat'
>
<DeleteIcon />
</button>

View file

@ -21,10 +21,12 @@ const ClearConversation = () => {
return (
<>
<button className='btn btn-neutral'
<button
className='btn btn-neutral'
onClick={() => {
setIsModalOpen(true);
}}
aria-label={t('clearConversation') as string}
>
<DeleteIcon />
{t('clearConversation')}

View file

@ -29,6 +29,7 @@ const ThemeSwitcher = () => {
<button
className='items-center gap-3 btn btn-neutral'
onClick={switchTheme}
aria-label='toggle dark/light mode'
>
{theme === 'dark' ? <SunIcon /> : <MoonIcon />}
{t(getOppositeTheme(theme) + 'Mode')}

View file

@ -27,6 +27,7 @@ const MobileBar = () => {
onClick={() => {
setHideSideMenu(false);
}}
aria-label='open sidebar'
>
<span className='sr-only'>Open sidebar</span>
<MenuIcon />
@ -44,6 +45,7 @@ const MobileBar = () => {
onClick={() => {
if (!generating) addChat();
}}
aria-label='new chat'
>
<PlusIcon className='h-6 w-6' />
</button>

View file

@ -49,6 +49,7 @@ const PopupModal = ({
type='button'
className='text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white'
onClick={_handleClose}
aria-label='close modal'
>
<CrossIcon2 />
</button>
@ -70,6 +71,7 @@ const PopupModal = ({
type='button'
className='btn btn-primary'
onClick={handleConfirm}
aria-label='confirm'
>
{t('confirm')}
</button>
@ -79,6 +81,7 @@ const PopupModal = ({
type='button'
className='btn btn-neutral'
onClick={_handleClose}
aria-label='cancel'
>
{t('cancel')}
</button>

View file

@ -17,6 +17,7 @@ const ExportPrompt = () => {
onClick={() => {
exportPrompts(prompts);
}}
aria-label={t('export') as string}
>
{t('export')}
</button>

View file

@ -63,6 +63,7 @@ const ImportPrompt = () => {
<button
className='btn btn-small btn-primary mt-3'
onClick={handleFileUpload}
aria-label={t('import') as string}
>
{t('import')}
</button>

View file

@ -15,7 +15,11 @@ const PromptLibraryMenu = () => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
return (
<div>
<button className='btn btn-neutral' onClick={() => setIsModalOpen(true)}>
<button
className='btn btn-neutral'
onClick={() => setIsModalOpen(true)}
aria-label={t('promptLibrary') as string}
>
{t('promptLibrary')}
</button>
{isModalOpen && (

View file

@ -45,6 +45,7 @@ const ShareGPT = React.memo(() => {
onClick={() => {
setIsModalOpen(true);
}}
aria-label={t('postOnShareGPT.title') as string}
>
{t('postOnShareGPT.title')}
</button>

View file

@ -10,7 +10,10 @@ const StopGeneratingButton = () => {
className='absolute bottom-6 left-0 right-0 m-auto flex md:w-full md:m-auto gap-0 md:gap-2 justify-center'
onClick={() => setGenerating(false)}
>
<button className='btn relative btn-neutral border-0 md:border'>
<button
className='btn relative btn-neutral border-0 md:border'
aria-label='stop generating'
>
<div className='flex w-full items-center justify-center gap-2'>
<svg
stroke='currentColor'