1 |
|
2 | import React, {PureComponent} from 'react';
|
3 | import PropTypes from 'prop-types';
|
4 | import chevronRightIcon from '@jetbrains/icons/chevron-right.svg';
|
5 | import chevronDownIcon from '@jetbrains/icons/chevron-down.svg';
|
6 |
|
7 | import Link from '../link/link';
|
8 | import Text from '../text/text';
|
9 | import LoaderInline from '../loader-inline/loader-inline';
|
10 |
|
11 | import Icon from '../icon';
|
12 |
|
13 | import Title from './title';
|
14 |
|
15 | import styles from './data-list.css';
|
16 |
|
17 | export const moreLessButtonStates = {
|
18 | UNUSED: 0,
|
19 | MORE: 1,
|
20 | MORE_LOADING: 2,
|
21 | LESS: 3
|
22 | };
|
23 |
|
24 | const ITEM_LEFT_OFFSET = 32;
|
25 | const LIST_LEFT_OFFSET = 24;
|
26 |
|
27 |
|
28 | export default class Item extends PureComponent {
|
29 | static propTypes = {
|
30 | item: PropTypes.object,
|
31 | title: PropTypes.node,
|
32 | items: PropTypes.array,
|
33 | className: PropTypes.string,
|
34 | level: PropTypes.number,
|
35 | parentShift: PropTypes.number,
|
36 |
|
37 | itemFormatter: PropTypes.func,
|
38 |
|
39 | collapsible: PropTypes.bool,
|
40 | collapsed: PropTypes.bool,
|
41 | onCollapse: PropTypes.func,
|
42 | onExpand: PropTypes.func,
|
43 |
|
44 | showFocus: PropTypes.bool,
|
45 | onFocus: PropTypes.func,
|
46 |
|
47 | selection: PropTypes.object,
|
48 | selectable: PropTypes.bool,
|
49 | selected: PropTypes.bool,
|
50 | onSelect: PropTypes.func,
|
51 |
|
52 | showMoreLessButton: PropTypes.number,
|
53 | onItemMoreLess: PropTypes.func
|
54 | };
|
55 |
|
56 | static defaultProps = {
|
57 | items: [],
|
58 | level: 0,
|
59 | parentShift: 0,
|
60 | showMoreLessButton: moreLessButtonStates.UNUSED,
|
61 | onItemMoreLess: () => {}
|
62 | };
|
63 |
|
64 |
|
65 | onShowMore = () => {
|
66 | const {onItemMoreLess, item} = this.props;
|
67 | onItemMoreLess(item, true);
|
68 | };
|
69 |
|
70 | onShowLess = () => {
|
71 | const {onItemMoreLess, item} = this.props;
|
72 | onItemMoreLess(item, false);
|
73 | };
|
74 |
|
75 | onFocus = () => {
|
76 | const {onFocus, item} = this.props;
|
77 | onFocus(item);
|
78 | };
|
79 |
|
80 | onSelect = selected => {
|
81 | const {onSelect, item} = this.props;
|
82 | onSelect(item, selected);
|
83 | };
|
84 |
|
85 | renderItem = (model, parentShift) => {
|
86 | const {
|
87 | onFocus, onSelect, selection, level,
|
88 | itemFormatter
|
89 | } = this.props;
|
90 |
|
91 | const item = itemFormatter(model);
|
92 |
|
93 | return (
|
94 | <Item
|
95 | key={item.key || item.id}
|
96 | item={model}
|
97 | title={item.title}
|
98 | items={item.items}
|
99 | level={level + 1}
|
100 | parentShift={parentShift}
|
101 |
|
102 | itemFormatter={itemFormatter}
|
103 |
|
104 | collapsible={item.collapsible}
|
105 | collapsed={item.collapsed}
|
106 | onCollapse={item.onCollapse}
|
107 | onExpand={item.onExpand}
|
108 |
|
109 | focused={selection.isFocused(model)}
|
110 | showFocus={selection.isFocused(model)}
|
111 | onFocus={onFocus}
|
112 |
|
113 | selection={selection}
|
114 | selectable={item.selectable}
|
115 | selected={selection.isSelected(model)}
|
116 | onSelect={onSelect}
|
117 | />
|
118 | );
|
119 | };
|
120 |
|
121 | render() {
|
122 | const {
|
123 | title, items, showMoreLessButton,
|
124 | level, parentShift, showFocus,
|
125 | selectable, selected,
|
126 | collapsible, collapsed, onCollapse, onExpand
|
127 | } = this.props;
|
128 |
|
129 | let moreLessButton;
|
130 | if (showMoreLessButton === moreLessButtonStates.MORE ||
|
131 | showMoreLessButton === moreLessButtonStates.MORE_LOADING) {
|
132 | moreLessButton = (
|
133 | <Text comment>
|
134 | <Link
|
135 | inherit
|
136 | pseudo
|
137 | onClick={this.onShowMore}
|
138 | >Show more</Link>
|
139 | {showMoreLessButton === moreLessButtonStates.MORE_LOADING &&
|
140 | <LoaderInline className={styles.showMoreLoader}/>
|
141 | }
|
142 | </Text>
|
143 | );
|
144 | } else if (showMoreLessButton === moreLessButtonStates.LESS) {
|
145 | moreLessButton = (
|
146 | <Text comment>
|
147 | <Link
|
148 | inherit
|
149 | pseudo
|
150 | onClick={this.onShowLess}
|
151 | >Show less</Link>
|
152 | </Text>
|
153 | );
|
154 | }
|
155 |
|
156 | let collapserExpander = null;
|
157 | if (collapsible) {
|
158 | if (collapsed) {
|
159 | collapserExpander = (
|
160 | <div
|
161 | className={styles.expanderBox}
|
162 | onClick={onExpand}
|
163 | >
|
164 | <Icon
|
165 | glyph={chevronRightIcon}
|
166 | className={styles.collapseIcon}
|
167 | data-test="ring-data-list-expand"
|
168 | />
|
169 | </div>
|
170 | );
|
171 | } else {
|
172 | collapserExpander = (
|
173 | <div
|
174 | className={styles.expanderBox}
|
175 | onClick={onCollapse}
|
176 | >
|
177 | <Icon
|
178 | glyph={chevronDownIcon}
|
179 | className={styles.collapseIcon}
|
180 | data-test="ring-data-list-collapse"
|
181 | />
|
182 | </div>
|
183 | );
|
184 | }
|
185 | }
|
186 |
|
187 | const itemIsEmpty = !items.length || (collapsible && collapsed);
|
188 | const offset = level * LIST_LEFT_OFFSET + ITEM_LEFT_OFFSET + parentShift;
|
189 |
|
190 | return (
|
191 | <li>
|
192 | <Title
|
193 | title={title}
|
194 | focused={showFocus}
|
195 | showFocus={showFocus}
|
196 | selectable={selectable}
|
197 | selected={selected}
|
198 | collapserExpander={collapserExpander}
|
199 | onFocus={this.onFocus}
|
200 | onSelect={this.onSelect}
|
201 | offset={offset}
|
202 | />
|
203 |
|
204 | {!itemIsEmpty ? (
|
205 | <ul className={styles.itemContent}>
|
206 | {items.map(model => this.renderItem(model, parentShift))}
|
207 |
|
208 | {showMoreLessButton !== moreLessButtonStates.UNUSED
|
209 | ? <li className={styles.showMore}>{moreLessButton}</li>
|
210 | : null
|
211 | }
|
212 | </ul>
|
213 | ) : null}
|
214 | </li>
|
215 | );
|
216 | }
|
217 | }
|