diff --git a/packages/components/src/components/cascader/helper.ts b/packages/components/src/components/cascader/helper.ts index b979bd6aad..196650653a 100644 --- a/packages/components/src/components/cascader/helper.ts +++ b/packages/components/src/components/cascader/helper.ts @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import isEmpty from 'lodash/isEmpty'; import findIndex from 'lodash/findIndex'; -import { NodeData, KeyMapping } from './menu-item'; +import { NodeData, KeyMapping } from './interface'; /** * 以分割符组合 前缀、后缀 diff --git a/packages/components/src/components/cascader/interface.ts b/packages/components/src/components/cascader/interface.ts index 97c614fb2f..f7c9302e07 100644 --- a/packages/components/src/components/cascader/interface.ts +++ b/packages/components/src/components/cascader/interface.ts @@ -1,6 +1,68 @@ import { DropdownProps } from '../dropdown/interface'; import { SizeType } from '../config-provider/SizeContext'; -import { Props as MenuProps } from './menu'; + +export type Value = string | number; +export type KeyMapping = { + label?: string; + value?: string; +}; + +export type NodeData = { + label?: string; + value?: Value; + disabled?: boolean; + children?: NodeData[]; + groupId?: Value; + groupName?: string; + [key: string]: unknown; +}; + +export interface MenuItemProps { + className?: string; + style?: React.CSSProperties; + dataSource: NodeData; + keyMapping?: KeyMapping; + value?: Value; + keyword?: string; + expanded?: boolean; + ignoreCase?: boolean; + deepSearch?: boolean; + parentsData?: NodeData[]; + onClick?: (event: React.MouseEvent, nodeData: NodeData) => void; + onMouseEnter?: (event: React.MouseEvent, nodeData: NodeData) => void; + trigger?: 'click' | 'hover'; + selectAny?: boolean; + onTrigger?: (event: React.MouseEvent | React.KeyboardEvent, nodeData: NodeData) => void; + beforeSelect?: ( + event: React.MouseEvent | React.KeyboardEvent, + nodeData: NodeData + ) => void | NodeData[] | Promise; + onSelect?: (nodeData: NodeData, parentsData: NodeData[], event: React.MouseEvent | React.KeyboardEvent) => void; + onKeyUp?: (event: React.KeyboardEvent) => void; + onFocus?: (event: React.FocusEvent) => void; + onBlur?: (event: React.FocusEvent) => void; + onMouseLeave?: (event: React.MouseEvent) => void; + onRender?: (nodeData: NodeData) => React.ReactElement; + afterInner?: (nodeData: NodeData) => React.ReactNode; +} + +export type MaybeElementOrFn = React.ReactNode | ((dataSource: NodeData[]) => React.ReactNode); + +export interface MenuProps extends Omit { + dataSource?: NodeData[]; + onRender?: (nodeData: NodeData) => React.ReactElement; + open?: boolean; + depth?: number; + header?: MaybeElementOrFn; + footer?: MaybeElementOrFn; + offsetLeft?: number; + offsetTop?: number; + getEmpty?: (keyword?: string) => React.ReactElement; + groupName?: boolean | MaybeElementOrFn; + parentMenu?: HTMLDivElement; + expandedId?: NodeData['value']; + autoFocus?: boolean; +} export interface Props extends MenuProps { /** diff --git a/packages/components/src/components/cascader/menu-item.tsx b/packages/components/src/components/cascader/menu-item.tsx index 088f05853b..9959d9f928 100644 --- a/packages/components/src/components/cascader/menu-item.tsx +++ b/packages/components/src/components/cascader/menu-item.tsx @@ -1,54 +1,12 @@ import { CheckOutlined, DownFilled } from '@gio-design/icons'; -import React, { FocusEvent, KeyboardEvent, MouseEvent, ReactElement } from 'react'; +import React from 'react'; import classNames from 'classnames'; import isEmpty from 'lodash/isEmpty'; import isFunction from 'lodash/isFunction'; +import { MenuItemProps as Props, NodeData } from './interface'; import { dataFilter, dataKeyMapping, isHit, makeSearchParttern, useDynamicData, withPrefix } from './helper'; -export type Value = string | number; - -export type KeyMapping = { - label?: string; - value?: string; -}; - -export type NodeData = { - label?: string; - value?: Value; - disabled?: boolean; - children?: NodeData[]; - groupId?: Value; - groupName?: string; - [key: string]: unknown; -}; - -export interface Props { - className?: string; - style?: React.CSSProperties; - dataSource: NodeData; - keyMapping?: KeyMapping; - value?: Value; - keyword?: string; - expanded?: boolean; - ignoreCase?: boolean; - deepSearch?: boolean; - parentsData?: NodeData[]; - onClick?: (event: MouseEvent, nodeData: NodeData) => void; - onMouseEnter?: (event: MouseEvent, nodeData: NodeData) => void; - trigger?: 'click' | 'hover'; - selectAny?: boolean; - onTrigger?: (event: MouseEvent | KeyboardEvent, nodeData: NodeData) => void; - beforeSelect?: (event: MouseEvent | KeyboardEvent, nodeData: NodeData) => void | NodeData[] | Promise; - onSelect?: (nodeData: NodeData, parentsData: NodeData[], event: MouseEvent | KeyboardEvent) => void; - onKeyUp?: (event: KeyboardEvent) => void; - onFocus?: (event: FocusEvent) => void; - onBlur?: (event: FocusEvent) => void; - onMouseLeave?: (event: MouseEvent) => void; - onRender?: (nodeData: NodeData) => ReactElement; - afterInner?: (nodeData: NodeData) => React.ReactNode; -} - const triggerMap = { click: 'onClick', hover: 'onMouseEnter', @@ -109,12 +67,12 @@ const MenuItem = React.forwardRef((props, ref) => { const withWrapperCls = withPrefix('cascader-menu-item'); const mergedTrigger = triggerMap[trigger.toLowerCase() as typeof trigger]; - const getCopyEvent = (event: T) => { + const getCopyEvent = (event: T) => { event.persist(); return { ...event }; }; - const resolveBeforeSelect = (event: MouseEvent | KeyboardEvent) => { + const resolveBeforeSelect = (event: React.MouseEvent | React.KeyboardEvent) => { if (disabled) { return Promise.reject(Error('disabled')); } @@ -127,7 +85,7 @@ const MenuItem = React.forwardRef((props, ref) => { if (isEmpty(data.children) || selectAny) { setDataSource(data); try { - if (!(event.type === 'keyup' && (event as KeyboardEvent).key === 'ArrowRight')) { + if (!(event.type === 'keyup' && (event as React.KeyboardEvent).key === 'ArrowRight')) { onSelect?.(data, parentsData, event); } } catch (e) { @@ -146,7 +104,7 @@ const MenuItem = React.forwardRef((props, ref) => { }); }; - const handleMouseEnter = (event: MouseEvent) => { + const handleMouseEnter = (event: React.MouseEvent) => { const target = event.nativeEvent.target as HTMLDivElement; // 这里有个奇怪的问题,点 input 会触发这里的 MouseEvent 事件 if (trigger === 'hover' && target.closest('.cascader-menu-outer') && !disabled) { @@ -155,7 +113,7 @@ const MenuItem = React.forwardRef((props, ref) => { onMouseEnter?.(event, dataSource); }; - const handleClick = (event: MouseEvent) => { + const handleClick = (event: React.MouseEvent) => { const eventCopy = getCopyEvent(event); resolveBeforeSelect(eventCopy) .then((data) => { @@ -164,7 +122,7 @@ const MenuItem = React.forwardRef((props, ref) => { .catch(() => onClick?.(eventCopy, dataSource)); }; - const handleKeyUp = (event: KeyboardEvent) => { + const handleKeyUp = (event: React.KeyboardEvent) => { if ([' ', 'Enter', 'ArrowRight'].indexOf(event.key) >= 0) { event.preventDefault(); event.stopPropagation(); @@ -174,7 +132,7 @@ const MenuItem = React.forwardRef((props, ref) => { } }; - const handleKeyDown = (event: KeyboardEvent) => { + const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === ' ') { event.preventDefault(); } @@ -187,6 +145,7 @@ const MenuItem = React.forwardRef((props, ref) => { keyword && deepSearch ? !isEmpty(dataFilter(dataSource.children, parttern, deepSearch, keyMapping.label)) : !noChild; + const checked = value === dataValue && (selectAny || !mergedHasChild); let childNode = (
@@ -194,9 +153,7 @@ const MenuItem = React.forwardRef((props, ref) => { {shouldRenderKeyword && keyword && dataLabel ? renderKeyword(dataLabel, keyword, ignoreCase) : dataLabel}
- {value === dataValue && (selectAny || !mergedHasChild) && ( - - )} + {checked && } {mergedHasChild && }
@@ -212,10 +169,12 @@ const MenuItem = React.forwardRef((props, ref) => { className={classNames(className, withWrapperCls(), disabled && withWrapperCls('disabled'))} ref={ref} style={style} + data-checked={checked} >
= (props) => { +const InnerMenu: React.FC = (props) => { const { className, style = {}, @@ -112,7 +112,7 @@ const InnerMenu: React.FC = (props) => { ); }; -const Menu = React.forwardRef((props, ref) => { +const Menu = React.forwardRef((props, ref) => { const { className, style, open, onTrigger, ...others } = props; const wrapRef = useMergeRef(ref); const [canOpen, setCanOpen] = useState(open); diff --git a/packages/components/src/components/cascader/single-menu.tsx b/packages/components/src/components/cascader/single-menu.tsx index 046210817a..49bc2ad6d1 100644 --- a/packages/components/src/components/cascader/single-menu.tsx +++ b/packages/components/src/components/cascader/single-menu.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import classNames from 'classnames'; import groupBy from 'lodash/groupBy'; import isEmpty from 'lodash/isEmpty'; @@ -8,31 +8,14 @@ import trim from 'lodash/trim'; import useMergeRef from '../../utils/hooks/useMergeRef'; import { dataFilter, makeSearchParttern, mergeKeyMapping, toInt, withPrefix } from './helper'; import Empty from './empty'; -import MenuItem, { Props as MenuItemProps, NodeData } from './menu-item'; - -type MaybeElementOrFn = React.ReactNode | ((dataSource: NodeData[]) => React.ReactNode); +import MenuItem from './menu-item'; +import { MenuProps, MaybeElementOrFn, NodeData } from './interface'; const getMayBeElement = (maybeElement: MaybeElementOrFn, dataSource: NodeData[]) => { return isFunction(maybeElement) ? maybeElement(dataSource) : maybeElement; }; -export interface Props extends Omit { - dataSource?: NodeData[]; - onRender?: (nodeData: NodeData) => ReactElement; - open?: boolean; - depth?: number; - header?: MaybeElementOrFn; - footer?: MaybeElementOrFn; - offsetLeft?: number; - offsetTop?: number; - getEmpty?: (keyword?: string) => React.ReactElement; - groupName?: boolean | MaybeElementOrFn; - parentMenu?: HTMLDivElement; - expandedId?: NodeData['value']; - autoFocus?: boolean; -} - -const SingleMenu = React.forwardRef((props, ref) => { +const SingleMenu = React.forwardRef((props, ref) => { const { className, style = {}, diff --git a/packages/components/src/components/cascader/style/index.less b/packages/components/src/components/cascader/style/index.less index ed65275b48..da2e4857c5 100644 --- a/packages/components/src/components/cascader/style/index.less +++ b/packages/components/src/components/cascader/style/index.less @@ -9,7 +9,6 @@ display: inline-flex; input.@{input-cls}-content { - // min-width: 250px; cursor: pointer; } @@ -25,8 +24,6 @@ } &-dropdown { - position: relative; - .@{dropdown-cls} { &-content { background: transparent !important; @@ -81,14 +78,23 @@ &-group { &-name { - padding: 6px 12px 0 12px; + position: relative; + margin: 6px 12px 0 12px; + padding-top: 6px; color: #999; font-size: 12px; } - &:not(:last-child) { - .cascader-menu-item:last-child .cascader-menu-item-content { - border-bottom: 1px solid #dce0f2; + &:not(:first-child) { + .cascader-menu-group-name { + &::after { + position: absolute; + top: 0; + left: 0; + width: 100%; + border-bottom: 1px solid #dce0f2; + content: ''; + } } } } @@ -135,10 +141,22 @@ &-item { outline: none; + &[data-checked='true'] { + > .cascader-menu-item-inner { + background: #f7f8fc; + + .cascader-menu-item-content { + cursor: default; + } + } + } + &-inner { + border-radius: 4px; + &:hover { color: #1248e9; - background-color: #f7f8fc; + background-color: #ebedf5; border-radius: 4px; } diff --git a/packages/website/src/components/basic/cascader/demo/group.tsx b/packages/website/src/components/basic/cascader/demo/group.tsx index 183dfc3731..a27f4a1f6d 100644 --- a/packages/website/src/components/basic/cascader/demo/group.tsx +++ b/packages/website/src/components/basic/cascader/demo/group.tsx @@ -17,7 +17,7 @@ const Basic = (): JSX.Element => { return (
- + 显示分组名