lucid-ui
Version:
A UI component library from AppNexus.
145 lines (144 loc) • 5.82 kB
JavaScript
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'react-peek/prop-types';
import _ from 'lodash';
import { isPlainObjectOrEsModule, omitFunctionPropsDeep, } from './state-management';
// creates a React component
export function createClass(spec) {
const { _isPrivate = false, getDefaultProps, statics = {}, components = {}, reducers = {}, selectors = {}, initialState = getDefaultProps &&
omitFunctionPropsDeep(getDefaultProps.apply(spec)), propName = null, propTypes = {}, render = () => null, ...restDefinition } = spec;
const propTypeValidators = {
...propTypes,
..._.mapValues(spec.components, (componentValue, componentKey) => PropTypes.any `Props for ${componentValue.displayName || componentKey}`),
};
// Intentionally keep this object type inferred so it can be passed to
// `createReactClass`
const newDefinition = {
getDefaultProps,
...restDefinition,
statics: {
...statics,
...components,
_isPrivate,
reducers,
selectors,
initialState,
propName,
},
propTypes: propTypeValidators,
render,
};
if (!_.isUndefined(newDefinition.statics)) {
newDefinition.statics.definition = newDefinition;
}
const newClass = createReactClass(newDefinition);
// This conditional (and breaking change) was introduced to help us move from
// legacy React classes to functional components & es6 classes which lack
// `getDefaultProps`.
if (newClass.getDefaultProps) {
newClass.defaultProps = newClass.getDefaultProps();
delete newClass.getDefaultProps;
}
return newClass;
}
// return all elements matching the specified types
export function filterTypes(children, types) {
if (types === undefined)
return [];
return _.filter(React.Children.toArray(children), (element) => React.isValidElement(element) &&
_.includes(_.castArray(types), element.type));
}
// return all elements found in props and children of the specified types
export function findTypes(props, types) {
if (types === undefined) {
return [];
}
// get elements from props (using types.propName)
const elementsFromProps = _.reduce(_.castArray(types), (acc, type) => {
return _.isNil(type.propName)
? []
: createElements(type, _.flatten(_.values(_.pick(props, type.propName))));
}, []);
if (props.children === undefined) {
return elementsFromProps;
}
// return elements from props and elements from children
return elementsFromProps.concat(filterTypes(props.children, types));
}
// return all elements found in props and children of the specified types
// export function findTypes<P2>(
// props: { children?: React.ReactNode },
// types?: TypesType<P2>
// ): React.ReactNode[] {
// if (types === undefined) {
// return [];
// }
// // get elements from props (using types.propName)
// const elementsFromProps: React.ReactNode[] = _.reduce(
// _.castArray<any>(types),
// (acc: React.ReactNode[], type): React.ReactNode[] => {
// return _.isNil(type.propName)
// ? []
// : createElements(
// type,
// _.flatten(_.values(_.pick(props, type.propName)))
// );
// },
// []
// );
// if (props.children === undefined) {
// return elementsFromProps;
// }
// // return elements from props and elements from children
// return elementsFromProps.concat(filterTypes<P2>(props.children, types));
// }
// return all elements not matching the specified types
export function rejectTypes(children, types) {
types = [].concat(types); // coerce to Array
return _.reject(React.Children.toArray(children), (element) => React.isValidElement(element) && _.includes(types, element.type));
}
// return an array of elements (of the given type) for each of the values
export function createElements(type, values = []) {
return _.reduce(values, (elements, typeValue) => {
if (React.isValidElement(typeValue) && typeValue.type === type) {
return elements.concat(typeValue);
}
else if (isPlainObjectOrEsModule(typeValue) &&
!React.isValidElement(typeValue)) {
return elements.concat(React.createElement(type, typeValue));
}
else if (_.isUndefined(typeValue)) {
return elements;
}
else {
return elements.concat(React.createElement(type, null, typeValue));
}
}, []);
}
// return the first element found in props and children of the specificed type(s)
export function getFirst(props, types, defaultValue) {
return _.first(findTypes(props, types)) || defaultValue;
}
// Omit props defined in propTypes of the given type and any extra keys given
// in third argument
//
// We also have a "magic" prop that's always excluded called `callbackId`. That
// prop can be used to identify a component in a list without having to create
// extra closures.
//
// Note: The Partial<P> type is referring to the props passed into the omitProps,
// not the props defined on the component.
export function omitProps(props, component, keys = [], targetIsDOMElement = true) {
// We only want to exclude the `callbackId` key when we're omitting props
// destined for a dom element
const additionalOmittedKeys = targetIsDOMElement
? ['initialState', 'callbackId']
: ['initialState'];
// this is to support non-createClass components that we've converted to TypeScript
if (component === undefined) {
return _.omit(props, keys.concat(additionalOmittedKeys));
}
return _.omit(props, _.keys(component.propTypes)
.concat(keys)
.concat(additionalOmittedKeys));
}