UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

145 lines (144 loc) 5.82 kB
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)); }