@performant-software/semantic-components 1.0.10-beta.0 → 1.0.10-beta.2
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/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/main.css +61 -51
- package/package.json +2 -2
- package/src/components/DataList.js +4 -2
- package/src/components/FileUploadModal.js +19 -0
- package/src/components/FilterLabels.css +3 -0
- package/src/components/FilterLabels.js +64 -0
- package/src/components/List.css +8 -0
- package/src/components/List.js +64 -31
- package/src/components/ListFilters.js +41 -36
- package/src/i18n/en.json +5 -0
- package/src/index.js +1 -1
- package/types/components/DataList.js.flow +4 -2
- package/types/components/FileUploadModal.js.flow +19 -0
- package/types/components/FilterLabels.js.flow +64 -0
- package/types/components/List.js.flow +64 -31
- package/types/components/ListFilters.js.flow +41 -36
- package/types/index.js.flow +1 -1
package/build/main.css
CHANGED
|
@@ -58,6 +58,61 @@
|
|
|
58
58
|
margin-right: 25px;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
.association-dropdown {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
flex-wrap: wrap;
|
|
65
|
+
width: 100%;
|
|
66
|
+
}
|
|
67
|
+
.association-dropdown .buttons {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-wrap: wrap;
|
|
70
|
+
flex-grow: 0.1;
|
|
71
|
+
}
|
|
72
|
+
.association-dropdown .dropdown-container {
|
|
73
|
+
flex-grow: 1;
|
|
74
|
+
}
|
|
75
|
+
.association-dropdown .inline-dropdown {
|
|
76
|
+
width: 100%;
|
|
77
|
+
}
|
|
78
|
+
.association-dropdown .ui.search.selection.dropdown > input.search {
|
|
79
|
+
width: 100%;
|
|
80
|
+
}
|
|
81
|
+
.association-dropdown div[role="listbox"] div[role="option"] span.text {
|
|
82
|
+
display: block;
|
|
83
|
+
}
|
|
84
|
+
.association-dropdown div[role="alert"] {
|
|
85
|
+
max-width: 100%;
|
|
86
|
+
}
|
|
87
|
+
.ui.form.field .ui.input .association-dropdown input.dropdown-search-input {
|
|
88
|
+
width: 100%;
|
|
89
|
+
}
|
|
90
|
+
.ui.form .wide.field .ui.input .association-dropdown input.dropdown-search-input {
|
|
91
|
+
width: 100%;
|
|
92
|
+
}
|
|
93
|
+
.ui.dropdown:not(.button) > .default.text {
|
|
94
|
+
color: rgba(95, 95, 95, 0.86);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.date-input.ui.icon.input > i.icon.right {
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
pointer-events: inherit;
|
|
100
|
+
left: auto;
|
|
101
|
+
right: 1px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.fuzzy-date-modal .accuracy-container .ui.radio.checkbox {
|
|
105
|
+
padding-right: 10px;
|
|
106
|
+
}
|
|
107
|
+
.fuzzy-date-modal .button-container {
|
|
108
|
+
margin-left: 8px;
|
|
109
|
+
margin-top: 23px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.filter-labels {
|
|
113
|
+
margin-top: 1em;
|
|
114
|
+
}
|
|
115
|
+
|
|
61
116
|
.header .flex-end-menu {
|
|
62
117
|
justify-content: flex-end;
|
|
63
118
|
}
|
|
@@ -65,6 +120,12 @@
|
|
|
65
120
|
.header .per-page-menu {
|
|
66
121
|
flex-shrink: 0;
|
|
67
122
|
}
|
|
123
|
+
.header .ui.grid.filter-labels > .row:first-child {
|
|
124
|
+
padding-bottom: 0px;
|
|
125
|
+
}
|
|
126
|
+
.header .ui.grid > .row:nth-child(2) {
|
|
127
|
+
padding-top: 0px;
|
|
128
|
+
}
|
|
68
129
|
|
|
69
130
|
.accordion-data-list {
|
|
70
131
|
margin-bottom: 1em;
|
|
@@ -110,42 +171,6 @@
|
|
|
110
171
|
padding-right: 15px;
|
|
111
172
|
}
|
|
112
173
|
|
|
113
|
-
.association-dropdown {
|
|
114
|
-
display: flex;
|
|
115
|
-
align-items: center;
|
|
116
|
-
flex-wrap: wrap;
|
|
117
|
-
width: 100%;
|
|
118
|
-
}
|
|
119
|
-
.association-dropdown .buttons {
|
|
120
|
-
display: flex;
|
|
121
|
-
flex-wrap: wrap;
|
|
122
|
-
flex-grow: 0.1;
|
|
123
|
-
}
|
|
124
|
-
.association-dropdown .dropdown-container {
|
|
125
|
-
flex-grow: 1;
|
|
126
|
-
}
|
|
127
|
-
.association-dropdown .inline-dropdown {
|
|
128
|
-
width: 100%;
|
|
129
|
-
}
|
|
130
|
-
.association-dropdown .ui.search.selection.dropdown > input.search {
|
|
131
|
-
width: 100%;
|
|
132
|
-
}
|
|
133
|
-
.association-dropdown div[role="listbox"] div[role="option"] span.text {
|
|
134
|
-
display: block;
|
|
135
|
-
}
|
|
136
|
-
.association-dropdown div[role="alert"] {
|
|
137
|
-
max-width: 100%;
|
|
138
|
-
}
|
|
139
|
-
.ui.form.field .ui.input .association-dropdown input.dropdown-search-input {
|
|
140
|
-
width: 100%;
|
|
141
|
-
}
|
|
142
|
-
.ui.form .wide.field .ui.input .association-dropdown input.dropdown-search-input {
|
|
143
|
-
width: 100%;
|
|
144
|
-
}
|
|
145
|
-
.ui.dropdown:not(.button) > .default.text {
|
|
146
|
-
color: rgba(95, 95, 95, 0.86);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
174
|
.audio-player.ui.modal > .content > audio {
|
|
150
175
|
width: 100%;
|
|
151
176
|
}
|
|
@@ -252,13 +277,6 @@
|
|
|
252
277
|
margin-top: 20px;
|
|
253
278
|
}
|
|
254
279
|
|
|
255
|
-
.date-input.ui.icon.input > i.icon.right {
|
|
256
|
-
cursor: pointer;
|
|
257
|
-
pointer-events: inherit;
|
|
258
|
-
left: auto;
|
|
259
|
-
right: 1px;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
280
|
.react-calendar {
|
|
263
281
|
width: 350px;
|
|
264
282
|
max-width: 100%;
|
|
@@ -504,14 +522,6 @@ div.react-calendar {
|
|
|
504
522
|
margin-bottom: 0;
|
|
505
523
|
}
|
|
506
524
|
|
|
507
|
-
.fuzzy-date-modal .accuracy-container .ui.radio.checkbox {
|
|
508
|
-
padding-right: 10px;
|
|
509
|
-
}
|
|
510
|
-
.fuzzy-date-modal .button-container {
|
|
511
|
-
margin-left: 8px;
|
|
512
|
-
margin-top: 23px;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
525
|
.horizontal-cards .ui.cards {
|
|
516
526
|
display: flex;
|
|
517
527
|
flex-direction: row;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@performant-software/semantic-components",
|
|
3
|
-
"version": "1.0.10-beta.
|
|
3
|
+
"version": "1.0.10-beta.2",
|
|
4
4
|
"description": "A package of shared components based on the Semantic UI Framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./build/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "webpack --mode production && flow-copy-source -v src types"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@performant-software/shared-components": "^1.0.10-beta.
|
|
15
|
+
"@performant-software/shared-components": "^1.0.10-beta.2",
|
|
16
16
|
"@react-google-maps/api": "^2.8.1",
|
|
17
17
|
"axios": "^0.26.1",
|
|
18
18
|
"i18next": "^19.4.4",
|
|
@@ -51,7 +51,8 @@ type Props = {
|
|
|
51
51
|
component: ComponentType<any>,
|
|
52
52
|
defaults?: any,
|
|
53
53
|
props?: any,
|
|
54
|
-
onChange?: (filter: any) => Promise<any
|
|
54
|
+
onChange?: (filter: any) => Promise<any>,
|
|
55
|
+
showLabels?: boolean
|
|
55
56
|
},
|
|
56
57
|
|
|
57
58
|
/**
|
|
@@ -530,7 +531,7 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
|
|
|
530
531
|
*/
|
|
531
532
|
render() {
|
|
532
533
|
const { filters = {} } = this.props;
|
|
533
|
-
const { component, props } = filters;
|
|
534
|
+
const { component, props, showLabels } = filters;
|
|
534
535
|
|
|
535
536
|
return (
|
|
536
537
|
<>
|
|
@@ -541,6 +542,7 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
|
|
|
541
542
|
active: this.isFilterActive(),
|
|
542
543
|
component,
|
|
543
544
|
onChange: this.onFilterChange.bind(this),
|
|
545
|
+
showLabels,
|
|
544
546
|
props: {
|
|
545
547
|
...props,
|
|
546
548
|
onCreateFilter: this.onCreateFilter.bind(this),
|
|
@@ -28,6 +28,11 @@ type Props = {
|
|
|
28
28
|
*/
|
|
29
29
|
closeOnComplete?: boolean,
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Component to render at top of modal.
|
|
33
|
+
*/
|
|
34
|
+
headerComponent?: ComponentType<any>,
|
|
35
|
+
|
|
31
36
|
/**
|
|
32
37
|
* Component to render within the modal.
|
|
33
38
|
*/
|
|
@@ -343,6 +348,13 @@ const FileUploadModal: ComponentType<any> = (props: Props) => {
|
|
|
343
348
|
);
|
|
344
349
|
}, [statuses, props.strategy]);
|
|
345
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Memoization and case correction for the <code>headerComponent</code> prop.
|
|
353
|
+
*
|
|
354
|
+
* @type {React$AbstractComponent<*, *>}
|
|
355
|
+
*/
|
|
356
|
+
const HeaderComponent = useMemo(() => props.headerComponent, [props.headerComponent]);
|
|
357
|
+
|
|
346
358
|
/**
|
|
347
359
|
* Memoization and case correction for the <code>itemComponent</code> prop.
|
|
348
360
|
*
|
|
@@ -404,6 +416,13 @@ const FileUploadModal: ComponentType<any> = (props: Props) => {
|
|
|
404
416
|
<FileUpload
|
|
405
417
|
onFilesAdded={onAddFiles}
|
|
406
418
|
/>
|
|
419
|
+
{ HeaderComponent && (
|
|
420
|
+
<HeaderComponent
|
|
421
|
+
items={items}
|
|
422
|
+
onSetItems={setItems}
|
|
423
|
+
uploading={uploading}
|
|
424
|
+
/>
|
|
425
|
+
)}
|
|
407
426
|
<Item.Group
|
|
408
427
|
as={Form}
|
|
409
428
|
divided
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React, { useCallback } from 'react';
|
|
4
|
+
import { Button, Label } from 'semantic-ui-react';
|
|
5
|
+
import _ from 'underscore';
|
|
6
|
+
import i18n from '../i18n/i18n';
|
|
7
|
+
import { FilterOperatorOptions, type Filter } from './ListFilters';
|
|
8
|
+
import './FilterLabels.css';
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
filters: Array<Filter>,
|
|
12
|
+
onClear?: () => void,
|
|
13
|
+
onClick: (filter: Filter) => void
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const FilterLabels = (props: Props) => {
|
|
17
|
+
/**
|
|
18
|
+
* Returns the content string for the passed filter.
|
|
19
|
+
*
|
|
20
|
+
* @type {function(*): string}
|
|
21
|
+
*/
|
|
22
|
+
const getContent = useCallback((filter) => {
|
|
23
|
+
// Content will always container the label
|
|
24
|
+
const content = [filter.label];
|
|
25
|
+
|
|
26
|
+
// If an option can be found, use the text from the operator
|
|
27
|
+
const option = _.findWhere(FilterOperatorOptions, { key: filter.operator });
|
|
28
|
+
if (option) {
|
|
29
|
+
content.push(option.text);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Append the value in quotes, if present
|
|
33
|
+
if (filter.value) {
|
|
34
|
+
content.push(`"${filter.value}"`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return content.join(' ');
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Label.Group
|
|
42
|
+
className='filter-labels'
|
|
43
|
+
>
|
|
44
|
+
{ _.map(props.filters, (filter) => (
|
|
45
|
+
<Label
|
|
46
|
+
basic
|
|
47
|
+
content={getContent(filter)}
|
|
48
|
+
onRemove={() => props.onClick(filter)}
|
|
49
|
+
/>
|
|
50
|
+
))}
|
|
51
|
+
{ props.onClear && (
|
|
52
|
+
<Label
|
|
53
|
+
as={Button}
|
|
54
|
+
color='red'
|
|
55
|
+
content={i18n.t('FilterLabels.buttons.clear')}
|
|
56
|
+
icon='times'
|
|
57
|
+
onClick={props.onClear}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
</Label.Group>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default FilterLabels;
|
package/src/components/List.css
CHANGED
package/src/components/List.js
CHANGED
|
@@ -15,6 +15,7 @@ import _ from 'underscore';
|
|
|
15
15
|
import i18n from '../i18n/i18n';
|
|
16
16
|
import DropdownButton from './DropdownButton';
|
|
17
17
|
import EditModal from './EditModal';
|
|
18
|
+
import FilterLabels from './FilterLabels';
|
|
18
19
|
import './List.css';
|
|
19
20
|
|
|
20
21
|
type Action = {
|
|
@@ -105,7 +106,8 @@ type Props = {
|
|
|
105
106
|
component: Component<{}>,
|
|
106
107
|
props?: any,
|
|
107
108
|
state?: any,
|
|
108
|
-
onChange: (params: any) => Promise<any
|
|
109
|
+
onChange: (params: any) => Promise<any>,
|
|
110
|
+
showLabels?: boolean
|
|
109
111
|
},
|
|
110
112
|
|
|
111
113
|
/**
|
|
@@ -383,6 +385,18 @@ const useList = (WrappedComponent: ComponentType<any>) => (
|
|
|
383
385
|
this.setState({ modalFilter: true });
|
|
384
386
|
}
|
|
385
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Calls the filter onChange function with the passed filter removed.
|
|
390
|
+
*
|
|
391
|
+
* @param filter
|
|
392
|
+
*
|
|
393
|
+
* @returns {*}
|
|
394
|
+
*/
|
|
395
|
+
onRemoveFilter(filter) {
|
|
396
|
+
const { onChange, props: { item } } = this.props.filters;
|
|
397
|
+
return onChange({ filters: _.filter(item.filters, (f) => f.uid !== filter.uid) });
|
|
398
|
+
}
|
|
399
|
+
|
|
386
400
|
/**
|
|
387
401
|
* Saves the passed item and closes the add/edit modal.
|
|
388
402
|
*
|
|
@@ -754,6 +768,8 @@ const useList = (WrappedComponent: ComponentType<any>) => (
|
|
|
754
768
|
renderHeader = true;
|
|
755
769
|
}
|
|
756
770
|
|
|
771
|
+
const hasLabels = filters && filters.showLabels && !_.isEmpty(filters.props.item.filters);
|
|
772
|
+
|
|
757
773
|
if (!renderHeader) {
|
|
758
774
|
return null;
|
|
759
775
|
}
|
|
@@ -763,41 +779,58 @@ const useList = (WrappedComponent: ComponentType<any>) => (
|
|
|
763
779
|
className='header'
|
|
764
780
|
>
|
|
765
781
|
<Grid
|
|
766
|
-
|
|
767
|
-
verticalAlign='
|
|
782
|
+
className={hasLabels ? 'filter-labels' : undefined}
|
|
783
|
+
verticalAlign='top'
|
|
768
784
|
>
|
|
769
|
-
<Grid.
|
|
770
|
-
|
|
785
|
+
<Grid.Row
|
|
786
|
+
columns={2}
|
|
771
787
|
>
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
<
|
|
778
|
-
|
|
779
|
-
borderless
|
|
780
|
-
secondary
|
|
781
|
-
className='flex-end-menu'
|
|
788
|
+
<Grid.Column
|
|
789
|
+
textAlign='left'
|
|
790
|
+
>
|
|
791
|
+
{ _.map(buttons, this.renderButton.bind(this)) }
|
|
792
|
+
</Grid.Column>
|
|
793
|
+
<Grid.Column
|
|
794
|
+
textAlign='right'
|
|
782
795
|
>
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
796
|
+
<Menu
|
|
797
|
+
compact
|
|
798
|
+
borderless
|
|
799
|
+
secondary
|
|
800
|
+
className='flex-end-menu'
|
|
801
|
+
>
|
|
802
|
+
{ renderListHeader && (
|
|
803
|
+
<Menu.Menu className='list-header-menu'>
|
|
804
|
+
{ renderListHeader() }
|
|
805
|
+
</Menu.Menu>
|
|
806
|
+
)}
|
|
807
|
+
<Menu.Menu>
|
|
808
|
+
{ filters && this.renderFilterButton() }
|
|
786
809
|
</Menu.Menu>
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
<Menu.Menu
|
|
793
|
-
{
|
|
810
|
+
{ perPageOptions && (
|
|
811
|
+
<Menu.Menu className='per-page-menu'>
|
|
812
|
+
{ this.renderPerPage() }
|
|
813
|
+
</Menu.Menu>
|
|
814
|
+
)}
|
|
815
|
+
<Menu.Menu>
|
|
816
|
+
{ renderSearch && renderSearch() }
|
|
794
817
|
</Menu.Menu>
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
818
|
+
</Menu>
|
|
819
|
+
</Grid.Column>
|
|
820
|
+
</Grid.Row>
|
|
821
|
+
{ hasLabels && (
|
|
822
|
+
<Grid.Row
|
|
823
|
+
columns={1}
|
|
824
|
+
>
|
|
825
|
+
<Grid.Column>
|
|
826
|
+
<FilterLabels
|
|
827
|
+
filters={filters.props.item.filters}
|
|
828
|
+
onClear={() => filters.onChange({ filters: [] })}
|
|
829
|
+
onClick={(filter) => this.onRemoveFilter(filter)}
|
|
830
|
+
/>
|
|
831
|
+
</Grid.Column>
|
|
832
|
+
</Grid.Row>
|
|
833
|
+
)}
|
|
801
834
|
</Grid>
|
|
802
835
|
</div>
|
|
803
836
|
);
|
|
@@ -75,6 +75,40 @@ const FilterOperators = {
|
|
|
75
75
|
lessThan: 'less_than'
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
+
const FilterOperatorOptions = [{
|
|
79
|
+
key: FilterOperators.equal,
|
|
80
|
+
value: FilterOperators.equal,
|
|
81
|
+
text: i18n.t('ListFilters.operators.equal')
|
|
82
|
+
}, {
|
|
83
|
+
key: FilterOperators.notEqual,
|
|
84
|
+
value: FilterOperators.notEqual,
|
|
85
|
+
text: i18n.t('ListFilters.operators.notEqual')
|
|
86
|
+
}, {
|
|
87
|
+
key: FilterOperators.contain,
|
|
88
|
+
value: FilterOperators.contain,
|
|
89
|
+
text: i18n.t('ListFilters.operators.contain')
|
|
90
|
+
}, {
|
|
91
|
+
key: FilterOperators.notContain,
|
|
92
|
+
value: FilterOperators.notContain,
|
|
93
|
+
text: i18n.t('ListFilters.operators.notContain')
|
|
94
|
+
}, {
|
|
95
|
+
key: FilterOperators.empty,
|
|
96
|
+
value: FilterOperators.empty,
|
|
97
|
+
text: i18n.t('ListFilters.operators.empty')
|
|
98
|
+
}, {
|
|
99
|
+
key: FilterOperators.notEmpty,
|
|
100
|
+
value: FilterOperators.notEmpty,
|
|
101
|
+
text: i18n.t('ListFilters.operators.notEmpty')
|
|
102
|
+
}, {
|
|
103
|
+
key: FilterOperators.greaterThan,
|
|
104
|
+
value: FilterOperators.greaterThan,
|
|
105
|
+
text: i18n.t('ListFilters.operators.greaterThan')
|
|
106
|
+
}, {
|
|
107
|
+
key: FilterOperators.lessThan,
|
|
108
|
+
value: FilterOperators.lessThan,
|
|
109
|
+
text: i18n.t('ListFilters.operators.lessThan')
|
|
110
|
+
}];
|
|
111
|
+
|
|
78
112
|
const OperatorsByType = {
|
|
79
113
|
[FilterTypes.boolean]: [
|
|
80
114
|
FilterOperators.equal
|
|
@@ -110,40 +144,6 @@ const OperatorsByType = {
|
|
|
110
144
|
]
|
|
111
145
|
};
|
|
112
146
|
|
|
113
|
-
const OperatorOptions = [{
|
|
114
|
-
key: FilterOperators.equal,
|
|
115
|
-
value: FilterOperators.equal,
|
|
116
|
-
text: i18n.t('ListFilters.operators.equal')
|
|
117
|
-
}, {
|
|
118
|
-
key: FilterOperators.notEqual,
|
|
119
|
-
value: FilterOperators.notEqual,
|
|
120
|
-
text: i18n.t('ListFilters.operators.notEqual')
|
|
121
|
-
}, {
|
|
122
|
-
key: FilterOperators.contain,
|
|
123
|
-
value: FilterOperators.contain,
|
|
124
|
-
text: i18n.t('ListFilters.operators.contain')
|
|
125
|
-
}, {
|
|
126
|
-
key: FilterOperators.notContain,
|
|
127
|
-
value: FilterOperators.notContain,
|
|
128
|
-
text: i18n.t('ListFilters.operators.notContain')
|
|
129
|
-
}, {
|
|
130
|
-
key: FilterOperators.empty,
|
|
131
|
-
value: FilterOperators.empty,
|
|
132
|
-
text: i18n.t('ListFilters.operators.empty')
|
|
133
|
-
}, {
|
|
134
|
-
key: FilterOperators.notEmpty,
|
|
135
|
-
value: FilterOperators.notEmpty,
|
|
136
|
-
text: i18n.t('ListFilters.operators.notEmpty')
|
|
137
|
-
}, {
|
|
138
|
-
key: FilterOperators.greaterThan,
|
|
139
|
-
value: FilterOperators.greaterThan,
|
|
140
|
-
text: i18n.t('ListFilters.operators.greaterThan')
|
|
141
|
-
}, {
|
|
142
|
-
key: FilterOperators.lessThan,
|
|
143
|
-
value: FilterOperators.lessThan,
|
|
144
|
-
text: i18n.t('ListFilters.operators.lessThan')
|
|
145
|
-
}];
|
|
146
|
-
|
|
147
147
|
const ListFilters = (props: Props) => {
|
|
148
148
|
/**
|
|
149
149
|
* Returns the available operators for the passed filter type.
|
|
@@ -152,7 +152,7 @@ const ListFilters = (props: Props) => {
|
|
|
152
152
|
*/
|
|
153
153
|
const getOperatorsByType = useCallback((type: string) => {
|
|
154
154
|
const operators = OperatorsByType[type];
|
|
155
|
-
return _.filter(
|
|
155
|
+
return _.filter(FilterOperatorOptions, (option) => !operators || _.contains(operators, option.key));
|
|
156
156
|
}, []);
|
|
157
157
|
|
|
158
158
|
/**
|
|
@@ -400,5 +400,10 @@ export default ListFilters;
|
|
|
400
400
|
|
|
401
401
|
export {
|
|
402
402
|
FilterTypes,
|
|
403
|
-
FilterOperators
|
|
403
|
+
FilterOperators,
|
|
404
|
+
FilterOperatorOptions
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
export type {
|
|
408
|
+
Filter
|
|
404
409
|
};
|
package/src/i18n/en.json
CHANGED
package/src/index.js
CHANGED
|
@@ -96,4 +96,4 @@ export type { BatchEditProps } from './hooks/BatchEdit';
|
|
|
96
96
|
// Constants
|
|
97
97
|
export { Views as ItemViews } from './components/ItemsToggle';
|
|
98
98
|
export { SORT_ASCENDING, SORT_DESCENDING } from './components/DataList';
|
|
99
|
-
export { FilterTypes, FilterOperators } from './components/ListFilters';
|
|
99
|
+
export { FilterTypes, FilterOperators, FilterOperatorOptions } from './components/ListFilters';
|
|
@@ -51,7 +51,8 @@ type Props = {
|
|
|
51
51
|
component: ComponentType<any>,
|
|
52
52
|
defaults?: any,
|
|
53
53
|
props?: any,
|
|
54
|
-
onChange?: (filter: any) => Promise<any
|
|
54
|
+
onChange?: (filter: any) => Promise<any>,
|
|
55
|
+
showLabels?: boolean
|
|
55
56
|
},
|
|
56
57
|
|
|
57
58
|
/**
|
|
@@ -530,7 +531,7 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
|
|
|
530
531
|
*/
|
|
531
532
|
render() {
|
|
532
533
|
const { filters = {} } = this.props;
|
|
533
|
-
const { component, props } = filters;
|
|
534
|
+
const { component, props, showLabels } = filters;
|
|
534
535
|
|
|
535
536
|
return (
|
|
536
537
|
<>
|
|
@@ -541,6 +542,7 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
|
|
|
541
542
|
active: this.isFilterActive(),
|
|
542
543
|
component,
|
|
543
544
|
onChange: this.onFilterChange.bind(this),
|
|
545
|
+
showLabels,
|
|
544
546
|
props: {
|
|
545
547
|
...props,
|
|
546
548
|
onCreateFilter: this.onCreateFilter.bind(this),
|
|
@@ -28,6 +28,11 @@ type Props = {
|
|
|
28
28
|
*/
|
|
29
29
|
closeOnComplete?: boolean,
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Component to render at top of modal.
|
|
33
|
+
*/
|
|
34
|
+
headerComponent?: ComponentType<any>,
|
|
35
|
+
|
|
31
36
|
/**
|
|
32
37
|
* Component to render within the modal.
|
|
33
38
|
*/
|
|
@@ -343,6 +348,13 @@ const FileUploadModal: ComponentType<any> = (props: Props) => {
|
|
|
343
348
|
);
|
|
344
349
|
}, [statuses, props.strategy]);
|
|
345
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Memoization and case correction for the <code>headerComponent</code> prop.
|
|
353
|
+
*
|
|
354
|
+
* @type {React$AbstractComponent<*, *>}
|
|
355
|
+
*/
|
|
356
|
+
const HeaderComponent = useMemo(() => props.headerComponent, [props.headerComponent]);
|
|
357
|
+
|
|
346
358
|
/**
|
|
347
359
|
* Memoization and case correction for the <code>itemComponent</code> prop.
|
|
348
360
|
*
|
|
@@ -404,6 +416,13 @@ const FileUploadModal: ComponentType<any> = (props: Props) => {
|
|
|
404
416
|
<FileUpload
|
|
405
417
|
onFilesAdded={onAddFiles}
|
|
406
418
|
/>
|
|
419
|
+
{ HeaderComponent && (
|
|
420
|
+
<HeaderComponent
|
|
421
|
+
items={items}
|
|
422
|
+
onSetItems={setItems}
|
|
423
|
+
uploading={uploading}
|
|
424
|
+
/>
|
|
425
|
+
)}
|
|
407
426
|
<Item.Group
|
|
408
427
|
as={Form}
|
|
409
428
|
divided
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React, { useCallback } from 'react';
|
|
4
|
+
import { Button, Label } from 'semantic-ui-react';
|
|
5
|
+
import _ from 'underscore';
|
|
6
|
+
import i18n from '../i18n/i18n';
|
|
7
|
+
import { FilterOperatorOptions, type Filter } from './ListFilters';
|
|
8
|
+
import './FilterLabels.css';
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
filters: Array<Filter>,
|
|
12
|
+
onClear?: () => void,
|
|
13
|
+
onClick: (filter: Filter) => void
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const FilterLabels = (props: Props) => {
|
|
17
|
+
/**
|
|
18
|
+
* Returns the content string for the passed filter.
|
|
19
|
+
*
|
|
20
|
+
* @type {function(*): string}
|
|
21
|
+
*/
|
|
22
|
+
const getContent = useCallback((filter) => {
|
|
23
|
+
// Content will always container the label
|
|
24
|
+
const content = [filter.label];
|
|
25
|
+
|
|
26
|
+
// If an option can be found, use the text from the operator
|
|
27
|
+
const option = _.findWhere(FilterOperatorOptions, { key: filter.operator });
|
|
28
|
+
if (option) {
|
|
29
|
+
content.push(option.text);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Append the value in quotes, if present
|
|
33
|
+
if (filter.value) {
|
|
34
|
+
content.push(`"${filter.value}"`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return content.join(' ');
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Label.Group
|
|
42
|
+
className='filter-labels'
|
|
43
|
+
>
|
|
44
|
+
{ _.map(props.filters, (filter) => (
|
|
45
|
+
<Label
|
|
46
|
+
basic
|
|
47
|
+
content={getContent(filter)}
|
|
48
|
+
onRemove={() => props.onClick(filter)}
|
|
49
|
+
/>
|
|
50
|
+
))}
|
|
51
|
+
{ props.onClear && (
|
|
52
|
+
<Label
|
|
53
|
+
as={Button}
|
|
54
|
+
color='red'
|
|
55
|
+
content={i18n.t('FilterLabels.buttons.clear')}
|
|
56
|
+
icon='times'
|
|
57
|
+
onClick={props.onClear}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
</Label.Group>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default FilterLabels;
|