@squiz/resource-browser 1.69.2 → 2.1.9-rc.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/CHANGELOG.md +89 -38
- package/LICENSE.md +15 -0
- package/README.md +9 -0
- package/jest.config.ts +22 -21
- package/lib/Hooks/useSelectedState.d.ts +15 -0
- package/lib/Hooks/useSelectedState.js +16 -0
- package/lib/Hooks/useSources.d.ts +6 -6
- package/lib/Hooks/useSources.js +26 -1
- package/lib/MainContainer/MainContainer.d.ts +17 -0
- package/lib/MainContainer/MainContainer.js +61 -0
- package/lib/Plugin/Plugin.d.ts +13 -0
- package/lib/Plugin/Plugin.js +17 -0
- package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +2 -3
- package/lib/ResourceBrowserContext/ResourceBrowserContext.js +4 -17
- package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +24 -0
- package/lib/ResourceBrowserInput/ResourceBrowserInput.js +16 -0
- package/lib/ResourcePicker/ResourcePicker.d.ts +6 -4
- package/lib/ResourcePicker/ResourcePicker.js +14 -8
- package/lib/ResourcePicker/States/Selected.d.ts +10 -4
- package/lib/ResourcePicker/States/Selected.js +11 -32
- package/lib/SourceDropdown/SourceDropdown.d.ts +5 -11
- package/lib/SourceDropdown/SourceDropdown.js +20 -99
- package/lib/SourceList/SourceList.d.ts +5 -16
- package/lib/SourceList/SourceList.js +14 -75
- package/lib/index.css +42 -202
- package/lib/index.d.ts +7 -7
- package/lib/index.js +69 -13
- package/lib/types.d.ts +41 -59
- package/package.json +82 -80
- package/src/Hooks/useSelectedState.spec.ts +46 -0
- package/src/Hooks/useSelectedState.ts +22 -0
- package/src/Hooks/useSources.spec.ts +60 -13
- package/src/Hooks/useSources.ts +35 -5
- package/src/Icons/CircledLoopIcon.tsx +8 -8
- package/src/MainContainer/MainContainer.spec.tsx +203 -0
- package/src/MainContainer/MainContainer.stories.tsx +62 -0
- package/src/MainContainer/MainContainer.tsx +101 -0
- package/src/Plugin/Plugin.spec.tsx +46 -0
- package/src/Plugin/Plugin.tsx +20 -0
- package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +65 -106
- package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +24 -39
- package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +192 -0
- package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +81 -0
- package/src/ResourcePicker/ResourcePicker.spec.tsx +159 -116
- package/src/ResourcePicker/ResourcePicker.stories.tsx +28 -24
- package/src/ResourcePicker/ResourcePicker.tsx +79 -59
- package/src/ResourcePicker/States/Error.tsx +8 -8
- package/src/ResourcePicker/States/Loading.tsx +3 -3
- package/src/ResourcePicker/States/Selected.tsx +66 -73
- package/src/ResourcePicker/mock-image-resource.json +25 -47
- package/src/ResourcePicker/mock-resource.json +11 -13
- package/src/ResourcePicker/resource-picker.scss +13 -13
- package/src/SourceDropdown/SourceDropdown.spec.tsx +65 -391
- package/src/SourceDropdown/SourceDropdown.stories.tsx +21 -24
- package/src/SourceDropdown/SourceDropdown.tsx +80 -258
- package/src/SourceList/SourceList.spec.tsx +37 -430
- package/src/SourceList/SourceList.stories.tsx +17 -37
- package/src/SourceList/SourceList.tsx +28 -155
- package/src/__mocks__/MockModels.ts +56 -25
- package/src/__mocks__/PluginExample.tsx +98 -0
- package/src/__mocks__/StorybookHelpers.tsx +141 -0
- package/src/__mocks__/renderWithContext.tsx +14 -18
- package/src/__mocks__/sample-sources.json +32 -0
- package/src/index.scss +18 -8
- package/src/index.spec.tsx +277 -99
- package/src/index.stories.tsx +65 -39
- package/src/index.tsx +119 -57
- package/src/types.ts +54 -63
- package/tailwind.config.cjs +92 -92
- package/vite.config.js +12 -12
- package/lib/Hooks/useCategorisedSources.d.ts +0 -14
- package/lib/Hooks/useCategorisedSources.js +0 -38
- package/lib/Hooks/useChildResources.d.ts +0 -16
- package/lib/Hooks/useChildResources.js +0 -13
- package/lib/Hooks/usePreselectedResourcePath.d.ts +0 -20
- package/lib/Hooks/usePreselectedResourcePath.js +0 -31
- package/lib/Hooks/useRecentLocations.d.ts +0 -5
- package/lib/Hooks/useRecentLocations.js +0 -38
- package/lib/Hooks/useRecentResourcesPaths.d.ts +0 -20
- package/lib/Hooks/useRecentResourcesPaths.js +0 -30
- package/lib/Hooks/useResource.d.ts +0 -28
- package/lib/Hooks/useResource.js +0 -25
- package/lib/Hooks/useResourcePath.d.ts +0 -16
- package/lib/Hooks/useResourcePath.js +0 -64
- package/lib/Icons/HistoryIcon.d.ts +0 -4
- package/lib/Icons/HistoryIcon.js +0 -13
- package/lib/PreviewPanel/PreviewPanel.d.ts +0 -5
- package/lib/PreviewPanel/PreviewPanel.js +0 -8
- package/lib/PreviewPanel/details/MatrixResource.d.ts +0 -7
- package/lib/PreviewPanel/details/MatrixResource.js +0 -35
- package/lib/ResourceBreadcrumb/ResourceBreadcrumb.d.ts +0 -9
- package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +0 -54
- package/lib/ResourceList/ResourceList.d.ts +0 -18
- package/lib/ResourceList/ResourceList.js +0 -49
- package/lib/ResourcePickerContainer/ResourcePickerContainer.d.ts +0 -17
- package/lib/ResourcePickerContainer/ResourcePickerContainer.js +0 -166
- package/lib/StatusIndicator/StatusIndicator.d.ts +0 -8
- package/lib/StatusIndicator/StatusIndicator.js +0 -27
- package/lib/utils/findBestMatchLineage.d.ts +0 -2
- package/lib/utils/findBestMatchLineage.js +0 -28
- package/lib/utils/uuid.d.ts +0 -1
- package/lib/utils/uuid.js +0 -6
- package/src/Hooks/useCategorisedSources.spec.ts +0 -39
- package/src/Hooks/useCategorisedSources.ts +0 -46
- package/src/Hooks/useChildResources.spec.ts +0 -29
- package/src/Hooks/useChildResources.ts +0 -21
- package/src/Hooks/usePreselectedResourcePath.ts +0 -54
- package/src/Hooks/useRecentLocations.spec.ts +0 -81
- package/src/Hooks/useRecentLocations.ts +0 -44
- package/src/Hooks/useRecentResourcesPaths.ts +0 -54
- package/src/Hooks/useResource.spec.ts +0 -61
- package/src/Hooks/useResource.ts +0 -40
- package/src/Hooks/useResourcePath.spec.ts +0 -120
- package/src/Hooks/useResourcePath.ts +0 -76
- package/src/Icons/HistoryIcon.tsx +0 -17
- package/src/PreviewPanel/PreviewPanel.spec.tsx +0 -198
- package/src/PreviewPanel/PreviewPanel.stories.tsx +0 -76
- package/src/PreviewPanel/PreviewPanel.tsx +0 -6
- package/src/PreviewPanel/details/MatrixResource.tsx +0 -54
- package/src/PreviewPanel/details/matrix-resource.scss +0 -16
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +0 -133
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.stories.tsx +0 -24
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +0 -79
- package/src/ResourceBreadcrumb/resource-breadcrumb.scss +0 -28
- package/src/ResourceBreadcrumb/sample-hierarchy.json +0 -27
- package/src/ResourceList/ResourceList.spec.tsx +0 -202
- package/src/ResourceList/ResourceList.stories.tsx +0 -40
- package/src/ResourceList/ResourceList.tsx +0 -83
- package/src/ResourceList/sample-resources.json +0 -851
- package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +0 -780
- package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +0 -45
- package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +0 -290
- package/src/SourceList/sample-sources.json +0 -251
- package/src/StatusIndicator/StatusIndicator.stories.tsx +0 -83
- package/src/StatusIndicator/StatusIndicator.tsx +0 -38
- package/src/__mocks__/JestHelpers.ts +0 -65
- package/src/__mocks__/StorybookHelpers.ts +0 -128
- package/src/__mocks__/jestHelpers.spec.ts +0 -38
- package/src/utils/findBestMatchLineage.spec.ts +0 -81
- package/src/utils/findBestMatchLineage.ts +0 -30
- package/src/utils/uuid.ts +0 -5
@@ -1,133 +0,0 @@
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-empty-function */
|
2
|
-
import React from 'react';
|
3
|
-
import { screen, render, waitFor } from '@testing-library/react';
|
4
|
-
import userEvent from '@testing-library/user-event';
|
5
|
-
|
6
|
-
import ResourceBreadcrumb from './ResourceBreadcrumb';
|
7
|
-
|
8
|
-
const hierarchy = [
|
9
|
-
{
|
10
|
-
key: '1',
|
11
|
-
label: 'HandyHomes website',
|
12
|
-
node: 'A',
|
13
|
-
},
|
14
|
-
{
|
15
|
-
key: '2',
|
16
|
-
label: 'Section 1',
|
17
|
-
node: 'B',
|
18
|
-
},
|
19
|
-
{
|
20
|
-
key: '3',
|
21
|
-
label: 'Pages',
|
22
|
-
node: 'C',
|
23
|
-
},
|
24
|
-
];
|
25
|
-
|
26
|
-
describe('ResourceBreadcrumb', () => {
|
27
|
-
const onBreadcrumbSelect = jest.fn();
|
28
|
-
|
29
|
-
it('Breadcrumb renders each item', async () => {
|
30
|
-
render(<ResourceBreadcrumb hierarchy={hierarchy} onBreadcrumbSelect={() => {}} onReturnToRoot={() => {}} />);
|
31
|
-
|
32
|
-
await waitFor(() => {
|
33
|
-
expect(screen.getByText('HandyHomes website')).toBeTruthy();
|
34
|
-
expect(screen.getByRole('button', { name: 'HandyHomes website' })).toBeTruthy();
|
35
|
-
|
36
|
-
expect(screen.getByText('Section 1')).toBeTruthy();
|
37
|
-
expect(screen.getByRole('button', { name: 'Section 1' })).toBeTruthy();
|
38
|
-
|
39
|
-
// Last item isn't a button
|
40
|
-
expect(screen.getByText('Pages')).toBeTruthy();
|
41
|
-
expect(screen.getByText('Pages').tagName.toLocaleLowerCase()).toEqual('div');
|
42
|
-
});
|
43
|
-
});
|
44
|
-
|
45
|
-
it('Clicking breadcrumb calls onBreadcrumbSelect', async () => {
|
46
|
-
render(
|
47
|
-
<ResourceBreadcrumb hierarchy={hierarchy} onBreadcrumbSelect={onBreadcrumbSelect} onReturnToRoot={() => {}} />,
|
48
|
-
);
|
49
|
-
|
50
|
-
const user = userEvent.setup();
|
51
|
-
user.click(screen.getByRole('button', { name: 'HandyHomes website' }));
|
52
|
-
|
53
|
-
await waitFor(() => {
|
54
|
-
expect(onBreadcrumbSelect).toHaveBeenCalledWith(hierarchy[0].node);
|
55
|
-
});
|
56
|
-
});
|
57
|
-
|
58
|
-
it('Clicking root icon calls onReturnToRoot', async () => {
|
59
|
-
const onReturnToRoot = jest.fn();
|
60
|
-
|
61
|
-
render(<ResourceBreadcrumb hierarchy={hierarchy} onBreadcrumbSelect={() => {}} onReturnToRoot={onReturnToRoot} />);
|
62
|
-
|
63
|
-
const user = userEvent.setup();
|
64
|
-
user.click(screen.getByRole('button', { name: 'Return to source list' }));
|
65
|
-
|
66
|
-
await waitFor(() => {
|
67
|
-
expect(onReturnToRoot).toHaveBeenCalled();
|
68
|
-
});
|
69
|
-
});
|
70
|
-
|
71
|
-
it('Truncates long hierarchy correctly', async () => {
|
72
|
-
const { container } = render(
|
73
|
-
<ResourceBreadcrumb
|
74
|
-
hierarchy={[
|
75
|
-
...hierarchy,
|
76
|
-
{
|
77
|
-
key: '4',
|
78
|
-
label: 'Item 4',
|
79
|
-
node: 'D',
|
80
|
-
},
|
81
|
-
{
|
82
|
-
key: '5',
|
83
|
-
label: 'Item 5',
|
84
|
-
node: 'E',
|
85
|
-
},
|
86
|
-
]}
|
87
|
-
onBreadcrumbSelect={() => {}}
|
88
|
-
onReturnToRoot={() => {}}
|
89
|
-
/>,
|
90
|
-
);
|
91
|
-
|
92
|
-
await waitFor(() => {
|
93
|
-
// This is handled by css, so check the collapse class is on the root
|
94
|
-
expect(container.querySelector('.resource-breadcrumb--collapsed')).toBeTruthy();
|
95
|
-
|
96
|
-
expect(screen.getByText('HandyHomes website')).toBeTruthy();
|
97
|
-
expect(screen.getByRole('button', { name: 'HandyHomes website' })).toBeTruthy();
|
98
|
-
|
99
|
-
// Expand button is rendered
|
100
|
-
expect(screen.getByRole('button', { name: 'Expand breadcrumb' })).toBeTruthy();
|
101
|
-
|
102
|
-
// Middle buttons are still rendered as they are hidden by CSS
|
103
|
-
expect(screen.queryByRole('button', { name: 'Section 1' })).toBeTruthy();
|
104
|
-
expect(screen.queryByRole('button', { name: 'Pages' })).toBeTruthy();
|
105
|
-
|
106
|
-
expect(screen.getByRole('button', { name: 'Item 4' })).toBeTruthy();
|
107
|
-
expect(screen.getByText('Item 5')).toBeTruthy();
|
108
|
-
});
|
109
|
-
|
110
|
-
// Click expander
|
111
|
-
const user = userEvent.setup();
|
112
|
-
user.click(screen.getByRole('button', { name: 'Expand breadcrumb' }));
|
113
|
-
|
114
|
-
// Expect css class to change
|
115
|
-
await waitFor(() => {
|
116
|
-
expect(container.querySelector('.resource-breadcrumb--expanded')).toBeTruthy();
|
117
|
-
});
|
118
|
-
});
|
119
|
-
|
120
|
-
// Check that the breadcrumb has a title attribute that is the full name of the item
|
121
|
-
it('Breadcrumb has title attribute', async () => {
|
122
|
-
render(<ResourceBreadcrumb hierarchy={hierarchy} onBreadcrumbSelect={() => {}} onReturnToRoot={() => {}} />);
|
123
|
-
|
124
|
-
await waitFor(() => {
|
125
|
-
expect(screen.getByRole('button', { name: 'HandyHomes website' }).firstChild).toHaveAttribute(
|
126
|
-
'title',
|
127
|
-
'HandyHomes website',
|
128
|
-
);
|
129
|
-
expect(screen.getByRole('button', { name: 'Section 1' }).firstChild).toHaveAttribute('title', 'Section 1');
|
130
|
-
expect(screen.getByText('Pages')).toHaveAttribute('title', 'Pages');
|
131
|
-
});
|
132
|
-
});
|
133
|
-
});
|
@@ -1,24 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { StoryFn, Meta } from '@storybook/react';
|
3
|
-
|
4
|
-
import ResourceBreadcrumb from './ResourceBreadcrumb';
|
5
|
-
import sampleHierarchy from './sample-hierarchy.json';
|
6
|
-
|
7
|
-
export default {
|
8
|
-
title: 'Resource Breadcrumb',
|
9
|
-
component: ResourceBreadcrumb,
|
10
|
-
} as Meta<typeof ResourceBreadcrumb>;
|
11
|
-
|
12
|
-
const Template: StoryFn<typeof ResourceBreadcrumb> = ({ hierarchy }) => (
|
13
|
-
<ResourceBreadcrumb
|
14
|
-
hierarchy={hierarchy}
|
15
|
-
onBreadcrumbSelect={(node) => alert(`Node Select: ${node}`)}
|
16
|
-
onReturnToRoot={() => alert(`Return to Root`)}
|
17
|
-
/>
|
18
|
-
);
|
19
|
-
|
20
|
-
export const Primary = Template.bind({});
|
21
|
-
|
22
|
-
Primary.args = {
|
23
|
-
hierarchy: sampleHierarchy,
|
24
|
-
};
|
@@ -1,79 +0,0 @@
|
|
1
|
-
import React, { useState, useCallback, useEffect } from 'react';
|
2
|
-
|
3
|
-
import { Icon, IconOptions } from '@squiz/generic-browser-lib';
|
4
|
-
import { Hierarchy } from '../types';
|
5
|
-
|
6
|
-
export interface ResourceBreadcrumbProps<T> {
|
7
|
-
hierarchy: Hierarchy<T>;
|
8
|
-
onBreadcrumbSelect: (node: T) => void;
|
9
|
-
onReturnToRoot: () => void;
|
10
|
-
}
|
11
|
-
|
12
|
-
const ResourceBreadcrumb = function <T>({ hierarchy, onBreadcrumbSelect, onReturnToRoot }: ResourceBreadcrumbProps<T>) {
|
13
|
-
// Track expanded state
|
14
|
-
const [expandedHierarchy, setExpandedHierarchy] = useState(false);
|
15
|
-
|
16
|
-
// On update (new page) reset the expanded state
|
17
|
-
useEffect(() => {
|
18
|
-
setExpandedHierarchy(false);
|
19
|
-
}, [hierarchy]);
|
20
|
-
|
21
|
-
// Handle toggling the state to expanded
|
22
|
-
const handleExpandHierarchy = useCallback(() => {
|
23
|
-
setExpandedHierarchy(true);
|
24
|
-
}, []);
|
25
|
-
|
26
|
-
return (
|
27
|
-
<nav
|
28
|
-
aria-label="Resource breadcrumb"
|
29
|
-
className={`text-sm text-gray-600 px-7 my-5 resource-breadcrumb ${
|
30
|
-
expandedHierarchy ? 'resource-breadcrumb--expanded' : 'resource-breadcrumb--collapsed'
|
31
|
-
}`}
|
32
|
-
>
|
33
|
-
<ol className="flex flex-wrap items-center">
|
34
|
-
<li className="flex items-center mr-3">
|
35
|
-
<button type="button" onClick={onReturnToRoot}>
|
36
|
-
<Icon icon={'root' as IconOptions} aria-label="Return to source list" className="" />
|
37
|
-
</button>
|
38
|
-
</li>
|
39
|
-
{hierarchy.map(({ key, label, node }, index) => {
|
40
|
-
return (
|
41
|
-
<li
|
42
|
-
key={`${key}`}
|
43
|
-
className="resource-breadcrumb__item max-md:hidden flex items-center mr-2 before:content-['/'] before:mr-2"
|
44
|
-
>
|
45
|
-
{index !== hierarchy.length - 1 && (
|
46
|
-
<button type="button" onClick={() => onBreadcrumbSelect(node)}>
|
47
|
-
<div className={`resource-breadcrumb__label`} title={label}>
|
48
|
-
{label}
|
49
|
-
</div>
|
50
|
-
</button>
|
51
|
-
)}
|
52
|
-
{index === hierarchy.length - 1 && (
|
53
|
-
<div className={`resource-breadcrumb__label md:font-semibold`} title={label}>
|
54
|
-
{label}
|
55
|
-
</div>
|
56
|
-
)}
|
57
|
-
|
58
|
-
{hierarchy.length > 3 && index === 0 && (
|
59
|
-
<div className="resource-breadcrumb__expander flex">
|
60
|
-
<div className="before:content-['/'] mx-2" />
|
61
|
-
<button
|
62
|
-
title="Expand breadcrumb"
|
63
|
-
aria-label="Expand breadcrumb"
|
64
|
-
onClick={handleExpandHierarchy}
|
65
|
-
className="flex items-center justify-center"
|
66
|
-
>
|
67
|
-
...
|
68
|
-
</button>
|
69
|
-
</div>
|
70
|
-
)}
|
71
|
-
</li>
|
72
|
-
);
|
73
|
-
})}
|
74
|
-
</ol>
|
75
|
-
</nav>
|
76
|
-
);
|
77
|
-
};
|
78
|
-
|
79
|
-
export default ResourceBreadcrumb;
|
@@ -1,28 +0,0 @@
|
|
1
|
-
.resource-breadcrumb--collapsed {
|
2
|
-
.resource-breadcrumb__label {
|
3
|
-
@apply max-w-[250px] md:max-w-[90px] truncate hover:underline focus:underline cursor-pointer;
|
4
|
-
}
|
5
|
-
|
6
|
-
.resource-breadcrumb__item:nth-child(n + 3) {
|
7
|
-
display: none;
|
8
|
-
}
|
9
|
-
|
10
|
-
.resource-breadcrumb__item:nth-last-child(-n + 2) {
|
11
|
-
display: flex;
|
12
|
-
@apply max-md:hidden;
|
13
|
-
}
|
14
|
-
|
15
|
-
.resource-breadcrumb__item:last-child {
|
16
|
-
display: flex;
|
17
|
-
}
|
18
|
-
}
|
19
|
-
|
20
|
-
.resource-breadcrumb--expanded {
|
21
|
-
.resource-breadcrumb__expander {
|
22
|
-
display: none;
|
23
|
-
}
|
24
|
-
|
25
|
-
.resource-breadcrumb__item:last-child {
|
26
|
-
display: flex;
|
27
|
-
}
|
28
|
-
}
|
@@ -1,27 +0,0 @@
|
|
1
|
-
[
|
2
|
-
{
|
3
|
-
"key": "1",
|
4
|
-
"label": "HandyHomes website",
|
5
|
-
"node": "A"
|
6
|
-
},
|
7
|
-
{
|
8
|
-
"key": "2",
|
9
|
-
"label": "Section 1 very long name",
|
10
|
-
"node": "B"
|
11
|
-
},
|
12
|
-
{
|
13
|
-
"key": "3",
|
14
|
-
"label": "Page 1 very long name",
|
15
|
-
"node": "C"
|
16
|
-
},
|
17
|
-
{
|
18
|
-
"key": "4",
|
19
|
-
"label": "Page 71 very long name",
|
20
|
-
"node": "D"
|
21
|
-
},
|
22
|
-
{
|
23
|
-
"key": "5",
|
24
|
-
"label": "Pages very long name",
|
25
|
-
"node": "E"
|
26
|
-
}
|
27
|
-
]
|
@@ -1,202 +0,0 @@
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-empty-function */
|
2
|
-
import React from 'react';
|
3
|
-
import { screen, render, waitFor } from '@testing-library/react';
|
4
|
-
import userEvent from '@testing-library/user-event';
|
5
|
-
import { mockResource } from '../__mocks__/MockModels';
|
6
|
-
import { useOverlayTriggerState, OverlayTriggerState } from 'react-stately';
|
7
|
-
import ResourceList from './ResourceList';
|
8
|
-
|
9
|
-
const resources = [
|
10
|
-
mockResource({ id: '1', type: { code: 'page', name: 'Page' }, name: 'Resource 1' }),
|
11
|
-
mockResource({ id: '2', type: { code: 'page', name: 'Page' }, name: 'Resource 2' }),
|
12
|
-
mockResource({ id: '3', type: { code: 'folder', name: 'Folder' }, name: 'Resource 3', childCount: 31 }),
|
13
|
-
mockResource({ id: '4', type: { code: 'folder', name: 'Folder' }, name: 'Resource 4', childCount: 55 }),
|
14
|
-
mockResource({ id: '5', type: { code: 'word_doc', name: 'MS Word Document' }, name: 'Resource 5' }),
|
15
|
-
];
|
16
|
-
|
17
|
-
function ResourceListTestWrapper({
|
18
|
-
constructFunction,
|
19
|
-
}: {
|
20
|
-
constructFunction: (previewModalState: OverlayTriggerState) => JSX.Element;
|
21
|
-
}) {
|
22
|
-
const previewModalState = useOverlayTriggerState({});
|
23
|
-
return constructFunction(previewModalState);
|
24
|
-
}
|
25
|
-
|
26
|
-
describe('ResourceList', () => {
|
27
|
-
it('Shows loading when isLoading true', async () => {
|
28
|
-
const reload = jest.fn();
|
29
|
-
|
30
|
-
render(
|
31
|
-
<ResourceListTestWrapper
|
32
|
-
constructFunction={(previewModalState) => {
|
33
|
-
return (
|
34
|
-
<ResourceList
|
35
|
-
resources={resources}
|
36
|
-
previewModalState={previewModalState}
|
37
|
-
isLoading={true}
|
38
|
-
onResourceSelect={() => {}}
|
39
|
-
onResourceDrillDown={() => {}}
|
40
|
-
error={null}
|
41
|
-
handleReload={reload}
|
42
|
-
handleReturnToRoot={reload}
|
43
|
-
/>
|
44
|
-
);
|
45
|
-
}}
|
46
|
-
/>,
|
47
|
-
);
|
48
|
-
|
49
|
-
await waitFor(() => {
|
50
|
-
expect(screen.getByLabelText('loading Resource list')).toBeTruthy();
|
51
|
-
});
|
52
|
-
});
|
53
|
-
|
54
|
-
it('Focus is moved to the resource list', async () => {
|
55
|
-
const reload = jest.fn();
|
56
|
-
|
57
|
-
render(
|
58
|
-
<ResourceListTestWrapper
|
59
|
-
constructFunction={(previewModalState) => {
|
60
|
-
return (
|
61
|
-
<ResourceList
|
62
|
-
resources={resources}
|
63
|
-
previewModalState={previewModalState}
|
64
|
-
isLoading={false}
|
65
|
-
onResourceSelect={() => {}}
|
66
|
-
onResourceDrillDown={() => {}}
|
67
|
-
error={null}
|
68
|
-
handleReload={reload}
|
69
|
-
handleReturnToRoot={reload}
|
70
|
-
/>
|
71
|
-
);
|
72
|
-
}}
|
73
|
-
/>,
|
74
|
-
);
|
75
|
-
|
76
|
-
await waitFor(() => {
|
77
|
-
expect(screen.queryByLabelText('Resource list')).toHaveFocus();
|
78
|
-
});
|
79
|
-
});
|
80
|
-
|
81
|
-
it('Resource list render each resource', async () => {
|
82
|
-
const reload = jest.fn();
|
83
|
-
|
84
|
-
render(
|
85
|
-
<ResourceListTestWrapper
|
86
|
-
constructFunction={(previewModalState) => {
|
87
|
-
return (
|
88
|
-
<ResourceList
|
89
|
-
resources={resources}
|
90
|
-
previewModalState={previewModalState}
|
91
|
-
isLoading={false}
|
92
|
-
onResourceSelect={() => {}}
|
93
|
-
onResourceDrillDown={() => {}}
|
94
|
-
error={null}
|
95
|
-
handleReload={reload}
|
96
|
-
handleReturnToRoot={reload}
|
97
|
-
/>
|
98
|
-
);
|
99
|
-
}}
|
100
|
-
/>,
|
101
|
-
);
|
102
|
-
|
103
|
-
await waitFor(() => {
|
104
|
-
expect(screen.queryByText('Resource 1')).toBeTruthy();
|
105
|
-
expect(screen.queryByText('Resource 2')).toBeTruthy();
|
106
|
-
expect(screen.queryByText('Resource 3')).toBeTruthy();
|
107
|
-
expect(screen.queryByText('Resource 4')).toBeTruthy();
|
108
|
-
expect(screen.queryByText('Resource 5')).toBeTruthy();
|
109
|
-
});
|
110
|
-
});
|
111
|
-
|
112
|
-
it('Clicking resource body triggers correct onResourceSelect', async () => {
|
113
|
-
const onResourceSelect = jest.fn();
|
114
|
-
const reload = jest.fn();
|
115
|
-
|
116
|
-
render(
|
117
|
-
<ResourceListTestWrapper
|
118
|
-
constructFunction={(previewModalState) => {
|
119
|
-
return (
|
120
|
-
<ResourceList
|
121
|
-
resources={resources}
|
122
|
-
previewModalState={previewModalState}
|
123
|
-
isLoading={false}
|
124
|
-
onResourceSelect={onResourceSelect}
|
125
|
-
onResourceDrillDown={() => {}}
|
126
|
-
error={null}
|
127
|
-
handleReload={reload}
|
128
|
-
handleReturnToRoot={reload}
|
129
|
-
/>
|
130
|
-
);
|
131
|
-
}}
|
132
|
-
/>,
|
133
|
-
);
|
134
|
-
|
135
|
-
const user = userEvent.setup();
|
136
|
-
user.click(screen.getByRole('button', { name: 'page Resource 1' }));
|
137
|
-
|
138
|
-
await waitFor(() => {
|
139
|
-
// Provides the item that was clicked and an id reference to the button that was clicked
|
140
|
-
expect(onResourceSelect).toHaveBeenCalledWith(resources[0], { id: expect.any(String) });
|
141
|
-
});
|
142
|
-
});
|
143
|
-
|
144
|
-
it('Clicking node child count triggers correct onResourceDrillDown', async () => {
|
145
|
-
const onResourceDrillDown = jest.fn();
|
146
|
-
const reload = jest.fn();
|
147
|
-
|
148
|
-
render(
|
149
|
-
<ResourceListTestWrapper
|
150
|
-
constructFunction={(previewModalState) => {
|
151
|
-
return (
|
152
|
-
<ResourceList
|
153
|
-
resources={resources}
|
154
|
-
previewModalState={previewModalState}
|
155
|
-
isLoading={false}
|
156
|
-
onResourceSelect={() => {}}
|
157
|
-
onResourceDrillDown={onResourceDrillDown}
|
158
|
-
error={null}
|
159
|
-
handleReload={reload}
|
160
|
-
handleReturnToRoot={reload}
|
161
|
-
/>
|
162
|
-
);
|
163
|
-
}}
|
164
|
-
/>,
|
165
|
-
);
|
166
|
-
|
167
|
-
const user = userEvent.setup();
|
168
|
-
user.click(screen.getByRole('button', { name: 'Drill down to Resource 3 children' }));
|
169
|
-
|
170
|
-
await waitFor(() => {
|
171
|
-
expect(onResourceDrillDown).toHaveBeenCalledWith(resources[2]);
|
172
|
-
});
|
173
|
-
});
|
174
|
-
|
175
|
-
it('Renders error state when an error occurs loading resource list', async () => {
|
176
|
-
const reload = jest.fn();
|
177
|
-
|
178
|
-
const { getByText } = render(
|
179
|
-
<ResourceListTestWrapper
|
180
|
-
constructFunction={(previewModalState) => {
|
181
|
-
return (
|
182
|
-
<ResourceList
|
183
|
-
resources={resources}
|
184
|
-
previewModalState={previewModalState}
|
185
|
-
isLoading={false}
|
186
|
-
onResourceSelect={() => {}}
|
187
|
-
onResourceDrillDown={() => {}}
|
188
|
-
error={new Error('This is a resource error!')}
|
189
|
-
handleReload={reload}
|
190
|
-
handleReturnToRoot={reload}
|
191
|
-
/>
|
192
|
-
);
|
193
|
-
}}
|
194
|
-
/>,
|
195
|
-
);
|
196
|
-
|
197
|
-
await waitFor(() => {
|
198
|
-
const errorMessage = getByText('This is a resource error!');
|
199
|
-
expect(errorMessage).toBeInTheDocument();
|
200
|
-
});
|
201
|
-
});
|
202
|
-
});
|
@@ -1,40 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { StoryFn, Meta } from '@storybook/react';
|
3
|
-
import { useOverlayTriggerState } from 'react-stately';
|
4
|
-
|
5
|
-
import ResourceList from './ResourceList';
|
6
|
-
import sampleResources from './sample-resources.json';
|
7
|
-
|
8
|
-
export default {
|
9
|
-
title: 'Resource List',
|
10
|
-
component: ResourceList,
|
11
|
-
} as Meta<typeof ResourceList>;
|
12
|
-
|
13
|
-
const Template: StoryFn<typeof ResourceList> = ({ resources, isLoading, allowedTypes }) => {
|
14
|
-
const previewModalState = useOverlayTriggerState({});
|
15
|
-
|
16
|
-
return (
|
17
|
-
<ResourceList
|
18
|
-
resources={resources}
|
19
|
-
previewModalState={previewModalState}
|
20
|
-
isLoading={isLoading}
|
21
|
-
onResourceSelect={({ id }) => alert(`Resource Select: ${id}`)}
|
22
|
-
onResourceDrillDown={({ id }) => alert(`Child Drill Down: ${id}`)}
|
23
|
-
allowedTypes={allowedTypes}
|
24
|
-
/>
|
25
|
-
);
|
26
|
-
};
|
27
|
-
|
28
|
-
export const Primary = Template.bind({});
|
29
|
-
Primary.args = {
|
30
|
-
resources: sampleResources,
|
31
|
-
isLoading: false,
|
32
|
-
allowedTypes: ['site', 'image'],
|
33
|
-
};
|
34
|
-
|
35
|
-
export const Loading = Template.bind({});
|
36
|
-
Loading.args = {
|
37
|
-
...Primary.args,
|
38
|
-
isLoading: true,
|
39
|
-
allowedTypes: ['site', 'image'],
|
40
|
-
};
|
@@ -1,83 +0,0 @@
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
2
|
-
import { OverlayTriggerState } from 'react-stately';
|
3
|
-
import { DOMAttributes, FocusableElement } from '@react-types/shared';
|
4
|
-
import { ResourceItem, ResourceState, SkeletonList } from '@squiz/generic-browser-lib';
|
5
|
-
|
6
|
-
import { Resource } from '../types';
|
7
|
-
|
8
|
-
export interface ResourceListProps {
|
9
|
-
resources: Array<Resource>;
|
10
|
-
selectedResource?: Resource | null;
|
11
|
-
previewModalState: OverlayTriggerState;
|
12
|
-
isLoading: boolean;
|
13
|
-
onResourceSelect: (resource: Resource, overlayProps: DOMAttributes<FocusableElement>) => void;
|
14
|
-
onResourceDrillDown: (resource: Resource) => void;
|
15
|
-
allowedTypes?: string[] | undefined;
|
16
|
-
handleReturnToRoot: () => void;
|
17
|
-
handleReload: () => void;
|
18
|
-
error: Error | null;
|
19
|
-
}
|
20
|
-
|
21
|
-
const ResourceList = function ({
|
22
|
-
resources,
|
23
|
-
selectedResource,
|
24
|
-
previewModalState,
|
25
|
-
isLoading,
|
26
|
-
onResourceSelect,
|
27
|
-
onResourceDrillDown,
|
28
|
-
allowedTypes,
|
29
|
-
handleReturnToRoot,
|
30
|
-
handleReload,
|
31
|
-
error,
|
32
|
-
}: ResourceListProps) {
|
33
|
-
const listRef = useRef<HTMLUListElement>(null);
|
34
|
-
|
35
|
-
// When resources change, because we are on a new page, reset focus to the list
|
36
|
-
useEffect(() => {
|
37
|
-
if (listRef.current) {
|
38
|
-
listRef.current?.focus({
|
39
|
-
preventScroll: true,
|
40
|
-
});
|
41
|
-
}
|
42
|
-
}, [resources]);
|
43
|
-
|
44
|
-
if (isLoading) {
|
45
|
-
return <SkeletonList itemCount={8} ariaLabel="loading Resource list" className="text-sm font-semibold" />;
|
46
|
-
}
|
47
|
-
|
48
|
-
return (
|
49
|
-
<ul
|
50
|
-
ref={listRef}
|
51
|
-
tabIndex={-1}
|
52
|
-
aria-label={`Resource list`}
|
53
|
-
className="flex flex-col text-sm font-semibold px-7 my-4 focus-visible:outline-0"
|
54
|
-
>
|
55
|
-
{/* Error State */}
|
56
|
-
{error && <ResourceState state="error" message={error.message} handleReload={handleReload} />}
|
57
|
-
|
58
|
-
{/* Empty State */}
|
59
|
-
{!error && resources.length === 0 && <ResourceState state="empty" handleReload={handleReturnToRoot} />}
|
60
|
-
|
61
|
-
{!error &&
|
62
|
-
resources.map((resource) => {
|
63
|
-
return (
|
64
|
-
<ResourceItem
|
65
|
-
key={resource.id}
|
66
|
-
item={resource}
|
67
|
-
selected={resource.id == selectedResource?.id}
|
68
|
-
label={resource.name}
|
69
|
-
type={resource.type.code}
|
70
|
-
childCount={resource.childCount}
|
71
|
-
previewModalState={previewModalState}
|
72
|
-
onSelect={onResourceSelect}
|
73
|
-
onDrillDown={onResourceDrillDown}
|
74
|
-
className="border-b-0 first:mt-0 first:rounded-t-lg last:rounded-b-lg last:border-b"
|
75
|
-
allowedTypes={allowedTypes}
|
76
|
-
/>
|
77
|
-
);
|
78
|
-
})}
|
79
|
-
</ul>
|
80
|
-
);
|
81
|
-
};
|
82
|
-
|
83
|
-
export default ResourceList;
|