@squiz/resource-browser 2.4.12 → 3.0.1-pre-alpha.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/README.md +4 -0
- package/lib/BrowseToSource/BrowseToSource.d.ts +8 -0
- package/lib/BrowseToSource/BrowseToSource.js +50 -0
- package/lib/Hooks/useAuth.js +11 -15
- package/lib/Hooks/useSelectedState.js +3 -7
- package/lib/Hooks/useSources.d.ts +2 -2
- package/lib/Hooks/useSources.js +19 -9
- package/lib/Icons/AdsClickIcon.d.ts +4 -0
- package/lib/Icons/AdsClickIcon.js +5 -0
- package/lib/Icons/ArrowDownIcon.d.ts +4 -0
- package/lib/Icons/ArrowDownIcon.js +5 -0
- package/lib/Icons/CircledLoopIcon.js +4 -11
- package/lib/MainContainer/MainContainer.d.ts +6 -4
- package/lib/MainContainer/MainContainer.js +33 -52
- package/lib/Plugin/Plugin.js +7 -14
- package/lib/ResourceBrowserContext/AuthProvider.js +9 -37
- package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +1 -0
- package/lib/ResourceBrowserContext/ResourceBrowserContext.js +10 -39
- package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +6 -4
- package/lib/ResourceBrowserInput/ResourceBrowserInput.js +5 -12
- package/lib/ResourceLauncher/ResourceLauncher.d.ts +8 -0
- package/lib/ResourceLauncher/ResourceLauncher.js +12 -0
- package/lib/ResourcePicker/ResourcePicker.js +20 -27
- package/lib/ResourcePicker/States/Error.js +6 -13
- package/lib/ResourcePicker/States/Loading.js +4 -11
- package/lib/ResourcePicker/States/Selected.js +12 -19
- package/lib/SourceDropdown/SourceDropdown.d.ts +2 -2
- package/lib/SourceDropdown/SourceDropdown.js +22 -48
- package/lib/SourceDropdownContainer/SourceDropdownContainer.d.ts +5 -0
- package/lib/SourceDropdownContainer/SourceDropdownContainer.js +12 -0
- package/lib/SourceList/SourceList.js +11 -16
- package/lib/index.css +102 -26
- package/lib/index.d.ts +4 -1
- package/lib/index.js +40 -66
- package/lib/types.d.ts +35 -3
- package/lib/types.js +5 -2
- package/lib/utils/authUtils.js +9 -16
- package/lib-esm/BrowseToSource/BrowseToSource.d.ts +8 -0
- package/lib-esm/BrowseToSource/BrowseToSource.js +50 -0
- package/lib-esm/Hooks/useAuth.d.ts +7 -0
- package/lib-esm/Hooks/useAuth.js +54 -0
- package/lib-esm/Hooks/useSelectedState.d.ts +15 -0
- package/lib-esm/Hooks/useSelectedState.js +12 -0
- package/lib-esm/Hooks/useSources.d.ts +14 -0
- package/lib-esm/Hooks/useSources.js +44 -0
- package/lib-esm/Icons/AdsClickIcon.d.ts +4 -0
- package/lib-esm/Icons/AdsClickIcon.js +5 -0
- package/lib-esm/Icons/ArrowDownIcon.d.ts +4 -0
- package/lib-esm/Icons/ArrowDownIcon.js +5 -0
- package/lib-esm/Icons/CircledLoopIcon.d.ts +4 -0
- package/lib-esm/Icons/CircledLoopIcon.js +5 -0
- package/lib-esm/MainContainer/MainContainer.d.ts +19 -0
- package/lib-esm/MainContainer/MainContainer.js +43 -0
- package/lib-esm/Plugin/Plugin.d.ts +13 -0
- package/lib-esm/Plugin/Plugin.js +12 -0
- package/lib-esm/ResourceBrowserContext/AuthProvider.d.ts +16 -0
- package/lib-esm/ResourceBrowserContext/AuthProvider.js +18 -0
- package/lib-esm/ResourceBrowserContext/ResourceBrowserContext.d.ts +15 -0
- package/lib-esm/ResourceBrowserContext/ResourceBrowserContext.js +26 -0
- package/lib-esm/ResourceBrowserInput/ResourceBrowserInput.d.ts +26 -0
- package/lib-esm/ResourceBrowserInput/ResourceBrowserInput.js +9 -0
- package/lib-esm/ResourceLauncher/ResourceLauncher.d.ts +8 -0
- package/lib-esm/ResourceLauncher/ResourceLauncher.js +12 -0
- package/lib-esm/ResourcePicker/ResourcePicker.d.ts +16 -0
- package/lib-esm/ResourcePicker/ResourcePicker.js +25 -0
- package/lib-esm/ResourcePicker/States/Error.d.ts +7 -0
- package/lib-esm/ResourcePicker/States/Error.js +6 -0
- package/lib-esm/ResourcePicker/States/Loading.d.ts +2 -0
- package/lib-esm/ResourcePicker/States/Loading.js +4 -0
- package/lib-esm/ResourcePicker/States/Selected.d.ts +15 -0
- package/lib-esm/ResourcePicker/States/Selected.js +20 -0
- package/lib-esm/SourceDropdown/SourceDropdown.d.ts +7 -0
- package/lib-esm/SourceDropdown/SourceDropdown.js +46 -0
- package/lib-esm/SourceDropdownContainer/SourceDropdownContainer.d.ts +5 -0
- package/lib-esm/SourceDropdownContainer/SourceDropdownContainer.js +12 -0
- package/lib-esm/SourceList/SourceList.d.ts +8 -0
- package/lib-esm/SourceList/SourceList.js +16 -0
- package/lib-esm/index.d.ts +18 -0
- package/lib-esm/index.js +79 -0
- package/lib-esm/types.d.ts +97 -0
- package/lib-esm/types.js +5 -0
- package/lib-esm/utils/authUtils.d.ts +5 -0
- package/lib-esm/utils/authUtils.js +31 -0
- package/package.json +18 -6
- package/src/BrowseToSource/BrowseToSource.spec.tsx +111 -0
- package/src/BrowseToSource/BrowseToSource.stories.tsx +29 -0
- package/src/BrowseToSource/BrowseToSource.tsx +111 -0
- package/src/Hooks/useSources.spec.ts +8 -4
- package/src/Hooks/useSources.ts +28 -13
- package/src/Icons/AdsClickIcon.tsx +11 -0
- package/src/Icons/ArrowDownIcon.tsx +11 -0
- package/src/MainContainer/MainContainer.spec.tsx +322 -108
- package/src/MainContainer/MainContainer.tsx +67 -27
- package/src/Plugin/Plugin.spec.tsx +2 -0
- package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +3 -0
- package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +2 -0
- package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +7 -0
- package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +16 -3
- package/src/ResourceLauncher/ResourceLauncher.spec.tsx +65 -0
- package/src/ResourceLauncher/ResourceLauncher.tsx +35 -0
- package/src/SourceDropdown/SourceDropdown.stories.tsx +1 -2
- package/src/SourceDropdown/SourceDropdown.tsx +8 -8
- package/src/SourceDropdownContainer/SourceDropdownContainer.spec.tsx +50 -0
- package/src/SourceDropdownContainer/SourceDropdownContainer.stories.tsx +62 -0
- package/src/SourceDropdownContainer/SourceDropdownContainer.tsx +27 -0
- package/src/__mocks__/MockModels.ts +16 -2
- package/src/__mocks__/PluginExample.tsx +8 -0
- package/src/__mocks__/StorybookHelpers.tsx +37 -1
- package/src/__mocks__/renderWithContext.tsx +1 -0
- package/src/index.spec.tsx +135 -41
- package/src/index.stories.tsx +12 -1
- package/src/index.tsx +45 -16
- package/src/types.ts +43 -3
- package/.eslintrc +0 -40
- package/.storybook/main.ts +0 -23
- package/.storybook/preview-body.html +0 -1
- package/.storybook/preview-head.html +0 -12
- package/.storybook/preview.ts +0 -16
- package/CHANGELOG.md +0 -244
- package/LICENSE.md +0 -15
- package/build.js +0 -21
- package/jest.config.ts +0 -30
- package/postcss.config.js +0 -21
- package/tailwind.config.cjs +0 -98
- package/tsconfig.json +0 -22
- package/tsconfig.storybook.json +0 -4
- package/tsconfig.test.json +0 -12
- package/vite.config.js +0 -20
package/src/index.spec.tsx
CHANGED
@@ -3,7 +3,7 @@ import { waitFor, act } from '@testing-library/react';
|
|
3
3
|
import { ResourceBrowser, ResourceBrowserProps } from './index';
|
4
4
|
import { mockSource, mockResource } from './__mocks__/MockModels';
|
5
5
|
import { renderWithContext } from './__mocks__/renderWithContext';
|
6
|
-
import { ResourceBrowserPlugin } from './types';
|
6
|
+
import { ResourceBrowserPlugin, ResourceBrowserSource, ResourceBrowserSourceWithPlugin, PluginLaunchModeType } from './types';
|
7
7
|
|
8
8
|
import * as RBI from './ResourceBrowserInput/ResourceBrowserInput';
|
9
9
|
jest.spyOn(RBI, 'ResourceBrowserInput');
|
@@ -27,18 +27,29 @@ describe('Resource browser input', () => {
|
|
27
27
|
renderSelectedResource: mockRenderSelectedResource,
|
28
28
|
sourceBrowserComponent: mockSourceBrowserComponent,
|
29
29
|
useResolveResource: mockUseResolveResource,
|
30
|
+
sourceSearchComponent: jest.fn(),
|
31
|
+
renderResourceLauncher: jest.fn(),
|
30
32
|
} as unknown as ResourceBrowserPlugin;
|
31
33
|
|
32
|
-
const renderComponent = (props: Partial<ResourceBrowserProps> = {}) => {
|
34
|
+
const renderComponent = (props: Partial<ResourceBrowserProps> = {}, searchEnabled?: boolean) => {
|
33
35
|
return renderWithContext(
|
34
36
|
<ResourceBrowser modalTitle="Asset picker" value={null} onChange={mockChange} onClear={mockOnClear} {...props} />,
|
35
37
|
{
|
36
38
|
onRequestSources: mockRequestSources,
|
37
39
|
plugins: [mockDamPlugin],
|
40
|
+
searchEnabled: !!searchEnabled,
|
38
41
|
},
|
39
42
|
);
|
40
43
|
};
|
41
44
|
|
45
|
+
// Internally the plugin is attached to the source so handle that for tests expectation
|
46
|
+
const calculateExpectedSource = (source: ResourceBrowserSource): ResourceBrowserSourceWithPlugin => {
|
47
|
+
return {
|
48
|
+
...source,
|
49
|
+
plugin: mockDamPlugin,
|
50
|
+
};
|
51
|
+
};
|
52
|
+
|
42
53
|
it('If only one valid source is provided will default to its Source and Plugin', async () => {
|
43
54
|
const source = mockSource({ type: 'dam' });
|
44
55
|
mockRequestSources.mockResolvedValueOnce([source]);
|
@@ -47,7 +58,7 @@ describe('Resource browser input', () => {
|
|
47
58
|
await waitFor(() => {
|
48
59
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
49
60
|
expect.objectContaining({
|
50
|
-
source,
|
61
|
+
source: calculateExpectedSource(source),
|
51
62
|
plugin: mockDamPlugin,
|
52
63
|
}),
|
53
64
|
{},
|
@@ -55,22 +66,6 @@ describe('Resource browser input', () => {
|
|
55
66
|
});
|
56
67
|
});
|
57
68
|
|
58
|
-
it('If multiple valid source is provided will not default to its Source', async () => {
|
59
|
-
const sources = [mockSource(), mockSource()];
|
60
|
-
mockRequestSources.mockResolvedValueOnce(sources);
|
61
|
-
renderComponent();
|
62
|
-
|
63
|
-
await waitFor(() => {
|
64
|
-
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
65
|
-
expect.objectContaining({
|
66
|
-
sources,
|
67
|
-
plugin: null,
|
68
|
-
}),
|
69
|
-
{},
|
70
|
-
);
|
71
|
-
});
|
72
|
-
});
|
73
|
-
|
74
69
|
it('If a resource is provided will default to its Source and Plugin to match', async () => {
|
75
70
|
const source = mockSource({ type: 'dam' });
|
76
71
|
mockRequestSources.mockResolvedValueOnce([source, mockSource({ type: 'matrix' })]);
|
@@ -80,8 +75,9 @@ describe('Resource browser input', () => {
|
|
80
75
|
await waitFor(() => {
|
81
76
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
82
77
|
expect.objectContaining({
|
83
|
-
source,
|
78
|
+
source: calculateExpectedSource(source),
|
84
79
|
plugin: mockDamPlugin,
|
80
|
+
pluginMode: null,
|
85
81
|
}),
|
86
82
|
{},
|
87
83
|
);
|
@@ -107,15 +103,16 @@ describe('Resource browser input', () => {
|
|
107
103
|
});
|
108
104
|
|
109
105
|
it('onSourceSelect will alter source passed to ResourceBrowserInput', async () => {
|
110
|
-
const
|
111
|
-
|
106
|
+
const sourcesInput = [mockSource({ type: 'dam' }), mockSource()];
|
107
|
+
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
108
|
+
mockRequestSources.mockResolvedValueOnce(sourcesInput);
|
112
109
|
renderComponent();
|
113
110
|
|
114
111
|
// Expect no source or plugin
|
115
112
|
await waitFor(() => {
|
116
113
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
117
114
|
expect.objectContaining({
|
118
|
-
sources,
|
115
|
+
sources: calculatedSources,
|
119
116
|
source: null,
|
120
117
|
plugin: null,
|
121
118
|
}),
|
@@ -127,15 +124,54 @@ describe('Resource browser input', () => {
|
|
127
124
|
const { setSource } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
128
125
|
// Invoke it
|
129
126
|
act(() => {
|
130
|
-
setSource(
|
127
|
+
setSource(calculatedSources[0]);
|
131
128
|
});
|
132
129
|
|
133
130
|
// Expect the source and plugin to be loaded
|
134
131
|
await waitFor(() => {
|
135
132
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
136
133
|
expect.objectContaining({
|
137
|
-
source:
|
134
|
+
source: calculatedSources[0],
|
138
135
|
plugin: mockDamPlugin,
|
136
|
+
pluginMode: null,
|
137
|
+
}),
|
138
|
+
{},
|
139
|
+
);
|
140
|
+
});
|
141
|
+
});
|
142
|
+
|
143
|
+
it('onSourceSelect will set mode is provided', async () => {
|
144
|
+
const sourcesInput = [mockSource({ type: 'dam' }), mockSource()];
|
145
|
+
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
146
|
+
mockRequestSources.mockResolvedValueOnce(sourcesInput);
|
147
|
+
renderComponent();
|
148
|
+
|
149
|
+
// Expect no source or plugin
|
150
|
+
await waitFor(() => {
|
151
|
+
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
152
|
+
expect.objectContaining({
|
153
|
+
sources: calculatedSources,
|
154
|
+
source: null,
|
155
|
+
plugin: null,
|
156
|
+
}),
|
157
|
+
{},
|
158
|
+
);
|
159
|
+
});
|
160
|
+
|
161
|
+
// Get the provided callback
|
162
|
+
const { setSource } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
163
|
+
// Invoke it
|
164
|
+
act(() => {
|
165
|
+
setSource(calculatedSources[0], { type: PluginLaunchModeType.Search, args: { query: 'myQuery' } });
|
166
|
+
});
|
167
|
+
|
168
|
+
// Expect the source and plugin to be loaded
|
169
|
+
await waitFor(() => {
|
170
|
+
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
171
|
+
expect.objectContaining({
|
172
|
+
source: calculatedSources[0],
|
173
|
+
plugin: mockDamPlugin,
|
174
|
+
pluginMode: { type: PluginLaunchModeType.Search, args: { query: 'myQuery' } },
|
139
175
|
}),
|
140
176
|
{},
|
141
177
|
);
|
@@ -152,7 +188,7 @@ describe('Resource browser input', () => {
|
|
152
188
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
153
189
|
expect.objectContaining({
|
154
190
|
value: resource,
|
155
|
-
source,
|
191
|
+
source: calculateExpectedSource(source),
|
156
192
|
plugin: mockDamPlugin,
|
157
193
|
}),
|
158
194
|
{},
|
@@ -160,20 +196,21 @@ describe('Resource browser input', () => {
|
|
160
196
|
});
|
161
197
|
|
162
198
|
await waitFor(() => {
|
163
|
-
expect(mockUseResolveResource).toHaveBeenCalledWith(resource.resourceId, source);
|
199
|
+
expect(mockUseResolveResource).toHaveBeenCalledWith(resource.resourceId, calculateExpectedSource(source));
|
164
200
|
});
|
165
201
|
});
|
166
202
|
|
167
203
|
it('onModalStateChange called with false will reset the Source and Plugin', async () => {
|
168
|
-
const
|
169
|
-
|
204
|
+
const sourcesInput = [mockSource({ type: 'dam' }), mockSource()];
|
205
|
+
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
206
|
+
mockRequestSources.mockResolvedValueOnce(sourcesInput);
|
170
207
|
renderComponent();
|
171
208
|
|
172
209
|
// Expect no source or plugin
|
173
210
|
await waitFor(() => {
|
174
211
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
175
212
|
expect.objectContaining({
|
176
|
-
sources,
|
213
|
+
sources: calculatedSources,
|
177
214
|
source: null,
|
178
215
|
plugin: null,
|
179
216
|
}),
|
@@ -185,14 +222,14 @@ describe('Resource browser input', () => {
|
|
185
222
|
const { setSource } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
186
223
|
// Invoke it
|
187
224
|
act(() => {
|
188
|
-
setSource(
|
225
|
+
setSource(calculatedSources[0]);
|
189
226
|
});
|
190
227
|
|
191
228
|
// Expect the source and plugin to be loaded
|
192
229
|
await waitFor(() => {
|
193
230
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
194
231
|
expect.objectContaining({
|
195
|
-
source:
|
232
|
+
source: calculatedSources[0],
|
196
233
|
plugin: mockDamPlugin,
|
197
234
|
}),
|
198
235
|
{},
|
@@ -207,9 +244,10 @@ describe('Resource browser input', () => {
|
|
207
244
|
await waitFor(() => {
|
208
245
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
209
246
|
expect.objectContaining({
|
210
|
-
sources,
|
247
|
+
sources: calculatedSources,
|
211
248
|
source: null,
|
212
249
|
plugin: null,
|
250
|
+
pluginMode: null,
|
213
251
|
}),
|
214
252
|
{},
|
215
253
|
);
|
@@ -225,7 +263,7 @@ describe('Resource browser input', () => {
|
|
225
263
|
await waitFor(() => {
|
226
264
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
227
265
|
expect.objectContaining({
|
228
|
-
source: sources[0],
|
266
|
+
source: calculateExpectedSource(sources[0]),
|
229
267
|
plugin: mockDamPlugin,
|
230
268
|
}),
|
231
269
|
{},
|
@@ -241,7 +279,7 @@ describe('Resource browser input', () => {
|
|
241
279
|
await waitFor(() => {
|
242
280
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
243
281
|
expect.objectContaining({
|
244
|
-
source: sources[0],
|
282
|
+
source: calculateExpectedSource(sources[0]),
|
245
283
|
plugin: mockDamPlugin,
|
246
284
|
}),
|
247
285
|
{},
|
@@ -249,16 +287,72 @@ describe('Resource browser input', () => {
|
|
249
287
|
});
|
250
288
|
});
|
251
289
|
|
290
|
+
it('onModalStateChange called with false will reset Source and Plugin if only one source exists AND search is enabled', async () => {
|
291
|
+
const sourcesInput = [mockSource({ type: 'dam' })];
|
292
|
+
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
293
|
+
mockRequestSources.mockResolvedValueOnce(sourcesInput);
|
294
|
+
renderComponent({}, true);
|
295
|
+
|
296
|
+
// Expect no source or plugin
|
297
|
+
await waitFor(() => {
|
298
|
+
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
299
|
+
expect.objectContaining({
|
300
|
+
sources: calculatedSources,
|
301
|
+
source: null,
|
302
|
+
plugin: null,
|
303
|
+
}),
|
304
|
+
{},
|
305
|
+
);
|
306
|
+
});
|
307
|
+
|
308
|
+
// Get the provided callback
|
309
|
+
const { setSource } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
310
|
+
// Invoke it
|
311
|
+
act(() => {
|
312
|
+
setSource(calculatedSources[0]);
|
313
|
+
});
|
314
|
+
|
315
|
+
// Expect the source and plugin to be loaded
|
316
|
+
await waitFor(() => {
|
317
|
+
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
318
|
+
expect.objectContaining({
|
319
|
+
source: calculatedSources[0],
|
320
|
+
plugin: mockDamPlugin,
|
321
|
+
}),
|
322
|
+
{},
|
323
|
+
);
|
324
|
+
});
|
325
|
+
|
326
|
+
// Invoke modal close callback
|
327
|
+
const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
328
|
+
onModalStateChange(false);
|
329
|
+
|
330
|
+
// Expect no source or plugin
|
331
|
+
await waitFor(() => {
|
332
|
+
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
333
|
+
expect.objectContaining({
|
334
|
+
sources: calculatedSources,
|
335
|
+
source: null,
|
336
|
+
plugin: null,
|
337
|
+
pluginMode: null,
|
338
|
+
}),
|
339
|
+
{},
|
340
|
+
);
|
341
|
+
});
|
342
|
+
});
|
343
|
+
|
252
344
|
it('onModalStateChange called with false will not reset the Source and Plugin if a value exists', async () => {
|
253
|
-
const
|
254
|
-
|
255
|
-
|
345
|
+
const sourcesInput = [mockSource({ type: 'dam' }), mockSource()];
|
346
|
+
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
347
|
+
mockRequestSources.mockResolvedValueOnce(sourcesInput);
|
348
|
+
|
349
|
+
renderComponent({ value: { sourceId: sourcesInput[0].id, resourceId: '123456' } });
|
256
350
|
|
257
351
|
await waitFor(() => {
|
258
352
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
259
353
|
expect.objectContaining({
|
260
|
-
sources,
|
261
|
-
source:
|
354
|
+
sources: calculatedSources,
|
355
|
+
source: calculatedSources[0],
|
262
356
|
plugin: mockDamPlugin,
|
263
357
|
}),
|
264
358
|
{},
|
@@ -274,7 +368,7 @@ describe('Resource browser input', () => {
|
|
274
368
|
await waitFor(() => {
|
275
369
|
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
276
370
|
expect.objectContaining({
|
277
|
-
source:
|
371
|
+
source: calculatedSources[0],
|
278
372
|
plugin: mockDamPlugin,
|
279
373
|
}),
|
280
374
|
{},
|
package/src/index.stories.tsx
CHANGED
@@ -37,7 +37,11 @@ const Template: StoryFn<typeof ResourceBrowser> = (props) => {
|
|
37
37
|
return (
|
38
38
|
<div className="w-[400px] m-3">
|
39
39
|
<ResourceBrowserContextProvider
|
40
|
-
value={{
|
40
|
+
value={{
|
41
|
+
onRequestSources: onRequestSources as () => Promise<ResourceBrowserSource[]>,
|
42
|
+
plugins,
|
43
|
+
searchEnabled: props.searchEnabled,
|
44
|
+
}}
|
41
45
|
>
|
42
46
|
<ResourceBrowser {...props} value={resource} onChange={onChange} onClear={undefined} />
|
43
47
|
</ResourceBrowserContextProvider>
|
@@ -55,6 +59,7 @@ Primary.args = {
|
|
55
59
|
callbackWait: 0,
|
56
60
|
singleSource: false,
|
57
61
|
headerPortal: false,
|
62
|
+
searchEnabled: false,
|
58
63
|
};
|
59
64
|
|
60
65
|
export const Selected = Template.bind({});
|
@@ -77,3 +82,9 @@ SingleSource.args = {
|
|
77
82
|
...Primary.args,
|
78
83
|
singleSource: true,
|
79
84
|
};
|
85
|
+
|
86
|
+
export const SearchEnabled = Template.bind({});
|
87
|
+
SearchEnabled.args = {
|
88
|
+
...Primary.args,
|
89
|
+
searchEnabled: true,
|
90
|
+
};
|
package/src/index.tsx
CHANGED
@@ -1,12 +1,31 @@
|
|
1
1
|
import React, { useState, useContext, useEffect, useCallback } from 'react';
|
2
2
|
|
3
3
|
import { ResourceBrowserContext, ResourceBrowserContextProvider } from './ResourceBrowserContext/ResourceBrowserContext';
|
4
|
-
import {
|
4
|
+
import {
|
5
|
+
PluginLaunchMode,
|
6
|
+
ResourceBrowserUnresolvedResource,
|
7
|
+
ResourceBrowserResource,
|
8
|
+
ResourceBrowserPlugin,
|
9
|
+
ResourceBrowserSourceWithPlugin,
|
10
|
+
} from './types';
|
5
11
|
import { useSources } from './Hooks/useSources';
|
6
12
|
import { PluginRender } from './Plugin/Plugin';
|
7
13
|
import { AuthProvider, useAuthContext, AuthContext } from './ResourceBrowserContext/AuthProvider';
|
8
14
|
|
9
|
-
|
15
|
+
import BrowseToSource from './BrowseToSource/BrowseToSource';
|
16
|
+
import SourceDropdown from './SourceDropdown/SourceDropdown';
|
17
|
+
import SourceDropdownContainer from './SourceDropdownContainer/SourceDropdownContainer';
|
18
|
+
|
19
|
+
export {
|
20
|
+
ResourceBrowserContext,
|
21
|
+
ResourceBrowserContextProvider,
|
22
|
+
useAuthContext,
|
23
|
+
AuthProvider,
|
24
|
+
AuthContext,
|
25
|
+
BrowseToSource,
|
26
|
+
SourceDropdown,
|
27
|
+
SourceDropdownContainer,
|
28
|
+
};
|
10
29
|
export * from './types';
|
11
30
|
|
12
31
|
export type ResourceBrowserProps = {
|
@@ -21,24 +40,26 @@ export type ResourceBrowserProps = {
|
|
21
40
|
export const ResourceBrowser = (props: ResourceBrowserProps) => {
|
22
41
|
const { value } = props;
|
23
42
|
const [error, setError] = useState<Error | null>(null);
|
24
|
-
const { onRequestSources, plugins } = useContext(ResourceBrowserContext);
|
43
|
+
const { onRequestSources, searchEnabled, plugins } = useContext(ResourceBrowserContext);
|
25
44
|
|
26
45
|
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
27
|
-
const [source, setSource] = useState<
|
46
|
+
const [source, setSource] = useState<ResourceBrowserSourceWithPlugin | null>(null);
|
47
|
+
const [mode, setMode] = useState<PluginLaunchMode | null>(null);
|
28
48
|
const { data: sources, isLoading, error: sourcesError } = useSources({ onRequestSources, plugins });
|
29
49
|
const [plugin, setPlugin] = useState<ResourceBrowserPlugin | null>(null);
|
30
50
|
|
31
51
|
// MainContainer will render a list of sources of one is not provided to it, callback to allow it to set the source once a user selects
|
32
52
|
const handleSourceSelect = useCallback(
|
33
|
-
(source:
|
53
|
+
(source: ResourceBrowserSourceWithPlugin, mode?: PluginLaunchMode) => {
|
34
54
|
setSource(source);
|
55
|
+
setMode(mode || null);
|
35
56
|
},
|
36
|
-
[setSource],
|
57
|
+
[setSource, setMode],
|
37
58
|
);
|
38
59
|
|
39
|
-
//
|
60
|
+
// If an existing resource is passed in auto select its source
|
40
61
|
useEffect(() => {
|
41
|
-
let source:
|
62
|
+
let source: ResourceBrowserSourceWithPlugin | null = null;
|
42
63
|
setError(null);
|
43
64
|
|
44
65
|
// If there is a provided value try to use its source
|
@@ -50,34 +71,38 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
|
|
50
71
|
// Set an error as the passed in value's source wasnt returned by onRequestSources
|
51
72
|
setError(new Error('Unable to find resource source.'));
|
52
73
|
}
|
53
|
-
} else if (sources?.length === 1) {
|
54
|
-
// If only one source is passed
|
74
|
+
} else if (sources?.length === 1 && !searchEnabled) {
|
75
|
+
// If only one source is passed and search is not enabled select it automatically
|
55
76
|
source = sources[0];
|
56
77
|
}
|
57
78
|
|
58
79
|
setSource(source);
|
80
|
+
setMode(null); // Passed in resource will always use the default mode
|
59
81
|
}, [value, isLoading, sources, setSource, setError]);
|
60
82
|
|
83
|
+
// When a source is selected update our plugin reference to match (legacy support)
|
84
|
+
// the plugin is now attached to the source directly when fetched from the context so use that instead when possible
|
61
85
|
useEffect(() => {
|
62
|
-
if (source) {
|
63
|
-
|
64
|
-
return plugin.type === source.type;
|
65
|
-
});
|
66
|
-
setPlugin(plugin || null);
|
86
|
+
if (source?.plugin) {
|
87
|
+
setPlugin(source.plugin);
|
67
88
|
} else {
|
68
89
|
setPlugin(null);
|
69
90
|
}
|
70
91
|
}, [plugins, source]);
|
71
92
|
|
93
|
+
// The modal has some control over it own open/closed state (for WCAG reasons) so keep this in sync with our state
|
72
94
|
const handleModalStateChange = useCallback(
|
73
95
|
(isOpen: boolean) => {
|
74
96
|
setIsModalOpen(isOpen);
|
75
97
|
},
|
76
98
|
[setIsModalOpen],
|
77
99
|
);
|
100
|
+
|
101
|
+
// If the modal closes and we dont have a value clear the source state so it goes back to the launcher on re-open
|
78
102
|
useEffect(() => {
|
79
|
-
if (!isModalOpen && !value && sources?.length > 1) {
|
103
|
+
if (!isModalOpen && !value && (sources?.length > 1 || searchEnabled)) {
|
80
104
|
setSource(null);
|
105
|
+
setMode(null);
|
81
106
|
}
|
82
107
|
}, [sources, isModalOpen]);
|
83
108
|
|
@@ -94,6 +119,8 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
|
|
94
119
|
isLoading={isLoading}
|
95
120
|
error={sourcesError || error}
|
96
121
|
plugin={plugin}
|
122
|
+
pluginMode={mode}
|
123
|
+
searchEnabled={searchEnabled}
|
97
124
|
useResource={() => {
|
98
125
|
return {
|
99
126
|
data: null,
|
@@ -116,6 +143,8 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
|
|
116
143
|
isLoading={isLoading}
|
117
144
|
error={error}
|
118
145
|
plugin={plugin}
|
146
|
+
pluginMode={mode}
|
147
|
+
searchEnabled={searchEnabled}
|
119
148
|
useResource={thisPlugin.useResolveResource}
|
120
149
|
isModalOpen={isModalOpen}
|
121
150
|
onModalStateChange={handleModalStateChange}
|
package/src/types.ts
CHANGED
@@ -20,10 +20,25 @@ export interface ResourceBrowserSource {
|
|
20
20
|
type: ResourceBrowserPluginType;
|
21
21
|
}
|
22
22
|
|
23
|
+
export interface ResourceBrowserSourceWithPlugin extends ResourceBrowserSource {
|
24
|
+
// Sources plugin for quick reference
|
25
|
+
plugin: ResourceBrowserPlugin;
|
26
|
+
}
|
27
|
+
|
23
28
|
export interface ResourceBrowserSourceWithConfig extends ResourceBrowserSource {
|
24
29
|
configuration?: AuthenticationConfiguration;
|
25
30
|
}
|
26
31
|
|
32
|
+
export enum PluginLaunchModeType {
|
33
|
+
'Browse' = 'browse',
|
34
|
+
'Search' = 'search',
|
35
|
+
}
|
36
|
+
|
37
|
+
export type PluginLaunchMode = {
|
38
|
+
type: PluginLaunchModeType;
|
39
|
+
args?: ResourceBrowserSearchUIArgs | ResourceBrowserUIArgs;
|
40
|
+
};
|
41
|
+
|
27
42
|
export type ResourceBrowserUnresolvedResource = {
|
28
43
|
sourceId: string;
|
29
44
|
resourceId: string;
|
@@ -48,12 +63,31 @@ export type ResourceBrowserSelectedState = {
|
|
48
63
|
description: Array<ReactElement>;
|
49
64
|
};
|
50
65
|
|
51
|
-
|
66
|
+
type ResourceBrowserProps = {
|
52
67
|
source: ResourceBrowserSource;
|
68
|
+
onSourceSelect(source: ResourceBrowserSource, mode?: PluginLaunchMode): void;
|
69
|
+
sources: ResourceBrowserSourceWithPlugin[];
|
53
70
|
allowedTypes?: string[];
|
54
71
|
headerPortal?: Element;
|
55
|
-
preselectedResource?: ResourceBrowserResource;
|
56
72
|
onSelected: (resource: ResourceBrowserResource) => void;
|
73
|
+
searchEnabled: boolean;
|
74
|
+
};
|
75
|
+
|
76
|
+
export type ResourceBrowserUIArgs = {
|
77
|
+
preselectedResource?: ResourceBrowserResource;
|
78
|
+
browseTo?: ResourceBrowserUnresolvedResource;
|
79
|
+
};
|
80
|
+
export type ResourceBrowserUIProps = ResourceBrowserProps & ResourceBrowserUIArgs;
|
81
|
+
|
82
|
+
export type ResourceBrowserSearchUIArgs = {
|
83
|
+
query?: string;
|
84
|
+
};
|
85
|
+
export type ResourceBrowserSearchUIProps = ResourceBrowserProps & ResourceBrowserSearchUIArgs;
|
86
|
+
|
87
|
+
export type ResourceBrowserLauncherProps = {
|
88
|
+
source: ResourceBrowserSource;
|
89
|
+
onSearch: (query: string) => void;
|
90
|
+
onBrowse: (browseTo?: ResourceBrowserUnresolvedResource) => void;
|
57
91
|
};
|
58
92
|
|
59
93
|
export type useResolveResourceResponse = {
|
@@ -72,9 +106,15 @@ export interface ResourceBrowserPlugin {
|
|
72
106
|
// Setting to create a header portal
|
73
107
|
createHeaderPortal?: boolean;
|
74
108
|
|
75
|
-
/** React Functional Component to provde the UI to render to allow a user to
|
109
|
+
/** React Functional Component to provde the UI to render to allow a user to browse for resource to use */
|
76
110
|
sourceBrowserComponent: () => React.FunctionComponent<ResourceBrowserUIProps>;
|
77
111
|
|
112
|
+
/** React Functional Component to provde the UI to render to allow a user to search for resource to use */
|
113
|
+
sourceSearchComponent: () => React.FunctionComponent<ResourceBrowserSearchUIProps>;
|
114
|
+
|
115
|
+
/** React Functional Component to provde the sources launcher view */
|
116
|
+
renderResourceLauncher: () => React.FunctionComponent<ResourceBrowserLauncherProps>;
|
117
|
+
|
78
118
|
/** Function to provde the the summary information to show what resource is currently selected */
|
79
119
|
renderSelectedResource: (resource: ResourceBrowserResource) => Promise<ResourceBrowserSelectedState>;
|
80
120
|
|
package/.eslintrc
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"extends": ["@squiz"],
|
3
|
-
"plugins": ["import", "prettier"],
|
4
|
-
"rules": {
|
5
|
-
"prettier/prettier": "error",
|
6
|
-
},
|
7
|
-
"settings": {
|
8
|
-
"import/parsers": {
|
9
|
-
"@typescript-eslint/parser": [".ts", ".tsx"],
|
10
|
-
},
|
11
|
-
"import/resolver": {
|
12
|
-
"typescript": {
|
13
|
-
"alwaysTryTypes": true,
|
14
|
-
}
|
15
|
-
}
|
16
|
-
},
|
17
|
-
"ignorePatterns": ["**/lib/"],
|
18
|
-
"overrides": [
|
19
|
-
{
|
20
|
-
"files": ["*.spec.ts", "*.spec.tsx"],
|
21
|
-
"rules": {
|
22
|
-
"no-var": 0,
|
23
|
-
"import/no-extraneous-dependencies": 0,
|
24
|
-
}
|
25
|
-
},
|
26
|
-
{
|
27
|
-
"files": ["*.config.ts", "*.config.js", "build.js"],
|
28
|
-
"rules": {
|
29
|
-
"import/no-extraneous-dependencies": 0,
|
30
|
-
}
|
31
|
-
},
|
32
|
-
{
|
33
|
-
"files": ["*.stories.ts", "*.stories.tsx"],
|
34
|
-
"rules": {
|
35
|
-
"import/no-extraneous-dependencies": 0,
|
36
|
-
"no-console": "off"
|
37
|
-
}
|
38
|
-
}
|
39
|
-
]
|
40
|
-
}
|
package/.storybook/main.ts
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
import type { StorybookConfig } from '@storybook/react-vite';
|
2
|
-
const config: StorybookConfig = {
|
3
|
-
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
4
|
-
addons: [
|
5
|
-
'@storybook/addon-links',
|
6
|
-
'@storybook/addon-essentials',
|
7
|
-
'@storybook/addon-interactions',
|
8
|
-
{
|
9
|
-
name: '@storybook/addon-styling',
|
10
|
-
options: {
|
11
|
-
postCss: true,
|
12
|
-
},
|
13
|
-
},
|
14
|
-
],
|
15
|
-
framework: {
|
16
|
-
name: '@storybook/react-vite',
|
17
|
-
options: {},
|
18
|
-
},
|
19
|
-
docs: {
|
20
|
-
autodocs: 'tag',
|
21
|
-
},
|
22
|
-
};
|
23
|
-
export default config;
|
@@ -1 +0,0 @@
|
|
1
|
-
<body class="squiz-rb-scope"></body>
|
package/.storybook/preview.ts
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
import type { Preview } from '@storybook/react';
|
2
|
-
import '../src/index.scss';
|
3
|
-
|
4
|
-
const preview: Preview = {
|
5
|
-
parameters: {
|
6
|
-
actions: { argTypesRegex: '^on[A-Z].*' },
|
7
|
-
controls: {
|
8
|
-
matchers: {
|
9
|
-
color: /(background|color)$/i,
|
10
|
-
date: /Date$/,
|
11
|
-
},
|
12
|
-
},
|
13
|
-
},
|
14
|
-
};
|
15
|
-
|
16
|
-
export default preview;
|