This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+166
View File
@@ -0,0 +1,166 @@
# @rc-component/dropdown
react dropdown component.
[![NPM version][npm-image]][npm-url] [![npm download][download-image]][download-url] [![build status][github-actions-image]][github-actions-url] [![Codecov][codecov-image]][codecov-url] [![bundle size][bundlephobia-image]][bundlephobia-url] [![dumi][dumi-image]][dumi-url]
[npm-image]: https://img.shields.io/npm/v/@rc-component/dropdown.svg?style=flat-square
[npm-url]: https://npmjs.org/package/@rc-component/dropdown
[travis-image]: https://img.shields.io/travis/react-component/dropdown/master?style=flat-square
[travis-url]: https://travis-ci.com/react-component/dropdown
[github-actions-image]: https://github.com/react-component/dropdown/actions/workflows/ci.yml/badge.svg
[github-actions-url]: https://github.com/react-component/dropdown/actions/workflows/ci.yml
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/dropdown/master.svg?style=flat-square
[codecov-url]: https://app.codecov.io/gh/react-component/dropdown
[david-url]: https://david-dm.org/react-component/dropdown
[david-image]: https://david-dm.org/react-component/dropdown/status.svg?style=flat-square
[david-dev-url]: https://david-dm.org/react-component/dropdown?type=dev
[david-dev-image]: https://david-dm.org/react-component/dropdown/dev-status.svg?style=flat-square
[download-image]: https://img.shields.io/npm/dm/@rc-component/dropdown.svg?style=flat-square
[download-url]: https://npmjs.org/package/@rc-component/dropdown
[bundlephobia-url]: https://bundlephobia.com/package/@rc-component/dropdown
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/dropdown
[dumi-url]: https://github.com/umijs/dumi
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
## Screenshot
![](https://t.alipayobjects.com/images/rmsweb/T1bWpgXgBaXXXXXXXX.png)
## Example
online example: http://react-component.github.io/dropdown/examples/
## install
[![@rc-component/dropdown](https://nodei.co/npm/@rc-component/dropdown.png)](https://npmjs.org/package/@rc-component/dropdown)
## Usage
```js
var Dropdown = require('@rc-component/dropdown');
// use dropdown
```
## API
### props
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 100px;">name</th>
<th style="width: 50px;">type</th>
<th style="width: 50px;">default</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>overlayClassName</td>
<td>String</td>
<td></td>
<td>additional css class of root dom node</td>
</tr>
<tr>
<td>openClassName</td>
<td>String</td>
         <td>`${prefixCls}-open`</td>
         <td>className of trigger when dropdown is opened</td>
</tr>
<tr>
<td>prefixCls</td>
<td>String</td>
<td>rc-dropdown</td>
<td>prefix class name</td>
</tr>
<tr>
<td>transitionName</td>
<td>String</td>
<td></td>
<td>dropdown menu's animation css class name</td>
</tr>
<tr>
<td>animation</td>
<td>String</td>
<td></td>
<td>part of dropdown menu's animation css class name</td>
</tr>
<tr>
<td>placement</td>
<td>String</td>
<td>bottomLeft</td>
<td>Position of menu item. There are: top, topCenter, topRight, bottomLeft, bottom, bottomRight </td>
</tr>
<tr>
<td>onVisibleChange</td>
<td>Function</td>
<td></td>
<td>call when visible is changed</td>
</tr>
<tr>
<td>visible</td>
<td>boolean</td>
<td></td>
<td>whether tooltip is visible</td>
</tr>
<tr>
<td>defaultVisible</td>
<td>boolean</td>
<td></td>
<td>whether tooltip is visible initially</td>
</tr>
<tr>
<td>overlay</td>
<td>rc-menu</td>
<td></td>
<td><a href="https://github.com/react-component/menu">rc-menu</a> element</td>
</tr>
<tr>
<td>onOverlayClick</td>
<td>function(e)</td>
<td></td>
<td>call when overlay is clicked</td>
</tr>
<tr>
<td>minOverlayWidthMatchTrigger</td>
<td>boolean</td>
         <td>true (false when set alignPoint)</td>
         <td>whether overlay's width must not be less than trigger's </td>
</tr>
<tr>
<td>getPopupContainer</td>
<td>Function(menuDOMNode): HTMLElement</td>
<th>() => document.body</th>
<td>Where to render the DOM node of dropdown</td>
</tr>
</tbody>
</table>
Note: Additional props are passed into the underlying [rc-trigger](https://github.com/react-component/trigger) component. This can be useful for example, to display the dropdown in a separate [portal](https://reactjs.org/docs/portals.html)-driven window via the `getDocument()` rc-trigger prop.
## Development
```bash
npm install
npm start
```
## Test Case
```bash
npm test
npm run chrome-test
```
## Coverage
```bash
npm run coverage
```
open coverage/ dir
## License
@rc-component/dropdown is released under the MIT license.
+245
View File
@@ -0,0 +1,245 @@
@font-face {
font-family: 'anticon';
src: url('//at.alicdn.com/t/font_1434092639_4910953.eot');
/* IE9*/
src: url('//at.alicdn.com/t/font_1434092639_4910953.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('//at.alicdn.com/t/font_1434092639_4910953.woff') format('woff'), /* chrome、firefox */ url('//at.alicdn.com/t/font_1434092639_4910953.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ url('//at.alicdn.com/t/font_1434092639_4910953.svg#iconfont') format('svg');
/* iOS 4.1- */
}
.rc-dropdown {
position: absolute;
left: -9999px;
top: -9999px;
z-index: 1070;
display: block;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 12px;
font-weight: normal;
line-height: 1.5;
}
.rc-dropdown-hidden {
display: none;
}
.rc-dropdown .rc-menu {
outline: none;
position: relative;
list-style-type: none;
padding: 0;
margin: 2px 0 2px;
text-align: left;
background-color: #fff;
border-radius: 3px;
box-shadow: 0 1px 5px #ccc;
background-clip: padding-box;
border: 1px solid #ccc;
}
.rc-dropdown .rc-menu > li {
margin: 0;
padding: 0;
}
.rc-dropdown .rc-menu:before {
content: "";
position: absolute;
top: -4px;
left: 0;
width: 100%;
height: 4px;
background: #ffffff;
background: rgba(255, 255, 255, 0.01);
}
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item {
position: relative;
display: block;
padding: 7px 10px;
clear: both;
font-size: 12px;
font-weight: normal;
color: #666666;
white-space: nowrap;
}
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item:hover,
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item-active,
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item-selected {
background-color: #ebfaff;
}
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item-selected {
position: relative;
}
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item-selected:after {
content: '\e613';
font-family: 'anticon';
font-weight: bold;
position: absolute;
top: 6px;
right: 16px;
color: #3CB8F0;
}
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item-disabled {
color: #ccc;
cursor: not-allowed;
pointer-events: none;
}
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item-disabled:hover {
color: #ccc;
background-color: #fff;
cursor: not-allowed;
}
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item:last-child {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.rc-dropdown .rc-menu > .rc-dropdown .rc-menu-item-divider {
height: 1px;
margin: 1px 0;
overflow: hidden;
background-color: #e5e5e5;
line-height: 0;
}
.rc-dropdown-slide-up-enter,
.rc-dropdown-slide-up-appear {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
display: block !important;
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
.rc-dropdown-slide-up-leave {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
display: block !important;
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
.rc-dropdown-slide-up-enter.rc-dropdown-slide-up-enter-active.rc-dropdown-placement-bottomLeft,
.rc-dropdown-slide-up-appear.rc-dropdown-slide-up-appear-active.rc-dropdown-placement-bottomLeft,
.rc-dropdown-slide-up-enter.rc-dropdown-slide-up-enter-active.rc-dropdown-placement-bottomCenter,
.rc-dropdown-slide-up-appear.rc-dropdown-slide-up-appear-active.rc-dropdown-placement-bottomCenter,
.rc-dropdown-slide-up-enter.rc-dropdown-slide-up-enter-active.rc-dropdown-placement-bottomRight,
.rc-dropdown-slide-up-appear.rc-dropdown-slide-up-appear-active.rc-dropdown-placement-bottomRight {
animation-name: rcDropdownSlideUpIn;
animation-play-state: running;
}
.rc-dropdown-slide-up-enter.rc-dropdown-slide-up-enter-active.rc-dropdown-placement-topLeft,
.rc-dropdown-slide-up-appear.rc-dropdown-slide-up-appear-active.rc-dropdown-placement-topLeft,
.rc-dropdown-slide-up-enter.rc-dropdown-slide-up-enter-active.rc-dropdown-placement-topCenter,
.rc-dropdown-slide-up-appear.rc-dropdown-slide-up-appear-active.rc-dropdown-placement-topCenter,
.rc-dropdown-slide-up-enter.rc-dropdown-slide-up-enter-active.rc-dropdown-placement-topRight,
.rc-dropdown-slide-up-appear.rc-dropdown-slide-up-appear-active.rc-dropdown-placement-topRight {
animation-name: rcDropdownSlideDownIn;
animation-play-state: running;
}
.rc-dropdown-slide-up-leave.rc-dropdown-slide-up-leave-active.rc-dropdown-placement-bottomLeft,
.rc-dropdown-slide-up-leave.rc-dropdown-slide-up-leave-active.rc-dropdown-placement-bottomCenter,
.rc-dropdown-slide-up-leave.rc-dropdown-slide-up-leave-active.rc-dropdown-placement-bottomRight {
animation-name: rcDropdownSlideUpOut;
animation-play-state: running;
}
.rc-dropdown-slide-up-leave.rc-dropdown-slide-up-leave-active.rc-dropdown-placement-topLeft,
.rc-dropdown-slide-up-leave.rc-dropdown-slide-up-leave-active.rc-dropdown-placement-topCenter,
.rc-dropdown-slide-up-leave.rc-dropdown-slide-up-leave-active.rc-dropdown-placement-topRight {
animation-name: rcDropdownSlideDownOut;
animation-play-state: running;
}
@keyframes rcDropdownSlideUpIn {
0% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
100% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
}
@keyframes rcDropdownSlideUpOut {
0% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
}
@keyframes rcDropdownSlideDownIn {
0% {
opacity: 0;
transform-origin: 0% 100%;
transform: scaleY(0);
}
100% {
opacity: 1;
transform-origin: 0% 100%;
transform: scaleY(1);
}
}
@keyframes rcDropdownSlideDownOut {
0% {
opacity: 1;
transform-origin: 0% 100%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 100%;
transform: scaleY(0);
}
}
.rc-dropdown-arrow {
position: absolute;
border-width: 8px / 2;
border-color: transparent;
box-shadow: 0 1px 5px #ccc;
border-style: solid;
transform: rotate(45deg);
}
.rc-dropdown-show-arrow.rc-dropdown-placement-top,
.rc-dropdown-show-arrow.rc-dropdown-placement-topLeft,
.rc-dropdown-show-arrow.rc-dropdown-placement-topRight {
padding-bottom: 6px;
}
.rc-dropdown-show-arrow.rc-dropdown-placement-bottom,
.rc-dropdown-show-arrow.rc-dropdown-placement-bottomLeft,
.rc-dropdown-show-arrow.rc-dropdown-placement-bottomRight {
padding-top: 6px;
}
.rc-dropdown-placement-top .rc-dropdown-arrow,
.rc-dropdown-placement-topLeft .rc-dropdown-arrow,
.rc-dropdown-placement-topRight .rc-dropdown-arrow {
bottom: 4px;
border-top-color: white;
}
.rc-dropdown-placement-top .rc-dropdown-arrow {
left: 50%;
}
.rc-dropdown-placement-topLeft .rc-dropdown-arrow {
left: 15%;
}
.rc-dropdown-placement-topRight .rc-dropdown-arrow {
right: 15%;
}
.rc-dropdown-placement-bottom .rc-dropdown-arrow,
.rc-dropdown-placement-bottomLeft .rc-dropdown-arrow,
.rc-dropdown-placement-bottomRight .rc-dropdown-arrow {
top: 4px;
border-bottom-color: white;
}
.rc-dropdown-placement-bottom .rc-dropdown-arrow {
left: 50%;
}
.rc-dropdown-placement-bottomLeft .rc-dropdown-arrow {
left: 15%;
}
.rc-dropdown-placement-bottomRight .rc-dropdown-arrow {
right: 15%;
}
+28
View File
@@ -0,0 +1,28 @@
import type { TriggerProps, TriggerRef } from '@rc-component/trigger';
import type { ActionType, AlignType, AnimationType, BuildInPlacements } from '@rc-component/trigger/lib/interface';
import React from 'react';
import Placements from './placements';
export interface DropdownProps extends Pick<TriggerProps, 'getPopupContainer' | 'children' | 'mouseEnterDelay' | 'mouseLeaveDelay' | 'onPopupAlign' | 'builtinPlacements' | 'autoDestroy'> {
minOverlayWidthMatchTrigger?: boolean;
arrow?: boolean;
onVisibleChange?: (visible: boolean) => void;
onOverlayClick?: (e: Event) => void;
prefixCls?: string;
transitionName?: string;
overlayClassName?: string;
openClassName?: string;
animation?: AnimationType;
align?: AlignType;
overlayStyle?: React.CSSProperties;
placement?: keyof typeof Placements;
placements?: BuildInPlacements;
overlay?: (() => React.ReactElement) | React.ReactElement;
trigger?: ActionType | ActionType[];
alignPoint?: boolean;
showAction?: ActionType[];
hideAction?: ActionType[];
visible?: boolean;
autoFocus?: boolean;
}
declare const Dropdown: React.ForwardRefExoticComponent<DropdownProps & React.RefAttributes<TriggerRef>>;
export default Dropdown;
+122
View File
@@ -0,0 +1,122 @@
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import Trigger from '@rc-component/trigger';
import { composeRef, getNodeRef, supportRef } from "@rc-component/util/es/ref";
import { clsx } from 'clsx';
import React from 'react';
import useAccessibility from "./hooks/useAccessibility";
import Overlay from "./Overlay";
import Placements from "./placements";
const Dropdown = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
arrow = false,
prefixCls = 'rc-dropdown',
transitionName,
animation,
align,
placement = 'bottomLeft',
placements = Placements,
getPopupContainer,
showAction,
hideAction,
overlayClassName,
overlayStyle,
visible,
trigger = ['hover'],
autoFocus,
overlay,
children,
onVisibleChange,
...otherProps
} = props;
const [triggerVisible, setTriggerVisible] = React.useState();
const mergedVisible = 'visible' in props ? visible : triggerVisible;
const mergedMotionName = animation ? `${prefixCls}-${animation}` : transitionName;
const triggerRef = React.useRef(null);
const overlayRef = React.useRef(null);
const childRef = React.useRef(null);
React.useImperativeHandle(ref, () => triggerRef.current);
const handleVisibleChange = newVisible => {
setTriggerVisible(newVisible);
onVisibleChange?.(newVisible);
};
useAccessibility({
visible: mergedVisible,
triggerRef: childRef,
onVisibleChange: handleVisibleChange,
autoFocus,
overlayRef
});
const onClick = e => {
const {
onOverlayClick
} = props;
setTriggerVisible(false);
if (onOverlayClick) {
onOverlayClick(e);
}
};
const getMenuElement = () => /*#__PURE__*/React.createElement(Overlay, {
ref: overlayRef,
overlay: overlay,
prefixCls: prefixCls,
arrow: arrow
});
const getMenuElementOrLambda = () => {
if (typeof overlay === 'function') {
return getMenuElement;
}
return getMenuElement();
};
const getMinOverlayWidthMatchTrigger = () => {
const {
minOverlayWidthMatchTrigger,
alignPoint
} = props;
if ('minOverlayWidthMatchTrigger' in props) {
return minOverlayWidthMatchTrigger;
}
return !alignPoint;
};
const getOpenClassName = () => {
const {
openClassName
} = props;
if (openClassName !== undefined) {
return openClassName;
}
return `${prefixCls}-open`;
};
const childrenNode = /*#__PURE__*/React.cloneElement(children, {
className: clsx(children.props?.className, mergedVisible && getOpenClassName()),
ref: supportRef(children) ? composeRef(childRef, getNodeRef(children)) : undefined
});
let triggerHideAction = hideAction;
if (!triggerHideAction && trigger.indexOf('contextMenu') !== -1) {
triggerHideAction = ['click'];
}
return /*#__PURE__*/React.createElement(Trigger, _extends({
builtinPlacements: placements
}, otherProps, {
prefixCls: prefixCls,
ref: triggerRef,
popupClassName: clsx(overlayClassName, {
[`${prefixCls}-show-arrow`]: arrow
}),
popupStyle: overlayStyle,
action: trigger,
showAction: showAction,
hideAction: triggerHideAction,
popupPlacement: placement,
popupAlign: align,
popupMotion: {
motionName: mergedMotionName
},
popupVisible: mergedVisible,
stretch: getMinOverlayWidthMatchTrigger() ? 'minWidth' : '',
popup: getMenuElementOrLambda(),
onOpenChange: handleVisibleChange,
onPopupClick: onClick,
getPopupContainer: getPopupContainer
}), childrenNode);
});
export default Dropdown;
+5
View File
@@ -0,0 +1,5 @@
import React from 'react';
import type { DropdownProps } from './Dropdown';
export type OverlayProps = Pick<DropdownProps, 'overlay' | 'arrow' | 'prefixCls'>;
declare const Overlay: React.ForwardRefExoticComponent<OverlayProps & React.RefAttributes<HTMLElement>>;
export default Overlay;
+25
View File
@@ -0,0 +1,25 @@
import { composeRef, getNodeRef, supportRef } from "@rc-component/util/es/ref";
import React, { forwardRef, useMemo } from 'react';
const Overlay = /*#__PURE__*/forwardRef((props, ref) => {
const {
overlay,
arrow,
prefixCls
} = props;
const overlayNode = useMemo(() => {
let overlayElement;
if (typeof overlay === 'function') {
overlayElement = overlay();
} else {
overlayElement = overlay;
}
return overlayElement;
}, [overlay]);
const composedRef = composeRef(ref, getNodeRef(overlayNode));
return /*#__PURE__*/React.createElement(React.Fragment, null, arrow && /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-arrow`
}), /*#__PURE__*/React.cloneElement(overlayNode, {
ref: supportRef(overlayNode) ? composedRef : undefined
}));
});
export default Overlay;
@@ -0,0 +1,10 @@
import * as React from 'react';
interface UseAccessibilityProps {
visible: boolean;
triggerRef: React.RefObject<any>;
onVisibleChange?: (visible: boolean) => void;
autoFocus?: boolean;
overlayRef?: React.RefObject<any>;
}
export default function useAccessibility({ visible, triggerRef, onVisibleChange, autoFocus, overlayRef, }: UseAccessibilityProps): void;
export {};
@@ -0,0 +1,66 @@
import KeyCode from "@rc-component/util/es/KeyCode";
import raf from "@rc-component/util/es/raf";
import * as React from 'react';
const {
ESC,
TAB
} = KeyCode;
export default function useAccessibility({
visible,
triggerRef,
onVisibleChange,
autoFocus,
overlayRef
}) {
const focusMenuRef = React.useRef(false);
const handleCloseMenuAndReturnFocus = () => {
if (visible) {
triggerRef.current?.focus?.();
onVisibleChange?.(false);
}
};
const focusMenu = () => {
if (overlayRef.current?.focus) {
overlayRef.current.focus();
focusMenuRef.current = true;
return true;
}
return false;
};
const handleKeyDown = event => {
switch (event.keyCode) {
case ESC:
handleCloseMenuAndReturnFocus();
break;
case TAB:
{
let focusResult = false;
if (!focusMenuRef.current) {
focusResult = focusMenu();
}
if (focusResult) {
event.preventDefault();
} else {
handleCloseMenuAndReturnFocus();
}
break;
}
}
};
React.useEffect(() => {
if (visible) {
window.addEventListener('keydown', handleKeyDown);
if (autoFocus) {
// FIXME: hack with raf
raf(focusMenu, 3);
}
return () => {
window.removeEventListener('keydown', handleKeyDown);
focusMenuRef.current = false;
};
}
return () => {
focusMenuRef.current = false;
};
}, [visible]); // eslint-disable-line react-hooks/exhaustive-deps
}
+5
View File
@@ -0,0 +1,5 @@
export type { TriggerProps } from '@rc-component/trigger';
export type { DropdownProps } from './Dropdown';
export type { OverlayProps } from './Overlay';
import Dropdown from './Dropdown';
export default Dropdown;
+2
View File
@@ -0,0 +1,2 @@
import Dropdown from "./Dropdown";
export default Dropdown;
+57
View File
@@ -0,0 +1,57 @@
declare const placements: {
topLeft: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
top: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
topRight: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
bottomLeft: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
bottom: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
bottomRight: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
};
export default placements;
+44
View File
@@ -0,0 +1,44 @@
const autoAdjustOverflow = {
adjustX: 1,
adjustY: 1
};
const targetOffset = [0, 0];
const placements = {
topLeft: {
points: ['bl', 'tl'],
overflow: autoAdjustOverflow,
offset: [0, -4],
targetOffset
},
top: {
points: ['bc', 'tc'],
overflow: autoAdjustOverflow,
offset: [0, -4],
targetOffset
},
topRight: {
points: ['br', 'tr'],
overflow: autoAdjustOverflow,
offset: [0, -4],
targetOffset
},
bottomLeft: {
points: ['tl', 'bl'],
overflow: autoAdjustOverflow,
offset: [0, 4],
targetOffset
},
bottom: {
points: ['tc', 'bc'],
overflow: autoAdjustOverflow,
offset: [0, 4],
targetOffset
},
bottomRight: {
points: ['tr', 'br'],
overflow: autoAdjustOverflow,
offset: [0, 4],
targetOffset
}
};
export default placements;
+28
View File
@@ -0,0 +1,28 @@
import type { TriggerProps, TriggerRef } from '@rc-component/trigger';
import type { ActionType, AlignType, AnimationType, BuildInPlacements } from '@rc-component/trigger/lib/interface';
import React from 'react';
import Placements from './placements';
export interface DropdownProps extends Pick<TriggerProps, 'getPopupContainer' | 'children' | 'mouseEnterDelay' | 'mouseLeaveDelay' | 'onPopupAlign' | 'builtinPlacements' | 'autoDestroy'> {
minOverlayWidthMatchTrigger?: boolean;
arrow?: boolean;
onVisibleChange?: (visible: boolean) => void;
onOverlayClick?: (e: Event) => void;
prefixCls?: string;
transitionName?: string;
overlayClassName?: string;
openClassName?: string;
animation?: AnimationType;
align?: AlignType;
overlayStyle?: React.CSSProperties;
placement?: keyof typeof Placements;
placements?: BuildInPlacements;
overlay?: (() => React.ReactElement) | React.ReactElement;
trigger?: ActionType | ActionType[];
alignPoint?: boolean;
showAction?: ActionType[];
hideAction?: ActionType[];
visible?: boolean;
autoFocus?: boolean;
}
declare const Dropdown: React.ForwardRefExoticComponent<DropdownProps & React.RefAttributes<TriggerRef>>;
export default Dropdown;
+129
View File
@@ -0,0 +1,129 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _trigger = _interopRequireDefault(require("@rc-component/trigger"));
var _ref = require("@rc-component/util/lib/ref");
var _clsx = require("clsx");
var _react = _interopRequireDefault(require("react"));
var _useAccessibility = _interopRequireDefault(require("./hooks/useAccessibility"));
var _Overlay = _interopRequireDefault(require("./Overlay"));
var _placements = _interopRequireDefault(require("./placements"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const Dropdown = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
const {
arrow = false,
prefixCls = 'rc-dropdown',
transitionName,
animation,
align,
placement = 'bottomLeft',
placements = _placements.default,
getPopupContainer,
showAction,
hideAction,
overlayClassName,
overlayStyle,
visible,
trigger = ['hover'],
autoFocus,
overlay,
children,
onVisibleChange,
...otherProps
} = props;
const [triggerVisible, setTriggerVisible] = _react.default.useState();
const mergedVisible = 'visible' in props ? visible : triggerVisible;
const mergedMotionName = animation ? `${prefixCls}-${animation}` : transitionName;
const triggerRef = _react.default.useRef(null);
const overlayRef = _react.default.useRef(null);
const childRef = _react.default.useRef(null);
_react.default.useImperativeHandle(ref, () => triggerRef.current);
const handleVisibleChange = newVisible => {
setTriggerVisible(newVisible);
onVisibleChange?.(newVisible);
};
(0, _useAccessibility.default)({
visible: mergedVisible,
triggerRef: childRef,
onVisibleChange: handleVisibleChange,
autoFocus,
overlayRef
});
const onClick = e => {
const {
onOverlayClick
} = props;
setTriggerVisible(false);
if (onOverlayClick) {
onOverlayClick(e);
}
};
const getMenuElement = () => /*#__PURE__*/_react.default.createElement(_Overlay.default, {
ref: overlayRef,
overlay: overlay,
prefixCls: prefixCls,
arrow: arrow
});
const getMenuElementOrLambda = () => {
if (typeof overlay === 'function') {
return getMenuElement;
}
return getMenuElement();
};
const getMinOverlayWidthMatchTrigger = () => {
const {
minOverlayWidthMatchTrigger,
alignPoint
} = props;
if ('minOverlayWidthMatchTrigger' in props) {
return minOverlayWidthMatchTrigger;
}
return !alignPoint;
};
const getOpenClassName = () => {
const {
openClassName
} = props;
if (openClassName !== undefined) {
return openClassName;
}
return `${prefixCls}-open`;
};
const childrenNode = /*#__PURE__*/_react.default.cloneElement(children, {
className: (0, _clsx.clsx)(children.props?.className, mergedVisible && getOpenClassName()),
ref: (0, _ref.supportRef)(children) ? (0, _ref.composeRef)(childRef, (0, _ref.getNodeRef)(children)) : undefined
});
let triggerHideAction = hideAction;
if (!triggerHideAction && trigger.indexOf('contextMenu') !== -1) {
triggerHideAction = ['click'];
}
return /*#__PURE__*/_react.default.createElement(_trigger.default, _extends({
builtinPlacements: placements
}, otherProps, {
prefixCls: prefixCls,
ref: triggerRef,
popupClassName: (0, _clsx.clsx)(overlayClassName, {
[`${prefixCls}-show-arrow`]: arrow
}),
popupStyle: overlayStyle,
action: trigger,
showAction: showAction,
hideAction: triggerHideAction,
popupPlacement: placement,
popupAlign: align,
popupMotion: {
motionName: mergedMotionName
},
popupVisible: mergedVisible,
stretch: getMinOverlayWidthMatchTrigger() ? 'minWidth' : '',
popup: getMenuElementOrLambda(),
onOpenChange: handleVisibleChange,
onPopupClick: onClick,
getPopupContainer: getPopupContainer
}), childrenNode);
});
var _default = exports.default = Dropdown;
+5
View File
@@ -0,0 +1,5 @@
import React from 'react';
import type { DropdownProps } from './Dropdown';
export type OverlayProps = Pick<DropdownProps, 'overlay' | 'arrow' | 'prefixCls'>;
declare const Overlay: React.ForwardRefExoticComponent<OverlayProps & React.RefAttributes<HTMLElement>>;
export default Overlay;
+33
View File
@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _ref = require("@rc-component/util/lib/ref");
var _react = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const Overlay = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
const {
overlay,
arrow,
prefixCls
} = props;
const overlayNode = (0, _react.useMemo)(() => {
let overlayElement;
if (typeof overlay === 'function') {
overlayElement = overlay();
} else {
overlayElement = overlay;
}
return overlayElement;
}, [overlay]);
const composedRef = (0, _ref.composeRef)(ref, (0, _ref.getNodeRef)(overlayNode));
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, arrow && /*#__PURE__*/_react.default.createElement("div", {
className: `${prefixCls}-arrow`
}), /*#__PURE__*/_react.default.cloneElement(overlayNode, {
ref: (0, _ref.supportRef)(overlayNode) ? composedRef : undefined
}));
});
var _default = exports.default = Overlay;
@@ -0,0 +1,10 @@
import * as React from 'react';
interface UseAccessibilityProps {
visible: boolean;
triggerRef: React.RefObject<any>;
onVisibleChange?: (visible: boolean) => void;
autoFocus?: boolean;
overlayRef?: React.RefObject<any>;
}
export default function useAccessibility({ visible, triggerRef, onVisibleChange, autoFocus, overlayRef, }: UseAccessibilityProps): void;
export {};
@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useAccessibility;
var _KeyCode = _interopRequireDefault(require("@rc-component/util/lib/KeyCode"));
var _raf = _interopRequireDefault(require("@rc-component/util/lib/raf"));
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const {
ESC,
TAB
} = _KeyCode.default;
function useAccessibility({
visible,
triggerRef,
onVisibleChange,
autoFocus,
overlayRef
}) {
const focusMenuRef = React.useRef(false);
const handleCloseMenuAndReturnFocus = () => {
if (visible) {
triggerRef.current?.focus?.();
onVisibleChange?.(false);
}
};
const focusMenu = () => {
if (overlayRef.current?.focus) {
overlayRef.current.focus();
focusMenuRef.current = true;
return true;
}
return false;
};
const handleKeyDown = event => {
switch (event.keyCode) {
case ESC:
handleCloseMenuAndReturnFocus();
break;
case TAB:
{
let focusResult = false;
if (!focusMenuRef.current) {
focusResult = focusMenu();
}
if (focusResult) {
event.preventDefault();
} else {
handleCloseMenuAndReturnFocus();
}
break;
}
}
};
React.useEffect(() => {
if (visible) {
window.addEventListener('keydown', handleKeyDown);
if (autoFocus) {
// FIXME: hack with raf
(0, _raf.default)(focusMenu, 3);
}
return () => {
window.removeEventListener('keydown', handleKeyDown);
focusMenuRef.current = false;
};
}
return () => {
focusMenuRef.current = false;
};
}, [visible]); // eslint-disable-line react-hooks/exhaustive-deps
}
+5
View File
@@ -0,0 +1,5 @@
export type { TriggerProps } from '@rc-component/trigger';
export type { DropdownProps } from './Dropdown';
export type { OverlayProps } from './Overlay';
import Dropdown from './Dropdown';
export default Dropdown;
+9
View File
@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _Dropdown = _interopRequireDefault(require("./Dropdown"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _default = exports.default = _Dropdown.default;
@@ -0,0 +1,57 @@
declare const placements: {
topLeft: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
top: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
topRight: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
bottomLeft: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
bottom: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
bottomRight: {
points: string[];
overflow: {
adjustX: number;
adjustY: number;
};
offset: number[];
targetOffset: number[];
};
};
export default placements;
+50
View File
@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
const autoAdjustOverflow = {
adjustX: 1,
adjustY: 1
};
const targetOffset = [0, 0];
const placements = {
topLeft: {
points: ['bl', 'tl'],
overflow: autoAdjustOverflow,
offset: [0, -4],
targetOffset
},
top: {
points: ['bc', 'tc'],
overflow: autoAdjustOverflow,
offset: [0, -4],
targetOffset
},
topRight: {
points: ['br', 'tr'],
overflow: autoAdjustOverflow,
offset: [0, -4],
targetOffset
},
bottomLeft: {
points: ['tl', 'bl'],
overflow: autoAdjustOverflow,
offset: [0, 4],
targetOffset
},
bottom: {
points: ['tc', 'bc'],
overflow: autoAdjustOverflow,
offset: [0, 4],
targetOffset
},
bottomRight: {
points: ['tr', 'br'],
overflow: autoAdjustOverflow,
offset: [0, 4],
targetOffset
}
};
var _default = exports.default = placements;
+82
View File
@@ -0,0 +1,82 @@
{
"name": "@rc-component/dropdown",
"version": "1.0.2",
"description": "dropdown ui component for react",
"keywords": [
"react",
"react-dropdown"
],
"homepage": "http://github.com/react-component/dropdown",
"bugs": {
"url": "http://github.com/react-component/dropdown/issues"
},
"repository": {
"type": "git",
"url": "git@github.com:react-component/dropdown.git"
},
"license": "MIT",
"maintainers": [
"yiminghe@gmail.com",
"hualei5280@gmail.com"
],
"main": "lib/index",
"module": "./es/index",
"files": [
"lib",
"es",
"assets/*.css"
],
"scripts": {
"build": "dumi build",
"compile": "father build && lessc assets/index.less assets/index.css",
"coverage": "rc-test --coverage",
"lint": "eslint src/ docs/examples/ --ext .tsx,.ts,.jsx,.js",
"now-build": "npm run build",
"prepare": "husky install && dumi setup",
"prepublishOnly": "npm run compile && rc-np",
"start": "dumi dev",
"test": "rc-test"
},
"lint-staged": {
"**/*.{js,jsx,tsx,ts,md,json}": [
"prettier --write",
"git add"
]
},
"dependencies": {
"@rc-component/trigger": "^3.0.0",
"@rc-component/util": "^1.2.1",
"clsx": "^2.1.1"
},
"devDependencies": {
"@rc-component/father-plugin": "^2.0.2",
"@rc-component/menu": "^1.0.0",
"@rc-component/np": "^1.0.3",
"@rc-component/resize-observer": "^1.0.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.0.0",
"@types/node": "^24.5.2",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/warning": "^3.0.0",
"@umijs/fabric": "^3.0.0",
"dumi": "^2.0.0",
"eslint": "^7.18.0",
"father": "^4.0.0",
"glob": "^10.0.0",
"husky": "^8.0.3",
"jest-environment-jsdom": "^29.5.0",
"less": "^4.1.1",
"lint-staged": "^13.2.1",
"prettier": "^2.8.7",
"rc-test": "^7.0.14",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
"react": ">=16.11.0",
"react-dom": ">=16.11.0"
}
}