Skip to content

Commit 8f8c123

Browse files
authored
Merge pull request #290 from caryaharper/create-type-options-for-button-ref
Generically type ButtonProps
2 parents b086d98 + 08c6d54 commit 8f8c123

File tree

3 files changed

+31
-9
lines changed

3 files changed

+31
-9
lines changed

src/use-dropdown-menu.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
import React, { useState, useRef, createRef, useEffect, useMemo } from 'react';
33

44
// Create interface for button properties
5-
interface ButtonProps
5+
interface ButtonProps<ButtonElement extends HTMLElement>
66
extends Pick<
7-
React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,
7+
React.DetailedHTMLProps<React.ButtonHTMLAttributes<ButtonElement>, ButtonElement>,
88
'onKeyDown' | 'onClick' | 'tabIndex' | 'role' | 'aria-haspopup' | 'aria-expanded'
99
> {
10-
ref: React.RefObject<HTMLButtonElement>;
10+
ref: React.RefObject<ButtonElement>;
1111
}
1212

1313
// A custom Hook that abstracts away the listeners/controls for dropdown menus
1414
export interface DropdownMenuOptions {
1515
disableFocusFirstItemOnClick?: boolean;
1616
}
1717

18-
interface DropdownMenuResponse {
19-
readonly buttonProps: ButtonProps;
18+
interface DropdownMenuResponse<ButtonElement extends HTMLElement> {
19+
readonly buttonProps: ButtonProps<ButtonElement>;
2020
readonly itemProps: {
2121
onKeyDown: (e: React.KeyboardEvent<HTMLAnchorElement>) => void;
2222
tabIndex: number;
@@ -28,15 +28,18 @@ interface DropdownMenuResponse {
2828
readonly moveFocus: (itemIndex: number) => void;
2929
}
3030

31-
export default function useDropdownMenu(itemCount: number, options?: DropdownMenuOptions): DropdownMenuResponse {
31+
export default function useDropdownMenu<ButtonElement extends HTMLElement = HTMLButtonElement>(
32+
itemCount: number,
33+
options?: DropdownMenuOptions
34+
): DropdownMenuResponse<ButtonElement> {
3235
// Use state
3336
const [isOpen, setIsOpen] = useState<boolean>(false);
3437
const currentFocusIndex = useRef<number | null>(null);
3538
const firstRun = useRef(true);
3639
const clickedOpen = useRef(false);
3740

3841
// Create refs
39-
const buttonRef = useRef<HTMLButtonElement>(null);
42+
const buttonRef = useRef<ButtonElement>(null);
4043
const itemRefs = useMemo<React.RefObject<HTMLAnchorElement>[]>(
4144
() => Array.from({ length: itemCount }, () => createRef<HTMLAnchorElement>()),
4245
[itemCount]
@@ -216,7 +219,7 @@ export default function useDropdownMenu(itemCount: number, options?: DropdownMen
216219
};
217220

218221
// Define spreadable props for button and items
219-
const buttonProps: ButtonProps = {
222+
const buttonProps: ButtonProps<ButtonElement> = {
220223
onKeyDown: buttonListener,
221224
onClick: buttonListener,
222225
tabIndex: 0,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
title: Type parameters
3+
---
4+
5+
You can customize the behavior with optional type parameters.
6+
7+
Type constraint | Default | Possible values
8+
:--- | :--- | :---
9+
`HTMLElement` | `HTMLButtonElement` | Any type that extends `HTMLElement`
10+
11+
```tsx
12+
const { buttonProps, itemProps, isOpen } = useDropdownMenu<HTMLDivElement>(3);
13+
```
14+
15+
```tsx
16+
<div {...buttonProps} id='menu-button'>
17+
Example
18+
</div>
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', 'design/options'],
4+
Design: ['design/return-object', 'design/accessibility', 'design/options', 'design/type-parameters'],
55
},
66
};

0 commit comments

Comments
 (0)