Skip to content

Commit 3b4e493

Browse files
committed
feat(mergeProps): utility to merge component props and global config
1 parent ea6698f commit 3b4e493

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
},
4040
"dependencies": {
4141
"is-mobile": "^5.0.0",
42+
"classnames": "^2.5.1",
43+
"is-plain-object": "^5.0.0",
4244
"react-is": "^18.2.0"
4345
},
4446
"devDependencies": {

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { default as useEvent } from './hooks/useEvent';
22
export { default as useMergedState } from './hooks/useMergedState';
3+
export { default as mergeProps } from './mergeProps';
34
export { supportNodeRef, supportRef, useComposeRef } from './ref';
45
export { default as get } from './utils/get';
56
export { default as set } from './utils/set';

src/mergeProps.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import classNames from 'classnames';
2+
import { isPlainObject } from 'is-plain-object';
3+
import { isValidElement } from 'react';
4+
5+
function mergeClassNames<T>(classNamesA: T, classNamesB: T): T {
6+
const result = { ...classNamesA };
7+
Object.keys(classNamesB).forEach(key => {
8+
result[key] = mergeClassName(classNamesA[key], classNamesB[key]);
9+
});
10+
return result;
11+
}
12+
13+
function mergeClassName(classNameA?: any, classNameB?: any): string {
14+
if (typeof classNameA === 'object' || typeof classNameB === 'object') {
15+
return mergeClassNames(classNameA, classNameB);
16+
}
17+
18+
return classNames(classNameA, classNameB);
19+
}
20+
21+
export default function mergeProps<T>(...list: T[]): T {
22+
if (list.length > 2) {
23+
return mergeProps(list[0], mergeProps(...list.slice(1)));
24+
}
25+
const result: T = { ...list[0] };
26+
list[1] &&
27+
Object.keys(list[1]).forEach(key => {
28+
if (key === 'className') {
29+
result[key] = classNames(result[key], list[1][key]);
30+
} else if (key === 'classNames') {
31+
result[key] = mergeClassNames(result[key], list[1][key]);
32+
} else if (isPlainObject(list[1][key]) && !isValidElement(list[1][key])) {
33+
result[key] = mergeProps(result[key], list[1][key]);
34+
} else {
35+
result[key] = list[1][key] ?? result[key];
36+
}
37+
});
38+
return result;
39+
}

tests/mergeProps.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import mergeProps from '../src/mergeProps';
2+
3+
test('merge className', () => {
4+
expect(mergeProps({ className: 'foo' }, { className: 'bar' })).toEqual({
5+
className: 'foo bar',
6+
});
7+
});
8+
9+
test('merge classNames', () => {
10+
expect(
11+
mergeProps(
12+
{
13+
classNames: {
14+
body: 'bam',
15+
footer: 'foo',
16+
},
17+
},
18+
{
19+
classNames: {
20+
footer: 'bar',
21+
header: 'boo',
22+
},
23+
},
24+
),
25+
).toEqual({
26+
classNames: {
27+
body: 'bam',
28+
footer: 'foo bar',
29+
header: 'boo',
30+
},
31+
});
32+
});
33+
34+
test('merge style', () => {
35+
expect(
36+
mergeProps(
37+
{
38+
style: {
39+
background: '#000',
40+
color: '#666',
41+
},
42+
},
43+
{
44+
style: {
45+
background: '#fff',
46+
},
47+
},
48+
),
49+
).toEqual({
50+
style: {
51+
background: '#fff',
52+
color: '#666',
53+
},
54+
});
55+
});
56+
57+
test('merge boolean prop', () => {
58+
expect(
59+
mergeProps(
60+
{
61+
disabled: true,
62+
loading: false,
63+
},
64+
{
65+
disabled: false,
66+
},
67+
),
68+
).toEqual({
69+
disabled: false,
70+
loading: false,
71+
});
72+
});
73+
74+
test('merge number prop', () => {
75+
expect(
76+
mergeProps(
77+
{
78+
value: 1,
79+
},
80+
{
81+
value: 2,
82+
},
83+
),
84+
).toEqual({
85+
value: 2,
86+
});
87+
});
88+
89+
test('merge non-plain object prop', () => {
90+
const dateObj = new Date();
91+
const urlObj = new URL('https://example.com/');
92+
expect(
93+
mergeProps(
94+
{
95+
value: dateObj,
96+
},
97+
{
98+
value: urlObj,
99+
},
100+
),
101+
).toEqual({
102+
value: urlObj,
103+
});
104+
});

0 commit comments

Comments
 (0)