/* eslint-disable camelcase */ import {ReactNode, MouseEvent, FocusEvent, CSSProperties} from 'react' import dayjs, {Dayjs} from 'dayjs' import c from 'classnames' import {Popper} from '@befe/brick-comp-popper' import {UiTimePanel, COL_NAMES} from '@befe/brick-comp-time-picker' import {Link} from '@befe/brick-comp-link' import {Button} from '@befe/brick-comp-button' import { createDelayActionQueue, createListenerGroup, createRef, getClosestMappedSize, pickDerivedStateFromProps, } from '@befe/brick-utils' import {isUndefined, pickBy} from 'lodash-es' import {InputProps} from '@befe/brick-comp-input' import {ConfiguredComponent} from '@befe/brick-core' import {UiRangePanel} from '../ui-comps/ui-range-panel' import { createMonthPanel, TypeModuleMonthPanel, } from '../modules/create-month-panel' import {getPickerMode} from '../utils/picker-utils' import {createDatePanel} from '../modules/create-date-panel' import {getValueExtractor} from '../utils/get-value-extractor' import {extendDayjsPlugins} from '../utils/extend-dayjs-plugins' import {localeDictDatePicker} from '../locale' import {syncDisplayedDatePair} from '../utils/sync-range-displayed-date-pair' import {QuickActions} from './create-quick-actions' import {DEFAULT_ITEM_FORMAT} from './const-default-format' import {createRangePickerUserInput} from './create-range-input' import { TypeActionLink, TypeDatePickerMode, TypeDateValue, TypePickerSize, TypePickerStatus, } from './prop-types' import {preventPickerBlurred} from './create-inner-date-picker' type InnerRangePickerPropsFromInput = Pick export interface RangePickerProps extends InnerRangePickerPropsFromInput { className?: string startValue?: TypeDateValue endValue?: TypeDateValue defaultStartValue?: TypeDateValue defaultEndValue?: TypeDateValue disabled?: boolean size?: TypePickerSize status?: TypePickerStatus mode?: TypeDatePickerMode itemFormat?: string renderItem?: ( currentDate: Dayjs, today: Dayjs ) => (ReactNode | string | number) /** * @todo: 未实现 range picker 的 disabled 问题 */ getDisabledItem?: (currentDate: Dayjs, today: Dayjs) => boolean /** * 当日期面板打开时, 设置其 "显示的面板位置" * * 这个可能主要在当没有选择日期时, 用于确定面板月份的显示策略 ( 缺省方案是显示 "当月" + "1 个月" ) */ getDefaultDisplayDateRange?: ( today: Dayjs, ) => { start: Dayjs end: Dayjs } showTime?: boolean hideNow?: boolean showSecond?: boolean getDisabledTime?: (currentDate: Dayjs) => { start: number | null end: number | null } open?: boolean | undefined clearable?: boolean startPlaceholder?: string endPlaceholder?: string startStyle?: CSSProperties endStyle?: CSSProperties popupClassName?: string popupStyle?: CSSProperties onChange?: (start: Dayjs | null, end: Dayjs | null) => void /** * @todo: 待实现 * * 这个传递 start, end 的原因, 可参见 date picker 的 onOpenChange 的注释 */ onOpenChange?: ( openStatus: boolean, start: Dayjs | null, end: Dayjs | null, ) => void renderFooter?: () => ReactNode | TypeActionLink[] renderQuickActions?: () => TypeActionLink[] onFocus?: (e: FocusEvent) => void onBlur?: (e: FocusEvent) => void autoFocus?: boolean onMouseEnter?: (e: MouseEvent) => void onMouseLeave?: (e: MouseEvent) => void yearStart?: number yearEnd?: number } export interface RangePickerState { open: boolean isStartZooming: boolean isEndZooming: boolean startValue: Dayjs | null endValue: Dayjs | null startDisplayedDate: Dayjs | null endDisplayedDate: Dayjs | null startZoomedDisplayedDate: Dayjs | null endZoomedDisplayedDate: Dayjs | null isStartUserInput: boolean startUserInputText: string isEndUserInput: boolean endUserInputText: string startFocus: boolean endFocus: boolean isSelecting: boolean firstSelectedDate: Dayjs | null secondHoveringDate: Dayjs | null dateTimeValue: Dayjs | null dateTimeDisplayedDate: Dayjs | null dateTimeStage: 'start' | 'end' dateTimeState: { hour: number | undefined minute: number | undefined second: number | undefined } } type TypeRangePickerValueProp = 'startValue' | 'endValue'; type TypeRangePickerDisplayedProp = 'startDisplayedDate' | 'endDisplayedDate'; type TypeRangePickerDefaultProp = 'defaultStartValue' | 'defaultEndValue'; function isDateTimeRangeProps(props: RangePickerProps) { return (props.mode || 'date') === 'date' && props.showTime } function initPickerValue( comp: InnerRangePicker, { defaultValueProp, valueProp, displayDateProp, mode, }: { defaultValueProp: TypeRangePickerDefaultProp valueProp: TypeRangePickerValueProp displayDateProp: TypeRangePickerDisplayedProp mode: RangePickerProps['mode'] } ) { mode = mode || 'date' const intialValue = typeof comp.props[defaultValueProp] === 'undefined' ? comp.props[valueProp] : comp.props[defaultValueProp] comp.state[valueProp] = intialValue ? isDateTimeRangeProps(comp.props) ? dayjs(intialValue) : dayjs(intialValue).startOf(mode) : null comp.state[displayDateProp] = dayjs(intialValue || undefined) .startOf(mode === 'date' ? 'month' : 'year') } /** */ export class InnerRangePicker extends ConfiguredComponent { static dayjs = dayjs static displayName = 'RangePicker' static defaultProps: RangePickerProps = { clearable: true, } defaultItemFormat = DEFAULT_ITEM_FORMAT[this.props.mode || 'date'] componentLocale = localeDictDatePicker state: RangePickerState = { open: false, startValue: null, endValue: null, startDisplayedDate: null, endDisplayedDate: null, isStartZooming: false, isEndZooming: false, startZoomedDisplayedDate: null, endZoomedDisplayedDate: null, isStartUserInput: false, startUserInputText: '', isEndUserInput: false, endUserInputText: '', startFocus: false, endFocus: false, isSelecting: false, firstSelectedDate: null, secondHoveringDate: null, dateTimeValue: null, dateTimeDisplayedDate: null, dateTimeStage: 'start', dateTimeState: { hour: undefined, minute: undefined, second: undefined, }, } refPanelWrapper = createRef() userInput = createRangePickerUserInput(this) get size(): RangePickerProps['size'] { return this.getDefaultValueUsingTheme('size', 'baseSize') } /** * @deprecated @0427: 为了避免歧义, 改为 inputWrapper (因此处并非 input element) */ get inputElem() { return this.userInput.inputWrapperElem } get inputWrapperElem() { return this.userInput.inputWrapperElem } get startInputElem() { return this.userInput.startInputElem } get endInputElem() { return this.userInput.endInputElem } focus() { this.startInputElem?.focus() } getWeekDayText = (day: number) => this.getLocaleText('get_week_day_text', day) getPanelTitle_yearMonth = (date: Dayjs) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return this.getLocaleText('get_year_month_title', date as any) } getPanelTitle_year = (date: Dayjs) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return this.getLocaleText('get_year_title', date as any) } startDatePanel = createDatePanel(this, { isRange: true, isStartPanel: true, getTitle: this.getPanelTitle_yearMonth, getWeekDayText: this.getWeekDayText, }) endDatePanel = createDatePanel(this, { isRange: true, isStartPanel: false, getTitle: this.getPanelTitle_yearMonth, getWeekDayText: this.getWeekDayText, }) dateTimePanel = createDatePanel(this, { valueProp: 'dateTimeValue', displayProp: 'dateTimeDisplayedDate', onMouseDown: preventPickerBlurred, getTitle: this.getPanelTitle_yearMonth, getWeekDayText: this.getWeekDayText, renderPanelSideContent: () => this.renderTimeArea(), }) getMonthText = (month: number) => this.getLocaleText('get_month_text', month) zoomedStartDatePanel: TypeModuleMonthPanel = createMonthPanel(this, { isRange: true, isForDateZooming: true, isStartPanel: true, valueProp: 'startDisplayedDate', displayProp: 'startZoomedDisplayedDate', getTitle: this.getPanelTitle_year, getMonthText: this.getMonthText, }) zoomedEndDatePanel: TypeModuleMonthPanel = createMonthPanel(this, { isRange: true, isForDateZooming: true, isStartPanel: false, valueProp: 'endDisplayedDate', displayProp: 'endZoomedDisplayedDate', getTitle: this.getPanelTitle_year, getMonthText: this.getMonthText, }) /** * @todo: month range picker */ startMonthPanel = createMonthPanel(this, { isRange: true, isStartPanel: true, getTitle: this.getPanelTitle_year, getMonthText: this.getMonthText, syncRangePickersYearAside: () => { const year = this.state.startDisplayedDate?.year() if (!year) { return } const endMonthPanel = this.endMonthPanel endMonthPanel.setYearRangeAnchor(year) endMonthPanel.syncAsideScroll() }, }) endMonthPanel = createMonthPanel(this, { isRange: true, isStartPanel: false, getTitle: this.getPanelTitle_year, getMonthText: this.getMonthText, syncRangePickersYearAside: () => { const year = this.state.endDisplayedDate?.year() if (!year) { return } const startMonthPanel = this.startMonthPanel startMonthPanel.setYearRangeAnchor(year) startMonthPanel.syncAsideScroll() }, }) actionQueue = createDelayActionQueue() listenerGroup = createListenerGroup() get className() { const {className} = this.props const size = this.size return c( 'brick-range-picker', { [`brick-range-picker-size-${size || ''}`]: size, }, className ) } get popupClassName() { const {popupClassName} = this.props const size = getClosestMappedSize(this.size) return c( 'brick-range-picker-popper', { [`brick-range-picker-popper-size-${size || ''}`]: size, 'brick-date-picker-popper-show-time': this.isDateTimeRange, }, popupClassName ) } get computedItemFormat() { const itemFormat = this.props.itemFormat if (itemFormat) { return itemFormat } if (this.isDateTimeRange) { return DEFAULT_ITEM_FORMAT[ this.props.showSecond ? 'date-time' : 'date-time-without-second' ] } const mode = this.props.mode || 'date' return DEFAULT_ITEM_FORMAT[mode] } get computedItemValueExtractor() { return getValueExtractor(this) } get shouldShowPanel() { return this.state.open || this.state.isStartUserInput || this.state.isEndUserInput } triggerChange = ( { startValue = this.state.startValue, endValue = this.state.endValue, }: { startValue?: Dayjs | null endValue?: Dayjs | null } ) => { const {onChange} = this.props onChange && onChange( startValue, endValue ) } handleCalendarIconClick = () => { if (!this.props.disabled) { this.setOpenStatus({ open: true, }) this.startInputElem?.focus() } } setOpenStatus = ( { open = this.state.open, isStartUserInput = this.state.isStartUserInput, isEndUserInput = this.state.isEndUserInput, }: { open?: boolean isStartUserInput?: boolean isEndUserInput?: boolean }) => { const {getDefaultDisplayDateRange} = this.props const prevOpen = this.shouldShowPanel const currOpen = open || isStartUserInput || isEndUserInput this.setState({ open, isStartUserInput, isEndUserInput, }) if (currOpen !== prevOpen && currOpen) { const startValue = this.state.startValue const endValue = this.state.endValue const { startDisplayedDate, endDisplayedDate, } = syncDisplayedDatePair( this.props.mode, startValue, endValue, getDefaultDisplayDateRange ) this.setState({ isSelecting: false, startDisplayedDate, endDisplayedDate, }) if (this.props.mode === 'month') { const startMonthPanel = this.startMonthPanel startMonthPanel.setYearRangeAnchor( startDisplayedDate.year() ) startMonthPanel.syncAsideScroll() const endMonthPanel = this.endMonthPanel endMonthPanel.setYearRangeAnchor( endDisplayedDate.year() ) endMonthPanel.syncAsideScroll() } } } refUITimePanel: UiTimePanel | null = null get isDateTimeRange() { return isDateTimeRangeProps(this.props) } syncDateTimeState = ( value: Dayjs | null, onStateChanged?: () => void ) => { this.setState({ dateTimeValue: value, dateTimeDisplayedDate: value || dayjs(), dateTimeState: { hour: value?.hour(), minute: value?.minute(), second: value?.second(), }, }, onStateChanged) } focusDateTimeStage = (stage: 'start' | 'end') => { if (!this.isDateTimeRange) { return } const value = stage === 'end' ? this.state.endValue || this.state.startValue || this.state.dateTimeValue || dayjs() : this.state.startValue || this.state.dateTimeValue || dayjs() this.setState({ dateTimeStage: stage, dateTimeValue: value, dateTimeDisplayedDate: value, dateTimeState: { hour: value.hour(), minute: value.minute(), second: value.second(), }, }) } handleDateTimeSelected = (selectedDate: Dayjs) => { const time = this.state.dateTimeState const value = selectedDate .set('hour', time.hour || 0) .set('minute', time.minute || 0) .set('second', this.props.showSecond ? (time.second || 0) : 0) const isStartStage = this.state.dateTimeStage === 'start' this.setState({ dateTimeValue: value, dateTimeDisplayedDate: value, isStartUserInput: isStartStage || this.state.isStartUserInput, isEndUserInput: !isStartStage || this.state.isEndUserInput, startUserInputText: isStartStage ? this.userInput.formatStartInputValue(value) : this.state.startUserInputText, endUserInputText: isStartStage ? this.state.endUserInputText : this.userInput.formatEndInputValue(value), }) } changeDateTime = ( propName: typeof COL_NAMES[number], propValue: number ) => { const currentValue = this.state.dateTimeValue if (!currentValue) { return } const value = currentValue.set(propName, propValue) this.setState({ dateTimeValue: value, dateTimeDisplayedDate: value, dateTimeState: { ...this.state.dateTimeState, [propName]: propValue, }, }) } setCurrentDateTime = () => { const value = dayjs().startOf('second') this.syncDateTimeState(value, () => { this.refUITimePanel?.scrollToSelected() }) } confirmDateTime = () => { const value = this.state.dateTimeValue if (!value) { return } if (this.state.dateTimeStage === 'start') { this.setState({ startValue: value, dateTimeStage: 'end', dateTimeValue: this.state.endValue || value, dateTimeDisplayedDate: this.state.endValue || value, dateTimeState: { hour: (this.state.endValue || value).hour(), minute: (this.state.endValue || value).minute(), second: (this.state.endValue || value).second(), }, startUserInputText: this.userInput.formatStartInputValue(value), }, () => { this.endInputElem?.focus() }) return } let startValue = this.state.startValue let endValue: Dayjs | null = value if (startValue && endValue.isBefore(startValue)) { [startValue, endValue] = [endValue, startValue] } this.setState({ startValue, endValue, dateTimeStage: 'start', dateTimeValue: endValue, startUserInputText: startValue ? this.userInput.formatStartInputValue(startValue) : '', endUserInputText: endValue ? this.userInput.formatEndInputValue(endValue) : '', open: false, isStartUserInput: false, isEndUserInput: false, }) this.triggerChange({startValue, endValue}) this.props.onOpenChange?.(false, startValue, endValue) } getDisabledTimeRangeList = () => { const getDisabledTime = this.props.getDisabledTime const currentDate = this.state.dateTimeValue if (!getDisabledTime || !currentDate) { return [] } const {start, end} = getDisabledTime(currentDate) const toTimeTuple = (seconds: number): [number, number, number] => [ Math.floor(seconds / 3600), Math.floor(seconds % 3600 / 60), seconds % 60, ] const ranges: Array<[ [number, number, number] | null, [number, number, number] | null, ]> = [] if (start !== null && start > 0) { ranges.push([[0, 0, 0], toTimeTuple(start - 1)]) } if (end !== null && end < 86399) { ranges.push([toTimeTuple(end + 1), [23, 59, 59]]) } return ranges } renderTimeArea = () => ( { this.refUITimePanel = panel }} hour={this.state.dateTimeState.hour} minute={this.state.dateTimeState.minute} second={this.state.dateTimeState.second} showSecond={this.props.showSecond} getDisabledRangeList={this.getDisabledTimeRangeList} onChange={this.changeDateTime} /> ) constructor(props: any) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument super(props) // 确保 dayjs 插件扩展成功 extendDayjsPlugins() if (this.props.mode === 'quarter') { console.error('目前 brick 范围选择器暂不支持季度模式') } initPickerValue(this, { defaultValueProp: 'defaultStartValue', valueProp: 'startValue', displayDateProp: 'startDisplayedDate', mode: this.props.mode, }) initPickerValue(this, { defaultValueProp: 'defaultEndValue', valueProp: 'endValue', displayDateProp: 'endDisplayedDate', mode: this.props.mode, }) // 确保前后不要出现同一个月 if ( this.state.endDisplayedDate!.isSame(this.state.startDisplayedDate) ) { const mode = this.props.mode || 'date' if (mode === 'date') { this.state.endDisplayedDate = this.state.endDisplayedDate!.add(1, 'month') } else if (mode === 'month') { this.state.endDisplayedDate = this.state.endDisplayedDate!.add(1, 'year') } } const initialDateTimeValue = this.props.startValue ? dayjs(this.props.startValue) : this.state.startValue || dayjs() this.state.dateTimeValue = initialDateTimeValue this.state.dateTimeDisplayedDate = initialDateTimeValue this.state.dateTimeState = { hour: initialDateTimeValue.hour(), minute: initialDateTimeValue.minute(), second: initialDateTimeValue.second(), } const onRangeSelected = (startValue: Dayjs, endValue: Dayjs) => { this.triggerChange({ startValue, endValue, }) this.setState({ startUserInputText: this.userInput.formatStartInputValue(startValue), endUserInputText: this.userInput.formatEndInputValue(endValue), }) this.setOpenStatus({ open: false, isStartUserInput: false, isEndUserInput: false, }) } this.startDatePanel.init({ onZoomOut: () => { this.setState({ isStartZooming: true, startZoomedDisplayedDate: this.state.startDisplayedDate, }) this.zoomedStartDatePanel.setYearRangeAnchor( this.state.startDisplayedDate!.year() ) this.zoomedStartDatePanel.syncAsideScroll() }, onSelectedRange: onRangeSelected, }) this.endDatePanel.init({ onZoomOut: () => { this.setState({ isEndZooming: true, endZoomedDisplayedDate: this.state.endDisplayedDate, }) this.zoomedEndDatePanel.setYearRangeAnchor( this.state.startDisplayedDate!.year() ) this.zoomedEndDatePanel.syncAsideScroll() }, onSelectedRange: onRangeSelected, }) this.dateTimePanel.init({ onZoomOut: () => undefined, onSelected: this.handleDateTimeSelected, }) this.zoomedStartDatePanel.init({ onZoomIn: () => { this.setState({isStartZooming: false}) }, onSelected: (value) => { const nextMonth = value.add(1, 'month') if (nextMonth.isAfter(this.state.endDisplayedDate)) { this.setState({ endDisplayedDate: nextMonth, }) } this.setState({ isStartZooming: false, }) }, }) this.zoomedEndDatePanel.init({ onZoomIn: () => { this.setState({isEndZooming: false}) }, onSelected: (value) => { this.setState({isEndZooming: false}) const prevMonth = value.subtract(1, 'month') if (prevMonth.isBefore(this.state.startDisplayedDate)) { this.setState({ startDisplayedDate: prevMonth, }) } this.setState({ isEndZooming: false, }) }, }) this.startMonthPanel.init({ onSelectedRange: onRangeSelected, }) this.endMonthPanel.init({ onSelectedRange: onRangeSelected, }) } static getDerivedStateFromProps( nextProps: RangePickerProps ) { const partialState = pickDerivedStateFromProps( nextProps, ['open', 'startValue', 'endValue'] ) const mode = nextProps.mode || 'date' if (partialState) { if (partialState.startValue) { const value = dayjs(partialState.startValue) partialState.startValue = isDateTimeRangeProps(nextProps) ? value : value.startOf(mode) } if (partialState.endValue) { const value = dayjs(partialState.endValue) partialState.endValue = isDateTimeRangeProps(nextProps) ? value : value.startOf(mode) } } return partialState || null } componentDidMount(): void { this.listenerGroup.add(document, { mousedown: (e: MouseEvent) => { const targetElem = e.target as Node if ( this.state.open && this.refPanelWrapper.elem && this.refPanelWrapper.elem.elemPopperWrap && !this.refPanelWrapper.elem.elemPopperWrap.contains(targetElem) && this.userInput.inputWrapperElem && !this.userInput.inputWrapperElem.contains(targetElem) ) { this.setOpenStatus({ open: false, }) } }, }) this.actionQueue.execute() } componentDidUpdate( prevProps: Readonly, prevState: Readonly ): void { this.actionQueue.execute() if ( this.isDateTimeRange && ( prevState.open !== this.state.open || prevState.dateTimeStage !== this.state.dateTimeStage ) ) { this.refUITimePanel?.scrollToSelected() } } componentWillUnmount(): void { this.listenerGroup.removeAll() } quickActions = new QuickActions() /** * @question. 这个是为了做 "测试用" 提供的内部 API? * * @param start * @param end */ setValue = (start?: TypeDateValue, end?: TypeDateValue) => { let startValue: Dayjs | null | undefined let endValue: Dayjs | null | undefined const mode = getPickerMode(this.props.mode) if (start) { startValue = isDateTimeRangeProps(this.props) ? dayjs(start) : dayjs(start).startOf(mode) } else if (start === null) { startValue = null } if (end) { endValue = isDateTimeRangeProps(this.props) ? dayjs(end) : dayjs(end).startOf(mode) if (startValue && endValue.isBefore(startValue)) { ([startValue, endValue] = [ endValue, startValue, ]) } } else if (end === null) { endValue = null } const resultState = pickBy({ startValue, endValue, }, (value) => !isUndefined(value)) as unknown as Pick const isStartUndefined = isUndefined(startValue) if (isStartUndefined) { resultState.isStartZooming = false } const isEndUndefined = isUndefined(endValue) if (isEndUndefined) { resultState.isEndZooming = false } if (isStartUndefined && isEndUndefined) { return } this.setState(resultState) this.syncDisplayDate() } getValue = () => { return [ this.state.startValue, this.state.endValue, ] } syncDisplayDate = () => { const startValue = this.state.startValue const endValue = this.state.endValue let endDisplayedDate = ( endValue && endValue.startOf('month') ) || this.state.endDisplayedDate if ( endDisplayedDate && startValue ) { const mode = this.props.mode || 'date' if ( mode === 'date' && endDisplayedDate.isSame(startValue, 'month') ) { endDisplayedDate = startValue.add(1, 'month') } else if ( mode === 'month' && endDisplayedDate.isSame(startValue, 'year') ) { endDisplayedDate = startValue.add(1, 'year') } } this.setState({ startDisplayedDate: startValue && startValue.startOf('month'), endDisplayedDate, isStartZooming: false, isEndZooming: false, isSelecting: false, }) } renderDateTimePanel = () => { const quickActionsElem = this.quickActions.renderQuickActions({ getValue: () => this.state.dateTimeValue, getActionQueue: () => this.actionQueue, syncDisplayDate: () => this.syncDateTimeState(this.state.dateTimeValue), onMouseDown: preventPickerBlurred, renderQuickActions: this.props.renderQuickActions, }) return (
{quickActionsElem ? (
{quickActionsElem} {this.dateTimePanel.renderPanel()}
) : this.dateTimePanel.renderPanel()}
{this.props.hideNow ? null : ( 此刻 )}
) } renderPanel() { if (this.isDateTimeRange) { return this.renderDateTimePanel() } const { titleElem: leftTitleElem, contentElem: leftContentElem, asideElem: leftAsideElem, contentType: leftContentType, } = this.state.isStartZooming ? this.zoomedStartDatePanel.getRenderElem() : this.props.mode === 'month' ? this.startMonthPanel.getRenderElem() : this.startDatePanel.getRenderElem() const { titleElem: rightTitleElem, contentElem: rightContentElem, asideElem: rightAsideElem, contentType: rightContentType, } = this.state.isEndZooming ? this.zoomedEndDatePanel.getRenderElem() : this.props.mode === 'month' ? this.endMonthPanel.getRenderElem() : this.endDatePanel.getRenderElem() const quickActionsElem = this.quickActions.renderQuickActions({ getValue: () => this.state.startValue, getActionQueue: () => this.actionQueue, syncDisplayDate: () => this.syncDisplayDate(), onMouseDown: preventPickerBlurred, renderQuickActions: this.props.renderQuickActions, }) return ( ) } render() { const shouldShowPanel = this.shouldShowPanel const props = this.props return (
{this.userInput.renderInput({ status: props.status, disabled: props.disabled, size: this.size, })} {this.renderPanel()}
) } }