Skip to content

Commit 3d4799d

Browse files
authored
[ally] Add a11y controls (ymatsakova) (#368)
1 parent 568d33b commit 3d4799d

File tree

9 files changed

+73
-6
lines changed

9 files changed

+73
-6
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "4.5.7",
2+
"version": "4.5.8",
33
"license": "MIT",
44
"main": "dist/index.js",
55
"typings": "dist/index.d.ts",

src/components/body/EmojiCategory.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
display: flex;
1919
align-items: center;
2020
font-weight: bold;
21+
font-size: 16px;
22+
margin: 0;
2123
text-transform: capitalize;
2224
backdrop-filter: blur(3px);
2325
padding: var(--epr-category-label-padding);

src/components/body/EmojiCategory.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function EmojiCategory({
3333
data-name={category}
3434
aria-label={categoryName}
3535
>
36-
<div className={ClassNames.label}>{categoryName}</div>
36+
<h2 className={ClassNames.label}>{categoryName}</h2>
3737
<div className={ClassNames.categoryContent}>{children}</div>
3838
</li>
3939
);

src/components/header/Search.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
min-width: 0;
55
}
66

7+
.EmojiPickerReact .epr-search-container .epr-status-visually-hidden {
8+
clip: rect(0 0 0 0);
9+
clip-path: inset(50%);
10+
height: 1px;
11+
overflow: hidden;
12+
position: absolute;
13+
white-space: nowrap;
14+
width: 1px;
15+
}
16+
717
.EmojiPickerReact .epr-search-container input.epr-search {
818
outline: none;
919
transition: all 0.2s ease-in-out;

src/components/header/Search.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function Search() {
4747
const clearSearch = useClearSearch();
4848
const placeholder = useSearchPlaceHolderConfig();
4949
const autoFocus = useAutoFocusSearchConfig();
50-
const { onChange } = useFilter();
50+
const { statusSearchResults, searchTerm, onChange } = useFilter();
5151

5252
const input = SearchInputRef?.current;
5353
const value = input?.value;
@@ -62,6 +62,7 @@ export function Search() {
6262
onFocus={closeAllOpenToggles}
6363
className="epr-search"
6464
type="text"
65+
aria-controls='epr-search-id'
6566
placeholder={placeholder}
6667
onChange={event => {
6768
setInc(inc + 1);
@@ -71,6 +72,17 @@ export function Search() {
7172
}}
7273
ref={SearchInputRef}
7374
/>
75+
{searchTerm ? (
76+
<div
77+
role='status'
78+
className={clsx('epr-status-search-results', 'epr-status-visually-hidden')}
79+
aria-live='polite'
80+
id='epr-search-id'
81+
aria-atomic='true'
82+
>
83+
{statusSearchResults}
84+
</div>
85+
) : null}
7486
<div className="epr-icn-search" />
7587
<Button
7688
className={clsx('epr-btn-clear-search', 'epr-visible-on-search-only')}

src/components/navigation/CategoryNavigation.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,36 @@ export function CategoryNavigation() {
2727
const hideCustomCategory = useShouldHideCustomEmojis();
2828

2929
return (
30-
<div className="epr-category-nav" ref={CategoryNavigationRef}>
30+
<div
31+
className='epr-category-nav'
32+
role='tablist'
33+
aria-label='Category navigation'
34+
id='epr-category-nav-id'
35+
ref={CategoryNavigationRef}
36+
>
3137
{categoriesConfig.map(categoryConfig => {
3238
const category = categoryFromCategoryConfig(categoryConfig);
39+
const isActiveCategory = category === activeCategory;
3340

3441
if (isCustomCategory(categoryConfig) && hideCustomCategory) {
3542
return null;
3643
}
3744

3845
return (
3946
<Button
40-
tabIndex={isSearchMode ? -1 : 0}
47+
tabIndex={(isSearchMode || isActiveCategory) ? -1 : 0}
4148
className={clsx('epr-cat-btn', `epr-icn-${category}`, {
42-
[ClassNames.active]: category === activeCategory
49+
[ClassNames.active]: isActiveCategory
4350
})}
4451
key={category}
4552
onClick={() => {
4653
setActiveCategory(category);
4754
scrollCategoryIntoView(category);
4855
}}
4956
aria-label={categoryNameFromCategoryConfig(categoryConfig)}
57+
aria-selected={isActiveCategory}
58+
role='tab'
59+
aria-controls='epr-category-nav-id'
5060
/>
5161
);
5262
})}

src/config/config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ import { CustomEmoji } from './customEmojiConfig';
2222
const KNOWN_FAILING_EMOJIS = ['2640-fe0f', '2642-fe0f', '2695-fe0f'];
2323

2424
export const DEFAULT_SEARCH_PLACEHOLDER = 'Search';
25+
export const SEARCH_RESULTS_NO_RESULTS_FOUND = 'No results found';
26+
export const SEARCH_RESULTS_SUFFIX =
27+
' found. Use up and down arrow keys to navigate.';
28+
export const SEARCH_RESULTS_ONE_RESULT_FOUND =
29+
'1 result' + SEARCH_RESULTS_SUFFIX;
30+
export const SEARCH_RESULTS_MULTIPLE_RESULTS_FOUND =
31+
'%n results' + SEARCH_RESULTS_SUFFIX;
2532

2633
export function mergeConfig(
2734
userConfig: PickerConfig = {}

src/config/useConfig.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import {
1111
import { CategoriesConfig } from './categoryConfig';
1212
import {
1313
DEFAULT_SEARCH_PLACEHOLDER,
14+
SEARCH_RESULTS_NO_RESULTS_FOUND,
15+
SEARCH_RESULTS_ONE_RESULT_FOUND,
16+
SEARCH_RESULTS_MULTIPLE_RESULTS_FOUND,
1417
PickerDimensions,
1518
PreviewConfig
1619
} from './config';
@@ -126,3 +129,16 @@ function getDimension(dimensionConfig: PickerDimensions): PickerDimensions {
126129
? `${dimensionConfig}px`
127130
: dimensionConfig;
128131
}
132+
133+
export function useSearchResultsConfig(searchResultsCount: number): string {
134+
const hasResults = searchResultsCount > 0;
135+
const isPlural = searchResultsCount > 1;
136+
137+
if (hasResults) {
138+
return isPlural ?
139+
SEARCH_RESULTS_MULTIPLE_RESULTS_FOUND.replace('%n', searchResultsCount.toString())
140+
: SEARCH_RESULTS_ONE_RESULT_FOUND;
141+
}
142+
143+
return SEARCH_RESULTS_NO_RESULTS_FOUND;
144+
}

src/hooks/useFilter.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { DataEmoji } from '../dataUtils/DataTypes';
1212
import { emojiNames } from '../dataUtils/emojiSelectors';
1313

1414
import { useFocusSearchInput } from './useFocus';
15+
import { useSearchResultsConfig } from '../config/useConfig';
1516

1617
function useSetFilterRef() {
1718
const filterRef = useFilterRef();
@@ -63,11 +64,13 @@ export function useFilter() {
6364
const applySearch = useApplySearch();
6465

6566
const [searchTerm] = useSearchTermState();
67+
const statusSearchResults = getStatusSearchResults(filterRef.current, searchTerm);
6668

6769
return {
6870
onChange,
6971
searchTerm,
7072
SearchInputRef,
73+
statusSearchResults,
7174
};
7275

7376
function onChange(inputValue: string) {
@@ -183,3 +186,10 @@ export function getNormalizedSearchTerm(str: string): string {
183186

184187
return str.trim().toLowerCase();
185188
}
189+
190+
function getStatusSearchResults(filterState: FilterState, searchTerm: string): string {
191+
if (!filterState?.[searchTerm]) return '';
192+
193+
const searchResultsCount = Object.entries(filterState?.[searchTerm])?.length || 0;
194+
return useSearchResultsConfig(searchResultsCount);
195+
}

0 commit comments

Comments
 (0)