Skip to content

Commit

Permalink
feat: clean up dropdown props
Browse files Browse the repository at this point in the history
  • Loading branch information
kyletsang committed Jul 23, 2021
1 parent e0a993b commit 5b4cf8a
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 115 deletions.
111 changes: 52 additions & 59 deletions src/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import qsa from 'dom-helpers/querySelectorAll';
import addEventListener from 'dom-helpers/addEventListener';
import { useCallback, useRef, useEffect, useMemo, useContext } from 'react';
import * as React from 'react';
import PropTypes from 'prop-types';
import { useUncontrolledProp } from 'uncontrollable';
import usePrevious from '@restart/hooks/usePrevious';
import useForceUpdate from '@restart/hooks/useForceUpdate';
Expand All @@ -25,7 +24,6 @@ import SelectableContext from './SelectableContext';
import { SelectCallback } from './types';
import { dataAttr } from './DataKey';
import { Placement } from './usePopper';
import { placements } from './popper';

export type {
DropdownMenuProps,
Expand All @@ -36,91 +34,88 @@ export type {
DropdownItemProps,
};

const propTypes = {
/**
* A render prop that returns the root dropdown element. The `props`
* argument should spread through to an element containing _both_ the
* menu and toggle in order to handle keyboard events for focus management.
*
* @type {Function ({
* props: {
* onKeyDown: (SyntheticEvent) => void,
* },
* }) => React.Element}
*/
children: PropTypes.node,
export interface DropdownInjectedProps {
onKeyDown: React.KeyboardEventHandler;
}

/**
* The PopperJS placement for positioning the Dropdown menu in relation to it's Toggle.
*
* @default 'bottom-start'
*/
placement: PropTypes.oneOf(placements),
export type ToggleEvent = React.SyntheticEvent | KeyboardEvent | MouseEvent;

export interface ToggleMetadata {
source: string | undefined;
originalEvent: ToggleEvent | undefined;
}

export interface DropdownProps {
/**
* Controls the focus behavior for when the Dropdown is opened. Set to
* `true` to always focus the first menu item, `keyboard` to focus only when
* navigating via the keyboard, or `false` to disable completely
* The PopperJS placement for positioning the Dropdown menu in relation to
* its Toggle.
*
* The Default behavior is `false` **unless** the Menu has a `role="menu"`
* where it will default to `keyboard` to match the recommended [ARIA Authoring practices](https://www.w3.org/TR/wai-aria-practices-1.1/#menubutton).
* @default 'bottom-start'
*/
focusFirstItemOnShow: PropTypes.oneOf([false, true, 'keyboard']),
placement?: Placement;

/**
* A css slector string that will return __focusable__ menu items.
* Selectors should be relative to the menu component:
* e.g. ` > li:not('.disabled')`
* Sets the initial visibility of the Dropdown.
*/
itemSelector: PropTypes.string,
defaultShow?: boolean;

/**
* Whether or not the Dropdown is visible.
*
* @controllable onToggle
*/
show: PropTypes.bool,
show?: boolean;

/**
* Sets the initial show position of the Dropdown.
* A callback fired when a DropdownItem has been selected.
*/
defaultShow: PropTypes.bool,
onSelect?: SelectCallback;

/**
* A callback fired when the Dropdown wishes to change visibility. Called with the requested
* `show` value, the DOM event, and the source that fired it: `'click'`,`'keydown'`,`'rootClose'`, or `'select'`.
* A callback fired when the Dropdown wishes to change visibility. Called with
* the requested `show` value, the DOM event, and the source that fired it:
* `'click'`,`'keydown'`,`'rootClose'`, or `'select'`.
*
* ```ts static
* function(
* isOpen: boolean,
* event: SyntheticEvent,
* nextShow: boolean,
* meta: ToggleMetadata,
* ): void
* ```
*
* @controllable show
*/
onToggle: PropTypes.func,
};

export interface DropdownInjectedProps {
onKeyDown: React.KeyboardEventHandler;
}
onToggle?: (nextShow: boolean, meta: ToggleMetadata) => void;

export type ToggleEvent = React.SyntheticEvent | KeyboardEvent | MouseEvent;
/**
* A css selector string that will return __focusable__ menu items.
* Selectors should be relative to the menu component:
* e.g. ` > li:not('.disabled')`
*/
itemSelector?: string;

export interface ToggleMetadata {
source: string | undefined;
originalEvent: ToggleEvent | undefined;
}
/**
* Controls the focus behavior for when the Dropdown is opened. Set to
* `true` to always focus the first menu item, `keyboard` to focus only when
* navigating via the keyboard, or `false` to disable completely
*
* The Default behavior is `false` **unless** the Menu has a `role="menu"`
* where it will default to `keyboard` to match the recommended [ARIA Authoring
* practices](https://www.w3.org/TR/wai-aria-practices-1.1/#menubutton).
*/
focusFirstItemOnShow?: boolean | 'keyboard';

export interface DropdownProps {
placement?: Placement;
defaultShow?: boolean;
show?: boolean;
onSelect?: SelectCallback;
onToggle?: (nextShow: boolean, meta: ToggleMetadata) => void;
itemSelector?: string;
focusFirstItemOnShow?: false | true | 'keyboard';
/**
* A render prop that returns the root dropdown element. The `props`
* argument should spread through to an element containing _both_ the
* menu and toggle in order to handle keyboard events for focus management.
*
* @type {Function ({
* props: {
* onKeyDown: (SyntheticEvent) => void,
* },
* }) => React.Element}
*/
children: React.ReactNode;
}

Expand Down Expand Up @@ -344,8 +339,6 @@ function Dropdown({

Dropdown.displayName = 'Dropdown';

Dropdown.propTypes = propTypes;

Dropdown.Menu = DropdownMenu;
Dropdown.Toggle = DropdownToggle;
Dropdown.Item = DropdownItem;
Expand Down
98 changes: 52 additions & 46 deletions src/DropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { useContext, useRef } from 'react';
import * as React from 'react';
import useCallbackRef from '@restart/hooks/useCallbackRef';
Expand All @@ -11,17 +10,62 @@ import usePopper, {
} from './usePopper';
import useRootClose, { RootCloseOptions } from './useRootClose';
import mergeOptionsWithPopperConfig from './mergeOptionsWithPopperConfig';
import { placements } from './popper';

export interface UseDropdownMenuOptions {
/**
* Enables the Popper.js `flip` modifier, allowing the Dropdown to
* automatically adjust it's placement in case of overlap with the viewport or
* toggle. See the [flip docs](https://popper.js.org/docs/v2/modifiers/flip/)
* for more info.
*/
flip?: boolean;

/**
* Controls the visible state of the menu, generally this is provided by the
* parent `Dropdown` component, but may also be specified as a prop directly.
*/
show?: boolean;

/**
* Use the `fixed` positioning strategy in Popper. This is used if the
* dropdown toggle is in a fixed container.
*/
fixed?: boolean;

/**
* The PopperJS placement for positioning the Dropdown menu in relation to it's Toggle.
* Generally this is provided by the parent `Dropdown` component,
* but may also be specified as a prop directly.
*/
placement?: Placement;

/**
* Whether or not to use Popper for positioning the menu.
*/
usePopper?: boolean;

/**
* Whether or not to add scroll and resize listeners to update menu position.
*
* See the [event listeners docs](https://popper.js.org/docs/v2/modifiers/event-listeners/)
* for more info.
*/
enableEventListeners?: boolean;

/**
* Offset of the dropdown menu from the dropdown toggle. See the
* [offset docs](https://popper.js.org/docs/v2/modifiers/offset/) for more info.
*/
offset?: Offset;

/**
* Override the default event used by RootCloseWrapper.
*/
rootCloseEvent?: RootCloseOptions['clickTrigger'];

/**
* A set of popper options and props passed directly to react-popper's Popper component.
*/
popperConfig?: Omit<UsePopperOptions, 'enabled' | 'placement'>;
}

Expand Down Expand Up @@ -132,10 +176,14 @@ export function useDropdownMenu(options: UseDropdownMenuOptions = {}) {
return [menuProps, metadata] as const;
}

const propTypes = {
const defaultProps = {
usePopper: true,
};

export interface DropdownMenuProps extends UseDropdownMenuOptions {
/**
* A render prop that returns a Menu element. The `props`
* argument should spread through to **a component that can accept a ref**.
* argument should be spread through to **a component that can accept a ref**.
*
* @type {Function ({
* show: boolean,
Expand All @@ -154,47 +202,6 @@ const propTypes = {
* },
* }) => React.Element}
*/
children: PropTypes.func.isRequired,

/**
* Controls the visible state of the menu, generally this is
* provided by the parent `Dropdown` component,
* but may also be specified as a prop directly.
*/
show: PropTypes.bool,

/**
* The PopperJS placement for positioning the Dropdown menu in relation to it's Toggle.
* Generally this is provided by the parent `Dropdown` component,
* but may also be specified as a prop directly.
*/
placement: PropTypes.oneOf(placements),

/**
* Enables the Popper.js `flip` modifier, allowing the Dropdown to
* automatically adjust it's placement in case of overlap with the viewport or toggle.
* Refer to the [flip docs](https://popper.js.org/popper-documentation.html#modifiers..flip.enabled) for more info
*/
flip: PropTypes.bool,

usePopper: PropTypes.oneOf([true, false]),

/**
* A set of popper options and props passed directly to react-popper's Popper component.
*/
popperConfig: PropTypes.object,

/**
* Override the default event used by RootCloseWrapper.
*/
rootCloseEvent: PropTypes.string,
};

const defaultProps = {
usePopper: true,
};

export interface DropdownMenuProps extends UseDropdownMenuOptions {
children: (
props: UserDropdownMenuProps,
meta: UseDropdownMenuMetadata,
Expand All @@ -215,7 +222,6 @@ function DropdownMenu({ children, ...options }: DropdownMenuProps) {

DropdownMenu.displayName = 'DropdownMenu';

DropdownMenu.propTypes = propTypes;
DropdownMenu.defaultProps = defaultProps;

/** @component */
Expand Down
15 changes: 5 additions & 10 deletions src/DropdownToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import PropTypes from 'prop-types';

import { useContext, useCallback } from 'react';
import * as React from 'react';
import { useSSRSafeId } from './ssr';
Expand Down Expand Up @@ -60,26 +58,24 @@ export function useDropdownToggle(): [
return [props, { show, toggle }];
}

const propTypes = {
export interface DropdownToggleProps {
/**
* A render prop that returns a Toggle element. The `props`
* argument should spread through to **a component that can accept a ref**. Use
* the `onToggle` argument to toggle the menu open or closed
*
* @type {Function ({
* show: boolean,
* toggle: (show: boolean) => void,
* props: {
* ref: (?HTMLElement) => void,
* aria-haspopup: true
* aria-expanded: boolean
* },
* meta: {
* show: boolean,
* toggle: (show: boolean) => void,
* }
* }) => React.Element}
*/
children: PropTypes.func.isRequired,
};

export interface DropdownToggleProps {
children: (
props: UseDropdownToggleProps,
meta: UseDropdownToggleMetadata,
Expand All @@ -99,7 +95,6 @@ function DropdownToggle({ children }: DropdownToggleProps) {
}

DropdownToggle.displayName = 'DropdownToggle';
DropdownToggle.propTypes = propTypes;

/** @component */
export default DropdownToggle;

0 comments on commit 5b4cf8a

Please sign in to comment.