@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.
Files changed (141) hide show
  1. package/CHANGELOG.md +89 -38
  2. package/LICENSE.md +15 -0
  3. package/README.md +9 -0
  4. package/jest.config.ts +22 -21
  5. package/lib/Hooks/useSelectedState.d.ts +15 -0
  6. package/lib/Hooks/useSelectedState.js +16 -0
  7. package/lib/Hooks/useSources.d.ts +6 -6
  8. package/lib/Hooks/useSources.js +26 -1
  9. package/lib/MainContainer/MainContainer.d.ts +17 -0
  10. package/lib/MainContainer/MainContainer.js +61 -0
  11. package/lib/Plugin/Plugin.d.ts +13 -0
  12. package/lib/Plugin/Plugin.js +17 -0
  13. package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +2 -3
  14. package/lib/ResourceBrowserContext/ResourceBrowserContext.js +4 -17
  15. package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +24 -0
  16. package/lib/ResourceBrowserInput/ResourceBrowserInput.js +16 -0
  17. package/lib/ResourcePicker/ResourcePicker.d.ts +6 -4
  18. package/lib/ResourcePicker/ResourcePicker.js +14 -8
  19. package/lib/ResourcePicker/States/Selected.d.ts +10 -4
  20. package/lib/ResourcePicker/States/Selected.js +11 -32
  21. package/lib/SourceDropdown/SourceDropdown.d.ts +5 -11
  22. package/lib/SourceDropdown/SourceDropdown.js +20 -99
  23. package/lib/SourceList/SourceList.d.ts +5 -16
  24. package/lib/SourceList/SourceList.js +14 -75
  25. package/lib/index.css +42 -202
  26. package/lib/index.d.ts +7 -7
  27. package/lib/index.js +69 -13
  28. package/lib/types.d.ts +41 -59
  29. package/package.json +82 -80
  30. package/src/Hooks/useSelectedState.spec.ts +46 -0
  31. package/src/Hooks/useSelectedState.ts +22 -0
  32. package/src/Hooks/useSources.spec.ts +60 -13
  33. package/src/Hooks/useSources.ts +35 -5
  34. package/src/Icons/CircledLoopIcon.tsx +8 -8
  35. package/src/MainContainer/MainContainer.spec.tsx +203 -0
  36. package/src/MainContainer/MainContainer.stories.tsx +62 -0
  37. package/src/MainContainer/MainContainer.tsx +101 -0
  38. package/src/Plugin/Plugin.spec.tsx +46 -0
  39. package/src/Plugin/Plugin.tsx +20 -0
  40. package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +65 -106
  41. package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +24 -39
  42. package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +192 -0
  43. package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +81 -0
  44. package/src/ResourcePicker/ResourcePicker.spec.tsx +159 -116
  45. package/src/ResourcePicker/ResourcePicker.stories.tsx +28 -24
  46. package/src/ResourcePicker/ResourcePicker.tsx +79 -59
  47. package/src/ResourcePicker/States/Error.tsx +8 -8
  48. package/src/ResourcePicker/States/Loading.tsx +3 -3
  49. package/src/ResourcePicker/States/Selected.tsx +66 -73
  50. package/src/ResourcePicker/mock-image-resource.json +25 -47
  51. package/src/ResourcePicker/mock-resource.json +11 -13
  52. package/src/ResourcePicker/resource-picker.scss +13 -13
  53. package/src/SourceDropdown/SourceDropdown.spec.tsx +65 -391
  54. package/src/SourceDropdown/SourceDropdown.stories.tsx +21 -24
  55. package/src/SourceDropdown/SourceDropdown.tsx +80 -258
  56. package/src/SourceList/SourceList.spec.tsx +37 -430
  57. package/src/SourceList/SourceList.stories.tsx +17 -37
  58. package/src/SourceList/SourceList.tsx +28 -155
  59. package/src/__mocks__/MockModels.ts +56 -25
  60. package/src/__mocks__/PluginExample.tsx +98 -0
  61. package/src/__mocks__/StorybookHelpers.tsx +141 -0
  62. package/src/__mocks__/renderWithContext.tsx +14 -18
  63. package/src/__mocks__/sample-sources.json +32 -0
  64. package/src/index.scss +18 -8
  65. package/src/index.spec.tsx +277 -99
  66. package/src/index.stories.tsx +65 -39
  67. package/src/index.tsx +119 -57
  68. package/src/types.ts +54 -63
  69. package/tailwind.config.cjs +92 -92
  70. package/vite.config.js +12 -12
  71. package/lib/Hooks/useCategorisedSources.d.ts +0 -14
  72. package/lib/Hooks/useCategorisedSources.js +0 -38
  73. package/lib/Hooks/useChildResources.d.ts +0 -16
  74. package/lib/Hooks/useChildResources.js +0 -13
  75. package/lib/Hooks/usePreselectedResourcePath.d.ts +0 -20
  76. package/lib/Hooks/usePreselectedResourcePath.js +0 -31
  77. package/lib/Hooks/useRecentLocations.d.ts +0 -5
  78. package/lib/Hooks/useRecentLocations.js +0 -38
  79. package/lib/Hooks/useRecentResourcesPaths.d.ts +0 -20
  80. package/lib/Hooks/useRecentResourcesPaths.js +0 -30
  81. package/lib/Hooks/useResource.d.ts +0 -28
  82. package/lib/Hooks/useResource.js +0 -25
  83. package/lib/Hooks/useResourcePath.d.ts +0 -16
  84. package/lib/Hooks/useResourcePath.js +0 -64
  85. package/lib/Icons/HistoryIcon.d.ts +0 -4
  86. package/lib/Icons/HistoryIcon.js +0 -13
  87. package/lib/PreviewPanel/PreviewPanel.d.ts +0 -5
  88. package/lib/PreviewPanel/PreviewPanel.js +0 -8
  89. package/lib/PreviewPanel/details/MatrixResource.d.ts +0 -7
  90. package/lib/PreviewPanel/details/MatrixResource.js +0 -35
  91. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.d.ts +0 -9
  92. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +0 -54
  93. package/lib/ResourceList/ResourceList.d.ts +0 -18
  94. package/lib/ResourceList/ResourceList.js +0 -49
  95. package/lib/ResourcePickerContainer/ResourcePickerContainer.d.ts +0 -17
  96. package/lib/ResourcePickerContainer/ResourcePickerContainer.js +0 -166
  97. package/lib/StatusIndicator/StatusIndicator.d.ts +0 -8
  98. package/lib/StatusIndicator/StatusIndicator.js +0 -27
  99. package/lib/utils/findBestMatchLineage.d.ts +0 -2
  100. package/lib/utils/findBestMatchLineage.js +0 -28
  101. package/lib/utils/uuid.d.ts +0 -1
  102. package/lib/utils/uuid.js +0 -6
  103. package/src/Hooks/useCategorisedSources.spec.ts +0 -39
  104. package/src/Hooks/useCategorisedSources.ts +0 -46
  105. package/src/Hooks/useChildResources.spec.ts +0 -29
  106. package/src/Hooks/useChildResources.ts +0 -21
  107. package/src/Hooks/usePreselectedResourcePath.ts +0 -54
  108. package/src/Hooks/useRecentLocations.spec.ts +0 -81
  109. package/src/Hooks/useRecentLocations.ts +0 -44
  110. package/src/Hooks/useRecentResourcesPaths.ts +0 -54
  111. package/src/Hooks/useResource.spec.ts +0 -61
  112. package/src/Hooks/useResource.ts +0 -40
  113. package/src/Hooks/useResourcePath.spec.ts +0 -120
  114. package/src/Hooks/useResourcePath.ts +0 -76
  115. package/src/Icons/HistoryIcon.tsx +0 -17
  116. package/src/PreviewPanel/PreviewPanel.spec.tsx +0 -198
  117. package/src/PreviewPanel/PreviewPanel.stories.tsx +0 -76
  118. package/src/PreviewPanel/PreviewPanel.tsx +0 -6
  119. package/src/PreviewPanel/details/MatrixResource.tsx +0 -54
  120. package/src/PreviewPanel/details/matrix-resource.scss +0 -16
  121. package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +0 -133
  122. package/src/ResourceBreadcrumb/ResourceBreadcrumb.stories.tsx +0 -24
  123. package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +0 -79
  124. package/src/ResourceBreadcrumb/resource-breadcrumb.scss +0 -28
  125. package/src/ResourceBreadcrumb/sample-hierarchy.json +0 -27
  126. package/src/ResourceList/ResourceList.spec.tsx +0 -202
  127. package/src/ResourceList/ResourceList.stories.tsx +0 -40
  128. package/src/ResourceList/ResourceList.tsx +0 -83
  129. package/src/ResourceList/sample-resources.json +0 -851
  130. package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +0 -780
  131. package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +0 -45
  132. package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +0 -290
  133. package/src/SourceList/sample-sources.json +0 -251
  134. package/src/StatusIndicator/StatusIndicator.stories.tsx +0 -83
  135. package/src/StatusIndicator/StatusIndicator.tsx +0 -38
  136. package/src/__mocks__/JestHelpers.ts +0 -65
  137. package/src/__mocks__/StorybookHelpers.ts +0 -128
  138. package/src/__mocks__/jestHelpers.spec.ts +0 -38
  139. package/src/utils/findBestMatchLineage.spec.ts +0 -81
  140. package/src/utils/findBestMatchLineage.ts +0 -30
  141. 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;