From 62dda88ea0f8d1058c4ab1c4910c177230daa5b9 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Mon, 20 Feb 2023 14:08:09 +0800 Subject: [PATCH] refactor: cascader --- components/cascader/index.en-US.md | 4 +- components/cascader/index.tsx | 146 ++++++++++++-------- components/cascader/index.zh-CN.md | 4 +- components/cascader/style/index.less | 105 --------------- components/cascader/style/index.ts | 165 +++++++++++++++++++++++ components/cascader/style/index.tsx | 8 -- components/cascader/style/rtl.less | 19 --- components/style.ts | 2 +- components/theme/interface/components.ts | 4 +- components/vc-cascader/Cascader.tsx | 3 +- 10 files changed, 262 insertions(+), 198 deletions(-) delete mode 100644 components/cascader/style/index.less create mode 100644 components/cascader/style/index.ts delete mode 100644 components/cascader/style/index.tsx delete mode 100644 components/cascader/style/rtl.less diff --git a/components/cascader/index.en-US.md b/components/cascader/index.en-US.md index 0d74d18df..851558f82 100644 --- a/components/cascader/index.en-US.md +++ b/components/cascader/index.en-US.md @@ -2,7 +2,7 @@ category: Components type: Data Entry title: Cascader -cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*tokLTp73TsQAAAAAAAAAAAAADrJ8AQ/original --- Cascade selection box. @@ -28,7 +28,7 @@ Cascade selection box. | changeOnSelect | (Work on single select) change value on each selection if set to true, see above demo for details | boolean | false | | | disabled | whether disabled select | boolean | false | | | displayRender | render function of displaying selected options, you can use #displayRender="{labels, selectedOptions}". | `({labels, selectedOptions}) => VNode` | `labels => labels.join(' / ')` | | -| dropdownClassName | additional className of popup overlay | string | - | 3.0 | +| popupClassName | additional className of popup overlay | string | - | 4.0 | | dropdownStyle | additional style of popup overlay | CSSProperties | {} | 3.0 | | expandIcon | Customize the current item expand icon | slot | - | 3.0 | | expandTrigger | expand current item when click or hover | `click` \| `hover` | 'click' | | diff --git a/components/cascader/index.tsx b/components/cascader/index.tsx index cda779651..29adf450f 100644 --- a/components/cascader/index.tsx +++ b/components/cascader/index.tsx @@ -26,7 +26,11 @@ import type { ValueType } from '../vc-cascader/Cascader'; import type { InputStatus } from '../_util/statusUtils'; import { getStatusClassNames, getMergedStatus } from '../_util/statusUtils'; import { FormItemInputContext } from '../form/FormItemContext'; +import { useCompactItemContext } from '../space/Compact'; +import useSelectStyle from '../select/style'; +import useStyle from './style'; +import { useInjectDisabled } from '../config-provider/DisabledContext'; // Align the design since we use `rc-select` in root. This help: // - List search content will show all content // - Hover opacity style @@ -38,7 +42,7 @@ export type FieldNamesType = FieldNames; export type FilledFieldNamesType = Required; -function highlightKeyword(str: string, lowerKeyword: string, prefixCls: string | undefined) { +function highlightKeyword(str: string, lowerKeyword: string, prefixCls?: string) { const cells = str .toLowerCase() .split(lowerKeyword) @@ -108,6 +112,9 @@ export function cascaderProps, options: Array as PropType, + popupClassName: String, + /** @deprecated Please use `popupClassName` instead */ + dropdownClassName: String, 'onUpdate:value': Function as PropType<(value: ValueType) => void>, }; } @@ -129,6 +136,14 @@ const Cascader = defineComponent({ allowClear: true, }), setup(props, { attrs, expose, slots, emit }) { + // ====================== Warning ====================== + if (process.env.NODE_ENV !== 'production') { + devWarning( + !props.dropdownClassName, + 'Cascader', + '`dropdownClassName` is deprecated. Please use `popupClassName` instead.', + ); + } const formItemContext = useInjectFormItemContext(); const formItemInputContext = FormItemInputContext.useInject(); const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status)); @@ -139,9 +154,18 @@ const Cascader = defineComponent({ direction, getPopupContainer, renderEmpty, - size, + size: contextSize, + disabled, } = useConfigInject('cascader', props); const prefixCls = computed(() => getPrefixCls('select', props.prefixCls)); + const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); + const mergedSize = computed(() => compactSize.value || contextSize.value); + const contextDisabled = useInjectDisabled(); + const mergedDisabled = computed(() => disabled.value ?? contextDisabled.value); + + const [wrapSelectSSR, hashId] = useSelectStyle(prefixCls); + const [wrapCascaderSSR] = useStyle(cascaderPrefixCls); + const isRtl = computed(() => direction.value === 'rtl'); // =================== Warning ===================== if (process.env.NODE_ENV !== 'production') { @@ -176,11 +200,12 @@ const Cascader = defineComponent({ // =================== Dropdown ==================== const mergedDropdownClassName = computed(() => classNames( - props.dropdownClassName || props.popupClassName, + props.popupClassName || props.dropdownClassName, `${cascaderPrefixCls.value}-dropdown`, { [`${cascaderPrefixCls.value}-dropdown-rtl`]: isRtl.value, }, + hashId.value, ), ); @@ -253,61 +278,66 @@ const Cascader = defineComponent({ }, slots, ); - return ( - , - }} - tagRender={props.tagRender || slots.tagRender} - displayRender={props.displayRender || slots.displayRender} - maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder} - showArrow={formItemInputContext.hasFeedback || props.showArrow} - onChange={handleChange} - onBlur={handleBlur} - v-slots={slots} - ref={selectRef} - /> + return wrapCascaderSSR( + wrapSelectSSR( + , + }} + tagRender={props.tagRender || slots.tagRender} + displayRender={props.displayRender || slots.displayRender} + maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder} + showArrow={formItemInputContext.hasFeedback || props.showArrow} + onChange={handleChange} + onBlur={handleBlur} + v-slots={slots} + ref={selectRef} + />, + ), ); }; }, diff --git a/components/cascader/index.zh-CN.md b/components/cascader/index.zh-CN.md index 7dea635ff..de1987528 100644 --- a/components/cascader/index.zh-CN.md +++ b/components/cascader/index.zh-CN.md @@ -3,7 +3,7 @@ category: Components type: 数据录入 title: Cascader subtitle: 级联选择 -cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*tokLTp73TsQAAAAAAAAAAAAADrJ8AQ/original --- 级联选择框。 @@ -30,7 +30,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg | defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | | | disabled | 禁用 | boolean | false | | | displayRender | 选择后展示的渲染函数,可使用 #displayRender="{labels, selectedOptions}" | `({labels, selectedOptions}) => VNode` | `labels => labels.join(' / ')` | | -| dropdownClassName | 自定义浮层类名 | string | - | 3.0 | +| popupClassName | 自定义浮层类名 | string | - | 4.0 | | dropdownStyle | 自定义浮层样式 | CSSProperties | {} | 3.0 | | expandIcon | 自定义次级菜单展开图标 | slot | - | 3.0 | | expandTrigger | 次级菜单的展开方式 | `click` \| `hover` | 'click' | | diff --git a/components/cascader/style/index.less b/components/cascader/style/index.less deleted file mode 100644 index 1e4822f3e..000000000 --- a/components/cascader/style/index.less +++ /dev/null @@ -1,105 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; -@import '../../input/style/mixin'; -@import '../../checkbox/style/mixin'; - -@cascader-prefix-cls: ~'@{ant-prefix}-cascader'; - -.antCheckboxFn(@checkbox-prefix-cls: ~'@{cascader-prefix-cls}-checkbox'); - -.@{cascader-prefix-cls} { - width: 184px; - - &-checkbox { - top: 0; - margin-right: @padding-xs; - } - - &-menus { - display: flex; - flex-wrap: nowrap; - align-items: flex-start; - - &.@{cascader-prefix-cls}-menu-empty { - .@{cascader-prefix-cls}-menu { - width: 100%; - height: auto; - } - } - } - - &-menu { - flex-grow: 1; - min-width: 111px; - height: 180px; - margin: 0; - margin: -@dropdown-edge-child-vertical-padding 0; - padding: @cascader-dropdown-edge-child-vertical-padding 0; - overflow: auto; - vertical-align: top; - list-style: none; - border-right: @border-width-base @border-style-base @cascader-menu-border-color-split; - -ms-overflow-style: -ms-autohiding-scrollbar; // https://github.com/ant-design/ant-design/issues/11857 - - &-item { - display: flex; - flex-wrap: nowrap; - align-items: center; - padding: @cascader-dropdown-vertical-padding @control-padding-horizontal; - overflow: hidden; - line-height: @cascader-dropdown-line-height; - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - transition: all 0.3s; - - &:hover { - background: @item-hover-bg; - } - - &-disabled { - color: @disabled-color; - cursor: not-allowed; - - &:hover { - background: transparent; - } - } - - .@{cascader-prefix-cls}-menu-empty & { - color: @disabled-color; - cursor: default; - pointer-events: none; - } - - &-active:not(&-disabled) { - &, - &:hover { - font-weight: @select-item-selected-font-weight; - background-color: @cascader-item-selected-bg; - } - } - - &-content { - flex: auto; - } - - &-expand &-expand-icon, - &-loading-icon { - margin-left: @padding-xss; - color: @text-color-secondary; - font-size: 10px; - - .@{cascader-prefix-cls}-menu-item-disabled& { - color: @disabled-color; - } - } - - &-keyword { - color: @highlight-color; - } - } - } -} - -@import './rtl'; diff --git a/components/cascader/style/index.ts b/components/cascader/style/index.ts new file mode 100644 index 000000000..07379da99 --- /dev/null +++ b/components/cascader/style/index.ts @@ -0,0 +1,165 @@ +import { getStyle as getCheckboxStyle } from '../../checkbox/style'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook } from '../../theme/internal'; +import { textEllipsis } from '../../_style'; +import { genCompactItemStyle } from '../../_style/compact-item'; + +export interface ComponentToken { + controlWidth: number; + controlItemWidth: number; + dropdownHeight: number; +} + +type CascaderToken = FullToken<'Cascader'>; + +// =============================== Base =============================== +const genBaseStyle: GenerateStyle = token => { + const { prefixCls, componentCls, antCls } = token; + const cascaderMenuItemCls = `${componentCls}-menu-item`; + const iconCls = ` + &${cascaderMenuItemCls}-expand ${cascaderMenuItemCls}-expand-icon, + ${cascaderMenuItemCls}-loading-icon + `; + + const itemPaddingVertical = Math.round( + (token.controlHeight - token.fontSize * token.lineHeight) / 2, + ); + + return [ + // ===================================================== + // == Control == + // ===================================================== + { + [componentCls]: { + width: token.controlWidth, + }, + }, + // ===================================================== + // == Popup == + // ===================================================== + { + [`${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.paddingXXS, + 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: `${itemPaddingVertical}px ${token.paddingSM}px`, + 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.fontWeightStrong, + backgroundColor: token.controlItemBgActive, + }, + }, + + '&-content': { + flex: 'auto', + }, + + [iconCls]: { + marginInlineStart: token.paddingXXS, + color: token.colorTextDescription, + fontSize: token.fontSizeIcon, + }, + + '&-keyword': { + color: token.colorHighlight, + }, + }, + }, + }, + }, + ], + }, + // ===================================================== + // == RTL == + // ===================================================== + { + [`${componentCls}-dropdown-rtl`]: { + direction: 'rtl', + }, + }, + // ===================================================== + // == Space Compact == + // ===================================================== + genCompactItemStyle(token), + ]; +}; + +// ============================== Export ============================== +export default genComponentStyleHook('Cascader', token => [genBaseStyle(token)], { + controlWidth: 184, + controlItemWidth: 111, + dropdownHeight: 180, +}); diff --git a/components/cascader/style/index.tsx b/components/cascader/style/index.tsx deleted file mode 100644 index b4e1b0338..000000000 --- a/components/cascader/style/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import '../../style/index.less'; -import './index.less'; - -// style dependencies -import '../../empty/style'; -import '../../select/style'; - -// deps-lint-skip: form diff --git a/components/cascader/style/rtl.less b/components/cascader/style/rtl.less deleted file mode 100644 index c70bf1dd3..000000000 --- a/components/cascader/style/rtl.less +++ /dev/null @@ -1,19 +0,0 @@ -// We can not import reference of `./index` directly since it will make dead loop in less -@import (reference) '../../style/themes/index'; -@cascader-prefix-cls: ~'@{ant-prefix}-cascader'; - -.@{cascader-prefix-cls}-rtl { - .@{cascader-prefix-cls}-menu-item { - &-expand-icon, - &-loading-icon { - margin-right: @padding-xss; - margin-left: 0; - } - } - - .@{cascader-prefix-cls}-checkbox { - top: 0; - margin-right: 0; - margin-left: @padding-xs; - } -} diff --git a/components/style.ts b/components/style.ts index 6639fcd9c..1d0ffa3f1 100644 --- a/components/style.ts +++ b/components/style.ts @@ -27,7 +27,7 @@ import './radio/style'; // import './switch/style'; import './auto-complete/style'; // import './affix/style'; -import './cascader/style'; +// import './cascader/style'; // import './back-top/style'; // import './modal/style'; // import './alert/style'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 31b2238f6..9c8638327 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -7,7 +7,7 @@ import type { ComponentToken as ButtonComponentToken } from '../../button/style' import type { ComponentToken as CalendarComponentToken } from '../../calendar/style'; import type { ComponentToken as CardComponentToken } from '../../card/style'; import type { ComponentToken as CarouselComponentToken } from '../../carousel/style'; -// import type { ComponentToken as CascaderComponentToken } from '../../cascader/style'; +import type { ComponentToken as CascaderComponentToken } from '../../cascader/style'; import type { ComponentToken as CheckboxComponentToken } from '../../checkbox/style'; import type { ComponentToken as CollapseComponentToken } from '../../collapse/style'; import type { ComponentToken as DatePickerComponentToken } from '../../date-picker/style'; @@ -62,7 +62,7 @@ export interface ComponentTokenMap { Breadcrumb?: {}; Card?: CardComponentToken; Carousel?: CarouselComponentToken; - // Cascader?: CascaderComponentToken; + Cascader?: CascaderComponentToken; Checkbox?: CheckboxComponentToken; Collapse?: CollapseComponentToken; Comment?: {}; diff --git a/components/vc-cascader/Cascader.tsx b/components/vc-cascader/Cascader.tsx index 8374c76d5..b00b8fe4b 100644 --- a/components/vc-cascader/Cascader.tsx +++ b/components/vc-cascader/Cascader.tsx @@ -5,6 +5,7 @@ import type { DisplayValueType, Placement } from '../vc-select/BaseSelect'; import { baseSelectPropsWithoutPrivate } from '../vc-select/BaseSelect'; import omit from '../_util/omit'; import type { Key, VueNode } from '../_util/type'; +import { objectType } from '../_util/type'; import PropTypes from '../_util/vue-types'; import { initDefaultProps } from '../_util/props-util'; import useId from '../vc-select/hooks/useId'; @@ -68,7 +69,7 @@ function baseCascaderProps, + fieldNames: objectType(), children: Array as PropType, // Value