uui/components/tables/columnsConfigurationModal/ColumnsConfigurationModal.tsx (215 lines of code) (raw):

import * as React from 'react'; import { useCallback, useMemo, useState } from 'react'; import { ColumnsConfig, cx, DataColumnProps, IModal } from '@epam/uui-core'; import { Accordion, ColumnsConfigurationRowProps, IconContainer, useColumnsConfiguration } from '@epam/uui-components'; import { ReactComponent as MenuIcon } from '@epam/assets/icons/navigation-more_vert-outline.svg'; import { ReactComponent as ResetIcon } from '@epam/assets/icons/navigation-refresh-outline.svg'; import { ReactComponent as ExpandedIcon } from '@epam/assets/icons/navigation-chevron_down-outline.svg'; import { ReactComponent as CollapsedIcon } from '@epam/assets/icons/navigation-chevron_right-outline.svg'; import { FlexRow, FlexRowProps, FlexSpacer, Panel, ScrollBars } from '../../layout'; import { Button, ButtonProps, LinkButton } from '../../buttons'; import { Dropdown, DropdownMenuBody, DropdownMenuButton, ModalBlocker, ModalFooter, ModalHeader, ModalWindow, Tooltip } from '../../overlays'; import { Text } from '../../typography'; import { CountIndicator, CountIndicatorProps } from '../../widgets'; import { SearchInput, SearchInputProps } from '../../inputs'; import { ColumnRow } from './ColumnRow'; import { i18n as uuiI18n } from '../../../i18n'; import { settings } from '../../../settings'; import css from './ColumnsConfigurationModal.module.scss'; export interface ColumnsConfigurationModalProps<TItem, TId, TFilter> extends IModal<ColumnsConfig> { columnsConfig?: ColumnsConfig; defaultConfig: ColumnsConfig; columns: DataColumnProps<TItem, TId, TFilter>[]; renderItem?: (column: DataColumnProps<TItem, TId, TFilter>) => React.ReactNode; getSearchFields?: (column: DataColumnProps<TItem, TId, TFilter>) => string[]; } const renderGroupTitle = (title: string, amount: number) => ( <FlexRow cx={ css.group } > <Text size="none" cx={ css.groupTitle } > {title} </Text> <CountIndicator caption={ amount } color="neutral" size={ settings.sizes.dataTable.columnsConfigurationModal.countIndicator as CountIndicatorProps['size'] } /> <FlexSpacer /> </FlexRow> ); export function ColumnsConfigurationModal<TItem, TId, TFilter>(props: ColumnsConfigurationModalProps<TItem, TId, TFilter>) { const i18n = uuiI18n.tables.columnsConfigurationModal; const { columns, columnsConfig: initialColumnsConfig, defaultConfig, ...modalProps } = props; const { groupedColumns, searchValue, columnsConfig, reset, checkAll, uncheckAll, setSearchValue, hasAnySelectedColumns, } = useColumnsConfiguration({ initialColumnsConfig, columns, defaultConfig, getSearchFields: props.getSearchFields, }); const apply = useCallback(() => modalProps.success(columnsConfig), [columnsConfig, modalProps]); const close = useCallback(() => modalProps.abort(), [modalProps]); const isNoData = useMemo(() => Object.values(groupedColumns).every((v) => !v.length), [groupedColumns]); const renderVisible = () => { const amountPinnedLeft = groupedColumns.displayedPinnedLeft.length; const amountPinnedRight = groupedColumns.displayedPinnedRight.length; const amountUnPinned = groupedColumns.displayedUnpinned.length; const totalAmountOfVisibleColumns = amountPinnedLeft + amountUnPinned + amountPinnedRight; if (!totalAmountOfVisibleColumns) { return null; } const hasDividerBelowPinnedLeft = !!(amountPinnedLeft && amountUnPinned); const hasDividerAbovePinnedRight = !!(amountPinnedRight && amountUnPinned); const divider = ( <FlexRow size={ null } cx={ css.hDivider } /> ); return ( <> {renderGroupTitle(i18n.displayedSectionTitle, totalAmountOfVisibleColumns)} <SubGroup renderItem={ props.renderItem } title={ i18n.pinnedToTheLeftSubgroupTitle } items={ groupedColumns.displayedPinnedLeft } /> {hasDividerBelowPinnedLeft && divider} <SubGroup renderItem={ props.renderItem } title={ i18n.notPinnedSubgroupTitle } items={ groupedColumns.displayedUnpinned } /> {hasDividerAbovePinnedRight && divider} <SubGroup renderItem={ props.renderItem } title={ i18n.pinnedToTheRightSubgroupTitle } items={ groupedColumns.displayedPinnedRight } /> </> ); }; const renderHidden = () => { const items = groupedColumns.hidden; const title = renderGroupTitle(i18n.hiddenSectionTitle, items.length); if (!items.length) { return null; } return ( <> { title } <SubGroup renderItem={ props.renderItem } items={ items } /> </> ); }; const applyButton = <Button caption={ i18n.applyButton } isDisabled={ !hasAnySelectedColumns } color="primary" onClick={ apply } />; return ( <ModalBlocker { ...modalProps }> <ModalWindow cx={ css.root } height="95dvh" maxHeight="95dvh" width={ settings.sizes.dataTable.columnsConfigurationModal.width }> <ModalHeader title={ i18n.configureColumnsTitle } onClose={ close } /> <FlexRow borderBottom={ true } cx={ css.searchArea } > <SearchInput size={ settings.sizes.dataTable.columnsConfigurationModal.search as SearchInputProps['size'] } value={ searchValue } onValueChange={ setSearchValue } placeholder={ i18n.searchPlaceholder } /> <Dropdown closeOnTargetClick={ true } renderBody={ () => ( <DropdownMenuBody minWidth={ 100 }> <DropdownMenuButton caption={ i18n.clearAllButton } onClick={ uncheckAll } /> <DropdownMenuButton caption={ i18n.selectAllButton } onClick={ checkAll } /> </DropdownMenuBody> ) } renderTarget={ (props) => ( <Button { ...props } fill="none" icon={ MenuIcon } size={ settings.sizes.dataTable.columnsConfigurationModal.search as ButtonProps['size'] } color="secondary" isDropdown={ false } /> ) } /> </FlexRow> <Panel background="surface-main" cx={ css.mainPanel }> <ScrollBars> {renderVisible()} {renderHidden()} {isNoData && ( <FlexRow cx={ css.noData }> <Text cx={ css.noDataTitle }> {i18n.noResultsFound.title} </Text> <Text cx={ css.noDataSubTitle }> {i18n.noResultsFound.subTitle} </Text> </FlexRow> )} </ScrollBars> </Panel> <ModalFooter borderTop> <LinkButton icon={ ResetIcon } caption={ i18n.resetToDefaultButton } onClick={ reset } /> <FlexSpacer /> <Button fill="none" color="secondary" caption={ i18n.cancelButton } onClick={ close } /> {!hasAnySelectedColumns ? ( <Tooltip content={ i18n.enableAtLeastOneColumnMessage } placement="top-end" color="critical"> {applyButton} </Tooltip> ) : ( applyButton )} </ModalFooter> </ModalWindow> </ModalBlocker> ); } function SubGroup(props: { items: ColumnsConfigurationRowProps[]; renderItem: (column: DataColumnProps) => React.ReactNode; title?: React.ReactNode }) { const [isExpanded, setIsExpanded] = useState<boolean>(true); const { title, items, renderItem } = props; const isCollapsible = !!title; if (items.length) { const content = ( <FlexRow cx={ css.groupItems } size={ settings.sizes.dataTable.columnsConfigurationModal.columnRow as FlexRowProps['size'] } > {items.map((c) => ( <ColumnRow column={ c } key={ c.key } renderItem={ renderItem } /> ))} </FlexRow> ); if (isCollapsible) { const renderTitle = (isOpened: boolean) => { const toggleIcon = isOpened ? ExpandedIcon : CollapsedIcon; return ( <FlexRow cx={ cx(css.subgroup) } > <IconContainer size={ settings.sizes.dataTable.columnsConfigurationModal.subgroupIcon } icon={ toggleIcon } /> <Text size="none" color="tertiary" cx={ css.subgroupTitle } > { title } </Text> </FlexRow> ); }; return ( <Accordion value={ isExpanded } onValueChange={ setIsExpanded } renderTitle={ renderTitle } cx={ css.subgroupAccordion } > { content } </Accordion> ); } return content; } return null; }