@squiz/resource-browser 1.32.1-alpha.15 → 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
@@ -0,0 +1,46 @@
|
|
1
|
+
import { ScopedSource, Source } from '../types';
|
2
|
+
import { useMemo } from 'react';
|
3
|
+
|
4
|
+
export type CategorisedSource = {
|
5
|
+
key: string;
|
6
|
+
label: string;
|
7
|
+
sources: ScopedSource[];
|
8
|
+
};
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Groups a list of source into scoped/un-scoped for rendering in the source list.
|
12
|
+
*
|
13
|
+
* @param {Source[]} sources
|
14
|
+
*
|
15
|
+
* @return {SourceList}
|
16
|
+
*/
|
17
|
+
export const useCategorisedSources = (sources: Source[]): CategorisedSource[] => {
|
18
|
+
return useMemo(() => {
|
19
|
+
const categorised: CategorisedSource[] = [];
|
20
|
+
const uncategorised: ScopedSource[] = [];
|
21
|
+
|
22
|
+
sources.forEach((source) => {
|
23
|
+
if (source.nodes.length > 0) {
|
24
|
+
const category: CategorisedSource = { key: source.id, label: source.name, sources: [] };
|
25
|
+
|
26
|
+
source.nodes.forEach((resource) => {
|
27
|
+
category.sources.push({ source, resource });
|
28
|
+
});
|
29
|
+
|
30
|
+
categorised.push(category);
|
31
|
+
} else {
|
32
|
+
uncategorised.push({ source, resource: null });
|
33
|
+
}
|
34
|
+
});
|
35
|
+
|
36
|
+
if (uncategorised.length > 0) {
|
37
|
+
categorised.push({
|
38
|
+
key: 'other',
|
39
|
+
label: 'Other systems',
|
40
|
+
sources: uncategorised,
|
41
|
+
});
|
42
|
+
}
|
43
|
+
|
44
|
+
return categorised;
|
45
|
+
}, [sources]);
|
46
|
+
};
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
2
|
+
import { mockResource, mockScopedSource } from '../__mocks__/MockModels';
|
3
|
+
import { useChildResources } from './useChildResources';
|
4
|
+
|
5
|
+
describe('useChildResources', () => {
|
6
|
+
it('Should trigger and return the loaded child resources', async () => {
|
7
|
+
const source = mockScopedSource();
|
8
|
+
const currentResource = mockResource({ name: 'Current resource' });
|
9
|
+
const children = [mockResource({ name: 'Child 1' })];
|
10
|
+
const onRequestChildren = jest.fn().mockResolvedValue(children);
|
11
|
+
const { result } = renderHook(() =>
|
12
|
+
useChildResources({
|
13
|
+
source,
|
14
|
+
currentResource,
|
15
|
+
onRequestChildren,
|
16
|
+
}),
|
17
|
+
);
|
18
|
+
|
19
|
+
expect(result.current.isLoading).toBe(true);
|
20
|
+
expect(result.current.error).toBe(null);
|
21
|
+
expect(result.current.resources).toEqual([]);
|
22
|
+
|
23
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
24
|
+
|
25
|
+
expect(result.current.isLoading).toBe(false);
|
26
|
+
expect(result.current.error).toBe(null);
|
27
|
+
expect(result.current.resources).toBe(children);
|
28
|
+
});
|
29
|
+
|
30
|
+
it('Should return the error if loading resources fails', async () => {
|
31
|
+
const source = mockScopedSource();
|
32
|
+
const currentResource = mockResource({ name: 'Current resource' });
|
33
|
+
const error = new Error('Loading the resources failed.');
|
34
|
+
const onRequestChildren = jest.fn().mockRejectedValue(error);
|
35
|
+
const { result } = renderHook(() =>
|
36
|
+
useChildResources({
|
37
|
+
source,
|
38
|
+
currentResource,
|
39
|
+
onRequestChildren,
|
40
|
+
}),
|
41
|
+
);
|
42
|
+
|
43
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
44
|
+
|
45
|
+
expect(result.current.isLoading).toBe(false);
|
46
|
+
expect(result.current.error).toBe(error);
|
47
|
+
expect(result.current.resources).toEqual([]);
|
48
|
+
});
|
49
|
+
});
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { useEffect, useState } from 'react';
|
2
|
+
import { Resource, ScopedSource, Source } from '../types';
|
3
|
+
|
4
|
+
type UseChildResourcesProps = {
|
5
|
+
source: ScopedSource | null;
|
6
|
+
currentResource: Resource | null;
|
7
|
+
onRequestChildren: (source: Source, resource: Resource | null) => Promise<Resource[]>;
|
8
|
+
};
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Triggers a reload of the child resources when the source or current resource change.
|
12
|
+
*
|
13
|
+
* @param {ScopedSource|null} source
|
14
|
+
* @param {Resource|null} currentResource
|
15
|
+
* @param {Function} onRequestChildren
|
16
|
+
*/
|
17
|
+
export const useChildResources = ({ source, currentResource, onRequestChildren }: UseChildResourcesProps) => {
|
18
|
+
const [isLoading, setIsLoading] = useState(false);
|
19
|
+
const [error, setError] = useState<Error | null>(null);
|
20
|
+
const [resources, setResources] = useState<Resource[]>([]);
|
21
|
+
|
22
|
+
// trigger a reload of the resources when the source or the current resource changes.
|
23
|
+
useEffect(() => {
|
24
|
+
setError(null);
|
25
|
+
setResources([]);
|
26
|
+
|
27
|
+
if (source) {
|
28
|
+
setIsLoading(true);
|
29
|
+
|
30
|
+
onRequestChildren(source.source, currentResource)
|
31
|
+
.then((resources: Array<Resource>) => {
|
32
|
+
setResources(resources);
|
33
|
+
setIsLoading(false);
|
34
|
+
})
|
35
|
+
.catch((e) => {
|
36
|
+
setError(e);
|
37
|
+
setIsLoading(false);
|
38
|
+
});
|
39
|
+
}
|
40
|
+
}, [source, currentResource]);
|
41
|
+
|
42
|
+
return { isLoading, error, resources };
|
43
|
+
};
|
@@ -0,0 +1,124 @@
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
2
|
+
import { mockScopedSource, mockResource } from '../__mocks__/MockModels';
|
3
|
+
import { useResourcePath } from './useResourcePath';
|
4
|
+
import { Resource, Hierarchy, ScopedSource } from '../types';
|
5
|
+
|
6
|
+
describe('useResourcePath', () => {
|
7
|
+
it('Should return expected initial state', () => {
|
8
|
+
const {
|
9
|
+
result: { current: result },
|
10
|
+
} = renderHook(() => useResourcePath());
|
11
|
+
|
12
|
+
expect(result).toEqual({
|
13
|
+
source: null,
|
14
|
+
currentResource: null,
|
15
|
+
hierarchy: [],
|
16
|
+
setSource: expect.any(Function),
|
17
|
+
push: expect.any(Function),
|
18
|
+
popUntil: expect.any(Function),
|
19
|
+
});
|
20
|
+
});
|
21
|
+
|
22
|
+
it('setSource should update the source and clear the current hierarchy', async () => {
|
23
|
+
const { result } = renderHook(() => useResourcePath());
|
24
|
+
const source = mockScopedSource({
|
25
|
+
source: { id: '1', name: 'Test source' },
|
26
|
+
resource: { id: '2', name: 'Test resource' },
|
27
|
+
});
|
28
|
+
const resource = mockResource({ id: '3', name: 'Test resource 2' });
|
29
|
+
|
30
|
+
await act(() => result.current.setSource(source));
|
31
|
+
await act(() => result.current.push(resource));
|
32
|
+
|
33
|
+
// make sure the current resource/hierarchy is populated
|
34
|
+
expect(result.current.currentResource).toBe(resource);
|
35
|
+
expect(result.current.hierarchy).toHaveLength(2);
|
36
|
+
|
37
|
+
// set the source again
|
38
|
+
await act(() => result.current.setSource(source));
|
39
|
+
|
40
|
+
// make sure the current resource and hierarchy are reset
|
41
|
+
expect(result.current.source).toBe(source);
|
42
|
+
expect(result.current.currentResource).toBe(null);
|
43
|
+
expect(result.current.hierarchy).toEqual([{ key: 'source:1-resource:2', label: 'Test resource', node: source }]);
|
44
|
+
});
|
45
|
+
|
46
|
+
it('push should add the node to the hierarchy', async () => {
|
47
|
+
const { result } = renderHook(() => useResourcePath());
|
48
|
+
const source = mockScopedSource({ source: { id: '1' } });
|
49
|
+
const site = mockResource({ id: '1', type: { code: 'site', name: 'Site' }, name: 'My site' });
|
50
|
+
const page = mockResource({ id: '2', type: { code: 'page', name: 'Page' }, name: 'My page' });
|
51
|
+
|
52
|
+
await act(() => result.current.setSource(source));
|
53
|
+
await act(() => result.current.push(site));
|
54
|
+
await act(() => result.current.push(page));
|
55
|
+
|
56
|
+
expect(result.current.source).toBe(source);
|
57
|
+
expect(result.current.currentResource).toBe(page);
|
58
|
+
expect(result.current.hierarchy).toEqual([
|
59
|
+
{ key: 'source:1-resource:unscoped', label: source.source.name, node: source },
|
60
|
+
{ key: '1', label: site.name, node: site },
|
61
|
+
{ key: '2', label: page.name, node: page },
|
62
|
+
]);
|
63
|
+
});
|
64
|
+
|
65
|
+
it.each([
|
66
|
+
[
|
67
|
+
'popping to the last node should remove nothing',
|
68
|
+
mockResource({ id: '3' }),
|
69
|
+
[
|
70
|
+
{ key: 'source:1-resource:unscoped', label: 'Test source', node: mockScopedSource({ source: { id: '1' } }) },
|
71
|
+
{ key: '1', label: 'Resource 1', node: mockResource({ id: '1', name: 'Resource 1' }) },
|
72
|
+
{ key: '2', label: 'Resource 2', node: mockResource({ id: '2', name: 'Resource 2' }) },
|
73
|
+
{ key: '3', label: 'Resource 3', node: mockResource({ id: '3', name: 'Resource 3' }) },
|
74
|
+
],
|
75
|
+
],
|
76
|
+
[
|
77
|
+
'popping to a node not in the hierarchy should remove everything',
|
78
|
+
mockResource({ id: 'resource-not-in-hierarchy' }),
|
79
|
+
[],
|
80
|
+
],
|
81
|
+
[
|
82
|
+
'popping to the source should remove everything except the source',
|
83
|
+
mockScopedSource({ source: { id: '1' } }),
|
84
|
+
[{ key: 'source:1-resource:unscoped', label: 'Test source', node: mockScopedSource({ source: { id: '1' } }) }],
|
85
|
+
],
|
86
|
+
[
|
87
|
+
'popping to a resource node should remove the resources after it',
|
88
|
+
mockResource({ id: '2' }),
|
89
|
+
[
|
90
|
+
{ key: 'source:1-resource:unscoped', label: 'Test source', node: mockScopedSource({ source: { id: '1' } }) },
|
91
|
+
{ key: '1', label: 'Resource 1', node: mockResource({ id: '1', name: 'Resource 1' }) },
|
92
|
+
{ key: '2', label: 'Resource 2', node: mockResource({ id: '2', name: 'Resource 2' }) },
|
93
|
+
],
|
94
|
+
],
|
95
|
+
])(
|
96
|
+
'popUntil should remove from the hierarchy until the node being popped to is hit - %s',
|
97
|
+
async (
|
98
|
+
description: string,
|
99
|
+
node: ScopedSource | Resource,
|
100
|
+
expectedHierarchy: Hierarchy<ScopedSource | Resource>,
|
101
|
+
) => {
|
102
|
+
const { result } = renderHook(() => useResourcePath());
|
103
|
+
const source = mockScopedSource({ source: { id: '1' } });
|
104
|
+
const mockResources = [
|
105
|
+
mockResource({ id: '1', name: 'Resource 1' }),
|
106
|
+
mockResource({ id: '2', name: 'Resource 2' }),
|
107
|
+
mockResource({ id: '3', name: 'Resource 3' }),
|
108
|
+
];
|
109
|
+
|
110
|
+
await act(() => result.current.setSource(source));
|
111
|
+
await act(() => result.current.push(mockResources[0]));
|
112
|
+
await act(() => result.current.push(mockResources[1]));
|
113
|
+
await act(() => result.current.push(mockResources[2]));
|
114
|
+
|
115
|
+
// make sure hierarchy has expected length before popping
|
116
|
+
expect(result.current.source).toBe(source);
|
117
|
+
expect(result.current.hierarchy).toHaveLength(4);
|
118
|
+
|
119
|
+
await act(() => result.current.popUntil(node));
|
120
|
+
|
121
|
+
expect(result.current.hierarchy).toEqual(expectedHierarchy);
|
122
|
+
},
|
123
|
+
);
|
124
|
+
});
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
2
|
+
import { Hierarchy, ScopedSource, Resource } from '../types';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Retains the state relating to where in the resource tree we are.
|
6
|
+
*
|
7
|
+
* Including:
|
8
|
+
* * The source.
|
9
|
+
* * A full path from the source to the leaf resource being browser.
|
10
|
+
*/
|
11
|
+
export const useResourcePath = () => {
|
12
|
+
const [source, setSourceInternal] = useState<ScopedSource | null>(null);
|
13
|
+
const [resourceStack, setResourceStack] = useState<Array<Resource>>([]);
|
14
|
+
|
15
|
+
const setSource = useCallback((source: ScopedSource | null) => {
|
16
|
+
setSourceInternal(source);
|
17
|
+
setResourceStack([]);
|
18
|
+
}, []);
|
19
|
+
|
20
|
+
const push = useCallback(
|
21
|
+
(resource: Resource) => {
|
22
|
+
setResourceStack([...resourceStack, resource]);
|
23
|
+
},
|
24
|
+
[resourceStack],
|
25
|
+
);
|
26
|
+
|
27
|
+
const popUntil = useCallback(
|
28
|
+
(node: ScopedSource | Resource) => {
|
29
|
+
if ('source' in node) {
|
30
|
+
// source node selected, just switch to the source
|
31
|
+
setSource(node);
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
|
35
|
+
// resource node selected
|
36
|
+
const newResourceStack = [...resourceStack];
|
37
|
+
|
38
|
+
// pop until we reach the node that was selected in the breadcrumb
|
39
|
+
while (newResourceStack.length > 0 && newResourceStack[newResourceStack.length - 1].id !== node?.id) {
|
40
|
+
newResourceStack.pop();
|
41
|
+
}
|
42
|
+
|
43
|
+
if (newResourceStack.length === 0) {
|
44
|
+
// if we didn't find the node clear the source to push the user back to the root
|
45
|
+
setSource(null);
|
46
|
+
} else {
|
47
|
+
setResourceStack(newResourceStack);
|
48
|
+
}
|
49
|
+
},
|
50
|
+
[source, resourceStack],
|
51
|
+
);
|
52
|
+
|
53
|
+
const hierarchy = useMemo<Hierarchy<ScopedSource | Resource>>(() => {
|
54
|
+
if (!source) {
|
55
|
+
return [];
|
56
|
+
}
|
57
|
+
|
58
|
+
return [
|
59
|
+
{
|
60
|
+
key: `source:${source.source.id}-resource:${source.resource?.id || 'unscoped'}`,
|
61
|
+
label: source.resource?.name || source.source.name,
|
62
|
+
node: source,
|
63
|
+
},
|
64
|
+
...resourceStack.map((resource) => ({ key: resource.id, node: resource, label: resource.name })),
|
65
|
+
];
|
66
|
+
}, [source, resourceStack]);
|
67
|
+
|
68
|
+
return {
|
69
|
+
source,
|
70
|
+
currentResource: (resourceStack[resourceStack.length - 1] || null) as Resource | null,
|
71
|
+
hierarchy,
|
72
|
+
setSource,
|
73
|
+
push,
|
74
|
+
popUntil,
|
75
|
+
};
|
76
|
+
};
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
2
|
+
import { mockSource } from '../__mocks__/MockModels';
|
3
|
+
import { useSources } from './useSources';
|
4
|
+
|
5
|
+
describe('useSources', () => {
|
6
|
+
it('Should trigger and load the sources', async () => {
|
7
|
+
const sources = [mockSource()];
|
8
|
+
const onRequestSources = jest.fn().mockResolvedValue(sources);
|
9
|
+
const { result } = renderHook(() => useSources({ onRequestSources }));
|
10
|
+
|
11
|
+
expect(result.current.isLoading).toBe(true);
|
12
|
+
expect(result.current.error).toBe(null);
|
13
|
+
expect(result.current.sources).toEqual([]);
|
14
|
+
|
15
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
16
|
+
|
17
|
+
expect(result.current.isLoading).toBe(false);
|
18
|
+
expect(result.current.error).toBe(null);
|
19
|
+
expect(result.current.sources).toBe(sources);
|
20
|
+
});
|
21
|
+
|
22
|
+
it('Should return the error if loading resources fails', async () => {
|
23
|
+
const error = new Error('Loading the sources failed.');
|
24
|
+
const onRequestSources = jest.fn().mockRejectedValue(error);
|
25
|
+
const { result } = renderHook(() => useSources({ onRequestSources }));
|
26
|
+
|
27
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
28
|
+
|
29
|
+
expect(result.current.isLoading).toBe(false);
|
30
|
+
expect(result.current.error).toBe(error);
|
31
|
+
expect(result.current.sources).toEqual([]);
|
32
|
+
});
|
33
|
+
});
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
2
|
+
import { Source } from '../types';
|
3
|
+
|
4
|
+
type UseSourcesProps = {
|
5
|
+
onRequestSources: () => Promise<Source[]>;
|
6
|
+
};
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Loads and caches the source list when a component using the hook is mounted.
|
10
|
+
*
|
11
|
+
* @param {Function} onRequestSources
|
12
|
+
*/
|
13
|
+
export const useSources = ({ onRequestSources }: UseSourcesProps) => {
|
14
|
+
const [isLoading, setIsLoading] = useState(true);
|
15
|
+
const [error, setError] = useState<Error | null>(null);
|
16
|
+
const [sources, setSources] = useState<Source[]>([]);
|
17
|
+
const loadSources = useCallback(() => {
|
18
|
+
onRequestSources()
|
19
|
+
.then((sources) => {
|
20
|
+
setIsLoading(false);
|
21
|
+
setSources(sources);
|
22
|
+
})
|
23
|
+
.catch((error) => {
|
24
|
+
setIsLoading(false);
|
25
|
+
setError(error);
|
26
|
+
});
|
27
|
+
}, []);
|
28
|
+
|
29
|
+
// trigger a load of the sources when the component using the hook is initially rendered.
|
30
|
+
useEffect(loadSources, []);
|
31
|
+
|
32
|
+
return { isLoading, error, sources, reload: loadSources };
|
33
|
+
};
|
@@ -27,13 +27,13 @@ Root.args = {
|
|
27
27
|
|
28
28
|
export const Audio = Template.bind({});
|
29
29
|
Audio.args = {
|
30
|
-
icon: '
|
30
|
+
icon: 'audio_file',
|
31
31
|
resourceSource: 'matrix',
|
32
32
|
};
|
33
33
|
|
34
34
|
export const Excel = Template.bind({});
|
35
35
|
Excel.args = {
|
36
|
-
icon: '
|
36
|
+
icon: 'excel_doc',
|
37
37
|
resourceSource: 'matrix',
|
38
38
|
};
|
39
39
|
|
@@ -57,19 +57,19 @@ Image.args = {
|
|
57
57
|
|
58
58
|
export const Page = Template.bind({});
|
59
59
|
Page.args = {
|
60
|
-
icon: '
|
60
|
+
icon: 'page_standard',
|
61
61
|
resourceSource: 'matrix',
|
62
62
|
};
|
63
63
|
|
64
64
|
export const Pdf = Template.bind({});
|
65
65
|
Pdf.args = {
|
66
|
-
icon: '
|
66
|
+
icon: 'pdf_file',
|
67
67
|
resourceSource: 'matrix',
|
68
68
|
};
|
69
69
|
|
70
70
|
export const Powerpoint = Template.bind({});
|
71
71
|
Powerpoint.args = {
|
72
|
-
icon: '
|
72
|
+
icon: 'powerpoint_doc',
|
73
73
|
resourceSource: 'matrix',
|
74
74
|
};
|
75
75
|
|
@@ -81,13 +81,13 @@ Site.args = {
|
|
81
81
|
|
82
82
|
export const Video = Template.bind({});
|
83
83
|
Video.args = {
|
84
|
-
icon: '
|
84
|
+
icon: 'video_file',
|
85
85
|
resourceSource: 'matrix',
|
86
86
|
};
|
87
87
|
|
88
88
|
export const Word = Template.bind({});
|
89
89
|
Word.args = {
|
90
|
-
icon: '
|
90
|
+
icon: 'word_doc',
|
91
91
|
resourceSource: 'matrix',
|
92
92
|
};
|
93
93
|
|
package/src/Icons/Icon.tsx
CHANGED
@@ -9,9 +9,7 @@ export const iconSources = {
|
|
9
9
|
|
10
10
|
// The resource sources options
|
11
11
|
export type ResourceSources = keyof typeof iconSources;
|
12
|
-
|
13
|
-
// Get the list of possible icons from the icon sources
|
14
|
-
export type IconOptions = keyof (typeof iconSources)[ResourceSources];
|
12
|
+
export type IconOptions = string;
|
15
13
|
|
16
14
|
/**
|
17
15
|
* Renders an icon based on the resource source and the icon name
|
@@ -39,18 +37,15 @@ function Icon({
|
|
39
37
|
isDecorative?: boolean;
|
40
38
|
} & React.HTMLAttributes<HTMLElement> &
|
41
39
|
React.SVGAttributes<SVGElement>) {
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
{ isDecorative: boolean } & React.SVGProps<SVGSVGElement>
|
49
|
-
>;
|
40
|
+
const icons = (iconSources[resourceSource] as any) || null;
|
41
|
+
|
42
|
+
// If the resource source is the current source and the icon is in the current source map, render the icon
|
43
|
+
if (icons && icon && icon in icons) {
|
44
|
+
// Get the icon from the current source map
|
45
|
+
const Icon = icons[icon] as React.FunctionComponent<{ isDecorative: boolean } & React.SVGProps<SVGSVGElement>>;
|
50
46
|
|
51
|
-
|
52
|
-
|
53
|
-
}
|
47
|
+
// Render the icon
|
48
|
+
return <Icon isDecorative={isDecorative} {...props} />;
|
54
49
|
}
|
55
50
|
|
56
51
|
// If the icon is not defined, render the default icon
|
@@ -24,7 +24,7 @@ export default function Audio({ isDecorative, ...props }: { isDecorative: boolea
|
|
24
24
|
<circle cx="7.5" cy="16.5" fill="#73c0cb" r="2" stroke="#057e91" />
|
25
25
|
<path d="m16 7 1 .32v7.18c0 .2761-.2239.5-.5.5s-.5-.2239-.5-.5z" fill="#057e91" />
|
26
26
|
<circle cx="14.5" cy="14.5" fill="#73c0cb" r="2" stroke="#057e91" />
|
27
|
-
{!isDecorative && <title>audio icon</title>}
|
27
|
+
{!isDecorative && <title>audio file icon</title>}
|
28
28
|
</svg>
|
29
29
|
);
|
30
30
|
}
|
@@ -23,7 +23,7 @@ export default function Excel({ isDecorative, ...props }: { isDecorative: boolea
|
|
23
23
|
<rect y="8" width="12" height="12" rx="2" fill="#4FAD6F" />
|
24
24
|
<path d="M3.5 11.5L8.5 16.5" stroke="white" strokeLinecap="round" />
|
25
25
|
<path d="M8.5 11.5L3.5 16.5" stroke="white" strokeLinecap="round" />
|
26
|
-
{!isDecorative && <title>excel icon</title>}
|
26
|
+
{!isDecorative && <title>excel doc icon</title>}
|
27
27
|
</svg>
|
28
28
|
);
|
29
29
|
}
|
@@ -2,17 +2,17 @@ import { Audio, Excel, Folder, GenericFile, Image, Page, Pdf, Powerpoint, Site,
|
|
2
2
|
|
3
3
|
// Define our map of matrix types to icons
|
4
4
|
const MatrixResourceMap = {
|
5
|
-
|
6
|
-
|
5
|
+
audio_file: Audio,
|
6
|
+
excel_doc: Excel,
|
7
7
|
folder: Folder,
|
8
|
-
generic_file: GenericFile,
|
8
|
+
generic_file: GenericFile, // fallback when a type doesn't match.
|
9
9
|
image: Image,
|
10
10
|
page_standard: Page,
|
11
|
-
|
12
|
-
|
11
|
+
pdf_file: Pdf,
|
12
|
+
powerpoint_doc: Powerpoint,
|
13
13
|
site: Site,
|
14
|
-
|
15
|
-
|
14
|
+
video_file: Video,
|
15
|
+
word_doc: Word,
|
16
16
|
};
|
17
17
|
|
18
18
|
// Export our map
|
@@ -28,7 +28,7 @@ export default function Pdf({ isDecorative, ...props }: { isDecorative: boolean
|
|
28
28
|
<path d="m11.5 17.5v-3.8c0-.1105.0895-.2.2-.2h1.8" />
|
29
29
|
<path d="m11.5 15.5h1" />
|
30
30
|
</g>
|
31
|
-
{!isDecorative && <title>pdf icon</title>}
|
31
|
+
{!isDecorative && <title>pdf file icon</title>}
|
32
32
|
</svg>
|
33
33
|
);
|
34
34
|
}
|
@@ -28,7 +28,7 @@ export default function Powerpoint({
|
|
28
28
|
<path d="m3.5 11.5v5" />
|
29
29
|
<path d="m3.5 11.5h3.5c.33333 0 1.5 0 1.5 1.5s-1.16667 1.5-1.5 1.5h-3" />
|
30
30
|
</g>
|
31
|
-
{!isDecorative && <title>powerpoint icon</title>}
|
31
|
+
{!isDecorative && <title>powerpoint doc icon</title>}
|
32
32
|
</svg>
|
33
33
|
);
|
34
34
|
}
|
@@ -24,7 +24,7 @@ export default function Word({ isDecorative, ...props }: { isDecorative: boolean
|
|
24
24
|
<path d="m14.5 3v3.5c0 .55228.4477 1 1 1h3.5" stroke="#bcbcbc" />
|
25
25
|
<rect fill="#0774d2" height="12" rx="2" width="12" y="8" />
|
26
26
|
<path d="m2.5 11.5 1.5 5 2-5 2 5 1.5-5" stroke="#fff" strokeLinecap="round" strokeLinejoin="round" />
|
27
|
-
{!isDecorative && <title>word icon</title>}
|
27
|
+
{!isDecorative && <title>word doc icon</title>}
|
28
28
|
</svg>
|
29
29
|
);
|
30
30
|
}
|
package/src/Modal/Modal.tsx
CHANGED
@@ -44,7 +44,7 @@ function Modal({ isDismissable, state, overlayProps, children, ...props }: Modal
|
|
44
44
|
<div className="squiz-rb-scope">
|
45
45
|
<div
|
46
46
|
{...underlayProps}
|
47
|
-
className="h-full z-[
|
47
|
+
className="h-full z-[9998] fixed inset-0 before:z-40 before:fixed before:inset-0 before:bg-black before:bg-opacity-25"
|
48
48
|
>
|
49
49
|
<div {...modalProps} ref={ref} className="h-full flex items-center justify-center">
|
50
50
|
<ModalContent {...overlayProps}>{(titleProps) => children(titleProps)}</ModalContent>
|
@@ -77,7 +77,7 @@ function PreviewModal({ state, overlayProps, children, onClose, ...props }: Prev
|
|
77
77
|
ref={modalRef}
|
78
78
|
{...underlayProps}
|
79
79
|
{...keyboardProps}
|
80
|
-
className="fixed z-
|
80
|
+
className="fixed z-[9999] overflow-y-scroll bottom-0 w-full h-[50vh] bg-white border-t border-gray-300"
|
81
81
|
>
|
82
82
|
<div ref={overlayRef} {...modalProps} className="h-full">
|
83
83
|
<PreviewModalContent {...overlayProps} onClose={onClose}>
|