@squiz/resource-browser 3.2.3 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/lib/ResourceLauncher/ResourceLauncher.js +4 -1
- package/lib/index.css +13 -4
- package/lib/index.d.ts +2 -1
- package/lib/index.js +10 -2
- package/package.json +2 -2
- package/src/ResourceLauncher/ResourceLauncher.spec.tsx +14 -0
- package/src/ResourceLauncher/ResourceLauncher.stories.tsx +55 -0
- package/src/ResourceLauncher/ResourceLauncher.tsx +6 -0
- package/src/index.spec.tsx +32 -1
- package/src/index.stories.tsx +7 -0
- package/src/index.tsx +12 -3
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 3.3.1
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- c8240b6: Minor styling updates
|
8
|
+
- Updated dependencies [c8240b6]
|
9
|
+
- @squiz/resource-browser-ui-lib@1.2.1
|
10
|
+
|
11
|
+
## 3.3.0
|
12
|
+
|
13
|
+
### Minor Changes
|
14
|
+
|
15
|
+
- 9e6268d: Allow users to restrict plugin usage
|
16
|
+
|
3
17
|
## 3.2.3
|
4
18
|
|
5
19
|
### Patch Changes
|
@@ -4,6 +4,7 @@ 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
|
+
const sds_1 = require("@squiz/sds");
|
7
8
|
const types_1 = require("../types");
|
8
9
|
function ResourceLauncher({ sources, onSourceSelect }) {
|
9
10
|
return (react_1.default.createElement("div", { className: "overflow-y-auto w-screen max-w-[400px] min-h-[290px] flex-1 grow-[3] border-r border-gray-300 bg-gray-100 pl-6 pr-6 pb-6 pt-4" },
|
@@ -11,7 +12,9 @@ function ResourceLauncher({ sources, onSourceSelect }) {
|
|
11
12
|
const SourceLauncher = source.plugin.renderResourceLauncher();
|
12
13
|
return (react_1.default.createElement("li", { key: index, className: "flex items-stretch relative" },
|
13
14
|
react_1.default.createElement("div", { className: `squiz-rb-plugin squiz-rb-plugin--${source.plugin?.type} w-full` },
|
14
|
-
react_1.default.createElement(SourceLauncher, { source: source, onSearch: (query) => onSourceSelect(source, { type: types_1.PluginLaunchModeType.Search, args: { query } }), onBrowse: (browseTo) => onSourceSelect(source, { type: types_1.PluginLaunchModeType.Browse, args: { browseTo } }) })
|
15
|
+
react_1.default.createElement(SourceLauncher, { source: source, onSearch: (query) => onSourceSelect(source, { type: types_1.PluginLaunchModeType.Search, args: { query } }), onBrowse: (browseTo) => onSourceSelect(source, { type: types_1.PluginLaunchModeType.Browse, args: { browseTo } }) }),
|
16
|
+
sources.length > 1 && index < sources.length - 1 && (react_1.default.createElement("div", { className: "pt-3 pb-3" },
|
17
|
+
react_1.default.createElement(sds_1.Divider, null))))));
|
15
18
|
}))));
|
16
19
|
}
|
17
20
|
exports.default = ResourceLauncher;
|
package/lib/index.css
CHANGED
@@ -5201,6 +5201,9 @@
|
|
5201
5201
|
.squiz-rb-scope .rounded-lg:not(.squiz-rb-plugin *) {
|
5202
5202
|
border-radius: 0.5rem;
|
5203
5203
|
}
|
5204
|
+
.squiz-rb-scope .border:not(.squiz-rb-plugin *) {
|
5205
|
+
border-width: 1px;
|
5206
|
+
}
|
5204
5207
|
.squiz-rb-scope .border-0:not(.squiz-rb-plugin *) {
|
5205
5208
|
border-width: 0px;
|
5206
5209
|
}
|
@@ -5364,6 +5367,9 @@
|
|
5364
5367
|
.squiz-rb-scope .pb-0:not(.squiz-rb-plugin *) {
|
5365
5368
|
padding-bottom: 0px;
|
5366
5369
|
}
|
5370
|
+
.squiz-rb-scope .pb-3:not(.squiz-rb-plugin *) {
|
5371
|
+
padding-bottom: 0.75rem;
|
5372
|
+
}
|
5367
5373
|
.squiz-rb-scope .pb-4:not(.squiz-rb-plugin *) {
|
5368
5374
|
padding-bottom: 1rem;
|
5369
5375
|
}
|
@@ -5893,7 +5899,12 @@
|
|
5893
5899
|
width: 48px;
|
5894
5900
|
}
|
5895
5901
|
.squiz-rb-scope .sourceLauncher:not(.squiz-rb-plugin *) {
|
5896
|
-
|
5902
|
+
margin-bottom: 0.5rem;
|
5903
|
+
}
|
5904
|
+
.squiz-rb-scope .sourceLauncher__top-row:not(.squiz-rb-plugin *) {
|
5905
|
+
display: flex;
|
5906
|
+
align-items: flex-end;
|
5907
|
+
justify-content: space-between;
|
5897
5908
|
margin-bottom: 0.5rem;
|
5898
5909
|
}
|
5899
5910
|
.squiz-rb-scope .sourceLauncher__title:not(.squiz-rb-plugin *) {
|
@@ -5904,10 +5915,8 @@
|
|
5904
5915
|
color: #4f4f4f;
|
5905
5916
|
}
|
5906
5917
|
.squiz-rb-scope .sourceLauncher__browse-group:not(.squiz-rb-plugin *) {
|
5907
|
-
position: absolute;
|
5908
|
-
right: 0;
|
5909
|
-
top: 0;
|
5910
5918
|
display: flex;
|
5919
|
+
align-items: center;
|
5911
5920
|
}
|
5912
5921
|
.squiz-rb-scope .sourceLauncher__or:not(.squiz-rb-plugin *) {
|
5913
5922
|
display: flex;
|
package/lib/index.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { ResourceBrowserContext, ResourceBrowserContextProvider } from './ResourceBrowserContext/ResourceBrowserContext';
|
3
|
-
import { InlineType, ResourceBrowserUnresolvedResource, ResourceBrowserResource } from './types';
|
3
|
+
import { InlineType, ResourceBrowserUnresolvedResource, ResourceBrowserResource, ResourceBrowserPluginType } from './types';
|
4
4
|
import { AuthProvider, useAuthContext, AuthContext } from './ResourceBrowserContext/AuthProvider';
|
5
5
|
import BrowseToSource from './BrowseToSource/BrowseToSource';
|
6
6
|
import SourceDropdown from './SourceDropdown/SourceDropdown';
|
@@ -10,6 +10,7 @@ export * from './types';
|
|
10
10
|
export type ResourceBrowserProps = {
|
11
11
|
modalTitle: string;
|
12
12
|
allowedTypes?: string[];
|
13
|
+
allowedPlugins?: ResourceBrowserPluginType[];
|
13
14
|
isDisabled?: boolean;
|
14
15
|
value: ResourceBrowserUnresolvedResource | null;
|
15
16
|
inline?: boolean;
|
package/lib/index.js
CHANGED
@@ -48,11 +48,19 @@ const SourceDropdownContainer_1 = __importDefault(require("./SourceDropdownConta
|
|
48
48
|
exports.SourceDropdownContainer = SourceDropdownContainer_1.default;
|
49
49
|
__exportStar(require("./types"), exports);
|
50
50
|
const ResourceBrowser = (props) => {
|
51
|
-
const { value, inline, inlineType } = props;
|
51
|
+
const { value, inline, inlineType, allowedPlugins } = props;
|
52
52
|
const [error, setError] = (0, react_1.useState)(null);
|
53
|
-
const { onRequestSources, searchEnabled, plugins } = (0, react_1.useContext)(ResourceBrowserContext_1.ResourceBrowserContext);
|
53
|
+
const { onRequestSources, searchEnabled, plugins: allPlugins } = (0, react_1.useContext)(ResourceBrowserContext_1.ResourceBrowserContext);
|
54
54
|
const [isModalOpen, setIsModalOpen] = (0, react_1.useState)(false);
|
55
55
|
const [source, setSource] = (0, react_1.useState)(null);
|
56
|
+
const plugins = (0, react_1.useMemo)(() => {
|
57
|
+
// Allow some usages e.g. plugin specific like MatrixAssetWidget restrict what plugins show
|
58
|
+
if (!allowedPlugins)
|
59
|
+
return allPlugins; // No restrictions, return all
|
60
|
+
return allPlugins.filter((plugin) => {
|
61
|
+
return allowedPlugins.includes(plugin.type); // Restrict based on plugin type
|
62
|
+
});
|
63
|
+
}, [allowedPlugins, allPlugins]);
|
56
64
|
const [mode, setMode] = (0, react_1.useState)(null);
|
57
65
|
const { data: sources, isLoading, error: sourcesError, reload: reloadSources } = (0, useSources_1.useSources)({ onRequestSources, plugins });
|
58
66
|
const plugin = source?.plugin || null;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/resource-browser",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.3.1",
|
4
4
|
"main": "lib/index.js",
|
5
5
|
"types": "lib/index.d.ts",
|
6
6
|
"private": false,
|
@@ -28,7 +28,7 @@
|
|
28
28
|
"@react-types/shared": "^3.23.1",
|
29
29
|
"@squiz/dx-json-schema-lib": "^1.67.0",
|
30
30
|
"@squiz/generic-browser-lib": "1.67.2",
|
31
|
-
"@squiz/resource-browser-ui-lib": "^1.
|
31
|
+
"@squiz/resource-browser-ui-lib": "^1.2.1",
|
32
32
|
"clsx": "^2.1.0",
|
33
33
|
"expiry-map": "^2.0.0",
|
34
34
|
"p-memoize": "^4.0.4",
|
@@ -23,6 +23,20 @@ describe('ResourceLauncher', () => {
|
|
23
23
|
expect(screen.queryAllByText('Resource Launcher UI').length).toEqual(2);
|
24
24
|
});
|
25
25
|
|
26
|
+
it('should render the divider only for source items except the last one', () => {
|
27
|
+
const sources = [
|
28
|
+
mockSourceWithPlugin({ plugin: defaultPlugin }),
|
29
|
+
mockSourceWithPlugin({ id: '2', plugin: defaultPlugin }),
|
30
|
+
mockSourceWithPlugin({ id: '3', plugin: defaultPlugin }),
|
31
|
+
];
|
32
|
+
const onSourceSelect = jest.fn();
|
33
|
+
const { container } = render(<ResourceLauncher sources={sources} onSourceSelect={onSourceSelect} />);
|
34
|
+
|
35
|
+
const dividerContainers = container.querySelectorAll('div.pt-3.pb-3');
|
36
|
+
// With 3 sources, there should be 2 dividers (in the first two <li>s)
|
37
|
+
expect(dividerContainers.length).toEqual(sources.length - 1);
|
38
|
+
});
|
39
|
+
|
26
40
|
it('onSourceSelect query', async () => {
|
27
41
|
const sources = [mockSourceWithPlugin({ plugin: defaultPlugin })];
|
28
42
|
const onSourceSelect = jest.fn();
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { StoryFn, Meta } from '@storybook/react';
|
3
|
+
import ResourceLauncher from './ResourceLauncher';
|
4
|
+
import sampleSources from '../__mocks__/sample-sources.json';
|
5
|
+
import { ResourceBrowserSourceWithPlugin, PluginLaunchMode, PluginLaunchModeType } from '../types';
|
6
|
+
|
7
|
+
const DummyLauncher = ({
|
8
|
+
source,
|
9
|
+
onSearch,
|
10
|
+
onBrowse,
|
11
|
+
}: {
|
12
|
+
source: any;
|
13
|
+
onSearch: (query: string) => void;
|
14
|
+
onBrowse: (browseTo: string) => void;
|
15
|
+
}) => (
|
16
|
+
<div className="p-2 border rounded">
|
17
|
+
<div>{source.name}</div>
|
18
|
+
<button onClick={() => onSearch('sample query')} className="mr-2">
|
19
|
+
Search
|
20
|
+
</button>
|
21
|
+
<button onClick={() => onBrowse('sample browse')}>Browse</button>
|
22
|
+
</div>
|
23
|
+
);
|
24
|
+
|
25
|
+
const sourcesWithDummyPlugin = (sampleSources as any[]).map((source) => ({
|
26
|
+
...source,
|
27
|
+
plugin: {
|
28
|
+
...source.plugin,
|
29
|
+
type: source.plugin?.type || 'dummy',
|
30
|
+
// renderResourceLauncher should return a component. Here we return a function that renders DummyLauncher.
|
31
|
+
renderResourceLauncher: () => DummyLauncher,
|
32
|
+
},
|
33
|
+
})) as ResourceBrowserSourceWithPlugin[];
|
34
|
+
|
35
|
+
export default {
|
36
|
+
title: 'Resource Launcher',
|
37
|
+
component: ResourceLauncher,
|
38
|
+
} as Meta<typeof ResourceLauncher>;
|
39
|
+
|
40
|
+
const Template: StoryFn<typeof ResourceLauncher> = (args) => {
|
41
|
+
return (
|
42
|
+
<div className="flex justify-center m-3">
|
43
|
+
<ResourceLauncher
|
44
|
+
{...args}
|
45
|
+
sources={sourcesWithDummyPlugin}
|
46
|
+
onSourceSelect={(source, mode: PluginLaunchMode) => {
|
47
|
+
console.log(`Source ${source?.name} / ${source.id} was selected with mode:`, mode);
|
48
|
+
}}
|
49
|
+
/>
|
50
|
+
</div>
|
51
|
+
);
|
52
|
+
};
|
53
|
+
|
54
|
+
export const Primary = Template.bind({});
|
55
|
+
Primary.args = {};
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import React from 'react';
|
2
|
+
import { Divider } from '@squiz/sds';
|
2
3
|
import { ResourceBrowserSource, ResourceBrowserSourceWithPlugin, PluginLaunchMode, PluginLaunchModeType } from '../types';
|
3
4
|
|
4
5
|
interface ResourceLauncherProps {
|
@@ -23,6 +24,11 @@ function ResourceLauncher({ sources, onSourceSelect }: ResourceLauncherProps) {
|
|
23
24
|
onSourceSelect(source, { type: PluginLaunchModeType.Browse, args: { browseTo } })
|
24
25
|
}
|
25
26
|
/>
|
27
|
+
{sources.length > 1 && index < sources.length - 1 && (
|
28
|
+
<div className="pt-3 pb-3">
|
29
|
+
<Divider />
|
30
|
+
</div>
|
31
|
+
)}
|
26
32
|
</div>
|
27
33
|
</li>
|
28
34
|
);
|
package/src/index.spec.tsx
CHANGED
@@ -10,6 +10,9 @@ jest.spyOn(RBI, 'ResourceBrowserInput');
|
|
10
10
|
|
11
11
|
import * as Plugin from './Plugin/Plugin';
|
12
12
|
|
13
|
+
import * as useSources from './Hooks/useSources';
|
14
|
+
jest.spyOn(useSources, 'useSources');
|
15
|
+
|
13
16
|
var useSourceReloadMock = jest.fn();
|
14
17
|
jest.mock('./Hooks/useSources', () => {
|
15
18
|
const actual = jest.requireActual('./Hooks/useSources');
|
@@ -57,12 +60,14 @@ describe('Resource browser input', () => {
|
|
57
60
|
renderResourceLauncher: jest.fn(),
|
58
61
|
} as ResourceBrowserPlugin;
|
59
62
|
|
63
|
+
const plugins = [mockMatrixPlugin, mockDamPlugin];
|
64
|
+
|
60
65
|
const renderComponent = (props: Partial<ResourceBrowserProps> = {}, searchEnabled?: boolean) => {
|
61
66
|
return renderWithContext(
|
62
67
|
<ResourceBrowser modalTitle="Asset picker" value={null} onChange={mockChange} onClear={mockOnClear} {...props} />,
|
63
68
|
{
|
64
69
|
onRequestSources: mockRequestSources,
|
65
|
-
plugins
|
70
|
+
plugins,
|
66
71
|
searchEnabled: !!searchEnabled,
|
67
72
|
},
|
68
73
|
);
|
@@ -76,6 +81,32 @@ describe('Resource browser input', () => {
|
|
76
81
|
};
|
77
82
|
};
|
78
83
|
|
84
|
+
it('allowedPlugins will restrict which plugins are used', async () => {
|
85
|
+
// Works as expected with no restriction
|
86
|
+
(useSources.useSources as jest.Mock).mockClear();
|
87
|
+
renderComponent();
|
88
|
+
let expectedPlugins = plugins;
|
89
|
+
await waitFor(() => {
|
90
|
+
expect(useSources.useSources).toHaveBeenLastCalledWith(
|
91
|
+
expect.objectContaining({
|
92
|
+
plugins: expectedPlugins,
|
93
|
+
}),
|
94
|
+
);
|
95
|
+
});
|
96
|
+
|
97
|
+
// Works as expected with restrictions
|
98
|
+
(useSources.useSources as jest.Mock).mockClear();
|
99
|
+
renderComponent({ allowedPlugins: ['matrix'] });
|
100
|
+
expectedPlugins = plugins.filter((plugin) => plugin.type === 'matrix');
|
101
|
+
await waitFor(() => {
|
102
|
+
expect(useSources.useSources).toHaveBeenLastCalledWith(
|
103
|
+
expect.objectContaining({
|
104
|
+
plugins: expectedPlugins,
|
105
|
+
}),
|
106
|
+
);
|
107
|
+
});
|
108
|
+
});
|
109
|
+
|
79
110
|
it('If only one valid source is provided will default to its Source and Plugin', async () => {
|
80
111
|
const source = mockSource({ type: 'dam' });
|
81
112
|
mockRequestSources.mockResolvedValueOnce([source]);
|
package/src/index.stories.tsx
CHANGED
@@ -86,6 +86,13 @@ SingleSource.args = {
|
|
86
86
|
singleSource: true,
|
87
87
|
};
|
88
88
|
|
89
|
+
export const RestrictedSource = Template.bind({});
|
90
|
+
RestrictedSource.args = {
|
91
|
+
...Primary.args,
|
92
|
+
allowedPlugins: ['matrix'],
|
93
|
+
searchEnabled: true,
|
94
|
+
};
|
95
|
+
|
89
96
|
export const SearchEnabled = Template.bind({});
|
90
97
|
SearchEnabled.args = {
|
91
98
|
...Primary.args,
|
package/src/index.tsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { useState, useContext, useEffect, useCallback } from 'react';
|
1
|
+
import React, { useState, useContext, useEffect, useCallback, useMemo } from 'react';
|
2
2
|
|
3
3
|
import { ResourceBrowserContext, ResourceBrowserContextProvider } from './ResourceBrowserContext/ResourceBrowserContext';
|
4
4
|
import {
|
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
ResourceBrowserUnresolvedResource,
|
8
8
|
ResourceBrowserResource,
|
9
9
|
ResourceBrowserSourceWithPlugin,
|
10
|
+
ResourceBrowserPluginType,
|
10
11
|
} from './types';
|
11
12
|
import { useSources } from './Hooks/useSources';
|
12
13
|
import { PluginRender } from './Plugin/Plugin';
|
@@ -31,6 +32,7 @@ export * from './types';
|
|
31
32
|
export type ResourceBrowserProps = {
|
32
33
|
modalTitle: string;
|
33
34
|
allowedTypes?: string[];
|
35
|
+
allowedPlugins?: ResourceBrowserPluginType[];
|
34
36
|
isDisabled?: boolean;
|
35
37
|
value: ResourceBrowserUnresolvedResource | null;
|
36
38
|
inline?: boolean; // Will render open button only, no input / showing of existing selection
|
@@ -40,12 +42,19 @@ export type ResourceBrowserProps = {
|
|
40
42
|
};
|
41
43
|
|
42
44
|
export const ResourceBrowser = (props: ResourceBrowserProps) => {
|
43
|
-
const { value, inline, inlineType } = props;
|
45
|
+
const { value, inline, inlineType, allowedPlugins } = props;
|
44
46
|
const [error, setError] = useState<Error | null>(null);
|
45
|
-
const { onRequestSources, searchEnabled, plugins } = useContext(ResourceBrowserContext);
|
47
|
+
const { onRequestSources, searchEnabled, plugins: allPlugins } = useContext(ResourceBrowserContext);
|
46
48
|
|
47
49
|
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
48
50
|
const [source, setSource] = useState<ResourceBrowserSourceWithPlugin | null>(null);
|
51
|
+
const plugins = useMemo(() => {
|
52
|
+
// Allow some usages e.g. plugin specific like MatrixAssetWidget restrict what plugins show
|
53
|
+
if (!allowedPlugins) return allPlugins; // No restrictions, return all
|
54
|
+
return allPlugins.filter((plugin) => {
|
55
|
+
return allowedPlugins.includes(plugin.type); // Restrict based on plugin type
|
56
|
+
});
|
57
|
+
}, [allowedPlugins, allPlugins]);
|
49
58
|
const [mode, setMode] = useState<PluginLaunchMode | null>(null);
|
50
59
|
const { data: sources, isLoading, error: sourcesError, reload: reloadSources } = useSources({ onRequestSources, plugins });
|
51
60
|
const plugin = source?.plugin || null;
|