Skip to content

Commit 9401baa

Browse files
authored
Merge pull request #279 from sparksuite/disable-focus-first-option
Option to disable focusing first menu item on click
2 parents 4385c5e + acd63d9 commit 9401baa

File tree

8 files changed

+61
-19
lines changed

8 files changed

+61
-19
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-accessible-dropdown-menu-hook",
3-
"version": "2.3.1",
3+
"version": "3.0.0",
44
"description": "A simple Hook for creating fully accessible dropdown menus in React",
55
"main": "dist/use-dropdown-menu.js",
66
"types": "dist/use-dropdown-menu.d.ts",

src/use-dropdown-menu.test.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
// Imports
22
import React, { useState } from 'react';
3-
import useDropdownMenu from './use-dropdown-menu';
3+
import useDropdownMenu, { DropdownMenuOptions } from './use-dropdown-menu';
44
import { fireEvent, render, screen } from '@testing-library/react';
55
import userEvent from '@testing-library/user-event';
66

77
// A mock component for testing the Hook
8-
const TestComponent: React.FC = () => {
8+
interface Props {
9+
options?: DropdownMenuOptions;
10+
}
11+
12+
const TestComponent: React.FC<Props> = ({ options }) => {
913
const [itemCount, setItemCount] = useState(4);
10-
const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(itemCount);
14+
const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(itemCount, options);
1115

1216
const clickHandlers: (() => void)[] = [(): void => console.log('Item one clicked'), (): void => setIsOpen(false)];
1317

@@ -79,20 +83,28 @@ it('Moves the focus to the first menu item after pressing space while focused on
7983
expect(screen.getByText('1 Item')).toHaveFocus();
8084
});
8185

82-
it('Moves the focus to the first menu item after clicking the menu to open it, then pressing tab while focused on the menu button', () => {
86+
it('Moves the focus to the first menu item after clicking the menu to open it', () => {
8387
render(<TestComponent />);
8488

8589
userEvent.click(screen.getByText('Primary'));
8690

91+
expect(screen.getByText('1 Item')).toHaveFocus();
92+
});
93+
94+
it('Moves the focus to the first menu item after clicking the menu to open it, then pressing tab while focused on the menu button, if `disableFocusFirstItemOnClick` is specified', () => {
95+
render(<TestComponent options={{ disableFocusFirstItemOnClick: true }} />);
96+
97+
userEvent.click(screen.getByText('Primary'));
98+
8799
expect(screen.getByText('Primary')).toHaveFocus();
88100

89101
userEvent.tab();
90102

91103
expect(screen.getByText('1 Item')).toHaveFocus();
92104
});
93105

94-
it('Moves the focus to the first menu item after clicking the menu to open it, then pressing arrow down while focused on the menu button', () => {
95-
render(<TestComponent />);
106+
it('Moves the focus to the first menu item after clicking the menu to open it, then pressing arrow down while focused on the menu button, if `disableFocusFirstItemOnClick` is specified', () => {
107+
render(<TestComponent options={{ disableFocusFirstItemOnClick: true }} />);
96108

97109
userEvent.click(screen.getByText('Primary'));
98110

src/use-dropdown-menu.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ interface ButtonProps
1111
}
1212

1313
// A custom Hook that abstracts away the listeners/controls for dropdown menus
14+
export interface DropdownMenuOptions {
15+
disableFocusFirstItemOnClick?: boolean;
16+
}
17+
1418
interface DropdownMenuResponse {
1519
readonly buttonProps: ButtonProps;
1620
readonly itemProps: {
@@ -23,7 +27,7 @@ interface DropdownMenuResponse {
2327
readonly setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
2428
}
2529

26-
export default function useDropdownMenu(itemCount: number): DropdownMenuResponse {
30+
export default function useDropdownMenu(itemCount: number, options?: DropdownMenuOptions): DropdownMenuResponse {
2731
// Use state
2832
const [isOpen, setIsOpen] = useState<boolean>(false);
2933
const currentFocusIndex = useRef<number | null>(null);
@@ -56,7 +60,7 @@ export default function useDropdownMenu(itemCount: number): DropdownMenuResponse
5660
}
5761

5862
// If the menu is currently open focus on the first item in the menu
59-
if (isOpen && !clickedOpen.current) {
63+
if (isOpen && !options?.disableFocusFirstItemOnClick) {
6064
moveFocus(0);
6165
} else if (!isOpen) {
6266
clickedOpen.current = false;
@@ -137,7 +141,10 @@ export default function useDropdownMenu(itemCount: number): DropdownMenuResponse
137141
setIsOpen(false);
138142
}
139143
} else {
140-
clickedOpen.current = !isOpen;
144+
if (options?.disableFocusFirstItemOnClick) {
145+
clickedOpen.current = !isOpen;
146+
}
147+
141148
setIsOpen(!isOpen);
142149
}
143150
};

test-projects/browser/src/app.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ it('Has the correct page title', async () => {
3535
await expect(page.title()).resolves.toMatch('Browser');
3636
});
3737

38-
it('Leaves focus on the button after clicking it', async () => {
38+
it('Focuses the first menu item when menu button is clicked', async () => {
3939
await page.click('#menu-button');
4040
await menuOpen();
4141

42-
expect(await currentFocusID()).toBe('menu-button');
42+
expect(await currentFocusID()).toBe('menu-item-1');
4343
});
4444

4545
it('Focuses on the menu button after pressing escape', async () => {

website/docs/design/options.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
title: Options
3+
---
4+
5+
You can customize the behavior with options, passed as the second argument.
6+
7+
Option | Default | Possible values
8+
:--- | :--- | :---
9+
`disableFocusFirstItemOnClick` | `false` | `boolean`
10+
11+
Option | Explanation
12+
:--- | :---
13+
`disableFocusFirstItemOnClick` | If specified as `true` the default behavior of focusing the first menu item on click will be disabled. The menu button will instead retain focus.
14+
15+
```js
16+
const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(numberOfItems, {
17+
disableFocusFirstItemOnClick: true,
18+
});
19+
```

website/sidebars.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
22
default: {
33
'Getting started': ['getting-started/install', 'getting-started/import', 'getting-started/using'],
4-
Design: ['design/return-object', 'design/accessibility'],
4+
Design: ['design/return-object', 'design/accessibility', 'design/options'],
55
},
66
};

website/src/css/custom.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,19 @@
110110
text-decoration: underline;
111111
}
112112

113+
/* Fallback for browsers that do not support :focus-visible. */
113114
.demo-menu a:focus {
114115
border-color: #3a8eb8;
115116
}
116117

118+
.demo-menu a:focus:not(:focus-visible) {
119+
border-color: transparent;
120+
}
121+
122+
.demo-menu a:focus-visible {
123+
border-color: #3a8eb8;
124+
}
125+
117126
.demo-menu a svg {
118127
color: #777;
119128
margin-right: 0.5rem;

website/src/pages/demo.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,7 @@ const Demo: React.FC = () => {
6565
The menu can be revealed by clicking the button, or by focusing the button and pressing enter /
6666
space
6767
</li>
68-
<li>If the menu is revealed with the keyboard, the first menu item is automatically focused</li>
69-
<li>
70-
If the menu is revealed with the mouse, the first menu item can be focused by pressing tab / arrow
71-
down
72-
</li>
73-
<li>If the menu is revealed with the mouse, the menu can be be closed by pressing escape</li>
68+
<li>When the menu is revealed, the first menu item is automatically focused</li>
7469
<li>
7570
<em>Once focus is in the menu…</em>
7671

0 commit comments

Comments
 (0)