Skip to content

Commit

Permalink
feat: accessible descriptions and labels for various components (#674)
Browse files Browse the repository at this point in the history
* feat: added descriptions for content buttons, links, basic cards, caption
  • Loading branch information
Kyzyl-ool authored Nov 7, 2023
1 parent b5be2da commit 44ef8fa
Show file tree
Hide file tree
Showing 13 changed files with 79 additions and 13 deletions.
8 changes: 7 additions & 1 deletion src/blocks/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, {useContext} from 'react';

import {useUniqId} from '@gravity-ui/uikit';

import {Button, HTML, Media, RouterLink} from '../../components';
import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs/HeaderBreadcrumbs';
import {getMediaImage} from '../../components/Media/Image/utils';
Expand Down Expand Up @@ -96,6 +98,7 @@ export const HeaderBlock = (props: WithChildren<HeaderBlockFullProps>) => {
const imageThemed = image && getThemedValue(image, theme);
const videoThemed = video && getThemedValue(video, theme);
const fullWidth = backgroundThemed?.fullWidth || backgroundThemed?.fullWidthMedia;
const titleId = useUniqId();

return (
<header
Expand Down Expand Up @@ -134,7 +137,7 @@ export const HeaderBlock = (props: WithChildren<HeaderBlockFullProps>) => {
<HTML>{overtitle}</HTML>
</div>
)}
<h1 className={b('title')}>
<h1 className={b('title')} id={titleId}>
{status}
{renderTitle ? renderTitle(title) : <HTML>{title}</HTML>}
</h1>
Expand All @@ -157,6 +160,9 @@ export const HeaderBlock = (props: WithChildren<HeaderBlockFullProps>) => {
key={index}
className={b('button')}
size="xl"
extraProps={{
'aria-describedby': titleId,
}}
{...button}
/>
</RouterLink>
Expand Down
14 changes: 13 additions & 1 deletion src/blocks/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, {Fragment, useRef, useState} from 'react';

import {useUniqId} from '@gravity-ui/uikit';

import AnimateBlock from '../../components/AnimateBlock/AnimateBlock';
import ButtonTabs, {ButtonTabsItemProps} from '../../components/ButtonTabs/ButtonTabs';
import FullscreenImage from '../../components/FullscreenImage/FullscreenImage';
Expand Down Expand Up @@ -36,13 +38,19 @@ export const TabsBlock = ({
const ref = useRef<HTMLDivElement>(null);
const mediaWidth = ref?.current?.offsetWidth;
const mediaHeight = mediaWidth && getHeight(mediaWidth);
const captionId = useUniqId();

let imageProps;

if (activeTabData) {
const themedImage = getThemedValue(activeTabData.image, theme);

imageProps = themedImage && getMediaImage(themedImage);
if (activeTabData.caption && imageProps) {
Object.assign(imageProps, {
'aria-describedby': captionId,
});
}
}

const showMedia = Boolean(activeTabData?.media || imageProps);
Expand Down Expand Up @@ -99,7 +107,11 @@ export const TabsBlock = ({
<FullscreenImage {...imageProps} imageClassName={b('image')} />
</Fragment>
)}
{activeTabData?.caption && <p className={b('caption')}>{activeTabData.caption}</p>}
{activeTabData?.caption && (
<p className={b('caption')} id={captionId}>
{activeTabData.caption}
</p>
)}
</Col>
);

Expand Down
7 changes: 5 additions & 2 deletions src/components/BackLink/BackLink.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {ReactNode, useCallback, useContext} from 'react';
import React, {HTMLProps, ReactNode, useCallback, useContext} from 'react';

import {Button, ButtonSize, Icon} from '@gravity-ui/uikit';

Expand All @@ -9,14 +9,15 @@ import {DefaultEventNames, Tabbable} from '../../models';

export type Theme = 'default' | 'special';

export interface BackLinkProps extends Tabbable {
export interface BackLinkProps<T = HTMLElement> extends Tabbable {
url: string;
title: ReactNode;
theme?: Theme;
size?: ButtonSize;
className?: string;
shouldHandleBackAction?: boolean;
onClick?: () => void;
extraProps?: HTMLProps<T>;
}

export default function BackLink(props: BackLinkProps) {
Expand All @@ -30,6 +31,7 @@ export default function BackLink(props: BackLinkProps) {
shouldHandleBackAction = false,
onClick,
tabIndex,
extraProps,
} = props;
const handleAnalytics = useAnalytics(DefaultEventNames.ShareButton, url);

Expand Down Expand Up @@ -59,6 +61,7 @@ export default function BackLink(props: BackLinkProps) {
href={shouldHandleBackAction ? undefined : url}
onClick={shouldHandleBackAction ? backActionHandler : undefined}
tabIndex={tabIndex}
extraProps={extraProps}
>
<Icon data={ArrowSidebar} size={24} />
<span>{title}</span>
Expand Down
1 change: 0 additions & 1 deletion src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import './Button.scss';
export interface ButtonProps extends Omit<ButtonParams, 'url'>, QAProps {
className?: string;
url?: string;
urlTitle?: string;
onClick?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
}

Expand Down
2 changes: 2 additions & 0 deletions src/components/FileLink/FileLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const FileLink = (props: WithChildren<FileLinkProps>) => {
onClick,
tabIndex,
urlTitle,
extraProps,
} = props;
const fileExt = getFileExt(href) as FileExtension;
const labelTheme = (FileExtensionThemes[fileExt] || 'unknown') as LabelProps['theme'];
Expand All @@ -74,6 +75,7 @@ const FileLink = (props: WithChildren<FileLinkProps>) => {
tabIndex={tabIndex}
title={urlTitle}
{...getLinkProps(href, hostname)}
{...extraProps}
>
{text}
</a>
Expand Down
7 changes: 4 additions & 3 deletions src/components/FullscreenImage/FullscreenImage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {CSSProperties, useState} from 'react';
import React, {CSSProperties, HTMLProps, useState} from 'react';

import {Icon, Modal} from '@gravity-ui/uikit';

Expand All @@ -14,21 +14,22 @@ export interface FullscreenImageProps extends ImageProps {
imageClassName?: string;
modalImageClass?: string;
imageStyle?: CSSProperties;
extraProps?: HTMLProps<HTMLDivElement>;
}

const b = block('fullscreen-image');
const FULL_SCREEN_ICON_SIZE = 18;
const CLOSE_ICON_SIZE = 30;

const FullscreenImage = (props: FullscreenImageProps) => {
const {imageClassName, modalImageClass, imageStyle, alt = i18n('img-alt')} = props;
const {imageClassName, modalImageClass, imageStyle, alt = i18n('img-alt'), extraProps} = props;
const [isOpened, setIsOpened] = useState(false);

const openModal = () => setIsOpened(true);
const closeModal = () => setIsOpened(false);

return (
<div className={b()}>
<div className={b()} {...extraProps}>
<div className={b('image-wrapper')}>
<Image
{...props}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Image/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const Image = (props: ImageProps) => {
onClick,
containerClassName,
qa,
...rest
} = props;
const [imgLoadingError, setImgLoadingError] = useState(false);

Expand Down Expand Up @@ -113,6 +114,7 @@ const Image = (props: ImageProps) => {
style={style}
onClick={onClick}
onError={() => setImgLoadingError(true)}
{...rest}
/>
</picture>
);
Expand Down
4 changes: 4 additions & 0 deletions src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const LinkBlock = (props: WithChildren<LinkFullProps>) => {
tabIndex,
qa,
urlTitle,
extraProps,
} = props;
const qaAttributes = getQaAttrubutes(qa, ['normal']);

Expand All @@ -82,6 +83,7 @@ const LinkBlock = (props: WithChildren<LinkFullProps>) => {
url={href}
onClick={onClick}
tabIndex={tabIndex}
extraProps={extraProps}
/>
);
case 'file-link':
Expand All @@ -94,6 +96,7 @@ const LinkBlock = (props: WithChildren<LinkFullProps>) => {
textSize={textSize}
onClick={onClick}
tabIndex={tabIndex}
extraProps={extraProps}
/>
);
case 'normal': {
Expand All @@ -109,6 +112,7 @@ const LinkBlock = (props: WithChildren<LinkFullProps>) => {
title={urlTitle}
{...linkProps}
data-qa={qaAttributes.normal}
{...extraProps}
>
{arrow ? (
<Fragment>
Expand Down
4 changes: 3 additions & 1 deletion src/components/Title/Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ const b = block('title');

export interface TitleProps extends TitleParams {
colSizes?: GridColumnSizesType;
id?: string;
}

const Title = ({
title,
subtitle,
className,
colSizes = {all: 12, sm: 8},
id,
}: TitleProps & ClassNameProps) => {
if (!title && !subtitle) {
return null;
Expand All @@ -29,7 +31,7 @@ const Title = ({
!title || typeof title === 'string' ? ({text: title} as TitleItemProps) : title;

return (
<div className={b(null, className)}>
<div className={b(null, className)} id={id}>
{text && (
<Col reset sizes={colSizes}>
<TitleItem text={text} {...titleProps} />
Expand Down
2 changes: 2 additions & 0 deletions src/models/constructor-items/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@ export interface ContentItemProps {

export interface ContentBlockProps {
title?: TitleItemBaseProps | string;
titleId?: string;
text?: string;
textId?: string;
additionalInfo?: string;
links?: LinkProps[];
buttons?: ButtonProps[];
Expand Down
7 changes: 5 additions & 2 deletions src/models/constructor-items/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {CSSProperties, ReactNode} from 'react';
import React, {CSSProperties, HTMLProps, ReactNode} from 'react';

import {ButtonView, ButtonProps as UikitButtonProps} from '@gravity-ui/uikit';

Expand Down Expand Up @@ -124,7 +124,7 @@ interface LoopProps {

// images

export interface ImageInfoProps {
export interface ImageInfoProps extends Pick<HTMLProps<HTMLImageElement>, 'aria-describedby'> {
alt?: string;
disableCompress?: boolean;
}
Expand Down Expand Up @@ -179,6 +179,7 @@ export interface LinkProps extends AnalyticsEventsBase, Stylable, Tabbable {
target?: string;
metrikaGoals?: MetrikaGoal;
pixelEvents?: ButtonPixel;
extraProps?: HTMLProps<HTMLAnchorElement>;
}

export interface FileLinkProps extends ClassNameProps, Tabbable {
Expand All @@ -189,6 +190,7 @@ export interface FileLinkProps extends ClassNameProps, Tabbable {
theme?: ContentTheme;
urlTitle?: string;
onClick?: () => void;
extraProps?: HTMLProps<HTMLAnchorElement>;
}

// buttons
Expand All @@ -205,6 +207,7 @@ export interface ButtonProps
Pick<UikitButtonProps, 'size' | 'width' | 'extraProps'> {
text: string;
url: string;
urlTitle?: string;
primary?: boolean;
theme?: ButtonTheme;
img?: ButtonImageProps | string;
Expand Down
12 changes: 11 additions & 1 deletion src/sub-blocks/BasicCard/BasicCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';

import {useUniqId} from '@gravity-ui/uikit';

import {Content} from '../';
import CardBase from '../../components/CardBase/CardBase';
import Image from '../../components/Image/Image';
Expand All @@ -24,9 +26,15 @@ const BasicCard = (props: BasicCardProps) => {
...cardParams
} = props;
const iconProps = icon && getMediaImage(icon);
const titleId = useUniqId();
const descriptionId = useUniqId();

return (
<CardBase className={b()} {...cardParams}>
<CardBase
className={b()}
{...cardParams}
extraProps={{'aria-describedby': descriptionId, 'aria-labelledby': titleId}}
>
<CardBase.Content>
<div className={b('content', {['icon-position']: iconPosition})}>
{iconProps && (
Expand All @@ -37,7 +45,9 @@ const BasicCard = (props: BasicCardProps) => {
)}
<Content
title={title}
titleId={titleId}
text={text}
textId={descriptionId}
additionalInfo={additionalInfo}
links={links}
buttons={buttons}
Expand Down
22 changes: 21 additions & 1 deletion src/sub-blocks/Content/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';

import {useUniqId} from '@gravity-ui/uikit';

import {Button, Title, YFMWrapper} from '../../components';
import LinkBlock from '../../components/Link/Link';
import {Col} from '../../grid';
Expand Down Expand Up @@ -49,7 +51,9 @@ export type ContentProps = ContentBlockProps & ClassNameProps & QAProps;
const Content = (props: ContentProps) => {
const {
title,
titleId: titleIdFromProps,
text,
textId,
additionalInfo,
size = 'l',
links,
Expand All @@ -69,6 +73,8 @@ const Content = (props: ContentProps) => {
: title;

const hasTitle = Boolean(title);
const defaultTitleId = useUniqId();
const titleId = titleIdFromProps || defaultTitleId;

return (
<Col
Expand All @@ -77,12 +83,20 @@ const Content = (props: ContentProps) => {
sizes={colSizes}
qa={qaAttributes.container}
>
{title && <Title className={b('title')} title={titleProps} colSizes={{all: 12}} />}
{title && (
<Title
className={b('title')}
title={titleProps}
colSizes={{all: 12}}
id={titleId}
/>
)}
{text && (
<div className={b('text', {['without-title']: !hasTitle})}>
<YFMWrapper
content={text}
modifiers={{constructor: true, [`constructor-size-${size}`]: true}}
id={textId}
/>
</div>
)}
Expand All @@ -108,6 +122,9 @@ const Content = (props: ContentProps) => {
textSize={getLinkSize(size)}
key={link.url}
qa={qaAttributes.link}
extraProps={{
'aria-describedby': link.urlTitle ? undefined : titleId,
}}
/>
))}
</div>
Expand All @@ -121,6 +138,9 @@ const Content = (props: ContentProps) => {
key={item.url}
size={getButtonSize(size)}
qa={qaAttributes.button}
extraProps={{
'aria-describedby': item.urlTitle ? undefined : titleId,
}}
/>
))}
</div>
Expand Down

0 comments on commit 44ef8fa

Please sign in to comment.