/
A11yContext.tsx
117 lines (102 loc) · 3.07 KB
/
A11yContext.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import * as React from 'react';
import { themes, convert } from '@storybook/theming';
import { Result } from 'axe-core';
import { useChannel, useStorybookState, useAddonState } from '@storybook/api';
import { STORY_CHANGED, STORY_RENDERED } from '@storybook/core-events';
import { ADDON_ID, EVENTS } from '../constants';
export interface Results {
passes: Result[];
violations: Result[];
incomplete: Result[];
}
interface A11yContextStore {
results: Results;
setResults: (results: Results) => void;
highlighted: string[];
toggleHighlight: (target: string[], highlight: boolean) => void;
clearHighlights: () => void;
tab: number;
setTab: (index: number) => void;
}
const colorsByType = [
convert(themes.light).color.negative, // VIOLATION,
convert(themes.light).color.positive, // PASS,
convert(themes.light).color.warning, // INCOMPLETION,
];
export const A11yContext = React.createContext<A11yContextStore>({
results: {
passes: [],
incomplete: [],
violations: [],
},
setResults: () => {},
highlighted: [],
toggleHighlight: () => {},
clearHighlights: () => {},
tab: 0,
setTab: () => {},
});
interface A11yContextProviderProps {
active: boolean;
}
const defaultResult = {
passes: [],
incomplete: [],
violations: [],
};
export const A11yContextProvider: React.FC<A11yContextProviderProps> = ({ active, ...props }) => {
const [results, setResults] = useAddonState<Results>(ADDON_ID, defaultResult);
const [tab, setTab] = React.useState(0);
const [highlighted, setHighlighted] = React.useState<string[]>([]);
const { storyId } = useStorybookState();
const handleToggleHighlight = React.useCallback((target: string[], highlight: boolean) => {
setHighlighted((prevHighlighted) =>
highlight
? [...prevHighlighted, ...target]
: prevHighlighted.filter((t) => !target.includes(t))
);
}, []);
const handleRun = (renderedStoryId: string) => {
emit(EVENTS.REQUEST, renderedStoryId);
};
const handleClearHighlights = React.useCallback(() => setHighlighted([]), []);
const handleSetTab = React.useCallback((index: number) => {
handleClearHighlights();
setTab(index);
}, []);
const handleReset = React.useCallback(() => {
setTab(0);
setResults(defaultResult);
// Highlights is cleared by a11yHighlights.ts
}, []);
const emit = useChannel({
[STORY_RENDERED]: handleRun,
[STORY_CHANGED]: handleReset,
});
React.useEffect(() => {
emit(EVENTS.HIGHLIGHT, { elements: highlighted, color: colorsByType[tab] });
}, [highlighted, tab]);
React.useEffect(() => {
if (active) {
handleRun(storyId);
} else {
handleClearHighlights();
}
}, [active, handleClearHighlights, emit, storyId]);
if (!active) return null;
return (
<A11yContext.Provider
value={{
results,
setResults,
highlighted,
toggleHighlight: handleToggleHighlight,
clearHighlights: handleClearHighlights,
tab,
setTab: handleSetTab,
}}
{...props}
/>
);
};
export const useA11yContext = () => React.useContext(A11yContext);