Skip to content

Commit 57406d0

Browse files
committedAug 14, 2021
feat(website): improve accessibility of component tabs
1 parent 3825b12 commit 57406d0

File tree

6 files changed

+154
-147
lines changed

6 files changed

+154
-147
lines changed
 

‎packages/bar/src/compute/grouped.ts

+60-61
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ type Params<RawDatum, XScaleInput, YScaleInput> = {
1919
const gt = (value: number, other: number) => value > other
2020
const lt = (value: number, other: number) => value < other
2121

22-
const flatten = <T>(array: T[][]) => ([] as T[]).concat(...array)
2322
const range = (start: number, end: number) =>
2423
Array.from(' '.repeat(end - start), (_, index) => start + index)
2524

@@ -44,42 +43,42 @@ const generateVerticalGroupedBars = <RawDatum extends Record<string, unknown>>(
4443
barWidth: number,
4544
reverse: boolean,
4645
yRef: number
47-
) => {
46+
): ComputedBarDatum<RawDatum>[] => {
4847
const compare = reverse ? lt : gt
4948
const getY = (d: number) => (compare(d, 0) ? yScale(d) ?? 0 : yRef)
5049
const getHeight = (d: number, y: number) => (compare(d, 0) ? yRef - y : (yScale(d) ?? 0) - yRef)
5150
const cleanedData = data.map(filterNullValues)
5251

53-
const bars = flatten(
54-
keys.map((key, i) =>
55-
range(0, xScale.domain().length).map(index => {
56-
const [rawValue, value] = coerceValue(data[index][key])
57-
const indexValue = getIndex(data[index])
58-
const x = (xScale(indexValue) ?? 0) + barWidth * i + innerPadding * i
59-
const y = getY(value)
60-
const barHeight = getHeight(value, y)
61-
const barData = {
62-
id: key,
63-
value: rawValue === null ? rawValue : value,
64-
formattedValue: formatValue(value),
65-
hidden: false,
66-
index,
67-
indexValue,
68-
data: cleanedData[index],
69-
}
70-
71-
return {
72-
key: `${key}.${barData.indexValue}`,
73-
data: barData,
74-
x,
75-
y,
76-
width: barWidth,
77-
height: barHeight,
78-
color: getColor(barData),
79-
label: getTooltipLabel(barData),
80-
}
52+
const bars: ComputedBarDatum<RawDatum>[] = []
53+
keys.forEach((key, i) =>
54+
range(0, xScale.domain().length).forEach(index => {
55+
const [rawValue, value] = coerceValue(data[index][key])
56+
const indexValue = getIndex(data[index])
57+
const x = (xScale(indexValue) ?? 0) + barWidth * i + innerPadding * i
58+
const y = getY(value)
59+
const barHeight = getHeight(value, y)
60+
const barData: ComputedDatum<RawDatum> = {
61+
id: key,
62+
value: rawValue === null ? rawValue : value,
63+
formattedValue: formatValue(value),
64+
hidden: false,
65+
index,
66+
indexValue,
67+
data: cleanedData[index],
68+
}
69+
70+
bars.push({
71+
key: `${key}.${barData.indexValue}`,
72+
index: bars.length,
73+
data: barData,
74+
x,
75+
y,
76+
width: barWidth,
77+
height: barHeight,
78+
color: getColor(barData),
79+
label: getTooltipLabel(barData),
8180
})
82-
)
81+
})
8382
)
8483

8584
return bars
@@ -103,42 +102,42 @@ const generateHorizontalGroupedBars = <RawDatum extends Record<string, unknown>>
103102
barHeight: number,
104103
reverse: boolean,
105104
xRef: number
106-
) => {
105+
): ComputedBarDatum<RawDatum>[] => {
107106
const compare = reverse ? lt : gt
108107
const getX = (d: number) => (compare(d, 0) ? xRef : xScale(d) ?? 0)
109108
const getWidth = (d: number, x: number) => (compare(d, 0) ? (xScale(d) ?? 0) - xRef : xRef - x)
110109
const cleanedData = data.map(filterNullValues)
111110

112-
const bars = flatten(
113-
keys.map((key, i) =>
114-
range(0, yScale.domain().length).map(index => {
115-
const [rawValue, value] = coerceValue(data[index][key])
116-
const indexValue = getIndex(data[index])
117-
const x = getX(value)
118-
const y = (yScale(indexValue) ?? 0) + barHeight * i + innerPadding * i
119-
const barWidth = getWidth(value, x)
120-
const barData = {
121-
id: key,
122-
value: rawValue === null ? rawValue : value,
123-
formattedValue: formatValue(value),
124-
hidden: false,
125-
index,
126-
indexValue,
127-
data: cleanedData[index],
128-
}
129-
130-
return {
131-
key: `${key}.${barData.indexValue}`,
132-
data: barData,
133-
x,
134-
y,
135-
width: barWidth,
136-
height: barHeight,
137-
color: getColor(barData),
138-
label: getTooltipLabel(barData),
139-
}
111+
const bars: ComputedBarDatum<RawDatum>[] = []
112+
keys.forEach((key, i) =>
113+
range(0, yScale.domain().length).forEach(index => {
114+
const [rawValue, value] = coerceValue(data[index][key])
115+
const indexValue = getIndex(data[index])
116+
const x = getX(value)
117+
const y = (yScale(indexValue) ?? 0) + barHeight * i + innerPadding * i
118+
const barWidth = getWidth(value, x)
119+
const barData: ComputedDatum<RawDatum> = {
120+
id: key,
121+
value: rawValue === null ? rawValue : value,
122+
formattedValue: formatValue(value),
123+
hidden: false,
124+
index,
125+
indexValue,
126+
data: cleanedData[index],
127+
}
128+
129+
bars.push({
130+
key: `${key}.${barData.indexValue}`,
131+
index: bars.length,
132+
data: barData,
133+
x,
134+
y,
135+
width: barWidth,
136+
height: barHeight,
137+
color: getColor(barData),
138+
label: getTooltipLabel(barData),
140139
})
141-
)
140+
})
142141
)
143142

144143
return bars

‎packages/bar/src/compute/stacked.ts

+62-62
Original file line numberDiff line numberDiff line change
@@ -39,41 +39,41 @@ const generateVerticalStackedBars = <RawDatum extends Record<string, unknown>>(
3939
}: Params<RawDatum, string, number>,
4040
barWidth: number,
4141
reverse: boolean
42-
) => {
42+
): ComputedBarDatum<RawDatum>[] => {
4343
const getY = (d: StackDatum<RawDatum>) => yScale(d[reverse ? 0 : 1])
4444
const getHeight = (d: StackDatum<RawDatum>, y: number) => (yScale(d[reverse ? 1 : 0]) ?? 0) - y
4545

46-
const bars = flattenDeep(
47-
stackedData.map(stackedDataItem =>
48-
xScale.domain().map((index, i) => {
49-
const d = stackedDataItem[i]
50-
const x = xScale(getIndex(d.data)) ?? 0
51-
const y = (getY(d) ?? 0) + innerPadding * 0.5
52-
const barHeight = getHeight(d, y) - innerPadding
53-
const [rawValue, value] = coerceValue(d.data[stackedDataItem.key])
54-
55-
const barData = {
56-
id: stackedDataItem.key,
57-
value: rawValue === null ? rawValue : value,
58-
formattedValue: formatValue(value),
59-
hidden: false,
60-
index: i,
61-
indexValue: index,
62-
data: filterNullValues(d.data),
63-
}
64-
65-
return {
66-
key: `${stackedDataItem.key}.${index}`,
67-
data: barData,
68-
x,
69-
y,
70-
width: barWidth,
71-
height: barHeight,
72-
color: getColor(barData),
73-
label: getTooltipLabel(barData),
74-
}
46+
const bars: ComputedBarDatum<RawDatum>[] = []
47+
stackedData.forEach(stackedDataItem =>
48+
xScale.domain().forEach((index, i) => {
49+
const d = stackedDataItem[i]
50+
const x = xScale(getIndex(d.data)) ?? 0
51+
const y = (getY(d) ?? 0) + innerPadding * 0.5
52+
const barHeight = getHeight(d, y) - innerPadding
53+
const [rawValue, value] = coerceValue(d.data[stackedDataItem.key])
54+
55+
const barData: ComputedDatum<RawDatum> = {
56+
id: stackedDataItem.key,
57+
value: rawValue === null ? rawValue : value,
58+
formattedValue: formatValue(value),
59+
hidden: false,
60+
index: i,
61+
indexValue: index,
62+
data: filterNullValues(d.data),
63+
}
64+
65+
bars.push({
66+
key: `${stackedDataItem.key}.${index}`,
67+
index: bars.length,
68+
data: barData,
69+
x,
70+
y,
71+
width: barWidth,
72+
height: barHeight,
73+
color: getColor(barData),
74+
label: getTooltipLabel(barData),
7575
})
76-
)
76+
})
7777
)
7878

7979
return bars
@@ -95,41 +95,41 @@ const generateHorizontalStackedBars = <RawDatum extends Record<string, unknown>>
9595
}: Params<RawDatum, number, string>,
9696
barHeight: number,
9797
reverse: boolean
98-
) => {
98+
): ComputedBarDatum<RawDatum>[] => {
9999
const getX = (d: StackDatum<RawDatum>) => xScale(d[reverse ? 1 : 0])
100100
const getWidth = (d: StackDatum<RawDatum>, x: number) => (xScale(d[reverse ? 0 : 1]) ?? 0) - x
101101

102-
const bars = flattenDeep(
103-
stackedData.map(stackedDataItem =>
104-
yScale.domain().map((index, i) => {
105-
const d = stackedDataItem[i]
106-
const y = yScale(getIndex(d.data)) ?? 0
107-
const x = (getX(d) ?? 0) + innerPadding * 0.5
108-
const barWidth = getWidth(d, x) - innerPadding
109-
const [rawValue, value] = coerceValue(d.data[stackedDataItem.key])
110-
111-
const barData = {
112-
id: stackedDataItem.key,
113-
value: rawValue === null ? rawValue : value,
114-
formattedValue: formatValue(value),
115-
hidden: false,
116-
index: i,
117-
indexValue: index,
118-
data: filterNullValues(d.data),
119-
}
120-
121-
return {
122-
key: `${stackedDataItem.key}.${index}`,
123-
data: barData,
124-
x,
125-
y,
126-
width: barWidth,
127-
height: barHeight,
128-
color: getColor(barData),
129-
label: getTooltipLabel(barData),
130-
}
102+
const bars: ComputedBarDatum<RawDatum>[] = []
103+
stackedData.forEach(stackedDataItem =>
104+
yScale.domain().forEach((index, i) => {
105+
const d = stackedDataItem[i]
106+
const y = yScale(getIndex(d.data)) ?? 0
107+
const x = (getX(d) ?? 0) + innerPadding * 0.5
108+
const barWidth = getWidth(d, x) - innerPadding
109+
const [rawValue, value] = coerceValue(d.data[stackedDataItem.key])
110+
111+
const barData: ComputedDatum<RawDatum> = {
112+
id: stackedDataItem.key,
113+
value: rawValue === null ? rawValue : value,
114+
formattedValue: formatValue(value),
115+
hidden: false,
116+
index: i,
117+
indexValue: index,
118+
data: filterNullValues(d.data),
119+
}
120+
121+
bars.push({
122+
key: `${stackedDataItem.key}.${index}`,
123+
index: bars.length,
124+
data: barData,
125+
x,
126+
y,
127+
width: barWidth,
128+
height: barHeight,
129+
color: getColor(barData),
130+
label: getTooltipLabel(barData),
131131
})
132-
)
132+
})
133133
)
134134

135135
return bars

‎packages/bar/src/hooks.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ComputedBarDatumWithValue,
99
LegendData,
1010
BarLegendProps,
11+
ComputedBarDatum,
1112
} from './types'
1213
import { defaultProps } from './props'
1314
import { generateGroupedBars, generateStackedBars, getLegendData } from './compute'
@@ -116,9 +117,14 @@ export const useBar = <RawDatum extends BarDatum>({
116117

117118
const barsWithValue = useMemo(
118119
() =>
119-
bars.filter(
120-
(bar): bar is ComputedBarDatumWithValue<RawDatum> => bar.data.value !== null
121-
),
120+
bars
121+
.filter(
122+
(bar): bar is ComputedBarDatumWithValue<RawDatum> => bar.data.value !== null
123+
)
124+
.map((bar, index) => ({
125+
...bar,
126+
index,
127+
})),
122128
[bars]
123129
)
124130

‎packages/bar/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export type ComputedBarDatumWithValue<RawDatum> = ComputedBarDatum<RawDatum> & {
4848

4949
export type ComputedBarDatum<RawDatum> = {
5050
key: string
51+
index: number
5152
data: ComputedDatum<RawDatum>
5253
x: number
5354
y: number

‎website/src/components/components/ComponentTabs.js

+19-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* (c) 2016 Raphaël Benitte
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import React, { useState } from 'react'
102
import PropTypes from 'prop-types'
113
import styled from 'styled-components'
@@ -38,10 +30,14 @@ const ComponentTabs = ({
3830

3931
let content
4032
if (currentTab === 'chart') {
41-
content = <Content id="chart">{children}</Content>
33+
content = (
34+
<Content id="chart" role="tabpanel">
35+
{children}
36+
</Content>
37+
)
4238
} else if (currentTab === 'code') {
4339
content = (
44-
<Code>
40+
<Code role="tabpanel">
4541
<Highlight code={code} language="jsx" />
4642
</Code>
4743
)
@@ -55,15 +51,20 @@ const ComponentTabs = ({
5551

5652
return (
5753
<Wrapper className={`chart-tabs--${currentTab}`}>
58-
<Nav>
59-
{availableTabs.map(tab => {
54+
<Nav role="tablist">
55+
{availableTabs.map((tab, index) => {
6056
const isCurrent = tab === currentTab
6157
const icon = tab === 'chart' ? chartClass : tab
6258
const iconColors = isCurrent || hoverTab === tab ? 'colored' : 'neutral'
6359

6460
return (
6561
<NavItem
6662
key={tab}
63+
role="tab"
64+
tabIndex={0}
65+
aria-setsize={availableTabs.length}
66+
aria-posinset={index + 1}
67+
aria-selected={isCurrent}
6768
className="no-select"
6869
isCurrent={isCurrent}
6970
onClick={() => setCurrentTab(tab)}
@@ -76,7 +77,12 @@ const ComponentTabs = ({
7677
)
7778
})}
7879
{diceRoll && (
79-
<DiceRollButton className="no-select" onClick={diceRoll}>
80+
<DiceRollButton
81+
className="no-select"
82+
onClick={diceRoll}
83+
role="button"
84+
tabIndex={0}
85+
>
8086
roll the dice
8187
</DiceRollButton>
8288
)}

‎website/src/data/components/bar/props.js

+3-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import { svgDefaultProps } from '@nivo/bar'
102
import {
113
themeProperty,
@@ -598,6 +590,9 @@ const props = [
598590
description: `
599591
If enabled, focusing will also reveal the tooltip if \`isInteractive\` is \`true\`,
600592
when a bar gains focus and hide it on blur.
593+
594+
Also note that if this option is enabled, focusing a bar will reposition the tooltip
595+
at a fixed location.
601596
`,
602597
type: 'boolean',
603598
controlType: 'switch',

0 commit comments

Comments
 (0)
Please sign in to comment.