mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 17:44:35 +08:00
feat: Cascader.Panel support (#45089)
* chore: init * chore: panel style * docs: update demo * chore: fill style * test: update snapshot * docs: update demo * chore: push * docs: update desc * chore: fix icons * chore: use shared hooks * test: update snapshot * chore: fix lint
This commit is contained in:
parent
69d048784f
commit
7e692ad585
64
components/cascader/Panel.tsx
Normal file
64
components/cascader/Panel.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Panel } from 'rc-cascader';
|
||||
import type { PickType } from 'rc-cascader/lib/Panel';
|
||||
|
||||
import type { CascaderProps } from '.';
|
||||
import DefaultRenderEmpty from '../config-provider/defaultRenderEmpty';
|
||||
import useBase from './hooks/useBase';
|
||||
import useCheckable from './hooks/useCheckable';
|
||||
import useColumnIcons from './hooks/useColumnIcons';
|
||||
import useStyle from './style';
|
||||
import usePanelStyle from './style/panel';
|
||||
|
||||
export type PanelPickType = Exclude<PickType, 'checkable'> | 'multiple' | 'rootClassName';
|
||||
|
||||
export type CascaderPanelProps = Pick<CascaderProps, PanelPickType>;
|
||||
|
||||
export default function CascaderPanel(props: CascaderPanelProps) {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
multiple,
|
||||
rootClassName,
|
||||
notFoundContent,
|
||||
direction,
|
||||
expandIcon,
|
||||
} = props;
|
||||
|
||||
const [prefixCls, cascaderPrefixCls, mergedDirection, renderEmpty] = useBase(
|
||||
customizePrefixCls,
|
||||
direction,
|
||||
);
|
||||
|
||||
const [, hashId] = useStyle(cascaderPrefixCls);
|
||||
usePanelStyle(cascaderPrefixCls);
|
||||
|
||||
const isRtl = mergedDirection === 'rtl';
|
||||
|
||||
// ===================== Icon ======================
|
||||
const [mergedExpandIcon, loadingIcon] = useColumnIcons(prefixCls, isRtl, expandIcon);
|
||||
|
||||
// ===================== Empty =====================
|
||||
const mergedNotFoundContent = notFoundContent || renderEmpty?.('Cascader') || (
|
||||
<DefaultRenderEmpty componentName="Cascader" />
|
||||
);
|
||||
|
||||
// =================== Multiple ====================
|
||||
const checkable = useCheckable(cascaderPrefixCls, multiple);
|
||||
|
||||
// ==================== Render =====================
|
||||
|
||||
return (
|
||||
<Panel
|
||||
{...props}
|
||||
checkable={checkable}
|
||||
prefixCls={cascaderPrefixCls}
|
||||
className={classNames(className, hashId, rootClassName)}
|
||||
notFoundContent={mergedNotFoundContent}
|
||||
direction={mergedDirection}
|
||||
expandIcon={mergedExpandIcon}
|
||||
loadingIcon={loadingIcon}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1729,6 +1729,248 @@ exports[`renders components/cascader/demo/multiple.tsx extend context correctly
|
||||
|
||||
exports[`renders components/cascader/demo/multiple.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/cascader/demo/panel.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-flex ant-flex-align-flex-start ant-flex-gap-small ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-cascader-panel"
|
||||
>
|
||||
<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
|
||||
class="ant-cascader-panel"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-checkbox"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-checkbox-inner"
|
||||
/>
|
||||
</span>
|
||||
<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"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-checkbox"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-checkbox-inner"
|
||||
/>
|
||||
</span>
|
||||
<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
|
||||
class="ant-cascader-panel ant-cascader-panel-empty"
|
||||
>
|
||||
<div
|
||||
class="ant-empty ant-empty-normal ant-empty-small"
|
||||
>
|
||||
<div
|
||||
class="ant-empty-image"
|
||||
>
|
||||
<svg
|
||||
height="41"
|
||||
viewBox="0 0 64 41"
|
||||
width="64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
transform="translate(0 1)"
|
||||
>
|
||||
<ellipse
|
||||
cx="32"
|
||||
cy="33"
|
||||
fill="#f5f5f5"
|
||||
rx="32"
|
||||
ry="7"
|
||||
/>
|
||||
<g
|
||||
fill-rule="nonzero"
|
||||
stroke="#d9d9d9"
|
||||
>
|
||||
<path
|
||||
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
|
||||
/>
|
||||
<path
|
||||
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
|
||||
fill="#fafafa"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="ant-empty-description"
|
||||
>
|
||||
No data
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/cascader/demo/panel.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/cascader/demo/placement.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
|
@ -710,6 +710,246 @@ exports[`renders components/cascader/demo/multiple.tsx correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/cascader/demo/panel.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-flex ant-flex-align-flex-start ant-flex-gap-small ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-cascader-panel"
|
||||
>
|
||||
<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
|
||||
class="ant-cascader-panel"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-checkbox"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-checkbox-inner"
|
||||
/>
|
||||
</span>
|
||||
<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"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-checkbox"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-checkbox-inner"
|
||||
/>
|
||||
</span>
|
||||
<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
|
||||
class="ant-cascader-panel ant-cascader-panel-empty"
|
||||
>
|
||||
<div
|
||||
class="ant-empty ant-empty-normal ant-empty-small"
|
||||
>
|
||||
<div
|
||||
class="ant-empty-image"
|
||||
>
|
||||
<svg
|
||||
height="41"
|
||||
viewBox="0 0 64 41"
|
||||
width="64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
transform="translate(0 1)"
|
||||
>
|
||||
<ellipse
|
||||
cx="32"
|
||||
cy="33"
|
||||
fill="#f5f5f5"
|
||||
rx="32"
|
||||
ry="7"
|
||||
/>
|
||||
<g
|
||||
fill-rule="nonzero"
|
||||
stroke="#d9d9d9"
|
||||
>
|
||||
<path
|
||||
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
|
||||
/>
|
||||
<path
|
||||
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
|
||||
fill="#fafafa"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="ant-empty-description"
|
||||
>
|
||||
No data
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/cascader/demo/placement.tsx correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
|
7
components/cascader/demo/panel.md
Normal file
7
components/cascader/demo/panel.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
适用于一些需要内嵌适用的场景。
|
||||
|
||||
## en-US
|
||||
|
||||
Used for inline view case.
|
57
components/cascader/demo/panel.tsx
Normal file
57
components/cascader/demo/panel.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { Cascader, Flex } from 'antd';
|
||||
|
||||
interface Option {
|
||||
value: string | number;
|
||||
label: string;
|
||||
children?: Option[];
|
||||
}
|
||||
|
||||
const options: Option[] = [
|
||||
{
|
||||
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 onChange = (value: string[]) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Flex vertical gap="small" align="flex-start">
|
||||
<Cascader.Panel options={options} onChange={onChange} />
|
||||
<Cascader.Panel multiple options={options} onChange={onChange} />
|
||||
<Cascader.Panel />
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default App;
|
22
components/cascader/hooks/useBase.ts
Normal file
22
components/cascader/hooks/useBase.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { ConfigContext, type RenderEmptyHandler } from '../../config-provider';
|
||||
|
||||
export default function useBase(
|
||||
customizePrefixCls?: string,
|
||||
direction?: 'ltr' | 'rtl',
|
||||
): [
|
||||
prefixCls: string,
|
||||
cascaderPrefixCls: string,
|
||||
direction?: 'ltr' | 'rtl',
|
||||
renderEmpty?: RenderEmptyHandler,
|
||||
] {
|
||||
const { getPrefixCls, direction: rootDirection, renderEmpty } = React.useContext(ConfigContext);
|
||||
|
||||
const mergedDirection = direction || rootDirection;
|
||||
|
||||
const prefixCls = getPrefixCls('select', customizePrefixCls);
|
||||
const cascaderPrefixCls = getPrefixCls('cascader', customizePrefixCls);
|
||||
|
||||
return [prefixCls, cascaderPrefixCls, mergedDirection, renderEmpty];
|
||||
}
|
8
components/cascader/hooks/useCheckable.tsx
Normal file
8
components/cascader/hooks/useCheckable.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default function useCheckable(cascaderPrefixCls: string, multiple?: boolean) {
|
||||
return React.useMemo(
|
||||
() => (multiple ? <span className={`${cascaderPrefixCls}-checkbox-inner`} /> : false),
|
||||
[multiple],
|
||||
);
|
||||
}
|
23
components/cascader/hooks/useColumnIcons.tsx
Normal file
23
components/cascader/hooks/useColumnIcons.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
|
||||
import RightOutlined from '@ant-design/icons/RightOutlined';
|
||||
|
||||
export default function useColumnIcons(
|
||||
prefixCls: string,
|
||||
rtl: boolean,
|
||||
expandIcon?: React.ReactNode,
|
||||
) {
|
||||
let mergedExpandIcon = expandIcon;
|
||||
if (!expandIcon) {
|
||||
mergedExpandIcon = rtl ? <LeftOutlined /> : <RightOutlined />;
|
||||
}
|
||||
|
||||
const loadingIcon = (
|
||||
<span className={`${prefixCls}-menu-item-loading-icon`}>
|
||||
<LoadingOutlined spin />
|
||||
</span>
|
||||
);
|
||||
|
||||
return [mergedExpandIcon, loadingIcon];
|
||||
}
|
@ -36,6 +36,7 @@ Cascade selection box.
|
||||
<code src="./demo/custom-dropdown.tsx">Custom dropdown</code>
|
||||
<code src="./demo/placement.tsx">Placement</code>
|
||||
<code src="./demo/status.tsx">Status</code>
|
||||
<code src="./demo/panel.tsx" version=">= 5.10.0">Panel</code>
|
||||
<code src="./demo/render-panel.tsx" debug>_InternalPanelDoNotUseOrYouWillBeFired</code>
|
||||
|
||||
## API
|
||||
|
@ -1,7 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
|
||||
import RightOutlined from '@ant-design/icons/RightOutlined';
|
||||
import classNames from 'classnames';
|
||||
import type {
|
||||
BaseOptionType,
|
||||
@ -29,9 +26,13 @@ import type { SizeType } from '../config-provider/SizeContext';
|
||||
import { FormItemInputContext } from '../form/context';
|
||||
import useSelectStyle from '../select/style';
|
||||
import useBuiltinPlacements from '../select/useBuiltinPlacements';
|
||||
import useShowArrow from '../select/useShowArrow';
|
||||
import useIcons from '../select/useIcons';
|
||||
import useShowArrow from '../select/useShowArrow';
|
||||
import { useCompactItemContext } from '../space/Compact';
|
||||
import useBase from './hooks/useBase';
|
||||
import useCheckable from './hooks/useCheckable';
|
||||
import useColumnIcons from './hooks/useColumnIcons';
|
||||
import CascaderPanel from './Panel';
|
||||
import useStyle from './style';
|
||||
|
||||
// Align the design since we use `rc-select` in root. This help:
|
||||
@ -174,15 +175,10 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
const {
|
||||
getPopupContainer: getContextPopupContainer,
|
||||
getPrefixCls,
|
||||
renderEmpty,
|
||||
direction: rootDirection,
|
||||
popupOverflow,
|
||||
cascader,
|
||||
} = React.useContext(ConfigContext);
|
||||
|
||||
const mergedDirection = direction || rootDirection;
|
||||
const isRtl = mergedDirection === 'rtl';
|
||||
|
||||
// =================== Form =====================
|
||||
const {
|
||||
status: contextStatus,
|
||||
@ -205,20 +201,25 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
);
|
||||
}
|
||||
|
||||
// =================== No Found ====================
|
||||
const mergedNotFoundContent = notFoundContent || renderEmpty?.('Cascader') || (
|
||||
<DefaultRenderEmpty componentName="Cascader" />
|
||||
);
|
||||
|
||||
// ==================== Prefix =====================
|
||||
const [prefixCls, cascaderPrefixCls, mergedDirection, renderEmpty] = useBase(
|
||||
customizePrefixCls,
|
||||
direction,
|
||||
);
|
||||
const isRtl = mergedDirection === 'rtl';
|
||||
|
||||
const rootPrefixCls = getPrefixCls();
|
||||
const prefixCls = getPrefixCls('select', customizePrefixCls);
|
||||
const cascaderPrefixCls = getPrefixCls('cascader', customizePrefixCls);
|
||||
|
||||
const [wrapSelectSSR, hashId] = useSelectStyle(prefixCls);
|
||||
const [wrapCascaderSSR] = useStyle(cascaderPrefixCls);
|
||||
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
|
||||
// =================== No Found ====================
|
||||
const mergedNotFoundContent = notFoundContent || renderEmpty?.('Cascader') || (
|
||||
<DefaultRenderEmpty componentName="Cascader" />
|
||||
);
|
||||
|
||||
// =================== Dropdown ====================
|
||||
const mergedDropdownClassName = classNames(
|
||||
popupClassName || dropdownClassName,
|
||||
@ -258,22 +259,10 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
const mergedDisabled = customDisabled ?? disabled;
|
||||
|
||||
// ===================== Icon ======================
|
||||
let mergedExpandIcon = expandIcon;
|
||||
if (!expandIcon) {
|
||||
mergedExpandIcon = isRtl ? <LeftOutlined /> : <RightOutlined />;
|
||||
}
|
||||
|
||||
const loadingIcon = (
|
||||
<span className={`${prefixCls}-menu-item-loading-icon`}>
|
||||
<LoadingOutlined spin />
|
||||
</span>
|
||||
);
|
||||
const [mergedExpandIcon, loadingIcon] = useColumnIcons(prefixCls, isRtl, expandIcon);
|
||||
|
||||
// =================== Multiple ====================
|
||||
const checkable = React.useMemo(
|
||||
() => (multiple ? <span className={`${cascaderPrefixCls}-checkbox-inner`} /> : false),
|
||||
[multiple],
|
||||
);
|
||||
const checkable = useCheckable(cascaderPrefixCls, multiple);
|
||||
|
||||
// ===================== Icons =====================
|
||||
const showSuffixIcon = useShowArrow(props.suffixIcon, showArrow);
|
||||
@ -349,6 +338,7 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
displayName: string;
|
||||
SHOW_PARENT: typeof SHOW_PARENT;
|
||||
SHOW_CHILD: typeof SHOW_CHILD;
|
||||
Panel: typeof CascaderPanel;
|
||||
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
|
||||
};
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
@ -361,6 +351,7 @@ const PurePanel = genPurePanel(Cascader);
|
||||
|
||||
Cascader.SHOW_PARENT = SHOW_PARENT;
|
||||
Cascader.SHOW_CHILD = SHOW_CHILD;
|
||||
Cascader.Panel = CascaderPanel;
|
||||
Cascader._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;
|
||||
|
||||
export default Cascader;
|
||||
|
@ -37,6 +37,7 @@ demo:
|
||||
<code src="./demo/custom-dropdown.tsx">扩展菜单</code>
|
||||
<code src="./demo/placement.tsx">弹出位置</code>
|
||||
<code src="./demo/status.tsx">自定义状态</code>
|
||||
<code src="./demo/panel.tsx" version=">= 5.10.0">面板使用</code>
|
||||
<code src="./demo/render-panel.tsx" debug>_InternalPanelDoNotUseOrYouWillBeFired</code>
|
||||
|
||||
## API
|
||||
|
119
components/cascader/style/columns.ts
Normal file
119
components/cascader/style/columns.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import type { CSSInterpolation } from '@ant-design/cssinjs';
|
||||
|
||||
import type { CascaderToken } from '.';
|
||||
import { getStyle as getCheckboxStyle } from '../../checkbox/style';
|
||||
import { textEllipsis } from '../../style';
|
||||
import type { GenerateStyle } from '../../theme/internal';
|
||||
|
||||
const getColumnsStyle: GenerateStyle<CascaderToken> = (token: CascaderToken): CSSInterpolation => {
|
||||
const { prefixCls, componentCls } = token;
|
||||
|
||||
const cascaderMenuItemCls = `${componentCls}-menu-item`;
|
||||
const iconCls = `
|
||||
&${cascaderMenuItemCls}-expand ${cascaderMenuItemCls}-expand-icon,
|
||||
${cascaderMenuItemCls}-loading-icon
|
||||
`;
|
||||
|
||||
return [
|
||||
// ==================== Checkbox ====================
|
||||
getCheckboxStyle(`${prefixCls}-checkbox`, token),
|
||||
|
||||
{
|
||||
[componentCls]: {
|
||||
// ================== Checkbox ==================
|
||||
'&-checkbox': {
|
||||
top: 0,
|
||||
marginInlineEnd: token.paddingXS,
|
||||
},
|
||||
|
||||
// ==================== Menu ====================
|
||||
// >>> Menus
|
||||
'&-menus': {
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'flex-start',
|
||||
|
||||
[`&${componentCls}-menu-empty`]: {
|
||||
[`${componentCls}-menu`]: {
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
|
||||
[cascaderMenuItemCls]: {
|
||||
color: token.colorTextDisabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// >>> Menu
|
||||
'&-menu': {
|
||||
flexGrow: 1,
|
||||
flexShrink: 0,
|
||||
minWidth: token.controlItemWidth,
|
||||
height: token.dropdownHeight,
|
||||
margin: 0,
|
||||
padding: token.menuPadding,
|
||||
overflow: 'auto',
|
||||
verticalAlign: 'top',
|
||||
listStyle: 'none',
|
||||
'-ms-overflow-style': '-ms-autohiding-scrollbar', // https://github.com/ant-design/ant-design/issues/11857
|
||||
|
||||
'&:not(:last-child)': {
|
||||
borderInlineEnd: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`,
|
||||
},
|
||||
|
||||
'&-item': {
|
||||
...textEllipsis,
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'center',
|
||||
padding: token.optionPadding,
|
||||
lineHeight: token.lineHeight,
|
||||
cursor: 'pointer',
|
||||
transition: `all ${token.motionDurationMid}`,
|
||||
borderRadius: token.borderRadiusSM,
|
||||
|
||||
'&:hover': {
|
||||
background: token.controlItemBgHover,
|
||||
},
|
||||
'&-disabled': {
|
||||
color: token.colorTextDisabled,
|
||||
cursor: 'not-allowed',
|
||||
|
||||
'&:hover': {
|
||||
background: 'transparent',
|
||||
},
|
||||
|
||||
[iconCls]: {
|
||||
color: token.colorTextDisabled,
|
||||
},
|
||||
},
|
||||
|
||||
[`&-active:not(${cascaderMenuItemCls}-disabled)`]: {
|
||||
[`&, &:hover`]: {
|
||||
fontWeight: token.optionSelectedFontWeight,
|
||||
backgroundColor: token.optionSelectedBg,
|
||||
},
|
||||
},
|
||||
|
||||
'&-content': {
|
||||
flex: 'auto',
|
||||
},
|
||||
|
||||
[iconCls]: {
|
||||
marginInlineStart: token.paddingXXS,
|
||||
color: token.colorTextDescription,
|
||||
fontSize: token.fontSizeIcon,
|
||||
},
|
||||
|
||||
'&-keyword': {
|
||||
color: token.colorHighlight,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default getColumnsStyle;
|
@ -1,9 +1,10 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import { getStyle as getCheckboxStyle } from '../../checkbox/style';
|
||||
import { textEllipsis } from '../../style';
|
||||
|
||||
import { genCompactItemStyle } from '../../style/compact-item';
|
||||
import type { GlobalToken } from '../../theme';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook } from '../../theme/internal';
|
||||
import getColumnsStyle from './columns';
|
||||
|
||||
export interface ComponentToken {
|
||||
/**
|
||||
@ -43,16 +44,11 @@ export interface ComponentToken {
|
||||
menuPadding: CSSProperties['padding'];
|
||||
}
|
||||
|
||||
type CascaderToken = FullToken<'Cascader'>;
|
||||
export type CascaderToken = FullToken<'Cascader'>;
|
||||
|
||||
// =============================== Base ===============================
|
||||
const genBaseStyle: GenerateStyle<CascaderToken> = (token) => {
|
||||
const { prefixCls, componentCls, antCls } = token;
|
||||
const cascaderMenuItemCls = `${componentCls}-menu-item`;
|
||||
const iconCls = `
|
||||
&${cascaderMenuItemCls}-expand ${cascaderMenuItemCls}-expand-icon,
|
||||
${cascaderMenuItemCls}-loading-icon
|
||||
`;
|
||||
const { componentCls, antCls } = token;
|
||||
|
||||
return [
|
||||
// =====================================================
|
||||
@ -68,107 +64,12 @@ const genBaseStyle: GenerateStyle<CascaderToken> = (token) => {
|
||||
// =====================================================
|
||||
{
|
||||
[`${componentCls}-dropdown`]: [
|
||||
// ==================== Checkbox ====================
|
||||
getCheckboxStyle(`${prefixCls}-checkbox`, token),
|
||||
{
|
||||
[`&${antCls}-select-dropdown`]: {
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
[componentCls]: {
|
||||
// ================== Checkbox ==================
|
||||
'&-checkbox': {
|
||||
top: 0,
|
||||
marginInlineEnd: token.paddingXS,
|
||||
},
|
||||
|
||||
// ==================== Menu ====================
|
||||
// >>> Menus
|
||||
'&-menus': {
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'flex-start',
|
||||
|
||||
[`&${componentCls}-menu-empty`]: {
|
||||
[`${componentCls}-menu`]: {
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
|
||||
[cascaderMenuItemCls]: {
|
||||
color: token.colorTextDisabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// >>> Menu
|
||||
'&-menu': {
|
||||
flexGrow: 1,
|
||||
minWidth: token.controlItemWidth,
|
||||
height: token.dropdownHeight,
|
||||
margin: 0,
|
||||
padding: token.menuPadding,
|
||||
overflow: 'auto',
|
||||
verticalAlign: 'top',
|
||||
listStyle: 'none',
|
||||
'-ms-overflow-style': '-ms-autohiding-scrollbar', // https://github.com/ant-design/ant-design/issues/11857
|
||||
|
||||
'&:not(:last-child)': {
|
||||
borderInlineEnd: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`,
|
||||
},
|
||||
|
||||
'&-item': {
|
||||
...textEllipsis,
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'center',
|
||||
padding: token.optionPadding,
|
||||
lineHeight: token.lineHeight,
|
||||
cursor: 'pointer',
|
||||
transition: `all ${token.motionDurationMid}`,
|
||||
borderRadius: token.borderRadiusSM,
|
||||
|
||||
'&:hover': {
|
||||
background: token.controlItemBgHover,
|
||||
},
|
||||
'&-disabled': {
|
||||
color: token.colorTextDisabled,
|
||||
cursor: 'not-allowed',
|
||||
|
||||
'&:hover': {
|
||||
background: 'transparent',
|
||||
},
|
||||
|
||||
[iconCls]: {
|
||||
color: token.colorTextDisabled,
|
||||
},
|
||||
},
|
||||
|
||||
[`&-active:not(${cascaderMenuItemCls}-disabled)`]: {
|
||||
[`&, &:hover`]: {
|
||||
fontWeight: token.optionSelectedFontWeight,
|
||||
backgroundColor: token.optionSelectedBg,
|
||||
},
|
||||
},
|
||||
|
||||
'&-content': {
|
||||
flex: 'auto',
|
||||
},
|
||||
|
||||
[iconCls]: {
|
||||
marginInlineStart: token.paddingXXS,
|
||||
color: token.colorTextDescription,
|
||||
fontSize: token.fontSizeIcon,
|
||||
},
|
||||
|
||||
'&-keyword': {
|
||||
color: token.colorHighlight,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
getColumnsStyle(token),
|
||||
],
|
||||
},
|
||||
// =====================================================
|
||||
@ -187,22 +88,24 @@ const genBaseStyle: GenerateStyle<CascaderToken> = (token) => {
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export const prepareComponentToken = (token: GlobalToken) => {
|
||||
const itemPaddingVertical = Math.round(
|
||||
(token.controlHeight - token.fontSize * token.lineHeight) / 2,
|
||||
);
|
||||
|
||||
return {
|
||||
controlWidth: 184,
|
||||
controlItemWidth: 111,
|
||||
dropdownHeight: 180,
|
||||
optionSelectedBg: token.controlItemBgActive,
|
||||
optionSelectedFontWeight: token.fontWeightStrong,
|
||||
optionPadding: `${itemPaddingVertical}px ${token.paddingSM}px`,
|
||||
menuPadding: token.paddingXXS,
|
||||
};
|
||||
};
|
||||
|
||||
export default genComponentStyleHook(
|
||||
'Cascader',
|
||||
(token) => [genBaseStyle(token)],
|
||||
(token) => {
|
||||
const itemPaddingVertical = Math.round(
|
||||
(token.controlHeight - token.fontSize * token.lineHeight) / 2,
|
||||
);
|
||||
|
||||
return {
|
||||
controlWidth: 184,
|
||||
controlItemWidth: 111,
|
||||
dropdownHeight: 180,
|
||||
optionSelectedBg: token.controlItemBgActive,
|
||||
optionSelectedFontWeight: token.fontWeightStrong,
|
||||
optionPadding: `${itemPaddingVertical}px ${token.paddingSM}px`,
|
||||
menuPadding: token.paddingXXS,
|
||||
};
|
||||
},
|
||||
prepareComponentToken,
|
||||
);
|
||||
|
41
components/cascader/style/panel.ts
Normal file
41
components/cascader/style/panel.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { CSSObject } from '@ant-design/cssinjs';
|
||||
|
||||
import { prepareComponentToken, type CascaderToken } from '.';
|
||||
import { genComponentStyleHook, type GenerateStyle } from '../../theme/internal';
|
||||
import getColumnsStyle from './columns';
|
||||
|
||||
// ============================== Panel ===============================
|
||||
const genPanelStyle: GenerateStyle<CascaderToken> = (token: CascaderToken): CSSObject => {
|
||||
const { componentCls } = token;
|
||||
|
||||
return {
|
||||
[`${componentCls}-panel`]: [
|
||||
getColumnsStyle(token),
|
||||
{
|
||||
display: 'inline-flex',
|
||||
border: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`,
|
||||
borderRadius: token.borderRadiusLG,
|
||||
overflowX: 'auto',
|
||||
maxWidth: '100%',
|
||||
|
||||
[`${componentCls}-menus`]: {
|
||||
alignItems: 'stretch',
|
||||
},
|
||||
[`${componentCls}-menu`]: {
|
||||
height: 'auto',
|
||||
},
|
||||
|
||||
'&-empty': {
|
||||
padding: token.paddingXXS,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genComponentStyleHook(
|
||||
['Cascader', 'Panel'],
|
||||
(token) => genPanelStyle(token),
|
||||
prepareComponentToken,
|
||||
);
|
@ -123,7 +123,7 @@
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"dayjs": "^1.11.1",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"rc-cascader": "~3.17.0",
|
||||
"rc-cascader": "~3.18.1",
|
||||
"rc-checkbox": "~3.1.0",
|
||||
"rc-collapse": "~3.7.1",
|
||||
"rc-dialog": "~9.3.3",
|
||||
|
Loading…
Reference in New Issue
Block a user