Skip to content

Commit

Permalink
Merge pull request #290 from caryaharper/create-type-options-for-butt…
Browse files Browse the repository at this point in the history
…on-ref

Generically type ButtonProps
  • Loading branch information
WesCossick authored Jan 19, 2022
2 parents b086d98 + 08c6d54 commit 8f8c123
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 9 deletions.
19 changes: 11 additions & 8 deletions src/use-dropdown-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@
import React, { useState, useRef, createRef, useEffect, useMemo } from 'react';

// Create interface for button properties
interface ButtonProps
interface ButtonProps<ButtonElement extends HTMLElement>
extends Pick<
React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,
React.DetailedHTMLProps<React.ButtonHTMLAttributes<ButtonElement>, ButtonElement>,
'onKeyDown' | 'onClick' | 'tabIndex' | 'role' | 'aria-haspopup' | 'aria-expanded'
> {
ref: React.RefObject<HTMLButtonElement>;
ref: React.RefObject<ButtonElement>;
}

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

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

export default function useDropdownMenu(itemCount: number, options?: DropdownMenuOptions): DropdownMenuResponse {
export default function useDropdownMenu<ButtonElement extends HTMLElement = HTMLButtonElement>(
itemCount: number,
options?: DropdownMenuOptions
): DropdownMenuResponse<ButtonElement> {
// Use state
const [isOpen, setIsOpen] = useState<boolean>(false);
const currentFocusIndex = useRef<number | null>(null);
const firstRun = useRef(true);
const clickedOpen = useRef(false);

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

// Define spreadable props for button and items
const buttonProps: ButtonProps = {
const buttonProps: ButtonProps<ButtonElement> = {
onKeyDown: buttonListener,
onClick: buttonListener,
tabIndex: 0,
Expand Down
19 changes: 19 additions & 0 deletions website/docs/design/type-parameters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Type parameters
---

You can customize the behavior with optional type parameters.

Type constraint | Default | Possible values
:--- | :--- | :---
`HTMLElement` | `HTMLButtonElement` | Any type that extends `HTMLElement`

```tsx
const { buttonProps, itemProps, isOpen } = useDropdownMenu<HTMLDivElement>(3);
```

```tsx
<div {...buttonProps} id='menu-button'>
Example
</div>
```
2 changes: 1 addition & 1 deletion website/sidebars.js
Original file line number Diff line number Diff line change
@@ -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/options'],
Design: ['design/return-object', 'design/accessibility', 'design/options', 'design/type-parameters'],
},
};

0 comments on commit 8f8c123

Please sign in to comment.