mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
🙌 a11y: Searchbar/Conversations List Focus (#7096)
* chore: remove redundancy of useSetRecoilState and useRecoilValue with useRecoilState in SearchBar * refactor: remove unnecessary focus effect on text area in ChatForm * refactor: improve searchbar and clear search button accessibility * fix: add tabIndex to Conversations component for improved accessibility, moves focus directly conversation items * style: adjust margin in Header component for improved layout symmetry with Nav * chore: imports order
This commit is contained in:
parent
550c7cc68a
commit
6826c0ed43
4 changed files with 18 additions and 15 deletions
|
@ -36,7 +36,7 @@ export default function Header() {
|
|||
return (
|
||||
<div className="sticky top-0 z-10 flex h-14 w-full items-center justify-between bg-white p-2 font-semibold text-text-primary dark:bg-gray-800">
|
||||
<div className="hide-scrollbar flex w-full items-center justify-between gap-2 overflow-x-auto">
|
||||
<div className="mx-2 flex items-center gap-2">
|
||||
<div className="mx-1 flex items-center gap-2">
|
||||
{!navVisible && <OpenSidebar setNavVisible={setNavVisible} />}
|
||||
{!navVisible && <HeaderNewChat />}
|
||||
{<ModelSelector startupConfig={startupConfig} />}
|
||||
|
|
|
@ -151,12 +151,6 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
|
|||
|
||||
const textValue = useWatch({ control: methods.control, name: 'text' });
|
||||
|
||||
useEffect(() => {
|
||||
if (!search.isSearching && textAreaRef.current && !disableInputs) {
|
||||
textAreaRef.current.focus();
|
||||
}
|
||||
}, [search.isSearching, disableInputs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (textAreaRef.current) {
|
||||
const style = window.getComputedStyle(textAreaRef.current);
|
||||
|
|
|
@ -220,6 +220,7 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
role="list"
|
||||
aria-label="Conversations"
|
||||
onRowsRendered={handleRowsRendered}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { forwardRef, useState, useCallback, useMemo, useEffect, Ref } from 'react';
|
||||
import React, { forwardRef, useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { useSetRecoilState, useRecoilValue } from 'recoil';
|
||||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
@ -13,7 +13,7 @@ type SearchBarProps = {
|
|||
isSmallScreen?: boolean;
|
||||
};
|
||||
|
||||
const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) => {
|
||||
const SearchBar = forwardRef((props: SearchBarProps, ref: React.Ref<HTMLDivElement>) => {
|
||||
const localize = useLocalize();
|
||||
const location = useLocation();
|
||||
const queryClient = useQueryClient();
|
||||
|
@ -21,11 +21,11 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
|
|||
const { isSmallScreen } = props;
|
||||
|
||||
const [text, setText] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [showClearIcon, setShowClearIcon] = useState(false);
|
||||
|
||||
const { newConversation } = useNewConvo();
|
||||
const setSearchState = useSetRecoilState(store.search);
|
||||
const search = useRecoilValue(store.search);
|
||||
const [search, setSearchState] = useRecoilState(store.search);
|
||||
|
||||
const clearSearch = useCallback(() => {
|
||||
if (location.pathname.includes('/search')) {
|
||||
|
@ -44,6 +44,7 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
|
|||
isTyping: false,
|
||||
}));
|
||||
clearSearch();
|
||||
inputRef.current?.focus();
|
||||
}, [setSearchState, clearSearch]);
|
||||
|
||||
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
|
@ -108,6 +109,7 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
|
|||
<Search className="absolute left-3 h-4 w-4 text-text-secondary group-focus-within:text-text-primary group-hover:text-text-primary" />
|
||||
<input
|
||||
type="text"
|
||||
ref={inputRef}
|
||||
className="m-0 mr-0 w-full border-none bg-transparent p-0 pl-7 text-sm leading-tight placeholder-text-secondary placeholder-opacity-100 focus-visible:outline-none group-focus-within:placeholder-text-primary group-hover:placeholder-text-primary"
|
||||
value={text}
|
||||
onChange={onChange}
|
||||
|
@ -122,14 +124,20 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
|
|||
autoComplete="off"
|
||||
dir="auto"
|
||||
/>
|
||||
<X
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${localize('com_ui_clear')} ${localize('com_ui_search')}`}
|
||||
className={cn(
|
||||
'absolute right-[7px] h-5 w-5 cursor-pointer transition-opacity duration-200',
|
||||
'absolute right-[7px] flex h-5 w-5 items-center justify-center rounded-full border-none bg-transparent p-0 transition-opacity duration-200',
|
||||
showClearIcon ? 'opacity-100' : 'opacity-0',
|
||||
isSmallScreen === true ? 'right-[16px]' : '',
|
||||
)}
|
||||
onClick={clearText}
|
||||
/>
|
||||
tabIndex={showClearIcon ? 0 : -1}
|
||||
disabled={!showClearIcon}
|
||||
>
|
||||
<X className="h-5 w-5 cursor-pointer" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue