import * as React from 'react'; /** * A panel item context is metadata passed to a panel item to give some * context as to where in the container it is being rendered. */ export interface PanelItemContext { index: number; } export type PanelFragment = React.ReactNode; /** * panel item prop can be statically assigned or dynamically determined * based on a provided item context */ export type PanelItemProp< TValue, TContext extends PanelItemContext = PanelItemContext > = TValue | ((context: TContext) => TValue); /** * panel items can be wrapped in a different component * this allows composing new items with an existing panel */ export type PanelItemTemplate< TContext extends PanelItemContext = PanelItemContext > = (fragment: PanelFragment, context: TContext) => PanelFragment; /** * panel item props allow component to inject props to the rendered * block structure. */ export interface PanelItemProps< T = {}, TContext extends PanelItemContext = PanelItemContext > { /** * apply custom class name to the corresponding panel item */ itemClassName?: PanelItemProp; /** * apply custom style to the corresponding panel item */ itemStyle?: PanelItemProp; /** * apply custom props to the corresponding panel item */ itemProps?: PanelItemProp; } export interface PanelTemplateProps< T extends PanelItemContext = PanelItemContext > { /** * apply a custom template to the renderd panel item */ itemTemplate?: PanelItemTemplate; } export interface PanelRenderProps { compact?: boolean; /** * apply a custom content to an empty panel with no items to render */ emptyContent?: PanelFragment; } export interface PanelProps< T = {}, TContext extends PanelItemContext = PanelItemContext > extends PanelItemProps, PanelTemplateProps, PanelRenderProps { fill?: boolean; } export abstract class Panel extends React.Component< TProps > { public static displayName = 'Panel'; public static defaultComponentClass = 'div'; public static getPanelItemPropValue< TValue, TContext extends PanelItemContext >(prop: (PanelItemProp) | undefined, context: TContext) { if (prop instanceof Function) { return prop(context); } return prop; } protected renderPanel( panelClassName?: string, panelProps?: PanelProps, componentClass?: React.ReactType, ) { const { className, children, props, rest } = React.Component.restProps( panelProps || this.props, x => { const { itemClassName, itemStyle, itemProps, itemTemplate, compact, emptyContent, } = x; return { itemClassName, itemStyle, itemProps, itemTemplate, compact, emptyContent, }; }, ); const Component = componentClass || Panel.defaultComponentClass; const componentClassName = this.wxr.classNames( 'Panel', { compact: props.compact }, panelClassName, className, ); return ( {this.renderItems(children)} ); } protected renderItems( children?: React.ReactNode, componentClass?: React.ReactType, ) { const props: PanelProps = this.props; return React.Children.map(children || this.props.children, (x, i) => { return this.renderItem(x, i, componentClass); }) .asIterable() .filter(x => (x ? true : false)) .defaultIfEmpty(this.renderEmpty()) .toArray(); } protected renderEmpty() { if (this.props.emptyContent) { return (
{this.props.emptyContent}
); } return false; } protected renderItem( itemTemplate: PanelFragment, index: number, componentClass?: React.ReactType, ): PanelFragment { const context = { index }; const fragment = this.renderItemFragment( itemTemplate, context, componentClass == null ? Panel.defaultComponentClass : componentClass, ); if (this.props.itemTemplate == null) { return fragment; } return this.props.itemTemplate(fragment, context); } protected renderItemFragment( itemTemplate: PanelFragment, context: PanelItemContext, Component: React.ReactType, ): PanelFragment { if (itemTemplate) { const key = this.getItemKey(itemTemplate, context.index); const className = this.wxr.classNames( 'Panel-Item', Panel.getPanelItemPropValue(this.props.itemClassName, context), ); const style = Panel.getPanelItemPropValue(this.props.itemStyle, context); const props: {} | undefined = Panel.getPanelItemPropValue(this.props.itemProps, context) || {}; return Component === '' && React.isValidElement(itemTemplate) ? ( React.cloneElement(itemTemplate, { key, className, style, ...props }) ) : ( {itemTemplate} ); } return null; } protected getItemKey(itemTemplate: PanelFragment, index: number) { let key: any = index; if ( itemTemplate != null && React.isValidElement(itemTemplate) && itemTemplate.key != null ) { key = itemTemplate.key; } return key; } }