@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,99 @@
1
+ // @flow
2
+
3
+ import { useEditContainer, type EditContainerProps } from '@performant-software/shared-components';
4
+ import React, { type ComponentType, useState } from 'react';
5
+ import {
6
+ Button,
7
+ Dimmer,
8
+ Loader,
9
+ Message,
10
+ Modal
11
+ } from 'semantic-ui-react';
12
+ import Toaster from './Toaster';
13
+ import i18n from '../i18n/i18n';
14
+ import './EditModal.css';
15
+
16
+ type Props = EditContainerProps & {
17
+ component: ComponentType<any>,
18
+ onClose: () => void,
19
+ onSave: () => Promise<any>,
20
+ showLoading: boolean
21
+ };
22
+
23
+ const EditModal = (props: Props) => {
24
+ const OuterComponent = props.component;
25
+
26
+ const [showToaster, setShowToaster] = useState(false);
27
+ const hasErrors = !!(props.errors && props.errors.length);
28
+
29
+ return (
30
+ <OuterComponent
31
+ {...props}
32
+ >
33
+ { props.showLoading && props.loading && (
34
+ <Dimmer
35
+ active={props.loading}
36
+ inverted
37
+ >
38
+ <Loader
39
+ content={i18n.t('Common.messages.loading')}
40
+ />
41
+ </Dimmer>
42
+ )}
43
+ { showToaster && hasErrors && (
44
+ <Toaster
45
+ onDismiss={() => setShowToaster(false)}
46
+ timeout={0}
47
+ type={Toaster.MessageTypes.negative}
48
+ >
49
+ <Message.Header
50
+ content={i18n.t('Common.messages.error.header')}
51
+ />
52
+ <Message.List
53
+ items={props.errors}
54
+ />
55
+ </Toaster>
56
+ )}
57
+ <Modal.Actions
58
+ className='edit-modal-actions'
59
+ >
60
+ <Button
61
+ disabled={props.saving}
62
+ onClick={() => {
63
+ setShowToaster(true);
64
+ return props.onSave();
65
+ }}
66
+ primary
67
+ size='medium'
68
+ type='submit'
69
+ >
70
+ { i18n.t('Common.buttons.save') }
71
+ { props.saving && (
72
+ <Loader
73
+ active
74
+ className='saving'
75
+ inline
76
+ size='tiny'
77
+ />
78
+ )}
79
+ </Button>
80
+ <Button
81
+ disabled={props.saving}
82
+ inverted
83
+ onClick={props.onClose.bind(this)}
84
+ primary
85
+ size='medium'
86
+ type='button'
87
+ >
88
+ { i18n.t('Common.buttons.cancel') }
89
+ </Button>
90
+ </Modal.Actions>
91
+ </OuterComponent>
92
+ );
93
+ };
94
+
95
+ EditModal.defaultProps = {
96
+ showLoading: true
97
+ };
98
+
99
+ export default useEditContainer(EditModal);
@@ -0,0 +1,249 @@
1
+ // @flow
2
+
3
+ import { useEditContainer, type EditContainerProps } from '@performant-software/shared-components';
4
+ import React, { Component, type ComponentType } from 'react';
5
+ import {
6
+ Dimmer,
7
+ Form,
8
+ Loader,
9
+ Menu,
10
+ Message,
11
+ type MenuProps
12
+ } from 'semantic-ui-react';
13
+ import _ from 'underscore';
14
+ import CancelButton from './CancelButton';
15
+ import SaveButton from './SaveButton';
16
+ import Toaster from './Toaster';
17
+ import i18n from '../i18n/i18n';
18
+ import './EditPage.css';
19
+
20
+ type Props = EditContainerProps & {
21
+ className?: string,
22
+ component: ComponentType<any>,
23
+ menu?: MenuProps,
24
+ onClose: () => void,
25
+ onSave: () => Promise<any>,
26
+ showLoading: boolean
27
+ };
28
+
29
+ type State = {
30
+ currentTab: string,
31
+ showToaster: boolean
32
+ };
33
+
34
+ class EditPage extends Component<Props, State> {
35
+ static defaultProps: any;
36
+
37
+ /**
38
+ * Constructs a new EditPage component.
39
+ *
40
+ * @param props
41
+ */
42
+ constructor(props: Props) {
43
+ super(props);
44
+
45
+ this.state = {
46
+ currentTab: '',
47
+ showToaster: false
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Sets the current tab to the first tab in the array.
53
+ */
54
+ componentDidMount() {
55
+ if (this.props.menu) {
56
+ const tab = _.first(this.props.menu.items);
57
+ this.setState({ currentTab: tab && tab.key });
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Resets the <code>showToaster</code> property when the set of errors changes.
63
+ *
64
+ * @param prevProps
65
+ */
66
+ componentDidUpdate(prevProps: Props) {
67
+ if (!_.isEmpty(this.props.errors) && !_.isEmpty(prevProps.errors) && prevProps.errors !== this.props.errors) {
68
+ this.setState({ showToaster: true });
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Shows the toaster and calls the onSave prop.
74
+ *
75
+ * @returns {*}
76
+ */
77
+ onSave() {
78
+ this.setState({ showToaster: true });
79
+ return this.props.onSave();
80
+ }
81
+
82
+ /**
83
+ * Renders the EditPage component.
84
+ *
85
+ * @returns {*}
86
+ */
87
+ render() {
88
+ return (
89
+ <Dimmer.Dimmable
90
+ as='div'
91
+ className={`edit-page ${this.props.className || ''}`}
92
+ >
93
+ { this.renderLoading() }
94
+ <Form
95
+ noValidate
96
+ >
97
+ { this.renderMenu() }
98
+ { this.renderButtons() }
99
+ { this.renderComponent() }
100
+ { this.renderToaster() }
101
+ </Form>
102
+ </Dimmer.Dimmable>
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Renders the buttons and container if no menu is present.
108
+ *
109
+ * @returns {null|*}
110
+ */
111
+ renderButtons() {
112
+ if (this.props.menu) {
113
+ return null;
114
+ }
115
+
116
+ return (
117
+ <div
118
+ className='button-container'
119
+ >
120
+ <SaveButton
121
+ onClick={this.onSave.bind(this)}
122
+ saving={this.props.saving}
123
+ />
124
+ <CancelButton
125
+ disabled={this.props.saving}
126
+ onClick={this.props.onClose.bind(this)}
127
+ />
128
+ </div>
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Renders the wrapped component.
134
+ *
135
+ * @returns {*}
136
+ */
137
+ renderComponent() {
138
+ const WrappedComponent = this.props.component;
139
+
140
+ return (
141
+ <WrappedComponent
142
+ {...this.props}
143
+ currentTab={this.state.currentTab}
144
+ />
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Renders the loading indicator.
150
+ *
151
+ * @returns {null|*}
152
+ */
153
+ renderLoading() {
154
+ if (!(this.props.showLoading && this.props.loading)) {
155
+ return null;
156
+ }
157
+
158
+ return (
159
+ <Dimmer
160
+ active={this.props.loading}
161
+ inverted
162
+ >
163
+ <Loader
164
+ content={i18n.t('Common.messages.loading')}
165
+ />
166
+ </Dimmer>
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Renders the menu (if present).
172
+ *
173
+ * @returns {null|*}
174
+ */
175
+ renderMenu() {
176
+ if (!this.props.menu) {
177
+ return null;
178
+ }
179
+
180
+ return (
181
+ <Menu
182
+ {..._.omit(this.props.menu, 'items')}
183
+ >
184
+ { this.props.menu && _.map(this.props.menu.items, (item) => (
185
+ <Menu.Item
186
+ active={item.key === this.state.currentTab}
187
+ key={item.key}
188
+ name={item.name}
189
+ onClick={() => this.setState({ currentTab: item.key })}
190
+ />
191
+ ))}
192
+ <Menu.Menu
193
+ position='right'
194
+ >
195
+ <Menu.Item>
196
+ <SaveButton
197
+ onClick={this.onSave.bind(this)}
198
+ saving={this.props.saving}
199
+ />
200
+ <CancelButton
201
+ disabled={this.props.saving}
202
+ onClick={this.props.onClose.bind(this)}
203
+ />
204
+ </Menu.Item>
205
+ </Menu.Menu>
206
+ </Menu>
207
+ );
208
+ }
209
+
210
+ /**
211
+ * Renders the toaster component.
212
+ *
213
+ * @returns {null|*}
214
+ */
215
+ renderToaster() {
216
+ if (!this.state.showToaster) {
217
+ return null;
218
+ }
219
+
220
+ if (!(this.props.errors && this.props.errors.length)) {
221
+ return null;
222
+ }
223
+
224
+ return (
225
+ <Toaster
226
+ onDismiss={() => this.setState({ showToaster: false })}
227
+ timeout={0}
228
+ type={Toaster.MessageTypes.negative}
229
+ >
230
+ <Message.Header
231
+ content={i18n.t('Common.messages.error.header')}
232
+ />
233
+ <Message.List
234
+ items={this.props.errors}
235
+ />
236
+ </Toaster>
237
+ );
238
+ }
239
+ }
240
+
241
+ EditPage.defaultProps = {
242
+ showLoading: true
243
+ };
244
+
245
+ export default useEditContainer(EditPage);
246
+
247
+ export type EditPageProps = EditContainerProps & {
248
+ currentTab: string
249
+ };
@@ -0,0 +1,278 @@
1
+ // @flow
2
+
3
+ import React, { Component, type ComponentType } from 'react';
4
+ import { Table } from 'semantic-ui-react';
5
+ import uuid from 'react-uuid';
6
+ import _ from 'underscore';
7
+ import DataTable from './DataTable';
8
+ import Draggable from './Draggable';
9
+ import './EmbeddedList.css';
10
+
11
+ import type { Action } from './List';
12
+ import type { Column } from './DataTable';
13
+
14
+ type ListButton = {
15
+ render: () => ComponentType<any>
16
+ };
17
+
18
+ type Props = {
19
+ actions: Array<Action>,
20
+ addButton?: {
21
+ location: string,
22
+ color: string
23
+ },
24
+ buttons?: Array<ListButton>,
25
+ className?: string,
26
+ columns: Array<Column>,
27
+ configurable: boolean,
28
+ items: Array<any>,
29
+ modal?: {
30
+ component: ComponentType<any>,
31
+ props: any,
32
+ state: any
33
+ },
34
+ onCopy?: (item: any) => any,
35
+ onDelete: (item: any) => void,
36
+ onDrag?: (dragIndex: number, hoverIndex: number) => void,
37
+ onSave?: (item: any) => void,
38
+ renderDeleteModal?: ({ selectedItem: any, onCancel: () => void, onConfirm: () => void }) => void,
39
+ renderEmptyRow?: () => void,
40
+ selectable: boolean,
41
+ onRowSelect: (Array<{id: number}>),
42
+ selectedRows: Array<{id: number}>,
43
+ showRecordCount: boolean,
44
+ };
45
+
46
+ type State = {
47
+ sortColumn: ?string,
48
+ sortDirection: ?string
49
+ };
50
+
51
+ const PATH_DELIMITER = '.';
52
+ const SORT_ASCENDING = 'ascending';
53
+ const SORT_DESCENDING = 'descending';
54
+
55
+ /**
56
+ * The EmbeddedList component can be used to display a collection of records that live within a parent object. This is
57
+ * especially useful when the collection is to be saved at the same time as the parent.
58
+ */
59
+ class EmbeddedList extends Component<Props, State> {
60
+ static defaultProps: any;
61
+
62
+ /**
63
+ * Constructs a new EmbeddedList component.
64
+ *
65
+ * @param props
66
+ */
67
+ constructor(props: Props) {
68
+ super(props);
69
+
70
+ this.state = {
71
+ sortColumn: null,
72
+ sortDirection: null
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Sorts the table by the first column.
78
+ */
79
+ componentDidMount() {
80
+ const column = _.find(this.props.columns, (c) => c.sortable !== false);
81
+
82
+ if (column) {
83
+ this.onColumnClick(column);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Returns the sorted, filtered list of items.
89
+ *
90
+ * @returns {*}
91
+ */
92
+ getItems() {
93
+ const { items } = this.props;
94
+ const { sortColumn, sortDirection } = this.state;
95
+
96
+ return _.orderBy(_.filter(items, (item) => !item._destroy), sortColumn, sortDirection);
97
+ }
98
+
99
+ /**
100
+ * Sorts the table by the passed column.
101
+ *
102
+ * @param column
103
+ */
104
+ onColumnClick(column: Column) {
105
+ /*
106
+ * We'll disable the column sorting if the table rows are draggable. Making the table rows draggable implies
107
+ * that the sorting will be done manually. Allowing column click sorting could make things confusing.
108
+ */
109
+ if (this.props.onDrag) {
110
+ return;
111
+ }
112
+
113
+ /*
114
+ * If the column is not sortable, we'll do nothing. Check explicity for "false" here because the default behavior
115
+ * should allow columns to be sortable.
116
+ */
117
+ if (column.sortable === false) {
118
+ return;
119
+ }
120
+
121
+ const sortColumn = column.name;
122
+ let sortDirection = SORT_ASCENDING;
123
+
124
+ if (column.name === this.state.sortColumn) {
125
+ sortDirection = this.state.sortDirection === SORT_ASCENDING ? SORT_DESCENDING : SORT_ASCENDING;
126
+ }
127
+
128
+ this.setState({ sortColumn, sortDirection });
129
+ }
130
+
131
+ /**
132
+ * Deletes the passed item. This function returns a promise so that calls can be chained together.
133
+ *
134
+ * @param item
135
+ *
136
+ * @returns {Promise}
137
+ */
138
+ onDelete(item: any) {
139
+ this.props.onDelete(item);
140
+ return Promise.resolve();
141
+ }
142
+
143
+ /**
144
+ * Saves the passed item, resets the state, and reloads the data. This function returns a promise so that calls can
145
+ * be chained together.
146
+ *
147
+ * @param item
148
+ *
149
+ * @returns {Promise}
150
+ */
151
+ onSave(item: any) {
152
+ const uid = item.uid ? item.uid : uuid();
153
+
154
+ if (this.props.onSave) {
155
+ this.props.onSave({ ...item, uid });
156
+ }
157
+
158
+ return Promise.resolve();
159
+ }
160
+
161
+ /**
162
+ * Renders the EmbeddedList component.
163
+ *
164
+ * @returns {*}
165
+ */
166
+ render() {
167
+ return (
168
+ <DataTable
169
+ {...this.props}
170
+ actions={this.props.actions}
171
+ addButton={this.props.addButton}
172
+ buttons={this.props.buttons}
173
+ className={`embedded-list ${this.props.className ? this.props.className : ''}`}
174
+ configurable={this.props.configurable}
175
+ columns={this.props.columns}
176
+ count={this.props.items.length}
177
+ items={this.getItems()}
178
+ modal={this.props.modal}
179
+ onColumnClick={this.onColumnClick.bind(this)}
180
+ onCopy={this.props.onCopy}
181
+ onDrag={this.props.onDrag}
182
+ onDelete={this.onDelete.bind(this)}
183
+ onSave={this.onSave.bind(this)}
184
+ renderDeleteModal={this.props.renderDeleteModal}
185
+ renderEmptyRow={this.props.renderEmptyRow}
186
+ renderItem={this.renderItem.bind(this)}
187
+ sortColumn={this.state.sortColumn}
188
+ sortDirection={this.state.sortDirection}
189
+ tableProps={{
190
+ celled: true,
191
+ sortable: !this.props.onDrag
192
+ }}
193
+ selectable={this.props.selectable}
194
+ onRowSelect={this.props.onRowSelect}
195
+ selectedRows={this.props.selectedRows}
196
+ showRecordCount={this.props.showRecordCount}
197
+ />
198
+ );
199
+ }
200
+
201
+ /**
202
+ * Renders the passed item. This function should be used if the table row is draggable.
203
+ *
204
+ * @param item
205
+ * @param index
206
+ * @param children
207
+ *
208
+ * @returns {*}
209
+ */
210
+ renderItem(item: any, index: number, children: ComponentType<any>) {
211
+ if (this.props.onDrag) {
212
+ // Since the item may not be saved yet, we'll look for the ID or UID columns as the key. This is necessary to
213
+ // maintain the correct element when dragging.
214
+ const key = item.id || item.uid;
215
+
216
+ return (
217
+ <Draggable
218
+ id={key}
219
+ index={index}
220
+ item={item}
221
+ key={key}
222
+ onDrag={this.props.onDrag.bind(this)}
223
+ >
224
+ <Table.Row>
225
+ { children }
226
+ </Table.Row>
227
+ </Draggable>
228
+ );
229
+ }
230
+
231
+ return (
232
+ <Table.Row
233
+ key={index}
234
+ >
235
+ { children }
236
+ </Table.Row>
237
+ );
238
+ }
239
+ }
240
+
241
+ /**
242
+ * UnderscoreJS mixin to allow sorting by a property string (i.e. "person.name") and a sort
243
+ * direction ("ascending" or "descending").
244
+ */
245
+ _.mixin({
246
+ orderBy: (items, property, direction) => {
247
+ if (!property) {
248
+ return items;
249
+ }
250
+
251
+ const sortProperty = _.property(property.split(PATH_DELIMITER));
252
+ let ordered = _.sortBy(items, (item) => sortProperty(item));
253
+
254
+ if (direction === SORT_DESCENDING) {
255
+ ordered = ordered.reverse();
256
+ }
257
+
258
+ return ordered;
259
+ }
260
+ });
261
+
262
+ EmbeddedList.defaultProps = {
263
+ items: [],
264
+ addButton: {
265
+ location: 'top'
266
+ },
267
+ buttons: [],
268
+ className: '',
269
+ configurable: true,
270
+ modal: undefined,
271
+ onCopy: undefined,
272
+ onDrag: undefined,
273
+ onSave: () => {},
274
+ renderDeleteModal: undefined,
275
+ renderEmptyRow: undefined
276
+ };
277
+
278
+ export default EmbeddedList;
@@ -0,0 +1,54 @@
1
+ // @flow
2
+
3
+ import React, { useRef } from 'react';
4
+ import { Button } from 'semantic-ui-react';
5
+
6
+ type Props = {
7
+ multiple?: boolean,
8
+ onSelection: (files: Array<File>) => void
9
+ };
10
+
11
+ const FileInputButton = ({ onSelection, multiple, ...buttonProps }: Props) => {
12
+ const fileInputRef = useRef();
13
+
14
+ /**
15
+ * Converts the passed file list to an array.
16
+ *
17
+ * @param fileList
18
+ *
19
+ * @returns {[]}
20
+ */
21
+ const toArray = (fileList) => {
22
+ const array = [];
23
+
24
+ for (let i = 0; i < fileList.length; i += 1) {
25
+ array.push(fileList.item(i));
26
+ }
27
+
28
+ return array;
29
+ };
30
+
31
+ return (
32
+ <>
33
+ <Button
34
+ {...buttonProps}
35
+ onClick={() => fileInputRef.current && fileInputRef.current.click()}
36
+ />
37
+ <input
38
+ ref={fileInputRef}
39
+ type='file'
40
+ multiple={multiple}
41
+ onChange={(e) => onSelection(toArray(e.target.files))}
42
+ style={{
43
+ display: 'none'
44
+ }}
45
+ />
46
+ </>
47
+ );
48
+ };
49
+
50
+ FileInputButton.defaultProps = {
51
+ multiple: false
52
+ };
53
+
54
+ export default FileInputButton;