-
Notifications
You must be signed in to change notification settings - Fork 99
/
utils.js
144 lines (123 loc) · 4.01 KB
/
utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import React from "react";
/* eslint-disable @typescript-eslint/no-empty-function */
export const noop = () => {};
/**
* Tests if children are nil in React and Preact.
* @param {Object} children The children prop of a component.
* @returns {Boolean}
*/
export const isChildrenNil = (children) =>
children === null ||
children === undefined ||
(Array.isArray(children) && children.length === 0);
/**
* Gets only specified types children
* @param children
* @param {Array} types
* @returns {[]}
*/
export const getChildren = (children, types) => {
const ret = [];
const strTypes = types.map((t) => t.displayName || t.name);
React.Children.toArray(children).forEach((item) => {
const idx = types.indexOf(item.type);
if (idx !== -1) {
ret[idx] = item;
} else {
const is = item?.props?.as ?? item?.props?.is;
const typeofIs = typeof is;
if (typeofIs === "function") {
// Type
const fIdx = types.indexOf(is);
if (fIdx !== -1) {
ret[fIdx] = React.cloneElement(item, { ...item.props, as: null }); // Cloning to remove "as" attribute, which is not desirable
}
} else if (typeofIs === "object") {
// forward ref
const typeName = is.name || is.displayName;
const tIdx = strTypes.indexOf(typeName);
if (tIdx !== -1) {
ret[tIdx] = React.cloneElement(item, { ...item.props, as: null }); // Cloning to remove "as" attribute, which is not desirable
}
} else if (typeofIs === "string") {
const sIdx = strTypes.indexOf(is);
if (sIdx !== -1) {
ret[sIdx] = item;
}
}
}
});
return ret;
};
export const getComponentName = (component) => {
if (typeof component === "string") {
return component;
}
if ("type" in component) {
const componentType = typeof component.type;
if (componentType === "function" || componentType === "object") {
if ("displayName" in component.type) {
return component.type.displayName;
}
if ("name" in component.type) {
return component.type.name;
}
} else if (componentType === "string") {
return component.type;
}
return "undefined";
}
return "undefined";
};
/**
* PropTypes validator.
* Checks if all children is allowed by its types.
* Empty string nodes are always allowed for convenience.
* Returns function for propTypes
* @param {Array} allowedTypes
* @return {Function}
*/
export const allowedChildren = (allowedTypes) => (
props,
propName,
componentName
) => {
const allowedTypesAsStrings = allowedTypes.map(
(t) => t.name || t.displayName
);
// Function as Child is not supported by React.Children... functions
// and can be antipattern: https://americanexpress.io/faccs-are-an-antipattern/
// But we don't check fd function is passed as children and its intentional
// Passing function as children has no effect in chat-ui-kit
const forbidden = React.Children.toArray(props[propName]).find((item) => {
if (typeof item === "string" && item.trim().length === 0) {
// Ignore string
return false;
}
if (allowedTypes.indexOf(item.type) === -1) {
const is = item?.props?.as || item?.props?.is;
const typeofIs = typeof is;
if (typeofIs === "function") {
// Type
return allowedTypes.indexOf(is) === -1;
} else if (typeofIs === "object") {
// Forward ref
const typeName = is.name || is.displayName;
return allowedTypesAsStrings.indexOf(typeName) === -1;
} else if (typeofIs === "string") {
return allowedTypesAsStrings.indexOf(is) === -1;
} else {
return true;
}
}
return undefined;
});
if (typeof forbidden !== "undefined") {
const typeName = getComponentName(forbidden);
const allowedNames = allowedTypes
.map((t) => t.name || t.displayName)
.join(", ");
const errMessage = `"${typeName}" is not a valid child for ${componentName}. Allowed types: ${allowedNames}`;
return new Error(errMessage);
}
};