@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
@@ -44,9 +44,9 @@ const ResourceBreadcrumb = function ({ hierarchy, onBreadcrumbSelect, onReturnTo
|
|
44
44
|
react_1.default.createElement("li", { className: "flex items-center mr-3" },
|
45
45
|
react_1.default.createElement("button", { type: "button", onClick: onReturnToRoot },
|
46
46
|
react_1.default.createElement(Icon_1.default, { icon: 'root', "aria-label": "Return to source list", className: "" }))),
|
47
|
-
hierarchy.map(({
|
48
|
-
return (react_1.default.createElement("li", { key: `${
|
49
|
-
index !== hierarchy.length - 1 && (react_1.default.createElement("button", { type: "button", onClick: () => onBreadcrumbSelect(
|
47
|
+
hierarchy.map(({ key, label, node }, index) => {
|
48
|
+
return (react_1.default.createElement("li", { key: `${key}`, className: "resource-breadcrumb__item max-md:hidden flex items-center mr-2 before:content-['/'] before:mr-2" },
|
49
|
+
index !== hierarchy.length - 1 && (react_1.default.createElement("button", { type: "button", onClick: () => onBreadcrumbSelect(node) },
|
50
50
|
react_1.default.createElement("div", { className: `resource-breadcrumb__label`, title: label }, label))),
|
51
51
|
index === hierarchy.length - 1 && (react_1.default.createElement("div", { className: `resource-breadcrumb__label md:font-semibold`, title: label }, label)),
|
52
52
|
hierarchy.length > 3 && index === 0 && (react_1.default.createElement("div", { className: "resource-breadcrumb__expander flex" },
|
@@ -1,19 +1,17 @@
|
|
1
1
|
/// <reference types="react" />
|
2
2
|
import { DOMAttributes, FocusableElement } from '@react-types/shared';
|
3
3
|
import { OverlayTriggerState } from 'react-stately';
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
id: NodeIdentifier;
|
8
|
-
selected: boolean;
|
4
|
+
interface ResourceItem<T> {
|
5
|
+
item: T;
|
6
|
+
selected?: boolean;
|
9
7
|
label: string;
|
10
8
|
type: string;
|
11
9
|
childCount: number;
|
12
10
|
previewModalState: OverlayTriggerState;
|
13
|
-
onSelect: (node:
|
14
|
-
onDrillDown: (node:
|
11
|
+
onSelect: (node: T, overlayProps: DOMAttributes<FocusableElement>) => void;
|
12
|
+
onDrillDown: (node: T) => void;
|
15
13
|
className: string;
|
16
14
|
allowedTypes?: string[] | undefined;
|
17
15
|
}
|
18
|
-
declare const ResourceItem: ({
|
16
|
+
declare const ResourceItem: <T>({ item, selected, label, type, childCount, previewModalState, onSelect, onDrillDown, className, allowedTypes, }: ResourceItem<T>) => JSX.Element;
|
19
17
|
export default ResourceItem;
|
@@ -7,18 +7,18 @@ const react_1 = __importDefault(require("react"));
|
|
7
7
|
const react_aria_1 = require("react-aria");
|
8
8
|
const Icon_1 = __importDefault(require("../Icons/Icon"));
|
9
9
|
const ModalOpeningButton_1 = __importDefault(require("../Modal/ModalOpeningButton"));
|
10
|
-
const ResourceItem = ({
|
10
|
+
const ResourceItem = ({ item, selected, label, type, childCount, previewModalState, onSelect, onDrillDown, className, allowedTypes, }) => {
|
11
11
|
const { triggerProps, overlayProps } = (0, react_aria_1.useOverlayTrigger)({ type: 'dialog' }, previewModalState);
|
12
12
|
const isDisabled = allowedTypes !== undefined && !allowedTypes.includes(type);
|
13
13
|
const title = isDisabled ? "You can't select this item" : label;
|
14
14
|
return (react_1.default.createElement("li", { className: `flex items-stretch p-1 bg-white border border-grey-200 ${className}` },
|
15
|
-
react_1.default.createElement(ModalOpeningButton_1.default, { type: "button", ...triggerProps, isDisabled: isDisabled, onPress: () => onSelect(
|
15
|
+
react_1.default.createElement(ModalOpeningButton_1.default, { type: "button", ...triggerProps, isDisabled: isDisabled, onPress: () => onSelect(item, overlayProps), className: `
|
16
16
|
relative grow flex items-center p-4 rounded ${selected ? 'bg-blue-100 text-blue-400' : ''} ${childCount > 0 ? 'mr-2' : ''} ${isDisabled ? 'font-normal text-gray-600 cursor-not-allowed' : 'hover:bg-gray-100 focus:bg-gray-100'}
|
17
17
|
`, title: title },
|
18
18
|
react_1.default.createElement(Icon_1.default, { icon: type, resourceSource: "matrix", "aria-label": type, className: `mr-4 shrink-0 ${isDisabled && 'opacity-40'}` }),
|
19
19
|
react_1.default.createElement("span", { className: "text-left break-all" }, label),
|
20
20
|
childCount <= 0 && react_1.default.createElement(Icon_1.default, { icon: 'arrow-right', className: "absolute right-5" })),
|
21
|
-
childCount > 0 && (react_1.default.createElement("button", { type: "button", "aria-label": `Drill down to ${label} children`, onClick: () => onDrillDown(
|
21
|
+
childCount > 0 && (react_1.default.createElement("button", { type: "button", "aria-label": `Drill down to ${label} children`, onClick: () => onDrillDown(item), 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` },
|
22
22
|
react_1.default.createElement("span", { className: "ml-auto flex items-center" },
|
23
23
|
react_1.default.createElement("span", { className: "truncate w-10 text-right", title: String(childCount) }, childCount),
|
24
24
|
react_1.default.createElement(Icon_1.default, { icon: 'arrow-right', className: "ml-1" }))))));
|
@@ -1,14 +1,15 @@
|
|
1
1
|
/// <reference types="react" />
|
2
2
|
import { OverlayTriggerState } from 'react-stately';
|
3
3
|
import { DOMAttributes, FocusableElement } from '@react-types/shared';
|
4
|
-
import {
|
4
|
+
import { Resource } from '../types';
|
5
5
|
export interface ResourceListProps {
|
6
6
|
resources: Array<Resource>;
|
7
|
+
selectedResource?: Resource | null;
|
7
8
|
previewModalState: OverlayTriggerState;
|
8
9
|
isLoading: boolean;
|
9
|
-
onResourceSelect: (
|
10
|
-
onResourceDrillDown: (
|
10
|
+
onResourceSelect: (resource: Resource, overlayProps: DOMAttributes<FocusableElement>) => void;
|
11
|
+
onResourceDrillDown: (resource: Resource) => void;
|
11
12
|
allowedTypes?: string[] | undefined;
|
12
13
|
}
|
13
|
-
declare const ResourceList: ({ resources, previewModalState, isLoading, onResourceSelect, onResourceDrillDown, allowedTypes, }: ResourceListProps) => JSX.Element;
|
14
|
+
declare const ResourceList: ({ resources, selectedResource, previewModalState, isLoading, onResourceSelect, onResourceDrillDown, allowedTypes, }: ResourceListProps) => JSX.Element;
|
14
15
|
export default ResourceList;
|
@@ -29,7 +29,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
const react_1 = __importStar(require("react"));
|
30
30
|
const ResourceItem_1 = __importDefault(require("../ResourceItem/ResourceItem"));
|
31
31
|
const SkeletonListItem_1 = require("../Skeleton/ListItem/SkeletonListItem");
|
32
|
-
const ResourceList = function ({ resources, previewModalState, isLoading, onResourceSelect, onResourceDrillDown, allowedTypes, }) {
|
32
|
+
const ResourceList = function ({ resources, selectedResource, previewModalState, isLoading, onResourceSelect, onResourceDrillDown, allowedTypes, }) {
|
33
33
|
const listRef = (0, react_1.useRef)(null);
|
34
34
|
// When resources change, because we are on a new page, reset focus to the list
|
35
35
|
(0, react_1.useEffect)(() => {
|
@@ -44,8 +44,8 @@ const ResourceList = function ({ resources, previewModalState, isLoading, onReso
|
|
44
44
|
return react_1.default.createElement(SkeletonListItem_1.SkeletonListItem, { key: index });
|
45
45
|
}))),
|
46
46
|
!isLoading &&
|
47
|
-
resources.map((
|
48
|
-
return (react_1.default.createElement(ResourceItem_1.default, { key:
|
47
|
+
resources.map((resource) => {
|
48
|
+
return (react_1.default.createElement(ResourceItem_1.default, { key: resource.id, item: resource, selected: resource.id == selectedResource?.id, label: resource.name, type: resource.type.code, childCount: resource.childCount, previewModalState: previewModalState, onSelect: onResourceSelect, onDrillDown: onResourceDrillDown, className: "border-b-0 first:mt-0 first:rounded-t-lg last:rounded-b-lg last:border-b", allowedTypes: allowedTypes }));
|
49
49
|
})));
|
50
50
|
};
|
51
51
|
exports.default = ResourceList;
|
@@ -1,15 +1,14 @@
|
|
1
1
|
/// <reference types="react" />
|
2
2
|
import { DOMAttributes, FocusableElement } from '@react-types/shared';
|
3
|
-
import {
|
3
|
+
import { Source, Resource, HydratedResourceReference } from '../types';
|
4
4
|
interface ResourcePickerContainerProps {
|
5
5
|
title: string;
|
6
6
|
titleAriaProps: DOMAttributes<FocusableElement>;
|
7
7
|
allowedTypes: string[] | undefined;
|
8
8
|
onRequestSources: () => Promise<Source[]>;
|
9
|
-
onRequestChildren(
|
10
|
-
|
11
|
-
onChange(resource: NodeIdentifier | null): void;
|
9
|
+
onRequestChildren(source: Source, resource: Resource | null): Promise<Resource[]>;
|
10
|
+
onChange(resource: HydratedResourceReference | null): void;
|
12
11
|
onClose: () => void;
|
13
12
|
}
|
14
|
-
declare function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onRequestSources, onRequestChildren,
|
13
|
+
declare function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onRequestSources, onRequestChildren, onChange, onClose, }: ResourcePickerContainerProps): JSX.Element;
|
15
14
|
export default ResourcePickerContainer;
|
@@ -33,113 +33,58 @@ const ResourceList_1 = __importDefault(require("../ResourceList/ResourceList"));
|
|
33
33
|
const ResourceBreadcrumb_1 = __importDefault(require("../ResourceBreadcrumb/ResourceBreadcrumb"));
|
34
34
|
const PreviewPanel_1 = __importDefault(require("../PreviewPanel/PreviewPanel"));
|
35
35
|
const SourceDropdown_1 = __importDefault(require("../SourceDropdown/SourceDropdown"));
|
36
|
-
|
36
|
+
const useResourcePath_1 = require("../Hooks/useResourcePath");
|
37
|
+
const useChildResources_1 = require("../Hooks/useChildResources");
|
38
|
+
const useSources_1 = require("../Hooks/useSources");
|
39
|
+
function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onRequestSources, onRequestChildren, onChange, onClose, }) {
|
37
40
|
const previewModalState = (0, react_stately_1.useOverlayTriggerState)({});
|
38
|
-
const [
|
39
|
-
const [isMainLoading, setIsMainLoading] = (0, react_1.useState)(false);
|
40
|
-
const [isSecondaryLoading, setIsSecondaryLoading] = (0, react_1.useState)(false);
|
41
|
-
const [currentNode, setCurrentNode] = (0, react_1.useState)(null);
|
42
|
-
const [selectedId, setSelectedId] = (0, react_1.useState)(null);
|
41
|
+
const [selectedResource, setSelectedResource] = (0, react_1.useState)(null);
|
43
42
|
const [previewModalOverlayProps, setPreviewModalOverlayProps] = (0, react_1.useState)({});
|
44
|
-
const
|
45
|
-
const
|
46
|
-
const
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
hierarchy.forEach((hNode) => {
|
56
|
-
if (reachedNode === false) {
|
57
|
-
newHierarchy.push(hNode);
|
58
|
-
if (hNode.id === node) {
|
59
|
-
reachedNode = true;
|
60
|
-
}
|
61
|
-
}
|
62
|
-
});
|
63
|
-
}
|
64
|
-
else {
|
65
|
-
let label = resources.find((resource) => resource.id === node)?.label || '';
|
66
|
-
// Might be a source
|
67
|
-
if (!label) {
|
68
|
-
const source = sources.find((source) => source.id === node.source);
|
69
|
-
if (source) {
|
70
|
-
label = source.nodes.find((resource) => resource.id === node)?.label || '';
|
71
|
-
}
|
72
|
-
}
|
73
|
-
// If we are jumping to a complete other spot and the container knows it, it can request a complete reset
|
74
|
-
if (!resetHierarchy) {
|
75
|
-
newHierarchy = hierarchy.slice();
|
76
|
-
}
|
77
|
-
newHierarchy.push({
|
78
|
-
id: node,
|
79
|
-
label,
|
80
|
-
});
|
81
|
-
}
|
82
|
-
setHierarchy(newHierarchy);
|
83
|
-
};
|
84
|
-
const handleResourceSelected = (0, react_1.useCallback)((node, overlayProps) => {
|
43
|
+
const { source, currentResource, hierarchy, setSource, push, popUntil } = (0, useResourcePath_1.useResourcePath)();
|
44
|
+
const { isLoading: isSourceLoading, sources } = (0, useSources_1.useSources)({ onRequestSources });
|
45
|
+
const { isLoading: isResourcesLoading, resources } = (0, useChildResources_1.useChildResources)({
|
46
|
+
source,
|
47
|
+
currentResource,
|
48
|
+
onRequestChildren,
|
49
|
+
});
|
50
|
+
const handleResourceDrillDown = (0, react_1.useCallback)((resource) => {
|
51
|
+
push(resource);
|
52
|
+
}, [push]);
|
53
|
+
const handleResourceSelected = (0, react_1.useCallback)((resource, overlayProps) => {
|
85
54
|
setPreviewModalOverlayProps(overlayProps);
|
86
|
-
|
87
|
-
setSelectedId(node);
|
88
|
-
setSelectedNodeDetails(null);
|
89
|
-
onRequestResource(node).then((detail) => {
|
90
|
-
setSelectedNodeDetails(detail);
|
91
|
-
setIsSecondaryLoading(false);
|
92
|
-
});
|
55
|
+
setSelectedResource(resource);
|
93
56
|
}, []);
|
94
|
-
const
|
95
|
-
|
96
|
-
|
97
|
-
adjustHierarchy(node, resetHierarchy || false);
|
98
|
-
setSelectedId(null);
|
99
|
-
setSelectedNodeDetails(null);
|
100
|
-
setResources([]);
|
101
|
-
onRequestChildren(node).then((resources) => {
|
102
|
-
setResources(resources);
|
103
|
-
setIsMainLoading(false);
|
104
|
-
});
|
105
|
-
}, [resources, sources]);
|
57
|
+
const handleSourceDrilldown = (0, react_1.useCallback)((source) => {
|
58
|
+
setSource(source);
|
59
|
+
}, [setSource]);
|
106
60
|
const handleReturnToRoot = (0, react_1.useCallback)(() => {
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
setSelectedNodeDetails(null);
|
112
|
-
}, []);
|
113
|
-
const handleDetailSelect = (0, react_1.useCallback)((node) => {
|
114
|
-
onChange(node);
|
61
|
+
setSource(null);
|
62
|
+
}, [setSource]);
|
63
|
+
const handleDetailSelect = (0, react_1.useCallback)((resource) => {
|
64
|
+
onChange({ resource, source: source?.source });
|
115
65
|
onClose();
|
116
|
-
}, []);
|
66
|
+
}, [source]);
|
117
67
|
const handleDetailClose = (0, react_1.useCallback)(() => {
|
118
|
-
|
68
|
+
setSelectedResource(null);
|
119
69
|
}, []);
|
120
|
-
//
|
70
|
+
// When the active node changes clear the selected resource
|
121
71
|
(0, react_1.useEffect)(() => {
|
122
|
-
|
123
|
-
|
124
|
-
setSources(sources);
|
125
|
-
setIsSourceLoading(false);
|
126
|
-
});
|
127
|
-
}, []);
|
72
|
+
setSelectedResource(null);
|
73
|
+
}, [hierarchy]);
|
128
74
|
return (react_1.default.createElement("div", { className: "relative flex flex-col h-full" },
|
129
75
|
react_1.default.createElement("div", { className: "flex items-center p-6" },
|
130
76
|
react_1.default.createElement("h2", { ...titleAriaProps, className: "text-xl leading-6 text-gray-800 font-semibold mr-6" }, title),
|
131
77
|
react_1.default.createElement("div", { className: "px-3 border-l border-grey-300 w-300px" },
|
132
|
-
react_1.default.createElement(SourceDropdown_1.default, { sources: sources,
|
78
|
+
react_1.default.createElement(SourceDropdown_1.default, { sources: sources, selectedSource: source, isLoading: isSourceLoading, onSourceSelect: handleSourceDrilldown, onRootSelect: handleReturnToRoot })),
|
133
79
|
react_1.default.createElement("button", { type: "button", "aria-label": `Close ${title} dialog`, onClick: onClose, className: "absolute top-2 right-2 p-2.5 rounded hover:bg-blue-100 focus:bg-blue-100" },
|
134
80
|
react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
|
135
81
|
react_1.default.createElement("path", { d: "M13.3 0.710017C13.1131 0.522765 12.8595 0.417532 12.595 0.417532C12.3305 0.417532 12.0768 0.522765 11.89 0.710017L6.99997 5.59002L2.10997 0.700017C1.92314 0.512765 1.66949 0.407532 1.40497 0.407532C1.14045 0.407532 0.886802 0.512765 0.699971 0.700017C0.309971 1.09002 0.309971 1.72002 0.699971 2.11002L5.58997 7.00002L0.699971 11.89C0.309971 12.28 0.309971 12.91 0.699971 13.3C1.08997 13.69 1.71997 13.69 2.10997 13.3L6.99997 8.41002L11.89 13.3C12.28 13.69 12.91 13.69 13.3 13.3C13.69 12.91 13.69 12.28 13.3 11.89L8.40997 7.00002L13.3 2.11002C13.68 1.73002 13.68 1.09002 13.3 0.710017Z", fill: "currentColor" })))),
|
136
82
|
react_1.default.createElement("div", { className: "flex border-t border-grey-300 h-[calc(100%-92px)]" },
|
137
83
|
react_1.default.createElement("div", { className: "overflow-y-scroll flex-1 grow-[3] border-r border-gray-300" },
|
138
84
|
react_1.default.createElement("h3", { className: "sr-only" }, "Resource List"),
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
react_1.default.createElement(PreviewPanel_1.default, { node: selectedId, resourceDetail: selectedNodeDetails, modalState: previewModalState, isLoading: isSecondaryLoading, previewModalOverlayProps: previewModalOverlayProps, allowedTypes: allowedTypes, onSelect: handleDetailSelect, onClose: handleDetailClose }))));
|
85
|
+
hierarchy.length > 0 && (react_1.default.createElement(ResourceBreadcrumb_1.default, { hierarchy: hierarchy, onBreadcrumbSelect: popUntil, onReturnToRoot: handleReturnToRoot })),
|
86
|
+
!source && (react_1.default.createElement(SourceList_1.default, { sources: sources, previewModalState: previewModalState, isLoading: isSourceLoading, onSourceSelect: handleSourceDrilldown, onSourceDrillDown: handleSourceDrilldown, allowedTypes: allowedTypes })),
|
87
|
+
source && (react_1.default.createElement(ResourceList_1.default, { previewModalState: previewModalState, resources: resources, selectedResource: selectedResource, isLoading: isResourcesLoading, onResourceSelect: handleResourceSelected, onResourceDrillDown: handleResourceDrillDown, allowedTypes: allowedTypes }))),
|
88
|
+
react_1.default.createElement(PreviewPanel_1.default, { resource: selectedResource, modalState: previewModalState, previewModalOverlayProps: previewModalOverlayProps, allowedTypes: allowedTypes, onSelect: handleDetailSelect, onClose: handleDetailClose }))));
|
144
89
|
}
|
145
90
|
exports.default = ResourcePickerContainer;
|
@@ -1,9 +1,9 @@
|
|
1
1
|
/// <reference types="react" />
|
2
|
-
import type { Source,
|
3
|
-
export default function SourceDropdown({ sources,
|
4
|
-
sources:
|
5
|
-
|
2
|
+
import type { Source, ScopedSource } from '../types';
|
3
|
+
export default function SourceDropdown({ sources, selectedSource, isLoading, onRootSelect, onSourceSelect, }: {
|
4
|
+
sources: Source[];
|
5
|
+
selectedSource: ScopedSource | null;
|
6
6
|
isLoading: boolean;
|
7
7
|
onRootSelect: () => void;
|
8
|
-
onSourceSelect: (
|
8
|
+
onSourceSelect: (source: ScopedSource) => void;
|
9
9
|
}): JSX.Element;
|
@@ -31,7 +31,9 @@ const interactions_1 = require("@react-aria/interactions");
|
|
31
31
|
const Spinner_1 = __importDefault(require("../Spinner/Spinner"));
|
32
32
|
const Icon_1 = __importDefault(require("../Icons/Icon"));
|
33
33
|
const uuid_1 = __importDefault(require("../uuid"));
|
34
|
-
|
34
|
+
const useCategorisedSources_1 = require("../Hooks/useCategorisedSources");
|
35
|
+
function SourceDropdown({ sources, selectedSource, isLoading, onRootSelect, onSourceSelect, }) {
|
36
|
+
const categorisedSources = (0, useCategorisedSources_1.useCategorisedSources)(sources);
|
35
37
|
const [uniqueId] = (0, react_1.useState)((0, uuid_1.default)());
|
36
38
|
const buttonRef = (0, react_1.useRef)(null);
|
37
39
|
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
@@ -50,34 +52,23 @@ function SourceDropdown({ sources, currentSource, isLoading, onRootSelect, onSou
|
|
50
52
|
}
|
51
53
|
},
|
52
54
|
});
|
53
|
-
const handleSourceClick = (
|
55
|
+
const handleSourceClick = (source) => {
|
54
56
|
setIsOpen(false);
|
55
57
|
buttonRef.current?.focus();
|
56
|
-
onSourceSelect(
|
58
|
+
onSourceSelect(source);
|
57
59
|
};
|
58
60
|
const handleRootSelect = () => {
|
59
61
|
setIsOpen(false);
|
60
62
|
buttonRef.current?.focus();
|
61
63
|
onRootSelect();
|
62
64
|
};
|
63
|
-
let currentResource = undefined;
|
64
|
-
for (let i = 0; i < sources.length; i++) {
|
65
|
-
const source = sources[i];
|
66
|
-
if (currentSource?.source === source.id) {
|
67
|
-
currentResource = source.nodes.find((node) => {
|
68
|
-
if (node.id.id === currentSource?.id) {
|
69
|
-
return node;
|
70
|
-
}
|
71
|
-
});
|
72
|
-
}
|
73
|
-
}
|
74
65
|
return (react_1.default.createElement("div", { ...focusWithinProps, ...keyboardProps, className: "relative w-72 border border-2 rounded border-gray-300" },
|
75
66
|
react_1.default.createElement("button", { ref: buttonRef, type: "button", "aria-label": "Source quick select", "aria-expanded": isOpen, "aria-controls": `${uniqueId}-button-menu`, onClick: () => setIsOpen(!isOpen), className: "relative flex items-center text-sm font-semibold p-2 w-full" },
|
76
|
-
|
67
|
+
selectedSource && (react_1.default.createElement(react_1.default.Fragment, null,
|
77
68
|
react_1.default.createElement("span", { className: "sr-only" }, "current source "),
|
78
|
-
react_1.default.createElement(Icon_1.default, { icon:
|
79
|
-
react_1.default.createElement("div", { className: "truncate max-w-[200px]" },
|
80
|
-
!
|
69
|
+
react_1.default.createElement(Icon_1.default, { icon: selectedSource.resource?.type.code, resourceSource: "matrix", "aria-hidden": true, className: "mr-2.5" }),
|
70
|
+
react_1.default.createElement("div", { className: "truncate max-w-[200px]" }, selectedSource.resource?.name || selectedSource.source.name))),
|
71
|
+
!selectedSource && (react_1.default.createElement(react_1.default.Fragment, null,
|
81
72
|
react_1.default.createElement("span", { className: "sr-only" }, "view "),
|
82
73
|
react_1.default.createElement(Icon_1.default, { icon: 'root', "aria-hidden": true, className: "mr-2.5" }),
|
83
74
|
"All available sources")),
|
@@ -90,16 +81,17 @@ function SourceDropdown({ sources, currentSource, isLoading, onRootSelect, onSou
|
|
90
81
|
isLoading && (react_1.default.createElement("li", { className: "mt-6" },
|
91
82
|
react_1.default.createElement(Spinner_1.default, { size: "lg", label: "Loading sources" }))),
|
92
83
|
!isLoading &&
|
93
|
-
|
94
|
-
return (react_1.default.createElement("li", { key:
|
84
|
+
categorisedSources.map(({ key, label, sources }, index) => {
|
85
|
+
return (react_1.default.createElement("li", { key: key, className: `flex flex-col text-sm font-semibold text-grey-800 ${index > 0 ? 'mt-3' : ''}` },
|
95
86
|
react_1.default.createElement("div", { className: "relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0" },
|
96
|
-
react_1.default.createElement("span", { className: "z-10 bg-gray-100 px-2.5" },
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
react_1.default.createElement("
|
102
|
-
|
87
|
+
react_1.default.createElement("span", { className: "z-10 bg-gray-100 px-2.5" }, label)),
|
88
|
+
sources?.length > 0 && (react_1.default.createElement("ul", { "aria-label": `${label} nodes`, className: "flex flex-col mt-2" }, sources.map(({ source, resource }) => {
|
89
|
+
const isSelectedSource = source.id === selectedSource?.source.id && resource?.id === selectedSource?.resource?.id;
|
90
|
+
return (react_1.default.createElement("li", { key: `${source.id}-${resource?.id}`, className: "flex items-center bg-white border border-b-0 last:border-b border-grey-200 first:rounded-t last:rounded-b" },
|
91
|
+
react_1.default.createElement("button", { type: "button", onClick: () => handleSourceClick({ source, resource }), className: `relative grow flex items-center p-2.5 hover:bg-gray-100 focus:bg-gray-100 ${isSelectedSource ? 'bg-blue-100 text-blue-400' : ''}` },
|
92
|
+
react_1.default.createElement(Icon_1.default, { icon: resource?.type.code, resourceSource: "matrix", "aria-label": resource?.type.name, className: "shrink-0 mr-2.5" }),
|
93
|
+
react_1.default.createElement("span", { className: "text-left mr-7" }, resource?.name || source.name),
|
94
|
+
isSelectedSource && (react_1.default.createElement(Icon_1.default, { icon: 'selected', "aria-label": "selected", className: "absolute right-4" })))));
|
103
95
|
})))));
|
104
96
|
}))));
|
105
97
|
}
|
@@ -1,13 +1,13 @@
|
|
1
1
|
/// <reference types="react" />
|
2
2
|
import { OverlayTriggerState } from 'react-stately';
|
3
3
|
import { DOMAttributes, FocusableElement } from '@react-types/shared';
|
4
|
-
import {
|
4
|
+
import { Source, ScopedSource } from '../types';
|
5
5
|
export interface SourceListProps {
|
6
|
-
sources:
|
6
|
+
sources: Source[];
|
7
7
|
previewModalState: OverlayTriggerState;
|
8
8
|
isLoading: boolean;
|
9
|
-
onSourceSelect: (node:
|
10
|
-
onSourceDrillDown: (node:
|
9
|
+
onSourceSelect: (node: ScopedSource, overlayProps: DOMAttributes<FocusableElement>) => void;
|
10
|
+
onSourceDrillDown: (node: ScopedSource) => void;
|
11
11
|
allowedTypes?: string[] | undefined;
|
12
12
|
}
|
13
13
|
declare const SourceList: ({ sources, previewModalState, isLoading, onSourceSelect, onSourceDrillDown, allowedTypes, }: SourceListProps) => JSX.Element;
|
@@ -30,7 +30,9 @@ const react_1 = __importStar(require("react"));
|
|
30
30
|
const ResourceItem_1 = __importDefault(require("../ResourceItem/ResourceItem"));
|
31
31
|
const SkeletonList_1 = require("../Skeleton/List/SkeletonList");
|
32
32
|
const clsx_1 = __importDefault(require("clsx"));
|
33
|
+
const useCategorisedSources_1 = require("../Hooks/useCategorisedSources");
|
33
34
|
const SourceList = function ({ sources, previewModalState, isLoading, onSourceSelect, onSourceDrillDown, allowedTypes, }) {
|
35
|
+
const categorisedSources = (0, useCategorisedSources_1.useCategorisedSources)(sources);
|
34
36
|
const listRef = (0, react_1.useRef)(null);
|
35
37
|
(0, react_1.useEffect)(() => {
|
36
38
|
if (listRef.current) {
|
@@ -46,12 +48,12 @@ const SourceList = function ({ sources, previewModalState, isLoading, onSourceSe
|
|
46
48
|
react_1.default.createElement("li", null,
|
47
49
|
react_1.default.createElement(SkeletonList_1.SkeletonList, { itemCount: 3 })))),
|
48
50
|
!isLoading &&
|
49
|
-
|
50
|
-
return (react_1.default.createElement("li", { key:
|
51
|
+
categorisedSources.map(({ key, label, sources }, index) => {
|
52
|
+
return (react_1.default.createElement("li", { key: key, className: `flex flex-col text-sm font-semibold text-grey-800 ${index > 0 ? 'mt-3' : ''}` },
|
51
53
|
react_1.default.createElement("div", { className: "relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0" },
|
52
|
-
react_1.default.createElement("span", { className: "z-10 bg-gray-100 px-2.5" },
|
53
|
-
|
54
|
-
return (react_1.default.createElement(ResourceItem_1.default, { key: `${
|
54
|
+
react_1.default.createElement("span", { className: "z-10 bg-gray-100 px-2.5" }, label)),
|
55
|
+
sources.length > 0 && (react_1.default.createElement("ul", { "aria-label": `${label} nodes`, className: "flex flex-col" }, sources.map(({ source, resource }) => {
|
56
|
+
return (react_1.default.createElement(ResourceItem_1.default, { key: `${source.id}-${resource?.id}`, item: { source, resource }, label: resource?.name || source.name, type: resource?.type.code || 'folder', childCount: resource?.childCount || 0, previewModalState: previewModalState, onSelect: onSourceSelect, onDrillDown: onSourceDrillDown, className: "mt-3 rounded-lg", allowedTypes: allowedTypes }));
|
55
57
|
})))));
|
56
58
|
})));
|
57
59
|
};
|
package/lib/index.css
CHANGED
@@ -354,6 +354,9 @@
|
|
354
354
|
white-space: nowrap;
|
355
355
|
border-width: 0;
|
356
356
|
}
|
357
|
+
.squiz-rb-scope .visible {
|
358
|
+
visibility: visible;
|
359
|
+
}
|
357
360
|
.squiz-rb-scope .collapse {
|
358
361
|
visibility: collapse;
|
359
362
|
}
|
@@ -405,6 +408,9 @@
|
|
405
408
|
.squiz-rb-scope .z-50 {
|
406
409
|
z-index: 50;
|
407
410
|
}
|
411
|
+
.squiz-rb-scope .z-\[9998\] {
|
412
|
+
z-index: 9998;
|
413
|
+
}
|
408
414
|
.squiz-rb-scope .z-\[9999\] {
|
409
415
|
z-index: 9999;
|
410
416
|
}
|
package/lib/index.d.ts
CHANGED
@@ -1,37 +1,14 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
};
|
6
|
-
export type ResourceDetail = {
|
7
|
-
type: string;
|
8
|
-
name: string;
|
9
|
-
properties: Map<string, any>;
|
10
|
-
};
|
11
|
-
export type Resource = {
|
12
|
-
id: NodeIdentifier;
|
13
|
-
type: string;
|
14
|
-
selected: boolean;
|
15
|
-
label: string;
|
16
|
-
childCount: number;
|
17
|
-
};
|
18
|
-
export type Source = {
|
19
|
-
id: string;
|
20
|
-
name: string;
|
21
|
-
nodes: Array<Resource>;
|
22
|
-
};
|
23
|
-
export type Hierarchy = {
|
24
|
-
id: NodeIdentifier;
|
25
|
-
label: string;
|
26
|
-
};
|
27
|
-
export default function ComponentEditorContentBrowser({ showButtonLabel, buttonLabel, buttonIcon, modalTitle, allowedTypes, onRequestSources, onRequestChildren, onRequestResource, onChange, }: {
|
2
|
+
import { HydratedResourceReference, Resource, ResourceReference, Source } from './types';
|
3
|
+
export type { HydratedResourceReference, Resource, ResourceReference, Source };
|
4
|
+
export default function ComponentEditorContentBrowser({ showButtonLabel, buttonLabel, buttonIcon, modalTitle, allowedTypes, onRequestSources, onRequestChildren, onChange, }: {
|
28
5
|
showButtonLabel?: boolean;
|
29
6
|
buttonLabel: string;
|
30
7
|
buttonIcon: React.ReactNode;
|
31
8
|
modalTitle: string;
|
32
9
|
allowedTypes: string[] | undefined;
|
33
10
|
onRequestSources: () => Promise<Source[]>;
|
34
|
-
onRequestChildren(
|
35
|
-
onRequestResource(
|
36
|
-
onChange(resource:
|
11
|
+
onRequestChildren(source: Source, resource: Resource | null): Promise<Resource[]>;
|
12
|
+
onRequestResource(reference: ResourceReference): Promise<Resource | null>;
|
13
|
+
onChange(resource: HydratedResourceReference | null): void;
|
37
14
|
}): JSX.Element;
|
package/lib/index.js
CHANGED
@@ -4,12 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
6
|
const react_1 = __importDefault(require("react"));
|
7
|
-
// import { ResourceBrowserInternalContext } from './InternalContext/InternalContext';
|
8
7
|
const ModalTrigger_1 = __importDefault(require("./Modal/ModalTrigger"));
|
9
8
|
const ResourcePickerContainer_1 = __importDefault(require("./ResourcePickerContainer/ResourcePickerContainer"));
|
10
|
-
function ComponentEditorContentBrowser({ showButtonLabel, buttonLabel, buttonIcon, modalTitle, allowedTypes, onRequestSources, onRequestChildren,
|
9
|
+
function ComponentEditorContentBrowser({ showButtonLabel, buttonLabel, buttonIcon, modalTitle, allowedTypes, onRequestSources, onRequestChildren, onChange, }) {
|
11
10
|
const showLabel = showButtonLabel || false;
|
12
11
|
return (react_1.default.createElement("div", { className: "squiz-rb-scope" },
|
13
|
-
react_1.default.createElement(ModalTrigger_1.default, { showLabel: showLabel, label: buttonLabel, icon: buttonIcon }, (onClose, titleProps) => (react_1.default.createElement(ResourcePickerContainer_1.default, { title: modalTitle, titleAriaProps: titleProps, allowedTypes: allowedTypes, onClose: onClose, onRequestSources: onRequestSources, onRequestChildren: onRequestChildren,
|
12
|
+
react_1.default.createElement(ModalTrigger_1.default, { showLabel: showLabel, label: buttonLabel, icon: buttonIcon }, (onClose, titleProps) => (react_1.default.createElement(ResourcePickerContainer_1.default, { title: modalTitle, titleAriaProps: titleProps, allowedTypes: allowedTypes, onClose: onClose, onRequestSources: onRequestSources, onRequestChildren: onRequestChildren, onChange: onChange })))));
|
14
13
|
}
|
15
14
|
exports.default = ComponentEditorContentBrowser;
|
package/lib/uuid.js
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
4
|
-
// @ts-nocheck
|
5
3
|
function uuid() {
|
6
|
-
return
|
4
|
+
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => (Number(c) ^ (window.crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (Number(c) / 4)))).toString(16));
|
7
5
|
}
|
8
6
|
exports.default = uuid;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/resource-browser",
|
3
|
-
"version": "1.32.1-alpha.
|
3
|
+
"version": "1.32.1-alpha.16",
|
4
4
|
"main": "lib/index.js",
|
5
5
|
"types": "lib/index.d.ts",
|
6
6
|
"scripts": {
|
@@ -14,6 +14,7 @@
|
|
14
14
|
"clean": "rimraf ./lib"
|
15
15
|
},
|
16
16
|
"dependencies": {
|
17
|
+
"@squiz/dx-json-schema-lib": "1.32.1-alpha.16",
|
17
18
|
"react-aria": "3.23.1",
|
18
19
|
"react-responsive": "9.0.2",
|
19
20
|
"react-stately": "3.21.0"
|
@@ -70,5 +71,5 @@
|
|
70
71
|
"volta": {
|
71
72
|
"node": "18.15.0"
|
72
73
|
},
|
73
|
-
"gitHead": "
|
74
|
+
"gitHead": "09c056ac1b2e99db11146b4ecb545b10063db835"
|
74
75
|
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
2
|
+
import { useCategorisedSources } from './useCategorisedSources';
|
3
|
+
import { mockResource, mockSource } from '../__mocks__/MockModels';
|
4
|
+
|
5
|
+
describe('useCategorisedSources', () => {
|
6
|
+
it('Should return a list of scoped categories with non-restricted sources in their own category', () => {
|
7
|
+
const resources = [mockResource({ name: 'Images' }), mockResource({ name: 'Audio files' })];
|
8
|
+
const sources = [
|
9
|
+
mockSource({
|
10
|
+
id: '1',
|
11
|
+
name: 'Matrix source',
|
12
|
+
nodes: resources,
|
13
|
+
}),
|
14
|
+
mockSource({
|
15
|
+
id: '2',
|
16
|
+
name: 'Unrestricted source',
|
17
|
+
}),
|
18
|
+
];
|
19
|
+
const {
|
20
|
+
result: { current: result },
|
21
|
+
} = renderHook(() => useCategorisedSources(sources));
|
22
|
+
|
23
|
+
expect(result).toEqual([
|
24
|
+
{
|
25
|
+
key: '1',
|
26
|
+
label: 'Matrix source',
|
27
|
+
sources: [
|
28
|
+
{ resource: resources[0], source: sources[0] },
|
29
|
+
{ resource: resources[1], source: sources[0] },
|
30
|
+
],
|
31
|
+
},
|
32
|
+
{
|
33
|
+
key: 'other',
|
34
|
+
label: 'Other systems',
|
35
|
+
sources: [{ resource: null, source: sources[1] }],
|
36
|
+
},
|
37
|
+
]);
|
38
|
+
});
|
39
|
+
});
|