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:
二货爱吃白萝卜 2023-09-26 17:34:49 +08:00 committed by GitHub
parent 69d048784f
commit 7e692ad585
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 870 additions and 151 deletions

View 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}
/>
);
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
## zh-CN
适用于一些需要内嵌适用的场景。
## en-US
Used for inline view case.

View 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;

View 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];
}

View 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],
);
}

View 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];
}

View File

@ -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

View File

@ -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;

View File

@ -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

View 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;

View File

@ -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,
);

View 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,
);

View File

@ -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",