@squiz/resource-browser 1.32.1-alpha.14 → 1.32.1-alpha.16
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/jest.config.ts +12 -1
- package/lib/Hooks/useCategorisedSources.d.ts +14 -0
- package/lib/Hooks/useCategorisedSources.js +38 -0
- package/lib/Hooks/useChildResources.d.ts +19 -0
- package/lib/Hooks/useChildResources.js +35 -0
- package/lib/Hooks/useResourcePath.d.ts +16 -0
- package/lib/Hooks/useResourcePath.js +64 -0
- package/lib/Hooks/useSources.d.ts +16 -0
- package/lib/Hooks/useSources.js +29 -0
- package/lib/Icons/Icon.d.ts +7 -7
- package/lib/Icons/Icon.js +7 -9
- package/lib/Icons/MatrixResources/Audio.js +1 -1
- package/lib/Icons/MatrixResources/Excel.js +1 -1
- package/lib/Icons/MatrixResources/MatrixResourceMap.d.ts +6 -6
- package/lib/Icons/MatrixResources/MatrixResourceMap.js +6 -6
- package/lib/Icons/MatrixResources/Pdf.js +1 -1
- package/lib/Icons/MatrixResources/Powerpoint.js +1 -1
- package/lib/Icons/MatrixResources/Video.js +1 -1
- package/lib/Icons/MatrixResources/Word.js +1 -1
- package/lib/Modal/Modal.js +1 -1
- package/lib/PreviewPanel/PreviewModal.js +1 -1
- package/lib/PreviewPanel/PreviewPanel.d.ts +4 -6
- package/lib/PreviewPanel/PreviewPanel.js +11 -39
- package/lib/PreviewPanel/details/MatrixResource.d.ts +4 -9
- package/lib/PreviewPanel/details/MatrixResource.js +20 -16
- package/lib/ResourceBreadcrumb/ResourceBreadcrumb.d.ts +5 -5
- package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +3 -3
- package/lib/ResourceItem/ResourceItem.d.ts +6 -8
- package/lib/ResourceItem/ResourceItem.js +3 -3
- package/lib/ResourceList/ResourceList.d.ts +5 -4
- package/lib/ResourceList/ResourceList.js +3 -3
- package/lib/ResourcePickerContainer/ResourcePickerContainer.d.ts +4 -5
- package/lib/ResourcePickerContainer/ResourcePickerContainer.js +34 -89
- package/lib/SourceDropdown/SourceDropdown.d.ts +5 -5
- package/lib/SourceDropdown/SourceDropdown.js +19 -27
- package/lib/SourceList/SourceList.d.ts +4 -4
- package/lib/SourceList/SourceList.js +7 -5
- package/lib/index.css +6 -0
- package/lib/index.d.ts +6 -29
- package/lib/index.js +2 -3
- package/lib/uuid.js +1 -3
- package/package.json +3 -2
- package/src/Hooks/useCategorisedSources.spec.ts +39 -0
- package/src/Hooks/useCategorisedSources.ts +46 -0
- package/src/Hooks/useChildResources.spec.ts +49 -0
- package/src/Hooks/useChildResources.ts +43 -0
- package/src/Hooks/useResourcePath.spec.ts +124 -0
- package/src/Hooks/useResourcePath.ts +76 -0
- package/src/Hooks/useSources.spec.ts +33 -0
- package/src/Hooks/useSources.ts +33 -0
- package/src/Icons/Icon.stories.tsx +7 -7
- package/src/Icons/Icon.tsx +9 -14
- package/src/Icons/MatrixResources/Audio.tsx +1 -1
- package/src/Icons/MatrixResources/Excel.tsx +1 -1
- package/src/Icons/MatrixResources/MatrixResourceMap.ts +7 -7
- package/src/Icons/MatrixResources/Pdf.tsx +1 -1
- package/src/Icons/MatrixResources/Powerpoint.tsx +1 -1
- package/src/Icons/MatrixResources/Video.tsx +1 -1
- package/src/Icons/MatrixResources/Word.tsx +1 -1
- package/src/Modal/Modal.tsx +1 -1
- package/src/PreviewPanel/PreviewModal.tsx +1 -1
- package/src/PreviewPanel/PreviewPanel.spec.tsx +20 -62
- package/src/PreviewPanel/PreviewPanel.stories.tsx +16 -24
- package/src/PreviewPanel/PreviewPanel.tsx +15 -51
- package/src/PreviewPanel/details/MatrixResource.tsx +23 -19
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +13 -23
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.stories.tsx +1 -1
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +8 -9
- package/src/ResourceBreadcrumb/sample-hierarchy.json +15 -25
- package/src/ResourceItem/ResourceItem.tsx +10 -12
- package/src/ResourceList/ResourceList.spec.tsx +8 -53
- package/src/ResourceList/ResourceList.stories.tsx +2 -2
- package/src/ResourceList/ResourceList.tsx +12 -10
- package/src/ResourceList/sample-resources.json +551 -49
- package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +196 -315
- package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +7 -29
- package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +63 -127
- package/src/SourceDropdown/SourceDropdown.spec.tsx +63 -60
- package/src/SourceDropdown/SourceDropdown.stories.tsx +4 -7
- package/src/SourceDropdown/SourceDropdown.tsx +34 -41
- package/src/SourceList/SourceList.spec.tsx +38 -32
- package/src/SourceList/SourceList.tsx +17 -19
- package/src/SourceList/sample-sources.json +186 -77
- package/src/__mocks__/MockModels.ts +30 -0
- package/src/__mocks__/StorybookHelpers.ts +46 -0
- package/src/index.stories.tsx +13 -38
- package/src/index.tsx +5 -29
- package/src/types.d.ts +71 -0
- package/src/uuid.ts +2 -4
- package/src/SourceDropdown/sample-sources.json +0 -110
@@ -9,6 +9,7 @@ import { DOMAttributes, FocusableElement } from '@react-types/shared';
|
|
9
9
|
import { useOverlayTrigger } from 'react-aria';
|
10
10
|
|
11
11
|
import PreviewPanel from './PreviewPanel';
|
12
|
+
import { Resource } from '../types';
|
12
13
|
|
13
14
|
function PreviewPanelTestWrapper({
|
14
15
|
constructFunction,
|
@@ -24,35 +25,20 @@ function PreviewPanelTestWrapper({
|
|
24
25
|
return constructFunction(previewModalState, overlayProps);
|
25
26
|
}
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
isLoading
|
40
|
-
allowedTypes={undefined}
|
41
|
-
modalState={previewModalState}
|
42
|
-
previewModalOverlayProps={overlayProps}
|
43
|
-
onSelect={onSelect}
|
44
|
-
onClose={onClose}
|
45
|
-
/>
|
46
|
-
);
|
47
|
-
}}
|
48
|
-
/>,
|
49
|
-
);
|
50
|
-
|
51
|
-
await waitFor(() => {
|
52
|
-
expect(screen.getAllByText('Loading info')).toBeTruthy();
|
53
|
-
});
|
54
|
-
});
|
28
|
+
function mockResource(properties: Partial<Resource> = {}): Resource {
|
29
|
+
return {
|
30
|
+
id: 'test-resource',
|
31
|
+
name: 'Test resource',
|
32
|
+
type: { code: 'folder', name: 'Folder' },
|
33
|
+
status: { code: 'live', name: 'Live' },
|
34
|
+
url: 'https://no-where.com',
|
35
|
+
urls: [],
|
36
|
+
childCount: 0,
|
37
|
+
...properties,
|
38
|
+
};
|
39
|
+
}
|
55
40
|
|
41
|
+
describe('PreviewPanel', () => {
|
56
42
|
it('Renders a message when no item selected', async () => {
|
57
43
|
const onSelect = jest.fn();
|
58
44
|
const onClose = jest.fn();
|
@@ -62,9 +48,7 @@ describe('PreviewPanel', () => {
|
|
62
48
|
constructFunction={(previewModalState, overlayProps) => {
|
63
49
|
return (
|
64
50
|
<PreviewPanel
|
65
|
-
|
66
|
-
resourceDetail={null}
|
67
|
-
isLoading={false}
|
51
|
+
resource={null}
|
68
52
|
allowedTypes={undefined}
|
69
53
|
modalState={previewModalState}
|
70
54
|
previewModalOverlayProps={overlayProps}
|
@@ -91,16 +75,7 @@ describe('PreviewPanel', () => {
|
|
91
75
|
constructFunction={(previewModalState, overlayProps) => {
|
92
76
|
return (
|
93
77
|
<PreviewPanel
|
94
|
-
|
95
|
-
resourceDetail={{
|
96
|
-
type: 'folder',
|
97
|
-
name: 'TestResource',
|
98
|
-
properties: new Map([
|
99
|
-
['assetId', '12345'],
|
100
|
-
['status', 'UnderConstruction'],
|
101
|
-
]),
|
102
|
-
}}
|
103
|
-
isLoading={false}
|
78
|
+
resource={mockResource({ name: 'TestResource' })}
|
104
79
|
allowedTypes={undefined}
|
105
80
|
modalState={previewModalState}
|
106
81
|
previewModalOverlayProps={overlayProps}
|
@@ -129,16 +104,7 @@ describe('PreviewPanel', () => {
|
|
129
104
|
constructFunction={(previewModalState, overlayProps) => {
|
130
105
|
return (
|
131
106
|
<PreviewPanel
|
132
|
-
|
133
|
-
resourceDetail={{
|
134
|
-
type: 'folder',
|
135
|
-
name: 'TestResource',
|
136
|
-
properties: new Map([
|
137
|
-
['assetId', '12345'],
|
138
|
-
['status', 'UnderConstruction'],
|
139
|
-
]),
|
140
|
-
}}
|
141
|
-
isLoading={false}
|
107
|
+
resource={mockResource({ name: 'TestResource' })}
|
142
108
|
allowedTypes={undefined}
|
143
109
|
modalState={previewModalState}
|
144
110
|
previewModalOverlayProps={overlayProps}
|
@@ -160,22 +126,14 @@ describe('PreviewPanel', () => {
|
|
160
126
|
it('Clicking select button return source and id of resource being shown', async () => {
|
161
127
|
const onSelect = jest.fn();
|
162
128
|
const onClose = jest.fn();
|
129
|
+
const resource = mockResource();
|
163
130
|
|
164
131
|
render(
|
165
132
|
<PreviewPanelTestWrapper
|
166
133
|
constructFunction={(previewModalState, overlayProps) => {
|
167
134
|
return (
|
168
135
|
<PreviewPanel
|
169
|
-
|
170
|
-
resourceDetail={{
|
171
|
-
type: 'folder',
|
172
|
-
name: 'TestResource',
|
173
|
-
properties: new Map([
|
174
|
-
['assetId', '12345'],
|
175
|
-
['status', 'UnderConstruction'],
|
176
|
-
]),
|
177
|
-
}}
|
178
|
-
isLoading={false}
|
136
|
+
resource={resource}
|
179
137
|
allowedTypes={undefined}
|
180
138
|
modalState={previewModalState}
|
181
139
|
previewModalOverlayProps={overlayProps}
|
@@ -191,7 +149,7 @@ describe('PreviewPanel', () => {
|
|
191
149
|
user.click(screen.getByRole('button'));
|
192
150
|
|
193
151
|
await waitFor(() => {
|
194
|
-
expect(onSelect).toHaveBeenCalledWith(
|
152
|
+
expect(onSelect).toHaveBeenCalledWith(resource);
|
195
153
|
});
|
196
154
|
});
|
197
155
|
});
|
@@ -10,19 +10,17 @@ export default {
|
|
10
10
|
component: PreviewPanel,
|
11
11
|
} as Meta<typeof PreviewPanel>;
|
12
12
|
|
13
|
-
const Template: StoryFn<typeof PreviewPanel> = ({
|
13
|
+
const Template: StoryFn<typeof PreviewPanel> = ({ resource, allowedTypes }) => {
|
14
14
|
const previewModalState = useOverlayTriggerState({});
|
15
15
|
const { overlayProps } = useOverlayTrigger({ type: 'dialog' }, previewModalState);
|
16
16
|
|
17
17
|
return (
|
18
18
|
<PreviewPanel
|
19
|
-
|
20
|
-
resourceDetail={resourceDetail}
|
19
|
+
resource={resource}
|
21
20
|
modalState={previewModalState}
|
22
21
|
previewModalOverlayProps={overlayProps}
|
23
|
-
isLoading={isLoading}
|
24
22
|
allowedTypes={allowedTypes}
|
25
|
-
onSelect={({
|
23
|
+
onSelect={({ id, name }) => alert(`Resource Selected: ${id} - ${name}`)}
|
26
24
|
onClose={() => alert(`OnClose Selected`)}
|
27
25
|
/>
|
28
26
|
);
|
@@ -30,32 +28,26 @@ const Template: StoryFn<typeof PreviewPanel> = ({ node, resourceDetail, isLoadin
|
|
30
28
|
|
31
29
|
export const Primary = Template.bind({});
|
32
30
|
Primary.args = {
|
33
|
-
|
31
|
+
resource: {
|
34
32
|
id: '1',
|
35
|
-
source: '1',
|
36
|
-
},
|
37
|
-
resourceDetail: {
|
38
|
-
type: 'page',
|
39
33
|
name: 'Products',
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
34
|
+
type: {
|
35
|
+
code: 'page_standard',
|
36
|
+
name: 'Standard Page',
|
37
|
+
},
|
38
|
+
status: {
|
39
|
+
code: 'live',
|
40
|
+
name: 'Live',
|
41
|
+
},
|
42
|
+
url: 'http://my-squiz.net/assets/1',
|
43
|
+
urls: [],
|
44
|
+
childCount: 0,
|
44
45
|
},
|
45
|
-
isLoading: false,
|
46
|
-
isNotSelected: false,
|
47
46
|
allowedTypes: undefined,
|
48
47
|
};
|
49
48
|
|
50
49
|
export const NoSelected = Template.bind({});
|
51
50
|
NoSelected.args = {
|
52
51
|
...Primary.args,
|
53
|
-
|
54
|
-
resourceDetail: null,
|
55
|
-
};
|
56
|
-
|
57
|
-
export const Loading = Template.bind({});
|
58
|
-
Loading.args = {
|
59
|
-
...Primary.args,
|
60
|
-
isLoading: true,
|
52
|
+
resource: null,
|
61
53
|
};
|
@@ -1,29 +1,24 @@
|
|
1
|
-
import React, { useEffect
|
1
|
+
import React, { useEffect } from 'react';
|
2
2
|
import { useMediaQuery } from 'react-responsive';
|
3
3
|
import { OverlayTriggerState } from 'react-stately';
|
4
4
|
import { DOMAttributes, FocusableElement } from '@react-types/shared';
|
5
5
|
|
6
6
|
import Icon, { IconOptions } from '../Icons/Icon';
|
7
7
|
import MatrixResource from './details/MatrixResource';
|
8
|
-
import {
|
8
|
+
import { Resource } from '../types';
|
9
9
|
import PreviewModal from './PreviewModal';
|
10
|
-
import Spinner from '../Spinner/Spinner';
|
11
10
|
|
12
11
|
export interface PreviewPanelProps {
|
13
|
-
|
14
|
-
resourceDetail: ResourceDetail | null;
|
15
|
-
isLoading: boolean;
|
12
|
+
resource: Resource | null;
|
16
13
|
modalState: OverlayTriggerState;
|
17
14
|
previewModalOverlayProps: DOMAttributes<FocusableElement>;
|
18
15
|
allowedTypes: string[] | undefined;
|
19
|
-
onSelect: (
|
16
|
+
onSelect: (resource: Resource) => void;
|
20
17
|
onClose: () => void;
|
21
18
|
}
|
22
19
|
|
23
20
|
const PreviewPanel = function ({
|
24
|
-
|
25
|
-
resourceDetail,
|
26
|
-
isLoading,
|
21
|
+
resource,
|
27
22
|
previewModalOverlayProps,
|
28
23
|
modalState,
|
29
24
|
onSelect,
|
@@ -31,44 +26,23 @@ const PreviewPanel = function ({
|
|
31
26
|
}: PreviewPanelProps) {
|
32
27
|
// Watch the media size to see if we are on mobile size
|
33
28
|
const isMobile = useMediaQuery({ query: '(max-width: 640px)' });
|
34
|
-
const previousIsMobile = useRef<boolean | null>(null);
|
35
29
|
|
30
|
+
// If we are on mobile and the selected resource changes show the preview panel modal.
|
36
31
|
useEffect(() => {
|
37
|
-
if (
|
38
|
-
modalState.setOpen(
|
39
|
-
previousIsMobile.current = true;
|
40
|
-
} else {
|
41
|
-
previousIsMobile.current = false;
|
32
|
+
if (!modalState.isOpen) {
|
33
|
+
modalState.setOpen(Boolean(resource && isMobile));
|
42
34
|
}
|
43
|
-
}, []);
|
35
|
+
}, [resource, isMobile]);
|
44
36
|
|
45
|
-
|
46
|
-
useEffect(() => {
|
47
|
-
// Only trigger if we have a node selected, otherwise the modal will re-open itself as soon as its closed
|
48
|
-
if (node) {
|
49
|
-
if (previousIsMobile.current !== isMobile) {
|
50
|
-
previousIsMobile.current = isMobile;
|
51
|
-
|
52
|
-
if (isMobile) {
|
53
|
-
// If no mobile open the modal automatically
|
54
|
-
modalState.setOpen(true);
|
55
|
-
} else {
|
56
|
-
// If not in mobile close the modal automatically
|
57
|
-
modalState.setOpen(false);
|
58
|
-
}
|
59
|
-
}
|
60
|
-
}
|
61
|
-
}, [node, isMobile, modalState]);
|
62
|
-
|
63
|
-
let previewPanel = node && resourceDetail && (
|
37
|
+
const previewPanel = resource && (
|
64
38
|
<>
|
65
39
|
<div className="flex flex-col grow">
|
66
|
-
<MatrixResource {
|
40
|
+
<MatrixResource resource={resource} />
|
67
41
|
</div>
|
68
42
|
<div className="flex justify-end border-t border-gray-300">
|
69
43
|
<button
|
70
44
|
type="button"
|
71
|
-
onClick={() => onSelect(
|
45
|
+
onClick={() => onSelect(resource)}
|
72
46
|
className="rounded text-sm text-white bg-blue-300 py-2 px-2.5 m-5"
|
73
47
|
>
|
74
48
|
Select
|
@@ -76,16 +50,6 @@ const PreviewPanel = function ({
|
|
76
50
|
</div>
|
77
51
|
</>
|
78
52
|
);
|
79
|
-
if (isLoading) {
|
80
|
-
previewPanel = (
|
81
|
-
<div className="flex flex-col grow">
|
82
|
-
<div className="flex flex-col grow items-center mt-20 mx-20">
|
83
|
-
<Spinner />
|
84
|
-
<div className="text-sm text-gray-600 text-center mt-4">Loading info</div>
|
85
|
-
</div>
|
86
|
-
</div>
|
87
|
-
);
|
88
|
-
}
|
89
53
|
|
90
54
|
return (
|
91
55
|
<div className="sm:overflow-y-scroll sm:flex-1 sm:grow-[2] bg-white">
|
@@ -93,7 +57,7 @@ const PreviewPanel = function ({
|
|
93
57
|
{!isMobile && <h3 className="sr-only">Resource Details</h3>}
|
94
58
|
|
95
59
|
{/* Nothing selected, show an info message */}
|
96
|
-
{
|
60
|
+
{resource === null && (
|
97
61
|
<div className="max-sm:hidden flex flex-col h-full">
|
98
62
|
<div className="flex flex-col grow items-center mt-20 mx-20">
|
99
63
|
<Icon icon={'resource-select' as IconOptions} aria-hidden />
|
@@ -108,14 +72,14 @@ const PreviewPanel = function ({
|
|
108
72
|
)}
|
109
73
|
|
110
74
|
{/* Resource details shows in a new modal / bottom popover on mobile size */}
|
111
|
-
{
|
75
|
+
{resource && isMobile && modalState.isOpen && (
|
112
76
|
<PreviewModal state={modalState} overlayProps={previewModalOverlayProps} onClose={onClose}>
|
113
77
|
{previewPanel}
|
114
78
|
</PreviewModal>
|
115
79
|
)}
|
116
80
|
|
117
81
|
{/* If not mobile, just print the details out */}
|
118
|
-
{
|
82
|
+
{resource && !isMobile && <div className="flex flex-col h-full">{previewPanel}</div>}
|
119
83
|
</div>
|
120
84
|
);
|
121
85
|
};
|
@@ -1,53 +1,57 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
|
3
|
-
import {
|
3
|
+
import { Resource } from '../../types';
|
4
4
|
import Icon, { IconOptions } from '../../Icons/Icon';
|
5
5
|
|
6
6
|
const statusColour = {
|
7
|
-
|
8
|
-
|
7
|
+
// Duplicated from the Matrix repository.
|
8
|
+
// src/Api/AssetManagementApi/Constants/AssetStatuses.php - contains a list of possible statuses.
|
9
|
+
// frontend/src/styles/common/status-colors.scss - contains the colours used for those statuses in Matrix.
|
10
|
+
unknown: '#ff0000',
|
11
|
+
archived: '#c98a67',
|
12
|
+
under_construction: '#94d1f9',
|
13
|
+
pending_approval: '#d0bbf0',
|
14
|
+
approved_to_go_live: '#f7eaa2',
|
15
|
+
live: '#bfe60a',
|
16
|
+
up_for_review: '#72cd32',
|
17
|
+
safe_editing: '#ff97b0',
|
18
|
+
safe_editing_pending_approval: '#d688db',
|
19
|
+
safe_edit_approved_to_go_live: '#ffb34a',
|
9
20
|
};
|
10
21
|
|
11
|
-
|
12
|
-
|
13
|
-
Live = 'Live',
|
14
|
-
}
|
15
|
-
|
16
|
-
export type MatrixAsset = ResourceDetail & {
|
17
|
-
assetId?: string;
|
18
|
-
status?: MatrixStatus;
|
22
|
+
type MatrixResourceProps = {
|
23
|
+
resource: Resource;
|
19
24
|
};
|
20
25
|
|
21
|
-
const MatrixResource = ({ type, name,
|
22
|
-
const
|
23
|
-
const status = properties.get('status');
|
26
|
+
const MatrixResource = ({ resource: { id, type, name, status } }: MatrixResourceProps) => {
|
27
|
+
const color = statusColour[status.code as keyof typeof statusColour] || statusColour.unknown;
|
24
28
|
|
25
29
|
return (
|
26
30
|
<div>
|
27
31
|
<div className="flex flex-col items-center text-gray-800 mt-7 mx-5 pb-4 border-b border-gray-300">
|
28
|
-
<Icon icon={type as IconOptions} resourceSource="matrix" className="w-14 h-14" />
|
32
|
+
<Icon icon={type.code as IconOptions} resourceSource="matrix" className="w-14 h-14" />
|
29
33
|
<div className="mt-4 font-semibold text-base">{name}</div>
|
30
34
|
</div>
|
31
35
|
<div className="text-gray-800 mx-6 mt-4">
|
32
36
|
<dl className="flex flex-col text-sm">
|
33
37
|
<div className="flex mb-2">
|
34
38
|
<dt className="w-[60px] mr-4 text-gray-600">Type</dt>
|
35
|
-
<dd className="font-semibold">{type}</dd>
|
39
|
+
<dd className="font-semibold">{type.name}</dd>
|
36
40
|
</div>
|
37
41
|
|
38
42
|
<div className="flex mb-2">
|
39
43
|
<dt className="w-[60px] mr-4 text-gray-600">Asset ID</dt>
|
40
|
-
<dd className="font-semibold">#{
|
44
|
+
<dd className="font-semibold">#{id}</dd>
|
41
45
|
</div>
|
42
46
|
|
43
47
|
<div className="flex mb-2">
|
44
48
|
<dt className="w-[60px] mr-4 text-gray-600">Status</dt>
|
45
49
|
<dd className="flex items-center font-semibold">
|
46
50
|
<span
|
47
|
-
style={{ backgroundColor:
|
51
|
+
style={{ backgroundColor: color }}
|
48
52
|
className="block rounded-full w-3 h-3 mr-1 border border-solid border-black border-opacity-20"
|
49
53
|
></span>
|
50
|
-
{
|
54
|
+
{status.name}
|
51
55
|
</dd>
|
52
56
|
</div>
|
53
57
|
</dl>
|
@@ -7,29 +7,25 @@ import ResourceBreadcrumb from './ResourceBreadcrumb';
|
|
7
7
|
|
8
8
|
const hierarchy = [
|
9
9
|
{
|
10
|
-
|
11
|
-
id: '1',
|
12
|
-
source: '1',
|
13
|
-
},
|
10
|
+
key: '1',
|
14
11
|
label: 'HandyHomes website',
|
12
|
+
node: 'A',
|
15
13
|
},
|
16
14
|
{
|
17
|
-
|
18
|
-
id: '2',
|
19
|
-
source: '1',
|
20
|
-
},
|
15
|
+
key: '2',
|
21
16
|
label: 'Section 1',
|
17
|
+
node: 'B',
|
22
18
|
},
|
23
19
|
{
|
24
|
-
|
25
|
-
id: '3',
|
26
|
-
source: '1',
|
27
|
-
},
|
20
|
+
key: '3',
|
28
21
|
label: 'Pages',
|
22
|
+
node: 'C',
|
29
23
|
},
|
30
24
|
];
|
31
25
|
|
32
26
|
describe('ResourceBreadcrumb', () => {
|
27
|
+
const onBreadcrumbSelect = jest.fn();
|
28
|
+
|
33
29
|
it('Breadcrumb renders each item', async () => {
|
34
30
|
render(<ResourceBreadcrumb hierarchy={hierarchy} onBreadcrumbSelect={() => {}} onReturnToRoot={() => {}} />);
|
35
31
|
|
@@ -47,8 +43,6 @@ describe('ResourceBreadcrumb', () => {
|
|
47
43
|
});
|
48
44
|
|
49
45
|
it('Clicking breadcrumb calls onBreadcrumbSelect', async () => {
|
50
|
-
const onBreadcrumbSelect = jest.fn();
|
51
|
-
|
52
46
|
render(
|
53
47
|
<ResourceBreadcrumb hierarchy={hierarchy} onBreadcrumbSelect={onBreadcrumbSelect} onReturnToRoot={() => {}} />,
|
54
48
|
);
|
@@ -57,7 +51,7 @@ describe('ResourceBreadcrumb', () => {
|
|
57
51
|
user.click(screen.getByRole('button', { name: 'HandyHomes website' }));
|
58
52
|
|
59
53
|
await waitFor(() => {
|
60
|
-
expect(onBreadcrumbSelect).toHaveBeenCalledWith(
|
54
|
+
expect(onBreadcrumbSelect).toHaveBeenCalledWith(hierarchy[0].node);
|
61
55
|
});
|
62
56
|
});
|
63
57
|
|
@@ -80,18 +74,14 @@ describe('ResourceBreadcrumb', () => {
|
|
80
74
|
hierarchy={[
|
81
75
|
...hierarchy,
|
82
76
|
{
|
83
|
-
|
84
|
-
id: '4',
|
85
|
-
source: '1',
|
86
|
-
},
|
77
|
+
key: '4',
|
87
78
|
label: 'Item 4',
|
79
|
+
node: 'D',
|
88
80
|
},
|
89
81
|
{
|
90
|
-
|
91
|
-
id: '5',
|
92
|
-
source: '1',
|
93
|
-
},
|
82
|
+
key: '5',
|
94
83
|
label: 'Item 5',
|
84
|
+
node: 'E',
|
95
85
|
},
|
96
86
|
]}
|
97
87
|
onBreadcrumbSelect={() => {}}
|
@@ -12,7 +12,7 @@ export default {
|
|
12
12
|
const Template: StoryFn<typeof ResourceBreadcrumb> = ({ hierarchy }) => (
|
13
13
|
<ResourceBreadcrumb
|
14
14
|
hierarchy={hierarchy}
|
15
|
-
onBreadcrumbSelect={(
|
15
|
+
onBreadcrumbSelect={(node) => alert(`Node Select: ${node}`)}
|
16
16
|
onReturnToRoot={() => alert(`Return to Root`)}
|
17
17
|
/>
|
18
18
|
);
|
@@ -1,16 +1,15 @@
|
|
1
1
|
import React, { useState, useCallback, useEffect } from 'react';
|
2
2
|
|
3
3
|
import Icon, { IconOptions } from '../Icons/Icon';
|
4
|
+
import { Hierarchy } from '../types';
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
hierarchy: Array<Hierarchy>;
|
9
|
-
onBreadcrumbSelect: (node: NodeIdentifier) => void;
|
6
|
+
export interface ResourceBreadcrumbProps<T> {
|
7
|
+
hierarchy: Hierarchy<T>;
|
8
|
+
onBreadcrumbSelect: (node: T) => void;
|
10
9
|
onReturnToRoot: () => void;
|
11
10
|
}
|
12
11
|
|
13
|
-
const ResourceBreadcrumb = function ({ hierarchy, onBreadcrumbSelect, onReturnToRoot }: ResourceBreadcrumbProps) {
|
12
|
+
const ResourceBreadcrumb = function <T>({ hierarchy, onBreadcrumbSelect, onReturnToRoot }: ResourceBreadcrumbProps<T>) {
|
14
13
|
// Track expanded state
|
15
14
|
const [expandedHierarchy, setExpandedHierarchy] = useState(false);
|
16
15
|
|
@@ -37,14 +36,14 @@ const ResourceBreadcrumb = function ({ hierarchy, onBreadcrumbSelect, onReturnTo
|
|
37
36
|
<Icon icon={'root' as IconOptions} aria-label="Return to source list" className="" />
|
38
37
|
</button>
|
39
38
|
</li>
|
40
|
-
{hierarchy.map(({
|
39
|
+
{hierarchy.map(({ key, label, node }, index) => {
|
41
40
|
return (
|
42
41
|
<li
|
43
|
-
key={`${
|
42
|
+
key={`${key}`}
|
44
43
|
className="resource-breadcrumb__item max-md:hidden flex items-center mr-2 before:content-['/'] before:mr-2"
|
45
44
|
>
|
46
45
|
{index !== hierarchy.length - 1 && (
|
47
|
-
<button type="button" onClick={() => onBreadcrumbSelect(
|
46
|
+
<button type="button" onClick={() => onBreadcrumbSelect(node)}>
|
48
47
|
<div className={`resource-breadcrumb__label`} title={label}>
|
49
48
|
{label}
|
50
49
|
</div>
|
@@ -1,37 +1,27 @@
|
|
1
1
|
[
|
2
2
|
{
|
3
|
-
"
|
4
|
-
|
5
|
-
|
6
|
-
},
|
7
|
-
"label": "HandyHomes website"
|
3
|
+
"key": "1",
|
4
|
+
"label": "HandyHomes website",
|
5
|
+
"node": "A"
|
8
6
|
},
|
9
7
|
{
|
10
|
-
"
|
11
|
-
|
12
|
-
|
13
|
-
},
|
14
|
-
"label": "Section 1 very long name"
|
8
|
+
"key": "2",
|
9
|
+
"label": "Section 1 very long name",
|
10
|
+
"node": "B"
|
15
11
|
},
|
16
12
|
{
|
17
|
-
"
|
18
|
-
|
19
|
-
|
20
|
-
},
|
21
|
-
"label": "Page 1 very long name"
|
13
|
+
"key": "3",
|
14
|
+
"label": "Page 1 very long name",
|
15
|
+
"node": "C"
|
22
16
|
},
|
23
17
|
{
|
24
|
-
"
|
25
|
-
|
26
|
-
|
27
|
-
},
|
28
|
-
"label": "Page 71 very long name"
|
18
|
+
"key": "4",
|
19
|
+
"label": "Page 71 very long name",
|
20
|
+
"node": "D"
|
29
21
|
},
|
30
22
|
{
|
31
|
-
"
|
32
|
-
|
33
|
-
|
34
|
-
},
|
35
|
-
"label": "Pages very long name"
|
23
|
+
"key": "5",
|
24
|
+
"label": "Pages very long name",
|
25
|
+
"node": "E"
|
36
26
|
}
|
37
27
|
]
|
@@ -4,25 +4,23 @@ import { useOverlayTrigger } from 'react-aria';
|
|
4
4
|
import { OverlayTriggerState } from 'react-stately';
|
5
5
|
|
6
6
|
import Icon, { IconOptions } from '../Icons/Icon';
|
7
|
-
import { NodeIdentifier } from '../index';
|
8
7
|
import ModalOpeningButton from '../Modal/ModalOpeningButton';
|
9
8
|
|
10
|
-
interface ResourceItem {
|
11
|
-
|
12
|
-
|
13
|
-
selected: boolean;
|
9
|
+
interface ResourceItem<T> {
|
10
|
+
item: T;
|
11
|
+
selected?: boolean;
|
14
12
|
label: string;
|
15
13
|
type: string;
|
16
14
|
childCount: number;
|
17
15
|
previewModalState: OverlayTriggerState;
|
18
|
-
onSelect: (node:
|
19
|
-
onDrillDown: (node:
|
16
|
+
onSelect: (node: T, overlayProps: DOMAttributes<FocusableElement>) => void;
|
17
|
+
onDrillDown: (node: T) => void;
|
20
18
|
className: string;
|
21
19
|
allowedTypes?: string[] | undefined;
|
22
20
|
}
|
23
21
|
|
24
|
-
const ResourceItem = ({
|
25
|
-
|
22
|
+
const ResourceItem = <T,>({
|
23
|
+
item,
|
26
24
|
selected,
|
27
25
|
label,
|
28
26
|
type,
|
@@ -32,7 +30,7 @@ const ResourceItem = ({
|
|
32
30
|
onDrillDown,
|
33
31
|
className,
|
34
32
|
allowedTypes,
|
35
|
-
}: ResourceItem) => {
|
33
|
+
}: ResourceItem<T>) => {
|
36
34
|
const { triggerProps, overlayProps } = useOverlayTrigger({ type: 'dialog' }, previewModalState);
|
37
35
|
const isDisabled = allowedTypes !== undefined && !allowedTypes.includes(type);
|
38
36
|
const title = isDisabled ? "You can't select this item" : label;
|
@@ -43,7 +41,7 @@ const ResourceItem = ({
|
|
43
41
|
type="button"
|
44
42
|
{...triggerProps}
|
45
43
|
isDisabled={isDisabled}
|
46
|
-
onPress={() => onSelect(
|
44
|
+
onPress={() => onSelect(item, overlayProps)}
|
47
45
|
className={`
|
48
46
|
relative grow flex items-center p-4 rounded ${selected ? 'bg-blue-100 text-blue-400' : ''} ${
|
49
47
|
childCount > 0 ? 'mr-2' : ''
|
@@ -64,7 +62,7 @@ const ResourceItem = ({
|
|
64
62
|
<button
|
65
63
|
type="button"
|
66
64
|
aria-label={`Drill down to ${label} children`}
|
67
|
-
onClick={() => onDrillDown(
|
65
|
+
onClick={() => onDrillDown(item)}
|
68
66
|
className={`relative shrink-0 flex items-center p-4 rounded before:w-px before:h-[calc(100%-0.75rem)] before:bg-gray-200 before:absolute before:top-1.5 before:-left-1 hover:bg-gray-100 focus:bg-gray-100`}
|
69
67
|
>
|
70
68
|
<span className="ml-auto flex items-center">
|