pl-fe: seemingly accessibility improvements
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -247,8 +247,6 @@ const AutosuggestInput: React.FC<IAutosuggestInput> = ({
|
||||
|
||||
return [
|
||||
<div key='input' className='relative w-full'>
|
||||
<label className='sr-only'>{props.placeholder}</label>
|
||||
|
||||
<Input
|
||||
type='text'
|
||||
className={props.className}
|
||||
@ -269,6 +267,7 @@ const AutosuggestInput: React.FC<IAutosuggestInput> = ({
|
||||
data-testid='autosuggest-input'
|
||||
theme={props.theme}
|
||||
lang={props.lang}
|
||||
aria-label={props.placeholder}
|
||||
/>
|
||||
</div>,
|
||||
<Portal key='portal'>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { decode } from 'blurhash';
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
interface IBlurhash {
|
||||
interface IBlurhash extends Pick<React.CanvasHTMLAttributes<HTMLCanvasElement>, 'className' | 'aria-label' | 'aria-hidden'> {
|
||||
/** Hash to render */
|
||||
hash: string | null | undefined;
|
||||
/** Width of the blurred region in pixels. Defaults to 32. */
|
||||
@ -13,8 +13,6 @@ interface IBlurhash {
|
||||
* and canvas left untouched.
|
||||
*/
|
||||
dummy?: boolean;
|
||||
/** className of the canvas element. */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -283,6 +283,8 @@ const Item: React.FC<IItem> = ({
|
||||
<Blurhash
|
||||
hash={attachment.blurhash}
|
||||
className='⁂-media-gallery__preview'
|
||||
aria-label={!visible ? attachment.description : undefined}
|
||||
aria-hidden={visible}
|
||||
/>
|
||||
{(visible || !attachment.blurhash) && thumbnail}
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import AutosuggestAccountInput from 'pl-fe/components/autosuggest-account-input';
|
||||
import SvgIcon from 'pl-fe/components/ui/svg-icon';
|
||||
@ -63,12 +63,11 @@ const SearchInput = React.memo(() => {
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<label htmlFor='search' className='sr-only'><FormattedMessage id='search.placeholder' defaultMessage='Search' /></label>
|
||||
|
||||
<div className='relative'>
|
||||
<AutosuggestAccountInput
|
||||
id='search'
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
aria-label={intl.formatMessage(messages.placeholder)}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
@ -88,11 +87,13 @@ const SearchInput = React.memo(() => {
|
||||
<SvgIcon
|
||||
src={require('@phosphor-icons/core/regular/magnifying-glass.svg')}
|
||||
className={clsx('size-4 text-gray-600', { hidden: hasValue })}
|
||||
aria-hidden
|
||||
/>
|
||||
|
||||
<SvgIcon
|
||||
src={require('@phosphor-icons/core/regular/x.svg')}
|
||||
className={clsx('size-4 text-gray-600', { hidden: !hasValue })}
|
||||
aria-hidden
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -27,7 +27,7 @@ const SidebarNavigationLink = React.memo(React.forwardRef((props: ISidebarNaviga
|
||||
const matchRoute = useMatchRoute();
|
||||
const { demetricator } = useSettings();
|
||||
|
||||
const LinkComponent = (to === undefined ? 'div' : Link) as typeof Link;
|
||||
const LinkComponent = (to === undefined ? 'button' : Link) as typeof Link;
|
||||
|
||||
const isActive = matchRoute({ to }) !== false;
|
||||
|
||||
|
||||
@ -50,6 +50,7 @@ const StatusActionButton = React.forwardRef<HTMLButtonElement, IStatusActionButt
|
||||
<Icon
|
||||
src={active && filledIcon || icon}
|
||||
className={iconClassName}
|
||||
aria-hidden
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -66,12 +66,13 @@ const Accordion: React.FC<IAccordion> = ({ headline, children, menu, expanded =
|
||||
)}
|
||||
{action && actionIcon && (
|
||||
<button className='⁂-accordion__header__action' onClick={handleAction} title={actionLabel}>
|
||||
<Icon src={actionIcon} />
|
||||
<Icon src={actionIcon} aria-hidden />
|
||||
</button>
|
||||
)}
|
||||
<Icon
|
||||
src={require('@phosphor-icons/core/regular/caret-down.svg')}
|
||||
className='⁂-accordion__header__chevron'
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@ -68,9 +68,8 @@ const CardHeader: React.FC<ICardHeader> = ({ className, children, backHref, onBa
|
||||
const backAttributes = backHref ? { to: backHref } : { onClick: onBackClick };
|
||||
|
||||
return (
|
||||
<Comp {...backAttributes} className='⁂-card-header__button' aria-label={intl.formatMessage(messages.back)}>
|
||||
<SvgIcon src={require('@phosphor-icons/core/regular/arrow-left.svg')} />
|
||||
<span className='⁂-card-header__button__label' data-testid='back-button'>{intl.formatMessage(messages.back)}</span>
|
||||
<Comp {...backAttributes} className='⁂-card-header__button' aria-label={intl.formatMessage(messages.back)} title={intl.formatMessage(messages.back)}>
|
||||
<SvgIcon src={require('@phosphor-icons/core/regular/arrow-left.svg')} aria-hidden />
|
||||
</Comp>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@ import { Link, type LinkOptions } from '@tanstack/react-router';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import toast, { Toast as RHToast } from 'react-hot-toast';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { ToastText, ToastType } from 'pl-fe/toast';
|
||||
|
||||
@ -11,6 +11,10 @@ import Icon from './icon';
|
||||
import Stack from './stack';
|
||||
import Text from './text';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
const renderText = (text: ToastText) => {
|
||||
if (typeof text === 'string') {
|
||||
return text;
|
||||
@ -35,6 +39,8 @@ interface IToast {
|
||||
const Toast = (props: IToast) => {
|
||||
const { t, message, type, action, actionLinkOptions, actionLabel, summary } = props;
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const dismissToast = () => toast.dismiss(t.id);
|
||||
|
||||
const renderIcon = () => {
|
||||
@ -141,8 +147,8 @@ const Toast = (props: IToast) => {
|
||||
className='inline-flex rounded-md text-gray-600 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:text-gray-600 dark:hover:text-gray-500'
|
||||
onClick={dismissToast}
|
||||
data-testid='toast-dismiss'
|
||||
title={intl.formatMessage(messages.close)}
|
||||
>
|
||||
<span className='sr-only'><FormattedMessage id='lightbox.close' defaultMessage='Close' /></span>
|
||||
<Icon src={require('@phosphor-icons/core/regular/x.svg')} className='size-5' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -87,9 +87,9 @@ const ContentTypeButton: React.FC<IContentTypeButton> = ({ composeId, compact })
|
||||
}))}
|
||||
>
|
||||
<button className='⁂-content-type-button' title={compact ? option?.text : intl.formatMessage(messages.change_content_type)}>
|
||||
{option?.icon && <Icon src={option.icon} />}
|
||||
{option?.icon && <Icon src={option.icon} aria-hidden />}
|
||||
{compact ? undefined : option?.text}
|
||||
<Icon src={require('@phosphor-icons/core/regular/caret-down.svg')} />
|
||||
<Icon src={require('@phosphor-icons/core/regular/caret-down.svg')} aria-hidden />
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@ -165,6 +165,7 @@ const getLanguageDropdown = (composeId: string): React.FC<ILanguageDropdown> =>
|
||||
>
|
||||
<Icon
|
||||
src={isSearching ? require('@phosphor-icons/core/regular/backspace.svg') : require('@phosphor-icons/core/regular/magnifying-glass.svg')}
|
||||
aria-hidden
|
||||
/>
|
||||
</button>
|
||||
</label>
|
||||
@ -249,9 +250,9 @@ const LanguageDropdownButton: React.FC<ILanguageDropdownButton> = ({ composeId,
|
||||
className='⁂-language-dropdown'
|
||||
>
|
||||
<button title={intl.formatMessage(messages.languagePrompt)}>
|
||||
<Icon src={require('@phosphor-icons/core/regular/translate.svg')} />
|
||||
<Icon src={require('@phosphor-icons/core/regular/translate.svg')} aria-hidden />
|
||||
{buttonLabel}
|
||||
<Icon src={require('@phosphor-icons/core/regular/caret-down.svg')} />
|
||||
<Icon src={require('@phosphor-icons/core/regular/caret-down.svg')} aria-hidden />
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@ -182,9 +182,9 @@ const PrivacyDropdown: React.FC<IPrivacyDropdown> = ({
|
||||
return (
|
||||
<DropdownMenu items={items} width='16rem'>
|
||||
<button title={compact ? text : intl.formatMessage(messages.change_privacy)}>
|
||||
{valueOption?.icon && <Icon src={valueOption.icon} />}
|
||||
{valueOption?.icon && <Icon src={valueOption.icon} aria-hidden />}
|
||||
{compact ? undefined : text}
|
||||
<Icon src={require('@phosphor-icons/core/regular/caret-down.svg')} />
|
||||
<Icon src={require('@phosphor-icons/core/regular/caret-down.svg')} aria-hidden />
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@ -36,9 +36,7 @@ const Search: React.FC<ISearch> = ({ value, onSubmit }) => {
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<HStack space={2}>
|
||||
<label className='relative grow'>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
|
||||
|
||||
<label className='relative grow' title={intl.formatMessage(messages.search)}>
|
||||
<Input
|
||||
type='text'
|
||||
value={searchValue}
|
||||
@ -54,7 +52,7 @@ const Search: React.FC<ISearch> = ({ value, onSubmit }) => {
|
||||
onSubmit('');
|
||||
}}
|
||||
>
|
||||
<Icon src={require('@phosphor-icons/core/regular/backspace.svg')} aria-label={intl.formatMessage(messages.search)} className={clsx('size-5 text-gray-600', { hidden: !hasValue })} />
|
||||
<Icon src={require('@phosphor-icons/core/regular/backspace.svg')} className={clsx('size-5 text-gray-600', { hidden: !hasValue })} aria-hidden />
|
||||
</div>
|
||||
</label>
|
||||
|
||||
|
||||
@ -134,6 +134,8 @@ const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia, isLast }) =>
|
||||
'hidden': visible,
|
||||
'rounded-br-md': isLast,
|
||||
})}
|
||||
aria-label={!visible ? attachment.description : undefined}
|
||||
aria-hidden={visible}
|
||||
/>
|
||||
{visible && thumbnail}
|
||||
{!visible && icon}
|
||||
|
||||
@ -77,13 +77,12 @@ const SearchInput: React.FC<ISearchInput> = ({ className, placeholder, query })
|
||||
<div
|
||||
className={clsx('z-10 w-full bg-white/90 backdrop-blur backdrop-saturate-200 black:bg-black/75 dark:bg-primary-900/90', className)}
|
||||
>
|
||||
<label htmlFor='search' className='sr-only'>{placeholder || intl.formatMessage(messages.placeholder)}</label>
|
||||
|
||||
<div className='relative'>
|
||||
<Input
|
||||
type='text'
|
||||
id='search'
|
||||
placeholder={placeholder || intl.formatMessage(messages.placeholder)}
|
||||
aria-label={placeholder || intl.formatMessage(messages.placeholder)}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
|
||||
@ -102,9 +102,7 @@ const Search: React.FC<IAliasesSearch> = ({ onSubmit }) => {
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-1'>
|
||||
<label className='relative grow'>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
|
||||
|
||||
<label className='relative grow' title={intl.formatMessage(messages.search)}>
|
||||
<input
|
||||
className='block w-full rounded-full focus:border-primary-500 focus:ring-primary-500 dark:bg-gray-800 dark:text-white dark:placeholder:text-gray-500 sm:text-sm'
|
||||
type='text'
|
||||
@ -120,7 +118,7 @@ const Search: React.FC<IAliasesSearch> = ({ onSubmit }) => {
|
||||
onClick={handleClear}
|
||||
title={intl.formatMessage(messages.clear)}
|
||||
>
|
||||
<Icon src={require('@phosphor-icons/core/regular/backspace.svg')} className={clsx('size-5 text-gray-600', { 'hidden': !hasValue })} />
|
||||
<Icon src={require('@phosphor-icons/core/regular/backspace.svg')} className={clsx('size-5 text-gray-600', { 'hidden': !hasValue })} aria-hidden />
|
||||
</button>
|
||||
</label>
|
||||
<Button onClick={handleSubmit}>{intl.formatMessage(messages.searchTitle)}</Button>
|
||||
|
||||
@ -254,10 +254,6 @@ a.⁂-list-item,
|
||||
width: 1.5rem;
|
||||
@apply rtl:rotate-180;
|
||||
}
|
||||
|
||||
&__label {
|
||||
@apply sr-only;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -193,7 +193,7 @@ body {
|
||||
}
|
||||
|
||||
.⁂-sidebar-navigation-link {
|
||||
@apply flex items-center py-1 text-sm font-semibold space-x-4 rtl:space-x-reverse transition-colors duration-200;
|
||||
@apply flex items-center w-full py-1 text-sm font-semibold space-x-4 rtl:space-x-reverse transition-colors duration-200;
|
||||
|
||||
@media (height >= 960px) {
|
||||
padding: 0.5rem 0;
|
||||
|
||||
@ -144,7 +144,7 @@
|
||||
} @else if $theme == tertiary {
|
||||
@apply bg-white dark:bg-primary-900 border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500;
|
||||
} @else if $theme == accent {
|
||||
@apply border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300;
|
||||
@apply border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-white focus:ring-secondary-300;
|
||||
} @else if $theme == danger {
|
||||
@apply border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:ring-danger-500;
|
||||
} @else if $theme == transparent {
|
||||
|
||||
Reference in New Issue
Block a user