import { ComponentSlotClasses, useStyles, useTelemetry, useFluentContext } from '@fluentui/react-bindings'; import { ComponentSlotStylesPrepared, ComponentSlotStylesResolved, mergeStyles } from '@fluentui/styles'; import cx from 'classnames'; import * as _ from 'lodash'; import * as PropTypes from 'prop-types'; import * as React from 'react'; import { commonPropTypes, UIComponentProps, ChildrenComponentProps } from '../../utils'; type ChildrenFunction = (params: { styles: ComponentSlotStylesPrepared; classes: string }) => React.ReactElement; export type FlexItemChildren = React.ReactElement | ChildrenFunction; export interface FlexItemProps extends UIComponentProps, ChildrenComponentProps { /** Controls item's alignment. */ align?: 'auto' | 'start' | 'end' | 'center' | 'baseline' | 'stretch'; /** Defines size of the item. */ size?: 'size.half' | 'size.quarter' | 'size.small' | 'size.medium' | 'size.large' | string; /** * Item can fill remaining space of the container. * If numeric value is provided, remaining space will be distributed proportionally between all the items. * */ grow?: boolean | number; /** * Controls item's ability to shrink. * */ shrink?: boolean | number; /** * Item can be pushed towards opposite side in the container's direction. */ push?: boolean; /** * IGNORE (will be refactored and not exposed via API). * Value is automatically set by parent Flex component. */ flexDirection?: 'row' | 'column'; } export type FlexItemStylesProps = Pick; const applyStyles = ( element: React.ReactElement, styles: ComponentSlotStylesResolved, classes: ComponentSlotClasses, ): React.ReactElement => { if (!styles) { return element; } // if element is DOM element if (typeof element.type === 'string') { return React.cloneElement(element, { className: cx(element.props.className, classes.root), }); } // assuming element is Fluent UI element return React.cloneElement(element, { styles: mergeStyles(styles.root || {}, element.props.styles), }); }; export const flexItemClassName = 'ui-flex__item'; /** * A FlexItem is a layout component that customizes alignment of Flex child. */ export const FlexItem: React.FC & { __isFlexItem: boolean } = props => { const context = useFluentContext(); const { setStart, setEnd } = useTelemetry(FlexItem.displayName, context.telemetry); setStart(); const { align, children, className, design, grow, flexDirection, push, shrink, size, styles, variables } = props; const { classes, styles: resolvedStyles } = useStyles(FlexItem.displayName, { className: flexItemClassName, mapPropsToStyles: () => ({ align, grow, flexDirection, push, shrink, size, }), mapPropsToInlineStyles: () => ({ className, design, styles, variables, }), rtl: context.rtl, }); let element: React.ReactElement; // pass calculated bits using Render Props pattern if (typeof children === 'function') { element = children({ styles: resolvedStyles.root, classes: classes.root, }); } else if (_.isNil(children)) { element = null; } else { element = applyStyles(React.Children.only(children) as React.ReactElement, resolvedStyles, classes); } setEnd(); return element; }; FlexItem.displayName = 'FlexItem'; FlexItem.propTypes = { ...commonPropTypes.createCommon({ children: false, accessibility: false, content: false, }), children: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), align: PropTypes.oneOf(['auto', 'start', 'end', 'center', 'baseline', 'stretch']), size: PropTypes.oneOfType([ PropTypes.oneOf(['size.half', 'size.quarter', 'size.small', 'size.medium', 'size.large']), PropTypes.string, ]), shrink: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]), push: PropTypes.bool, /** * Will be automatically set by parent Flex component */ flexDirection: PropTypes.oneOf(['row', 'column']), }; // Boolean flag for now, Symbol-based approach may be used instead. // However, there are concerns related to browser compatibility if Symbols will be used. // Completely alternative approach - check class name of React element (and generalize this logic). FlexItem.__isFlexItem = true;