mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 20:49:53 +08:00
feat: Select/DatePicker/TimePicker/TreeSelect support placement (#33541)
* feat: select components add placement api * feat: select components add placement api * fix: delete placement * fix: change md demo and delete export * feat: cascader and tree-select add placement * feat: datapicker add placement api * fix: change repeat static declaration to single * test: updata test units * doc: change doc * fix: delete merge message & delete decalare ts * test: fix unit test * fix: add transName in select & treeSelect & cascader * fix: change common api in utils * fix: change useless if block to only * fix: change placement string to enum * fix: lint done Co-authored-by: 礼检 <ljj337009@antgroup.com>
This commit is contained in:
parent
803be5d121
commit
daa9804824
@ -1,5 +1,6 @@
|
||||
import { CSSMotionProps, MotionEventHandler, MotionEndEventHandler } from 'rc-motion';
|
||||
import { MotionEvent } from 'rc-motion/lib/interface';
|
||||
import { tuple } from './type';
|
||||
|
||||
// ================== Collapse Motion ==================
|
||||
const getCollapsedHeight: MotionEventHandler = () => ({ height: 0, opacity: 0 });
|
||||
@ -25,11 +26,21 @@ const collapseMotion: CSSMotionProps = {
|
||||
motionDeadline: 500,
|
||||
};
|
||||
|
||||
const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight');
|
||||
export type SelectCommonPlacement = typeof SelectPlacements[number];
|
||||
|
||||
const getTransitionDirection = (placement: SelectCommonPlacement | undefined) => {
|
||||
if (placement !== undefined && (placement === 'topLeft' || placement === 'topRight')) {
|
||||
return `slide-down`;
|
||||
}
|
||||
return `slide-up`;
|
||||
};
|
||||
|
||||
const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
|
||||
if (transitionName !== undefined) {
|
||||
return transitionName;
|
||||
}
|
||||
return `${rootPrefixCls}-${motion}`;
|
||||
};
|
||||
export { getTransitionName };
|
||||
export { getTransitionName, getTransitionDirection };
|
||||
export default collapseMotion;
|
||||
|
@ -1674,6 +1674,241 @@ exports[`renders ./components/cascader/demo/multiple.md extend context correctly
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/cascader/demo/placement.md extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button ant-radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</label>
|
||||
</div>,
|
||||
<br />,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="undefined_list"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="undefined_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
style="opacity:0"
|
||||
type="search"
|
||||
unselectable="on"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
>
|
||||
Please select
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-select-dropdown ant-cascader-dropdown"
|
||||
style="opacity:0;pointer-events:none;min-width:auto"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="ant-cascader-menus"
|
||||
>
|
||||
<ul
|
||||
class="ant-cascader-menu"
|
||||
role="menu"
|
||||
>
|
||||
<li
|
||||
aria-checked="false"
|
||||
class="ant-cascader-menu-item ant-cascader-menu-item-expand"
|
||||
data-path-key="zhejiang"
|
||||
role="menuitemcheckbox"
|
||||
title="Zhejiang"
|
||||
>
|
||||
<div
|
||||
class="ant-cascader-menu-item-content"
|
||||
>
|
||||
Zhejiang
|
||||
</div>
|
||||
<div
|
||||
class="ant-cascader-menu-item-expand-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="right"
|
||||
class="anticon anticon-right"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="right"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
aria-checked="false"
|
||||
class="ant-cascader-menu-item ant-cascader-menu-item-expand"
|
||||
data-path-key="jiangsu"
|
||||
role="menuitemcheckbox"
|
||||
title="Jiangsu"
|
||||
>
|
||||
<div
|
||||
class="ant-cascader-menu-item-content"
|
||||
>
|
||||
Jiangsu
|
||||
</div>
|
||||
<div
|
||||
class="ant-cascader-menu-item-expand-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="right"
|
||||
class="anticon anticon-right"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="right"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-arrow"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-select-suffix"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/cascader/demo/search.md extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
|
||||
|
@ -658,6 +658,151 @@ exports[`renders ./components/cascader/demo/multiple.md correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/cascader/demo/placement.md correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button ant-radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</label>
|
||||
</div>,
|
||||
<br />,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="undefined_list"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="undefined_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
style="opacity:0"
|
||||
type="search"
|
||||
unselectable="on"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
>
|
||||
Please select
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-arrow"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-select-suffix"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/cascader/demo/search.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
|
||||
|
@ -368,6 +368,23 @@ describe('Cascader', () => {
|
||||
expect(wrapper.find('.ant-select-selection-placeholder').text()).toEqual(customPlaceholder);
|
||||
});
|
||||
|
||||
it('placement work correctly', () => {
|
||||
const customerOptions = [
|
||||
{
|
||||
value: 'zhejiang',
|
||||
label: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: 'Hangzhou',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const wrapper = mount(<Cascader options={customerOptions} placement="topRight" />);
|
||||
expect(wrapper.find('Trigger').prop('popupPlacement')).toEqual('topRight');
|
||||
});
|
||||
|
||||
it('popup correctly with defaultValue RTL', () => {
|
||||
const wrapper = mount(
|
||||
<ConfigProvider direction="rtl">
|
||||
@ -478,10 +495,12 @@ describe('Cascader', () => {
|
||||
describe('legacy props', () => {
|
||||
it('popupClassName', () => {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const wrapper = mount(<Cascader open popupPlacement="topRight" popupClassName="mock-cls" />);
|
||||
const wrapper = mount(
|
||||
<Cascader open popupPlacement="bottomLeft" popupClassName="mock-cls" />,
|
||||
);
|
||||
|
||||
expect(wrapper.exists('.mock-cls')).toBeTruthy();
|
||||
expect(wrapper.find('Trigger').prop('popupPlacement')).toEqual('topRight');
|
||||
expect(wrapper.find('Trigger').prop('popupPlacement')).toEqual('bottomLeft');
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Cascader] `popupClassName` is deprecated. Please use `dropdownClassName` instead.',
|
||||
|
77
components/cascader/demo/placement.md
Normal file
77
components/cascader/demo/placement.md
Normal file
@ -0,0 +1,77 @@
|
||||
---
|
||||
order: 13
|
||||
title:
|
||||
zh-CN: 弹出位置
|
||||
en-US: Placement
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
可以通过 `placement` 手动指定弹出的位置。
|
||||
|
||||
## en-US
|
||||
|
||||
You can manually specify the position of the popup via `placement`.
|
||||
|
||||
```jsx
|
||||
import { Cascader, Radio } from 'antd';
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: 'zhejiang',
|
||||
label: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: 'Hangzhou',
|
||||
children: [
|
||||
{
|
||||
value: 'xihu',
|
||||
label: 'West Lake',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'jiangsu',
|
||||
label: 'Jiangsu',
|
||||
children: [
|
||||
{
|
||||
value: 'nanjing',
|
||||
label: 'Nanjing',
|
||||
children: [
|
||||
{
|
||||
value: 'zhonghuamen',
|
||||
label: 'Zhong Hua Men',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const SetPlacementDemo = () => {
|
||||
const [placement, SetPlacement] = React.useState('topLeft');
|
||||
|
||||
const placementChange = e => {
|
||||
SetPlacement(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Radio.Group value={placement} onChange={placementChange}>
|
||||
<Radio.Button value="topLeft">topLeft</Radio.Button>
|
||||
<Radio.Button value="topRight">topRight</Radio.Button>
|
||||
<Radio.Button value="bottomLeft">bottomLeft</Radio.Button>
|
||||
<Radio.Button value="bottomRight">bottomRight</Radio.Button>
|
||||
</Radio.Group>
|
||||
<br />
|
||||
<br />
|
||||
<Cascader options={options} placeholder="Please select" placement={placement} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<SetPlacementDemo />, mountNode);
|
||||
```
|
@ -42,7 +42,7 @@ Cascade selection box.
|
||||
| open | Set visible of cascader popup | boolean | - | 4.17.0 |
|
||||
| options | The data options of cascade | [Option](#Option)\[] | - | |
|
||||
| placeholder | The input placeholder | string | `Please select` | |
|
||||
| placement | Use preset popup align config from builtinPlacements:`bottomLeft` `bottomRight` `topLeft` `topRight` | string | `bottomLeft` | 4.17.0 |
|
||||
| placement | Use preset popup align config from builtinPlacements | `bottomLeft` `bottomRight` `topLeft` `topRight` | `bottomLeft` | 4.17.0 |
|
||||
| showSearch | Whether show search input in single mode | boolean \| [Object](#showSearch) | false | |
|
||||
| size | The input size | `large` \| `middle` \| `small` | - | |
|
||||
| style | The additional style | CSSProperties | - | |
|
||||
|
@ -17,7 +17,7 @@ import { ConfigContext } from '../config-provider';
|
||||
import type { SizeType } from '../config-provider/SizeContext';
|
||||
import SizeContext from '../config-provider/SizeContext';
|
||||
import getIcons from '../select/utils/iconUtil';
|
||||
import { getTransitionName } from '../_util/motion';
|
||||
import { getTransitionName, getTransitionDirection, SelectCommonPlacement } from '../_util/motion';
|
||||
|
||||
// Align the design since we use `rc-select` in root. This help:
|
||||
// - List search content will show all content
|
||||
@ -85,7 +85,7 @@ export interface CascaderProps<DataNodeType>
|
||||
multiple?: boolean;
|
||||
size?: SizeType;
|
||||
bordered?: boolean;
|
||||
|
||||
placement?: SelectCommonPlacement;
|
||||
suffixIcon?: React.ReactNode;
|
||||
options?: DataNodeType[];
|
||||
}
|
||||
@ -107,6 +107,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
|
||||
popupClassName,
|
||||
dropdownClassName,
|
||||
expandIcon,
|
||||
placement,
|
||||
showSearch,
|
||||
allowClear = true,
|
||||
notFoundContent,
|
||||
@ -210,6 +211,16 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
|
||||
prefixCls,
|
||||
});
|
||||
|
||||
// ===================== Placement =====================
|
||||
const getPlacement = () => {
|
||||
if (placement !== undefined) {
|
||||
return placement;
|
||||
}
|
||||
return direction === 'rtl'
|
||||
? ('bottomRight' as SelectCommonPlacement)
|
||||
: ('bottomLeft' as SelectCommonPlacement);
|
||||
};
|
||||
|
||||
// ==================== Render =====================
|
||||
return (
|
||||
<RcCascader
|
||||
@ -226,6 +237,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
|
||||
)}
|
||||
{...(restProps as any)}
|
||||
direction={mergedDirection}
|
||||
placement={getPlacement()}
|
||||
notFoundContent={mergedNotFoundContent}
|
||||
allowClear={allowClear}
|
||||
showSearch={mergedShowSearch}
|
||||
@ -238,7 +250,11 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
|
||||
dropdownClassName={mergedDropdownClassName}
|
||||
dropdownPrefixCls={customizePrefixCls || cascaderPrefixCls}
|
||||
choiceTransitionName={getTransitionName(rootPrefixCls, '', choiceTransitionName)}
|
||||
transitionName={getTransitionName(rootPrefixCls, 'slide-up', transitionName)}
|
||||
transitionName={getTransitionName(
|
||||
rootPrefixCls,
|
||||
getTransitionDirection(placement),
|
||||
transitionName,
|
||||
)}
|
||||
getPopupContainer={getPopupContainer || getContextPopupContainer}
|
||||
ref={ref}
|
||||
/>
|
||||
|
@ -43,7 +43,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
|
||||
| open | 控制浮层显隐 | boolean | - | 4.17.0 |
|
||||
| options | 可选项数据源 | [Option](#Option)\[] | - | |
|
||||
| placeholder | 输入框占位文本 | string | `请选择` | |
|
||||
| placement | 浮层预设位置:`bottomLeft` `bottomRight` `topLeft` `topRight` | string | `bottomLeft` | 4.17.0 |
|
||||
| placement | 浮层预设位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | `bottomLeft` | 4.17.0 |
|
||||
| showSearch | 在选择框中显示搜索框 | boolean \| [Object](#showSearch) | false | |
|
||||
| size | 输入框大小 | `large` \| `middle` \| `small` | - | |
|
||||
| style | 自定义样式 | CSSProperties | - | |
|
||||
|
@ -199,4 +199,36 @@ describe('DatePicker', () => {
|
||||
expect(year).toBe(startDate.format('YYYY'));
|
||||
expect(wrapper.find('.ant-picker-time-panel').length).toBe(1);
|
||||
});
|
||||
|
||||
it('placement api work correctly ', () => {
|
||||
const popupAlignDefault = (points = ['tl', 'bl'], offset = [0, 4]) => ({
|
||||
points,
|
||||
offset,
|
||||
overflow: {
|
||||
adjustX: 1,
|
||||
adjustY: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = mount(
|
||||
<DatePicker.RangePicker defaultValue={moment()} placement="bottomLeft" />,
|
||||
);
|
||||
expect(wrapper.find('Trigger').prop('popupAlign')).toEqual(popupAlignDefault(['tl', 'bl']));
|
||||
wrapper.setProps({
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
expect(wrapper.find('Trigger').prop('popupAlign')).toEqual(popupAlignDefault(['tr', 'br']));
|
||||
wrapper.setProps({
|
||||
placement: 'topLeft',
|
||||
});
|
||||
expect(wrapper.find('Trigger').prop('popupAlign')).toEqual(
|
||||
popupAlignDefault(['bl', 'tl'], [0, -4]),
|
||||
);
|
||||
wrapper.setProps({
|
||||
placement: 'topRight',
|
||||
});
|
||||
expect(wrapper.find('Trigger').prop('popupAlign')).toEqual(
|
||||
popupAlignDefault(['br', 'tr'], [0, -4]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2349,6 +2349,216 @@ exports[`renders ./components/date-picker/demo/mode.md correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/date-picker/demo/placement.md correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button ant-radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</label>
|
||||
</div>,
|
||||
<br />,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-picker"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-input"
|
||||
>
|
||||
<input
|
||||
autocomplete="off"
|
||||
placeholder="Select date"
|
||||
readonly=""
|
||||
size="12"
|
||||
title=""
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="ant-picker-suffix"
|
||||
>
|
||||
<span
|
||||
aria-label="calendar"
|
||||
class="anticon anticon-calendar"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="calendar"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<br />,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-picker ant-picker-range"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-input ant-picker-input-active"
|
||||
>
|
||||
<input
|
||||
autocomplete="off"
|
||||
placeholder="Start date"
|
||||
readonly=""
|
||||
size="12"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-picker-range-separator"
|
||||
>
|
||||
<span
|
||||
aria-label="to"
|
||||
class="ant-picker-separator"
|
||||
>
|
||||
<span
|
||||
aria-label="swap-right"
|
||||
class="anticon anticon-swap-right"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="swap-right"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="0 0 1024 1024"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M873.1 596.2l-164-208A32 32 0 00684 376h-64.8c-6.7 0-10.4 7.7-6.3 13l144.3 183H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h695.9c26.8 0 41.7-30.8 25.2-51.8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-picker-input"
|
||||
>
|
||||
<input
|
||||
autocomplete="off"
|
||||
placeholder="End date"
|
||||
readonly=""
|
||||
size="12"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-picker-active-bar"
|
||||
style="left:0;width:0;position:absolute"
|
||||
/>
|
||||
<span
|
||||
class="ant-picker-suffix"
|
||||
>
|
||||
<span
|
||||
aria-label="calendar"
|
||||
class="anticon anticon-calendar"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="calendar"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/date-picker/demo/presetted-ranges.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical"
|
||||
|
47
components/date-picker/demo/placement.md
Normal file
47
components/date-picker/demo/placement.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
order: 28
|
||||
title:
|
||||
zh-CN: 基本
|
||||
en-US: Basic
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
可以通过 `placement` 手动指定弹出的位置。
|
||||
|
||||
## en-US
|
||||
|
||||
You can manually specify the position of the popup via `placement`.
|
||||
|
||||
```jsx
|
||||
import { DatePicker, Space, Radio } from 'antd';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
const SetPlacementDemo = () => {
|
||||
const [placement, SetPlacement] = React.useState('topLeft');
|
||||
|
||||
const placementChange = e => {
|
||||
SetPlacement(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Radio.Group value={placement} onChange={placementChange}>
|
||||
<Radio.Button value="topLeft">topLeft</Radio.Button>
|
||||
<Radio.Button value="topRight">topRight</Radio.Button>
|
||||
<Radio.Button value="bottomLeft">bottomLeft</Radio.Button>
|
||||
<Radio.Button value="bottomRight">bottomRight</Radio.Button>
|
||||
</Radio.Group>
|
||||
<br />
|
||||
<br />
|
||||
<DatePicker placement={placement} />
|
||||
<br />
|
||||
<br />
|
||||
<RangePicker placement={placement} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<SetPlacementDemo />, mountNode);
|
||||
```
|
@ -10,7 +10,7 @@ import enUS from '../locale/en_US';
|
||||
import { ConfigContext, ConfigConsumerProps } from '../../config-provider';
|
||||
import SizeContext from '../../config-provider/SizeContext';
|
||||
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
|
||||
import { getRangePlaceholder } from '../util';
|
||||
import { getRangePlaceholder, transPlacement2DropdownAlign } from '../util';
|
||||
import { RangePickerProps, PickerLocale, getTimeProps, Components } from '.';
|
||||
import { PickerComponentClass } from './interface';
|
||||
|
||||
@ -43,6 +43,7 @@ export default function generateRangePicker<DateType>(
|
||||
prefixCls: customizePrefixCls,
|
||||
getPopupContainer: customGetPopupContainer,
|
||||
className,
|
||||
placement,
|
||||
size: customizeSize,
|
||||
bordered = true,
|
||||
placeholder,
|
||||
@ -73,6 +74,7 @@ export default function generateRangePicker<DateType>(
|
||||
</span>
|
||||
}
|
||||
ref={this.pickerRef}
|
||||
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
|
||||
placeholder={getRangePlaceholder(picker, locale, placeholder)}
|
||||
suffixIcon={picker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />}
|
||||
clearIcon={<CloseCircleFilled />}
|
||||
|
@ -7,7 +7,7 @@ import RCPicker from 'rc-picker';
|
||||
import { PickerMode } from 'rc-picker/lib/interface';
|
||||
import { GenerateConfig } from 'rc-picker/lib/generate/index';
|
||||
import enUS from '../locale/en_US';
|
||||
import { getPlaceholder } from '../util';
|
||||
import { getPlaceholder, transPlacement2DropdownAlign } from '../util';
|
||||
import devWarning from '../../_util/devWarning';
|
||||
import { ConfigContext, ConfigConsumerProps } from '../../config-provider';
|
||||
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
|
||||
@ -68,6 +68,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
|
||||
className,
|
||||
size: customizeSize,
|
||||
bordered = true,
|
||||
placement,
|
||||
placeholder,
|
||||
...restProps
|
||||
} = this.props;
|
||||
@ -105,6 +106,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
|
||||
suffixIcon={
|
||||
mergedPicker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />
|
||||
}
|
||||
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
|
||||
clearIcon={<CloseCircleFilled />}
|
||||
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
|
||||
nextIcon={<span className={`${prefixCls}-next-icon`} />}
|
||||
|
@ -17,6 +17,7 @@ import PickerTag from '../PickerTag';
|
||||
import { TimePickerLocale } from '../../time-picker';
|
||||
import generateSinglePicker from './generateSinglePicker';
|
||||
import generateRangePicker from './generateRangePicker';
|
||||
import { tuple } from '../../_util/type';
|
||||
|
||||
export const Components = { button: PickerButton, rangeItem: PickerTag };
|
||||
|
||||
@ -69,6 +70,8 @@ export function getTimeProps<DateType, DisabledTime>(
|
||||
showTime: showTimeObj,
|
||||
};
|
||||
}
|
||||
const DataPickerPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight');
|
||||
type DataPickerPlacement = typeof DataPickerPlacements[number];
|
||||
|
||||
type InjectDefaultProps<Props> = Omit<
|
||||
Props,
|
||||
@ -76,6 +79,7 @@ type InjectDefaultProps<Props> = Omit<
|
||||
> & {
|
||||
locale?: PickerLocale;
|
||||
size?: SizeType;
|
||||
placement?: DataPickerPlacement;
|
||||
bordered?: boolean;
|
||||
};
|
||||
|
||||
|
@ -69,6 +69,7 @@ The following APIs are shared by DatePicker, RangePicker.
|
||||
| panelRender | Customize panel render | (panelNode) => ReactNode | - | 4.5.0 |
|
||||
| picker | Set picker type | `date` \| `week` \| `month` \| `quarter` \| `year` | `date` | `quarter`: 4.1.0 |
|
||||
| placeholder | The placeholder of date input | string \| \[string,string] | - | |
|
||||
| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
|
||||
| popupStyle | To customize the style of the popup calendar | CSSProperties | {} | |
|
||||
| prevIcon | The custom prev icon | ReactNode | - | 4.17.0 |
|
||||
| size | To determine the size of the input box, the height of `large` and `small`, are 40px and 24px respectively, while default size is 32px | `large` \| `middle` \| `small` | - | |
|
||||
|
@ -70,6 +70,7 @@ import locale from 'antd/lib/locale/zh_CN';
|
||||
| panelRender | 自定义渲染面板 | (panelNode) => ReactNode | - | 4.5.0 |
|
||||
| picker | 设置选择器类型 | `date` \| `week` \| `month` \| `quarter` \| `year` | `date` | `quarter`: 4.1.0 |
|
||||
| placeholder | 输入框提示文字 | string \| \[string, string] | - | |
|
||||
| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
|
||||
| popupStyle | 额外的弹出日历样式 | CSSProperties | {} | |
|
||||
| prevIcon | 自定义上一个图标 | ReactNode | - | 4.17.0 |
|
||||
| size | 输入框大小,`large` 高度为 40px,`small` 为 24px,默认是 32px | `large` \| `middle` \| `small` | - | |
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { PickerMode } from 'rc-picker/lib/interface';
|
||||
import { DirectionType } from '../config-provider';
|
||||
import { SelectCommonPlacement } from '../_util/motion';
|
||||
import { PickerLocale } from './generatePicker';
|
||||
|
||||
export function getPlaceholder(
|
||||
@ -51,3 +53,56 @@ export function getRangePlaceholder(
|
||||
}
|
||||
return locale.lang.rangePlaceholder;
|
||||
}
|
||||
|
||||
export function transPlacement2DropdownAlign(
|
||||
direction: DirectionType,
|
||||
placement?: SelectCommonPlacement,
|
||||
) {
|
||||
const overflow = {
|
||||
adjustX: 1,
|
||||
adjustY: 1,
|
||||
};
|
||||
switch (placement) {
|
||||
case 'bottomLeft': {
|
||||
return {
|
||||
points: ['tl', 'bl'],
|
||||
offset: [0, 4],
|
||||
overflow,
|
||||
};
|
||||
}
|
||||
case 'bottomRight': {
|
||||
return {
|
||||
points: ['tr', 'br'],
|
||||
offset: [0, 4],
|
||||
overflow,
|
||||
};
|
||||
}
|
||||
case 'topLeft': {
|
||||
return {
|
||||
points: ['bl', 'tl'],
|
||||
offset: [0, -4],
|
||||
overflow,
|
||||
};
|
||||
}
|
||||
case 'topRight': {
|
||||
return {
|
||||
points: ['br', 'tr'],
|
||||
offset: [0, -4],
|
||||
overflow,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return direction === 'rtl'
|
||||
? {
|
||||
points: ['tr', 'br'],
|
||||
offset: [0, 4],
|
||||
overflow,
|
||||
}
|
||||
: {
|
||||
points: ['tl', 'bl'],
|
||||
offset: [0, 4],
|
||||
overflow,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4145,6 +4145,253 @@ exports[`renders ./components/select/demo/option-label-prop.md extend context co
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/select/demo/placement.md extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button ant-radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</label>
|
||||
</div>,
|
||||
<br />,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-select ant-select-single ant-select-show-arrow"
|
||||
style="width:120px"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-activedescendant="undefined_list_0"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="undefined_list"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="undefined_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
style="opacity:0"
|
||||
type="search"
|
||||
unselectable="on"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-item"
|
||||
title="HangZhou #310000"
|
||||
>
|
||||
HangZhou #310000
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-select-dropdown"
|
||||
style="opacity:0;pointer-events:none"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="undefined_list"
|
||||
role="listbox"
|
||||
style="height:0;width:0;overflow:hidden"
|
||||
>
|
||||
<div
|
||||
aria-label="HangZhou #310000"
|
||||
aria-selected="true"
|
||||
id="undefined_list_0"
|
||||
role="option"
|
||||
>
|
||||
HangZhou
|
||||
</div>
|
||||
<div
|
||||
aria-label="NingBo #315000"
|
||||
aria-selected="false"
|
||||
id="undefined_list_1"
|
||||
role="option"
|
||||
>
|
||||
NingBo
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="rc-virtual-list"
|
||||
style="position:relative"
|
||||
>
|
||||
<div
|
||||
class="rc-virtual-list-holder"
|
||||
style="max-height:256px;overflow-y:auto;overflow-anchor:none"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="rc-virtual-list-holder-inner"
|
||||
style="display:flex;flex-direction:column"
|
||||
>
|
||||
<div
|
||||
aria-selected="true"
|
||||
class="ant-select-item ant-select-item-option ant-select-item-option-active ant-select-item-option-selected"
|
||||
title="HangZhou #310000"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
HangZhou #310000
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-select-item ant-select-item-option"
|
||||
title="NingBo #315000"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
NingBo #315000
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-select-item ant-select-item-option"
|
||||
title="WenZhou #325000"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
WenZhou #325000
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-arrow"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-select-suffix"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/select/demo/responsive.md extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical"
|
||||
|
@ -1569,6 +1569,154 @@ exports[`renders ./components/select/demo/option-label-prop.md correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/select/demo/placement.md correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button ant-radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</label>
|
||||
</div>,
|
||||
<br />,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-select ant-select-single ant-select-show-arrow"
|
||||
style="width:120px"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-activedescendant="undefined_list_0"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="undefined_list"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="undefined_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
style="opacity:0"
|
||||
type="search"
|
||||
unselectable="on"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-item"
|
||||
title="HangZhou #310000"
|
||||
>
|
||||
HangZhou #310000
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-arrow"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-select-suffix"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/select/demo/responsive.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical"
|
||||
|
53
components/select/demo/placement.md
Normal file
53
components/select/demo/placement.md
Normal file
@ -0,0 +1,53 @@
|
||||
---
|
||||
order: 38
|
||||
title:
|
||||
zh-CN: 弹出位置
|
||||
en-US: Placement
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
可以通过 `placement` 手动指定弹出的位置。
|
||||
|
||||
## en-US
|
||||
|
||||
You can manually specify the position of the popup via `placement`.
|
||||
|
||||
```jsx
|
||||
import { Select, Radio } from 'antd';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const SetPlacementDemo = () => {
|
||||
const [placement, SetPlacement] = React.useState('topLeft');
|
||||
|
||||
const placementChange = e => {
|
||||
SetPlacement(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Radio.Group value={placement} onChange={placementChange}>
|
||||
<Radio.Button value="topLeft">topLeft</Radio.Button>
|
||||
<Radio.Button value="topRight">topRight</Radio.Button>
|
||||
<Radio.Button value="bottomLeft">bottomLeft</Radio.Button>
|
||||
<Radio.Button value="bottomRight">bottomRight</Radio.Button>
|
||||
</Radio.Group>
|
||||
<br />
|
||||
<br />
|
||||
<Select
|
||||
defaultValue="HangZhou"
|
||||
style={{ width: 120 }}
|
||||
dropdownMatchSelectWidth={false}
|
||||
placement={placement}
|
||||
>
|
||||
<Option value="HangZhou">HangZhou #310000</Option>
|
||||
<Option value="NingBo">NingBo #315000</Option>
|
||||
<Option value="WenZhou">WenZhou #325000</Option>
|
||||
</Select>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<SetPlacementDemo />, mountNode);
|
||||
```
|
@ -37,7 +37,7 @@ Select component to select value from options.
|
||||
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | |
|
||||
| dropdownRender | Customize dropdown content | (originNode: ReactNode) => ReactNode | - | |
|
||||
| dropdownStyle | The style of dropdown menu | CSSProperties | - | |
|
||||
| fieldNames | Customize node label, value, options field name | object | { label: `label`, value: `value`, options: `options` } | 4.17.0 |
|
||||
| fieldNames | Customize node title, key, options field name | object | { label: `label`, key: `key`, options: `options` } | 4.17.0 |
|
||||
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded | boolean \| function(inputValue, option) | true | |
|
||||
| filterSort | Sort function for search options sorting, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction | (optionA: Option, optionB: Option) => number | - | 4.9.0 |
|
||||
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. [Example](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
|
||||
@ -55,6 +55,7 @@ Select component to select value from options.
|
||||
| optionLabelProp | Which prop value of option will render as content of select. [Example](https://codesandbox.io/s/antd-reproduction-template-tk678) | string | `children` | |
|
||||
| options | Select options. Will get better perf than jsx definition | { label, value }\[] | - | |
|
||||
| placeholder | Placeholder of select | ReactNode | - | |
|
||||
| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
|
||||
| removeIcon | The custom remove icon | ReactNode | - | |
|
||||
| searchValue | The current input "search" text | string | - | |
|
||||
| showArrow | Whether to show the drop-down arrow | boolean | true(for single select), false(for multiple select) | |
|
||||
|
@ -9,7 +9,7 @@ import { OptionProps } from 'rc-select/lib/Option';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import getIcons from './utils/iconUtil';
|
||||
import SizeContext, { SizeType } from '../config-provider/SizeContext';
|
||||
import { getTransitionName } from '../_util/motion';
|
||||
import { getTransitionName, getTransitionDirection, SelectCommonPlacement } from '../_util/motion';
|
||||
|
||||
type RawValue = string | number;
|
||||
|
||||
@ -38,8 +38,9 @@ export interface SelectProps<
|
||||
OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType,
|
||||
> extends Omit<
|
||||
InternalSelectProps<ValueType, OptionType>,
|
||||
'inputIcon' | 'mode' | 'getInputElement' | 'getRawInputElement' | 'backfill'
|
||||
'inputIcon' | 'mode' | 'getInputElement' | 'getRawInputElement' | 'backfill' | 'placement'
|
||||
> {
|
||||
placement?: SelectCommonPlacement;
|
||||
mode?: 'multiple' | 'tags';
|
||||
}
|
||||
|
||||
@ -53,6 +54,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
|
||||
getPopupContainer,
|
||||
dropdownClassName,
|
||||
listHeight = 256,
|
||||
placement,
|
||||
listItemHeight = 24,
|
||||
size: customizeSize,
|
||||
notFoundContent,
|
||||
@ -123,17 +125,32 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
|
||||
className,
|
||||
);
|
||||
|
||||
// ===================== Placement =====================
|
||||
const getPlacement = () => {
|
||||
if (placement !== undefined) {
|
||||
return placement;
|
||||
}
|
||||
return direction === 'rtl'
|
||||
? ('bottomRight' as SelectCommonPlacement)
|
||||
: ('bottomLeft' as SelectCommonPlacement);
|
||||
};
|
||||
|
||||
return (
|
||||
<RcSelect<any, any>
|
||||
ref={ref as any}
|
||||
virtual={virtual}
|
||||
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
|
||||
{...selectProps}
|
||||
transitionName={getTransitionName(rootPrefixCls, 'slide-up', props.transitionName)}
|
||||
transitionName={getTransitionName(
|
||||
rootPrefixCls,
|
||||
getTransitionDirection(placement),
|
||||
props.transitionName,
|
||||
)}
|
||||
listHeight={listHeight}
|
||||
listItemHeight={listItemHeight}
|
||||
mode={mode as any}
|
||||
prefixCls={prefixCls}
|
||||
placement={getPlacement()}
|
||||
direction={direction}
|
||||
inputIcon={suffixIcon}
|
||||
menuItemSelectedIcon={itemIcon}
|
||||
|
@ -38,7 +38,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
|
||||
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | |
|
||||
| dropdownRender | 自定义下拉框内容 | (originNode: ReactNode) => ReactNode | - | |
|
||||
| dropdownStyle | 下拉菜单的 style 属性 | CSSProperties | - | |
|
||||
| fieldNames | 自定义节点 label、value、options 的字段 | object | { label: `label`, value: `value`, options: `options` } | 4.17.0 |
|
||||
| fieldNames | 自定义节点 label、key、options 的字段 | object | { label: `label`, key: `key`, options: `options` } | 4.17.0 |
|
||||
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 true,反之则返回 false | boolean \| function(inputValue, option) | true | |
|
||||
| filterSort | 搜索时对筛选结果项的排序函数, 类似[Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)里的 compareFunction | (optionA: Option, optionB: Option) => number | - | 4.9.0 |
|
||||
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
|
||||
@ -56,6 +56,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
|
||||
| optionLabelProp | 回填到选择框的 Option 的属性值,默认是 Option 的子元素。比如在子元素需要高亮效果时,此值可以设为 `value`。[示例](https://codesandbox.io/s/antd-reproduction-template-tk678) | string | `children` | |
|
||||
| options | 数据化配置选项内容,相比 jsx 定义会获得更好的渲染性能 | { label, value }\[] | - | |
|
||||
| placeholder | 选择框默认文本 | string | - | |
|
||||
| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
|
||||
| removeIcon | 自定义的多选框清除图标 | ReactNode | - | |
|
||||
| searchValue | 控制搜索文本 | string | - | |
|
||||
| showArrow | 是否显示下拉小箭头 | boolean | 单选为 true,多选为 false | |
|
||||
|
@ -42,6 +42,7 @@ import moment from 'moment';
|
||||
| minuteStep | Interval between minutes in picker | number | 1 | |
|
||||
| open | Whether to popup panel | boolean | false | |
|
||||
| placeholder | Display when there's no value | string \| \[string, string] | `Select a time` | |
|
||||
| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
|
||||
| popupClassName | The className of panel | string | - | |
|
||||
| popupStyle | The style of panel | CSSProperties | - | |
|
||||
| renderExtraFooter | Called from time picker panel to render some addon to its bottom | () => ReactNode | - | |
|
||||
|
@ -42,6 +42,7 @@ import moment from 'moment';
|
||||
| minuteStep | 分钟选项间隔 | number | 1 | |
|
||||
| open | 面板是否打开 | boolean | false | |
|
||||
| placeholder | 没有值的时候显示的内容 | string \| \[string, string] | `请选择时间` | |
|
||||
| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
|
||||
| popupClassName | 弹出层类名 | string | - | |
|
||||
| popupStyle | 弹出层样式对象 | object | - | |
|
||||
| renderExtraFooter | 选择框底部显示自定义的内容 | () => ReactNode | - | |
|
||||
|
@ -1144,6 +1144,433 @@ exports[`renders ./components/tree-select/demo/multiple.md extend context correc
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tree-select/demo/placement.md extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button ant-radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</label>
|
||||
</div>,
|
||||
<br />,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-select ant-tree-select ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="undefined_list"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="undefined_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
>
|
||||
Please select
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-select-dropdown ant-tree-select-dropdown"
|
||||
style="opacity:0;pointer-events:none;min-width:300px;max-height:400px;overflow:auto"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-select-tree"
|
||||
role="tree"
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
aria-label="for screen reader"
|
||||
disabled=""
|
||||
style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="ant-select-tree-treenode"
|
||||
style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
|
||||
>
|
||||
<div
|
||||
class="ant-select-tree-indent"
|
||||
>
|
||||
<div
|
||||
class="ant-select-tree-indent-unit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-tree-list"
|
||||
style="position:relative"
|
||||
>
|
||||
<div
|
||||
class="ant-select-tree-list-holder"
|
||||
style="max-height:256px;overflow-y:auto;overflow-anchor:none"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="ant-select-tree-list-holder-inner"
|
||||
style="display:flex;flex-direction:column"
|
||||
>
|
||||
<div
|
||||
aria-grabbed="false"
|
||||
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
|
||||
draggable="false"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-tree-indent"
|
||||
/>
|
||||
<span
|
||||
class="ant-select-tree-switcher ant-select-tree-switcher_open"
|
||||
>
|
||||
<span
|
||||
aria-label="caret-down"
|
||||
class="anticon anticon-caret-down ant-select-tree-switcher-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="caret-down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="0 0 1024 1024"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-tree-node-content-wrapper ant-select-tree-node-content-wrapper-open"
|
||||
title="parent 1"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-title"
|
||||
>
|
||||
parent 1
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-grabbed="false"
|
||||
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
|
||||
draggable="false"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-tree-indent"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-indent-unit ant-select-tree-indent-unit-start ant-select-tree-indent-unit-end"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-tree-switcher ant-select-tree-switcher_open"
|
||||
>
|
||||
<span
|
||||
aria-label="caret-down"
|
||||
class="anticon anticon-caret-down ant-select-tree-switcher-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="caret-down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="0 0 1024 1024"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-tree-node-content-wrapper ant-select-tree-node-content-wrapper-open"
|
||||
title="parent 1-0"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-title"
|
||||
>
|
||||
parent 1-0
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-grabbed="false"
|
||||
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
|
||||
draggable="false"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-tree-indent"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-indent-unit ant-select-tree-indent-unit-start ant-select-tree-indent-unit-end"
|
||||
/>
|
||||
<span
|
||||
class="ant-select-tree-indent-unit ant-select-tree-indent-unit-start"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-tree-switcher ant-select-tree-switcher-noop"
|
||||
/>
|
||||
<span
|
||||
class="ant-select-tree-node-content-wrapper ant-select-tree-node-content-wrapper-normal"
|
||||
title="leaf1"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-title"
|
||||
>
|
||||
leaf1
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-grabbed="false"
|
||||
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
|
||||
draggable="false"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-tree-indent"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-indent-unit ant-select-tree-indent-unit-start ant-select-tree-indent-unit-end"
|
||||
/>
|
||||
<span
|
||||
class="ant-select-tree-indent-unit ant-select-tree-indent-unit-start"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-tree-switcher ant-select-tree-switcher-noop"
|
||||
/>
|
||||
<span
|
||||
class="ant-select-tree-node-content-wrapper ant-select-tree-node-content-wrapper-normal"
|
||||
title="leaf2"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-title"
|
||||
>
|
||||
leaf2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-grabbed="false"
|
||||
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
|
||||
draggable="false"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-tree-indent"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-indent-unit ant-select-tree-indent-unit-start ant-select-tree-indent-unit-end"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-tree-switcher ant-select-tree-switcher_open"
|
||||
>
|
||||
<span
|
||||
aria-label="caret-down"
|
||||
class="anticon anticon-caret-down ant-select-tree-switcher-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="caret-down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="0 0 1024 1024"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-tree-node-content-wrapper ant-select-tree-node-content-wrapper-open"
|
||||
title="parent 1-1"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-title"
|
||||
>
|
||||
parent 1-1
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-grabbed="false"
|
||||
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
|
||||
draggable="false"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-tree-indent"
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-indent-unit ant-select-tree-indent-unit-start ant-select-tree-indent-unit-end"
|
||||
/>
|
||||
<span
|
||||
class="ant-select-tree-indent-unit ant-select-tree-indent-unit-end"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-tree-switcher ant-select-tree-switcher-noop"
|
||||
/>
|
||||
<span
|
||||
class="ant-select-tree-node-content-wrapper ant-select-tree-node-content-wrapper-normal"
|
||||
title=""
|
||||
>
|
||||
<span
|
||||
class="ant-select-tree-title"
|
||||
>
|
||||
<b
|
||||
style="color:#08c"
|
||||
>
|
||||
leaf3
|
||||
</b>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-arrow"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-select-suffix"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tree-select/demo/suffix.md extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-select ant-tree-select ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
|
||||
|
@ -257,6 +257,148 @@ exports[`renders ./components/tree-select/demo/multiple.md correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tree-select/demo/placement.md correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button ant-radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="topRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomLeft"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="bottomRight"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</label>
|
||||
</div>,
|
||||
<br />,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-select ant-tree-select ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="undefined_list"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="undefined_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
>
|
||||
Please select
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-arrow"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-select-suffix"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tree-select/demo/suffix.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-select ant-tree-select ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
|
||||
|
64
components/tree-select/demo/placement.md
Normal file
64
components/tree-select/demo/placement.md
Normal file
@ -0,0 +1,64 @@
|
||||
---
|
||||
order: 8
|
||||
title:
|
||||
zh-CN: 弹出位置
|
||||
en-US: Placement
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
可以通过 `placement` 手动指定弹出的位置。
|
||||
|
||||
## en-US
|
||||
|
||||
You can manually specify the position of the popup via `placement`.
|
||||
|
||||
```jsx
|
||||
import React, { useState } from 'react';
|
||||
import { TreeSelect, Radio } from 'antd';
|
||||
|
||||
const { TreeNode } = TreeSelect;
|
||||
|
||||
const Demo = () => {
|
||||
const [placement, SetPlacement] = useState('topLeft');
|
||||
|
||||
const placementChange = e => {
|
||||
SetPlacement(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Radio.Group value={placement} onChange={placementChange}>
|
||||
<Radio.Button value="topLeft">topLeft</Radio.Button>
|
||||
<Radio.Button value="topRight">topRight</Radio.Button>
|
||||
<Radio.Button value="bottomLeft">bottomLeft</Radio.Button>
|
||||
<Radio.Button value="bottomRight">bottomRight</Radio.Button>
|
||||
</Radio.Group>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<TreeSelect
|
||||
showSearch
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto', minWidth: 300 }}
|
||||
placeholder="Please select"
|
||||
dropdownMatchSelectWidth={false}
|
||||
placement={placement}
|
||||
allowClear
|
||||
treeDefaultExpandAll
|
||||
>
|
||||
<TreeNode value="parent 1" title="parent 1">
|
||||
<TreeNode value="parent 1-0" title="parent 1-0">
|
||||
<TreeNode value="leaf1" title="leaf1" />
|
||||
<TreeNode value="leaf2" title="leaf2" />
|
||||
</TreeNode>
|
||||
<TreeNode value="parent 1-1" title="parent 1-1">
|
||||
<TreeNode value="leaf3" title={<b style={{ color: '#08c' }}>leaf3</b>} />
|
||||
</TreeNode>
|
||||
</TreeNode>
|
||||
</TreeSelect>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<Demo />, mountNode);
|
||||
```
|
@ -37,6 +37,7 @@ Tree selection control.
|
||||
| multiple | Support multiple or not, will be `true` when enable `treeCheckable` | boolean | false | |
|
||||
| notFoundContent | Specify content to show when no result matches | ReactNode | `Not Found` | |
|
||||
| placeholder | Placeholder of the select input | string | - | |
|
||||
| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
|
||||
| searchValue | Work with `onSearch` to make search value controlled | string | - | |
|
||||
| showArrow | Whether to show the `suffixIcon`,when single selection mode, default `true` | boolean | - | |
|
||||
| showCheckedStrategy | The way show selected item in box when `treeCheckable` set. **Default:** just show child nodes. **`TreeSelect.SHOW_ALL`:** show all checked treeNodes (include parent treeNode). **`TreeSelect.SHOW_PARENT`:** show checked treeNodes (just show parent treeNode) | `TreeSelect.SHOW_ALL` \| `TreeSelect.SHOW_PARENT` \| `TreeSelect.SHOW_CHILD` | `TreeSelect.SHOW_CHILD` | |
|
||||
|
@ -16,7 +16,7 @@ import { AntTreeNodeProps, TreeProps } from '../tree';
|
||||
import getIcons from '../select/utils/iconUtil';
|
||||
import renderSwitcherIcon from '../tree/utils/iconUtil';
|
||||
import SizeContext, { SizeType } from '../config-provider/SizeContext';
|
||||
import { getTransitionName } from '../_util/motion';
|
||||
import { getTransitionName, getTransitionDirection, SelectCommonPlacement } from '../_util/motion';
|
||||
|
||||
type RawValue = string | number;
|
||||
|
||||
@ -43,6 +43,7 @@ export interface TreeSelectProps<
|
||||
> {
|
||||
suffixIcon?: React.ReactNode;
|
||||
size?: SizeType;
|
||||
placement?: SelectCommonPlacement;
|
||||
bordered?: boolean;
|
||||
treeLine?: TreeProps['showLine'];
|
||||
}
|
||||
@ -57,6 +58,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
||||
multiple,
|
||||
listHeight = 256,
|
||||
listItemHeight = 26,
|
||||
placement,
|
||||
notFoundContent,
|
||||
switcherIcon,
|
||||
treeLine,
|
||||
@ -119,6 +121,16 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
||||
'switcherIcon',
|
||||
]);
|
||||
|
||||
// ===================== Placement =====================
|
||||
const getPlacement = () => {
|
||||
if (placement !== undefined) {
|
||||
return placement;
|
||||
}
|
||||
return direction === 'rtl'
|
||||
? ('bottomRight' as SelectCommonPlacement)
|
||||
: ('bottomLeft' as SelectCommonPlacement);
|
||||
};
|
||||
|
||||
const mergedSize = customizeSize || size;
|
||||
const mergedClassName = classNames(
|
||||
!customizePrefixCls && treeSelectPrefixCls,
|
||||
@ -148,6 +160,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
||||
treeLine={!!treeLine}
|
||||
inputIcon={suffixIcon}
|
||||
multiple={multiple}
|
||||
placement={getPlacement()}
|
||||
removeIcon={removeIcon}
|
||||
clearIcon={clearIcon}
|
||||
switcherIcon={(nodeProps: AntTreeNodeProps) =>
|
||||
@ -159,7 +172,11 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
||||
treeMotion={null}
|
||||
dropdownClassName={mergedDropdownClassName}
|
||||
choiceTransitionName={getTransitionName(rootPrefixCls, '', choiceTransitionName)}
|
||||
transitionName={getTransitionName(rootPrefixCls, 'slide-up', transitionName)}
|
||||
transitionName={getTransitionName(
|
||||
rootPrefixCls,
|
||||
getTransitionDirection(placement),
|
||||
transitionName,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -38,6 +38,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Ax4DA0njr/TreeSelect.svg
|
||||
| multiple | 支持多选(当设置 treeCheckable 时自动变为 true) | boolean | false | |
|
||||
| notFoundContent | 当下拉列表为空时显示的内容 | ReactNode | `Not Found` | |
|
||||
| placeholder | 选择框默认文字 | string | - | |
|
||||
| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
|
||||
| searchValue | 搜索框的值,可以通过 `onSearch` 获取用户输入 | string | - | |
|
||||
| showArrow | 是否显示 `suffixIcon`,单选模式下默认 `true` | boolean | - | |
|
||||
| showCheckedStrategy | 配置 `treeCheckable` 时,定义选中项回填的方式。`TreeSelect.SHOW_ALL`: 显示所有选中节点(包括父节点)。`TreeSelect.SHOW_PARENT`: 只显示父节点(当父节点下所有子节点都选中时)。 默认只显示子节点 | `TreeSelect.SHOW_ALL` \| `TreeSelect.SHOW_PARENT` \| `TreeSelect.SHOW_CHILD` | `TreeSelect.SHOW_CHILD` | |
|
||||
|
Loading…
Reference in New Issue
Block a user