@k-int/stripes-kint-components 2.3.1 → 2.4.0

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 (30) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/es/index.js +26 -2
  3. package/es/lib/ActionList/ActionListFieldArray.js +3 -4
  4. package/es/lib/CustomProperties/Config/CustomPropertiesSettings.js +9 -6
  5. package/es/lib/CustomProperties/View/CustomPropertiesViewCtx.js +22 -15
  6. package/es/lib/CustomProperties/View/CustomPropertyCard.js +3 -2
  7. package/es/lib/FormModal/FormModal.js +2 -0
  8. package/es/lib/SASQLookupComponent/SASQLookupComponent.js +61 -105
  9. package/es/lib/SASQLookupComponent/TableBody/TableBody.js +146 -0
  10. package/es/lib/SASQLookupComponent/TableBody/index.js +19 -0
  11. package/es/lib/SASQLookupComponent/index.js +9 -1
  12. package/es/lib/SASQRoute/SASQRoute.js +2 -2
  13. package/es/lib/utils/highlightString.js +94 -0
  14. package/es/lib/utils/index.js +23 -1
  15. package/es/lib/utils/matchString.js +59 -0
  16. package/package.json +1 -1
  17. package/src/index.js +6 -1
  18. package/src/lib/ActionList/ActionListFieldArray.js +2 -3
  19. package/src/lib/CustomProperties/Config/CustomPropertiesSettings.js +9 -6
  20. package/src/lib/CustomProperties/View/CustomPropertiesViewCtx.js +13 -8
  21. package/src/lib/CustomProperties/View/CustomPropertyCard.js +2 -2
  22. package/src/lib/FormModal/FormModal.js +2 -0
  23. package/src/lib/SASQLookupComponent/SASQLookupComponent.js +39 -77
  24. package/src/lib/SASQLookupComponent/TableBody/TableBody.js +96 -0
  25. package/src/lib/SASQLookupComponent/TableBody/index.js +1 -0
  26. package/src/lib/SASQLookupComponent/index.js +2 -1
  27. package/src/lib/SASQRoute/SASQRoute.js +1 -1
  28. package/src/lib/utils/highlightString.js +42 -0
  29. package/src/lib/utils/index.js +4 -0
  30. package/src/lib/utils/matchString.js +14 -0
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import PropTypes from 'prop-types';
3
2
  import { FormattedMessage } from 'react-intl';
4
3
  import { useInfiniteQuery } from 'react-query';
@@ -17,34 +16,30 @@ import {
17
16
  Button,
18
17
  IconButton,
19
18
  Icon,
20
- MultiColumnList,
21
19
  Pane,
22
20
  PaneMenu,
23
21
  SearchField,
24
22
  } from '@folio/stripes/components';
25
- import NoResultsMessage from '../NoResultsMessage';
26
23
 
27
24
  import { generateKiwtQuery } from '../utils';
28
25
  import { useKiwtSASQuery, useLocalStorageState } from '../hooks';
29
26
 
27
+ import TableBody from './TableBody';
28
+
29
+ const SASQLookupComponent = (props) => {
30
+ const {
31
+ children,
32
+ fetchParameters = {},
33
+ FilterComponent = () => null,
34
+ FilterPaneHeaderComponent = () => null,
35
+ filterPaneProps,
36
+ id,
37
+ mainPaneProps,
38
+ noSearchField,
39
+ RenderBody,
40
+ sasqProps,
41
+ } = props;
30
42
 
31
- const SASQLookupComponent = ({
32
- children,
33
- fetchParameters = {},
34
- FilterPaneHeaderComponent = () => null,
35
- FilterComponent = () => null,
36
- history,
37
- id,
38
- location,
39
- mainPaneProps,
40
- match,
41
- mclProps,
42
- noSearchField,
43
- path,
44
- resultColumns = [],
45
- RenderBody,
46
- sasqProps,
47
- }) => {
48
43
  const { query, queryGetter, querySetter } = useKiwtSASQuery();
49
44
  const { 0: namespace } = useNamespace();
50
45
  const ky = useOkapiKy();
@@ -62,10 +57,7 @@ const SASQLookupComponent = ({
62
57
 
63
58
  const {
64
59
  data: totalData = {},
65
- error,
66
- isError,
67
- isLoading,
68
- fetchNextPage,
60
+ ...restOfInfiniteQueryProps
69
61
  } = useInfiniteQuery(
70
62
  [namespace, id, 'data', query],
71
63
  fetchPageData
@@ -93,20 +85,6 @@ const SASQLookupComponent = ({
93
85
  {}
94
86
  ) ?? {};
95
87
 
96
- // TODO focus handling to stop redraw movin to top
97
-
98
- const onNeedMoreData = (_askAmount, index) => {
99
- fetchNextPage({ pageParam: index });
100
- };
101
-
102
- // Build the map of column definitions
103
- const columnMapping = Object.fromEntries(
104
- resultColumns.map(e => [e.propertyPath, e.label])
105
- );
106
-
107
- // Build the list of visible columns
108
- const visibleColumns = resultColumns.map(e => e.propertyPath);
109
-
110
88
  return (
111
89
  <SearchAndSortQuery
112
90
  initialSearchState={{ query: '' }}
@@ -115,56 +93,23 @@ const SASQLookupComponent = ({
115
93
  {...sasqProps}
116
94
  >
117
95
  {
118
- ({
96
+ (sasqRenderProps) => {
97
+ const {
119
98
  activeFilters,
120
99
  filterChanged,
121
100
  getFilterHandlers,
122
101
  getSearchHandlers,
123
- onSort,
124
102
  onSubmitSearch,
125
103
  resetAll,
126
104
  searchChanged,
127
105
  searchValue
128
- }) => {
106
+ } = sasqRenderProps;
107
+
129
108
  const searchHandlers = getSearchHandlers();
130
- const sortOrder = query.sort ?? '';
131
109
  const disableReset = !filterChanged && !searchChanged;
132
110
 
133
111
  const filterCount = activeFilters.string ? activeFilters.string.split(',').length : 0;
134
112
 
135
- const TableBody = () => (
136
- <MultiColumnList
137
- autosize
138
- columnMapping={columnMapping}
139
- contentData={data?.results}
140
- isEmptyMessage={
141
- <NoResultsMessage
142
- {...{
143
- error,
144
- isError,
145
- isLoading,
146
- filterPaneIsVisible: filterPaneVisible,
147
- searchTerm: query.query,
148
- toggleFilterPane
149
- }}
150
- />
151
- }
152
- isSelected={({ item }) => item.id === match?.params?.id}
153
- onHeaderClick={onSort}
154
- onNeedMoreData={onNeedMoreData}
155
- onRowClick={(_e, rowData) => {
156
- history.push(`${path}/${rowData?.id}${location?.search}`);
157
- }}
158
- pagingType="click"
159
- sortDirection={sortOrder.startsWith('-') ? 'descending' : 'ascending'}
160
- sortOrder={sortOrder.replace(/^-/, '').replace(/,.*/, '')}
161
- totalCount={data.totalRecords}
162
- virtualize
163
- visibleColumns={visibleColumns}
164
- {...mclProps}
165
- />
166
- );
167
-
168
113
  const Body = RenderBody ?? TableBody;
169
114
 
170
115
  return (
@@ -184,6 +129,7 @@ const SASQLookupComponent = ({
184
129
  </PaneMenu>
185
130
  }
186
131
  paneTitle={<FormattedMessage id="stripes-smart-components.searchAndFilter" />}
132
+ {...filterPaneProps}
187
133
  >
188
134
  <form onSubmit={onSubmitSearch}>
189
135
  <FilterPaneHeaderComponent />
@@ -192,7 +138,13 @@ const SASQLookupComponent = ({
192
138
  <SearchField
193
139
  autoFocus
194
140
  name="query"
195
- onChange={searchHandlers.query}
141
+ onChange={e => {
142
+ if (e.target?.value) {
143
+ searchHandlers.query(e); // SASQ needs the whole event here
144
+ } else {
145
+ searchHandlers.reset();
146
+ }
147
+ }}
196
148
  onClear={searchHandlers.reset}
197
149
  value={searchValue.query}
198
150
  />
@@ -240,10 +192,19 @@ const SASQLookupComponent = ({
240
192
  </PaneMenu>
241
193
  :
242
194
  null}
195
+ noOverflow
196
+ padContent={false}
243
197
  paneSub={<FormattedMessage id="stripes-kint-components.sasqLookupComponent.mainPane.found" values={{ total: data?.total }} />}
244
198
  {...mainPaneProps}
245
199
  >
246
- <Body data={data} />
200
+ <Body
201
+ data={data}
202
+ query={query}
203
+ toggleFilterPane={toggleFilterPane}
204
+ {...restOfInfiniteQueryProps}
205
+ {...sasqProps}
206
+ {...props}
207
+ />
247
208
  </Pane>
248
209
  {children}
249
210
  </PersistedPaneset>
@@ -260,6 +221,7 @@ SASQLookupComponent.propTypes = {
260
221
  PropTypes.node
261
222
  ]),
262
223
  fetchParameters: PropTypes.object,
224
+ filterPaneProps: PropTypes.object,
263
225
  FilterComponent: PropTypes.oneOfType([
264
226
  PropTypes.func,
265
227
  PropTypes.node
@@ -0,0 +1,96 @@
1
+ import PropTypes from 'prop-types';
2
+
3
+ import { useHistory, useLocation } from 'react-router-dom';
4
+
5
+ import {
6
+ MultiColumnList,
7
+ } from '@folio/stripes/components';
8
+ import NoResultsMessage from '../../NoResultsMessage';
9
+
10
+ const TableBody = ({
11
+ data,
12
+ error,
13
+ fetchNextPage,
14
+ filterPaneVisible,
15
+ isError,
16
+ isLoading,
17
+ match,
18
+ mclProps,
19
+ onSort,
20
+ path,
21
+ resultColumns,
22
+ toggleFilterPane,
23
+ query,
24
+ }) => {
25
+ const sortOrder = query.sort ?? '';
26
+ const history = useHistory();
27
+ const location = useLocation();
28
+
29
+ const onNeedMoreData = (_askAmount, index) => {
30
+ fetchNextPage({ pageParam: index });
31
+ };
32
+
33
+ // Build the map of column definitions
34
+ const columnMapping = Object.fromEntries(
35
+ resultColumns.map(e => [e.propertyPath, e.label])
36
+ );
37
+
38
+ // Build the list of visible columns
39
+ const visibleColumns = resultColumns.map(e => e.propertyPath);
40
+
41
+ return (
42
+ <MultiColumnList
43
+ autosize
44
+ columnMapping={columnMapping}
45
+ contentData={data?.results}
46
+ hasMargin
47
+ isEmptyMessage={
48
+ <NoResultsMessage
49
+ {...{
50
+ error,
51
+ isError,
52
+ isLoading,
53
+ filterPaneIsVisible: filterPaneVisible,
54
+ searchTerm: query.query,
55
+ toggleFilterPane
56
+ }}
57
+ />
58
+ }
59
+ isSelected={({ item }) => item.id === match?.params?.id}
60
+ onHeaderClick={onSort}
61
+ onNeedMoreData={onNeedMoreData}
62
+ onRowClick={(_e, rowData) => {
63
+ history.push(`${path}/${rowData?.id}${location?.search}`);
64
+ }}
65
+ pagingType="click"
66
+ sortDirection={sortOrder.startsWith('-') ? 'descending' : 'ascending'}
67
+ sortOrder={sortOrder.replace(/^-/, '').replace(/,.*/, '')}
68
+ totalCount={data.totalRecords}
69
+ visibleColumns={visibleColumns}
70
+ {...mclProps}
71
+ />
72
+ );
73
+ };
74
+
75
+ TableBody.propTypes = {
76
+ data: PropTypes.shape({
77
+ totalRecords: PropTypes.number,
78
+ results: PropTypes.arrayOf(PropTypes.object)
79
+ }),
80
+ error: PropTypes.object,
81
+ fetchNextPage: PropTypes.func,
82
+ filterPaneVisible: PropTypes.bool,
83
+ history: PropTypes.object,
84
+ isError: PropTypes.bool,
85
+ isLoading: PropTypes.bool,
86
+ location: PropTypes.object,
87
+ match: PropTypes.object,
88
+ mclProps: PropTypes.object,
89
+ onSort: PropTypes.func,
90
+ path: PropTypes.string.isRequired,
91
+ query: PropTypes.object,
92
+ resultColumns: PropTypes.arrayOf(PropTypes.object),
93
+ toggleFilterPane: PropTypes.func
94
+ };
95
+
96
+ export default TableBody;
@@ -0,0 +1 @@
1
+ export { default } from './TableBody';
@@ -1 +1,2 @@
1
- export { default } from './SASQLookupComponent';
1
+ export { default as SASQLookupComponent } from './SASQLookupComponent';
2
+ export { default as TableBody } from './TableBody';
@@ -6,7 +6,7 @@ import {
6
6
  Switch
7
7
  } from 'react-router-dom';
8
8
 
9
- import SASQLookupComponent from '../SASQLookupComponent';
9
+ import { SASQLookupComponent } from '../SASQLookupComponent';
10
10
  import SASQViewComponent from '../SASQViewComponent';
11
11
 
12
12
  const SASQRoute = ({ children, path, fetchParameters, ...props }) => {
@@ -0,0 +1,42 @@
1
+ import matchString from './matchString';
2
+
3
+ const highlightString = (match, str, ignoreNull = true) => {
4
+ const [parts, regex] = matchString(match, str, ignoreNull);
5
+
6
+ return (
7
+ parts.filter(part => part).map((part, i) => (
8
+ regex.test(part) ?
9
+ <mark
10
+ key={i}
11
+ >
12
+ {part}
13
+ </mark> :
14
+ <span key={i}>
15
+ {part}
16
+ </span>
17
+ ))
18
+ );
19
+ };
20
+
21
+ const boldString = (match, str, ignoreNull = true) => {
22
+ const [parts, regex] = matchString(match, str, ignoreNull);
23
+
24
+ return (
25
+ parts.filter(part => part).map((part, i) => (
26
+ regex.test(part) ?
27
+ <strong
28
+ key={i}
29
+ >
30
+ {part}
31
+ </strong> :
32
+ <span key={i}>
33
+ {part}
34
+ </span>
35
+ ))
36
+ );
37
+ };
38
+
39
+ export {
40
+ boldString,
41
+ highlightString
42
+ };
@@ -15,3 +15,7 @@ export { default as groupCustomPropertiesByCtx } from './groupCustomPropertiesBy
15
15
  export { default as renderHelpText } from './renderHelpText';
16
16
  export { default as sortByLabel } from './sortByLabel';
17
17
  export { default as toCamelCase } from './toCamelCase'; // I hate that this exists
18
+
19
+
20
+ export { default as matchString } from './matchString';
21
+ export { boldString, highlightString } from './highlightString';
@@ -0,0 +1,14 @@
1
+ import escapeRegExp from 'lodash/escapeRegExp';
2
+
3
+ const matchString = (match, str, ignoreNull = true) => {
4
+ const regex = new RegExp(`${match.split(/(\s+)/).filter(h => h.trim()).map(hl => '(' + escapeRegExp(hl) + ')').join('|')}`, 'gi')
5
+ if (ignoreNull && !match) {
6
+ const nullRegex = /a^/gi; // Should match nothing
7
+
8
+ return [[str], nullRegex];
9
+ }
10
+
11
+ return [str.split(regex), regex];
12
+ };
13
+
14
+ export default matchString;