mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
🔘 a11y: Switch Contrast and File Input Key Events to WCAG (#4536)
* 🔘 a11y: Improve Contrast of Switch/Toggles to WCAG Standard
* refactor: Improve file attachment accessibility in Chat Input component
* refactor: clear input ref value before clicks
This commit is contained in:
parent
655f63714b
commit
2996058fa2
5 changed files with 46 additions and 39 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { FileUpload, TooltipAnchor } from '~/components/ui';
|
import { FileUpload, TooltipAnchor } from '~/components/ui';
|
||||||
import { AttachmentIcon } from '~/components/svg';
|
import { AttachmentIcon } from '~/components/svg';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
@ -14,19 +14,37 @@ const AttachFile = ({
|
||||||
handleFileChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
handleFileChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const isUploadDisabled = disabled ?? false;
|
const isUploadDisabled = disabled ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileUpload handleFileChange={handleFileChange} className="flex">
|
<FileUpload ref={inputRef} handleFileChange={handleFileChange}>
|
||||||
<TooltipAnchor
|
<TooltipAnchor
|
||||||
id="audio-recorder"
|
role="button"
|
||||||
|
id="attach-file"
|
||||||
aria-label={localize('com_sidepanel_attach_files')}
|
aria-label={localize('com_sidepanel_attach_files')}
|
||||||
disabled={isUploadDisabled}
|
disabled={isUploadDisabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute flex size-[35px] items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover',
|
'absolute flex size-[35px] items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50',
|
||||||
isRTL ? 'bottom-2 right-2' : 'bottom-2 left-1 md:left-2',
|
isRTL ? 'bottom-2 right-2' : 'bottom-2 left-1 md:left-2',
|
||||||
)}
|
)}
|
||||||
description={localize('com_sidepanel_attach_files')}
|
description={localize('com_sidepanel_attach_files')}
|
||||||
|
onKeyDownCapture={(e) => {
|
||||||
|
if (!inputRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
inputRef.current.value = '';
|
||||||
|
inputRef.current.click();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (!inputRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inputRef.current.value = '';
|
||||||
|
inputRef.current.click();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-center gap-2">
|
<div className="flex w-full items-center justify-center gap-2">
|
||||||
<AttachmentIcon />
|
<AttachmentIcon />
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,29 @@
|
||||||
import React, { useRef } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
|
|
||||||
type FileUploadProps = {
|
type FileUploadProps = {
|
||||||
handleFileChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
||||||
onClick?: () => void;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
onClick?: () => void;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
handleFileChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FileUpload: React.FC<FileUploadProps> = ({
|
const FileUpload = forwardRef<HTMLInputElement, FileUploadProps>(
|
||||||
handleFileChange,
|
({ children, handleFileChange }, ref) => {
|
||||||
children,
|
|
||||||
onClick,
|
|
||||||
className = '',
|
|
||||||
}) => {
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
|
||||||
if (onClick) {
|
|
||||||
onClick();
|
|
||||||
}
|
|
||||||
// necessary to reset the input
|
|
||||||
if (fileInputRef.current) {
|
|
||||||
fileInputRef.current.value = '';
|
|
||||||
}
|
|
||||||
fileInputRef.current?.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={handleButtonClick} style={{ cursor: 'pointer' }} className={className}>
|
<>
|
||||||
{children}
|
{children}
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={ref}
|
||||||
multiple
|
multiple
|
||||||
type="file"
|
type="file"
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
FileUpload.displayName = 'FileUpload';
|
||||||
|
|
||||||
export default FileUpload;
|
export default FileUpload;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const Switch = React.forwardRef<
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SwitchPrimitives.Root
|
<SwitchPrimitives.Root
|
||||||
className={cn(
|
className={cn(
|
||||||
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-switch-unchecked',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ html {
|
||||||
--chart-3: 197 37% 24%;
|
--chart-3: 197 37% 24%;
|
||||||
--chart-4: 43 74% 66%;
|
--chart-4: 43 74% 66%;
|
||||||
--chart-5: 27 87% 67%;
|
--chart-5: 27 87% 67%;
|
||||||
|
--switch-unchecked: 0 0% 58%;
|
||||||
}
|
}
|
||||||
.dark {
|
.dark {
|
||||||
--text-primary: var(--gray-100);
|
--text-primary: var(--gray-100);
|
||||||
|
|
@ -137,6 +138,7 @@ html {
|
||||||
--chart-3: 30 80% 55%;
|
--chart-3: 30 80% 55%;
|
||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 65% 60%;
|
||||||
--chart-5: 340 75% 55%;
|
--chart-5: 340 75% 55%;
|
||||||
|
--switch-unchecked: 0 0% 40%;
|
||||||
}
|
}
|
||||||
.gizmo {
|
.gizmo {
|
||||||
--text-primary: var(--gizmo-gray-950);
|
--text-primary: var(--gizmo-gray-950);
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ module.exports = {
|
||||||
/* These are test styles */
|
/* These are test styles */
|
||||||
border: 'hsl(var(--border))',
|
border: 'hsl(var(--border))',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
|
['switch-unchecked']: 'hsl(var(--switch-unchecked))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
background: 'hsl(var(--background))',
|
background: 'hsl(var(--background))',
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue