@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.
- package/CHANGELOG.md +12 -0
- package/lib/Hooks/usePreselectedResourcePath.js +8 -3
- package/lib/Hooks/useRecentLocations.d.ts +3 -8
- package/lib/Hooks/useRecentLocations.js +5 -1
- package/lib/Hooks/useRecentResourcesPaths.d.ts +20 -0
- package/lib/Hooks/useRecentResourcesPaths.js +30 -0
- package/lib/Hooks/useResource.d.ts +13 -0
- package/lib/Hooks/useResource.js +12 -1
- package/lib/ResourcePickerContainer/ResourcePickerContainer.js +62 -21
- package/lib/SourceDropdown/SourceDropdown.d.ts +3 -1
- package/lib/SourceDropdown/SourceDropdown.js +24 -22
- package/lib/SourceList/SourceList.d.ts +5 -1
- package/lib/SourceList/SourceList.js +19 -14
- package/lib/index.css +3 -0
- package/package.json +1 -1
- package/src/Hooks/usePreselectedResourcePath.ts +9 -5
- package/src/Hooks/useRecentLocations.spec.ts +36 -40
- package/src/Hooks/useRecentLocations.ts +10 -11
- package/src/Hooks/useRecentResourcesPaths.ts +54 -0
- package/src/Hooks/useResource.spec.ts +30 -1
- package/src/Hooks/useResource.ts +19 -0
- package/src/ResourcePicker/ResourcePicker.spec.tsx +18 -0
- package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +63 -21
- package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +12 -1
- package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +79 -30
- package/src/SourceDropdown/SourceDropdown.spec.tsx +92 -27
- package/src/SourceDropdown/SourceDropdown.tsx +33 -29
- package/src/SourceList/SourceList.spec.tsx +133 -71
- package/src/SourceList/SourceList.stories.tsx +14 -6
- package/src/SourceList/SourceList.tsx +55 -29
- package/src/SourceList/sample-sources.json +34 -2
- package/src/__mocks__/StorybookHelpers.ts +30 -1
- 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
|
10
|
+
const mockRecentSources: RecentResourcesPaths[] = [
|
10
11
|
{
|
11
|
-
|
12
|
-
source: {
|
12
|
+
source: mockSource({
|
13
13
|
id: '1',
|
14
|
-
name: '
|
15
|
-
nodes: [
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
361
|
-
|
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
|
-
|
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
|
31
|
-
const [recentLocationSelection, setRecentLocationSelection] = useState<
|
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:
|
70
|
+
const handleRecentLocationClick = (location: RecentResourcesPaths) => {
|
69
71
|
setIsOpen(false);
|
70
72
|
setRecentLocationSelection(location);
|
71
73
|
buttonRef.current?.focus();
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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 &&
|
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
|
-
{
|
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
|
-
|
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}-${
|
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 ||
|
198
|
+
icon={(lastResource?.type.code || 'folder') as IconOptions}
|
193
199
|
resourceSource="matrix"
|
194
|
-
aria-label={lastResource?.name || item.
|
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 ||
|
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">
|