UNPKG

react-cm-ui

Version:
555 lines (497 loc) 29.2 kB
import { forEach, isArray, isEmpty, map, } from 'lodash'; import ClassNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import { GRID_SIZES } from '../../layout/grid/gridConstants'; import { UI_CLASS_NAME, } from '../../global/constants'; import ActionBarActionsButton from './actionBarActionsButton'; // eslint-disable-line import/no-cycle import ActionBarSearch from './actionBarSearch'; import Button from '../../inputs/button'; import DropdownButton from '../../inputs/dropdownButton'; import Grid from '../../layout/grid'; import Icon from '../../dataDisplay/icon'; import List from '../../dataDisplay/list'; import Utils from '../../utils/utils'; const searchPropTypes = PropTypes.shape({ autoFocus: PropTypes.bool, onFocus: PropTypes.func, }); const propTypes = { children: PropTypes.element, className: PropTypes.string, columns: PropTypes.arrayOf(PropTypes.shape({ button: PropTypes.shape({}), dropdownButton: PropTypes.shape({ className: PropTypes.string, color: PropTypes.oneOf(Utils.colorEnums()), disable: PropTypes.bool, iconType: PropTypes.oneOf(['chevron-down', 'plus']), id: PropTypes.string, onClick: PropTypes.func, style: PropTypes.shape({}), title: PropTypes.string, }), jsx: PropTypes.node, lg: PropTypes.oneOf(GRID_SIZES), list: PropTypes.arrayOf(PropTypes.shape({ actionsButton: PropTypes.shape({}), divide: PropTypes.bool, jsx: PropTypes.node, iconBack: PropTypes.shape({}), iconFilter: PropTypes.shape({}), iconGrid: PropTypes.shape({}), iconSearch: searchPropTypes, iconTable: PropTypes.shape({}), })), md: PropTypes.oneOf(GRID_SIZES), search: searchPropTypes, sm: PropTypes.oneOf(GRID_SIZES), xl: PropTypes.oneOf(GRID_SIZES), })), /** * Used for DOM testing. https://testing-library.com/docs/queries/bytestid/ */ dataTestId: PropTypes.string, id: PropTypes.string, /** * Defines the `justify-content` style property. * It is applied for all screen sizes. */ justifyContent: PropTypes.oneOf([ 'center', 'flex-end', 'flex-start', 'space-around', 'space-between', 'space-evenly', ]), moduleType: PropTypes.oneOf(['drawer', 'page']), style: PropTypes.shape({}), // eslint-disable-line react/forbid-prop-types }; const defaultProps = { children: undefined, className: undefined, columns: [], dataTestId: `${UI_CLASS_NAME}-action_bar`, id: undefined, justifyContent: 'flex-start', moduleType: undefined, style: {}, }; const HAS_DRAWER_ACTION_BAR_CLASS_NAME = 'drawer-has_action_bar'; const HAS_PAGE_ACTION_BAR_CLASS_NAME = 'page-has_action_bar'; const HAS_DRAWER_CONTENT_ACTION_BAR_MOBILE_SEARCH_VISIBLE_CLASS_NAME = 'drawer--content-has_action_bar_mobile_search_visible'; const HAS_PAGE_CONTAINER_ACTION_BAR_MOBILE_SEARCH_VISIBLE_CLASS_NAME = 'page--container-has_action_bar_mobile_search_visible'; class ActionBar extends React.PureComponent { constructor() { super(); this.state = { isMobileSearchVisible: false, }; this.onMobileSearchIconToggle = this.onMobileSearchIconToggle.bind(this); } componentDidMount() { const { moduleType, } = this.props; if (this.actionBarRef) { const closestDrawer = this.actionBarRef.closest('.ui.drawer'); if (moduleType === 'drawer' && closestDrawer) { closestDrawer.classList.add(HAS_DRAWER_ACTION_BAR_CLASS_NAME); } const closestPage = this.actionBarRef.closest('.ui.page'); if (moduleType === 'page' && closestPage) { closestPage.classList.add(HAS_PAGE_ACTION_BAR_CLASS_NAME); } this.setMobileSearchAutoFocus(); } } componentWillUnmount() { const { moduleType } = this.props; if (this.actionBarRef) { const closestDrawer = this.actionBarRef.closest('.ui.drawer'); if (moduleType === 'drawer' && closestDrawer) { closestDrawer.classList.remove(HAS_DRAWER_ACTION_BAR_CLASS_NAME); } } } onMobileSearchIconToggle() { this.setState((prevState) => ({ isMobileSearchVisible: !prevState.isMobileSearchVisible, }), () => { const { moduleType } = this.props; const { isMobileSearchVisible } = this.state; if (this.actionBarRef) { const closestDrawer = this.actionBarRef.closest('.ui.drawer'); if (moduleType === 'drawer' && closestDrawer) { if (isMobileSearchVisible) { closestDrawer .querySelector('.ui.drawer--content') .classList .add( HAS_DRAWER_CONTENT_ACTION_BAR_MOBILE_SEARCH_VISIBLE_CLASS_NAME, ); } else { closestDrawer .querySelector('.ui.drawer--content') .classList .remove( HAS_DRAWER_CONTENT_ACTION_BAR_MOBILE_SEARCH_VISIBLE_CLASS_NAME, ); } } const closestPage = this.actionBarRef.closest('.ui.page'); if (moduleType === 'page' && closestPage) { if (isMobileSearchVisible) { closestPage .querySelector('.ui.page--container') .classList .add( HAS_PAGE_CONTAINER_ACTION_BAR_MOBILE_SEARCH_VISIBLE_CLASS_NAME, ); } else { closestPage .querySelector('.ui.page--container') .classList .remove( HAS_PAGE_CONTAINER_ACTION_BAR_MOBILE_SEARCH_VISIBLE_CLASS_NAME, ); } } } }); } setMobileSearchAutoFocus() { const { columns, } = this.props; let hasEventBeenCalled = false; forEach(columns, (c) => { if (c.list) { forEach(c.list, (l) => { if (!hasEventBeenCalled && l.iconSearch?.autoFocus) { this.onMobileSearchIconToggle(); hasEventBeenCalled = true; } }); } }); } render() { const { children, className, columns, dataTestId, id, justifyContent, moduleType, style, } = this.props; const { isMobileSearchVisible } = this.state; const containerClasses = ClassNames( 'ui', `${moduleType}--action_bar`, 'action_bar', className, ); let searchDataForMobile = null; let gridColumnKeyNum = 1; let gridColumnListItemKeyNum = 1; return ( <header className={containerClasses} data-testid={dataTestId} id={id} ref={(ref) => { this.actionBarRef = ref; }} style={style} > <div style={{ width: '100%' }}> {!isEmpty(columns) && ( <React.Fragment> <Grid alignItems="center" className="action_bar--grid" flexWrap="nowrap" justifyContent={justifyContent} spacing={2} > {map(columns, (column) => { const { button, dropdownButton, columns, // eslint-disable-line no-shadow jsx, search, } = column; let { list } = column; const gridColumnClasses = ClassNames('action_bar--grid_column', column.className); const gridColumnKey = `action_bar--grid_column-${gridColumnKeyNum++}`; // eslint-disable-line no-plusplus if (columns) { console.warn('ActionsBar\'s columns.columns is deprecated. Please use columns.list instead.'); // eslint-disable-line no-console list = [...columns]; } if (!button && !dropdownButton && !jsx && !list && !search) { console.warn( // eslint-disable-line no-console `${'<Page.ActionsBar>\'s column object must have one of the ' + 'following properties: button, jsx, or list.' + 'Please check this column object '}${JSON.stringify(column)}`, ); return false; } let dropdownButtonOptionKeyNum = 1; return ( <Grid.Column className={gridColumnClasses} key={gridColumnKey} lg={column.lg} md={column.md} sm={column.sm} /** * NOTE: Remove `flexBasis`, `flexGrow`, `flexShrink`, * and `width` when we finally remove the deprecated * style prop. */ style={({ flexBasis: column.flexBasis || null, flexGrow: column.flexGrow || null, flexShrink: column.flexShrink || null, width: 'auto', ...column.style, })} xl={column.xl} > {button && ( <Button color={button.color} designVersion={1} disable={!!button.disable || !!button.disabled} icon={button.iconType && !button.label} id={button.id} onClick={button.onClick} style={button.style} > {button.iconType && <Icon type={button.iconType} />} {button.label && <span>{button.label}</span>} </Button> )} {dropdownButton && ( <DropdownButton className={dropdownButton.className} color={dropdownButton.color} designVersion={1} disable={dropdownButton.disable} iconType={dropdownButton.iconType} id={dropdownButton.id} label={dropdownButton.label} onClick={dropdownButton.onClick} style={dropdownButton.style} title={dropdownButton.title} > {map(dropdownButton.options, (option) => { dropdownButtonOptionKeyNum += 1; return ( <DropdownButton.Option className={option.className} disable={!!option.disable || !!option.disabled} id={option.id} key={`action_bar_dropdown_button_option-${dropdownButtonOptionKeyNum}`} label={option.label} onClick={option.onClick} onKeyDown={option.onKeyDown} style={option.style} tabIndex={option.tabIndex} /> ); })} </DropdownButton> )} {isArray(list) && ( <List className="action_bar--list" horizontal > {map(list, (item) => { const { actionsButton, divide, jsx: listJsx, iconBack, iconFilter, iconGrid, iconSearch, iconTable, } = item; const listItemClassName = ClassNames('action_bar--list_item', { 'action_bar--list_item-jsx': listJsx, 'action_bar--list_item-icon_filter': iconFilter, 'action_bar--list_item-icon_grid': iconGrid, 'action_bar--list_item-icon_table': iconTable, }); const itemKey = `action_bar--list_item-${gridColumnListItemKeyNum++}`; // eslint-disable-line no-plusplus if (!actionsButton && !listJsx && !iconBack && !iconFilter && !iconGrid && !iconSearch && !iconTable ) { console.warn( // eslint-disable-line no-console, max-len `${'<Page.ActionsBar>\'s column.list object must have one of the ' + 'following properties: actionsButton, iconBack, iconFilter, iconGrid, or iconTable.' + 'Please check this column.list object '}${JSON.stringify(item)}`, ); return false; } if (iconSearch) { /* eslint-disable max-len */ /* eslint-disable react/jsx-props-no-spreading */ searchDataForMobile = ( <ActionBarSearch {...iconSearch} isMobileSearch isMobileSearchVisible={isMobileSearchVisible} type="mobileSearch" /> ); /* eslint-enable react/jsx-props-no-spreading */ /* eslint-enable max-len */ } return ( <List.Item className={listItemClassName} divide={!!divide} key={itemKey} style={({ alignItems: item.alignItems || 'flex-start', flexBasis: item.flexBasis || 'auto', flexGrow: item.flexGrow || 0, flexShrink: item.flexShrink || 0, width: 'auto', ...list.style, })} > {/* eslint-disable max-len */} {actionsButton && ( <ActionBarActionsButton className={actionsButton.className} iconBackgroundColor={actionsButton.iconBackgroundColor} iconBackgroundActiveColor={actionsButton.iconBackgroundActiveColor} iconType={actionsButton.iconType} id={actionsButton.id} isMobileSearchVisible={isMobileSearchVisible} header={actionsButton.header} moduleType={moduleType} options={actionsButton.options} style={actionsButton.style} /> )} {/* eslint-enable max-len */} {iconBack && ( <Icon color={iconBack.selected ? 'highlight' : null} id={iconBack.id} onClick={iconBack.onClick} title={iconBack.title || 'Back'} type="chevron-left" /> )} {/* eslint-disable max-len */} {iconFilter && ( <Icon color={iconFilter.selected || iconFilter.isFiltering ? 'highlight' : null} id={iconFilter.id} onClick={iconFilter.onClick} title={iconFilter.title || 'Filter'} type="filter" /> )} {/* eslint-enable max-len */} {iconGrid && ( <Icon color={iconGrid.selected ? 'highlight' : null} id={iconGrid.id} onClick={iconGrid.onClick} title={iconGrid.title || 'Grid View'} type="grid" /> )} {/* eslint-disable max-len */} {iconSearch && ( <Icon className="action_bar--search_icon" color={isMobileSearchVisible ? 'highlight' : null} id={iconSearch.id} onClick={this.onMobileSearchIconToggle} title={iconSearch.title || 'Search'} type="search" /> )} {/* eslint-enable max-len */} {iconTable && ( <Icon color={iconTable.selected ? 'highlight' : null} id={iconTable.id} onClick={iconTable.onClick} title={iconTable.title || 'Table View'} type="list" /> )} {listJsx} </List.Item> ); })} </List> )} {search && ( <ActionBarSearch autoFocus={search.autoFocus} id={search.id} onChange={search.onChange} onClearClick={search.onClearClick} onFocus={search.onFocus} onKeyDown={search.onKeyDown} placeholder={search.placeholder} value={search.value} /> )} {jsx} </Grid.Column> ); })} </Grid> {searchDataForMobile} </React.Fragment> )} {children} </div> </header> ); } } ActionBar.propTypes = propTypes; ActionBar.defaultProps = defaultProps; export default ActionBar;