@squiz/resource-browser 1.68.1 → 1.69.1

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 (33) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/Hooks/usePreselectedResourcePath.js +8 -3
  3. package/lib/Hooks/useRecentLocations.d.ts +3 -8
  4. package/lib/Hooks/useRecentLocations.js +5 -1
  5. package/lib/Hooks/useRecentResourcesPaths.d.ts +20 -0
  6. package/lib/Hooks/useRecentResourcesPaths.js +30 -0
  7. package/lib/Hooks/useResource.d.ts +13 -0
  8. package/lib/Hooks/useResource.js +12 -1
  9. package/lib/ResourcePickerContainer/ResourcePickerContainer.js +62 -21
  10. package/lib/SourceDropdown/SourceDropdown.d.ts +3 -1
  11. package/lib/SourceDropdown/SourceDropdown.js +24 -22
  12. package/lib/SourceList/SourceList.d.ts +5 -1
  13. package/lib/SourceList/SourceList.js +19 -14
  14. package/lib/index.css +3 -0
  15. package/package.json +1 -1
  16. package/src/Hooks/usePreselectedResourcePath.ts +9 -5
  17. package/src/Hooks/useRecentLocations.spec.ts +36 -40
  18. package/src/Hooks/useRecentLocations.ts +10 -11
  19. package/src/Hooks/useRecentResourcesPaths.ts +54 -0
  20. package/src/Hooks/useResource.spec.ts +30 -1
  21. package/src/Hooks/useResource.ts +19 -0
  22. package/src/ResourcePicker/ResourcePicker.spec.tsx +18 -0
  23. package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +63 -21
  24. package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +12 -1
  25. package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +79 -30
  26. package/src/SourceDropdown/SourceDropdown.spec.tsx +92 -27
  27. package/src/SourceDropdown/SourceDropdown.tsx +33 -29
  28. package/src/SourceList/SourceList.spec.tsx +133 -71
  29. package/src/SourceList/SourceList.stories.tsx +14 -6
  30. package/src/SourceList/SourceList.tsx +55 -29
  31. package/src/SourceList/sample-sources.json +34 -2
  32. package/src/__mocks__/StorybookHelpers.ts +30 -1
  33. package/src/index.stories.tsx +8 -2
@@ -5,31 +5,35 @@ import userEvent from '@testing-library/user-event';
5
5
  import SourceDropdown from './SourceDropdown';
6
6
  import { Source } from '../types';
7
7
  import { mockScopedSource, mockSource, mockResource } from '../__mocks__/MockModels';
8
+ import { RecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
8
9
 
9
- const mockLocalStorageData = [
10
+ const mockRecentSources: RecentResourcesPaths[] = [
10
11
  {
11
- path: [],
12
- source: {
12
+ source: mockSource({
13
13
  id: '1',
14
- name: 'Test source',
15
- nodes: [],
16
- },
17
- rootNode: {
18
- childCount: 0,
19
- id: '1',
20
- lineages: [],
21
- name: 'Test resource',
22
- status: {
23
- code: 'live',
24
- name: 'Live',
25
- },
26
- type: {
27
- code: 'folder',
28
- name: 'Folder',
29
- },
30
- url: 'https://no-where.com',
31
- urls: [],
32
- },
14
+ name: 'Source 1',
15
+ nodes: [
16
+ {
17
+ id: '1',
18
+ type: {
19
+ code: 'site',
20
+ name: 'Site',
21
+ },
22
+ name: 'Node 1',
23
+ childCount: 21,
24
+ },
25
+ {
26
+ id: '2',
27
+ type: {
28
+ code: 'site',
29
+ name: 'Site',
30
+ },
31
+ name: 'Node 2',
32
+ childCount: 13,
33
+ },
34
+ ],
35
+ }),
36
+ path: [mockResource()],
33
37
  },
34
38
  ];
35
39
 
@@ -95,6 +99,7 @@ describe('SourceDropdown', () => {
95
99
  onSourceSelect={() => {}}
96
100
  setSource={() => {}}
97
101
  currentResource={mockResource()}
102
+ recentSources={mockRecentSources}
98
103
  />,
99
104
  );
100
105
 
@@ -115,6 +120,7 @@ describe('SourceDropdown', () => {
115
120
  onSourceSelect={() => {}}
116
121
  setSource={() => {}}
117
122
  currentResource={mockResource()}
123
+ recentSources={mockRecentSources}
118
124
  />,
119
125
  );
120
126
 
@@ -140,6 +146,7 @@ describe('SourceDropdown', () => {
140
146
  onSourceSelect={() => {}}
141
147
  setSource={() => {}}
142
148
  currentResource={mockResource()}
149
+ recentSources={mockRecentSources}
143
150
  />,
144
151
  );
145
152
 
@@ -160,6 +167,7 @@ describe('SourceDropdown', () => {
160
167
  onSourceSelect={() => {}}
161
168
  setSource={() => {}}
162
169
  currentResource={mockResource()}
170
+ recentSources={mockRecentSources}
163
171
  />,
164
172
  );
165
173
 
@@ -186,6 +194,7 @@ describe('SourceDropdown', () => {
186
194
  onSourceSelect={() => {}}
187
195
  setSource={() => {}}
188
196
  currentResource={mockResource()}
197
+ recentSources={mockRecentSources}
189
198
  />
190
199
  <input />
191
200
  </div>,
@@ -229,6 +238,7 @@ describe('SourceDropdown', () => {
229
238
  onSourceSelect={() => {}}
230
239
  setSource={() => {}}
231
240
  currentResource={mockResource()}
241
+ recentSources={mockRecentSources}
232
242
  />
233
243
  <input />
234
244
  </div>,
@@ -273,6 +283,7 @@ describe('SourceDropdown', () => {
273
283
  onSourceSelect={() => {}}
274
284
  setSource={() => {}}
275
285
  currentResource={mockResource()}
286
+ recentSources={mockRecentSources}
276
287
  />,
277
288
  );
278
289
 
@@ -298,6 +309,7 @@ describe('SourceDropdown', () => {
298
309
  onSourceSelect={onSourceSelect}
299
310
  setSource={() => {}}
300
311
  currentResource={mockResource()}
312
+ recentSources={mockRecentSources}
301
313
  />,
302
314
  );
303
315
 
@@ -312,8 +324,6 @@ describe('SourceDropdown', () => {
312
324
  });
313
325
 
314
326
  it('Recent location sources are rendered when dropdown clicked', async () => {
315
- localStorage.setItem('rb_recent_locations', JSON.stringify(mockLocalStorageData));
316
-
317
327
  render(
318
328
  <SourceDropdown
319
329
  sources={sources}
@@ -323,6 +333,7 @@ describe('SourceDropdown', () => {
323
333
  onSourceSelect={() => {}}
324
334
  setSource={() => {}}
325
335
  currentResource={mockResource()}
336
+ recentSources={mockRecentSources}
326
337
  />,
327
338
  );
328
339
 
@@ -347,6 +358,7 @@ describe('SourceDropdown', () => {
347
358
  onSourceSelect={onSourceSelect}
348
359
  setSource={setSource}
349
360
  currentResource={mockResource()}
361
+ recentSources={mockRecentSources}
350
362
  />,
351
363
  );
352
364
 
@@ -357,11 +369,64 @@ describe('SourceDropdown', () => {
357
369
  await waitFor(() => {
358
370
  expect(setSource).toHaveBeenCalledWith(
359
371
  {
360
- source: mockLocalStorageData[0].source,
361
- resource: mockLocalStorageData[0].rootNode,
372
+ resource: {
373
+ childCount: 0,
374
+ id: '1',
375
+ lineages: [],
376
+ name: 'Test resource',
377
+ status: {
378
+ code: 'live',
379
+ name: 'Live',
380
+ },
381
+ type: {
382
+ code: 'folder',
383
+ name: 'Folder',
384
+ },
385
+ url: 'https://no-where.com',
386
+ urls: [],
387
+ },
388
+ source: {
389
+ id: '1',
390
+ name: 'Source 1',
391
+ nodes: [
392
+ {
393
+ childCount: 21,
394
+ id: '1',
395
+ lineages: [],
396
+ name: 'Node 1',
397
+ status: {
398
+ code: 'live',
399
+ name: 'Live',
400
+ },
401
+ type: {
402
+ code: 'site',
403
+ name: 'Site',
404
+ },
405
+ url: 'https://no-where.com',
406
+ urls: [],
407
+ },
408
+ {
409
+ childCount: 13,
410
+ id: '2',
411
+ lineages: [],
412
+ name: 'Node 2',
413
+ status: {
414
+ code: 'live',
415
+ name: 'Live',
416
+ },
417
+ type: {
418
+ code: 'site',
419
+ name: 'Site',
420
+ },
421
+ url: 'https://no-where.com',
422
+ urls: [],
423
+ },
424
+ ],
425
+ },
362
426
  },
363
- mockLocalStorageData[0].path,
427
+ [],
364
428
  );
429
+
365
430
  expect(screen.queryByRole('button', { name: 'All available sources' })).toBeFalsy();
366
431
  });
367
432
  });
@@ -6,8 +6,8 @@ import type { Source, ScopedSource, Resource } from '../types';
6
6
 
7
7
  import uuid from '../utils/uuid';
8
8
  import { useCategorisedSources } from '../Hooks/useCategorisedSources';
9
- import { useRecentLocations, RecentLocation } from '../Hooks/useRecentLocations';
10
9
  import { HistoryIcon } from '../Icons/HistoryIcon';
10
+ import { RecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
11
11
 
12
12
  export default function SourceDropdown({
13
13
  sources,
@@ -17,6 +17,7 @@ export default function SourceDropdown({
17
17
  onSourceSelect,
18
18
  setSource,
19
19
  currentResource,
20
+ recentSources,
20
21
  }: {
21
22
  sources: Source[];
22
23
  selectedSource: ScopedSource | null;
@@ -25,10 +26,11 @@ export default function SourceDropdown({
25
26
  onSourceSelect: (source: ScopedSource) => void;
26
27
  setSource: (source: ScopedSource | null, path?: Resource[]) => void;
27
28
  currentResource: Resource | null;
29
+ recentSources: RecentResourcesPaths[];
28
30
  }) {
29
31
  const categorisedSources = useCategorisedSources(sources);
30
- const { recentLocations } = useRecentLocations();
31
- const [recentLocationSelection, setRecentLocationSelection] = useState<RecentLocation | null>();
32
+ const filteredRecentSources = recentSources.filter((item) => item.path?.length);
33
+ const [recentLocationSelection, setRecentLocationSelection] = useState<RecentResourcesPaths | null>();
32
34
 
33
35
  const [uniqueId] = useState(uuid());
34
36
  const buttonRef = useRef<HTMLButtonElement>(null);
@@ -65,25 +67,31 @@ export default function SourceDropdown({
65
67
  onRootSelect();
66
68
  };
67
69
 
68
- const handleRecentLocationClick = (location: RecentLocation) => {
70
+ const handleRecentLocationClick = (location: RecentResourcesPaths) => {
69
71
  setIsOpen(false);
70
72
  setRecentLocationSelection(location);
71
73
  buttonRef.current?.focus();
72
- setSource(
73
- {
74
- source: location.source as Source,
75
- resource: location.rootNode,
76
- },
77
- location.path,
78
- );
74
+
75
+ if (location.path) {
76
+ const [rootNode, ...path] = location.path;
77
+ setSource(
78
+ {
79
+ source: location.source as Source,
80
+ resource: rootNode,
81
+ },
82
+ path,
83
+ );
84
+ }
79
85
  };
80
86
 
81
87
  useEffect(() => {
82
- const lastResource = recentLocationSelection?.path[recentLocationSelection.path.length - 1];
83
- // If the current resource selected in the resource browser is no longer the item selected in the
84
- // recent locations section dropdown then we set the selection to null to prevent active statuses.
85
- if (currentResource?.id !== lastResource?.id) {
86
- setRecentLocationSelection(null);
88
+ if (recentLocationSelection?.path) {
89
+ const lastResource = recentLocationSelection.path[recentLocationSelection.path.length - 1];
90
+ // If the current resource selected in the resource browser is no longer the item selected in the
91
+ // recent locations section dropdown then we set the selection to null to prevent active statuses.
92
+ if (currentResource && currentResource.id !== lastResource?.id) {
93
+ setRecentLocationSelection(null);
94
+ }
87
95
  }
88
96
  }, [recentLocationSelection, currentResource]);
89
97
 
@@ -161,7 +169,7 @@ export default function SourceDropdown({
161
169
  </li>
162
170
  )}
163
171
 
164
- {!isLoading && recentLocations.length > 0 && (
172
+ {!isLoading && filteredRecentSources.length > 0 && (
165
173
  <li className={`flex flex-col text-sm font-semibold text-grey-800`}>
166
174
  <div className="relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0">
167
175
  <span className="z-10 bg-gray-100 px-2.5 flex gap-1 items-center">
@@ -170,17 +178,15 @@ export default function SourceDropdown({
170
178
  </span>
171
179
  </div>
172
180
  <ul aria-label="recent location nodes" className="flex flex-col mt-2">
173
- {recentLocations.map((item, index) => {
174
- const lastResource = item.path[item.path.length - 1];
181
+ {filteredRecentSources.map((item, index) => {
182
+ const lastResource = item.path && item.path[item.path.length - 1];
175
183
  const isSelectedSource =
176
184
  item.source?.id === selectedSource?.source.id &&
177
- item.rootNode?.id === selectedSource?.resource?.id &&
178
- lastResource?.id === currentResource?.id &&
179
- recentLocationSelection;
185
+ lastResource?.id === recentLocationSelection?.path?.[recentLocationSelection.path?.length - 1]?.id;
180
186
 
181
187
  return (
182
188
  <li
183
- key={`${index}-${item.source?.id}-${item.rootNode?.id}`}
189
+ key={`${index}-${item.source?.id}-${lastResource?.id}`}
184
190
  className="flex items-center bg-white border border-b-0 last:border-b border-grey-200 first:rounded-t last:rounded-b"
185
191
  >
186
192
  <button
@@ -189,14 +195,12 @@ export default function SourceDropdown({
189
195
  className={`relative grow flex items-center p-2.5 hover:bg-gray-100 focus:bg-gray-100`}
190
196
  >
191
197
  <Icon
192
- icon={(lastResource?.type.code || item.rootNode?.type.code || 'folder') as IconOptions}
198
+ icon={(lastResource?.type.code || 'folder') as IconOptions}
193
199
  resourceSource="matrix"
194
- aria-label={lastResource?.name || item.rootNode?.name || item.source?.name}
200
+ aria-label={lastResource?.name || item.source?.name}
195
201
  className="shrink-0 mr-2.5"
196
202
  />
197
- <span className="text-left mr-7">
198
- {lastResource?.name || item.rootNode?.name || item.source?.name}
199
- </span>
203
+ <span className="text-left mr-7">{lastResource?.name || item.source?.name}</span>
200
204
  {isSelectedSource && (
201
205
  <Icon icon={'selected' as IconOptions} aria-label="selected" className="absolute right-4" />
202
206
  )}
@@ -214,7 +218,7 @@ export default function SourceDropdown({
214
218
  <li
215
219
  key={key}
216
220
  className={`flex flex-col text-sm font-semibold text-grey-800 ${
217
- index > 0 || recentLocations.length > 0 ? 'mt-3' : ''
221
+ index > 0 || filteredRecentSources.length > 0 ? 'mt-3' : ''
218
222
  }`}
219
223
  >
220
224
  <div className="relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0">