Skip to content

Commit 6b7ec1e

Browse files
authored
feat: add more semantic structure (#1168)
* feat: add semantic * chore: AI suggestion * test: clean up
1 parent 931b7a0 commit 6b7ec1e

File tree

6 files changed

+200
-10
lines changed

6 files changed

+200
-10
lines changed

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/BaseSelect.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { OptionListProps, RefOptionListProps } from '@/OptionList';
1+
import type { OptionListProps, RefOptionListProps } from '../src/OptionList';
22
import { fireEvent, render } from '@testing-library/react';
33
import { forwardRef, act } from 'react';
44
import BaseSelect from '../src/BaseSelect';

tests/semantic.test.tsx

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

0 commit comments

Comments
 (0)