src/data-list/item.tsx (188 lines of code) (raw):

import {PureComponent, type ReactNode} from 'react'; import chevronRightIcon from '@jetbrains/icons/chevron-right'; import chevronDownIcon from '@jetbrains/icons/chevron-down'; import Link from '../link/link'; import Text from '../text/text'; import LoaderInline from '../loader-inline/loader-inline'; import Button from '../button/button'; import {type SelectionItem} from '../table/selection'; import Title from './title'; import type Selection from './selection'; import styles from './data-list.css'; export enum moreLessButtonStates { UNUSED, MORE, MORE_LOADING, LESS, } const ITEM_LEFT_OFFSET = 32; const LIST_LEFT_OFFSET = 24; export interface BaseFormattedItem<T> { items?: readonly T[]; title?: ReactNode; collapsible?: boolean | null | undefined; collapsed?: boolean | null | undefined; onCollapse?: () => void; onExpand?: () => void; selectable?: boolean | undefined; } export interface FormattedItem<T> extends BaseFormattedItem<T> { key?: string | null | undefined; id?: string | number | null | undefined; } export interface ItemProps<T extends SelectionItem> extends BaseFormattedItem<T> { item: T; onFocus: (item: T) => void; onSelect: (item: T, selected: boolean) => void; itemFormatter: (item: T) => FormattedItem<T>; level: number; parentShift: number; showMoreLessButton: moreLessButtonStates; onItemMoreLess: (item: T, more: boolean) => void; className?: string | null | undefined; showFocus?: boolean | undefined; selection: Selection<T>; selected?: boolean | undefined; partialSelected?: boolean | undefined; } export default class Item<T extends SelectionItem> extends PureComponent<ItemProps<T>> { static defaultProps = { items: [], level: 0, parentShift: 0, showMoreLessButton: moreLessButtonStates.UNUSED, onItemMoreLess: () => {}, }; onShowMore = () => { const {onItemMoreLess, item} = this.props; onItemMoreLess(item, true); }; onShowLess = () => { const {onItemMoreLess, item} = this.props; onItemMoreLess(item, false); }; onFocus = () => { const {onFocus, item} = this.props; onFocus(item); }; onSelect = (selected: boolean) => { const {onSelect, item} = this.props; onSelect(item, selected); }; renderItem = (model: T, parentShift: number) => { const {onFocus, onSelect, selection, level, itemFormatter} = this.props; const item = itemFormatter(model); return ( <Item key={item.key || item.id} item={model} title={item.title} items={item.items} level={level + 1} parentShift={parentShift} itemFormatter={itemFormatter} collapsible={item.collapsible} collapsed={item.collapsed} onCollapse={item.onCollapse} onExpand={item.onExpand} showFocus={selection.isFocused(model)} onFocus={onFocus} selection={selection} selectable={item.selectable} selected={selection.isSelected(model)} partialSelected={selection.isPartialSelected(model)} onSelect={onSelect} /> ); }; render() { const { title, items, showMoreLessButton, level, parentShift, showFocus, selectable, selected, partialSelected, collapsible, collapsed, onCollapse, onExpand, } = this.props; let moreLessButton; if (showMoreLessButton === moreLessButtonStates.MORE || showMoreLessButton === moreLessButtonStates.MORE_LOADING) { moreLessButton = ( <Text info size={Text.Size.S}> <Link inherit pseudo onClick={this.onShowMore}> {'Show more'} </Link> {showMoreLessButton === moreLessButtonStates.MORE_LOADING && ( <LoaderInline className={styles.showMoreLoader} /> )} </Text> ); } else if (showMoreLessButton === moreLessButtonStates.LESS) { moreLessButton = ( <Text info size={Text.Size.S}> <Link inherit pseudo onClick={this.onShowLess}> {'Show less'} </Link> </Text> ); } let collapserExpander = null; if (collapsible) { if (collapsed) { collapserExpander = ( <Button title='Expand' onClick={onExpand} icon={chevronRightIcon} className={styles.collapseButton} iconClassName={styles.collapseIcon} data-test='ring-data-list-expand' /> ); } else { collapserExpander = ( <Button title='Collapse' onClick={onCollapse} icon={chevronDownIcon} className={styles.collapseButton} iconClassName={styles.collapseIcon} data-test='ring-data-list-collapse' /> ); } } const itemIsEmpty = !items?.length || (collapsible && collapsed); const offset = level * LIST_LEFT_OFFSET + ITEM_LEFT_OFFSET + parentShift; return ( <li> <Title title={title} focused={showFocus} showFocus={showFocus} selectable={selectable} selected={selected} partialSelected={partialSelected} collapserExpander={collapserExpander} onFocus={this.onFocus} onSelect={this.onSelect} offset={offset} /> {!itemIsEmpty ? ( <ul className={styles.itemContent}> {items.map(model => this.renderItem(model, parentShift))} {showMoreLessButton !== moreLessButtonStates.UNUSED ? ( <li className={styles.showMore}>{moreLessButton}</li> ) : null} </ul> ) : null} </li> ); } }