@k-int/stripes-kint-components 5.9.0 → 5.11.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.
- package/CHANGELOG.md +14 -0
- package/es/index.js +12 -0
- package/es/lib/Tags/Tags.js +143 -0
- package/es/lib/Tags/Tags.test.js +80 -0
- package/es/lib/Tags/hooks/index.js +28 -0
- package/es/lib/Tags/hooks/useTags.js +15 -0
- package/es/lib/Tags/hooks/useTagsEnabled.js +25 -0
- package/es/lib/Tags/index.js +40 -0
- package/es/lib/Tags/tagsConfig.js +10 -0
- package/es/lib/Typedown/Typedown.js +12 -2
- package/es/lib/hooks/typedownHooks/useTypedown.js +15 -2
- package/es/lib/hooks/useHelperApp.js +18 -15
- package/package.json +3 -2
- package/src/index.js +2 -0
- package/src/lib/FormModal/README.md +91 -0
- package/src/lib/Tags/Tags.js +145 -0
- package/src/lib/Tags/Tags.test.js +77 -0
- package/src/lib/Tags/hooks/index.js +2 -0
- package/src/lib/Tags/hooks/useTags.js +16 -0
- package/src/lib/Tags/hooks/useTagsEnabled.js +19 -0
- package/src/lib/Tags/index.js +4 -0
- package/src/lib/Tags/tagsConfig.js +16 -0
- package/src/lib/Typedown/README.md +21 -19
- package/src/lib/Typedown/Typedown.js +16 -3
- package/src/lib/hooks/typedownHooks/useTypedown.js +17 -3
- package/src/lib/hooks/useHelperApp.js +21 -13
- package/src/lib/utils/README.md +126 -56
- package/test/helpers/test-implementor-translations.json +4 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import { useQuery, useMutation, useQueryClient } from 'react-query';
|
|
5
|
+
import { CalloutContext, useOkapiKy } from '@folio/stripes/core';
|
|
6
|
+
|
|
7
|
+
import { uniqBy, sortBy, difference } from 'lodash';
|
|
8
|
+
import { Pane } from '@folio/stripes/components';
|
|
9
|
+
|
|
10
|
+
import { TagsForm } from '@folio/stripes/smart-components';
|
|
11
|
+
|
|
12
|
+
import { tagNamespaceArray } from './tagsConfig';
|
|
13
|
+
import { useTags } from './hooks';
|
|
14
|
+
import { useKintIntl } from '../hooks';
|
|
15
|
+
|
|
16
|
+
const Tags = ({
|
|
17
|
+
invalidateLinks = [], // If there are other queries that need invalidating, pass those here
|
|
18
|
+
labelOverrides = {},
|
|
19
|
+
link,
|
|
20
|
+
onToggle,
|
|
21
|
+
intlKey: passedIntlKey,
|
|
22
|
+
intlNS: passedIntlNS,
|
|
23
|
+
}) => {
|
|
24
|
+
const kintIntl = useKintIntl(passedIntlKey, passedIntlNS);
|
|
25
|
+
|
|
26
|
+
const ky = useOkapiKy();
|
|
27
|
+
const callout = useContext(CalloutContext);
|
|
28
|
+
const queryClient = useQueryClient();
|
|
29
|
+
|
|
30
|
+
// TAG GET/POST
|
|
31
|
+
const { data: { tags = [] } = {} } = useTags(
|
|
32
|
+
[...tagNamespaceArray, link]
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// istanbul ignore next
|
|
36
|
+
const { mutateAsync: postTags } = useMutation(
|
|
37
|
+
['tags', 'stripes-erm-components', 'Tags', 'postTags'],
|
|
38
|
+
(data) => ky.post('tags', { json: data }).then(() => {
|
|
39
|
+
queryClient.invalidateQueries('tags');
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// ENTITY GET/PUT
|
|
44
|
+
const { data: entity } = useQuery(
|
|
45
|
+
[link, 'stripes-erm-components', 'Tags'],
|
|
46
|
+
() => ky.get(link).json()
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// istanbul ignore next
|
|
50
|
+
const { mutateAsync: putEntity } = useMutation(
|
|
51
|
+
[link, 'stripes-erm-components', 'Tags', 'putEntity'],
|
|
52
|
+
(data) => ky.put(link, { json: data }).then(() => {
|
|
53
|
+
queryClient.invalidateQueries(link);
|
|
54
|
+
if (invalidateLinks?.length) {
|
|
55
|
+
invalidateLinks.forEach(il => queryClient.invalidateQueries(il));
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// add tags to global list of tags
|
|
61
|
+
// istanbul ignore next
|
|
62
|
+
const saveTags = (tagsToSave) => {
|
|
63
|
+
const newTag = difference(tagsToSave.map(t => (t.value || t)), tags.map(t => t.label.toLowerCase()));
|
|
64
|
+
if (!newTag || !newTag.length) return;
|
|
65
|
+
|
|
66
|
+
postTags({
|
|
67
|
+
label: newTag[0],
|
|
68
|
+
description: newTag[0]
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
callout.sendCallout({
|
|
72
|
+
message: kintIntl.formatKintMessage({
|
|
73
|
+
id: 'newTagCreated',
|
|
74
|
+
overrideValue: labelOverrides.newTagCreated
|
|
75
|
+
})
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// add tag to the list of entity tags
|
|
80
|
+
// istanbul ignore next
|
|
81
|
+
const saveEntityTags = (tagsToSave) => {
|
|
82
|
+
const tagListMap = (entity?.tags ?? []).map(tag => ({ 'value': tag.value }));
|
|
83
|
+
const tagsMap = tagsToSave.map(tag => ({ 'value': tag.value || tag }));
|
|
84
|
+
|
|
85
|
+
const newTags = sortBy(uniqBy([...tagListMap, ...tagsMap], 'value'));
|
|
86
|
+
|
|
87
|
+
putEntity({
|
|
88
|
+
tags: newTags
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const onAdd = (addTags) => {
|
|
93
|
+
saveEntityTags(addTags);
|
|
94
|
+
saveTags(addTags);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const onRemove = (tag) => {
|
|
98
|
+
const tagToDelete = (entity?.tags ?? []).filter(t => t.value.toLowerCase() === tag.toLowerCase());
|
|
99
|
+
|
|
100
|
+
putEntity({
|
|
101
|
+
tags: [{ id: tagToDelete[0].id, _delete:true }]
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const entityTags = (entity?.tags ?? []).map(tag => tag.value.toLowerCase());
|
|
106
|
+
return (
|
|
107
|
+
<Pane
|
|
108
|
+
defaultWidth="20%"
|
|
109
|
+
dismissible
|
|
110
|
+
id="tags-helper-pane"
|
|
111
|
+
onClose={onToggle}
|
|
112
|
+
paneSub={kintIntl.formatKintMessage(
|
|
113
|
+
{
|
|
114
|
+
id: 'numberOfTags',
|
|
115
|
+
overrideValue: labelOverrides.numberOfTags
|
|
116
|
+
},
|
|
117
|
+
{ count: entity?.tags?.length ?? 0 }
|
|
118
|
+
)}
|
|
119
|
+
paneTitle={kintIntl.formatKintMessage(
|
|
120
|
+
{
|
|
121
|
+
id: 'tags',
|
|
122
|
+
overrideValue: labelOverrides.tags
|
|
123
|
+
}
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
<TagsForm
|
|
127
|
+
entityTags={entityTags}
|
|
128
|
+
onAdd={onAdd}
|
|
129
|
+
onRemove={onRemove}
|
|
130
|
+
tags={tags}
|
|
131
|
+
/>
|
|
132
|
+
</Pane>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
Tags.propTypes = {
|
|
137
|
+
intlKey: PropTypes.string,
|
|
138
|
+
intlNS: PropTypes.string,
|
|
139
|
+
invalidateLinks: PropTypes.arrayOf(PropTypes.string),
|
|
140
|
+
labelOverrides: PropTypes.object,
|
|
141
|
+
link: PropTypes.string,
|
|
142
|
+
onToggle: PropTypes.func
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default Tags;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
IconButton,
|
|
6
|
+
Pane,
|
|
7
|
+
PaneHeader
|
|
8
|
+
} from '@folio/stripes-erm-testing';
|
|
9
|
+
|
|
10
|
+
import Tags from './Tags';
|
|
11
|
+
import { renderWithKintHarness } from '../../../test/jest';
|
|
12
|
+
|
|
13
|
+
const onToggle = jest.fn();
|
|
14
|
+
const onAdd = jest.fn();
|
|
15
|
+
const link = 'erm/sas/14c16fc4-f986-4e60-aa59-4e627fcf160b';
|
|
16
|
+
|
|
17
|
+
describe('Tags', () => {
|
|
18
|
+
let renderComponent;
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
renderComponent = renderWithKintHarness(
|
|
21
|
+
<MemoryRouter>
|
|
22
|
+
<Tags
|
|
23
|
+
invalidateLinks={[]}
|
|
24
|
+
link={link}
|
|
25
|
+
onAdd={onAdd}
|
|
26
|
+
onToggle={onToggle}
|
|
27
|
+
/>
|
|
28
|
+
</MemoryRouter>
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('renders the expected label', () => {
|
|
33
|
+
const { getByText } = renderComponent;
|
|
34
|
+
expect(getByText('0 Tags')).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('renders expected pane dismiss button ', async () => {
|
|
38
|
+
await IconButton('Close Tags').exists();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('renders expected open menu button ', () => {
|
|
42
|
+
const { getByRole } = renderComponent;
|
|
43
|
+
expect(
|
|
44
|
+
getByRole('button', {
|
|
45
|
+
name: 'stripes-components.multiSelection.dropdownTriggerLabel',
|
|
46
|
+
})
|
|
47
|
+
).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('renders tags heading ', () => {
|
|
51
|
+
const { getByRole } = renderComponent;
|
|
52
|
+
expect(getByRole('heading', { name: 'Tags' })).toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('renders expected region with zero tags', () => {
|
|
56
|
+
const { getByRole } = renderComponent;
|
|
57
|
+
expect(getByRole('region', { name: 'Tags 0 Tags' })).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('renders the expected multiSelectDescription', () => {
|
|
61
|
+
const { getByText } = renderComponent;
|
|
62
|
+
expect(getByText('Contains a list of any selected values, followed by an autocomplete textfield for selecting additional values.')).toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('renders the expected label', () => {
|
|
66
|
+
const { getByText } = renderComponent;
|
|
67
|
+
expect(getByText('0 items selected')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('displays the tags pane', async () => {
|
|
71
|
+
await Pane('Tags').is({ visible: true });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('displays the tags pane header', async () => {
|
|
75
|
+
await PaneHeader('Tags').is({ visible: true });
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useQuery } from 'react-query';
|
|
2
|
+
import { useOkapiKy } from '@folio/stripes/core';
|
|
3
|
+
import { defaultTagQuery, tagNamespaceArray } from '../tagsConfig';
|
|
4
|
+
|
|
5
|
+
const useTags = (namespaceArray, options) => {
|
|
6
|
+
const ky = useOkapiKy();
|
|
7
|
+
const nsArray = namespaceArray ?? tagNamespaceArray;
|
|
8
|
+
|
|
9
|
+
return useQuery(
|
|
10
|
+
nsArray,
|
|
11
|
+
() => ky.get(defaultTagQuery).json(),
|
|
12
|
+
options
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default useTags;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useOkapiKy } from '@folio/stripes/core';
|
|
2
|
+
import { useQuery } from 'react-query';
|
|
3
|
+
import { MOD_SETTINGS_ENDPOINT } from '../../constants/endpoints';
|
|
4
|
+
|
|
5
|
+
export const tagsEnabledQueryKey = [MOD_SETTINGS_ENDPOINT, 'query=(module==TAGS and configName==tags_enabled)', 'stripes-kint-components', 'useTagsEnabled'];
|
|
6
|
+
|
|
7
|
+
const useTagsEnabled = () => {
|
|
8
|
+
const ky = useOkapiKy();
|
|
9
|
+
|
|
10
|
+
const queryObject = useQuery(
|
|
11
|
+
tagsEnabledQueryKey,
|
|
12
|
+
() => ky.get(`${MOD_SETTINGS_ENDPOINT}?query=(module==TAGS and configName==tags_enabled)`).json()
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const { data: { configs: { 0: { value } = {} } = [] } = {} } = queryObject;
|
|
16
|
+
return !value || value === 'true';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default useTagsEnabled;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const tagNamespaceArray = ['tags', 'stripes-kint-components', 'Tags'];
|
|
2
|
+
|
|
3
|
+
const tagsPath = 'tags';
|
|
4
|
+
const defaultTagsParams = [
|
|
5
|
+
'limit=1000',
|
|
6
|
+
'query=cql.allRecords%3D1%20sortby%20label'
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const defaultTagQuery = `${tagsPath}?${defaultTagsParams?.join('&')}`;
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
tagsPath,
|
|
13
|
+
defaultTagsParams,
|
|
14
|
+
defaultTagQuery,
|
|
15
|
+
tagNamespaceArray
|
|
16
|
+
};
|
|
@@ -24,22 +24,24 @@ import Typedown from '@k-int/stripes-kint-components';
|
|
|
24
24
|
|
|
25
25
|
### Props
|
|
26
26
|
|
|
27
|
-
| Name
|
|
28
|
-
|
|
29
|
-
| className
|
|
30
|
-
| dataOptions
|
|
31
|
-
| displayClearItem
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
27
|
+
| Name | Type | Description | Default | Required |
|
|
28
|
+
|--------------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|---|
|
|
29
|
+
| className | string | CSS class name to apply to the component's outer div. | | ✕ |
|
|
30
|
+
| dataOptions | array | An array of objects representing the dropdown options. Each object should have a property (specified by `uniqueIdentificationPath`) that serves as a unique identifier. | | ✓ |
|
|
31
|
+
| displayClearItem | bool | Whether to display a clear icon when an item is selected. | `true` | ✕ |
|
|
32
|
+
| displayValueWhileOpen | bool | Whether or not to display the "value" underneath the typedown search while the dropdown is open. Defaults to true to avoid size changing onClick. | `true` | ✕ |
|
|
33
|
+
| endOfList | node/func | Component or function to render when the dropdown list is empty. Defaults to `<EndOfList />` from `@folio/stripes/components`. | `<EndOfList />` | ✕ |
|
|
34
|
+
| filterPath | string | Path to the property in `dataOptions` objects to use for filtering. If not provided, filtering is done on the property specified by `uniqueIdentificationPath`. | | ✕ |
|
|
35
|
+
| id | string | Id to apply to the component's outer div. | | ✕ |
|
|
36
|
+
| initialTimeoutDelay | number | Delay applied to `open` occurring on first render. Set to 800ms to avoid any stripes animations, such as being the first element focused in an opening modal. | 800 | ✕ |
|
|
37
|
+
| input | object | An object containing the input props typically provided by a form library like `react-final-form` or `redux-form`. Should include `name`, `value`, and `onChange`. | | ✓ |
|
|
38
|
+
| isSelected | func | A function `(inputValue, dataOption)` that determines if a given `dataOption` is currently selected. Useful when selected values are complex objects. If not provided, selection is determined by comparing the value of the `uniqueIdentificationPath` property of the `input.value` and the `dataOption`. | | ✕ |
|
|
39
|
+
| label | string/element | Label for the input field. | | ✕ |
|
|
40
|
+
| meta | object | Meta information about the field, typically provided by a form library. Useful for displaying error messages etc. | | ✕ |
|
|
41
|
+
| onChange | func | A callback function `(value)` that is called when a value is selected from the dropdown. This is in addition to the `input.onChange` provided. | | ✕ |
|
|
42
|
+
| onType | func | A callback function `(event)` that is called when the user types in the search field. Allows for custom filtering or data fetching based on user input. | | ✕ |
|
|
43
|
+
| renderFooter | func | A function `(displayData, currentlyTyped, exactMatch)` that renders a footer below the dropdown list. Receives the currently filtered `displayData`, what the user has typed, and whether it is an exact match. | | ✕ |
|
|
44
|
+
| renderListItem | func | A function `(option, currentlyTyped, exactMatch, optionIsSelected)` that renders each item in the dropdown list. Receives the current option, what the user has typed, whether there is an exact match, and if the option is selected. If not provided, the value of the `uniqueIdentificationPath` property of the option is displayed. | | ✕ |
|
|
45
|
+
| required | bool | Whether the input is required. | | ✕ |
|
|
46
|
+
| selectedStyles | string | CSS class name to apply to the selected item display. | | ✕ |
|
|
47
|
+
| uniqueIdentificationPath | string | Path to the property in `dataOptions` objects that serves as a unique identifier for each option and is used to determine if an option is selected. | `'id'` | ✕ |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import classnames from 'classnames';
|
|
4
4
|
|
|
@@ -16,8 +16,10 @@ const Typedown = ({
|
|
|
16
16
|
className,
|
|
17
17
|
dataOptions,
|
|
18
18
|
displayClearItem = true,
|
|
19
|
+
displayValueWhileOpen = true,
|
|
19
20
|
endOfList,
|
|
20
21
|
id,
|
|
22
|
+
initialOpenDelay = 800, // Initial opening delay of 800ms (handles any stripes animations)
|
|
21
23
|
input,
|
|
22
24
|
isSelected,
|
|
23
25
|
filterPath,
|
|
@@ -85,7 +87,12 @@ const Typedown = ({
|
|
|
85
87
|
resizeRef,
|
|
86
88
|
searchWidth
|
|
87
89
|
}
|
|
88
|
-
} = useTypedown(
|
|
90
|
+
} = useTypedown(
|
|
91
|
+
input.name,
|
|
92
|
+
{
|
|
93
|
+
timeout: initialOpenDelay
|
|
94
|
+
}
|
|
95
|
+
);
|
|
89
96
|
|
|
90
97
|
const renderItem = useCallback((option, optionIsSelected = false) => (
|
|
91
98
|
<div
|
|
@@ -197,6 +204,10 @@ const Typedown = ({
|
|
|
197
204
|
);
|
|
198
205
|
};
|
|
199
206
|
|
|
207
|
+
const displayValue = useMemo(() => {
|
|
208
|
+
return !!selectedUniqueId && (!open || displayValueWhileOpen);
|
|
209
|
+
}, [displayValueWhileOpen, open, selectedUniqueId]);
|
|
210
|
+
|
|
200
211
|
return (
|
|
201
212
|
<div
|
|
202
213
|
ref={resizeRef}
|
|
@@ -229,7 +240,7 @@ const Typedown = ({
|
|
|
229
240
|
>
|
|
230
241
|
{dropDown()}
|
|
231
242
|
</Popper>
|
|
232
|
-
{
|
|
243
|
+
{displayValue &&
|
|
233
244
|
<div
|
|
234
245
|
className={classnames(
|
|
235
246
|
css.selectedDisplay
|
|
@@ -257,6 +268,7 @@ Typedown.propTypes = {
|
|
|
257
268
|
className: PropTypes.string,
|
|
258
269
|
dataOptions: PropTypes.arrayOf(PropTypes.object),
|
|
259
270
|
displayClearItem: PropTypes.bool,
|
|
271
|
+
displayValueWhileOpen: PropTypes.bool,
|
|
260
272
|
endOfList: PropTypes.oneOfType([
|
|
261
273
|
PropTypes.func,
|
|
262
274
|
PropTypes.node,
|
|
@@ -264,6 +276,7 @@ Typedown.propTypes = {
|
|
|
264
276
|
]),
|
|
265
277
|
filterPath: PropTypes.string,
|
|
266
278
|
id: PropTypes.string,
|
|
279
|
+
initialOpenDelay: PropTypes.number,
|
|
267
280
|
input: PropTypes.object,
|
|
268
281
|
isSelected: PropTypes.func,
|
|
269
282
|
label: PropTypes.oneOfType([
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useRef } from 'react';
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useResizeDetector } from 'react-resize-detector';
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -18,7 +18,10 @@ import selectorSafe from '../../utils/selectorSafe';
|
|
|
18
18
|
|
|
19
19
|
import useTypedownToggle from './useTypedownToggle';
|
|
20
20
|
|
|
21
|
-
const useTypedown = (
|
|
21
|
+
const useTypedown = (
|
|
22
|
+
name,
|
|
23
|
+
{ timeout = 800 } = {}
|
|
24
|
+
) => {
|
|
22
25
|
// SEARCHFIELD COMPONENT
|
|
23
26
|
const searchFieldComponent = document.getElementById(`typedown-searchField-${selectorSafe(name)}`);
|
|
24
27
|
|
|
@@ -114,6 +117,17 @@ const useTypedown = (name) => {
|
|
|
114
117
|
|
|
115
118
|
// SET UP VARIABLES
|
|
116
119
|
const { open } = useTypedownToggle(name);
|
|
120
|
+
const [useOpen, setUseOpen] = useState(false);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
// Use setTimeout to update the message after 2000 milliseconds (2 seconds)
|
|
124
|
+
const timeoutId = setTimeout(() => {
|
|
125
|
+
setUseOpen(true);
|
|
126
|
+
}, timeout); // Wait 0.8 seconds for open prop to get used
|
|
127
|
+
|
|
128
|
+
// Cleanup function to clear the timeout if the component unmounts
|
|
129
|
+
return () => clearTimeout(timeoutId);
|
|
130
|
+
}, [timeout]);
|
|
117
131
|
|
|
118
132
|
// RESIZE STUFF
|
|
119
133
|
const { width: searchWidth, ref: resizeRef } = useResizeDetector();
|
|
@@ -134,7 +148,7 @@ const useTypedown = (name) => {
|
|
|
134
148
|
searchFieldKeyDownHandler
|
|
135
149
|
},
|
|
136
150
|
variables: {
|
|
137
|
-
open,
|
|
151
|
+
open: useOpen ? open : false,
|
|
138
152
|
portal,
|
|
139
153
|
resizeRef,
|
|
140
154
|
searchWidth
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { useHistory, useLocation } from 'react-router-dom';
|
|
3
3
|
|
|
4
4
|
import queryString from 'query-string';
|
|
5
5
|
import isEqual from 'lodash/isEqual';
|
|
6
6
|
|
|
7
|
-
let helperObject = {};
|
|
8
|
-
|
|
9
7
|
const useHelperApp = (helpers) => {
|
|
10
8
|
const history = useHistory();
|
|
11
9
|
const location = useLocation();
|
|
12
10
|
|
|
11
|
+
const [helperObject, setHelperObject] = useState({});
|
|
12
|
+
const [helperToggleFunctions, setHelperToggleFunctions] = useState({});
|
|
13
|
+
|
|
13
14
|
const query = queryString.parse(location.search);
|
|
14
15
|
|
|
15
16
|
const [currentHelper, setCurrentHelper] = useState(query?.helper);
|
|
@@ -22,9 +23,18 @@ const useHelperApp = (helpers) => {
|
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
useEffect(() => {
|
|
25
|
-
// Keep object outside of hook to avoid redraw, oncly change when keys change
|
|
26
26
|
if (!isEqual(Object.keys(helperObject), Object.keys(helpers))) {
|
|
27
|
-
|
|
27
|
+
setHelperObject(helpers);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const newHelperToggleFunctions = {};
|
|
31
|
+
Object.keys(helperObject).forEach(h => {
|
|
32
|
+
newHelperToggleFunctions[h] = () => handleToggleHelper(h);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!isEqual(Object.keys(helperToggleFunctions), Object.keys(newHelperToggleFunctions))) {
|
|
36
|
+
// This makes sure adding/removing helpers changes the functions
|
|
37
|
+
setHelperToggleFunctions(newHelperToggleFunctions);
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
if (currentHelper !== query?.helper) {
|
|
@@ -37,11 +47,14 @@ const useHelperApp = (helpers) => {
|
|
|
37
47
|
pathname: location.pathname,
|
|
38
48
|
search: `?${queryString.stringify(newQuery)}`
|
|
39
49
|
});
|
|
50
|
+
|
|
51
|
+
// When helper changes, reset helperToggleFunctions
|
|
52
|
+
setHelperToggleFunctions(newHelperToggleFunctions);
|
|
40
53
|
}
|
|
41
|
-
}, [currentHelper, helpers, history, location, query]);
|
|
54
|
+
}, [currentHelper, handleToggleHelper, helperObject, helperToggleFunctions, helpers, history, location, query]);
|
|
42
55
|
|
|
43
56
|
// Set the HelperComponent
|
|
44
|
-
const HelperComponent =
|
|
57
|
+
const HelperComponent = useCallback((props) => {
|
|
45
58
|
if (!query?.helper) return null;
|
|
46
59
|
|
|
47
60
|
let Component = null;
|
|
@@ -56,13 +69,8 @@ const useHelperApp = (helpers) => {
|
|
|
56
69
|
{...props}
|
|
57
70
|
/>
|
|
58
71
|
);
|
|
59
|
-
}
|
|
72
|
+
}, [handleToggleHelper, helperObject, query?.helper]);
|
|
60
73
|
|
|
61
|
-
// Set up the helperToggleFunctions
|
|
62
|
-
const helperToggleFunctions = {};
|
|
63
|
-
Object.keys(helperObject).forEach(h => {
|
|
64
|
-
helperToggleFunctions[h] = () => handleToggleHelper(h);
|
|
65
|
-
});
|
|
66
74
|
|
|
67
75
|
return { currentHelper, HelperComponent, helperToggleFunctions, isOpen };
|
|
68
76
|
};
|