Skip to content

Commit 07d13ad

Browse files
authored
feat: mentions support allowClear (#204)
* feat: mentions support allowClear * fix: lint issue * fix: remove unused code * fix: remove className for allow clear
1 parent fb168c2 commit 07d13ad

File tree

6 files changed

+289
-6
lines changed

6 files changed

+289
-6
lines changed

docs/demo.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ Option has `key` and filter only hit by `key`
3636
## Debug
3737

3838
<code src="./examples/debug.tsx"></code>
39+
40+
## Allow Clear
41+
42+
<code src="./examples/allowClear.tsx"></code>

docs/examples/allowClear.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Mentions from 'rc-mentions';
2+
import React, { useState } from 'react';
3+
4+
export default function App() {
5+
const [value, setValue] = useState('hello world');
6+
7+
return (
8+
<div>
9+
<p>Uncontrolled</p>
10+
<Mentions allowClear />
11+
<p>controlled</p>
12+
<Mentions value={value} onChange={setValue} allowClear />
13+
</div>
14+
);
15+
}

src/Mentions.tsx

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323

2424
type BaseTextareaAttrs = Omit<
2525
TextAreaProps,
26-
'prefix' | 'onChange' | 'onSelect' | 'allowClear' | 'showCount'
26+
'prefix' | 'onChange' | 'onSelect' | 'showCount'
2727
>;
2828

2929
export type Placement = 'top' | 'bottom';
@@ -73,7 +73,7 @@ const InternalMentions = forwardRef<MentionsRef, MentionsProps>(
7373
(props, ref) => {
7474
const {
7575
// Style
76-
prefixCls = 'rc-mentions',
76+
prefixCls,
7777
className,
7878
style,
7979

@@ -86,6 +86,7 @@ const InternalMentions = forwardRef<MentionsRef, MentionsProps>(
8686
children,
8787
options,
8888
open,
89+
allowClear,
8990

9091
// Events
9192
validateSearch = defaultValidateSearch,
@@ -475,16 +476,52 @@ const InternalMentions = forwardRef<MentionsRef, MentionsProps>(
475476
);
476477

477478
const Mentions = forwardRef<MentionsRef, MentionsProps>(
478-
({ suffix, prefixCls, classes, value, ...rest }, ref) => {
479+
(
480+
{
481+
suffix,
482+
prefixCls = 'rc-mentions',
483+
classes,
484+
defaultValue,
485+
value: customValue,
486+
allowClear,
487+
onChange,
488+
...rest
489+
},
490+
ref,
491+
) => {
492+
// ============================== Value ===============================
493+
const [mergedValue, setMergedValue] = useMergedState('', {
494+
defaultValue,
495+
value: customValue,
496+
});
497+
498+
// ============================== Change ==============================
499+
const triggerChange = (currentValue: string) => {
500+
setMergedValue(currentValue);
501+
onChange?.(currentValue);
502+
};
503+
504+
// ============================== Reset ===============================
505+
const handleReset = () => {
506+
triggerChange('');
507+
};
508+
479509
return (
480510
<BaseInput
481511
inputElement={
482-
<InternalMentions prefixCls={prefixCls} ref={ref} {...rest} />
512+
<InternalMentions
513+
prefixCls={prefixCls}
514+
ref={ref}
515+
onChange={triggerChange}
516+
{...rest}
517+
/>
483518
}
484519
suffix={suffix}
485520
prefixCls={prefixCls}
486521
classes={classes}
487-
value={value}
522+
value={mergedValue}
523+
allowClear={allowClear}
524+
handleReset={handleReset}
488525
/>
489526
);
490527
},

tests/AllowClear.spec.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { fireEvent, render } from '@testing-library/react';
2+
import type { TextareaHTMLAttributes } from 'react';
3+
import Mentions from '../src';
4+
5+
describe('should support allowClear', () => {
6+
it('should change type when click', () => {
7+
const { container } = render(<Mentions allowClear />);
8+
fireEvent.change(container.querySelector('textarea')!, {
9+
target: { value: '111' },
10+
});
11+
expect(container.querySelector('textarea')?.value).toEqual('111');
12+
expect(
13+
container.querySelector('.rc-mentions-clear-icon-hidden'),
14+
).toBeFalsy();
15+
fireEvent.click(container.querySelector('.rc-mentions-clear-icon')!);
16+
expect(
17+
container.querySelector('.rc-mentions-clear-icon-hidden'),
18+
).toBeTruthy();
19+
expect(container.querySelector('textarea')?.value).toEqual('');
20+
});
21+
22+
it('should not show icon if value is undefined, null or empty string', () => {
23+
const wrappers = [null, undefined, ''].map(val =>
24+
render(
25+
<Mentions
26+
allowClear
27+
value={val as TextareaHTMLAttributes<HTMLTextAreaElement>['value']}
28+
/>,
29+
),
30+
);
31+
wrappers.forEach(({ asFragment, container }) => {
32+
expect(container.querySelector('textarea')?.value).toEqual('');
33+
expect(
34+
container.querySelector('.rc-mentions-clear-icon-hidden'),
35+
).toBeTruthy();
36+
expect(asFragment().firstChild).toMatchSnapshot();
37+
});
38+
});
39+
40+
it('should not show icon if defaultValue is undefined, null or empty string', () => {
41+
const wrappers = [null, undefined, ''].map(val =>
42+
render(
43+
<Mentions
44+
allowClear
45+
defaultValue={
46+
val as TextareaHTMLAttributes<HTMLTextAreaElement>['value']
47+
}
48+
/>,
49+
),
50+
);
51+
wrappers.forEach(({ asFragment, container }) => {
52+
expect(container.querySelector('textarea')?.value).toEqual('');
53+
expect(
54+
container.querySelector('.rc-mentions-clear-icon-hidden'),
55+
).toBeTruthy();
56+
expect(asFragment().firstChild).toMatchSnapshot();
57+
});
58+
});
59+
60+
it('should trigger event correctly', () => {
61+
const onChange = jest.fn();
62+
const { container } = render(
63+
<Mentions allowClear defaultValue="111" onChange={onChange} />,
64+
);
65+
fireEvent.click(container.querySelector('.rc-mentions-clear-icon')!);
66+
expect(onChange).toHaveBeenCalledWith('');
67+
expect(container.querySelector('textarea')?.value).toBe('');
68+
});
69+
});
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`should support allowClear should not show icon if defaultValue is undefined, null or empty string 1`] = `
4+
<span
5+
class="rc-mentions-affix-wrapper"
6+
>
7+
<div
8+
class="rc-mentions"
9+
>
10+
<textarea
11+
class="rc-textarea"
12+
rows="1"
13+
/>
14+
</div>
15+
<span
16+
class="rc-mentions-suffix"
17+
>
18+
<span
19+
class="rc-mentions-clear-icon rc-mentions-clear-icon-hidden"
20+
role="button"
21+
tabindex="-1"
22+
>
23+
24+
</span>
25+
</span>
26+
</span>
27+
`;
28+
29+
exports[`should support allowClear should not show icon if defaultValue is undefined, null or empty string 2`] = `
30+
<span
31+
class="rc-mentions-affix-wrapper"
32+
>
33+
<div
34+
class="rc-mentions"
35+
>
36+
<textarea
37+
class="rc-textarea"
38+
rows="1"
39+
/>
40+
</div>
41+
<span
42+
class="rc-mentions-suffix"
43+
>
44+
<span
45+
class="rc-mentions-clear-icon rc-mentions-clear-icon-hidden"
46+
role="button"
47+
tabindex="-1"
48+
>
49+
50+
</span>
51+
</span>
52+
</span>
53+
`;
54+
55+
exports[`should support allowClear should not show icon if defaultValue is undefined, null or empty string 3`] = `
56+
<span
57+
class="rc-mentions-affix-wrapper"
58+
>
59+
<div
60+
class="rc-mentions"
61+
>
62+
<textarea
63+
class="rc-textarea"
64+
rows="1"
65+
/>
66+
</div>
67+
<span
68+
class="rc-mentions-suffix"
69+
>
70+
<span
71+
class="rc-mentions-clear-icon rc-mentions-clear-icon-hidden"
72+
role="button"
73+
tabindex="-1"
74+
>
75+
76+
</span>
77+
</span>
78+
</span>
79+
`;
80+
81+
exports[`should support allowClear should not show icon if value is undefined, null or empty string 1`] = `
82+
<span
83+
class="rc-mentions-affix-wrapper"
84+
>
85+
<div
86+
class="rc-mentions"
87+
>
88+
<textarea
89+
class="rc-textarea"
90+
rows="1"
91+
/>
92+
</div>
93+
<span
94+
class="rc-mentions-suffix"
95+
>
96+
<span
97+
class="rc-mentions-clear-icon rc-mentions-clear-icon-hidden"
98+
role="button"
99+
tabindex="-1"
100+
>
101+
102+
</span>
103+
</span>
104+
</span>
105+
`;
106+
107+
exports[`should support allowClear should not show icon if value is undefined, null or empty string 2`] = `
108+
<span
109+
class="rc-mentions-affix-wrapper"
110+
>
111+
<div
112+
class="rc-mentions"
113+
>
114+
<textarea
115+
class="rc-textarea"
116+
rows="1"
117+
/>
118+
</div>
119+
<span
120+
class="rc-mentions-suffix"
121+
>
122+
<span
123+
class="rc-mentions-clear-icon rc-mentions-clear-icon-hidden"
124+
role="button"
125+
tabindex="-1"
126+
>
127+
128+
</span>
129+
</span>
130+
</span>
131+
`;
132+
133+
exports[`should support allowClear should not show icon if value is undefined, null or empty string 3`] = `
134+
<span
135+
class="rc-mentions-affix-wrapper"
136+
>
137+
<div
138+
class="rc-mentions"
139+
>
140+
<textarea
141+
class="rc-textarea"
142+
rows="1"
143+
/>
144+
</div>
145+
<span
146+
class="rc-mentions-suffix"
147+
>
148+
<span
149+
class="rc-mentions-clear-icon rc-mentions-clear-icon-hidden"
150+
role="button"
151+
tabindex="-1"
152+
>
153+
154+
</span>
155+
</span>
156+
</span>
157+
`;

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
"@@/*": [".dumi/tmp/*"],
1313
"rc-mentions": ["src/index.ts"]
1414
}
15-
}
15+
},
16+
"include": [".dumirc.ts", "**/*.ts", "**/*.tsx"]
1617
}

0 commit comments

Comments
 (0)