@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.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/build/index.js +2 -0
- package/build/index.js.map +1 -0
- package/build/main.css +786 -0
- package/index.js +1 -0
- package/package.json +37 -0
- package/src/components/AccordionDataList.css +8 -0
- package/src/components/AccordionDataList.js +224 -0
- package/src/components/AccordionList.css +27 -0
- package/src/components/AccordionList.js +596 -0
- package/src/components/AccordionSelector.css +3 -0
- package/src/components/AccordionSelector.js +359 -0
- package/src/components/ArrowButtons.css +4 -0
- package/src/components/ArrowButtons.js +38 -0
- package/src/components/AssociatedDropdown.css +44 -0
- package/src/components/AssociatedDropdown.js +338 -0
- package/src/components/BooleanIcon.css +0 -0
- package/src/components/BooleanIcon.js +33 -0
- package/src/components/CancelButton.css +0 -0
- package/src/components/CancelButton.js +25 -0
- package/src/components/ColorButton.css +4 -0
- package/src/components/ColorButton.js +34 -0
- package/src/components/ColorPickerModal.css +3 -0
- package/src/components/ColorPickerModal.js +77 -0
- package/src/components/ColumnResize.css +9 -0
- package/src/components/ColumnResize.js +20 -0
- package/src/components/DataList.css +0 -0
- package/src/components/DataList.js +531 -0
- package/src/components/DataTable.css +43 -0
- package/src/components/DataTable.js +596 -0
- package/src/components/DataTableColumnSelector.css +10 -0
- package/src/components/DataTableColumnSelector.js +146 -0
- package/src/components/DateInput.css +6 -0
- package/src/components/DateInput.js +58 -0
- package/src/components/DatePicker.css +72 -0
- package/src/components/DatePicker.js +81 -0
- package/src/components/DescriptorField.css +0 -0
- package/src/components/DescriptorField.js +42 -0
- package/src/components/DownloadButton.css +0 -0
- package/src/components/DownloadButton.js +23 -0
- package/src/components/Draggable.css +0 -0
- package/src/components/Draggable.js +94 -0
- package/src/components/DropdownButton.css +3 -0
- package/src/components/DropdownButton.js +65 -0
- package/src/components/DropdownMenu.css +0 -0
- package/src/components/DropdownMenu.js +68 -0
- package/src/components/EditModal.css +8 -0
- package/src/components/EditModal.js +99 -0
- package/src/components/EditPage.css +7 -0
- package/src/components/EditPage.js +249 -0
- package/src/components/EmbeddedList.css +7 -0
- package/src/components/EmbeddedList.js +278 -0
- package/src/components/FileInputButton.css +0 -0
- package/src/components/FileInputButton.js +54 -0
- package/src/components/FileUpload.css +31 -0
- package/src/components/FileUpload.js +188 -0
- package/src/components/FileUploadModal.css +0 -0
- package/src/components/FileUploadModal.js +408 -0
- package/src/components/FuzzyDate.css +8 -0
- package/src/components/FuzzyDate.js +575 -0
- package/src/components/GoogleMap.css +0 -0
- package/src/components/GoogleMap.js +105 -0
- package/src/components/GooglePlacesSearch.css +0 -0
- package/src/components/GooglePlacesSearch.js +43 -0
- package/src/components/HorizontalCards.css +50 -0
- package/src/components/HorizontalCards.js +226 -0
- package/src/components/ItemCollection.css +3 -0
- package/src/components/ItemCollection.js +159 -0
- package/src/components/ItemList.css +0 -0
- package/src/components/ItemList.js +126 -0
- package/src/components/Items.css +19 -0
- package/src/components/Items.js +365 -0
- package/src/components/ItemsToggle.css +0 -0
- package/src/components/ItemsToggle.js +168 -0
- package/src/components/KeyboardField.css +4 -0
- package/src/components/KeyboardField.js +147 -0
- package/src/components/LazyDocument.css +21 -0
- package/src/components/LazyDocument.js +113 -0
- package/src/components/LazyImage.css +21 -0
- package/src/components/LazyImage.js +119 -0
- package/src/components/LazyVideo.css +21 -0
- package/src/components/LazyVideo.js +131 -0
- package/src/components/LinkButton.css +8 -0
- package/src/components/LinkButton.js +23 -0
- package/src/components/LinkLabel.css +8 -0
- package/src/components/LinkLabel.js +29 -0
- package/src/components/List.css +8 -0
- package/src/components/List.js +761 -0
- package/src/components/ListFilters.css +0 -0
- package/src/components/ListFilters.js +408 -0
- package/src/components/ListLoader.css +8 -0
- package/src/components/ListLoader.js +32 -0
- package/src/components/ListTable.css +3 -0
- package/src/components/ListTable.js +86 -0
- package/src/components/LoginModal.css +7 -0
- package/src/components/LoginModal.js +102 -0
- package/src/components/MasonryGrid.css +48 -0
- package/src/components/MasonryGrid.js +202 -0
- package/src/components/MediaGallery.css +37 -0
- package/src/components/MediaGallery.js +148 -0
- package/src/components/MediaGrid.css +72 -0
- package/src/components/MediaGrid.js +74 -0
- package/src/components/MediaList.css +3 -0
- package/src/components/MediaList.js +98 -0
- package/src/components/ModalDropdown.css +11 -0
- package/src/components/ModalDropdown.js +84 -0
- package/src/components/NestedAccordion.css +41 -0
- package/src/components/NestedAccordion.js +276 -0
- package/src/components/PhotoViewer.css +3 -0
- package/src/components/PhotoViewer.js +36 -0
- package/src/components/PlayButton.css +3 -0
- package/src/components/PlayButton.js +37 -0
- package/src/components/RemoteDropdown.css +13 -0
- package/src/components/RemoteDropdown.js +368 -0
- package/src/components/SaveButton.css +0 -0
- package/src/components/SaveButton.js +31 -0
- package/src/components/Section.css +0 -0
- package/src/components/Section.js +41 -0
- package/src/components/Selectize.css +11 -0
- package/src/components/Selectize.js +297 -0
- package/src/components/SelectizeHeader.css +3 -0
- package/src/components/SelectizeHeader.js +40 -0
- package/src/components/TabbedModal.css +14 -0
- package/src/components/TabbedModal.js +165 -0
- package/src/components/TabsMenu.css +0 -0
- package/src/components/TabsMenu.js +35 -0
- package/src/components/TagsList.css +0 -0
- package/src/components/TagsList.js +43 -0
- package/src/components/Thumbnail.css +0 -0
- package/src/components/Thumbnail.js +47 -0
- package/src/components/Toaster.css +9 -0
- package/src/components/Toaster.js +73 -0
- package/src/components/VideoFrameSelector.css +3 -0
- package/src/components/VideoFrameSelector.js +148 -0
- package/src/components/VideoPlayer.css +3 -0
- package/src/components/VideoPlayer.js +55 -0
- package/src/components/VideoPlayerButton.css +17 -0
- package/src/components/VideoPlayerButton.js +17 -0
- package/src/components/ViewXML.css +0 -0
- package/src/components/ViewXML.js +72 -0
- package/src/i18n/en.json +204 -0
- package/src/i18n/i18n.js +24 -0
- package/src/index.js +76 -0
- package/types/components/AccordionDataList.js.flow +224 -0
- package/types/components/AccordionList.js.flow +596 -0
- package/types/components/AccordionSelector.js.flow +359 -0
- package/types/components/ArrowButtons.js.flow +38 -0
- package/types/components/AssociatedDropdown.js.flow +338 -0
- package/types/components/BooleanIcon.js.flow +33 -0
- package/types/components/CancelButton.js.flow +25 -0
- package/types/components/ColorButton.js.flow +34 -0
- package/types/components/ColorPickerModal.js.flow +77 -0
- package/types/components/ColumnResize.js.flow +20 -0
- package/types/components/DataList.js.flow +531 -0
- package/types/components/DataTable.js.flow +596 -0
- package/types/components/DataTableColumnSelector.js.flow +146 -0
- package/types/components/DataView.js.flow +125 -0
- package/types/components/DateInput.js.flow +58 -0
- package/types/components/DatePicker.js.flow +81 -0
- package/types/components/DescriptorField.js.flow +42 -0
- package/types/components/DownloadButton.js.flow +23 -0
- package/types/components/Draggable.js.flow +94 -0
- package/types/components/DropdownButton.js.flow +65 -0
- package/types/components/DropdownMenu.js.flow +68 -0
- package/types/components/EditModal.js.flow +99 -0
- package/types/components/EditPage.js.flow +249 -0
- package/types/components/EmbeddedList.js.flow +278 -0
- package/types/components/FileInputButton.js.flow +54 -0
- package/types/components/FileUpload.js.flow +188 -0
- package/types/components/FileUploadModal.js.flow +408 -0
- package/types/components/FuzzyDate.js.flow +575 -0
- package/types/components/GoogleMap.js.flow +105 -0
- package/types/components/GooglePlacesSearch.js.flow +43 -0
- package/types/components/HorizontalCards.js.flow +226 -0
- package/types/components/ItemCollection.js.flow +159 -0
- package/types/components/ItemList.js.flow +126 -0
- package/types/components/Items.js.flow +365 -0
- package/types/components/ItemsToggle.js.flow +168 -0
- package/types/components/KeyboardField.js.flow +147 -0
- package/types/components/LazyDocument.js.flow +113 -0
- package/types/components/LazyImage.js.flow +119 -0
- package/types/components/LazyVideo.js.flow +131 -0
- package/types/components/LinkButton.js.flow +23 -0
- package/types/components/LinkLabel.js.flow +29 -0
- package/types/components/List.js.flow +761 -0
- package/types/components/ListFilters.js.flow +408 -0
- package/types/components/ListLoader.js.flow +32 -0
- package/types/components/ListTable.js.flow +86 -0
- package/types/components/LoginModal.js.flow +102 -0
- package/types/components/MasonryGrid.js.flow +202 -0
- package/types/components/MediaGallery.js.flow +148 -0
- package/types/components/MediaGrid.js.flow +74 -0
- package/types/components/MediaList.js.flow +98 -0
- package/types/components/MenuBar.js.flow +77 -0
- package/types/components/MenuSidebar.js.flow +72 -0
- package/types/components/ModalDropdown.js.flow +84 -0
- package/types/components/NestedAccordion.js.flow +276 -0
- package/types/components/PhotoViewer.js.flow +36 -0
- package/types/components/PlayButton.js.flow +37 -0
- package/types/components/RemoteDropdown.js.flow +368 -0
- package/types/components/SaveButton.js.flow +31 -0
- package/types/components/Section.js.flow +41 -0
- package/types/components/Selectize.js.flow +297 -0
- package/types/components/SelectizeHeader.js.flow +40 -0
- package/types/components/TabbedModal.js.flow +165 -0
- package/types/components/TabsMenu.js.flow +35 -0
- package/types/components/TagsList.js.flow +43 -0
- package/types/components/Thumbnail.js.flow +47 -0
- package/types/components/Toaster.js.flow +73 -0
- package/types/components/VideoFrameSelector.js.flow +148 -0
- package/types/components/VideoPlayer.js.flow +55 -0
- package/types/components/VideoPlayerButton.js.flow +17 -0
- package/types/components/ViewXML.js.flow +72 -0
- package/types/hooks/Imageable.js.flow +54 -0
- package/types/i18n/i18n.js.flow +24 -0
- package/types/index.js.flow +78 -0
- 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'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
|
+
};
|