From 4e7f9cf8c8dd905324fe99545c9b09394de31810 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Mon, 2 Aug 2021 18:30:14 -0500 Subject: [PATCH 01/16] added options to parameters --- src/use-dropdown-menu.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/use-dropdown-menu.ts b/src/use-dropdown-menu.ts index 512f10f..92da7e4 100644 --- a/src/use-dropdown-menu.ts +++ b/src/use-dropdown-menu.ts @@ -11,6 +11,10 @@ interface ButtonProps } // A custom Hook that abstracts away the listeners/controls for dropdown menus +interface DropdownMenuOptions { + focusFirstElementOnClick?: boolean; +} + interface DropdownMenuResponse { readonly buttonProps: ButtonProps; readonly itemProps: { @@ -23,7 +27,7 @@ interface DropdownMenuResponse { readonly setIsOpen: React.Dispatch>; } -export default function useDropdownMenu(itemCount: number): DropdownMenuResponse { +export default function useDropdownMenu(itemCount: number, options?: DropdownMenuOptions): DropdownMenuResponse { // Use state const [isOpen, setIsOpen] = useState(false); const currentFocusIndex = useRef(null); From c4ab9186bb77909f0131370e7f1802d27b90820c Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 12:55:30 -0500 Subject: [PATCH 02/16] renamed option and added option logic --- src/use-dropdown-menu.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/use-dropdown-menu.ts b/src/use-dropdown-menu.ts index 92da7e4..fdf9296 100644 --- a/src/use-dropdown-menu.ts +++ b/src/use-dropdown-menu.ts @@ -12,7 +12,7 @@ interface ButtonProps // A custom Hook that abstracts away the listeners/controls for dropdown menus interface DropdownMenuOptions { - focusFirstElementOnClick?: boolean; + disableFocusFirstItemOnClick?: boolean; } interface DropdownMenuResponse { @@ -60,7 +60,7 @@ export default function useDropdownMenu(itemCount: number, options?: DropdownMen } // If the menu is currently open focus on the first item in the menu - if (isOpen && !clickedOpen.current) { + if (isOpen && !options?.disableFocusFirstItemOnClick) { moveFocus(0); } else if (!isOpen) { clickedOpen.current = false; @@ -141,7 +141,10 @@ export default function useDropdownMenu(itemCount: number, options?: DropdownMen setIsOpen(false); } } else { - clickedOpen.current = !isOpen; + if (options?.disableFocusFirstItemOnClick) { + clickedOpen.current = !isOpen; + } + setIsOpen(!isOpen); } }; From 3874e4da1da14214db08255915e7b2e1c9bdfab2 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 13:43:01 -0500 Subject: [PATCH 03/16] exported options type --- src/use-dropdown-menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/use-dropdown-menu.ts b/src/use-dropdown-menu.ts index fdf9296..0048e4f 100644 --- a/src/use-dropdown-menu.ts +++ b/src/use-dropdown-menu.ts @@ -11,7 +11,7 @@ interface ButtonProps } // A custom Hook that abstracts away the listeners/controls for dropdown menus -interface DropdownMenuOptions { +export interface DropdownMenuOptions { disableFocusFirstItemOnClick?: boolean; } From 86f81f7d89e7a656608adaa339febb231a6dfeaa Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 13:43:10 -0500 Subject: [PATCH 04/16] modified existing tests --- src/use-dropdown-menu.test.tsx | 18 +++++++++++------- test-projects/browser/src/app.test.ts | 7 ------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/use-dropdown-menu.test.tsx b/src/use-dropdown-menu.test.tsx index 8a52842..06d39ca 100644 --- a/src/use-dropdown-menu.test.tsx +++ b/src/use-dropdown-menu.test.tsx @@ -1,13 +1,17 @@ // Imports import React, { useState } from 'react'; -import useDropdownMenu from './use-dropdown-menu'; +import useDropdownMenu, { DropdownMenuOptions } from './use-dropdown-menu'; import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; // A mock component for testing the Hook -const TestComponent: React.FC = () => { +interface Props { + options?: DropdownMenuOptions; +} + +const TestComponent: React.FC = ({ options }) => { const [itemCount, setItemCount] = useState(4); - const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(itemCount); + const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(itemCount, options); const clickHandlers: (() => void)[] = [(): void => console.log('Item one clicked'), (): void => setIsOpen(false)]; @@ -79,8 +83,8 @@ it('Moves the focus to the first menu item after pressing space while focused on expect(screen.getByText('1 Item')).toHaveFocus(); }); -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', () => { - render(); +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', () => { + render(); userEvent.click(screen.getByText('Primary')); @@ -91,8 +95,8 @@ it('Moves the focus to the first menu item after clicking the menu to open it, t expect(screen.getByText('1 Item')).toHaveFocus(); }); -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', () => { - render(); +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', () => { + render(); userEvent.click(screen.getByText('Primary')); diff --git a/test-projects/browser/src/app.test.ts b/test-projects/browser/src/app.test.ts index 9465d72..56746cf 100644 --- a/test-projects/browser/src/app.test.ts +++ b/test-projects/browser/src/app.test.ts @@ -35,13 +35,6 @@ it('Has the correct page title', async () => { await expect(page.title()).resolves.toMatch('Browser'); }); -it('Leaves focus on the button after clicking it', async () => { - await page.click('#menu-button'); - await menuOpen(); - - expect(await currentFocusID()).toBe('menu-button'); -}); - it('Focuses on the menu button after pressing escape', async () => { await page.focus('#menu-button'); await page.keyboard.down('Enter'); From 8c1ae6f77494d8a28131cf13b0260acd5cf8aac7 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 13:43:44 -0500 Subject: [PATCH 05/16] altered demo to describe default behavior with some added styles --- website/src/css/custom.css | 11 ++++++++++- website/src/pages/demo.tsx | 7 +------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 6603d4f..29c6f41 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -102,7 +102,7 @@ text-decoration: none; cursor: pointer; outline: none; - border: 0.1rem solid transparent; + border: 0.1rem solid; border-radius: 0.2rem; } @@ -110,10 +110,19 @@ text-decoration: underline; } +/* Fallback for browsers that do not support :focus-visible. */ .demo-menu a:focus { border-color: #3a8eb8; } +.demo-menu a:focus:not(:focus-visible) { + border-color: transparent; +} + +.demo-menu a:focus-visible { + border-color: #3a8eb8; +} + .demo-menu a svg { color: #777; margin-right: 0.5rem; diff --git a/website/src/pages/demo.tsx b/website/src/pages/demo.tsx index e0a0ddc..755cae7 100644 --- a/website/src/pages/demo.tsx +++ b/website/src/pages/demo.tsx @@ -65,12 +65,7 @@ const Demo: React.FC = () => { The menu can be revealed by clicking the button, or by focusing the button and pressing enter / space -
  • If the menu is revealed with the keyboard, the first menu item is automatically focused
  • -
  • - If the menu is revealed with the mouse, the first menu item can be focused by pressing tab / arrow - down -
  • -
  • If the menu is revealed with the mouse, the menu can be be closed by pressing escape
  • +
  • When the menu is revealed, the first menu item is automatically focused
  • Once focus is in the menu… From e68d7ccec98163d18c7e46b2c404281a88cc1891 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 13:50:15 -0500 Subject: [PATCH 06/16] added jest test --- src/use-dropdown-menu.test.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/use-dropdown-menu.test.tsx b/src/use-dropdown-menu.test.tsx index 06d39ca..8fd4b70 100644 --- a/src/use-dropdown-menu.test.tsx +++ b/src/use-dropdown-menu.test.tsx @@ -83,6 +83,14 @@ it('Moves the focus to the first menu item after pressing space while focused on expect(screen.getByText('1 Item')).toHaveFocus(); }); +it('Moves the focus to the first menu item after clicking the menu to open it', () => { + render(); + + userEvent.click(screen.getByText('Primary')); + + expect(screen.getByText('1 Item')).toHaveFocus(); +}); + 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', () => { render(); From 072c957f3455550a27dc48fa3764fe069b670401 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 14:07:46 -0500 Subject: [PATCH 07/16] replaced puppeteer test --- test-projects/browser/src/app.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test-projects/browser/src/app.test.ts b/test-projects/browser/src/app.test.ts index 56746cf..cbc21f2 100644 --- a/test-projects/browser/src/app.test.ts +++ b/test-projects/browser/src/app.test.ts @@ -35,6 +35,13 @@ it('Has the correct page title', async () => { await expect(page.title()).resolves.toMatch('Browser'); }); +it('Focuses the first menu item when menu button is clicked', async () => { + await page.click('#menu-button'); + await menuOpen(); + + expect(await currentFocusID()).toBe('menu-item-1'); +}); + it('Focuses on the menu button after pressing escape', async () => { await page.focus('#menu-button'); await page.keyboard.down('Enter'); From 39a6802a5cdadf956c4d5709e27a6c7d8ee93156 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 14:13:20 -0500 Subject: [PATCH 08/16] added and modified documentation --- website/docs/design/options.md | 13 +++++++++++++ website/docs/getting-started/using.md | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 website/docs/design/options.md diff --git a/website/docs/design/options.md b/website/docs/design/options.md new file mode 100644 index 0000000..3557c80 --- /dev/null +++ b/website/docs/design/options.md @@ -0,0 +1,13 @@ +--- +title: Options +--- + +This Hook takes in the following options: + +```ts +{ + disableFocusFirstItemOnClick?: boolean; +} +``` + +- **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. \ No newline at end of file diff --git a/website/docs/getting-started/using.md b/website/docs/getting-started/using.md index 1f69cc6..af90912 100644 --- a/website/docs/getting-started/using.md +++ b/website/docs/getting-started/using.md @@ -2,10 +2,10 @@ title: Using --- -To use the Hook, first call it, telling it how many items your menu will have: +To use the Hook, first call it, telling it how many items your menu will have, and specify any options you want to pass: ```jsx -const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(numberOfItems); +const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(numberOfItems, options); ``` Take the `buttonProps` object and spread it onto a button: From be718ccd73fdd5b731a7526223de422871da3a19 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 14:13:39 -0500 Subject: [PATCH 09/16] formatting --- src/use-dropdown-menu.test.tsx | 4 ++-- src/use-dropdown-menu.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/use-dropdown-menu.test.tsx b/src/use-dropdown-menu.test.tsx index 8fd4b70..7bcf238 100644 --- a/src/use-dropdown-menu.test.tsx +++ b/src/use-dropdown-menu.test.tsx @@ -92,7 +92,7 @@ it('Moves the focus to the first menu item after clicking the menu to open it', }); 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', () => { - render(); + render(); userEvent.click(screen.getByText('Primary')); @@ -104,7 +104,7 @@ it('Moves the focus to the first menu item after clicking the menu to open it, t }); 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', () => { - render(); + render(); userEvent.click(screen.getByText('Primary')); diff --git a/src/use-dropdown-menu.ts b/src/use-dropdown-menu.ts index 0048e4f..973105d 100644 --- a/src/use-dropdown-menu.ts +++ b/src/use-dropdown-menu.ts @@ -144,7 +144,7 @@ export default function useDropdownMenu(itemCount: number, options?: DropdownMen if (options?.disableFocusFirstItemOnClick) { clickedOpen.current = !isOpen; } - + setIsOpen(!isOpen); } }; From bad61e316ce1bcc0c099e163b72cd9f9422eb660 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 14:13:59 -0500 Subject: [PATCH 10/16] bumped major version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b458fc6..a5b42e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-accessible-dropdown-menu-hook", - "version": "2.3.1", + "version": "3.3.1", "description": "A simple Hook for creating fully accessible dropdown menus in React", "main": "dist/use-dropdown-menu.js", "types": "dist/use-dropdown-menu.d.ts", From 9ceb3a1b1ce53f208057378076d6a1fbb8c97a2e Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 14:22:05 -0500 Subject: [PATCH 11/16] fixed focus style --- website/src/css/custom.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 29c6f41..7c30838 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -102,7 +102,7 @@ text-decoration: none; cursor: pointer; outline: none; - border: 0.1rem solid; + border: 0.1rem solid transparent; border-radius: 0.2rem; } From 721a6594709d6b099c63ccf50e53dff60d4f25a1 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 14:33:26 -0500 Subject: [PATCH 12/16] added sidebar link --- website/sidebars.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/sidebars.js b/website/sidebars.js index 3b22f8f..65a5011 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -1,6 +1,6 @@ module.exports = { default: { 'Getting started': ['getting-started/install', 'getting-started/import', 'getting-started/using'], - Design: ['design/return-object', 'design/accessibility'], + Design: ['design/return-object', 'design/accessibility', 'design/options'], }, }; From 5b1fa1041d87211fcdfb16eb386865dad39c6f6b Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 18:09:15 -0500 Subject: [PATCH 13/16] reverted using --- website/docs/getting-started/using.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/getting-started/using.md b/website/docs/getting-started/using.md index af90912..1f69cc6 100644 --- a/website/docs/getting-started/using.md +++ b/website/docs/getting-started/using.md @@ -2,10 +2,10 @@ title: Using --- -To use the Hook, first call it, telling it how many items your menu will have, and specify any options you want to pass: +To use the Hook, first call it, telling it how many items your menu will have: ```jsx -const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(numberOfItems, options); +const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(numberOfItems); ``` Take the `buttonProps` object and spread it onto a button: From 06fe981a5e268571dc4f0e56cdc297d454280d01 Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 18:09:25 -0500 Subject: [PATCH 14/16] refactored options docs --- website/docs/design/options.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/website/docs/design/options.md b/website/docs/design/options.md index 3557c80..83b7060 100644 --- a/website/docs/design/options.md +++ b/website/docs/design/options.md @@ -2,12 +2,12 @@ title: Options --- -This Hook takes in the following options: +You can customize the behavior with options, passed as the second argument. -```ts -{ - disableFocusFirstItemOnClick?: boolean; -} -``` +Option | Default | Possible values +:--- | :--- | :--- +`disableFocusFirstItemOnClick` | `false` | `boolean` -- **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. \ No newline at end of file +Option | Explanation +:--- | :--- +`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. \ No newline at end of file From 27ee90ceee28c9b8737924311f61a1814cd50cfa Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Tue, 3 Aug 2021 18:21:30 -0500 Subject: [PATCH 15/16] fixed version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a5b42e4..5280c29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-accessible-dropdown-menu-hook", - "version": "3.3.1", + "version": "3.0.0", "description": "A simple Hook for creating fully accessible dropdown menus in React", "main": "dist/use-dropdown-menu.js", "types": "dist/use-dropdown-menu.d.ts", From e026cfc470c4e8d6430d8a0cafa15b469fd43cdb Mon Sep 17 00:00:00 2001 From: Cory Harper Date: Thu, 12 Aug 2021 15:41:16 -0500 Subject: [PATCH 16/16] added code example --- website/docs/design/options.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/website/docs/design/options.md b/website/docs/design/options.md index 83b7060..12a8d95 100644 --- a/website/docs/design/options.md +++ b/website/docs/design/options.md @@ -10,4 +10,10 @@ Option | Default | Possible values Option | Explanation :--- | :--- -`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. \ No newline at end of file +`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. + +```js +const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(numberOfItems, { + disableFocusFirstItemOnClick: true, +}); +``` \ No newline at end of file