Skip to content

Commit 96bce2b

Browse files
committed
feat: add semantic
1 parent 931b7a0 commit 96bce2b

5 files changed

Lines changed: 205 additions & 9 deletions

File tree

src/BaseSelect/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,16 @@ import SelectInput from '../SelectInput';
2828
import type { ComponentsConfig } from '../hooks/useComponents';
2929
import useComponents from '../hooks/useComponents';
3030

31-
export type BaseSelectSemanticName = 'prefix' | 'suffix' | 'input' | 'clear';
31+
export type BaseSelectSemanticName =
32+
| 'prefix'
33+
| 'suffix'
34+
| 'input'
35+
| 'clear'
36+
| 'placeholder'
37+
| 'content'
38+
| 'item'
39+
| 'itemContent'
40+
| 'itemRemove';
3241

3342
/**
3443
* ZombieJ:

src/SelectInput/Content/MultipleContent.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export default React.forwardRef<HTMLInputElement, SharedContentProps>(function M
4242
maxTagPlaceholder: maxTagPlaceholderFromContext,
4343
maxTagTextLength,
4444
maxTagCount,
45+
classNames,
46+
styles,
4547
} = useBaseProps();
4648

4749
const selectionItemPrefixCls = `${prefixCls}-selection-item`;
@@ -85,14 +87,25 @@ export default React.forwardRef<HTMLInputElement, SharedContentProps>(function M
8587
) => (
8688
<span
8789
title={getTitle(item)}
88-
className={clsx(selectionItemPrefixCls, {
89-
[`${selectionItemPrefixCls}-disabled`]: itemDisabled,
90-
})}
90+
className={clsx(
91+
selectionItemPrefixCls,
92+
{
93+
[`${selectionItemPrefixCls}-disabled`]: itemDisabled,
94+
},
95+
classNames?.item,
96+
)}
97+
style={styles?.item}
9198
>
92-
<span className={`${selectionItemPrefixCls}-content`}>{content}</span>
99+
<span
100+
className={clsx(`${selectionItemPrefixCls}-content`, classNames?.itemContent)}
101+
style={styles?.itemContent}
102+
>
103+
{content}
104+
</span>
93105
{closable && (
94106
<TransBtn
95-
className={`${selectionItemPrefixCls}-remove`}
107+
className={clsx(`${selectionItemPrefixCls}-remove`, classNames?.itemRemove)}
108+
style={styles?.itemRemove}
96109
onMouseDown={onPreventMouseDown}
97110
onClick={onClose}
98111
customizeIcon={removeIcon}
@@ -185,6 +198,8 @@ export default React.forwardRef<HTMLInputElement, SharedContentProps>(function M
185198
return (
186199
<Overflow
187200
prefixCls={`${prefixCls}-content`}
201+
className={classNames?.content}
202+
style={styles?.content}
188203
prefix={!displayValues.length && (!searchValue || !triggerOpen) ? <Placeholder /> : null}
189204
data={displayValues}
190205
renderItem={renderItem}

src/SelectInput/Content/Placeholder.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import * as React from 'react';
2+
import { clsx } from 'clsx';
23
import { useSelectInputContext } from '../context';
4+
import useBaseProps from '../../hooks/useBaseProps';
35

46
export interface PlaceholderProps {
57
show?: boolean;
68
}
79

810
export default function Placeholder(props: PlaceholderProps) {
911
const { prefixCls, placeholder, displayValues } = useSelectInputContext();
12+
const { classNames, styles } = useBaseProps();
1013
const { show = true } = props;
1114

1215
if (displayValues.length) {
@@ -15,9 +18,10 @@ export default function Placeholder(props: PlaceholderProps) {
1518

1619
return (
1720
<div
18-
className={`${prefixCls}-placeholder`}
21+
className={clsx(`${prefixCls}-placeholder`, classNames?.placeholder)}
1922
style={{
2023
visibility: show ? 'visible' : 'hidden',
24+
...styles?.placeholder,
2125
}}
2226
>
2327
{placeholder}

src/SelectInput/Content/SingleContent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const SingleContent = React.forwardRef<HTMLInputElement, SharedContentProps>(
1212
({ inputProps }, ref) => {
1313
const { prefixCls, searchValue, activeValue, displayValues, maxLength, mode } =
1414
useSelectInputContext();
15-
const { triggerOpen, title: rootTitle, showSearch } = useBaseProps();
15+
const { triggerOpen, title: rootTitle, showSearch, classNames, styles } = useBaseProps();
1616
const selectContext = React.useContext(SelectContext);
1717

1818
const [inputChanged, setInputChanged] = React.useState(false);
@@ -71,7 +71,7 @@ const SingleContent = React.forwardRef<HTMLInputElement, SharedContentProps>(
7171
}, [combobox, activeValue]);
7272

7373
return (
74-
<div className={`${prefixCls}-content`}>
74+
<div className={clsx(`${prefixCls}-content`, classNames?.content)} style={styles?.content}>
7575
{displayValue ? (
7676
<div {...optionProps}>{displayValue.label}</div>
7777
) : (

tests/semantic.test.tsx

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import React, { forwardRef } from 'react';
2+
import { render } from '@testing-library/react';
3+
import BaseSelect from '../src/BaseSelect';
4+
import type { OptionListProps, RefOptionListProps } from '@/OptionList';
5+
6+
const OptionList = forwardRef<RefOptionListProps, OptionListProps>(() => (
7+
<div className="popup">Popup</div>
8+
));
9+
10+
describe('BaseSelect Semantic Styles', () => {
11+
const defaultProps = {
12+
prefixCls: 'rc-select',
13+
OptionList,
14+
displayValues: [],
15+
emptyOptions: true,
16+
id: 'test',
17+
onDisplayValuesChange: () => {},
18+
onSearch: () => {},
19+
searchValue: '',
20+
};
21+
22+
it('should apply semantic classNames correctly', () => {
23+
const classNames = {
24+
prefix: 'custom-prefix',
25+
suffix: 'custom-suffix',
26+
input: 'custom-input',
27+
clear: 'custom-clear',
28+
placeholder: 'custom-placeholder',
29+
content: 'custom-content',
30+
item: 'custom-item',
31+
itemContent: 'custom-item-content',
32+
itemRemove: 'custom-item-remove',
33+
};
34+
35+
const { container } = render(
36+
<BaseSelect
37+
{...defaultProps}
38+
classNames={classNames}
39+
placeholder="Test placeholder"
40+
prefix={<span>Prefix</span>}
41+
suffix={<span>Suffix</span>}
42+
/>,
43+
);
44+
45+
// Test prefix className
46+
expect(container.querySelector('.rc-select-prefix')).toHaveClass('custom-prefix');
47+
48+
// Test suffix className
49+
expect(container.querySelector('.rc-select-suffix')).toHaveClass('custom-suffix');
50+
51+
// Test input className
52+
expect(container.querySelector('.rc-select-input')).toHaveClass('custom-input');
53+
54+
// Test content className
55+
expect(container.querySelector('.rc-select-content')).toHaveClass('custom-content');
56+
57+
// Test placeholder className
58+
expect(container.querySelector('.rc-select-placeholder')).toHaveClass('custom-placeholder');
59+
});
60+
61+
it('should apply semantic styles correctly', () => {
62+
const styles = {
63+
prefix: { color: 'red' },
64+
suffix: { color: 'blue' },
65+
input: { fontSize: '16px' },
66+
clear: { cursor: 'pointer' },
67+
placeholder: { opacity: 0.6 },
68+
content: { padding: '4px' },
69+
item: { margin: '2px' },
70+
itemContent: { fontWeight: 'bold' },
71+
itemRemove: { background: 'transparent' },
72+
};
73+
74+
const { container } = render(
75+
<BaseSelect
76+
{...defaultProps}
77+
styles={styles}
78+
placeholder="Test placeholder"
79+
prefix={<span>Prefix</span>}
80+
suffix={<span>Suffix</span>}
81+
/>,
82+
);
83+
84+
// Test prefix style
85+
expect(container.querySelector('.rc-select-prefix')).toHaveStyle({ color: 'red' });
86+
87+
// Test suffix style
88+
expect(container.querySelector('.rc-select-suffix')).toHaveStyle({ color: 'blue' });
89+
90+
// Test input style
91+
expect(container.querySelector('.rc-select-input')).toHaveStyle({ fontSize: '16px' });
92+
93+
// Test content style
94+
expect(container.querySelector('.rc-select-content')).toHaveStyle({ padding: '4px' });
95+
96+
// Test placeholder style
97+
expect(container.querySelector('.rc-select-placeholder')).toHaveStyle({ opacity: 0.6 });
98+
});
99+
100+
it('should apply item semantic styles in multiple mode', () => {
101+
const classNames = {
102+
item: 'custom-item',
103+
itemContent: 'custom-item-content',
104+
itemRemove: 'custom-item-remove',
105+
};
106+
107+
const styles = {
108+
item: { margin: '2px' },
109+
itemContent: { fontWeight: 'bold' },
110+
itemRemove: { background: 'transparent' },
111+
};
112+
113+
const displayValues = [
114+
{ key: '1', label: 'Option 1', value: '1' },
115+
{ key: '2', label: 'Option 2', value: '2' },
116+
];
117+
118+
const { container } = render(
119+
<BaseSelect
120+
{...defaultProps}
121+
mode="multiple"
122+
displayValues={displayValues}
123+
classNames={classNames}
124+
styles={styles}
125+
/>,
126+
);
127+
128+
// Test item className and style
129+
const items = container.querySelectorAll('.rc-select-selection-item');
130+
expect(items[0]).toHaveClass('custom-item');
131+
expect(items[0]).toHaveStyle({ margin: '2px' });
132+
133+
// Test item content className and style
134+
const itemContents = container.querySelectorAll('.rc-select-selection-item-content');
135+
expect(itemContents[0]).toHaveClass('custom-item-content');
136+
expect(itemContents[0]).toHaveStyle({ fontWeight: 'bold' });
137+
138+
// Test item remove className and style
139+
const removeButtons = container.querySelectorAll('.rc-select-selection-item-remove');
140+
expect(removeButtons[0]).toHaveClass('custom-item-remove');
141+
expect(removeButtons[0]).toHaveStyle({ background: 'transparent' });
142+
});
143+
144+
it('should apply clear icon semantic styles when allowClear is enabled', () => {
145+
const classNames = {
146+
clear: 'custom-clear',
147+
};
148+
149+
const styles = {
150+
clear: { cursor: 'pointer' },
151+
};
152+
153+
const { container } = render(
154+
<BaseSelect
155+
{...defaultProps}
156+
displayValues={[{ key: '1', label: 'Option 1', value: '1' }]}
157+
allowClear
158+
classNames={classNames}
159+
styles={styles}
160+
/>,
161+
);
162+
163+
// Test clear icon className and style
164+
const clearIcon = container.querySelector('.rc-select-clear');
165+
expect(clearIcon).toHaveClass('custom-clear');
166+
expect(clearIcon).toHaveStyle({ cursor: 'pointer' });
167+
});
168+
});

0 commit comments

Comments
 (0)