@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
File without changes
@@ -0,0 +1,408 @@
1
+ // @flow
2
+
3
+ import { type EditContainerProps } from '@performant-software/shared-components';
4
+ import React, {
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ type Element
9
+ } from 'react';
10
+ import uuid from 'react-uuid';
11
+ import {
12
+ Button,
13
+ Checkbox,
14
+ Dropdown,
15
+ Form,
16
+ Grid,
17
+ Header,
18
+ Input,
19
+ Modal
20
+ } from 'semantic-ui-react';
21
+ import _ from 'underscore';
22
+ import i18n from '../i18n/i18n';
23
+ import AssociatedDropdown from './AssociatedDropdown';
24
+ import DropdownButton from './DropdownButton';
25
+ import FuzzyDate from './FuzzyDate';
26
+
27
+ type Option = {
28
+ key: string | number,
29
+ icon?: string,
30
+ text: string,
31
+ value: any
32
+ };
33
+
34
+ type Filter = {
35
+ collectionName?: 'string',
36
+ key: string,
37
+ label: string,
38
+ onSearch?: (search: string) => Promise<any>,
39
+ operator: ?string,
40
+ options?: Array<Option>,
41
+ renderOption?: (item: any) => Option,
42
+ renderSearchQuery?: (item: any) => Element<any> | string,
43
+ searchQuery?: string,
44
+ type: string,
45
+ value: ?any
46
+ };
47
+
48
+ type Props = EditContainerProps & {
49
+ filters: Array<Filter>,
50
+ item: {
51
+ filters: Array<Filter>,
52
+ onSort: (filter: Filter) => any
53
+ }
54
+ };
55
+
56
+ const FilterTypes = {
57
+ boolean: 'boolean',
58
+ date: 'date',
59
+ integer: 'integer',
60
+ relationship: 'relationship',
61
+ select: 'select',
62
+ string: 'string',
63
+ text: 'text'
64
+ };
65
+
66
+ const Operators = {
67
+ equal: 'equal',
68
+ notEqual: 'not_equal',
69
+ contain: 'contain',
70
+ notContain: 'not_contain',
71
+ empty: 'empty',
72
+ notEmpty: 'not_empty',
73
+ greaterThan: 'greater_than',
74
+ lessThan: 'less_than'
75
+ };
76
+
77
+ const OperatorsByType = {
78
+ [FilterTypes.boolean]: [
79
+ Operators.equal
80
+ ],
81
+ [FilterTypes.date]: [
82
+ Operators.equal
83
+ ],
84
+ [FilterTypes.relationship]: [
85
+ Operators.equal,
86
+ Operators.notEqual,
87
+ Operators.empty,
88
+ Operators.notEmpty
89
+ ],
90
+ [FilterTypes.select]: [
91
+ Operators.equal,
92
+ Operators.notEqual,
93
+ Operators.empty,
94
+ Operators.notEmpty
95
+ ],
96
+ [FilterTypes.string]: [
97
+ Operators.equal,
98
+ Operators.notEqual,
99
+ Operators.contain,
100
+ Operators.notContain,
101
+ Operators.empty,
102
+ Operators.notEmpty
103
+ ],
104
+ [FilterTypes.text]: [
105
+ Operators.contain,
106
+ Operators.notContain,
107
+ Operators.empty,
108
+ Operators.notEmpty
109
+ ]
110
+ };
111
+
112
+ const OperatorOptions = [{
113
+ key: Operators.equal,
114
+ value: Operators.equal,
115
+ text: i18n.t('ListFilters.operators.equal')
116
+ }, {
117
+ key: Operators.notEqual,
118
+ value: Operators.notEqual,
119
+ text: i18n.t('ListFilters.operators.notEqual')
120
+ }, {
121
+ key: Operators.contain,
122
+ value: Operators.contain,
123
+ text: i18n.t('ListFilters.operators.contain')
124
+ }, {
125
+ key: Operators.notContain,
126
+ value: Operators.notContain,
127
+ text: i18n.t('ListFilters.operators.notContain')
128
+ }, {
129
+ key: Operators.empty,
130
+ value: Operators.empty,
131
+ text: i18n.t('ListFilters.operators.empty')
132
+ }, {
133
+ key: Operators.notEmpty,
134
+ value: Operators.notEmpty,
135
+ text: i18n.t('ListFilters.operators.notEmpty')
136
+ }, {
137
+ key: Operators.greaterThan,
138
+ value: Operators.greaterThan,
139
+ text: i18n.t('ListFilters.operators.greaterThan')
140
+ }, {
141
+ key: Operators.lessThan,
142
+ value: Operators.lessThan,
143
+ text: i18n.t('ListFilters.operators.lessThan')
144
+ }];
145
+
146
+ const ListFilters = (props: Props) => {
147
+ /**
148
+ * Returns the available operators for the passed filter type.
149
+ *
150
+ * @type {function(string): *}
151
+ */
152
+ const getOperatorsByType = useCallback((type: string) => {
153
+ const operators = OperatorsByType[type];
154
+ return _.filter(OperatorOptions, (option) => !operators || _.contains(operators, option.key));
155
+ }, []);
156
+
157
+ /**
158
+ * Default sort function.
159
+ *
160
+ * @param filter
161
+ */
162
+ const onDefaultSort = (filter) => filter.label;
163
+
164
+ /**
165
+ * Renders the input element for the passed filter.
166
+ *
167
+ * @type {(function(Filter): (null|*))|*}
168
+ */
169
+ const renderInput = useCallback((filter: Filter) => {
170
+ // No need to render an input for "empty" or "not empty" operators
171
+ if (filter.operator === Operators.empty || filter.operator === Operators.notEmpty) {
172
+ return null;
173
+ }
174
+
175
+ // Render a checkbox for boolean types
176
+ if (filter.type === FilterTypes.boolean) {
177
+ return (
178
+ <Checkbox
179
+ onChange={() => props.onSaveChildAssociation('filters', { ...filter, value: !filter.value })}
180
+ checked={filter.value}
181
+ />
182
+ );
183
+ }
184
+
185
+ // Render a text input for string, text, and integer types
186
+ if (filter.type === FilterTypes.string || filter.type === FilterTypes.text || filter.type === FilterTypes.integer) {
187
+ return (
188
+ <Input
189
+ onChange={(e, { value }) => props.onSaveChildAssociation('filters', { ...filter, value })}
190
+ value={filter.value}
191
+ />
192
+ );
193
+ }
194
+
195
+ // Render a fuzzy date input for date types
196
+ if (filter.type === FilterTypes.date) {
197
+ return (
198
+ <FuzzyDate
199
+ date={filter.value || {}}
200
+ description={false}
201
+ onChange={(value) => {
202
+ props.onSaveChildAssociation('filters', {
203
+ ...filter,
204
+ value
205
+ });
206
+ }}
207
+ title={filter.label}
208
+ />
209
+ );
210
+ }
211
+
212
+ // Render a dropdown
213
+ if (filter.type === FilterTypes.select) {
214
+ return (
215
+ <Dropdown
216
+ onChange={(e, { value }) => props.onSaveChildAssociation('filters', { ...filter, value })}
217
+ options={filter.options}
218
+ selectOnBlur={false}
219
+ selection
220
+ value={filter.value}
221
+ />
222
+ );
223
+ }
224
+
225
+ // Render an AssociatedDropdown for relationship types
226
+ if (filter.type === FilterTypes.relationship) {
227
+ const {
228
+ collectionName,
229
+ onSearch,
230
+ renderOption,
231
+ renderSearchQuery,
232
+ searchQuery,
233
+ value
234
+ } = filter;
235
+
236
+ if (!(collectionName && onSearch && renderOption)) {
237
+ return null;
238
+ }
239
+
240
+ return (
241
+ <AssociatedDropdown
242
+ collectionName={collectionName}
243
+ onSearch={onSearch}
244
+ onSelection={(item) => {
245
+ props.onSaveChildAssociation('filters', {
246
+ ...filter,
247
+ searchQuery: renderSearchQuery && renderSearchQuery(item),
248
+ value: item.id
249
+ });
250
+ }}
251
+ renderOption={renderOption}
252
+ required
253
+ searchQuery={searchQuery || ''}
254
+ value={value || null}
255
+ />
256
+ );
257
+ }
258
+
259
+ return null;
260
+ }, [props.item.filters, props.onSaveChildAssociation]);
261
+
262
+ /**
263
+ * Sort the filters according to the onSort prop if provided.
264
+ *
265
+ * @type {unknown}
266
+ */
267
+ const filters = useMemo(() => _.sortBy(
268
+ props.filters,
269
+ props.item.onSort || onDefaultSort
270
+ ), [props.filters, props.item.onSort]);
271
+
272
+ /**
273
+ * Since the filters may be restored from the session storage, complex object and functions are not serialized. Here
274
+ * we'll default any properties for existing filters that could not be serialized when the component is mounted.
275
+ */
276
+ useEffect(() => {
277
+ _.each(props.item.filters, (filter) => {
278
+ const defaults = _.findWhere(props.filters, { key: filter.key });
279
+
280
+ if (filter.type === FilterTypes.boolean) {
281
+ defaults.value = false;
282
+ }
283
+
284
+ props.onSaveChildAssociation('filters', _.defaults(filter, defaults));
285
+ });
286
+ }, []);
287
+
288
+ return (
289
+ <Modal
290
+ as={Form}
291
+ centered={false}
292
+ className='list-filters-modal'
293
+ noValidate
294
+ open
295
+ size='small'
296
+ >
297
+ <Modal.Header>
298
+ <Grid
299
+ columns={2}
300
+ >
301
+ <Grid.Column
302
+ verticalAlign='middle'
303
+ >
304
+ <Header
305
+ content={i18n.t('ListFilters.title')}
306
+ />
307
+ </Grid.Column>
308
+ <Grid.Column
309
+ textAlign='right'
310
+ >
311
+ <DropdownButton
312
+ color='green'
313
+ icon='plus'
314
+ options={_.map(filters, (filter) => ({
315
+ key: filter.key,
316
+ value: filter.key,
317
+ text: filter.label
318
+ }))}
319
+ onChange={(e, { value }) => {
320
+ const filter = _.findWhere(props.filters, { key: value });
321
+ props.onSaveChildAssociation('filters', {
322
+ ...filter,
323
+ uid: uuid(),
324
+ operator: Operators.equal
325
+ });
326
+ }}
327
+ scrolling
328
+ text={i18n.t('ListFilters.buttons.add')}
329
+ value=''
330
+ />
331
+ <Button
332
+ color='red'
333
+ content={i18n.t('ListFilters.buttons.reset')}
334
+ icon='repeat'
335
+ onClick={() => props.onReset()}
336
+ style={{
337
+ marginLeft: '1em'
338
+ }}
339
+ />
340
+ </Grid.Column>
341
+ </Grid>
342
+ </Modal.Header>
343
+ <Modal.Content>
344
+ { !_.isEmpty(props.item.filters) && (
345
+ <Grid>
346
+ { _.map(props.item.filters, (filter) => (
347
+ <Grid.Row
348
+ columns={4}
349
+ key={filter.key}
350
+ verticalAlign='middle'
351
+ >
352
+ <Grid.Column>
353
+ <Header
354
+ content={filter.label}
355
+ />
356
+ </Grid.Column>
357
+ <Grid.Column
358
+ width={5}
359
+ >
360
+ <Dropdown
361
+ options={getOperatorsByType(filter.type)}
362
+ onChange={(e, { value }) => props.onSaveChildAssociation('filters', {
363
+ ..._.omit(filter, 'value'),
364
+ operator: value,
365
+ })}
366
+ selection
367
+ value={filter.operator}
368
+ />
369
+ </Grid.Column>
370
+ <Grid.Column
371
+ width={5}
372
+ >
373
+ { renderInput(filter) }
374
+ </Grid.Column>
375
+ <Grid.Column
376
+ width={1}
377
+ >
378
+ <Button
379
+ basic
380
+ icon='times'
381
+ onClick={() => {
382
+ /*
383
+ * If we're removing the last filter, call the onReset prop to ensure the UI doesn't display
384
+ * as active.
385
+ */
386
+ if (props.item.filters && props.item.filters.length === 1) {
387
+ props.onReset();
388
+ } else {
389
+ props.onDeleteChildAssociation('filters', filter);
390
+ }
391
+ }}
392
+ />
393
+ </Grid.Column>
394
+ </Grid.Row>
395
+ ))}
396
+ </Grid>
397
+ )}
398
+ </Modal.Content>
399
+ { props.children }
400
+ </Modal>
401
+ );
402
+ };
403
+
404
+ export default ListFilters;
405
+
406
+ export {
407
+ FilterTypes
408
+ };
@@ -0,0 +1,8 @@
1
+ .listLoader.ui.segment {
2
+ position: absolute;
3
+ top: 50%;
4
+ left: 50%;
5
+ transform: translate(-50%, -50%);
6
+ padding: 50px;
7
+ z-index: 999;
8
+ }
@@ -0,0 +1,32 @@
1
+ // @flow
2
+
3
+ import React from 'react';
4
+ import { Dimmer, Loader, Segment } from 'semantic-ui-react';
5
+ import i18n from '../i18n/i18n';
6
+ import './ListLoader.css';
7
+
8
+ type Props = {
9
+ active: boolean
10
+ };
11
+
12
+ const ListLoader = (props: Props) => (
13
+ <Dimmer.Dimmable
14
+ as={Segment}
15
+ className='listLoader'
16
+ dimmed={props.active}
17
+ style={{
18
+ opacity: props.active ? '0.8' : '0'
19
+ }}
20
+ >
21
+ <Dimmer
22
+ active={props.active}
23
+ inverted
24
+ >
25
+ <Loader
26
+ content={i18n.t('Common.messages.loading')}
27
+ />
28
+ </Dimmer>
29
+ </Dimmer.Dimmable>
30
+ );
31
+
32
+ export default ListLoader;
@@ -0,0 +1,3 @@
1
+ .list-table .table-container {
2
+ margin-top: 20px;
3
+ }
@@ -0,0 +1,86 @@
1
+ // @flow
2
+
3
+ import React, { useEffect } from 'react';
4
+ import _ from 'underscore';
5
+ import DataTable from './DataTable';
6
+ import useDataList, { SORT_ASCENDING, SORT_DESCENDING } from './DataList';
7
+ import './ListTable.css';
8
+
9
+ import type { Column } from './DataTable';
10
+
11
+ type Props = {
12
+ columns: Array<Column>,
13
+ page: number,
14
+ onSort: (sortColumn: string, sortDirection: string, page?: number) => void,
15
+ onInit: (page?: number) => void,
16
+ sortColumn: string,
17
+ sortDirection: string
18
+ };
19
+
20
+ const ListTable = (props: Props) => {
21
+ /**
22
+ * Sorts the list by the selected column, and/or reverse the direction.
23
+ *
24
+ * @param column
25
+ */
26
+ const onColumnClick = (column) => {
27
+ if (!column.sortable) {
28
+ return;
29
+ }
30
+
31
+ const sortColumn = column.name;
32
+ let sortDirection = SORT_ASCENDING;
33
+
34
+ /**
35
+ * If the column has not yet been click-sorted, check to see if there is a default sort
36
+ * direction on the column. Otherwise, toggle the sort direction on the state.
37
+ */
38
+ if (column.name === props.sortColumn) {
39
+ sortDirection = props.sortDirection === SORT_ASCENDING ? SORT_DESCENDING : SORT_ASCENDING;
40
+ } else if (column.sortDirection) {
41
+ sortDirection = column.sortDirection;
42
+ }
43
+
44
+ props.onSort(sortColumn, sortDirection);
45
+ };
46
+
47
+ /**
48
+ * Initializes the list by calling the onSort prop. If no sortColumn prop is passed, we'll sort the list by the first
49
+ * sortable column.
50
+ */
51
+ useEffect(() => {
52
+ const { page, sortColumn, sortDirection = SORT_ASCENDING } = props;
53
+
54
+ if (props.sortColumn) {
55
+ props.onSort(sortColumn, sortDirection, page);
56
+ } else {
57
+ const sortableColumn = _.findWhere(props.columns, { sortable: true });
58
+ if (sortableColumn) {
59
+ onColumnClick(sortableColumn);
60
+ } else {
61
+ // If no columns are sortable, load the data as is
62
+ props.onInit();
63
+ }
64
+ }
65
+ }, []);
66
+
67
+ return (
68
+ <DataTable
69
+ {...props}
70
+ onColumnClick={onColumnClick.bind(this)}
71
+ />
72
+ );
73
+ };
74
+
75
+ ListTable.defaultProps = {
76
+ configurable: true,
77
+ onCopy: undefined,
78
+ renderDeleteModal: undefined,
79
+ renderEmptyRow: undefined,
80
+ tableProps: {
81
+ celled: true,
82
+ sortable: true
83
+ }
84
+ };
85
+
86
+ export default useDataList(ListTable);
@@ -0,0 +1,7 @@
1
+ .login-modal .ui.grid > .column > .row {
2
+ margin: 15px 150px 0px 150px;
3
+ }
4
+
5
+ .login-modal .ui.input.form-field {
6
+ display: block;
7
+ }
@@ -0,0 +1,102 @@
1
+ // @flow
2
+
3
+ import React, { type Element } from 'react';
4
+ import {
5
+ Button,
6
+ Form,
7
+ Grid,
8
+ Header,
9
+ Icon,
10
+ Input,
11
+ Message,
12
+ Modal
13
+ } from 'semantic-ui-react';
14
+ import i18n from '../i18n/i18n';
15
+ import './LoginModal.css';
16
+
17
+ type Props = {
18
+ disabled: boolean,
19
+ loginFailed: boolean,
20
+ onClose: () => void,
21
+ onLogin: () => void,
22
+ onPasswordChange: () => void,
23
+ onUsernameChange: () => void,
24
+ open: boolean,
25
+ trigger?: () => Element<any>,
26
+ placeholder: string
27
+ };
28
+
29
+ const LoginModal = (props: Props) => (
30
+ <Modal
31
+ as={Form}
32
+ className='login-modal'
33
+ error={props.loginFailed}
34
+ open={props.open}
35
+ size='small'
36
+ trigger={props.trigger}
37
+ >
38
+ <Header
39
+ icon='user circle'
40
+ content={i18n.t('LoginModal.header')}
41
+ />
42
+ <Message
43
+ error
44
+ header={i18n.t('LoginModal.loginErrorHeader')}
45
+ content={i18n.t('LoginModal.loginErrorContent')}
46
+ />
47
+ <Grid
48
+ padded='vertically'
49
+ textAlign='center'
50
+ >
51
+ <Grid.Column>
52
+ <Grid.Row>
53
+ <Input
54
+ autoFocus
55
+ className='form-field'
56
+ icon={<Icon name='at' />}
57
+ onChange={props.onUsernameChange.bind(this)}
58
+ placeholder={props.placeholder}
59
+ size='huge'
60
+ />
61
+ </Grid.Row>
62
+ <Grid.Row
63
+ className='row'
64
+ >
65
+ <Input
66
+ className='form-field'
67
+ icon={<Icon name='lock' />}
68
+ onChange={props.onPasswordChange.bind(this)}
69
+ placeholder={i18n.t('LoginModal.password')}
70
+ size='huge'
71
+ type='password'
72
+ />
73
+ </Grid.Row>
74
+ </Grid.Column>
75
+ </Grid>
76
+ <Modal.Actions>
77
+ <Button
78
+ disabled={props.disabled}
79
+ onClick={props.onLogin.bind(this)}
80
+ primary
81
+ size='large'
82
+ type='submit'
83
+ >
84
+ { i18n.t('LoginModal.buttonLogin') }
85
+ </Button>
86
+ <Button
87
+ inverted
88
+ onClick={props.onClose.bind(this)}
89
+ primary
90
+ size='large'
91
+ >
92
+ { i18n.t('LoginModal.buttonCancel') }
93
+ </Button>
94
+ </Modal.Actions>
95
+ </Modal>
96
+ );
97
+
98
+ LoginModal.defaultProps = {
99
+ placeholder: i18n.t('LoginModal.email')
100
+ };
101
+
102
+ export default LoginModal;
@@ -0,0 +1,48 @@
1
+ .masonryGrid {
2
+ display: flex;
3
+ }
4
+
5
+ .masonryGrid > .column {
6
+ flex: 1
7
+ }
8
+
9
+ .masonryGrid > .column > .itemContainer {
10
+ display: flex;
11
+ justify-content: center;
12
+ align-items: center;
13
+ background: #efefef;
14
+ }
15
+
16
+ .masonryGrid .placeholderWrapper {
17
+ height: 100%;
18
+ }
19
+
20
+ .masonryGrid > .column > .itemContainer > .item {
21
+ position: relative;
22
+ width: 100%;
23
+ z-index: 500;
24
+ }
25
+
26
+ .masonryGrid > .column > .itemContainer > .item .favoriteContainer {
27
+ position: absolute;
28
+ bottom: 1em;
29
+ right: 1em;
30
+ z-index: 600;
31
+ }
32
+
33
+ .masonryGrid > .column > .itemContainer > .item .extraContainer {
34
+ position: absolute;
35
+ bottom: 1em;
36
+ left: 1em;
37
+ }
38
+
39
+ .masonryGrid > .column > .itemContainer > .item .image {
40
+ height: 100%;
41
+ object-fit: cover;
42
+ width: 100%;
43
+ }
44
+
45
+ .masonryGrid > .column > .itemContainer > .item .ui.transition {
46
+ display: flex !important;
47
+ z-index: 500;
48
+ }