Repository: react-component/select
Pull request #1133: feat: support semantic for baseSelect
Author: login: thinkasany
Created: 2025-03-18T03:04:30Z | Merged: 2025-03-20T07:18:32Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **新功能**
  - 增加了自定义样式和类名的选项，用户现在可以为选择组件提供个性化的外观设置。
  - 新增了 `maxCount` 属性，允许用户设置选择项的最大数量。

- **重构**
  - 优化了样式和类名的管理逻辑，提高了组件在外观定制时的灵活性和稳定性。
  - 组件的输入部分现在通过新的钩子获取样式和类名，增强了代码的可维护性。

- **测试**
  - 增加了测试用例，以验证选择组件对自定义样式和类名的支持。
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
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<BaseSelectProps, keyof BaseSelectPrivateProps>;
 
 export interface BaseSelectProps extends BaseSelectPrivateProps, React.AriaAttributes {
+  // Style
   className?: string;
   style?: React.CSSProperties;
   classNames?: Partial<Record<BaseSelectSemanticName, string>>;
   styles?: Partial<Record<BaseSelectSemanticName, React.CSSProperties>>;
-  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<BaseSelectRef, BaseSelectProps>((props, ref)
     id,
     prefixCls,
     className,
+    styles,
+    classNames,
     showSearch,
     tagRender,
     showScrollBar = 'optional',
@@ -236,6 +241,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
     emptyOptions,
     notFoundContent = 'Not Found',
     onClear,
+    maxCount,
 
     // Mode
     mode,
@@ -408,15 +414,8 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
     [tokenSeparators],
   );
 
-  const {
-    maxCount,
-    rawValues,
-    classNames: selectClassNames,
-    styles,
-  } = React.useContext<SelectContextProps>(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<BaseSelectRef, BaseSelectProps>((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<BaseSelectRef, BaseSelectProps>((props, ref)
       multiple,
       toggleOpen: onToggleOpen,
       showScrollBar,
+      styles,
+      classNames,
     }),
     [
       props,
@@ -714,6 +715,8 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
       multiple,
       onToggleOpen,
       showScrollBar,
+      styles,
+      classNames,
     ],
   );
 
@@ -728,7 +731,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
   if (showSuffixIcon) {
     arrowNode = (
       <TransBtn
-        className={classNames(`${prefixCls}-arrow`, selectClassNames?.suffix, {
+        className={cls(`${prefixCls}-arrow`, classNames?.suffix, {
           [`${prefixCls}-arrow-loading`]: loading,
         })}
         style={styles?.suffix}
@@ -772,7 +775,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
   const optionList = <OptionList ref={listRef} />;
 
   // ============================= 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<BaseSelectRef, BaseSelectProps>((props, ref)
       ) : (
         <Selector
           {...props}
-          prefixClassName={selectClassNames?.prefix}
+          prefixClassName={classNames?.prefix}
           prefixStyle={styles?.prefix}
           domRef={selectorDomRef}
           prefixCls={prefixCls}
diff --git a/src/Select.tsx b/src/Select.tsx
index e741aa900..39da90411 100644
--- a/src/Select.tsx
+++ b/src/Select.tsx
@@ -208,7 +208,7 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
       labelInValue,
       onChange,
       maxCount,
-      classNames: selectClassNames,
+      classNames,
       styles,
       ...restProps
     } = props;
@@ -631,12 +631,10 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
         childrenAsData,
         maxCount,
         optionRender,
-        classNames: selectClassNames,
+        classNames,
         styles,
       };
     }, [
-      selectClassNames,
-      styles,
       maxCount,
       parsedOptions,
       displayOptions,
@@ -653,6 +651,8 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
       listItemHeight,
       childrenAsData,
       optionRender,
+      classNames,
+      styles,
     ]);
 
     // ========================== Warning ===========================
@@ -674,9 +674,13 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
           ref={ref}
           omitDomProps={OMIT_DOM_PROPS}
           mode={mode}
+          // >>> 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<Record<SemanticName, string>>;
   styles?: Partial<Record<SemanticName, React.CSSProperties>>;
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<InputRef, InputProps> = (props, ref)
     open,
     attrs,
   } = props;
-  const { classNames: contextClassNames, styles: contextStyles } =
-    React.useContext(SelectContext) || {};
+
+  const { classNames: contextClassNames, styles: contextStyles } = useBaseProps() || {};
 
   let inputNode: React.ComponentElement<any, any> = inputElement || <input />;
 
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<RefOptionListProps, any>((props, ref) => {
+      return <div ref={ref as unknown as React.Ref<HTMLDivElement>}>Option List</div>;
+    });
+    const { container } = render(
+      <BaseSelect
+        displayValues={[]}
+        prefixCls="rc-select"
+        id="base-select"
+        open
+        classNames={customClassNames}
+        styles={customStyle}
+        suffixIcon={<div>arrow</div>}
+        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);
+  });
 });


Reviews:
user: login: zombieJ
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T03:17:12Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between cd8465ab9ddb54157b4ab2b0c0ce38df5e4ddf20 and 2b2a1a75bec6581e8dabf3115682aa4cee5b3216.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `tests/Select.test.tsx` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>tests/Select.test.tsx (1)</summary><blockquote>

<details>
<summary>src/index.ts (1) (1)</summary>

* `BaseSelect` (9-9)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Actions: ✅ test</summary>

<details>
<summary>tests/Select.test.tsx</summary>

[error] 2496-2496: error TS2741: Property '$$typeof' is missing in type '() => React.JSX.Element' but required in type 'ForwardRefExoticComponent<Omit<any, "ref"> & RefAttributes<RefOptionListProps>>'.

</details>

</details>

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T03:08:47Z
user: login: zombieJ
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T07:10:18Z

Review comments:
diff_hunk: @@ -57,8 +58,11 @@ const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = (props, ref)
     open,
     attrs,
   } = props;
-  const { classNames: contextClassNames, styles: contextStyles } =
+  const { classNames: selectClassNames, styles: selectStyles } =
     React.useContext(SelectContext) || {};
+  const { classNames: baseSelectClassNames, styles: baseSelectStyles } = useBaseProps() || {};
+  const contextClassNames = selectClassNames ?? baseSelectClassNames;
+  const contextStyles = selectStyles ?? baseSelectStyles;
path: src/Selector/Input.tsx
user: login: zombieJ
body: 这两个是 `??` 关系？是不需要合并的么
created_at: 2025-03-18T03:17:12Z
author_association: MEMBER
diff_hunk: @@ -2464,4 +2465,46 @@ describe('Select.Basic', () => {
     expect(input).toHaveClass(customClassNames.input);
     expect(input).toHaveStyle(customStyle.input);
   });
+  it('support classnames and styles for baseSelect', () => {
+    const customClassNames = {
+      prefix: 'cutsom-prefix',
+      suffix: 'custom-suffix',
+      list: 'custom-list',
+      listItem: 'custom-item',
+      input: 'custom-input',
+    };
+    const customStyle = {
+      prefix: { color: 'red' },
+      suffix: { color: 'green' },
+      list: { color: 'yellow' },
+      listItem: { color: 'blue' },
+      input: { color: 'black' },
+    };
+    const { container } = render(
+      <BaseSelect
+        displayValues={[]}
+        prefixCls="rc-select"
+        id="base-select"
+        open
+        classNames={customClassNames}
+        styles={customStyle}
+        suffixIcon={<div>arrow</div>}
+        prefix="Foobar"
+        onDisplayValuesChange={() => {}}
+        searchValue=""
+        onSearch={() => {}}
+        OptionList={() => <div>Option List</div>}
+        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);
+  });
path: tests/Select.test.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**这个测试用例很好，但存在一个类型错误需要修复**

测试用例对 BaseSelect 组件的自定义样式和类名支持进行了全面验证，测试覆盖了多个元素（前缀、后缀、输入框）的样式应用。不过在第 2496 行发现一个 TypeScript 错误，与 `OptionList` 属性的类型有关。


根据管道错误信息，`OptionList` 期望的是一个 `ForwardRefExoticComponent`，而不是一个简单的函数组件。请更新这部分代码以修复类型错误：

```diff
-        OptionList={() => <div>Option List</div>}
+        OptionList={React.forwardRef(() => <div>Option List</div>)}
```

或者使用一个符合预期类型的模拟组件：

```diff
-        OptionList={() => <div>Option List</div>}
+        OptionList={({ ...props }) => <div {...props}>Option List</div>}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  it('support classnames and styles for baseSelect', () => {
    const customClassNames = {
      prefix: 'cutsom-prefix',
      suffix: 'custom-suffix',
      list: 'custom-list',
      listItem: 'custom-item',
      input: 'custom-input',
    };
    const customStyle = {
      prefix: { color: 'red' },
      suffix: { color: 'green' },
      list: { color: 'yellow' },
      listItem: { color: 'blue' },
      input: { color: 'black' },
    };
    const { container } = render(
      <BaseSelect
        displayValues={[]}
        prefixCls="rc-select"
        id="base-select"
        open
        classNames={customClassNames}
        styles={customStyle}
        suffixIcon={<div>arrow</div>}
        prefix="Foobar"
        onDisplayValuesChange={() => {}}
        searchValue=""
        onSearch={() => {}}
-        OptionList={() => <div>Option List</div>}
+        OptionList={React.forwardRef(() => <div>Option List</div>)}
        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);
  });
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: ✅ test</summary>

[error] 2496-2496: error TS2741: Property '$$typeof' is missing in type '() => React.JSX.Element' but required in type 'ForwardRefExoticComponent<Omit<any, "ref"> & RefAttributes<RefOptionListProps>>'.

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 1a6f835
created_at: 2025-03-20T03:08:47Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-03-18T03:04:35Z
body: [vc]: #pZAOkm+VFJA8+0oWBhT3PAZcghUqI+e/A4f23HuzM4M=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzZWxlY3QiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vcmVhY3QtY29tcG9uZW50L3NlbGVjdC9BMnpSMmZvYWlBRG1CUTJxRjljazhXeFpYUVVaIiwicHJldmlld1VybCI6InNlbGVjdC1naXQtZm9yay10aGlua2FzYW55LXN1cHBvcnQtZm9yLWItMDc0NjBhLXJlYWN0LWNvbXBvbmVudC52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6InNlbGVjdC1naXQtZm9yay10aGlua2FzYW55LXN1cHBvcnQtZm9yLWItMDc0NjBhLXJlYWN0LWNvbXBvbmVudC52ZXJjZWwuYXBwIn19XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **select** | ✅ Ready ([Inspect](https://vercel.com/react-component/select/A2zR2foaiADmBQ2qF9ck8WxZXQUZ)) | [Visit Preview](https://select-git-fork-thinkasany-support-for-b-07460a-react-component.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/select-git-fork-thinkasany-support-for-b-07460a-react-component.vercel.app?via=pr-comment-feedback-link) | Mar 20, 2025 7:15am |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-18T03:04:38Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
本次更新对 `BaseSelect` 组件进行了重构，新增了可选属性 `classNames` 和 `styles`，以增强样式灵活性。同时，组件内部逻辑调整为使用 `displayValues.length` 来判断选中项的最大数量。此外，`Input` 组件通过引入 `useBaseProps` hook 替代了对 `SelectContext` 的依赖，确保样式和类名的获取更加一致。测试用例也进行了相应更新，以验证新属性的应用。

## Changes

| 文件路径                           | 变更描述 |
| ---------------------------------- | -------- |
| src/BaseSelect/index.tsx           | 引入新的 props（`classNames` 和 `styles`），更新内部逻辑以使用 `displayValues.length` 判断最大选中项数量。 |
| src/Selector/Input.tsx             | 替换对 `SelectContext` 的引用，改为使用 `useBaseProps` hook 获取样式和类名。 |
| tests/Select.test.tsx              | 更新 `Select` 测试用例的描述并修正类名前缀的拼写，新增测试 `BaseSelect` 组件的自定义类名和样式的能力。 |
| src/Select.tsx                     | 将 `selectClassNames` 重命名为 `classNames`，更新上下文传递。 |
| src/SelectContext.ts               | 添加注释，说明 `SelectContext` 仅用于 `Select`，`BaseSelect` 不应使用此上下文。 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant BS as BaseSelect
    participant SC as SelectContext
    participant TB as TransBtn
    participant SEL as Selector
    BS->>BS: 从 props 中提取 classNames 与 styles
    BS->>SC: 获取上下文中的 classNames 与 styles
    SC-->>BS: 返回上下文默认值
    BS->>BS: 使用空值合并确定最终样式与类名
    BS->>TB: 将样式与类名传递给 TransBtn
    BS->>SEL: 将样式与类名传递给 Selector
```

```mermaid
sequenceDiagram
    participant I as Input组件
    participant UB as useBaseProps Hook
    I->>UB: 调用 useBaseProps 获取基本属性
    I->>I: 使用空值合并操作确定最终样式与类名
    I->>I: 渲染 Input 元素
```

## Possibly related PRs
- **react-component/select#1128**：该 PR 同样引入了新的 `classNames` 和 `styles` props，并更新了上下文值，与本次改动在代码层面存在较强关联。
- **react-component/picker#922**：该 PR 引入了类似的 `classNames` 和 `styles` 属性，增强了 Picker 组件的样式灵活性，与本次改动存在关联。
- **react-component/select#1060**：该 PR 引入了 `prefix` prop，增强了 `BaseSelect` 组件的自定义能力，与本次更新的 props 变更相关。

## Poem
> 嗨，我是小兔子，跳跃在代码林间，  
> 新增的 props 如春风拂面，  
> 回退机制悄然奏响和谐篇，  
> TransBtn 与 Selector 共谱欢颜，  
> 代码世界因变更而无限灿烂！  
> 🐇🌟

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> <details>
> <summary>src/BaseSelect/index.tsx</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 8.57.1
> 
> Error: Cannot read config file: /.eslintrc.js
> Error: Cannot find module '@umijs/fabric/dist/eslint'
> Require stack:
> - /.eslintrc.js
> - /node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs
> - /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/cli-engine.js
> - /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/eslint.js
> - /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/index.js
> - /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli.js
> - /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/bin/eslint.js
>     at Module._resolveFilename (node:internal/modules/cjs/loader:1248:15)
>     at Module._load (node:internal/modules/cjs/loader:1074:27)
>     at TracingChannel.traceSync (node:diagnostics_channel:315:14)
>     at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
>     at Module.require (node:internal/modules/cjs/loader:1339:12)
>     at require (node:internal/modules/helpers:135:16)
>     at Object.<anonymous> (/.eslintrc.js:1:14)
>     at Module._compile (node:internal/modules/cjs/loader:1546:14)
>     at Module._extensions..js (node:internal/modules/cjs/loader:1691:10)
>     at Module.load (node:internal/modules/cjs/loader:1317:32)
> 
> </details>
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c0bd1da1cdcab43abdfd9c8404f7514afe89bede and fb71cba346f2213e83dec359f6c2272a1e2073e9.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `src/BaseSelect/index.tsx` (11 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>src/BaseSelect/index.tsx (1)</summary><blockquote>

<details>
<summary>src/utils/valueUtil.ts (1) (1)</summary>

* `isValidCount` (23-25)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (11)</summary><blockquote>

<details>
<summary>src/BaseSelect/index.tsx (11)</summary>

`1-2`: **引入别名修改增强代码清晰度**

将`classnames`库的引入从`classNames`改为`cls`，避免与新增的`classNames`属性名冲突，增强了代码的可读性。

---

`132-136`: **良好的样式扩展支持**

新增的`classNames`和`styles`属性让组件支持更细粒度的样式自定义，可以分别控制prefix、suffix和input部分的样式。

---

`143-144`: **新增maxCount属性**

新增`maxCount`属性用于限制可选择的最大项数，这是一个有用的功能扩展。

---

`230-232`: **正确解构新增属性**

从props中正确解构出新增的`styles`和`classNames`属性，保持代码结构一致性。

---

`417-420`: **优化最大选择数量验证逻辑**

修改使用`displayValues.length`替代原来的`rawValues.length`来判断是否达到最大选择数量，更符合用户直观感受。

---

`425-429`: **一致性调整最大选择数量**

对`getSeparatedContent`函数的调用也相应更新，确保使用`displayValues.length`计算剩余可选数量，保持逻辑一致性。

---

`694-720`: **完善上下文传递**

将新增的`styles`和`classNames`属性正确添加到`baseSelectContext`中，确保子组件可以访问这些样式属性。依赖数组也相应更新，保持渲染优化。

---

`733-748`: **实现后缀图标样式自定义**

使用新的`cls`函数和`classNames?.suffix`为后缀图标添加样式定制能力，同时通过`styles?.suffix`支持内联样式定制。

---

`777-789`: **使用新的cls函数名**

将组件类名合并逻辑从`classNames`更新为`cls`，与导入别名保持一致。

---

`826-829`: **Selector组件样式传递增强**

为Selector组件添加`prefixClassName`和`prefixStyle`属性，传递自定义的前缀样式，增强组件的样式定制能力。

---

`31-31`: <details>
<summary>❓ Verification inconclusive</summary>

**验证BaseSelectSemanticName类型定义**

请确认前缀、后缀和输入部分的语义名称是否需要扩展，当前仅支持'prefix'、'suffix'和'input'，如果组件有其他部分也需要样式自定义，可能需要扩展此类型。

---

🏁 Script executed:

```shell
#!/bin/bash
# 查找代码库中可能使用BaseSelectSemanticName的其他地方
echo "检查BaseSelectSemanticName的使用情况:"
rg -C 2 "BaseSelectSemanticName"

echo "检查是否有其他组件使用semantic相关属性:"
rg -C 2 "classNames.*semantic|semantic.*classNames"
```

Length of output: 1675

---



</details>

**请确认 BaseSelectSemanticName 类型是否需要扩展**

在文件 `src/BaseSelect/index.tsx` 中，目前 `BaseSelectSemanticName` 类型仅定义了 `'prefix'`、`'suffix'` 和 `'input'`。但在 `src/Select.tsx` 中，通过扩展生成了新类型 `SemanticName`，额外包含了 `'listItem'` 和 `'list'`。因此请确认：

- 是否需要将 `'listItem'` 和 `'list'` 等额外语义名称直接加入到 BaseSelect 组件的基础类型中，统一由 `BaseSelectSemanticName` 表达；
- 或者目前在 Select 组件中扩展 BaseSelectSemanticName 的做法符合预期，两者职责分离，各自定义适用的样式部分。

请开发者根据组件设计和样式定制需求确认是否对 BaseSelectSemanticName 做进一步扩展。

</details>

</blockquote></details>

</details>
<!-- internal state start -->


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

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=react-component/select&utm_content=1133):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-03-18T03:05:52Z
body: ## [Codecov](https://app.codecov.io/gh/react-component/select/pull/1133?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=react-component) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 98.26%. Comparing base [(`327196e`)](https://app.codecov.io/gh/react-component/select/commit/327196e8d8d9e3c225483e7c6248eb9b71743a7a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=react-component) to head [(`fb71cba`)](https://app.codecov.io/gh/react-component/select/commit/fb71cba346f2213e83dec359f6c2272a1e2073e9?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=react-component).
> Report is 1 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1133      +/-   ##
==========================================
- Coverage   98.26%   98.26%   -0.01%     
==========================================
  Files          39       39              
  Lines        1501     1500       -1     
  Branches      454      425      -29     
==========================================
- Hits         1475     1474       -1     
  Misses         26       26              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/react-component/select/pull/1133?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=react-component).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=react-component).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE