feat(rtk-query): improve a11y of rtk-query-monitor tab panel #1126

This commit is contained in:
FaberVitale 2022-04-09 12:57:17 +02:00
parent d9ecb32efc
commit 8154bde35b
11 changed files with 94 additions and 20 deletions

View File

@ -127,6 +127,7 @@ export class QueryForm extends React.PureComponent<
{({ styling, base16Theme }) => { {({ styling, base16Theme }) => {
return ( return (
<form <form
id="rtk-query-monitor-query-selection-form"
action="#" action="#"
onSubmit={this.handleSubmit} onSubmit={this.handleSubmit}
{...styling('queryForm')} {...styling('queryForm')}

View File

@ -1,8 +1,10 @@
import { createSelector, Selector } from '@reduxjs/toolkit'; import { createSelector, Selector } from '@reduxjs/toolkit';
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { Action, AnyAction } from 'redux'; import { Action, AnyAction } from 'redux';
import { QueryPreviewTabs } from '../types';
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
import { emptyRecord, identity } from '../utils/object'; import { emptyRecord, identity } from '../utils/object';
import { TreeView } from './TreeView'; import { TreeView, TreeViewProps } from './TreeView';
export interface QueryPreviewActionsProps { export interface QueryPreviewActionsProps {
isWideLayout: boolean; isWideLayout: boolean;
@ -11,6 +13,12 @@ export interface QueryPreviewActionsProps {
const keySep = ' - '; const keySep = ' - ';
const rootProps: TreeViewProps['rootProps'] = {
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.actions),
id: renderTabPanelId(QueryPreviewTabs.actions),
role: 'tabpanel',
};
export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps> { export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps> {
selectFormattedActions: Selector< selectFormattedActions: Selector<
AnyAction[], AnyAction[],
@ -74,6 +82,7 @@ export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps>
return ( return (
<TreeView <TreeView
rootProps={rootProps}
data={this.selectFormattedActions(actionsOfQuery)} data={this.selectFormattedActions(actionsOfQuery)}
isWideLayout={isWideLayout} isWideLayout={isWideLayout}
shouldExpandNode={this.shouldExpandNode} shouldExpandNode={this.shouldExpandNode}

View File

@ -1,7 +1,8 @@
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { ApiStats, RtkQueryApiState } from '../types'; import { ApiStats, QueryPreviewTabs, RtkQueryApiState } from '../types';
import { StyleUtilsContext } from '../styles/createStylingFromTheme'; import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { TreeView } from './TreeView'; import { TreeView, TreeViewProps } from './TreeView';
import { renderTabPanelId, renderTabPanelButtonId } from '../utils/a11y';
export interface QueryPreviewApiProps { export interface QueryPreviewApiProps {
apiStats: ApiStats | null; apiStats: ApiStats | null;
@ -9,6 +10,12 @@ export interface QueryPreviewApiProps {
isWideLayout: boolean; isWideLayout: boolean;
} }
const rootProps: TreeViewProps['rootProps'] = {
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.apiConfig),
id: renderTabPanelId(QueryPreviewTabs.apiConfig),
role: 'tabpanel',
};
export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> { export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
shouldExpandApiStateNode = ( shouldExpandApiStateNode = (
keyPath: (string | number)[], keyPath: (string | number)[],
@ -33,7 +40,7 @@ export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
return ( return (
<StyleUtilsContext.Consumer> <StyleUtilsContext.Consumer>
{({ styling }) => ( {({ styling }) => (
<article {...styling('tabContent')}> <article {...rootProps} {...styling('tabContent')}>
<h2>{apiState.config.reducerPath}</h2> <h2>{apiState.config.reducerPath}</h2>
<TreeView <TreeView
before={<h3>State</h3>} before={<h3>State</h3>}

View File

@ -1,20 +1,26 @@
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { RtkResourceInfo } from '../types'; import { QueryPreviewTabs, RtkResourceInfo } from '../types';
import { TreeView } from './TreeView'; import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
import { TreeView, TreeViewProps } from './TreeView';
export interface QueryPreviewDataProps { export interface QueryPreviewDataProps {
data: RtkResourceInfo['state']['data']; data: RtkResourceInfo['state']['data'];
isWideLayout: boolean; isWideLayout: boolean;
} }
const rootProps: TreeViewProps['rootProps'] = {
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.data),
id: renderTabPanelId(QueryPreviewTabs.data),
role: 'tabpanel',
};
export class QueryPreviewData extends PureComponent<QueryPreviewDataProps> { export class QueryPreviewData extends PureComponent<QueryPreviewDataProps> {
shouldExpandNode = ( shouldExpandNode = (
keyPath: (string | number)[], keyPath: (string | number)[],
value: unknown, value: unknown,
layer: number layer: number
): boolean => { ): boolean => {
const lastKey = keyPath[keyPath.length - 1]; return layer <= 1;
return layer <= 1 && lastKey !== 'query' && lastKey !== 'mutation';
}; };
render(): ReactNode { render(): ReactNode {
@ -22,6 +28,7 @@ export class QueryPreviewData extends PureComponent<QueryPreviewDataProps> {
return ( return (
<TreeView <TreeView
rootProps={rootProps}
data={data} data={data}
isWideLayout={isWideLayout} isWideLayout={isWideLayout}
shouldExpandNode={this.shouldExpandNode} shouldExpandNode={this.shouldExpandNode}

View File

@ -1,6 +1,7 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme'; import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { QueryPreviewTabs, TabOption } from '../types'; import { QueryPreviewTabs, TabOption } from '../types';
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
import { emptyArray } from '../utils/object'; import { emptyArray } from '../utils/object';
export interface QueryPreviewHeaderProps { export interface QueryPreviewHeaderProps {
@ -28,7 +29,11 @@ export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps>
<div {...styling('previewHeader')}> <div {...styling('previewHeader')}>
<div {...styling('tabSelector')}> <div {...styling('tabSelector')}>
{tabs.map((tab) => ( {tabs.map((tab) => (
<div <button
type="button"
id={renderTabPanelButtonId(tab.value)}
aria-selected={tab.value === selectedTab}
role={'tab'}
onClick={() => this.handleTabClick(tab)} onClick={() => this.handleTabClick(tab)}
key={tab.value} key={tab.value}
{...styling( {...styling(
@ -42,7 +47,7 @@ export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps>
<span> <span>
{renderTabLabel ? renderTabLabel(tab) : tab.label} {renderTabLabel ? renderTabLabel(tab) : tab.label}
</span> </span>
</div> </button>
))} ))}
</div> </div>
</div> </div>

View File

@ -1,11 +1,12 @@
import { createSelector, Selector } from '@reduxjs/toolkit'; import { createSelector, Selector } from '@reduxjs/toolkit';
import { QueryStatus } from '@reduxjs/toolkit/dist/query'; import { QueryStatus } from '@reduxjs/toolkit/dist/query';
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { RtkResourceInfo, RTKStatusFlags } from '../types'; import { QueryPreviewTabs, RtkResourceInfo, RTKStatusFlags } from '../types';
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
import { formatMs } from '../utils/formatters'; import { formatMs } from '../utils/formatters';
import { identity } from '../utils/object'; import { identity } from '../utils/object';
import { getQueryStatusFlags } from '../utils/rtk-query'; import { getQueryStatusFlags } from '../utils/rtk-query';
import { TreeView } from './TreeView'; import { TreeView, TreeViewProps } from './TreeView';
type QueryTimings = { type QueryTimings = {
startedAt: string; startedAt: string;
@ -23,6 +24,12 @@ type FormattedQuery = {
| { query: RtkResourceInfo['state'] } | { query: RtkResourceInfo['state'] }
); );
const rootProps: TreeViewProps['rootProps'] = {
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.queryinfo),
id: renderTabPanelId(QueryPreviewTabs.queryinfo),
role: 'tabpanel',
};
export interface QueryPreviewInfoProps { export interface QueryPreviewInfoProps {
resInfo: RtkResourceInfo; resInfo: RtkResourceInfo;
isWideLayout: boolean; isWideLayout: boolean;
@ -97,6 +104,7 @@ export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
return ( return (
<TreeView <TreeView
rootProps={rootProps}
data={formattedQuery} data={formattedQuery}
isWideLayout={isWideLayout} isWideLayout={isWideLayout}
shouldExpandNode={this.shouldExpandNode} shouldExpandNode={this.shouldExpandNode}

View File

@ -1,6 +1,15 @@
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { RtkQueryApiState } from '../types'; import { QueryPreviewTabs, RtkQueryApiState } from '../types';
import { TreeView } from './TreeView'; import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
import { TreeView, TreeViewProps } from './TreeView';
const rootProps: TreeViewProps['rootProps'] = {
'aria-labelledby': renderTabPanelButtonId(
QueryPreviewTabs.querySubscriptions
),
id: renderTabPanelId(QueryPreviewTabs.querySubscriptions),
role: 'tabpanel',
};
export interface QueryPreviewSubscriptionsProps { export interface QueryPreviewSubscriptionsProps {
subscriptions: RtkQueryApiState['subscriptions'][keyof RtkQueryApiState['subscriptions']]; subscriptions: RtkQueryApiState['subscriptions'][keyof RtkQueryApiState['subscriptions']];
@ -12,7 +21,11 @@ export class QueryPreviewSubscriptions extends PureComponent<QueryPreviewSubscri
const { subscriptions } = this.props; const { subscriptions } = this.props;
return ( return (
<TreeView data={subscriptions} isWideLayout={this.props.isWideLayout} /> <TreeView
rootProps={rootProps}
data={subscriptions}
isWideLayout={this.props.isWideLayout}
/>
); );
} }
} }

View File

@ -1,11 +1,18 @@
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { RtkQueryTag } from '../types'; import { QueryPreviewTabs, RtkQueryTag } from '../types';
import { TreeView } from './TreeView'; import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
import { TreeView, TreeViewProps } from './TreeView';
interface QueryPreviewTagsState { interface QueryPreviewTagsState {
data: { tags: RtkQueryTag[] }; data: { tags: RtkQueryTag[] };
} }
const rootProps: TreeViewProps['rootProps'] = {
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.queryTags),
id: renderTabPanelId(QueryPreviewTabs.queryTags),
role: 'tabpanel',
};
export interface QueryPreviewTagsProps { export interface QueryPreviewTagsProps {
tags: RtkQueryTag[]; tags: RtkQueryTag[];
isWideLayout: boolean; isWideLayout: boolean;
@ -26,6 +33,8 @@ export class QueryPreviewTags extends PureComponent<
render(): ReactNode { render(): ReactNode {
const { isWideLayout, tags } = this.props; const { isWideLayout, tags } = this.props;
return <TreeView data={tags} isWideLayout={isWideLayout} />; return (
<TreeView rootProps={rootProps} data={tags} isWideLayout={isWideLayout} />
);
} }
} }

View File

@ -22,6 +22,9 @@ export interface TreeViewProps
before?: ReactNode; before?: ReactNode;
after?: ReactNode; after?: ReactNode;
children?: ReactNode; children?: ReactNode;
rootProps?: Partial<
Omit<React.HTMLAttributes<HTMLDivElement>, 'className' | 'style'>
>;
} }
export class TreeView extends React.PureComponent<TreeViewProps> { export class TreeView extends React.PureComponent<TreeViewProps> {
@ -80,13 +83,14 @@ export class TreeView extends React.PureComponent<TreeViewProps> {
keyPath, keyPath,
shouldExpandNode, shouldExpandNode,
hideRoot, hideRoot,
rootProps,
} = this.props; } = this.props;
return ( return (
<StyleUtilsContext.Consumer> <StyleUtilsContext.Consumer>
{({ styling, invertTheme, base16Theme }) => { {({ styling, invertTheme, base16Theme }) => {
return ( return (
<div {...styling('treeWrapper')}> <div {...rootProps} {...styling('treeWrapper')}>
{before} {before}
<JSONTree <JSONTree
keyPath={keyPath} keyPath={keyPath}

View File

@ -217,6 +217,8 @@ const getSheetFromColorMap = (map: ColorMap) => {
padding: '0 8px', padding: '0 8px',
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
boxShadow: 'none',
outline: 'none',
color: map.TEXT_COLOR, color: map.TEXT_COLOR,
'border-style': 'solid', 'border-style': 'solid',
'border-width': '1px', 'border-width': '1px',

View File

@ -0,0 +1,9 @@
import { QueryPreviewTabs } from '../types';
export function renderTabPanelId(value: QueryPreviewTabs): string {
return `rtk-query-monitor-tab-panel-${value}`;
}
export function renderTabPanelButtonId(value: QueryPreviewTabs): string {
return renderTabPanelId(value) + '-button';
}