@squiz/resource-browser 1.69.0 → 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.
@@ -1,6 +1,6 @@
1
1
  import { renderHook, waitFor } from '@testing-library/react';
2
2
  import { mockResource } from '../__mocks__/MockModels';
3
- import { useResource } from './useResource';
3
+ import { useResource, useResources } from './useResource';
4
4
 
5
5
  describe('useResource', () => {
6
6
  it('Should load the resource', async () => {
@@ -30,3 +30,32 @@ describe('useResource', () => {
30
30
  expect(onRequestResource).not.toBeCalled();
31
31
  });
32
32
  });
33
+
34
+ describe('useResources', () => {
35
+ it('Should load the resources', async () => {
36
+ const resources = mockResource();
37
+ const references = [{ source: 'source-id', resource: 'resource-id' }];
38
+ const onRequestResource = jest.fn().mockResolvedValue(resources);
39
+ const { result } = renderHook(() => useResources({ onRequestResource, references }));
40
+
41
+ expect(result.current.isLoading).toBe(true);
42
+ expect(result.current.error).toBe(null);
43
+ expect(result.current.data).toEqual(null);
44
+
45
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
46
+
47
+ expect(result.current.isLoading).toBe(false);
48
+ expect(result.current.data).toStrictEqual([resources]);
49
+ });
50
+
51
+ it('Should not load the resources if no references are provided', async () => {
52
+ const references = null;
53
+ const onRequestResource = jest.fn();
54
+ const { result } = renderHook(() => useResources({ onRequestResource, references }));
55
+
56
+ expect(result.current.isLoading).toBe(false);
57
+ expect(result.current.error).toBe(null);
58
+ expect(result.current.data).toEqual(null);
59
+ expect(onRequestResource).not.toBeCalled();
60
+ });
61
+ });
@@ -6,6 +6,11 @@ type UseResourceProps = {
6
6
  reference?: ResourceReference | null;
7
7
  };
8
8
 
9
+ type UseResourcesProps = {
10
+ onRequestResource: (reference: ResourceReference) => Promise<Resource | null>;
11
+ references?: ResourceReference[] | null;
12
+ };
13
+
9
14
  /**
10
15
  * Loads the resource indicated by the provided reference.
11
16
  */
@@ -17,3 +22,17 @@ export const useResource = ({ onRequestResource, reference }: UseResourceProps)
17
22
  },
18
23
  [reference?.source, reference?.resource],
19
24
  );
25
+
26
+ /**
27
+ * Loads the resources indicated by the provided reference.
28
+ */
29
+ export const useResources = ({ onRequestResource, references }: UseResourcesProps) => {
30
+ const callbackArray = references?.map((item) => () => onRequestResource(item));
31
+ return useAsync(
32
+ {
33
+ callback: callbackArray ? callbackArray : () => null,
34
+ defaultValue: null,
35
+ },
36
+ [],
37
+ );
38
+ };
@@ -102,4 +102,22 @@ describe('Resource picker', () => {
102
102
  expect(pickerLabel).toBeInTheDocument();
103
103
  expect(removeButton).not.toBeInTheDocument();
104
104
  });
105
+
106
+ it('should display a default status if an unsupported status is found for resource', () => {
107
+ render(
108
+ <ResourcePicker
109
+ {...defaultProps}
110
+ resource={{
111
+ ...mockResource,
112
+ status: {
113
+ code: 'this_is_not_real',
114
+ name: 'This is not real',
115
+ },
116
+ }}
117
+ />,
118
+ );
119
+
120
+ const item = screen.getByTitle('This is not real');
121
+ expect(item?.style.backgroundColor).toEqual('rgb(255, 0, 0)');
122
+ });
105
123
  });
@@ -71,6 +71,16 @@ const baseProps = {
71
71
  };
72
72
 
73
73
  describe('ResourcePickerContainer', () => {
74
+ beforeEach(() => {
75
+ localStorage.setItem(
76
+ 'rb_recent_locations',
77
+ JSON.stringify([
78
+ { resource: '32', source: '1' },
79
+ { resource: '20', source: '1' },
80
+ ]),
81
+ );
82
+ });
83
+
74
84
  it('Queries onRequestSources for source list on startup', async () => {
75
85
  const onRequestSources = jest.fn(() => {
76
86
  return Promise.resolve([]);
@@ -209,6 +219,8 @@ describe('ResourcePickerContainer', () => {
209
219
  ['the preselected resource lineage does not exist under a root node', 100],
210
220
  ['the preselected resource lineage does not appear under a root node', 200],
211
221
  ])('The source list is displayed if %s', async (description: string, preselectedResourceId: number) => {
222
+ localStorage.clear();
223
+
212
224
  const resources: Record<string, Resource> = {
213
225
  10: mockResource({
214
226
  id: '100',
@@ -256,8 +268,10 @@ describe('ResourcePickerContainer', () => {
256
268
  // Breadcrumbs should not be displayed.
257
269
  // Source list should be displayed.
258
270
  // "Leaf" resource should be selected, "Another leaf" resource should not be selected.
259
- expect(screen.queryByLabelText('Resource breadcrumb')).not.toBeInTheDocument();
260
- expect(screen.getByRole('button', { name: 'Drill down to Source root node #1 children' })).toBeInTheDocument();
271
+ await waitFor(() => expect(screen.queryByLabelText('Resource breadcrumb')).not.toBeInTheDocument());
272
+ await waitFor(() =>
273
+ expect(screen.getByRole('button', { name: 'Drill down to Source root node #1 children' })).toBeInTheDocument(),
274
+ );
261
275
  });
262
276
 
263
277
  it('Selecting a child count drills down', async () => {
@@ -734,6 +748,7 @@ describe('ResourcePickerContainer', () => {
734
748
  await waitFor(() => expect(onChangeMock).toHaveBeenCalled());
735
749
  await waitFor(() => expect(onCloseMock).toHaveBeenCalled());
736
750
  });
751
+
737
752
  it('Resource select works', async () => {
738
753
  const onChangeMock = jest.fn();
739
754
  const onCloseMock = jest.fn();
@@ -20,6 +20,8 @@ import { useChildResources } from '../Hooks/useChildResources';
20
20
  import { useSources } from '../Hooks/useSources';
21
21
  import { usePreselectedResourcePath } from '../Hooks/usePreselectedResourcePath';
22
22
  import { useRecentLocations } from '../Hooks/useRecentLocations';
23
+ import { useResources } from '../Hooks/useResource';
24
+ import { RecentResourcesPaths, useRecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
23
25
 
24
26
  interface ResourcePickerContainerProps {
25
27
  title: string;
@@ -51,7 +53,25 @@ function ResourcePickerContainer({
51
53
  const [selectedResourceId, setSelectedResourceId] = useState<string | null>(null);
52
54
  const [previewModalOverlayProps, setPreviewModalOverlayProps] = useState<DOMAttributes>({});
53
55
  const { source, currentResource, hierarchy, setSource, push, popUntil } = useResourcePath();
54
- const { addRecentLocation } = useRecentLocations();
56
+
57
+ // Recent locations relevant data
58
+ const { addRecentLocation, recentLocations } = useRecentLocations();
59
+
60
+ const { data: recentLocationsResources, isLoading: recentLocationsResourcesLoading } = useResources({
61
+ onRequestResource,
62
+ references: recentLocations,
63
+ });
64
+
65
+ const { data: recentLocationsSources, isLoading: recentLocationsLoading } = useRecentResourcesPaths({
66
+ sourceIds: recentLocations.map((item) => item.source),
67
+ resources: recentLocationsResources,
68
+ onRequestResource,
69
+ onRequestSources,
70
+ });
71
+
72
+ // Type check the returned values from recent locations requests
73
+ let recentSources: RecentResourcesPaths[] = [];
74
+ if (Array.isArray(recentLocationsSources)) recentSources = recentLocationsSources;
55
75
 
56
76
  const {
57
77
  data: sources,
@@ -59,12 +79,14 @@ function ResourcePickerContainer({
59
79
  reload: handleSourceReload,
60
80
  error: sourceError,
61
81
  } = useSources({ onRequestSources });
82
+
62
83
  const {
63
84
  data: resources,
64
85
  isLoading: isResourcesLoading,
65
86
  reload: handleResourceReload,
66
87
  error: resourceError,
67
88
  } = useChildResources({ source, currentResource, onRequestChildren });
89
+
68
90
  const {
69
91
  data: { source: preselectedSource, path: preselectedPath },
70
92
  isLoading: isPreselectedResourcePathLoading,
@@ -74,6 +96,7 @@ function ResourcePickerContainer({
74
96
  onRequestResource,
75
97
  onRequestSources,
76
98
  });
99
+
77
100
  const selectedResource = useMemo(() => {
78
101
  if (selectedSource) {
79
102
  return selectedSource?.nodes.find((resource: Resource) => resource.id === selectedResourceId) || null;
@@ -118,22 +141,17 @@ function ResourcePickerContainer({
118
141
 
119
142
  const handleDetailSelect = useCallback(
120
143
  (resource: Resource) => {
121
- onChange({ resource, source: selectedSource ?? (source?.source as Source) });
144
+ const detailSelectedSource = selectedSource ?? (source?.source as Source);
145
+ onChange({ resource, source: detailSelectedSource });
122
146
 
123
147
  // Find the path that got them to where they are
124
- const selectedPath = hierarchy.map((path) => {
125
- const pathNode = path.node as ScopedSource;
126
- return 'resource' in pathNode ? pathNode.resource : pathNode;
127
- });
128
-
129
- const [rootNode, ...path] = selectedPath;
148
+ const lastPathItem = hierarchy[hierarchy.length - 1]?.node as ScopedSource;
149
+ const lastPathResource = lastPathItem && 'resource' in lastPathItem ? lastPathItem?.resource : lastPathItem;
130
150
 
131
- if (rootNode) {
132
- // Update the recent locations in local storage
151
+ if (lastPathResource) {
133
152
  addRecentLocation({
134
- rootNode,
135
- path: path as Resource[],
136
- source: selectedSource ?? (source?.source as Source),
153
+ resource: lastPathResource.id,
154
+ source: detailSelectedSource.id,
137
155
  });
138
156
  }
139
157
 
@@ -188,11 +206,12 @@ function ResourcePickerContainer({
188
206
  <SourceDropdown
189
207
  sources={sources}
190
208
  selectedSource={source}
191
- isLoading={isSourceLoading}
209
+ isLoading={isSourceLoading || recentLocationsLoading || recentLocationsResourcesLoading}
192
210
  onSourceSelect={handleSourceDrilldown}
193
211
  onRootSelect={handleReturnToRoot}
194
212
  setSource={setSource}
195
213
  currentResource={currentResource}
214
+ recentSources={recentSources}
196
215
  />
197
216
  </div>
198
217
  <button
@@ -224,11 +243,17 @@ function ResourcePickerContainer({
224
243
  sources={sources}
225
244
  selectedResource={selectedResource}
226
245
  previewModalState={previewModalState}
227
- isLoading={isSourceLoading || isPreselectedResourcePathLoading}
246
+ isLoading={
247
+ isSourceLoading ||
248
+ isPreselectedResourcePathLoading ||
249
+ recentLocationsLoading ||
250
+ recentLocationsResourcesLoading
251
+ }
228
252
  onSourceSelect={handleSourceSelected}
229
253
  onSourceDrilldown={handleSourceDrilldown}
230
254
  handleReload={handleSourceReload}
231
255
  setSource={setSource}
256
+ recentSources={recentSources}
232
257
  error={sourceError}
233
258
  />
234
259
  )}
@@ -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">