@performant-software/semantic-components 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/build/index.js +2 -0
  4. package/build/index.js.map +1 -0
  5. package/build/main.css +786 -0
  6. package/index.js +1 -0
  7. package/package.json +37 -0
  8. package/src/components/AccordionDataList.css +8 -0
  9. package/src/components/AccordionDataList.js +224 -0
  10. package/src/components/AccordionList.css +27 -0
  11. package/src/components/AccordionList.js +596 -0
  12. package/src/components/AccordionSelector.css +3 -0
  13. package/src/components/AccordionSelector.js +359 -0
  14. package/src/components/ArrowButtons.css +4 -0
  15. package/src/components/ArrowButtons.js +38 -0
  16. package/src/components/AssociatedDropdown.css +44 -0
  17. package/src/components/AssociatedDropdown.js +338 -0
  18. package/src/components/BooleanIcon.css +0 -0
  19. package/src/components/BooleanIcon.js +33 -0
  20. package/src/components/CancelButton.css +0 -0
  21. package/src/components/CancelButton.js +25 -0
  22. package/src/components/ColorButton.css +4 -0
  23. package/src/components/ColorButton.js +34 -0
  24. package/src/components/ColorPickerModal.css +3 -0
  25. package/src/components/ColorPickerModal.js +77 -0
  26. package/src/components/ColumnResize.css +9 -0
  27. package/src/components/ColumnResize.js +20 -0
  28. package/src/components/DataList.css +0 -0
  29. package/src/components/DataList.js +531 -0
  30. package/src/components/DataTable.css +43 -0
  31. package/src/components/DataTable.js +596 -0
  32. package/src/components/DataTableColumnSelector.css +10 -0
  33. package/src/components/DataTableColumnSelector.js +146 -0
  34. package/src/components/DateInput.css +6 -0
  35. package/src/components/DateInput.js +58 -0
  36. package/src/components/DatePicker.css +72 -0
  37. package/src/components/DatePicker.js +81 -0
  38. package/src/components/DescriptorField.css +0 -0
  39. package/src/components/DescriptorField.js +42 -0
  40. package/src/components/DownloadButton.css +0 -0
  41. package/src/components/DownloadButton.js +23 -0
  42. package/src/components/Draggable.css +0 -0
  43. package/src/components/Draggable.js +94 -0
  44. package/src/components/DropdownButton.css +3 -0
  45. package/src/components/DropdownButton.js +65 -0
  46. package/src/components/DropdownMenu.css +0 -0
  47. package/src/components/DropdownMenu.js +68 -0
  48. package/src/components/EditModal.css +8 -0
  49. package/src/components/EditModal.js +99 -0
  50. package/src/components/EditPage.css +7 -0
  51. package/src/components/EditPage.js +249 -0
  52. package/src/components/EmbeddedList.css +7 -0
  53. package/src/components/EmbeddedList.js +278 -0
  54. package/src/components/FileInputButton.css +0 -0
  55. package/src/components/FileInputButton.js +54 -0
  56. package/src/components/FileUpload.css +31 -0
  57. package/src/components/FileUpload.js +188 -0
  58. package/src/components/FileUploadModal.css +0 -0
  59. package/src/components/FileUploadModal.js +408 -0
  60. package/src/components/FuzzyDate.css +8 -0
  61. package/src/components/FuzzyDate.js +575 -0
  62. package/src/components/GoogleMap.css +0 -0
  63. package/src/components/GoogleMap.js +105 -0
  64. package/src/components/GooglePlacesSearch.css +0 -0
  65. package/src/components/GooglePlacesSearch.js +43 -0
  66. package/src/components/HorizontalCards.css +50 -0
  67. package/src/components/HorizontalCards.js +226 -0
  68. package/src/components/ItemCollection.css +3 -0
  69. package/src/components/ItemCollection.js +159 -0
  70. package/src/components/ItemList.css +0 -0
  71. package/src/components/ItemList.js +126 -0
  72. package/src/components/Items.css +19 -0
  73. package/src/components/Items.js +365 -0
  74. package/src/components/ItemsToggle.css +0 -0
  75. package/src/components/ItemsToggle.js +168 -0
  76. package/src/components/KeyboardField.css +4 -0
  77. package/src/components/KeyboardField.js +147 -0
  78. package/src/components/LazyDocument.css +21 -0
  79. package/src/components/LazyDocument.js +113 -0
  80. package/src/components/LazyImage.css +21 -0
  81. package/src/components/LazyImage.js +119 -0
  82. package/src/components/LazyVideo.css +21 -0
  83. package/src/components/LazyVideo.js +131 -0
  84. package/src/components/LinkButton.css +8 -0
  85. package/src/components/LinkButton.js +23 -0
  86. package/src/components/LinkLabel.css +8 -0
  87. package/src/components/LinkLabel.js +29 -0
  88. package/src/components/List.css +8 -0
  89. package/src/components/List.js +761 -0
  90. package/src/components/ListFilters.css +0 -0
  91. package/src/components/ListFilters.js +408 -0
  92. package/src/components/ListLoader.css +8 -0
  93. package/src/components/ListLoader.js +32 -0
  94. package/src/components/ListTable.css +3 -0
  95. package/src/components/ListTable.js +86 -0
  96. package/src/components/LoginModal.css +7 -0
  97. package/src/components/LoginModal.js +102 -0
  98. package/src/components/MasonryGrid.css +48 -0
  99. package/src/components/MasonryGrid.js +202 -0
  100. package/src/components/MediaGallery.css +37 -0
  101. package/src/components/MediaGallery.js +148 -0
  102. package/src/components/MediaGrid.css +72 -0
  103. package/src/components/MediaGrid.js +74 -0
  104. package/src/components/MediaList.css +3 -0
  105. package/src/components/MediaList.js +98 -0
  106. package/src/components/ModalDropdown.css +11 -0
  107. package/src/components/ModalDropdown.js +84 -0
  108. package/src/components/NestedAccordion.css +41 -0
  109. package/src/components/NestedAccordion.js +276 -0
  110. package/src/components/PhotoViewer.css +3 -0
  111. package/src/components/PhotoViewer.js +36 -0
  112. package/src/components/PlayButton.css +3 -0
  113. package/src/components/PlayButton.js +37 -0
  114. package/src/components/RemoteDropdown.css +13 -0
  115. package/src/components/RemoteDropdown.js +368 -0
  116. package/src/components/SaveButton.css +0 -0
  117. package/src/components/SaveButton.js +31 -0
  118. package/src/components/Section.css +0 -0
  119. package/src/components/Section.js +41 -0
  120. package/src/components/Selectize.css +11 -0
  121. package/src/components/Selectize.js +297 -0
  122. package/src/components/SelectizeHeader.css +3 -0
  123. package/src/components/SelectizeHeader.js +40 -0
  124. package/src/components/TabbedModal.css +14 -0
  125. package/src/components/TabbedModal.js +165 -0
  126. package/src/components/TabsMenu.css +0 -0
  127. package/src/components/TabsMenu.js +35 -0
  128. package/src/components/TagsList.css +0 -0
  129. package/src/components/TagsList.js +43 -0
  130. package/src/components/Thumbnail.css +0 -0
  131. package/src/components/Thumbnail.js +47 -0
  132. package/src/components/Toaster.css +9 -0
  133. package/src/components/Toaster.js +73 -0
  134. package/src/components/VideoFrameSelector.css +3 -0
  135. package/src/components/VideoFrameSelector.js +148 -0
  136. package/src/components/VideoPlayer.css +3 -0
  137. package/src/components/VideoPlayer.js +55 -0
  138. package/src/components/VideoPlayerButton.css +17 -0
  139. package/src/components/VideoPlayerButton.js +17 -0
  140. package/src/components/ViewXML.css +0 -0
  141. package/src/components/ViewXML.js +72 -0
  142. package/src/i18n/en.json +204 -0
  143. package/src/i18n/i18n.js +24 -0
  144. package/src/index.js +76 -0
  145. package/types/components/AccordionDataList.js.flow +224 -0
  146. package/types/components/AccordionList.js.flow +596 -0
  147. package/types/components/AccordionSelector.js.flow +359 -0
  148. package/types/components/ArrowButtons.js.flow +38 -0
  149. package/types/components/AssociatedDropdown.js.flow +338 -0
  150. package/types/components/BooleanIcon.js.flow +33 -0
  151. package/types/components/CancelButton.js.flow +25 -0
  152. package/types/components/ColorButton.js.flow +34 -0
  153. package/types/components/ColorPickerModal.js.flow +77 -0
  154. package/types/components/ColumnResize.js.flow +20 -0
  155. package/types/components/DataList.js.flow +531 -0
  156. package/types/components/DataTable.js.flow +596 -0
  157. package/types/components/DataTableColumnSelector.js.flow +146 -0
  158. package/types/components/DataView.js.flow +125 -0
  159. package/types/components/DateInput.js.flow +58 -0
  160. package/types/components/DatePicker.js.flow +81 -0
  161. package/types/components/DescriptorField.js.flow +42 -0
  162. package/types/components/DownloadButton.js.flow +23 -0
  163. package/types/components/Draggable.js.flow +94 -0
  164. package/types/components/DropdownButton.js.flow +65 -0
  165. package/types/components/DropdownMenu.js.flow +68 -0
  166. package/types/components/EditModal.js.flow +99 -0
  167. package/types/components/EditPage.js.flow +249 -0
  168. package/types/components/EmbeddedList.js.flow +278 -0
  169. package/types/components/FileInputButton.js.flow +54 -0
  170. package/types/components/FileUpload.js.flow +188 -0
  171. package/types/components/FileUploadModal.js.flow +408 -0
  172. package/types/components/FuzzyDate.js.flow +575 -0
  173. package/types/components/GoogleMap.js.flow +105 -0
  174. package/types/components/GooglePlacesSearch.js.flow +43 -0
  175. package/types/components/HorizontalCards.js.flow +226 -0
  176. package/types/components/ItemCollection.js.flow +159 -0
  177. package/types/components/ItemList.js.flow +126 -0
  178. package/types/components/Items.js.flow +365 -0
  179. package/types/components/ItemsToggle.js.flow +168 -0
  180. package/types/components/KeyboardField.js.flow +147 -0
  181. package/types/components/LazyDocument.js.flow +113 -0
  182. package/types/components/LazyImage.js.flow +119 -0
  183. package/types/components/LazyVideo.js.flow +131 -0
  184. package/types/components/LinkButton.js.flow +23 -0
  185. package/types/components/LinkLabel.js.flow +29 -0
  186. package/types/components/List.js.flow +761 -0
  187. package/types/components/ListFilters.js.flow +408 -0
  188. package/types/components/ListLoader.js.flow +32 -0
  189. package/types/components/ListTable.js.flow +86 -0
  190. package/types/components/LoginModal.js.flow +102 -0
  191. package/types/components/MasonryGrid.js.flow +202 -0
  192. package/types/components/MediaGallery.js.flow +148 -0
  193. package/types/components/MediaGrid.js.flow +74 -0
  194. package/types/components/MediaList.js.flow +98 -0
  195. package/types/components/MenuBar.js.flow +77 -0
  196. package/types/components/MenuSidebar.js.flow +72 -0
  197. package/types/components/ModalDropdown.js.flow +84 -0
  198. package/types/components/NestedAccordion.js.flow +276 -0
  199. package/types/components/PhotoViewer.js.flow +36 -0
  200. package/types/components/PlayButton.js.flow +37 -0
  201. package/types/components/RemoteDropdown.js.flow +368 -0
  202. package/types/components/SaveButton.js.flow +31 -0
  203. package/types/components/Section.js.flow +41 -0
  204. package/types/components/Selectize.js.flow +297 -0
  205. package/types/components/SelectizeHeader.js.flow +40 -0
  206. package/types/components/TabbedModal.js.flow +165 -0
  207. package/types/components/TabsMenu.js.flow +35 -0
  208. package/types/components/TagsList.js.flow +43 -0
  209. package/types/components/Thumbnail.js.flow +47 -0
  210. package/types/components/Toaster.js.flow +73 -0
  211. package/types/components/VideoFrameSelector.js.flow +148 -0
  212. package/types/components/VideoPlayer.js.flow +55 -0
  213. package/types/components/VideoPlayerButton.js.flow +17 -0
  214. package/types/components/ViewXML.js.flow +72 -0
  215. package/types/hooks/Imageable.js.flow +54 -0
  216. package/types/i18n/i18n.js.flow +24 -0
  217. package/types/index.js.flow +78 -0
  218. package/webpack.config.js +3 -0
@@ -0,0 +1,761 @@
1
+ // @flow
2
+
3
+ import React, { Component, type ComponentType, type Element } from 'react';
4
+ import { Trans } from 'react-i18next';
5
+ import {
6
+ Button,
7
+ Confirm,
8
+ Grid,
9
+ Header,
10
+ Icon,
11
+ Menu,
12
+ Pagination
13
+ } from 'semantic-ui-react';
14
+ import _ from 'underscore';
15
+ import i18n from '../i18n/i18n';
16
+ import DropdownButton from './DropdownButton';
17
+ import EditModal from './EditModal';
18
+ import './List.css';
19
+
20
+ type Action = {
21
+ accept: (item: any) => boolean,
22
+ color?: string,
23
+ icon?: string,
24
+ name: string,
25
+ onClick?: (item: any) => void,
26
+ popup: {
27
+ content: string,
28
+ title: string
29
+ },
30
+ render?: (item: any, index: number) => Element<any>,
31
+ title?: string
32
+ };
33
+
34
+ type ListButton = {
35
+ accept?: () => boolean,
36
+ render: (index?: number) => Element<any>
37
+ };
38
+
39
+ type Props = {
40
+ actions: Array<Action>,
41
+ addButton: {
42
+ basic: boolean,
43
+ color: string,
44
+ location: string,
45
+ onClick?: () => void
46
+ },
47
+ buttons: Array<ListButton>,
48
+ count: number,
49
+ className: string,
50
+ configurable: boolean,
51
+ deleteButton?: {
52
+ color: string,
53
+ location: string,
54
+ onClick?: () => void
55
+ },
56
+ filters?: {
57
+ active: boolean,
58
+ component: Component<{}>,
59
+ props?: any,
60
+ state?: any,
61
+ onChange: (params: any) => Promise<any>
62
+ },
63
+ items: ?Array<any>,
64
+ loading?: boolean,
65
+ modal?: {
66
+ component: Element<any>,
67
+ props: any,
68
+ state: any
69
+ },
70
+ page: number,
71
+ pages: number,
72
+ onCopy?: (item: any) => any,
73
+ onDelete: (item: any) => void,
74
+ onDeleteAll?: () => Promise<any>,
75
+ onPageChange: () => void,
76
+ onPerPageChange: () => void,
77
+ onSave: (item: any) => Promise<any>,
78
+ onSelectAll?: (items: Array<any>) => void,
79
+ perPage: number,
80
+ perPageOptions: Array<number>,
81
+ renderDeleteModal?: ({ selectedItem: any, onCancel: () => void, onConfirm: () => void }) => Element<any>,
82
+ renderEmptyRow?: () => void,
83
+ renderItem?: (item: any, index: number, children?: any) => Element<any>,
84
+ renderListHeader?: () => ?Element<any>,
85
+ renderSearch?: () => Element<any>,
86
+ selectable?: boolean,
87
+ showRecordCount: boolean,
88
+ t: (key: string) => string
89
+ };
90
+
91
+ type State = {
92
+ modalDelete: boolean,
93
+ modalDeleteAll: boolean,
94
+ modalEdit: boolean,
95
+ modalFilter: boolean,
96
+ selectedItem: any
97
+ };
98
+
99
+ const BUTTON_KEY_ADD = 'add';
100
+ const BUTTON_KEY_DELETE_ALL = 'delete-all';
101
+
102
+ /**
103
+ * Renders a function to wrap the passed component in a List. This component will be used as the presentation for the
104
+ * list, additional logic will be defined elsewhere. This component will render list header buttons, pagination,
105
+ * search input, add/edit/delete modals, filter button, filter modal.
106
+ *
107
+ * @param WrappedComponent
108
+ */
109
+ const useList = (WrappedComponent: ComponentType<any>) => (
110
+ class extends Component<Props, State> {
111
+ // Default props
112
+ static defaultProps = {
113
+ actions: [],
114
+ addButton: {
115
+ basic: true,
116
+ color: 'green',
117
+ location: 'top'
118
+ },
119
+ buttons: [],
120
+ className: '',
121
+ filters: undefined,
122
+ items: [],
123
+ loading: false,
124
+ modal: undefined,
125
+ page: 1,
126
+ pages: 1,
127
+ onColumnClick: () => {},
128
+ onCopy: undefined,
129
+ onPageChange: () => {},
130
+ renderDeleteModal: undefined,
131
+ renderEmptyRow: undefined,
132
+ renderSearch: undefined,
133
+ renderItem: undefined,
134
+ sortColumn: undefined,
135
+ sortDirection: undefined
136
+ };
137
+
138
+ /**
139
+ * Constructs a new List component.
140
+ *
141
+ * @param props
142
+ */
143
+ constructor(props: Props) {
144
+ super(props);
145
+
146
+ this.state = {
147
+ modalDelete: false,
148
+ modalDeleteAll: false,
149
+ modalEdit: false,
150
+ modalFilter: false,
151
+ selectedItem: null
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Renders the list of buttons for the passed location.
157
+ *
158
+ * @param location
159
+ */
160
+ getButtons(location: string) {
161
+ const buttons = [];
162
+
163
+ const {
164
+ addButton = {},
165
+ deleteButton = {},
166
+ modal,
167
+ selectable
168
+ } = this.props;
169
+
170
+ // Add the add button to the list if the location specified is the passed location.
171
+ if (addButton.location === location && (addButton.onClick || modal) && !selectable) {
172
+ buttons.push({
173
+ render: this.renderAddButton.bind(this)
174
+ });
175
+ }
176
+
177
+ // Add the delete all button to the list if the location specified is the passed location.
178
+ if (deleteButton.location === location && this.props.onDeleteAll && !selectable) {
179
+ buttons.push({
180
+ render: this.renderDeleteAllButton.bind(this)
181
+ });
182
+ }
183
+
184
+ // Resolve the array of other buttons
185
+ buttons.push(..._.filter(this.props.buttons, (button) => {
186
+ let include = false;
187
+
188
+ /*
189
+ * Include the button if the buttons specifies the passed location.
190
+ * Include the button if no location is specified, but the add button is at the passed location.
191
+ * Finally, include the button if the passed location is the top location.
192
+ */
193
+ if ((button.location || 'top') === location) {
194
+ include = true;
195
+ } else if (!button.location && addButton && addButton.location === location) {
196
+ include = true;
197
+ }
198
+
199
+ return include;
200
+ }));
201
+
202
+ return buttons;
203
+ }
204
+
205
+ /**
206
+ * Displays the add/edit modal.
207
+ */
208
+ onAddButton() {
209
+ return this.props.addButton && this.props.addButton.onClick
210
+ ? this.props.addButton.onClick()
211
+ : this.setState({ modalEdit: true });
212
+ }
213
+
214
+ /**
215
+ * Copies the selected item and displays the add/edit modal.
216
+ *
217
+ * @param selectedItem
218
+ */
219
+ onCopyButton(selectedItem: any) {
220
+ const copy = this.props.onCopy
221
+ ? this.props.onCopy(selectedItem)
222
+ : _.omit(selectedItem, 'id', 'uid');
223
+
224
+ this.setState({ selectedItem: copy, modalEdit: true });
225
+ }
226
+
227
+ /**
228
+ * Deletes the currently selected item and clears the state.
229
+ *
230
+ * @returns {*}
231
+ */
232
+ onDelete() {
233
+ const { selectedItem } = this.state;
234
+ this.setState({ selectedItem: null, modalDelete: false });
235
+
236
+ return this.props.onDelete(selectedItem);
237
+ }
238
+
239
+ /**
240
+ * Deletes all items in the current list and resets the state.
241
+ *
242
+ * @returns {*}
243
+ */
244
+ onDeleteAll() {
245
+ this.setState({ modalDeleteAll: false });
246
+ return this.props.onDeleteAll && this.props.onDeleteAll();
247
+ }
248
+
249
+ /**
250
+ * Displays the delete all confirmation modal.
251
+ */
252
+ onDeleteAllButton() {
253
+ this.setState({ modalDeleteAll: true });
254
+ }
255
+
256
+ /**
257
+ * Displays the delete confirmation modal for the selected item.
258
+ *
259
+ * @param selectedItem
260
+ */
261
+ onDeleteButton(selectedItem: any) {
262
+ this.setState({ selectedItem, modalDelete: true });
263
+ }
264
+
265
+ /**
266
+ * Displays the add/edit modal for the selected item.
267
+ *
268
+ * @param selectedItem
269
+ */
270
+ onEditButton(selectedItem: any) {
271
+ this.setState({ selectedItem, modalEdit: true });
272
+ }
273
+
274
+ /**
275
+ * Opens the filters modal.
276
+ */
277
+ onFilterButton() {
278
+ this.setState({ modalFilter: true });
279
+ }
280
+
281
+ /**
282
+ * Saves the passed item and closes the add/edit modal.
283
+ *
284
+ * @param item
285
+ *
286
+ * @returns {*}
287
+ */
288
+ onSave(item: any) {
289
+ return this.props
290
+ .onSave(item)
291
+ .then(() => this.setState({ modalEdit: false, selectedItem: null }));
292
+ }
293
+
294
+ /**
295
+ * Calls the filters onChange prop and closes the modal.
296
+ *
297
+ * @param filters
298
+ *
299
+ * @returns {Q.Promise<any> | Promise<R> | Promise<any> | void | *}
300
+ */
301
+ onSaveFilter(filters: any) {
302
+ if (!this.props.filters) {
303
+ return null;
304
+ }
305
+
306
+ return this.props.filters
307
+ .onChange(filters)
308
+ .then(() => this.setState({ modalFilter: false }));
309
+ }
310
+
311
+ /**
312
+ * Renders the DataTable component.
313
+ *
314
+ * @returns {*}
315
+ */
316
+ render() {
317
+ return (
318
+ <div
319
+ className={`list ${this.props.className}`}
320
+ >
321
+ { this.renderHeader() }
322
+ <WrappedComponent
323
+ {...this.props}
324
+ actions={this.getActions()}
325
+ renderEmptyMessage={this.renderEmptyMessage.bind(this)}
326
+ />
327
+ { this.renderFooter() }
328
+ { this.renderEditModal() }
329
+ { this.renderDeleteModal() }
330
+ { this.renderDeleteAllModal() }
331
+ { this.renderFilterModal() }
332
+ </div>
333
+ );
334
+ }
335
+
336
+ /**
337
+ * Returns the list of actions with pre-populated defaults for edit, copy, and delete.
338
+ *
339
+ * @returns {*}
340
+ */
341
+ getActions() {
342
+ return _.map(this.props.actions, (action) => {
343
+ let defaults = {};
344
+
345
+ if (action.name === 'edit') {
346
+ defaults = {
347
+ icon: 'edit outline',
348
+ onClick: this.onEditButton.bind(this)
349
+ };
350
+ } else if (action.name === 'copy') {
351
+ defaults = {
352
+ icon: 'copy outline',
353
+ onClick: this.onCopyButton.bind(this)
354
+ };
355
+ } else if (action.name === 'delete') {
356
+ defaults = {
357
+ icon: 'times circle outline',
358
+ onClick: this.onDeleteButton.bind(this)
359
+ };
360
+ }
361
+
362
+ return _.defaults(action, defaults);
363
+ });
364
+ }
365
+
366
+ /**
367
+ * Renders the add button.
368
+ *
369
+ * @returns {*}
370
+ */
371
+ renderAddButton() {
372
+ if (!this.props.addButton) {
373
+ return null;
374
+ }
375
+
376
+ return (
377
+ <Button
378
+ basic={this.props.addButton.basic !== false}
379
+ color={this.props.addButton.color}
380
+ key={BUTTON_KEY_ADD}
381
+ onClick={this.onAddButton.bind(this)}
382
+ >
383
+ <Icon name='plus' />
384
+ { i18n.t('List.buttons.add') }
385
+ </Button>
386
+ );
387
+ }
388
+
389
+ /**
390
+ * Renders the passed button. If a render function is provided, call the render function. Otherwise, assume
391
+ * button props.
392
+ *
393
+ * @param button
394
+ * @param index
395
+ *
396
+ * @returns {*}
397
+ */
398
+ renderButton(button: any, index: number) {
399
+ if (button.render) {
400
+ return button.render(index);
401
+ }
402
+
403
+ if (button.accept && !button.accept()) {
404
+ return null;
405
+ }
406
+
407
+ return (
408
+ <Button
409
+ key={index}
410
+ {...button}
411
+ />
412
+ );
413
+ }
414
+
415
+ /**
416
+ * Renders the delete all button.
417
+ *
418
+ * @returns {null|*}
419
+ */
420
+ renderDeleteAllButton() {
421
+ if (!this.props.deleteButton) {
422
+ return null;
423
+ }
424
+
425
+ return (
426
+ <Button
427
+ basic
428
+ color={this.props.deleteButton.color}
429
+ key={BUTTON_KEY_DELETE_ALL}
430
+ onClick={this.onDeleteAllButton.bind(this)}
431
+ >
432
+ <Icon name='times' />
433
+ { i18n.t('List.buttons.deleteAll') }
434
+ </Button>
435
+ );
436
+ }
437
+
438
+ /**
439
+ * Renders the delete all modal if visible.
440
+ *
441
+ * @returns {null|*}
442
+ */
443
+ renderDeleteAllModal() {
444
+ if (!this.state.modalDeleteAll) {
445
+ return null;
446
+ }
447
+
448
+ return (
449
+ <Confirm
450
+ content={i18n.t('List.deleteAllContent')}
451
+ header={<Header icon='trash alternate outline' content={i18n.t('List.deleteAllHeader')} />}
452
+ onCancel={() => this.setState({ modalDeleteAll: false })}
453
+ onConfirm={this.onDeleteAll.bind(this)}
454
+ open
455
+ />
456
+ );
457
+ }
458
+
459
+ /**
460
+ * Renders the delete modal if visible.
461
+ *
462
+ * @returns {null|*}
463
+ */
464
+ renderDeleteModal() {
465
+ if (!this.state.modalDelete) {
466
+ return null;
467
+ }
468
+
469
+ const { selectedItem } = this.state;
470
+ const onCancel = () => this.setState({ selectedItem: null, modalDelete: false });
471
+ const onConfirm = this.onDelete.bind(this);
472
+
473
+ if (this.props.renderDeleteModal) {
474
+ return this.props.renderDeleteModal({ selectedItem, onConfirm, onCancel });
475
+ }
476
+
477
+ return (
478
+ <Confirm
479
+ content={i18n.t('List.deleteContent')}
480
+ header={<Header icon='trash alternate outline' content={i18n.t('List.deleteHeader')} />}
481
+ onCancel={onCancel}
482
+ onConfirm={onConfirm}
483
+ open
484
+ />
485
+ );
486
+ }
487
+
488
+ /**
489
+ * Renders the edit modal if visible.
490
+ *
491
+ * @returns {null|*}
492
+ */
493
+ renderEditModal() {
494
+ if (!this.props.modal || !this.state.modalEdit) {
495
+ return null;
496
+ }
497
+
498
+ const { component, props } = this.props.modal;
499
+
500
+ return (
501
+ <EditModal
502
+ component={component}
503
+ onClose={() => this.setState({ selectedItem: null, modalEdit: false })}
504
+ onSave={this.onSave.bind(this)}
505
+ item={this.state.selectedItem}
506
+ {...props}
507
+ />
508
+ );
509
+ }
510
+
511
+ /**
512
+ * Renders the empty message text/component. The message content is based on whether or not records can be added
513
+ * to this data table.
514
+ *
515
+ * @returns {*}
516
+ */
517
+ renderEmptyMessage() {
518
+ const { addButton = {}, modal } = this.props;
519
+ if (!(addButton.onClick || modal)) {
520
+ return i18n.t('List.emptyList');
521
+ }
522
+
523
+ return (
524
+ <Trans i18nKey='List.emptyListAdd'>
525
+ You haven&apos;t added any yet. Click
526
+ <div className='empty-button'>
527
+ { this.renderAddButton() }
528
+ </div>
529
+ to get started.
530
+ </Trans>
531
+ );
532
+ }
533
+
534
+ /**
535
+ * Renders the filter button component.
536
+ *
537
+ * @returns {null|*}
538
+ */
539
+ renderFilterButton() {
540
+ if (!(this.props.filters && this.props.filters.component)) {
541
+ return null;
542
+ }
543
+
544
+ return (
545
+ <Button
546
+ active={this.props.filters.active}
547
+ basic
548
+ icon='filter'
549
+ onClick={this.onFilterButton.bind(this)}
550
+ />
551
+ );
552
+ }
553
+
554
+ /**
555
+ * Renders the filter modal if visible.
556
+ *
557
+ * @returns {null|*}
558
+ */
559
+ renderFilterModal() {
560
+ if (!this.props.filters || !this.state.modalFilter) {
561
+ return null;
562
+ }
563
+
564
+ const { component, props } = this.props.filters;
565
+
566
+ return (
567
+ <EditModal
568
+ {...props}
569
+ component={component}
570
+ onClose={() => this.setState({ modalFilter: false })}
571
+ onSave={this.onSaveFilter.bind(this)}
572
+ />
573
+ );
574
+ }
575
+
576
+ /**
577
+ * Renders the list footer.
578
+ *
579
+ * @returns {null|*}
580
+ */
581
+ renderFooter() {
582
+ let renderFooter = false;
583
+
584
+ const buttons = this.getButtons('bottom');
585
+ if (buttons && buttons.length) {
586
+ renderFooter = true;
587
+ }
588
+
589
+ const hasPages = this.props.pages && this.props.pages > 1;
590
+ if (hasPages) {
591
+ renderFooter = true;
592
+ }
593
+
594
+ const showCount = this.props.count && this.props.showRecordCount;
595
+ if (showCount) {
596
+ renderFooter = true;
597
+ }
598
+
599
+ if (!renderFooter) {
600
+ return null;
601
+ }
602
+
603
+ return (
604
+ <div className='footer'>
605
+ <Grid
606
+ columns={2}
607
+ >
608
+ <Grid.Column
609
+ textAlign='left'
610
+ >
611
+ { showCount ? this.renderRecordCount() : '' }
612
+ { _.map(buttons, (button) => button.render()) }
613
+ </Grid.Column>
614
+ <Grid.Column
615
+ textAlign='right'
616
+ >
617
+ { hasPages ? this.renderPagination() : ''}
618
+ </Grid.Column>
619
+ </Grid>
620
+ </div>
621
+ );
622
+ }
623
+
624
+ /**
625
+ * Renders the table header.
626
+ *
627
+ * @returns {null|*}
628
+ */
629
+ renderHeader() {
630
+ let renderHeader = false;
631
+
632
+ const buttons = this.getButtons('top');
633
+
634
+ if (buttons && buttons.length) {
635
+ renderHeader = true;
636
+ }
637
+
638
+ const {
639
+ filters,
640
+ perPageOptions,
641
+ renderListHeader,
642
+ renderSearch
643
+ } = this.props;
644
+
645
+ if (filters || perPageOptions || renderListHeader || renderSearch) {
646
+ renderHeader = true;
647
+ }
648
+
649
+ if (!renderHeader) {
650
+ return null;
651
+ }
652
+
653
+ return (
654
+ <div
655
+ className='header'
656
+ >
657
+ <Grid
658
+ columns={2}
659
+ verticalAlign='bottom'
660
+ >
661
+ <Grid.Column
662
+ textAlign='left'
663
+ >
664
+ { _.map(buttons, this.renderButton.bind(this)) }
665
+ </Grid.Column>
666
+ <Grid.Column
667
+ textAlign='right'
668
+ >
669
+ <Menu
670
+ compact
671
+ borderless
672
+ secondary
673
+ className='flex-end-menu'
674
+ >
675
+ { renderListHeader && (
676
+ <Menu.Menu className='list-header-menu'>
677
+ { renderListHeader() }
678
+ </Menu.Menu>
679
+ )}
680
+ <Menu.Menu>
681
+ { filters && this.renderFilterButton() }
682
+ </Menu.Menu>
683
+ { perPageOptions && (
684
+ <Menu.Menu className='per-page-menu'>
685
+ { this.renderPerPage() }
686
+ </Menu.Menu>
687
+ )}
688
+ <Menu.Menu>
689
+ { renderSearch && renderSearch() }
690
+ </Menu.Menu>
691
+ </Menu>
692
+ </Grid.Column>
693
+ </Grid>
694
+ </div>
695
+ );
696
+ }
697
+
698
+ /**
699
+ * Renders the pagination component.
700
+ *
701
+ * @returns {null|*}
702
+ */
703
+ renderPagination() {
704
+ return (
705
+ <Pagination
706
+ activePage={this.props.page}
707
+ firstItem={null}
708
+ lastItem={null}
709
+ onPageChange={this.props.onPageChange.bind(this)}
710
+ size='mini'
711
+ totalPages={this.props.pages}
712
+ />
713
+ );
714
+ }
715
+
716
+ /**
717
+ * Renders the per page selector.
718
+ *
719
+ * @returns {JSX.Element}
720
+ */
721
+ renderPerPage() {
722
+ const { perPage } = this.props;
723
+
724
+ return (
725
+ <DropdownButton
726
+ basic
727
+ icon='list'
728
+ onChange={this.props.onPerPageChange.bind(this)}
729
+ options={_.map(this.props.perPageOptions, (count) => ({
730
+ key: count,
731
+ value: count,
732
+ text: count
733
+ }))}
734
+ text={i18n.t('List.labels.perPage', { perPage })}
735
+ value={perPage}
736
+ />
737
+ );
738
+ }
739
+
740
+ /**
741
+ * Renders the record count component.
742
+ *
743
+ * @returns {null|*}
744
+ */
745
+ renderRecordCount() {
746
+ const { count } = this.props;
747
+ return (
748
+ <p className='record-count'>
749
+ {`${Number(count).toLocaleString()} ${i18n.t('List.record', { count })}`}
750
+ </p>
751
+ );
752
+ }
753
+ }
754
+ );
755
+
756
+ export default useList;
757
+
758
+ export type {
759
+ Action,
760
+ Props
761
+ };