Skip to content

Commit

Permalink
Merge pull request #1330 from chanzuckerberg/jlee/dropdownFocusItemOn…
Browse files Browse the repository at this point in the history
…Click

chore(dropdownmenu, textfield, inputfield): clean up types and onclick, forward ref
  • Loading branch information
jinlee93 authored Oct 7, 2022
2 parents 42d2e9f + c1c8290 commit fd5b65d
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 145 deletions.
55 changes: 22 additions & 33 deletions src/components/ButtonDropdown/ButtonDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import clsx from 'clsx';
import React, { ReactNode } from 'react';
import styles from './ButtonDropdown.module.css';
import { DropdownMenu } from '../..';
import { ESCAPE_KEYCODE, TAB_KEYCODE } from '../../util/keycodes';
import type { ClickableStyleProps } from '../ClickableStyle';

export interface Props {
buttonAriaLabel?: string;
/**
* Makes button full width
* Aria label to be attacehd to the dropdown trigger button.
*/
fullWidth?: boolean;
buttonAriaLabel?: string;
/**
* Adds status to the button (e.g. error, success)
*/
Expand All @@ -23,10 +21,28 @@ export interface Props {
* Adds status to the button (e.g. error, success)
*/
buttonVariant?: ClickableStyleProps<'button'>['variant'];
/**
* Prop used to pass in `DropdownMenuItem` child components
*/
children?: ReactNode;
/**
* CSS class names that can be appended to the component.
*/
className?: string;
/**
* Disables the field and prevents editing the contents
*/
disabled?: boolean;
/**
* Prop used to pass in the dropdown menu trigger (dropdownTrigger={<Button />}). This
* allows for maximum flexbility with extending the button and passing in props from the
* outside
*/
dropdownMenuTrigger?: ReactNode;
/**
* Makes button full width
*/
fullWidth?: boolean;
/**
* Determines type of clickable
* - default renders a dropdown menu to the bottom left of the button
Expand All @@ -42,20 +58,6 @@ export interface Props {
* - **reset** The clickable is a reset clickable (resets the form-data to its initial values)
*/
type?: 'button' | 'reset' | 'submit';
/**
* Prop used to pass in the dropdown menu trigger (dropdownTrigger={<Button />}). This
* allows for maximum flexbility with extending the button and passing in props from the
* outside
*/
dropdownMenuTrigger?: ReactNode;
/**
* Prop used to pass in `DropdownMenuItem` child components
*/
children?: ReactNode;
/**
* CSS class names that can be appended to the component.
*/
className?: string;
}

/**
Expand Down Expand Up @@ -130,17 +132,6 @@ export const ButtonDropdown = ({
}
}

/**
* Handle keydown function
*
* If the escape key is struck, close the panel.
*/
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === ESCAPE_KEYCODE || e.key === TAB_KEYCODE) {
closePanel();
}
}

/**
* Toggle accordion panel
*/
Expand All @@ -151,7 +142,7 @@ export const ButtonDropdown = ({
const dropdownMenuTriggerWithProps = React.Children.map(
dropdownMenuTrigger,
// TODO: improve `any` type
(child: any, i: number) => {
(child: any) => {
// Checking isValidElement is the safe way and avoids a typescript
// error too.
if (React.isValidElement(child)) {
Expand Down Expand Up @@ -180,9 +171,7 @@ export const ButtonDropdown = ({
{dropdownMenuTriggerWithProps}
<DropdownMenu
className={styles['button-dropdown__dropdown-menu']}
handleOnClick={closePanel}
handleOnEscDown={(e) => handleKeyDown(e)}
handleOnTabDown={(e) => handleKeyDown(e)}
closeDropdownMenu={closePanel}
isActive={isActiveVar}
>
{children}
Expand Down
37 changes: 18 additions & 19 deletions src/components/DropdownMenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,9 @@ export type Props = {
*/
isActive?: boolean;
/**
* Invoked when the escape key is pressed.
* Callback used to close the dropdown menu. Typically sets active state to false in parent.
*/
handleOnEscDown?: (e: React.KeyboardEvent) => void;
/**
* Invoked when the tab key is pressed.
*/
handleOnTabDown?: (e: React.KeyboardEvent) => void;
/**
* Invoked when the dropdown menu is clicked. Used to close the dropdown menu.
*/
handleOnClick?: MouseEventHandler;
closeDropdownMenu?: () => void;
} & HTMLAttributes<HTMLElement>;

type Refs = {
Expand All @@ -67,10 +59,8 @@ export const DropdownMenuContext = createContext<ContextRefs | null>(null);
export const DropdownMenu: React.FC<Props> = ({
children,
className,
closeDropdownMenu,
isActive,
handleOnClick,
handleOnEscDown,
handleOnTabDown,
...other
}) => {
const refs = useRef<Refs>({
Expand All @@ -92,12 +82,14 @@ export const DropdownMenu: React.FC<Props> = ({
}, [isActive]);

const onKeyDown = (e: KeyboardEvent<HTMLUListElement>) => {
// Calls callback on escape and tab key triggers, typically to close the menu.
if (e.key === ESCAPE_KEYCODE && handleOnEscDown) {
handleOnEscDown(e);
}
if (e.key === TAB_KEYCODE && handleOnTabDown) {
handleOnTabDown(e);
// Calls callback on escape or tab key, typically to close the menu.
if (
(e.key === ESCAPE_KEYCODE || e.key === TAB_KEYCODE) &&
closeDropdownMenu
) {
closeDropdownMenu();
// Prevents focus from moving onto next element in tab order and keeps it on trigger button.
e.preventDefault();
}

// Focus next element with right or down arrow key.
Expand Down Expand Up @@ -143,6 +135,13 @@ export const DropdownMenu: React.FC<Props> = ({
}
};

const handleOnClick: MouseEventHandler = (e) => {
focusIndex = refs.current.list.findIndex((ref) =>
ref.contains(e.target as HTMLElement),
);
focusItem(focusIndex);
};

const componentClassName = clsx(styles['dropdown-menu'], className);
return (
<DropdownMenuContext.Provider value={{ refs }}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/DropdownMenuItem/DropdownMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type DropdownMenuItemProps = {
/**
* On click handler for component
*/
onClick?: () => void;
onClick?: MouseEventHandler;
/**
* On hover handler for component
*/
Expand Down
42 changes: 20 additions & 22 deletions src/components/InputField/InputField.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import clsx from 'clsx';
import React, { ChangeEventHandler } from 'react';
import React, { ChangeEventHandler, forwardRef } from 'react';
import styles from './InputField.module.css';

export type InputFieldProps = React.InputHTMLAttributes<HTMLInputElement> & {
Expand Down Expand Up @@ -121,25 +121,23 @@ export type InputFieldProps = React.InputHTMLAttributes<HTMLInputElement> & {
*
* Text input component for one line of text. For multiple lines, consider the Textarea component.
*/
export const InputField = ({
className,
disabled,
id,
isError,
...other
}: InputFieldProps) => {
const componentClassName = clsx(
styles['input-field'],
isError && styles['error'],
className,
);
export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(
({ className, disabled, id, isError, ...other }, ref) => {
const componentClassName = clsx(
styles['input-field'],
isError && styles['error'],
className,
);

return (
<input
className={componentClassName}
disabled={disabled}
id={id}
{...other}
/>
);
};
return (
<input
className={componentClassName}
disabled={disabled}
id={id}
ref={ref}
{...other}
/>
);
},
);
InputField.displayName = 'InputField';
Loading

0 comments on commit fd5b65d

Please sign in to comment.