diff --git a/src/BaseSelect/index.tsx b/src/BaseSelect/index.tsx index 4c04f713e..d19ee824a 100644 --- a/src/BaseSelect/index.tsx +++ b/src/BaseSelect/index.tsx @@ -1,5 +1,5 @@ import type { AlignType, BuildInPlacements } from '@rc-component/trigger/lib/interface'; -import classNames from 'classnames'; +import cls from 'classnames'; import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect'; import useMergedState from '@rc-component/util/lib/hooks/useMergedState'; import isMobile from '@rc-component/util/lib/isMobile'; @@ -27,8 +27,6 @@ import type { RefTriggerProps } from '../SelectTrigger'; import SelectTrigger from '../SelectTrigger'; import TransBtn from '../TransBtn'; import { getSeparatedContent, isValidCount } from '../utils/valueUtil'; -import SelectContext from '../SelectContext'; -import type { SelectContextProps } from '../SelectContext'; import Polite from './Polite'; export type BaseSelectSemanticName = 'prefix' | 'suffix' | 'input'; @@ -130,22 +128,27 @@ export interface BaseSelectPrivateProps { export type BaseSelectPropsWithoutPrivate = Omit; export interface BaseSelectProps extends BaseSelectPrivateProps, React.AriaAttributes { + // Style className?: string; style?: React.CSSProperties; classNames?: Partial>; styles?: Partial>; - title?: string; + + // Selector showSearch?: boolean; tagRender?: (props: CustomTagProps) => React.ReactElement; direction?: 'ltr' | 'rtl'; - maxLength?: number; - showScrollBar?: boolean | 'optional'; + autoFocus?: boolean; + placeholder?: React.ReactNode; + maxCount?: number; + // MISC + title?: string; tabIndex?: number; - autoFocus?: boolean; notFoundContent?: React.ReactNode; - placeholder?: React.ReactNode; onClear?: () => void; + maxLength?: number; + showScrollBar?: boolean | 'optional'; choiceTransitionName?: string; @@ -224,6 +227,8 @@ const BaseSelect = React.forwardRef((props, ref) id, prefixCls, className, + styles, + classNames, showSearch, tagRender, showScrollBar = 'optional', @@ -236,6 +241,7 @@ const BaseSelect = React.forwardRef((props, ref) emptyOptions, notFoundContent = 'Not Found', onClear, + maxCount, // Mode mode, @@ -408,15 +414,8 @@ const BaseSelect = React.forwardRef((props, ref) [tokenSeparators], ); - const { - maxCount, - rawValues, - classNames: selectClassNames, - styles, - } = React.useContext(SelectContext) || {}; - const onInternalSearch = (searchText: string, fromTyping: boolean, isCompositing: boolean) => { - if (multiple && isValidCount(maxCount) && rawValues?.size >= maxCount) { + if (multiple && isValidCount(maxCount) && displayValues.length >= maxCount) { return; } let ret = true; @@ -426,7 +425,7 @@ const BaseSelect = React.forwardRef((props, ref) const separatedList = getSeparatedContent( searchText, tokenSeparators, - isValidCount(maxCount) ? maxCount - rawValues.size : undefined, + isValidCount(maxCount) ? maxCount - displayValues.length : undefined, ); // Check if match the `tokenSeparators` @@ -703,6 +702,8 @@ const BaseSelect = React.forwardRef((props, ref) multiple, toggleOpen: onToggleOpen, showScrollBar, + styles, + classNames, }), [ props, @@ -714,6 +715,8 @@ const BaseSelect = React.forwardRef((props, ref) multiple, onToggleOpen, showScrollBar, + styles, + classNames, ], ); @@ -728,7 +731,7 @@ const BaseSelect = React.forwardRef((props, ref) if (showSuffixIcon) { arrowNode = ( ((props, ref) const optionList = ; // ============================= Select ============================= - const mergedClassName = classNames(prefixCls, className, { + const mergedClassName = cls(prefixCls, className, { [`${prefixCls}-focused`]: mockFocused, [`${prefixCls}-multiple`]: multiple, [`${prefixCls}-single`]: !multiple, @@ -821,7 +824,7 @@ const BaseSelect = React.forwardRef((props, ref) ) : ( >> Style + classNames={classNames} + styles={styles} // >>> Values displayValues={displayValues} onDisplayValuesChange={onDisplayValuesChange} + maxCount={maxCount} // >>> Trigger direction={direction} // >>> Search diff --git a/src/SelectContext.ts b/src/SelectContext.ts index 8d1e1b5b2..8918e4935 100644 --- a/src/SelectContext.ts +++ b/src/SelectContext.ts @@ -11,6 +11,9 @@ import type { import type { FlattenOptionData } from './interface'; // Use any here since we do not get the type during compilation +/** + * SelectContext is only used for Select. BaseSelect should not consume this context. + */ export interface SelectContextProps { classNames?: Partial>; styles?: Partial>; diff --git a/src/Selector/Input.tsx b/src/Selector/Input.tsx index ab71314b3..b5c4ed079 100644 --- a/src/Selector/Input.tsx +++ b/src/Selector/Input.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import classNames from 'classnames'; import { composeRef } from '@rc-component/util/lib/ref'; import { warning } from '@rc-component/util/lib/warning'; -import SelectContext from '../SelectContext'; +import useBaseProps from '../hooks/useBaseProps'; type InputRef = HTMLInputElement | HTMLTextAreaElement; interface InputProps { @@ -57,8 +57,8 @@ const Input: React.ForwardRefRenderFunction = (props, ref) open, attrs, } = props; - const { classNames: contextClassNames, styles: contextStyles } = - React.useContext(SelectContext) || {}; + + const { classNames: contextClassNames, styles: contextStyles } = useBaseProps() || {}; let inputNode: React.ComponentElement = inputElement || ; diff --git a/tests/Select.test.tsx b/tests/Select.test.tsx index d1e0cf70c..b79b9a131 100644 --- a/tests/Select.test.tsx +++ b/tests/Select.test.tsx @@ -13,7 +13,8 @@ import type { ScrollConfig } from 'rc-virtual-list/lib/List'; import React from 'react'; import type { SelectProps } from '../src'; import Select, { OptGroup, Option, useBaseProps } from '../src'; -import type { BaseSelectRef } from '../src/BaseSelect'; +import BaseSelect from '../src/BaseSelect'; +import type { BaseSelectRef, RefOptionListProps } from '../src/BaseSelect'; import allowClearTest from './shared/allowClearTest'; import blurTest from './shared/blurTest'; import focusTest from './shared/focusTest'; @@ -2417,9 +2418,10 @@ describe('Select.Basic', () => { expect(onBlur).toHaveBeenCalledTimes(2); expect(inputElem.value).toEqual('bb'); }); - it('support classnames and styles', () => { + + it('support classnames and styles for select', () => { const customClassNames = { - prefix: 'cutsom-prefix', + prefix: 'custom-prefix', suffix: 'custom-suffix', list: 'custom-list', listItem: 'custom-item', @@ -2464,4 +2466,46 @@ describe('Select.Basic', () => { expect(input).toHaveClass(customClassNames.input); expect(input).toHaveStyle(customStyle.input); }); + + it('support classnames and styles for baseSelect', () => { + const customClassNames = { + prefix: 'custom-prefix', + suffix: 'custom-suffix', + input: 'custom-input', + }; + const customStyle = { + prefix: { color: 'red' }, + suffix: { color: 'green' }, + input: { color: 'black' }, + }; + const OptionList = React.forwardRef((props, ref) => { + return
}>Option List
; + }); + const { container } = render( + arrow} + prefix="Foobar" + onDisplayValuesChange={() => {}} + searchValue="" + onSearch={() => {}} + OptionList={OptionList} + emptyOptions={false} + />, + ); + const prefix = container.querySelector('.rc-select-prefix'); + const suffix = container.querySelector('.rc-select-arrow'); + const input = container.querySelector('.rc-select-selection-search-input'); + expect(prefix).toHaveClass(customClassNames.prefix); + expect(prefix).toHaveStyle(customStyle.prefix); + expect(suffix).toHaveClass(customClassNames.suffix); + expect(suffix).toHaveStyle(customStyle.suffix); + expect(input).toHaveClass(customClassNames.input); + expect(input).toHaveStyle(customStyle.input); + }); });