@squiz/resource-browser 2.4.12 → 3.0.0-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.
Files changed (128) hide show
  1. package/README.md +4 -0
  2. package/lib/BrowseToSource/BrowseToSource.d.ts +8 -0
  3. package/lib/BrowseToSource/BrowseToSource.js +50 -0
  4. package/lib/Hooks/useAuth.js +11 -15
  5. package/lib/Hooks/useSelectedState.js +3 -7
  6. package/lib/Hooks/useSources.d.ts +2 -2
  7. package/lib/Hooks/useSources.js +19 -9
  8. package/lib/Icons/AdsClickIcon.d.ts +4 -0
  9. package/lib/Icons/AdsClickIcon.js +5 -0
  10. package/lib/Icons/ArrowDownIcon.d.ts +4 -0
  11. package/lib/Icons/ArrowDownIcon.js +5 -0
  12. package/lib/Icons/CircledLoopIcon.js +4 -11
  13. package/lib/MainContainer/MainContainer.d.ts +6 -4
  14. package/lib/MainContainer/MainContainer.js +33 -52
  15. package/lib/Plugin/Plugin.js +7 -14
  16. package/lib/ResourceBrowserContext/AuthProvider.js +9 -37
  17. package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +1 -0
  18. package/lib/ResourceBrowserContext/ResourceBrowserContext.js +10 -39
  19. package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +6 -4
  20. package/lib/ResourceBrowserInput/ResourceBrowserInput.js +5 -12
  21. package/lib/ResourceLauncher/ResourceLauncher.d.ts +8 -0
  22. package/lib/ResourceLauncher/ResourceLauncher.js +11 -0
  23. package/lib/ResourcePicker/ResourcePicker.js +20 -27
  24. package/lib/ResourcePicker/States/Error.js +6 -13
  25. package/lib/ResourcePicker/States/Loading.js +4 -11
  26. package/lib/ResourcePicker/States/Selected.js +12 -19
  27. package/lib/SourceDropdown/SourceDropdown.d.ts +2 -2
  28. package/lib/SourceDropdown/SourceDropdown.js +22 -48
  29. package/lib/SourceDropdownContainer/SourceDropdownContainer.d.ts +5 -0
  30. package/lib/SourceDropdownContainer/SourceDropdownContainer.js +12 -0
  31. package/lib/SourceList/SourceList.js +11 -16
  32. package/lib/index.css +102 -26
  33. package/lib/index.d.ts +4 -1
  34. package/lib/index.js +40 -66
  35. package/lib/types.d.ts +35 -3
  36. package/lib/types.js +5 -2
  37. package/lib/utils/authUtils.js +9 -16
  38. package/lib-esm/BrowseToSource/BrowseToSource.d.ts +8 -0
  39. package/lib-esm/BrowseToSource/BrowseToSource.js +50 -0
  40. package/lib-esm/Hooks/useAuth.d.ts +7 -0
  41. package/lib-esm/Hooks/useAuth.js +54 -0
  42. package/lib-esm/Hooks/useSelectedState.d.ts +15 -0
  43. package/lib-esm/Hooks/useSelectedState.js +12 -0
  44. package/lib-esm/Hooks/useSources.d.ts +14 -0
  45. package/lib-esm/Hooks/useSources.js +44 -0
  46. package/lib-esm/Icons/AdsClickIcon.d.ts +4 -0
  47. package/lib-esm/Icons/AdsClickIcon.js +5 -0
  48. package/lib-esm/Icons/ArrowDownIcon.d.ts +4 -0
  49. package/lib-esm/Icons/ArrowDownIcon.js +5 -0
  50. package/lib-esm/Icons/CircledLoopIcon.d.ts +4 -0
  51. package/lib-esm/Icons/CircledLoopIcon.js +5 -0
  52. package/lib-esm/MainContainer/MainContainer.d.ts +19 -0
  53. package/lib-esm/MainContainer/MainContainer.js +43 -0
  54. package/lib-esm/Plugin/Plugin.d.ts +13 -0
  55. package/lib-esm/Plugin/Plugin.js +12 -0
  56. package/lib-esm/ResourceBrowserContext/AuthProvider.d.ts +16 -0
  57. package/lib-esm/ResourceBrowserContext/AuthProvider.js +18 -0
  58. package/lib-esm/ResourceBrowserContext/ResourceBrowserContext.d.ts +15 -0
  59. package/lib-esm/ResourceBrowserContext/ResourceBrowserContext.js +26 -0
  60. package/lib-esm/ResourceBrowserInput/ResourceBrowserInput.d.ts +26 -0
  61. package/lib-esm/ResourceBrowserInput/ResourceBrowserInput.js +9 -0
  62. package/lib-esm/ResourceLauncher/ResourceLauncher.d.ts +8 -0
  63. package/lib-esm/ResourceLauncher/ResourceLauncher.js +11 -0
  64. package/lib-esm/ResourcePicker/ResourcePicker.d.ts +16 -0
  65. package/lib-esm/ResourcePicker/ResourcePicker.js +25 -0
  66. package/lib-esm/ResourcePicker/States/Error.d.ts +7 -0
  67. package/lib-esm/ResourcePicker/States/Error.js +6 -0
  68. package/lib-esm/ResourcePicker/States/Loading.d.ts +2 -0
  69. package/lib-esm/ResourcePicker/States/Loading.js +4 -0
  70. package/lib-esm/ResourcePicker/States/Selected.d.ts +15 -0
  71. package/lib-esm/ResourcePicker/States/Selected.js +20 -0
  72. package/lib-esm/SourceDropdown/SourceDropdown.d.ts +7 -0
  73. package/lib-esm/SourceDropdown/SourceDropdown.js +46 -0
  74. package/lib-esm/SourceDropdownContainer/SourceDropdownContainer.d.ts +5 -0
  75. package/lib-esm/SourceDropdownContainer/SourceDropdownContainer.js +12 -0
  76. package/lib-esm/SourceList/SourceList.d.ts +8 -0
  77. package/lib-esm/SourceList/SourceList.js +16 -0
  78. package/lib-esm/index.d.ts +18 -0
  79. package/lib-esm/index.js +79 -0
  80. package/lib-esm/types.d.ts +97 -0
  81. package/lib-esm/types.js +5 -0
  82. package/lib-esm/utils/authUtils.d.ts +5 -0
  83. package/lib-esm/utils/authUtils.js +31 -0
  84. package/package.json +18 -6
  85. package/src/BrowseToSource/BrowseToSource.spec.tsx +111 -0
  86. package/src/BrowseToSource/BrowseToSource.stories.tsx +29 -0
  87. package/src/BrowseToSource/BrowseToSource.tsx +111 -0
  88. package/src/Hooks/useSources.spec.ts +8 -4
  89. package/src/Hooks/useSources.ts +28 -13
  90. package/src/Icons/AdsClickIcon.tsx +11 -0
  91. package/src/Icons/ArrowDownIcon.tsx +11 -0
  92. package/src/MainContainer/MainContainer.spec.tsx +322 -108
  93. package/src/MainContainer/MainContainer.tsx +67 -27
  94. package/src/Plugin/Plugin.spec.tsx +2 -0
  95. package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +3 -0
  96. package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +2 -0
  97. package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +7 -0
  98. package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +16 -3
  99. package/src/ResourceLauncher/ResourceLauncher.spec.tsx +65 -0
  100. package/src/ResourceLauncher/ResourceLauncher.tsx +31 -0
  101. package/src/SourceDropdown/SourceDropdown.stories.tsx +1 -2
  102. package/src/SourceDropdown/SourceDropdown.tsx +8 -8
  103. package/src/SourceDropdownContainer/SourceDropdownContainer.spec.tsx +50 -0
  104. package/src/SourceDropdownContainer/SourceDropdownContainer.stories.tsx +62 -0
  105. package/src/SourceDropdownContainer/SourceDropdownContainer.tsx +27 -0
  106. package/src/__mocks__/MockModels.ts +16 -2
  107. package/src/__mocks__/PluginExample.tsx +8 -0
  108. package/src/__mocks__/StorybookHelpers.tsx +37 -1
  109. package/src/__mocks__/renderWithContext.tsx +1 -0
  110. package/src/index.spec.tsx +135 -41
  111. package/src/index.stories.tsx +12 -1
  112. package/src/index.tsx +45 -16
  113. package/src/types.ts +43 -3
  114. package/.eslintrc +0 -40
  115. package/.storybook/main.ts +0 -23
  116. package/.storybook/preview-body.html +0 -1
  117. package/.storybook/preview-head.html +0 -12
  118. package/.storybook/preview.ts +0 -16
  119. package/CHANGELOG.md +0 -244
  120. package/LICENSE.md +0 -15
  121. package/build.js +0 -21
  122. package/jest.config.ts +0 -30
  123. package/postcss.config.js +0 -21
  124. package/tailwind.config.cjs +0 -98
  125. package/tsconfig.json +0 -22
  126. package/tsconfig.storybook.json +0 -4
  127. package/tsconfig.test.json +0 -12
  128. package/vite.config.js +0 -20
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
2
  import { render, screen, waitFor, act, fireEvent } from '@testing-library/react';
3
3
  import MainContainer from './MainContainer';
4
- import { ResourceBrowserPlugin, ResourceBrowserSource } from '../types';
4
+ import { ResourceBrowserPlugin, ResourceBrowserSourceWithPlugin, PluginLaunchModeType } from '../types';
5
5
 
6
- import { mockSource, mockResource } from '../__mocks__/MockModels';
6
+ import { mockSource, mockResource, mockSourceWithPlugin } from '../__mocks__/MockModels';
7
7
 
8
8
  import SourceList from '../SourceList/SourceList'; // Import Functional Component
9
9
  jest.mock('../SourceList/SourceList'); // Mock the Functional Component
@@ -11,12 +11,23 @@ const ActualSourceList = jest.requireActual('../SourceList/SourceList').default;
11
11
  const MockSourceList = SourceList as jest.MockedFunction<typeof SourceList>; // Cast the mocked function so TS stops complaining
12
12
  MockSourceList.mockImplementation(ActualSourceList); // Return the actual function unless overridden
13
13
 
14
+ import ResourceLauncher from '../ResourceLauncher/ResourceLauncher'; // Import Functional Component
15
+ jest.mock('../ResourceLauncher/ResourceLauncher'); // Mock the Functional Component
16
+ const ActualResourceLauncher = jest.requireActual('../ResourceLauncher/ResourceLauncher').default; // Grab the real copy of it as normally we don't want the mock
17
+ const MockResourceLauncher = ResourceLauncher as jest.MockedFunction<typeof ResourceLauncher>; // Cast the mocked function so TS stops complaining
18
+ MockResourceLauncher.mockImplementation(ActualResourceLauncher); // Return the actual function unless overridden
19
+
14
20
  import SourceDropdown from '../SourceDropdown/SourceDropdown'; // Import Functional Component
15
21
  jest.mock('../SourceDropdown/SourceDropdown'); // Mock the Functional Component
16
22
  const ActualSourceDropdown = jest.requireActual('../SourceDropdown/SourceDropdown').default; // Grab the real copy of it as normally we don't want the mock
17
23
  const MockSourceDropdown = SourceDropdown as jest.MockedFunction<typeof SourceDropdown>; // Cast the mocked function so TS stops complaining
18
24
  MockSourceDropdown.mockImplementation(ActualSourceDropdown); // Return the actual function unless overridden
19
25
 
26
+ import SourceDropdownContainer from '../SourceDropdownContainer/SourceDropdownContainer';
27
+ jest.mock('../SourceDropdownContainer/SourceDropdownContainer', () => {
28
+ return jest.fn().mockReturnValue(<div />);
29
+ });
30
+
20
31
  const mockSourceBrowserComponent = jest.fn();
21
32
  const defaultProps = {
22
33
  title: '',
@@ -29,6 +40,8 @@ const defaultProps = {
29
40
  onClose: jest.fn(),
30
41
  preselectedResource: null,
31
42
  plugin: null,
43
+ pluginMode: null,
44
+ searchEnabled: false,
32
45
  };
33
46
 
34
47
  describe('MainContainer', () => {
@@ -41,13 +54,13 @@ describe('MainContainer', () => {
41
54
  expect(onClose).toHaveBeenCalled();
42
55
  });
43
56
 
44
- describe('no source selected', () => {
45
- it('should render Environment Selector title', () => {
46
- render(<MainContainer {...defaultProps} title="Select MyAsset" />);
47
- expect(screen.getByText('Environment Selector')).toBeInTheDocument();
48
- expect(screen.queryByText('Select MyAsset')).not.toBeInTheDocument();
49
- });
57
+ it('should render Environment Selector title', () => {
58
+ render(<MainContainer {...defaultProps} title="Select MyAsset" />);
59
+ expect(screen.getByText('Select MyAsset')).toBeInTheDocument();
60
+ expect(screen.queryByText('Environment Selector')).not.toBeInTheDocument();
61
+ });
50
62
 
63
+ describe('no source selected', () => {
51
64
  it('should not render source selector dropdown', () => {
52
65
  render(<MainContainer {...defaultProps} />);
53
66
  expect(MockSourceDropdown).not.toHaveBeenCalled();
@@ -58,8 +71,15 @@ describe('MainContainer', () => {
58
71
  expect(mockSourceBrowserComponent).not.toHaveBeenCalled();
59
72
  });
60
73
 
74
+ it('should render resource launcher list when searchEnabled using provided props', () => {
75
+ const sources: ResourceBrowserSourceWithPlugin[] = [];
76
+ const onSourceSelect = jest.fn();
77
+ render(<MainContainer {...defaultProps} searchEnabled={true} sources={sources} onSourceSelect={onSourceSelect} />);
78
+ expect(MockResourceLauncher).toHaveBeenCalledWith({ sources, onSourceSelect }, {});
79
+ });
80
+
61
81
  it('should render source list using provided props', () => {
62
- const sources: ResourceBrowserSource[] = [];
82
+ const sources: ResourceBrowserSourceWithPlugin[] = [];
63
83
  const onSourceSelect = jest.fn();
64
84
  render(<MainContainer {...defaultProps} sources={sources} onSourceSelect={onSourceSelect} />);
65
85
  expect(MockSourceList).toHaveBeenCalledWith({ sources, onSourceSelect }, {});
@@ -68,136 +88,330 @@ describe('MainContainer', () => {
68
88
 
69
89
  describe('source selected', () => {
70
90
  const source = mockSource({ type: 'dam' });
71
- const plugin = {
72
- type: 'dam',
73
- createHeaderPortal: false,
74
- sourceBrowserComponent: mockSourceBrowserComponent,
75
- renderSelectedResource: jest.fn(),
76
- useResolveResource: jest.fn(),
77
- } as ResourceBrowserPlugin;
78
- it('should render Environment Selector title', () => {
79
- render(<MainContainer {...defaultProps} title="Select MyAsset" selectedSource={source} plugin={plugin} />);
80
- expect(screen.getByText('Select MyAsset')).toBeInTheDocument();
81
- expect(screen.queryByText('Environment Selector')).not.toBeInTheDocument();
82
- });
83
91
 
84
- it('should render source selector dropdown if more than one source exists', () => {
85
- const sources: ResourceBrowserSource[] = [mockSource(), mockSource()];
86
- const onSourceSelect = jest.fn();
92
+ it('it should create a default source switcher if header portal not requested and sources > 1', async () => {
93
+ const source1 = mockSourceWithPlugin();
94
+ const source2 = mockSourceWithPlugin();
95
+
96
+ const plugin = {
97
+ type: 'dam',
98
+ createHeaderPortal: false,
99
+ sourceBrowserComponent: jest.fn,
100
+ renderSelectedResource: jest.fn(),
101
+ useResolveResource: jest.fn(),
102
+ sourceSearchComponent: jest.fn(),
103
+ renderResourceLauncher: jest.fn(),
104
+ } as ResourceBrowserPlugin;
87
105
  render(
88
106
  <MainContainer
89
107
  {...defaultProps}
90
- sources={sources}
91
- selectedSource={source}
108
+ sources={[source1, source2]}
109
+ selectedSource={source1}
92
110
  plugin={plugin}
93
- onSourceSelect={onSourceSelect}
111
+ pluginMode={{ type: PluginLaunchModeType.Browse }}
94
112
  />,
95
113
  );
96
- expect(MockSourceDropdown).toHaveBeenCalledWith({ sources, selectedSource: source, onSourceSelect }, {});
114
+
115
+ expect(SourceDropdownContainer).toHaveBeenCalled();
97
116
  });
98
117
 
99
- it('should not render source selector dropdown if one source exists', () => {
100
- const sources: ResourceBrowserSource[] = [mockSource()];
101
- const onSourceSelect = jest.fn();
118
+ it('it should not create a default source switcher if header portal not requested and sources < 2', async () => {
119
+ const source1 = mockSourceWithPlugin();
120
+
121
+ const plugin = {
122
+ type: 'dam',
123
+ createHeaderPortal: false,
124
+ sourceBrowserComponent: jest.fn,
125
+ renderSelectedResource: jest.fn(),
126
+ useResolveResource: jest.fn(),
127
+ sourceSearchComponent: jest.fn(),
128
+ renderResourceLauncher: jest.fn(),
129
+ } as ResourceBrowserPlugin;
102
130
  render(
103
131
  <MainContainer
104
132
  {...defaultProps}
105
- sources={sources}
106
- selectedSource={source}
133
+ sources={[source1]}
134
+ selectedSource={source1}
107
135
  plugin={plugin}
108
- onSourceSelect={onSourceSelect}
136
+ pluginMode={{ type: PluginLaunchModeType.Browse }}
109
137
  />,
110
138
  );
111
- expect(MockSourceDropdown).not.toHaveBeenCalled();
139
+
140
+ expect(SourceDropdownContainer).not.toHaveBeenCalled();
112
141
  });
113
142
 
114
- it('should create header portal for plugin', async () => {
115
- const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
116
- const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
117
- const plugin = {
118
- type: 'dam',
119
- createHeaderPortal: true,
120
- sourceBrowserComponent: mockSourceBrowserComponent,
121
- renderSelectedResource: jest.fn(),
122
- useResolveResource: jest.fn(),
123
- } as ResourceBrowserPlugin;
124
- render(<MainContainer {...defaultProps} selectedSource={source} plugin={plugin} />);
125
- expect(mockSourceBrowserComponent).toHaveBeenCalled();
126
-
127
- await waitFor(() => {
128
- expect(mockFunctionalComponent).toHaveBeenCalledWith(
129
- expect.objectContaining({
130
- headerPortal: expect.any(Element),
131
- }),
132
- {},
143
+ describe('Browser mode', () => {
144
+ it('should create header portal for plugin', async () => {
145
+ const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
146
+ const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
147
+ const plugin = {
148
+ type: 'dam',
149
+ createHeaderPortal: true,
150
+ sourceBrowserComponent: mockSourceBrowserComponent,
151
+ renderSelectedResource: jest.fn(),
152
+ useResolveResource: jest.fn(),
153
+ sourceSearchComponent: jest.fn(),
154
+ renderResourceLauncher: jest.fn(),
155
+ } as ResourceBrowserPlugin;
156
+ render(<MainContainer {...defaultProps} selectedSource={source} plugin={plugin} />);
157
+ expect(mockSourceBrowserComponent).toHaveBeenCalled();
158
+
159
+ await waitFor(() => {
160
+ expect(mockFunctionalComponent).toHaveBeenCalledWith(
161
+ expect.objectContaining({
162
+ headerPortal: expect.any(Element),
163
+ }),
164
+ {},
165
+ );
166
+ });
167
+ });
168
+
169
+ it('preselectedResource: should pass expected params to plugin UI generator functional component', async () => {
170
+ const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
171
+ const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
172
+ const plugin = {
173
+ type: 'dam',
174
+ createHeaderPortal: true,
175
+ sourceBrowserComponent: mockSourceBrowserComponent,
176
+ renderSelectedResource: jest.fn(),
177
+ useResolveResource: jest.fn(),
178
+ sourceSearchComponent: jest.fn(),
179
+ renderResourceLauncher: jest.fn(),
180
+ } as ResourceBrowserPlugin;
181
+
182
+ const resource = mockResource();
183
+ const props = {
184
+ allowedTypes: ['image'],
185
+ preselectedResource: resource,
186
+ };
187
+
188
+ render(<MainContainer {...defaultProps} selectedSource={source} plugin={plugin} {...props} />);
189
+ expect(mockSourceBrowserComponent).toHaveBeenCalled();
190
+
191
+ await waitFor(() => {
192
+ expect(mockFunctionalComponent).toHaveBeenCalledWith(
193
+ expect.objectContaining({
194
+ source: source,
195
+ allowedTypes: props.allowedTypes,
196
+ headerPortal: expect.any(Element),
197
+ preselectedResource: resource,
198
+ }),
199
+ {},
200
+ );
201
+ });
202
+ });
203
+
204
+ it('browseTo: should pass expected params to plugin UI generator functional component', async () => {
205
+ const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
206
+ const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
207
+ const plugin = {
208
+ type: 'dam',
209
+ createHeaderPortal: true,
210
+ sourceBrowserComponent: mockSourceBrowserComponent,
211
+ renderSelectedResource: jest.fn(),
212
+ useResolveResource: jest.fn(),
213
+ sourceSearchComponent: jest.fn(),
214
+ renderResourceLauncher: jest.fn(),
215
+ } as ResourceBrowserPlugin;
216
+
217
+ const resource = mockResource();
218
+ const browseTo = { resourceId: resource.id, sourceId: resource.source.id };
219
+ const props = {
220
+ allowedTypes: ['image'],
221
+ };
222
+
223
+ render(
224
+ <MainContainer
225
+ {...defaultProps}
226
+ selectedSource={source}
227
+ plugin={plugin}
228
+ pluginMode={{
229
+ type: PluginLaunchModeType.Browse,
230
+ args: { browseTo },
231
+ }}
232
+ {...props}
233
+ />,
133
234
  );
235
+ expect(mockSourceBrowserComponent).toHaveBeenCalled();
236
+
237
+ await waitFor(() => {
238
+ expect(mockFunctionalComponent).toHaveBeenCalledWith(
239
+ expect.objectContaining({
240
+ source: source,
241
+ allowedTypes: props.allowedTypes,
242
+ headerPortal: expect.any(Element),
243
+ browseTo,
244
+ }),
245
+ {},
246
+ );
247
+ });
134
248
  });
135
- });
136
249
 
137
- it('should pass expected params to plugin UI generator functional component', async () => {
138
- const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
139
- const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
140
- const plugin = {
141
- type: 'dam',
142
- createHeaderPortal: true,
143
- sourceBrowserComponent: mockSourceBrowserComponent,
144
- renderSelectedResource: jest.fn(),
145
- useResolveResource: jest.fn(),
146
- } as ResourceBrowserPlugin;
250
+ it('should pass onSelected to plugin UI generator FC when invoked calls change and close functions', async () => {
251
+ const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
252
+ const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
253
+ const plugin = {
254
+ type: 'dam',
255
+ createHeaderPortal: true,
256
+ sourceBrowserComponent: mockSourceBrowserComponent,
257
+ renderSelectedResource: jest.fn(),
258
+ useResolveResource: jest.fn(),
259
+ sourceSearchComponent: jest.fn(),
260
+ renderResourceLauncher: jest.fn(),
261
+ } as ResourceBrowserPlugin;
262
+
263
+ const onChange = jest.fn();
264
+ const onClose = jest.fn();
147
265
 
148
- const resource = mockResource();
149
- const props = {
150
- allowedTypes: ['image'],
151
- preselectedResource: resource,
152
- };
153
-
154
- render(<MainContainer {...defaultProps} selectedSource={source} plugin={plugin} {...props} />);
155
- expect(mockSourceBrowserComponent).toHaveBeenCalled();
156
-
157
- await waitFor(() => {
158
- expect(mockFunctionalComponent).toHaveBeenCalledWith(
159
- expect.objectContaining({
160
- source: source,
161
- allowedTypes: props.allowedTypes,
162
- headerPortal: expect.any(Element),
163
- preselectedResource: resource,
164
- }),
165
- {},
266
+ render(
267
+ <MainContainer
268
+ {...defaultProps}
269
+ searchEnabled={true}
270
+ selectedSource={source}
271
+ plugin={plugin}
272
+ onChange={onChange}
273
+ onClose={onClose}
274
+ />,
166
275
  );
276
+ expect(mockSourceBrowserComponent).toHaveBeenCalled();
277
+
278
+ await waitFor(() => {
279
+ expect(mockFunctionalComponent).toHaveBeenCalledWith(
280
+ expect.objectContaining({
281
+ onSelected: expect.any(Function),
282
+ searchEnabled: true,
283
+ }),
284
+ {},
285
+ );
286
+ });
287
+
288
+ const resource = mockResource();
289
+ const { onSelected } = mockFunctionalComponent.mock.calls[0][0];
290
+ onSelected(resource);
291
+ expect(onChange).toHaveBeenCalledWith(resource);
292
+ expect(onClose).toHaveBeenCalled();
167
293
  });
168
294
  });
169
295
 
170
- it('should pass onSelected to plugin UI generator FC when invoked calls change and close functions', async () => {
171
- const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
172
- const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
173
- const plugin = {
174
- type: 'dam',
175
- createHeaderPortal: true,
176
- sourceBrowserComponent: mockSourceBrowserComponent,
177
- renderSelectedResource: jest.fn(),
178
- useResolveResource: jest.fn(),
179
- } as ResourceBrowserPlugin;
296
+ describe('Search mode', () => {
297
+ it('should create header portal for plugin', async () => {
298
+ const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source search UI has been rendered</div>);
299
+ const mockSourceSearchComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
300
+ const plugin = {
301
+ type: 'dam',
302
+ createHeaderPortal: true,
303
+ sourceBrowserComponent: jest.fn,
304
+ renderSelectedResource: jest.fn(),
305
+ useResolveResource: jest.fn(),
306
+ sourceSearchComponent: mockSourceSearchComponent,
307
+ renderResourceLauncher: jest.fn(),
308
+ } as ResourceBrowserPlugin;
309
+ render(
310
+ <MainContainer
311
+ {...defaultProps}
312
+ selectedSource={source}
313
+ plugin={plugin}
314
+ pluginMode={{ type: PluginLaunchModeType.Search }}
315
+ />,
316
+ );
317
+ expect(mockSourceSearchComponent).toHaveBeenCalled();
180
318
 
181
- const onChange = jest.fn();
182
- const onClose = jest.fn();
319
+ await waitFor(() => {
320
+ expect(mockFunctionalComponent).toHaveBeenCalledWith(
321
+ expect.objectContaining({
322
+ headerPortal: expect.any(Element),
323
+ }),
324
+ {},
325
+ );
326
+ });
327
+ });
183
328
 
184
- render(<MainContainer {...defaultProps} selectedSource={source} plugin={plugin} onChange={onChange} onClose={onClose} />);
185
- expect(mockSourceBrowserComponent).toHaveBeenCalled();
329
+ it('should pass expected params to plugin UI generator functional component', async () => {
330
+ const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source search UI has been rendered</div>);
331
+ const mockSourceSearchComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
332
+ const plugin = {
333
+ type: 'dam',
334
+ createHeaderPortal: true,
335
+ sourceBrowserComponent: jest.fn(),
336
+ renderSelectedResource: jest.fn(),
337
+ useResolveResource: jest.fn(),
338
+ sourceSearchComponent: mockSourceSearchComponent,
339
+ renderResourceLauncher: jest.fn(),
340
+ } as ResourceBrowserPlugin;
186
341
 
187
- await waitFor(() => {
188
- expect(mockFunctionalComponent).toHaveBeenCalledWith(
189
- expect.objectContaining({
190
- onSelected: expect.any(Function),
191
- }),
192
- {},
342
+ const props = {
343
+ allowedTypes: ['image'],
344
+ };
345
+
346
+ render(
347
+ <MainContainer
348
+ {...defaultProps}
349
+ selectedSource={source}
350
+ plugin={plugin}
351
+ pluginMode={{ type: PluginLaunchModeType.Search, args: { query: 'myQuery' } }}
352
+ {...props}
353
+ />,
193
354
  );
355
+ expect(mockSourceSearchComponent).toHaveBeenCalled();
356
+
357
+ await waitFor(() => {
358
+ expect(mockFunctionalComponent).toHaveBeenCalledWith(
359
+ expect.objectContaining({
360
+ source: source,
361
+ allowedTypes: props.allowedTypes,
362
+ headerPortal: expect.any(Element),
363
+ query: 'myQuery',
364
+ }),
365
+ {},
366
+ );
367
+ });
194
368
  });
195
369
 
196
- const resource = mockResource();
197
- const { onSelected } = mockFunctionalComponent.mock.calls[0][0];
198
- onSelected(resource);
199
- expect(onChange).toHaveBeenCalledWith(resource);
200
- expect(onClose).toHaveBeenCalled();
370
+ it('should pass onSelected to plugin UI generator FC when invoked calls change and close functions', async () => {
371
+ const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source search UI has been rendered</div>);
372
+ const mockSourceSearchComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
373
+ const plugin = {
374
+ type: 'dam',
375
+ createHeaderPortal: true,
376
+ sourceBrowserComponent: jest.fn(),
377
+ renderSelectedResource: jest.fn(),
378
+ useResolveResource: jest.fn(),
379
+ sourceSearchComponent: mockSourceSearchComponent,
380
+ renderResourceLauncher: jest.fn(),
381
+ } as ResourceBrowserPlugin;
382
+
383
+ const onChange = jest.fn();
384
+ const onClose = jest.fn();
385
+
386
+ render(
387
+ <MainContainer
388
+ {...defaultProps}
389
+ selectedSource={source}
390
+ searchEnabled={true}
391
+ plugin={plugin}
392
+ pluginMode={{ type: PluginLaunchModeType.Search, args: { query: 'myQuery' } }}
393
+ onChange={onChange}
394
+ onClose={onClose}
395
+ />,
396
+ );
397
+ expect(mockSourceSearchComponent).toHaveBeenCalled();
398
+
399
+ await waitFor(() => {
400
+ expect(mockFunctionalComponent).toHaveBeenCalledWith(
401
+ expect.objectContaining({
402
+ onSelected: expect.any(Function),
403
+ searchEnabled: true,
404
+ }),
405
+ {},
406
+ );
407
+ });
408
+
409
+ const resource = mockResource();
410
+ const { onSelected } = mockFunctionalComponent.mock.calls[0][0];
411
+ onSelected(resource);
412
+ expect(onChange).toHaveBeenCalledWith(resource);
413
+ expect(onClose).toHaveBeenCalled();
414
+ });
201
415
  });
202
416
  });
203
417
  });
@@ -1,21 +1,34 @@
1
1
  import React, { useCallback, useState } from 'react';
2
2
  import { DOMAttributes, FocusableElement } from '@react-types/shared';
3
3
 
4
- import SourceDropdown from '../SourceDropdown/SourceDropdown';
5
4
  import SourceList from '../SourceList/SourceList';
6
- import { ResourceBrowserPlugin, ResourceBrowserSource, ResourceBrowserResource } from '../types';
5
+ import ResourceLauncher from '../ResourceLauncher/ResourceLauncher';
6
+ import SourceDropdownContainer from '../SourceDropdownContainer/SourceDropdownContainer';
7
+ import SourceDropdown from '../SourceDropdown/SourceDropdown';
8
+ import {
9
+ ResourceBrowserPlugin,
10
+ ResourceBrowserSource,
11
+ ResourceBrowserResource,
12
+ ResourceBrowserSourceWithPlugin,
13
+ PluginLaunchMode,
14
+ PluginLaunchModeType,
15
+ ResourceBrowserSearchUIArgs,
16
+ ResourceBrowserUIArgs,
17
+ } from '../types';
7
18
 
8
19
  interface MainContainerProps {
9
20
  title: string;
10
21
  titleAriaProps: DOMAttributes<FocusableElement>;
11
22
  allowedTypes: string[] | undefined;
12
- sources: ResourceBrowserSource[];
23
+ sources: ResourceBrowserSourceWithPlugin[];
13
24
  selectedSource: ResourceBrowserSource | null;
14
- onSourceSelect(source: ResourceBrowserSource | null): void;
25
+ onSourceSelect(source: ResourceBrowserSource, mode?: PluginLaunchMode): void;
15
26
  onChange(resource: ResourceBrowserResource | null): void;
16
27
  onClose: () => void;
17
28
  preselectedResource?: ResourceBrowserResource | null;
18
29
  plugin: ResourceBrowserPlugin | null;
30
+ pluginMode: PluginLaunchMode | null;
31
+ searchEnabled: boolean;
19
32
  }
20
33
 
21
34
  function MainContainer({
@@ -29,9 +42,13 @@ function MainContainer({
29
42
  onClose,
30
43
  preselectedResource,
31
44
  plugin,
45
+ pluginMode,
46
+ searchEnabled,
32
47
  }: MainContainerProps) {
33
48
  const [headerPortal, setHeaderPortal] = useState<HTMLDivElement | null>(null);
49
+ const pluginToRender = pluginMode ? pluginMode.type : PluginLaunchModeType.Browse; // Cant default a 'null' property in signature so set it here
34
50
  const SourceBrowser = plugin?.sourceBrowserComponent();
51
+ const SourceSearch = plugin?.sourceSearchComponent();
35
52
 
36
53
  // Can't use a useRef as it wont update on change when a source is selected, so need to use a ref callback to store in state
37
54
  const setHeaderPortalRef = useCallback(
@@ -46,21 +63,22 @@ function MainContainer({
46
63
  // MainContainer will either render the source list view if no source is set or the plugins UI if a source has been selected
47
64
  return (
48
65
  <div className="relative flex flex-col h-full text-gray-800">
49
- <div className="flex items-center py-4.5 pl-4.5 pr-10">
66
+ <div className="flex items-center py-3 pl-6 pr-10 min-h-[68px]">
50
67
  <h2 {...titleAriaProps} className="text-xl leading-6 text-gray-800 font-semibold mr-6">
51
- {!plugin && 'Environment Selector'}
52
- {plugin && title}
68
+ {title}
53
69
  </h2>
54
70
 
55
71
  {plugin && selectedSource && (
56
72
  <>
57
- {sources.length > 1 && (
58
- <div className="px-3 border-l border-gray-300 w-300px">
59
- <SourceDropdown sources={sources} selectedSource={selectedSource} onSourceSelect={onSourceSelect} />
60
- </div>
61
- )}
62
- {plugin.createHeaderPortal && (
63
- <div ref={setHeaderPortalRef} className="squiz-rb-plugin px-3 border-l border-gray-300 w-300px"></div>
73
+ {/* Custom header if plugin supports it */}
74
+ {plugin.createHeaderPortal && <div ref={setHeaderPortalRef} className="squiz-rb-plugin"></div>}
75
+ {/* Default header if plugin does not support */}
76
+ {!plugin.createHeaderPortal && sources.length > 1 && (
77
+ <SourceDropdownContainer isCollapsed={false} onExpand={() => {}}>
78
+ <div className={`border-l border-blue-200 pl-1 w-[204px]`}>
79
+ <SourceDropdown sources={sources} selectedSource={selectedSource} onSourceSelect={onSourceSelect} />
80
+ </div>
81
+ </SourceDropdownContainer>
64
82
  )}
65
83
  </>
66
84
  )}
@@ -80,21 +98,43 @@ function MainContainer({
80
98
  </button>
81
99
  </div>
82
100
  <div className="border-t border-gray-300 overflow-y-hidden">
83
- {plugin && selectedSource && SourceBrowser && (
84
- <div className="squiz-rb-plugin">
85
- <SourceBrowser
86
- source={selectedSource}
87
- allowedTypes={allowedTypes}
88
- headerPortal={plugin.createHeaderPortal && headerPortal ? headerPortal : undefined}
89
- preselectedResource={preselectedResource || undefined}
90
- onSelected={(resource: ResourceBrowserResource) => {
91
- onChange(resource);
92
- onClose();
93
- }}
94
- />
101
+ {plugin && selectedSource && (
102
+ <div className={`squiz-rb-plugin squiz-rb-plugin--${pluginMode?.type}`}>
103
+ {pluginToRender === PluginLaunchModeType.Browse && SourceBrowser && (
104
+ <SourceBrowser
105
+ source={selectedSource}
106
+ sources={sources}
107
+ onSourceSelect={onSourceSelect}
108
+ allowedTypes={allowedTypes}
109
+ headerPortal={plugin.createHeaderPortal && headerPortal ? headerPortal : undefined}
110
+ searchEnabled={searchEnabled}
111
+ preselectedResource={preselectedResource || undefined}
112
+ browseTo={(pluginMode?.args as ResourceBrowserUIArgs)?.browseTo}
113
+ onSelected={(resource: ResourceBrowserResource) => {
114
+ onChange(resource);
115
+ onClose();
116
+ }}
117
+ />
118
+ )}
119
+ {pluginToRender === PluginLaunchModeType.Search && SourceSearch && (
120
+ <SourceSearch
121
+ source={selectedSource}
122
+ sources={sources}
123
+ onSourceSelect={onSourceSelect}
124
+ allowedTypes={allowedTypes}
125
+ headerPortal={plugin.createHeaderPortal && headerPortal ? headerPortal : undefined}
126
+ searchEnabled={searchEnabled}
127
+ query={(pluginMode?.args as ResourceBrowserSearchUIArgs)?.query}
128
+ onSelected={(resource: ResourceBrowserResource) => {
129
+ onChange(resource);
130
+ onClose();
131
+ }}
132
+ />
133
+ )}
95
134
  </div>
96
135
  )}
97
- {!selectedSource && <SourceList sources={sources} onSourceSelect={onSourceSelect} />}
136
+ {!selectedSource && searchEnabled && <ResourceLauncher sources={sources} onSourceSelect={onSourceSelect} />}
137
+ {!selectedSource && !searchEnabled && <SourceList sources={sources} onSourceSelect={onSourceSelect} />}
98
138
  </div>
99
139
  </div>
100
140
  );