import { mergeStyles } from '@fluentui/styles'; import { ComponentWithAs, ComposePreparedOptions, ShorthandConfig } from '@fluentui/react-bindings'; import cx from 'classnames'; import * as _ from 'lodash'; import * as React from 'react'; import * as ReactIs from 'react-is'; import { ShorthandValue, Props, PropsOf, ShorthandRenderFunction } from '../types'; type HTMLTag = 'iframe' | 'img' | 'input'; type ShorthandProp = 'children' | 'src' | 'type'; interface CreateShorthandOptions
{
/** Default props object */
defaultProps?: () => Partial ;
}
// It's only necessary to map props that don't use 'children' as value ('children' is the default)
const mappedProps: { [key in HTMLTag]: ShorthandProp } = {
iframe: 'src',
img: 'src',
input: 'type',
};
export type ShorthandFactory = (
value: ShorthandValue ,
options?: CreateShorthandOptions ,
) => React.ReactElement | null | undefined;
// ============================================================
// Factory Creators
// ============================================================
/**
* @param config - Options passed to factory
* @returns A shorthand factory function waiting for `val` and `defaultProps`.
*/
export function createShorthandFactory ;
export function createShorthandFactory ;
export function createShorthandFactory ;
export function createShorthandFactory ({ Component, mappedProp, mappedArrayProp, allowsJSX }) {
if (!ReactIs.isValidElementType(Component)) {
throw new Error('createShorthandFactory() Component must be a string or function.');
}
return (value, options: CreateShorthandOptions ) =>
createShorthandInternal({
allowsJSX,
Component,
mappedProp,
mappedArrayProp,
value,
options,
});
}
// ============================================================
// Factories
// ============================================================
export function createShorthandInternal ({
Component,
mappedProp,
mappedArrayProp,
value,
options = {},
allowsJSX = true,
}: {
Component: React.ElementType ;
mappedProp?: string;
mappedArrayProp?: string;
allowsJSX?: boolean;
value?: ShorthandValue ;
options?: CreateShorthandOptions ;
}) {
if (!ReactIs.isValidElementType(Component)) {
throw new Error('createShorthand() Component must be a string or function.');
}
// short circuit noop values
const valIsNoop = _.isNil(value) || typeof value === 'boolean';
if (valIsNoop && !options.render) return null;
const valIsPrimitive = typeof value === 'string' || typeof value === 'number';
const valIsPropsObject = _.isPlainObject(value);
const valIsArray = _.isArray(value);
const valIsReactElement = React.isValidElement(value);
// unhandled type warning
if (process.env.NODE_ENV !== 'production') {
const displayName = typeof Component === 'string' ? Component : Component.displayName;
if (!valIsPrimitive && !valIsPropsObject && !valIsArray && !valIsReactElement && !valIsNoop) {
/* eslint-disable-next-line no-console */
console.error(
[
`The shorthand prop for "${displayName}" component was passed a JSX element but this slot only supports string|number|object|array|ReactElements.`,
' Use null|undefined|boolean for none.',
` Received: ${value}`,
].join(''),
);
}
if (!allowsJSX && valIsReactElement) {
/* eslint-disable-next-line no-console */
console.error(
[
`The shorthand prop for "${displayName}" component was passed a JSX element but this slot only supports string|number|object|array.`,
' Use null|undefined|boolean for none.',
` Received: ${value}`,
].join(''),
);
}
}
// ----------------------------------------
// Build up props
// ----------------------------------------
const defaultProps = options.defaultProps ? options.defaultProps() || ({} as Props ) : ({} as Props );
// User's props
const usersProps =
(valIsReactElement && ({} as Props )) || (valIsPropsObject && (value as Props )) || ({} as Props );
// Override props
const overrideProps: Props =
typeof options.overrideProps === 'function'
? (options.overrideProps({ ...defaultProps, ...usersProps }) as Props )
: (options.overrideProps as Props ) || ({} as Props );
// Merge props
const props: Props = { ...defaultProps, ...usersProps, ...overrideProps };
const mappedHTMLProps = mappedProps[overrideProps.as || defaultProps.as];
// Map prop for primitive value
if (valIsPrimitive || valIsReactElement) {
(props as any)[mappedHTMLProps || mappedProp || 'children'] = value;
}
// Map prop for array value
if (valIsArray) {
(props as any)[mappedHTMLProps || mappedArrayProp || 'children'] = value;
}
// Merge className
if (defaultProps.className || overrideProps.className || usersProps.className) {
const mergedClassesNames = cx(defaultProps.className, overrideProps.className, usersProps.className);
(props as any).className = _.uniq(mergedClassesNames.split(' ')).join(' ');
}
// Merge style
if (defaultProps.style || overrideProps.style || usersProps.style) {
(props as any).style = { ...defaultProps.style, ...usersProps.style, ...overrideProps.style };
}
// Merge styles
if (defaultProps.styles || overrideProps.styles || usersProps.styles) {
(props as any).styles = mergeStyles(defaultProps.styles, usersProps.styles, overrideProps.styles);
}
// ----------------------------------------
// Get key
// ----------------------------------------
const { generateKey = true } = options;
// Use key or generate key
if (generateKey && _.isNil(props.key)) {
if (valIsPrimitive) {
// use string/number shorthand values as the key
(props as any).key = value;
}
if (valIsReactElement) {
// use the key from React Element
const elementKey = (value as React.ReactElement).key;
// ;
fluentComposeConfig?: ComposePreparedOptions ;
},
value?: ShorthandValue ,
options?: CreateShorthandOptions ,
): React.ReactElement;
export function createShorthand (Component, value?, options?) {
const { mappedProp = 'children', allowsJSX = true, mappedArrayProp } =
Component.shorthandConfig || Component.fluentComposeConfig?.shorthandConfig || {};
return createShorthandInternal ({
Component,
mappedProp,
allowsJSX,
mappedArrayProp,
value,
options: options || {},
});
}