Skip to content

Commit 7030465

Browse files
committed
feat(Dropdown)!: migrate to restart/ui
BREAKING CHANGE: remove `onSelect` from DropdownItem
1 parent 1ff7af7 commit 7030465

File tree

8 files changed

+117
-253
lines changed

8 files changed

+117
-253
lines changed

src/Dropdown.tsx

Lines changed: 49 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,21 @@ import classNames from 'classnames';
22
import PropTypes from 'prop-types';
33
import * as React from 'react';
44
import { useContext, useMemo } from 'react';
5-
import BaseDropdown from 'react-overlays/Dropdown';
6-
import { DropDirection } from 'react-overlays/DropdownContext';
5+
import BaseDropdown, {
6+
DropdownProps as BaseDropdownProps,
7+
ToggleMetadata,
8+
} from '@restart/ui/Dropdown';
79
import { useUncontrolled } from 'uncontrollable';
810
import useEventCallback from '@restart/hooks/useEventCallback';
9-
import DropdownContext from './DropdownContext';
11+
import DropdownContext, { DropDirection } from './DropdownContext';
1012
import DropdownItem from './DropdownItem';
1113
import DropdownMenu from './DropdownMenu';
1214
import DropdownToggle from './DropdownToggle';
1315
import InputGroupContext from './InputGroupContext';
14-
import SelectableContext from './SelectableContext';
1516
import { useBootstrapPrefix } from './ThemeProvider';
1617
import createWithBsPrefix from './createWithBsPrefix';
17-
import {
18-
BsPrefixProps,
19-
BsPrefixRefForwardingComponent,
20-
SelectCallback,
21-
} from './helpers';
22-
import { AlignType, alignPropType } from './types';
18+
import { BsPrefixProps, BsPrefixRefForwardingComponent } from './helpers';
19+
import { AlignType, alignPropType, Placement } from './types';
2320

2421
const DropdownHeader = createWithBsPrefix('dropdown-header', {
2522
defaultProps: { role: 'heading' },
@@ -33,19 +30,13 @@ const DropdownItemText = createWithBsPrefix('dropdown-item-text', {
3330
});
3431

3532
export interface DropdownProps
36-
extends BsPrefixProps,
37-
Omit<React.HTMLAttributes<HTMLElement>, 'onSelect'> {
38-
drop?: 'up' | 'start' | 'end' | 'down';
33+
extends BaseDropdownProps,
34+
BsPrefixProps,
35+
Omit<React.HTMLAttributes<HTMLElement>, 'onSelect' | 'children'> {
36+
drop?: DropDirection;
3937
align?: AlignType;
40-
show?: boolean;
4138
flip?: boolean;
42-
onToggle?: (
43-
isOpen: boolean,
44-
event: React.SyntheticEvent,
45-
metadata: { source: 'select' | 'click' | 'rootClose' | 'keydown' },
46-
) => void;
4739
focusFirstItemOnShow?: boolean | 'keyboard';
48-
onSelect?: SelectCallback;
4940
navbar?: boolean;
5041
autoClose?: boolean | 'outside' | 'inside';
5142
}
@@ -156,7 +147,6 @@ const Dropdown: BsPrefixRefForwardingComponent<'div', DropdownProps> =
156147
...props
157148
} = useUncontrolled(pProps, { show: 'onToggle' });
158149

159-
const onSelectCtx = useContext(SelectableContext);
160150
const isInputGroup = useContext(InputGroupContext);
161151
const prefix = useBootstrapPrefix(bsPrefix, 'dropdown');
162152

@@ -174,67 +164,60 @@ const Dropdown: BsPrefixRefForwardingComponent<'div', DropdownProps> =
174164
};
175165

176166
const handleToggle = useEventCallback(
177-
(nextShow, event, source = event.type) => {
167+
(nextShow: boolean, meta: ToggleMetadata) => {
178168
if (
179-
event.currentTarget === document &&
180-
(source !== 'keydown' || event.key === 'Escape')
169+
meta.originalEvent!.currentTarget === document &&
170+
(meta.source !== 'keydown' ||
171+
(meta.originalEvent as any).key === 'Escape')
181172
)
182-
source = 'rootClose';
173+
meta.source = 'rootClose';
183174

184-
if (isClosingPermitted(source)) onToggle?.(nextShow, event, { source });
175+
if (isClosingPermitted(meta.source!)) onToggle?.(nextShow, meta);
185176
},
186177
);
187178

188-
const handleSelect = useEventCallback((key, event) => {
189-
onSelectCtx?.(key, event);
190-
onSelect?.(key, event);
191-
handleToggle(false, event, 'select');
192-
});
193-
194179
// TODO RTL: Flip directions based on RTL setting.
195-
let direction: DropDirection = drop as DropDirection;
196-
if (drop === 'start') {
197-
direction = 'left';
198-
} else if (drop === 'end') {
199-
direction = 'right';
200-
}
180+
const alignEnd = align === 'end';
181+
let placement: Placement = alignEnd ? 'bottom-end' : 'bottom-start';
182+
if (drop === 'up') placement = alignEnd ? 'top-end' : 'top-start';
183+
else if (drop === 'end') placement = alignEnd ? 'right-end' : 'right-start';
184+
else if (drop === 'start') placement = alignEnd ? 'left-end' : 'left-start';
201185

202186
const contextValue = useMemo(
203187
() => ({
204188
align,
189+
drop,
205190
}),
206-
[align],
191+
[align, drop],
207192
);
208193

209194
return (
210195
<DropdownContext.Provider value={contextValue}>
211-
<SelectableContext.Provider value={handleSelect}>
212-
<BaseDropdown
213-
drop={direction}
214-
show={show}
215-
alignEnd={align === 'end'}
216-
onToggle={handleToggle}
217-
focusFirstItemOnShow={focusFirstItemOnShow}
218-
itemSelector={`.${prefix}-item:not(.disabled):not(:disabled)`}
219-
>
220-
{isInputGroup ? (
221-
props.children
222-
) : (
223-
<Component
224-
{...props}
225-
ref={ref}
226-
className={classNames(
227-
className,
228-
show && 'show',
229-
(!drop || drop === 'down') && prefix,
230-
drop === 'up' && 'dropup',
231-
drop === 'end' && 'dropend',
232-
drop === 'start' && 'dropstart',
233-
)}
234-
/>
235-
)}
236-
</BaseDropdown>
237-
</SelectableContext.Provider>
196+
<BaseDropdown
197+
placement={placement}
198+
show={show}
199+
onSelect={onSelect}
200+
onToggle={handleToggle}
201+
focusFirstItemOnShow={focusFirstItemOnShow}
202+
itemSelector={`.${prefix}-item:not(.disabled):not(:disabled)`}
203+
>
204+
{isInputGroup ? (
205+
props.children
206+
) : (
207+
<Component
208+
{...props}
209+
ref={ref}
210+
className={classNames(
211+
className,
212+
show && 'show',
213+
(!drop || drop === 'down') && prefix,
214+
drop === 'up' && 'dropup',
215+
drop === 'end' && 'dropend',
216+
drop === 'start' && 'dropstart',
217+
)}
218+
/>
219+
)}
220+
</BaseDropdown>
238221
</DropdownContext.Provider>
239222
);
240223
});

src/DropdownButton.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ export interface DropdownButtonProps
2121
const propTypes = {
2222
/**
2323
* An html id attribute for the Toggle button, necessary for assistive technologies, such as screen readers.
24-
* @type {string|number}
25-
* @required
24+
* @type {string}
2625
*/
27-
id: PropTypes.any,
26+
id: PropTypes.string,
2827

2928
/** An `href` passed to the Toggle component */
3029
href: PropTypes.string,

src/DropdownContext.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import * as React from 'react';
22
import { AlignType } from './types';
33

4+
export type DropDirection = 'up' | 'start' | 'end' | 'down';
5+
46
export type DropdownContextValue = {
57
align?: AlignType;
8+
drop?: DropDirection;
69
};
710

811
const DropdownContext = React.createContext<DropdownContextValue>({});

src/DropdownItem.tsx

Lines changed: 21 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,17 @@
11
import classNames from 'classnames';
22
import PropTypes from 'prop-types';
33
import * as React from 'react';
4-
import { useContext } from 'react';
5-
import useEventCallback from '@restart/hooks/useEventCallback';
6-
7-
import SelectableContext, { makeEventKey } from './SelectableContext';
4+
import BaseDropdownItem, {
5+
useDropdownItem,
6+
DropdownItemProps as BaseDropdownItemProps,
7+
} from '@restart/ui/DropdownItem';
8+
import Anchor from '@restart/ui/Anchor';
89
import { useBootstrapPrefix } from './ThemeProvider';
9-
import NavContext from './NavContext';
10-
import SafeAnchor from './SafeAnchor';
11-
import {
12-
BsPrefixProps,
13-
BsPrefixRefForwardingComponent,
14-
SelectCallback,
15-
} from './helpers';
16-
import { EventKey } from './types';
10+
import { BsPrefixProps, BsPrefixRefForwardingComponent } from './helpers';
1711

1812
export interface DropdownItemProps
19-
extends BsPrefixProps,
20-
Omit<React.HTMLAttributes<HTMLElement>, 'onSelect'> {
21-
active?: boolean;
22-
disabled?: boolean;
23-
eventKey?: EventKey;
24-
href?: string;
25-
onSelect?: SelectCallback;
26-
}
13+
extends BaseDropdownItemProps,
14+
BsPrefixProps {}
2715

2816
const propTypes = {
2917
/** @default 'dropdown-item' */
@@ -54,85 +42,52 @@ const propTypes = {
5442
*/
5543
onClick: PropTypes.func,
5644

57-
/**
58-
* Callback fired when the menu item is selected.
59-
*
60-
* ```js
61-
* (eventKey: any, event: Object) => any
62-
* ```
63-
*/
64-
onSelect: PropTypes.func,
65-
6645
as: PropTypes.elementType,
6746
};
6847

69-
const defaultProps = {
70-
as: SafeAnchor,
71-
disabled: false,
72-
};
73-
7448
const DropdownItem: BsPrefixRefForwardingComponent<
75-
typeof SafeAnchor,
49+
typeof BaseDropdownItem,
7650
DropdownItemProps
7751
> = React.forwardRef(
7852
(
7953
{
8054
bsPrefix,
8155
className,
8256
eventKey,
83-
disabled,
84-
href,
57+
disabled = false,
8558
onClick,
86-
onSelect,
87-
active: propActive,
88-
as: Component,
59+
active,
60+
as: Component = Anchor,
8961
...props
90-
}: DropdownItemProps,
62+
},
9163
ref,
9264
) => {
9365
const prefix = useBootstrapPrefix(bsPrefix, 'dropdown-item');
94-
const onSelectCtx = useContext(SelectableContext);
95-
const navContext = useContext(NavContext);
96-
97-
const { activeKey } = navContext || {};
98-
const key = makeEventKey(eventKey, href);
99-
100-
const active =
101-
propActive == null && key != null
102-
? makeEventKey(activeKey) === key
103-
: propActive;
104-
105-
const handleClick = useEventCallback((event) => {
106-
// SafeAnchor handles the disabled case, but we handle it here
107-
// for other components
108-
if (disabled) return;
109-
onClick?.(event);
110-
onSelectCtx?.(key, event);
111-
onSelect?.(key, event);
66+
const [dropdownItemProps, meta] = useDropdownItem({
67+
key: eventKey,
68+
href: props.href,
69+
disabled,
70+
onClick,
71+
active,
11272
});
11373

11474
return (
115-
// "TS2604: JSX element type 'Component' does not have any construct or call signatures."
116-
// @ts-ignore
11775
<Component
11876
{...props}
77+
{...dropdownItemProps}
11978
ref={ref}
120-
href={href}
121-
disabled={disabled}
12279
className={classNames(
12380
className,
12481
prefix,
125-
active && 'active',
82+
meta.isActive && 'active',
12683
disabled && 'disabled',
12784
)}
128-
onClick={handleClick}
12985
/>
13086
);
13187
},
13288
);
13389

13490
DropdownItem.displayName = 'DropdownItem';
13591
DropdownItem.propTypes = propTypes;
136-
DropdownItem.defaultProps = defaultProps;
13792

13893
export default DropdownItem;

0 commit comments

Comments
 (0)