@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,297 @@
1
+ // @flow
2
+
3
+ import React, {
4
+ useCallback,
5
+ useEffect,
6
+ useState,
7
+ type ComponentType,
8
+ type Element
9
+ } from 'react';
10
+ import {
11
+ Button,
12
+ Form,
13
+ Grid,
14
+ Icon,
15
+ Modal,
16
+ Table
17
+ } from 'semantic-ui-react';
18
+ import _ from 'underscore';
19
+ import SelectizeHeader from './SelectizeHeader';
20
+ import i18n from '../i18n/i18n';
21
+ import useDataList from './DataList';
22
+ import './Selectize.css';
23
+ import useList, { type Props as ListProps } from './List';
24
+
25
+ type Props = {
26
+ centered?: boolean,
27
+ collectionName: string,
28
+ filters?: {
29
+ component: ComponentType<any>,
30
+ defaults: any,
31
+ props: any,
32
+ onChange?: (filter: any) => Promise<any>
33
+ },
34
+ modal?: {
35
+ onSave: (item: any) => Promise<any>
36
+ },
37
+ multiple?: boolean,
38
+ onClose: () => void,
39
+ onLoad: (params: any) => Promise<any>,
40
+ onSave: (items: any) => void,
41
+ selectedItems: Array<any>,
42
+ title: string
43
+ };
44
+
45
+ type GridProps = ListProps & {
46
+ isSelected: (item: any) => boolean,
47
+ onInit: () => void,
48
+ onItemSelection: (item: any) => void,
49
+ onSelect: (item: any) => void,
50
+ renderHeader: (params: any) => Element<any>,
51
+ renderItem: (item: any) => Element<any>,
52
+ selectedItem: any,
53
+ selectedItems: Array<any>
54
+ };
55
+
56
+ const SelectizeGrid = useDataList(useList((props: GridProps) => {
57
+ useEffect(() => {
58
+ if (props.onInit) {
59
+ props.onInit();
60
+ }
61
+ }, []);
62
+
63
+ /**
64
+ * Renders a checkmark of the passed item is selected.
65
+ *
66
+ * @type {(function(*): (null|*))|*}
67
+ */
68
+ const renderCheckmark = useCallback((item: any) => {
69
+ if (!props.isSelected(item)) {
70
+ return null;
71
+ }
72
+
73
+ return (
74
+ <Icon
75
+ color='green'
76
+ name='check'
77
+ />
78
+ );
79
+ }, [props.selectedItems]);
80
+
81
+ /**
82
+ * Renders the selectize header component.
83
+ *
84
+ * @returns {JSX.Element|*}
85
+ */
86
+ const renderHeader = () => {
87
+ if (props.renderHeader) {
88
+ return props.renderHeader({
89
+ onItemClick: props.onItemSelection.bind(this),
90
+ selectedItem: props.selectedItem,
91
+ selectedItems: props.selectedItems,
92
+ });
93
+ }
94
+
95
+ return (
96
+ <SelectizeHeader
97
+ isSelected={(item) => props.selectedItem === item}
98
+ items={props.selectedItems}
99
+ onItemClick={props.onItemSelection.bind(this)}
100
+ renderItem={props.renderItem.bind(this)}
101
+ />
102
+ );
103
+ };
104
+
105
+ /**
106
+ * Renders the list of items as a table.
107
+ *
108
+ * @type {(function(): (null|*))|*}
109
+ */
110
+ const renderItems = useCallback(() => {
111
+ if (_.isEmpty(props.items)) {
112
+ return null;
113
+ }
114
+
115
+ return (
116
+ <Table
117
+ basic
118
+ padded
119
+ selectable
120
+ >
121
+ <Table.Body>
122
+ { _.map(props.items, (item, index) => (
123
+ <Table.Row
124
+ key={index}
125
+ onClick={props.onSelect.bind(this, item)}
126
+ >
127
+ <Table.Cell>{ props.renderItem(item) }</Table.Cell>
128
+ <Table.Cell>
129
+ { renderCheckmark(item) }
130
+ </Table.Cell>
131
+ </Table.Row>
132
+ ))}
133
+ </Table.Body>
134
+ </Table>
135
+ );
136
+ }, [renderCheckmark, props.items, props.onSelect, props.renderItem]);
137
+
138
+ /**
139
+ * Renders the empty list component.
140
+ *
141
+ * @type {(function(): (null|*))|*}
142
+ */
143
+ const renderEmpty = useCallback(() => {
144
+ if ((props.items && props.items.length) || props.loading) {
145
+ return null;
146
+ }
147
+
148
+ return (
149
+ <Grid
150
+ className='empty'
151
+ padded='vertically'
152
+ >
153
+ <Grid.Column
154
+ textAlign='center'
155
+ >
156
+ <Grid.Row>
157
+ <Icon
158
+ name='search'
159
+ size='huge'
160
+ />
161
+ </Grid.Row>
162
+ <Grid.Row>
163
+ { i18n.t('Selectize.noRecords') }
164
+ </Grid.Row>
165
+ </Grid.Column>
166
+ </Grid>
167
+ );
168
+ }, [props.items, props.loading]);
169
+
170
+ return (
171
+ <Grid>
172
+ <Grid.Column
173
+ textAlign='center'
174
+ >
175
+ { renderHeader() }
176
+ { renderItems() }
177
+ { renderEmpty() }
178
+ </Grid.Column>
179
+ </Grid>
180
+ );
181
+ }));
182
+
183
+ const Selectize = (props: Props) => {
184
+ const [selectedItem, setSelectedItem] = useState();
185
+ const [selectedItems, setSelectedItems] = useState(props.selectedItems || []);
186
+
187
+ /**
188
+ * Returns true if the passed item is selected.
189
+ *
190
+ * @type {function(*): boolean}
191
+ */
192
+ const isSelected = useCallback((item) => !!_.findWhere(selectedItems, { id: item.id }, [selectedItems]));
193
+
194
+ /**
195
+ * If the passed item is selected, deselect the item. If we're not allowing multiple select, replace the selected
196
+ * items with the passed item. Otherwise, append the passed item to the list of selected items.
197
+ *
198
+ * @type {(function(*=): void)|*}
199
+ */
200
+ const onSelect = useCallback((item) => {
201
+ if (isSelected(item)) {
202
+ setSelectedItems((prevItems) => _.filter(prevItems, (i) => i.id !== item.id));
203
+ } else if (!props.multiple) {
204
+ setSelectedItems([item]);
205
+ } else {
206
+ setSelectedItems((prevItems) => [
207
+ ...prevItems,
208
+ item
209
+ ]);
210
+ }
211
+ }, [isSelected, props.multiple]);
212
+
213
+ /**
214
+ * Selects or deselects the single passed item.
215
+ *
216
+ * @type {(function(*=): void)|*}
217
+ */
218
+ const onItemSelection = useCallback((item) => {
219
+ if (selectedItem === item) {
220
+ setSelectedItem(null);
221
+ onSelect(item);
222
+ } else {
223
+ setSelectedItem(item);
224
+ }
225
+ }, [selectedItem, onSelect]);
226
+
227
+ /**
228
+ * Returns the promise from the modal onSave prop, if provided. Otherwise returns a resolved promise.
229
+ *
230
+ * @type {(function(*=): (*))|*}
231
+ */
232
+ const onSave = useCallback((item) => {
233
+ if (props.modal && props.modal.onSave) {
234
+ return props.modal.onSave(item).then((saved) => onSelect(saved));
235
+ }
236
+
237
+ return Promise.resolve();
238
+ }, [onSelect, props.modal]);
239
+
240
+ return (
241
+ <Modal
242
+ as={Form}
243
+ centered={props.centered}
244
+ className='selectize'
245
+ open
246
+ noValidate
247
+ size='small'
248
+ >
249
+ <Modal.Header
250
+ content={props.title}
251
+ />
252
+ <Modal.Content>
253
+ <SelectizeGrid
254
+ {...props}
255
+ actions={[]}
256
+ isSelected={isSelected}
257
+ onDelete={() => Promise.resolve()}
258
+ onDeleteAll={() => Promise.resolve()}
259
+ onItemSelection={onItemSelection}
260
+ onSave={onSave}
261
+ onSelect={onSelect}
262
+ selectedItem={selectedItem}
263
+ selectedItems={selectedItems}
264
+ />
265
+ </Modal.Content>
266
+ <Modal.Actions>
267
+ <Button
268
+ onClick={props.onSave.bind(this, selectedItems)}
269
+ primary
270
+ size='medium'
271
+ type='submit'
272
+ >
273
+ { i18n.t('Common.buttons.save') }
274
+ </Button>
275
+ <Button
276
+ inverted
277
+ onClick={props.onClose.bind(this)}
278
+ primary
279
+ size='medium'
280
+ type='button'
281
+ >
282
+ { i18n.t('Common.buttons.cancel') }
283
+ </Button>
284
+ </Modal.Actions>
285
+ </Modal>
286
+ );
287
+ };
288
+
289
+ Selectize.defaultProps = {
290
+ centered: false,
291
+ modal: undefined,
292
+ multiple: true,
293
+ searchable: true,
294
+ selectedItems: []
295
+ };
296
+
297
+ export default Selectize;
@@ -0,0 +1,3 @@
1
+ .selectize-header .ui.button.selected {
2
+ margin: 3px;
3
+ }
@@ -0,0 +1,40 @@
1
+ // @flow
2
+
3
+ import React, { type Element } from 'react';
4
+ import { Button, Segment } from 'semantic-ui-react';
5
+ import _ from 'underscore';
6
+ import './SelectizeHeader.css';
7
+
8
+ type Props = {
9
+ isSelected: (item: any) => boolean,
10
+ items: Array<any>,
11
+ onItemClick: (item: any) => void,
12
+ renderItem: (item: any) => string | Element<any>
13
+ };
14
+
15
+ const SelectizeHeader = (props: Props) => {
16
+ if (!(props.items && props.items.length)) {
17
+ return null;
18
+ }
19
+
20
+ return (
21
+ <Segment
22
+ className='selectize-header'
23
+ textAlign='left'
24
+ >
25
+ { _.map(props.items, (item, index) => (
26
+ <Button
27
+ className='selected'
28
+ content={props.renderItem(item)}
29
+ icon={props.isSelected(item) ? 'times circle outline' : undefined}
30
+ key={index}
31
+ onClick={props.onItemClick.bind(this, item)}
32
+ primary
33
+ size='small'
34
+ />
35
+ ))}
36
+ </Segment>
37
+ );
38
+ };
39
+
40
+ export default SelectizeHeader;
@@ -0,0 +1,14 @@
1
+ .tabbed-modal.ui .modal-header .ui.menu {
2
+ max-width: 100%;
3
+ overflow: auto;
4
+ padding: 5px 0px 5px 0px;
5
+ }
6
+
7
+ .tabbed-modal.ui .modal-header.inline-header .ui.menu {
8
+ display: inline-flex;
9
+ margin-left: 30px;
10
+ }
11
+
12
+ .tabbed-modal.ui .modal-header.inline-header .ui.header {
13
+ display: inline;
14
+ }
@@ -0,0 +1,165 @@
1
+ // @flow
2
+
3
+ import { Element } from '@performant-software/shared-components';
4
+ import React, { Component, type Node } from 'react';
5
+ import { Header, Menu, Modal } from 'semantic-ui-react';
6
+ import _ from 'underscore';
7
+ import './TabbedModal.css';
8
+
9
+ type Props = {
10
+ children: Node,
11
+ className?: string,
12
+ header: string,
13
+ inlineTabs?: boolean,
14
+ renderHeader?: () => Node
15
+ };
16
+
17
+ type State = {
18
+ tab: any
19
+ };
20
+
21
+ class TabbedModal extends Component<Props, State> {
22
+ static defaultProps: any;
23
+ static Tab: any;
24
+
25
+ /**
26
+ * Constructs a new TabbedModal component.
27
+ *
28
+ * @param props
29
+ */
30
+ constructor(props: Props) {
31
+ super(props);
32
+
33
+ this.state = {
34
+ tab: null
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Parses the child components and sets the actions and tabs list on the state. The current tab is set to the first
40
+ * child in the list of tabs.
41
+ */
42
+ componentDidMount() {
43
+ const tab = _.first(Element.findByType(this.props.children, TabbedModal.Tab));
44
+ this.setState({ tab: tab.props.name });
45
+ }
46
+
47
+ /**
48
+ * Returns the header classes.
49
+ *
50
+ * @returns {string}
51
+ */
52
+ getHeaderClasses() {
53
+ const classes = ['modal-header'];
54
+
55
+ if (this.props.inlineTabs) {
56
+ classes.push('inline-header');
57
+ }
58
+
59
+ return classes.join(' ');
60
+ }
61
+
62
+ /**
63
+ * Returns the modal classes.
64
+ *
65
+ * @returns {string}
66
+ */
67
+ getModalClasses() {
68
+ const classes = ['tabbed-modal'];
69
+
70
+ if (this.props.className) {
71
+ classes.push(this.props.className);
72
+ }
73
+
74
+ return classes.join(' ');
75
+ }
76
+
77
+ /**
78
+ * Renders the TabbedModal component.
79
+ *
80
+ * @returns {*}
81
+ */
82
+ render() {
83
+ const tabs = Element.findByType(this.props.children, TabbedModal.Tab);
84
+ const tab = _.find(tabs, (t) => t.props.name === this.state.tab);
85
+
86
+ return (
87
+ <Modal
88
+ className={this.getModalClasses()}
89
+ {..._.omit(this.props, 'header', 'renderHeader', 'inlineTabs', 'className')}
90
+ >
91
+ <Modal.Header
92
+ className={this.getHeaderClasses()}
93
+ >
94
+ { this.renderHeader() }
95
+ <Menu
96
+ float='right'
97
+ secondary
98
+ >
99
+ { _.map(Element.findByType(this.props.children, TabbedModal.Tab), this.renderTab.bind(this)) }
100
+ </Menu>
101
+ </Modal.Header>
102
+ <Modal.Content>
103
+ { tab && (
104
+ <div
105
+ key={tab.props.name}
106
+ >
107
+ { tab.props.children }
108
+ </div>
109
+ )}
110
+ </Modal.Content>
111
+ { Element.findByType(this.props.children, Modal.Actions) }
112
+ </Modal>
113
+ );
114
+ }
115
+
116
+ /**
117
+ * Renders the header.
118
+ *
119
+ * @returns {*}
120
+ */
121
+ renderHeader() {
122
+ if (this.props.renderHeader) {
123
+ return this.props.renderHeader();
124
+ }
125
+
126
+ return (
127
+ <Header content={this.props.header} />
128
+ );
129
+ }
130
+
131
+ /**
132
+ * Renders the passed tab.
133
+ *
134
+ * @param tab
135
+ *
136
+ * @returns {*}
137
+ */
138
+ renderTab(tab: TabbedModal.Tab) {
139
+ const { name } = tab.props;
140
+
141
+ return (
142
+ <Menu.Item
143
+ active={this.state.tab === name}
144
+ key={name}
145
+ name={name}
146
+ onClick={() => this.setState({ tab: name })}
147
+ >
148
+ { name }
149
+ </Menu.Item>
150
+ );
151
+ }
152
+ }
153
+
154
+ const Tab = (props) => props.children;
155
+ Tab.displayName = 'Tab';
156
+
157
+ TabbedModal.Tab = Tab;
158
+
159
+ TabbedModal.defaultProps = {
160
+ className: undefined,
161
+ inlineTabs: true,
162
+ renderHeader: undefined
163
+ };
164
+
165
+ export default TabbedModal;
File without changes
@@ -0,0 +1,35 @@
1
+ // @flow
2
+
3
+ import React from 'react';
4
+ import { Menu } from 'semantic-ui-react';
5
+ import _ from 'underscore';
6
+
7
+ type Tab = {
8
+ active: boolean,
9
+ key: string,
10
+ label: string,
11
+ visible: boolean
12
+ };
13
+
14
+ type Props = {
15
+ onTabClick: (tab: Tab) => void,
16
+ tabs: Array<Tab>
17
+ }
18
+
19
+ const TabsMenu = (props: Props) => (
20
+ <Menu
21
+ pointing
22
+ secondary
23
+ >
24
+ { _.map(_.where(props.tabs, { visible: true }), (tab) => (
25
+ <Menu.Item
26
+ active={tab.active}
27
+ key={tab.key}
28
+ name={tab.label}
29
+ onClick={props.onTabClick.bind(this, tab)}
30
+ />
31
+ ))}
32
+ </Menu>
33
+ );
34
+
35
+ export default TabsMenu;
File without changes
@@ -0,0 +1,43 @@
1
+ // @flow
2
+
3
+ import React from 'react';
4
+ import { Icon, Label } from 'semantic-ui-react';
5
+ import _ from 'underscore';
6
+ import './TagsList.css';
7
+
8
+ type Props = {
9
+ config?: {
10
+ [key: string]: {
11
+ background: string,
12
+ color: string
13
+ }
14
+ },
15
+ onDelete?: (tag: string) => void,
16
+ tags: Array<string>
17
+ };
18
+
19
+ const TagsList = (props: Props) => (
20
+ <div
21
+ className='tags-list'
22
+ >
23
+ { _.map(props.tags || [], (tag) => (
24
+ <Label
25
+ style={{
26
+ backgroundColor: props.config && props.config[tag] && props.config[tag].background,
27
+ color: props.config && props.config[tag] && props.config[tag].color,
28
+ marginRight: '10px'
29
+ }}
30
+ >
31
+ { tag }
32
+ { props.onDelete && (
33
+ <Icon
34
+ name='delete'
35
+ onClick={props.onDelete.bind(this, tag)}
36
+ />
37
+ )}
38
+ </Label>
39
+ ))}
40
+ </div>
41
+ );
42
+
43
+ export default TagsList;
File without changes
@@ -0,0 +1,47 @@
1
+ // @flow
2
+
3
+ import React, { useEffect, useState } from 'react';
4
+ import { Image, Loader } from 'semantic-ui-react';
5
+ import LazyImage from './LazyImage';
6
+
7
+ type Props = {
8
+ src: ?string,
9
+ style?: any
10
+ };
11
+
12
+ const Thumbnail = (props: Props) => {
13
+ const [loading, setLoading] = useState(true);
14
+
15
+ useEffect(() => {
16
+ if (!loading) {
17
+ setLoading(true);
18
+ }
19
+ }, [props.src]);
20
+
21
+ return (
22
+ <div>
23
+ <Loader
24
+ active={props.src && loading}
25
+ />
26
+ { props.src && (
27
+ <Image
28
+ {...props}
29
+ onLoad={() => setLoading(false)}
30
+ style={{
31
+ ...props.style,
32
+ visibility: loading ? 'hidden' : 'visible'
33
+ }}
34
+ />
35
+ )}
36
+ { !props.src && (
37
+ <LazyImage />
38
+ )}
39
+ </div>
40
+ );
41
+ };
42
+
43
+ Thumbnail.defaultProps = {
44
+ style: {}
45
+ };
46
+
47
+ export default Thumbnail;
@@ -0,0 +1,9 @@
1
+ .toaster.ui.message {
2
+ position: fixed;
3
+ top: 0;
4
+ right: 0;
5
+ left: 0;
6
+ margin: auto;
7
+ width: 30%;
8
+ z-index: 999;
9
+ }