Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 23a6ea7

Browse files
EmilyyyLiu刘欢
and
刘欢
authored
feat: Add attribute itemRender to 'segmented' (#301)
* feat: Add attributes to 'segmented' * feat: add props to itemRender * feat:add ts to ItemContent * feat: using...props * feat: change itemInfo value * feat: change ts * feat: delete SegementItem * feat: pass optionData to SegmentedOption * test: add itemRender test --------- Co-authored-by: 刘欢 <lh01217311@antgroup.com>
1 parent 17b4ba3 commit 23a6ea7

File tree

2 files changed

+105
-34
lines changed

2 files changed

+105
-34
lines changed

‎src/index.tsx‎

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export interface SegmentedLabeledOption<ValueType = SegmentedRawOption> {
2222
title?: string;
2323
}
2424

25+
type ItemRender = (
26+
node: React.ReactNode,
27+
info: { item: SegmentedLabeledOption },
28+
) => React.ReactNode;
29+
2530
type SegmentedOptions<T = SegmentedRawOption> = (
2631
| T
2732
| SegmentedLabeledOption<T>
@@ -44,6 +49,7 @@ export interface SegmentedProps<ValueType = SegmentedValue>
4449
name?: string;
4550
classNames?: Partial<Record<SemanticName, string>>;
4651
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
52+
itemRender?: ItemRender;
4753
}
4854

4955
function getValidTitle(option: SegmentedLabeledOption) {
@@ -80,6 +86,7 @@ const InternalSegmentedOption: React.FC<{
8086
style?: React.CSSProperties;
8187
classNames?: Partial<Record<SemanticName, string>>;
8288
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
89+
data: SegmentedLabeledOption;
8390
disabled?: boolean;
8491
checked: boolean;
8592
label: React.ReactNode;
@@ -95,12 +102,14 @@ const InternalSegmentedOption: React.FC<{
95102
onKeyDown: (e: React.KeyboardEvent) => void;
96103
onKeyUp: (e: React.KeyboardEvent) => void;
97104
onMouseDown: () => void;
105+
itemRender?: ItemRender;
98106
}> = ({
99107
prefixCls,
100108
className,
101109
style,
102110
styles,
103111
classNames: segmentedClassNames,
112+
data,
104113
disabled,
105114
checked,
106115
label,
@@ -113,15 +122,15 @@ const InternalSegmentedOption: React.FC<{
113122
onKeyDown,
114123
onKeyUp,
115124
onMouseDown,
125+
itemRender = (node: React.ReactNode) => node,
116126
}) => {
117127
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
118128
if (disabled) {
119129
return;
120130
}
121131
onChange(event, value);
122132
};
123-
124-
return (
133+
const itemContent: React.ReactNode = (
125134
<label
126135
className={classNames(className, {
127136
[`${prefixCls}-item-disabled`]: disabled,
@@ -155,6 +164,7 @@ const InternalSegmentedOption: React.FC<{
155164
</div>
156165
</label>
157166
);
167+
return itemRender(itemContent, { item: data });
158168
};
159169

160170
const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
@@ -174,6 +184,7 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
174184
styles,
175185
classNames: segmentedClassNames,
176186
motionName = 'thumb-motion',
187+
itemRender,
177188
...restProps
178189
} = props;
179190

@@ -258,6 +269,54 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
258269
break;
259270
}
260271
};
272+
273+
const renderOption = (segmentedOption: SegmentedLabeledOption) => {
274+
const {
275+
label,
276+
value: optionValue,
277+
disabled: optionDisabled,
278+
title,
279+
} = segmentedOption;
280+
const optionData: SegmentedLabeledOption = {
281+
label,
282+
value: optionValue,
283+
disabled: optionDisabled,
284+
title,
285+
};
286+
return (
287+
<InternalSegmentedOption
288+
{...segmentedOption}
289+
name={name}
290+
data={optionData}
291+
itemRender={itemRender}
292+
key={optionValue}
293+
prefixCls={prefixCls}
294+
className={classNames(
295+
segmentedOption.className,
296+
`${prefixCls}-item`,
297+
segmentedClassNames?.item,
298+
{
299+
[`${prefixCls}-item-selected`]:
300+
optionValue === rawValue && !thumbShow,
301+
[`${prefixCls}-item-focused`]:
302+
isFocused && isKeyboard && optionValue === rawValue,
303+
},
304+
)}
305+
style={styles?.item}
306+
classNames={segmentedClassNames}
307+
styles={styles}
308+
checked={optionValue === rawValue}
309+
onChange={handleChange}
310+
onFocus={handleFocus}
311+
onBlur={handleBlur}
312+
onKeyDown={handleKeyDown}
313+
onKeyUp={handleKeyUp}
314+
onMouseDown={handleMouseDown}
315+
disabled={!!disabled || !!optionDisabled}
316+
/>
317+
);
318+
};
319+
261320
return (
262321
<div
263322
role="radiogroup"
@@ -294,38 +353,7 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
294353
setThumbShow(false);
295354
}}
296355
/>
297-
{segmentedOptions.map((segmentedOption) => (
298-
<InternalSegmentedOption
299-
{...segmentedOption}
300-
name={name}
301-
key={segmentedOption.value}
302-
prefixCls={prefixCls}
303-
className={classNames(
304-
segmentedOption.className,
305-
`${prefixCls}-item`,
306-
segmentedClassNames?.item,
307-
{
308-
[`${prefixCls}-item-selected`]:
309-
segmentedOption.value === rawValue && !thumbShow,
310-
[`${prefixCls}-item-focused`]:
311-
isFocused &&
312-
isKeyboard &&
313-
segmentedOption.value === rawValue,
314-
},
315-
)}
316-
style={styles?.item}
317-
classNames={segmentedClassNames}
318-
styles={styles}
319-
checked={segmentedOption.value === rawValue}
320-
onChange={handleChange}
321-
onFocus={handleFocus}
322-
onBlur={handleBlur}
323-
onKeyDown={handleKeyDown}
324-
onKeyUp={handleKeyUp}
325-
onMouseDown={handleMouseDown}
326-
disabled={!!disabled || !!segmentedOption.disabled}
327-
/>
328-
))}
356+
{segmentedOptions.map(renderOption)}
329357
</div>
330358
</div>
331359
);

‎tests/index.test.tsx‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,4 +799,47 @@ describe('Segmented keyboard navigation', () => {
799799
expect(itemElement.style.color).toBe('yellow');
800800
expect(labelElement.style.backgroundColor).toBe('black');
801801
});
802+
describe('itemRender', () => {
803+
it('When "itemRender" is not configured, render the original "label"', () => {
804+
const { container } = render(
805+
<Segmented options={['iOS', 'Android', 'Web']} />,
806+
);
807+
const label = container.querySelector('.rc-segmented-item-label');
808+
expect(label).toHaveTextContent('iOS');
809+
});
810+
it('Configure "itemRender" to render the return value', () => {
811+
const { container } = render(
812+
<Segmented
813+
options={['iOS', 'Android', 'Web']}
814+
itemRender={(node) => <div className="test-title">{node}</div>}
815+
/>,
816+
);
817+
const labels = container.querySelectorAll('.test-title');
818+
expect(labels).toHaveLength(3);
819+
});
820+
it('should pass complete params to itemRender', () => {
821+
const mockItemRender = jest.fn((node, params) => node);
822+
const testData = {
823+
label: 'iOS',
824+
value: 'iOS',
825+
disabled: false,
826+
title: 'iOS',
827+
};
828+
render(
829+
<Segmented
830+
options={[{ ...testData, className: 'test-class' }, 'Android', 'Web']}
831+
itemRender={mockItemRender}
832+
/>,
833+
);
834+
expect(mockItemRender).toHaveBeenCalledTimes(3);
835+
const callArgs = mockItemRender.mock.calls[0];
836+
const receivedParams = callArgs[1];
837+
expect(receivedParams).toEqual({
838+
item: {
839+
...testData,
840+
},
841+
});
842+
expect(React.isValidElement(callArgs[0])).toBeTruthy();
843+
});
844+
});
802845
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /