@squiz/resource-browser 2.4.12 → 3.0.0-pre-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/lib/BrowseToSource/BrowseToSource.d.ts +8 -0
- package/lib/BrowseToSource/BrowseToSource.js +50 -0
- package/lib/Hooks/useAuth.js +11 -15
- package/lib/Hooks/useSelectedState.js +3 -7
- package/lib/Hooks/useSources.d.ts +2 -2
- package/lib/Hooks/useSources.js +19 -9
- package/lib/Icons/AdsClickIcon.d.ts +4 -0
- package/lib/Icons/AdsClickIcon.js +5 -0
- package/lib/Icons/ArrowDownIcon.d.ts +4 -0
- package/lib/Icons/ArrowDownIcon.js +5 -0
- package/lib/Icons/CircledLoopIcon.js +4 -11
- package/lib/MainContainer/MainContainer.d.ts +6 -4
- package/lib/MainContainer/MainContainer.js +33 -52
- package/lib/Plugin/Plugin.js +7 -14
- package/lib/ResourceBrowserContext/AuthProvider.js +9 -37
- package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +1 -0
- package/lib/ResourceBrowserContext/ResourceBrowserContext.js +10 -39
- package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +6 -4
- package/lib/ResourceBrowserInput/ResourceBrowserInput.js +5 -12
- package/lib/ResourceLauncher/ResourceLauncher.d.ts +8 -0
- package/lib/ResourceLauncher/ResourceLauncher.js +11 -0
- package/lib/ResourcePicker/ResourcePicker.js +20 -27
- package/lib/ResourcePicker/States/Error.js +6 -13
- package/lib/ResourcePicker/States/Loading.js +4 -11
- package/lib/ResourcePicker/States/Selected.js +12 -19
- package/lib/SourceDropdown/SourceDropdown.d.ts +2 -2
- package/lib/SourceDropdown/SourceDropdown.js +22 -48
- package/lib/SourceDropdownContainer/SourceDropdownContainer.d.ts +5 -0
- package/lib/SourceDropdownContainer/SourceDropdownContainer.js +12 -0
- package/lib/SourceList/SourceList.js +11 -16
- package/lib/index.css +102 -26
- package/lib/index.d.ts +4 -1
- package/lib/index.js +40 -66
- package/lib/types.d.ts +35 -3
- package/lib/types.js +5 -2
- package/lib/utils/authUtils.js +9 -16
- package/lib-esm/BrowseToSource/BrowseToSource.d.ts +8 -0
- package/lib-esm/BrowseToSource/BrowseToSource.js +50 -0
- package/lib-esm/Hooks/useAuth.d.ts +7 -0
- package/lib-esm/Hooks/useAuth.js +54 -0
- package/lib-esm/Hooks/useSelectedState.d.ts +15 -0
- package/lib-esm/Hooks/useSelectedState.js +12 -0
- package/lib-esm/Hooks/useSources.d.ts +14 -0
- package/lib-esm/Hooks/useSources.js +44 -0
- package/lib-esm/Icons/AdsClickIcon.d.ts +4 -0
- package/lib-esm/Icons/AdsClickIcon.js +5 -0
- package/lib-esm/Icons/ArrowDownIcon.d.ts +4 -0
- package/lib-esm/Icons/ArrowDownIcon.js +5 -0
- package/lib-esm/Icons/CircledLoopIcon.d.ts +4 -0
- package/lib-esm/Icons/CircledLoopIcon.js +5 -0
- package/lib-esm/MainContainer/MainContainer.d.ts +19 -0
- package/lib-esm/MainContainer/MainContainer.js +43 -0
- package/lib-esm/Plugin/Plugin.d.ts +13 -0
- package/lib-esm/Plugin/Plugin.js +12 -0
- package/lib-esm/ResourceBrowserContext/AuthProvider.d.ts +16 -0
- package/lib-esm/ResourceBrowserContext/AuthProvider.js +18 -0
- package/lib-esm/ResourceBrowserContext/ResourceBrowserContext.d.ts +15 -0
- package/lib-esm/ResourceBrowserContext/ResourceBrowserContext.js +26 -0
- package/lib-esm/ResourceBrowserInput/ResourceBrowserInput.d.ts +26 -0
- package/lib-esm/ResourceBrowserInput/ResourceBrowserInput.js +9 -0
- package/lib-esm/ResourceLauncher/ResourceLauncher.d.ts +8 -0
- package/lib-esm/ResourceLauncher/ResourceLauncher.js +11 -0
- package/lib-esm/ResourcePicker/ResourcePicker.d.ts +16 -0
- package/lib-esm/ResourcePicker/ResourcePicker.js +25 -0
- package/lib-esm/ResourcePicker/States/Error.d.ts +7 -0
- package/lib-esm/ResourcePicker/States/Error.js +6 -0
- package/lib-esm/ResourcePicker/States/Loading.d.ts +2 -0
- package/lib-esm/ResourcePicker/States/Loading.js +4 -0
- package/lib-esm/ResourcePicker/States/Selected.d.ts +15 -0
- package/lib-esm/ResourcePicker/States/Selected.js +20 -0
- package/lib-esm/SourceDropdown/SourceDropdown.d.ts +7 -0
- package/lib-esm/SourceDropdown/SourceDropdown.js +46 -0
- package/lib-esm/SourceDropdownContainer/SourceDropdownContainer.d.ts +5 -0
- package/lib-esm/SourceDropdownContainer/SourceDropdownContainer.js +12 -0
- package/lib-esm/SourceList/SourceList.d.ts +8 -0
- package/lib-esm/SourceList/SourceList.js +16 -0
- package/lib-esm/index.d.ts +18 -0
- package/lib-esm/index.js +79 -0
- package/lib-esm/types.d.ts +97 -0
- package/lib-esm/types.js +5 -0
- package/lib-esm/utils/authUtils.d.ts +5 -0
- package/lib-esm/utils/authUtils.js +31 -0
- package/package.json +18 -6
- package/src/BrowseToSource/BrowseToSource.spec.tsx +111 -0
- package/src/BrowseToSource/BrowseToSource.stories.tsx +29 -0
- package/src/BrowseToSource/BrowseToSource.tsx +111 -0
- package/src/Hooks/useSources.spec.ts +8 -4
- package/src/Hooks/useSources.ts +28 -13
- package/src/Icons/AdsClickIcon.tsx +11 -0
- package/src/Icons/ArrowDownIcon.tsx +11 -0
- package/src/MainContainer/MainContainer.spec.tsx +322 -108
- package/src/MainContainer/MainContainer.tsx +67 -27
- package/src/Plugin/Plugin.spec.tsx +2 -0
- package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +3 -0
- package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +2 -0
- package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +7 -0
- package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +16 -3
- package/src/ResourceLauncher/ResourceLauncher.spec.tsx +65 -0
- package/src/ResourceLauncher/ResourceLauncher.tsx +31 -0
- package/src/SourceDropdown/SourceDropdown.stories.tsx +1 -2
- package/src/SourceDropdown/SourceDropdown.tsx +8 -8
- package/src/SourceDropdownContainer/SourceDropdownContainer.spec.tsx +50 -0
- package/src/SourceDropdownContainer/SourceDropdownContainer.stories.tsx +62 -0
- package/src/SourceDropdownContainer/SourceDropdownContainer.tsx +27 -0
- package/src/__mocks__/MockModels.ts +16 -2
- package/src/__mocks__/PluginExample.tsx +8 -0
- package/src/__mocks__/StorybookHelpers.tsx +37 -1
- package/src/__mocks__/renderWithContext.tsx +1 -0
- package/src/index.spec.tsx +135 -41
- package/src/index.stories.tsx +12 -1
- package/src/index.tsx +45 -16
- package/src/types.ts +43 -3
- package/.eslintrc +0 -40
- package/.storybook/main.ts +0 -23
- package/.storybook/preview-body.html +0 -1
- package/.storybook/preview-head.html +0 -12
- package/.storybook/preview.ts +0 -16
- package/CHANGELOG.md +0 -244
- package/LICENSE.md +0 -15
- package/build.js +0 -21
- package/jest.config.ts +0 -30
- package/postcss.config.js +0 -21
- package/tailwind.config.cjs +0 -98
- package/tsconfig.json +0 -22
- package/tsconfig.storybook.json +0 -4
- package/tsconfig.test.json +0 -12
- package/vite.config.js +0 -20
@@ -19,6 +19,7 @@ describe('ResourceBrowserContext', () => {
|
|
19
19
|
expect(defaultContext).toEqual({
|
20
20
|
onRequestSources: expect.any(Function),
|
21
21
|
plugins: [],
|
22
|
+
searchEnabled: false,
|
22
23
|
});
|
23
24
|
expect(() => defaultContext?.onRequestSources()).toThrow('onRequestSources has not been configured.');
|
24
25
|
});
|
@@ -33,6 +34,7 @@ describe('ResourceBrowserContext', () => {
|
|
33
34
|
value={{
|
34
35
|
onRequestSources,
|
35
36
|
plugins: [],
|
37
|
+
searchEnabled: false,
|
36
38
|
}}
|
37
39
|
>
|
38
40
|
<ResourceBrowserContext.Consumer>
|
@@ -63,6 +65,7 @@ describe('ResourceBrowserContext', () => {
|
|
63
65
|
value={{
|
64
66
|
onRequestSources,
|
65
67
|
plugins: [],
|
68
|
+
searchEnabled: false,
|
66
69
|
}}
|
67
70
|
>
|
68
71
|
<ResourceBrowserContext.Consumer>
|
@@ -6,6 +6,7 @@ import ExpiryMap from 'expiry-map';
|
|
6
6
|
|
7
7
|
export type ResourceBrowserContextProps = {
|
8
8
|
onRequestSources: OnRequestSources;
|
9
|
+
searchEnabled: boolean;
|
9
10
|
plugins: Array<ResourceBrowserPlugin>;
|
10
11
|
};
|
11
12
|
|
@@ -17,6 +18,7 @@ export const ResourceBrowserContext = React.createContext<ResourceBrowserContext
|
|
17
18
|
onRequestSources: () => {
|
18
19
|
throw new Error('onRequestSources has not been configured.');
|
19
20
|
},
|
21
|
+
searchEnabled: false,
|
20
22
|
plugins: [],
|
21
23
|
});
|
22
24
|
|
@@ -25,6 +25,8 @@ describe('Resource browser input', () => {
|
|
25
25
|
useResource={mockUseResource}
|
26
26
|
onChange={mockChange}
|
27
27
|
plugin={null}
|
28
|
+
pluginMode={null}
|
29
|
+
searchEnabled={false}
|
28
30
|
source={null}
|
29
31
|
sources={[]}
|
30
32
|
isLoading={false}
|
@@ -149,6 +151,11 @@ describe('Resource browser input', () => {
|
|
149
151
|
return <></>;
|
150
152
|
};
|
151
153
|
},
|
154
|
+
sourceSearchComponent: () => {
|
155
|
+
return () => {
|
156
|
+
return <></>;
|
157
|
+
};
|
158
|
+
},
|
152
159
|
};
|
153
160
|
|
154
161
|
renderComponent({ value: { sourceId: source.id, resourceId: '100' }, plugin: plugin as ResourceBrowserPlugin });
|
@@ -1,7 +1,14 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import MainContainer from '../MainContainer/MainContainer';
|
3
3
|
import { ResourcePicker } from '../ResourcePicker/ResourcePicker';
|
4
|
-
import {
|
4
|
+
import {
|
5
|
+
ResourceBrowserPlugin,
|
6
|
+
ResourceBrowserSource,
|
7
|
+
ResourceBrowserUnresolvedResource,
|
8
|
+
ResourceBrowserResource,
|
9
|
+
ResourceBrowserSourceWithPlugin,
|
10
|
+
PluginLaunchMode,
|
11
|
+
} from '../types';
|
5
12
|
|
6
13
|
export type ResourceBrowserInputProps = {
|
7
14
|
modalTitle: string;
|
@@ -19,11 +26,13 @@ export type ResourceBrowserInputProps = {
|
|
19
26
|
onChange(resource: ResourceBrowserResource | null): void;
|
20
27
|
onClear?(): void;
|
21
28
|
plugin: ResourceBrowserPlugin | null;
|
29
|
+
pluginMode: PluginLaunchMode | null;
|
30
|
+
searchEnabled: boolean;
|
22
31
|
source: ResourceBrowserSource | null;
|
23
|
-
sources:
|
32
|
+
sources: ResourceBrowserSourceWithPlugin[];
|
24
33
|
isLoading: boolean;
|
25
34
|
error: Error | null;
|
26
|
-
setSource(source: ResourceBrowserSource
|
35
|
+
setSource(source: ResourceBrowserSource, mode?: PluginLaunchMode): void;
|
27
36
|
isModalOpen: boolean;
|
28
37
|
onModalStateChange(isOpen: boolean): void;
|
29
38
|
};
|
@@ -37,6 +46,8 @@ export const ResourceBrowserInput = ({
|
|
37
46
|
isDisabled,
|
38
47
|
onClear,
|
39
48
|
plugin,
|
49
|
+
pluginMode,
|
50
|
+
searchEnabled,
|
40
51
|
source,
|
41
52
|
sources,
|
42
53
|
isLoading,
|
@@ -68,6 +79,8 @@ export const ResourceBrowserInput = ({
|
|
68
79
|
sources={sources}
|
69
80
|
preselectedResource={resource}
|
70
81
|
plugin={plugin}
|
82
|
+
pluginMode={pluginMode}
|
83
|
+
searchEnabled={searchEnabled}
|
71
84
|
title={modalTitle}
|
72
85
|
titleAriaProps={titleProps}
|
73
86
|
allowedTypes={allowedTypes}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { render, screen } from '@testing-library/react';
|
3
|
+
import ResourceLauncher from './ResourceLauncher';
|
4
|
+
import { PluginLaunchModeType } from '../types';
|
5
|
+
|
6
|
+
import { mockPlugin, mockSourceWithPlugin, mockResource } from '../__mocks__/MockModels';
|
7
|
+
|
8
|
+
describe('ResourceLauncher', () => {
|
9
|
+
const RenderResourceLauncher = jest.fn().mockReturnValue(<div>Resource Launcher UI</div>);
|
10
|
+
const defaultPlugin = mockPlugin({
|
11
|
+
renderResourceLauncher: () => {
|
12
|
+
return RenderResourceLauncher;
|
13
|
+
},
|
14
|
+
});
|
15
|
+
|
16
|
+
it('should render <li> for each provided source', () => {
|
17
|
+
const sources = [mockSourceWithPlugin({ plugin: defaultPlugin }), mockSourceWithPlugin({ id: '2', plugin: defaultPlugin })];
|
18
|
+
const onSourceSelect = jest.fn();
|
19
|
+
render(<ResourceLauncher sources={sources} onSourceSelect={onSourceSelect} />);
|
20
|
+
|
21
|
+
expect(screen.getByLabelText('sources list')).toBeInTheDocument();
|
22
|
+
expect(screen.getByLabelText('sources list').children.length).toEqual(2);
|
23
|
+
expect(screen.queryAllByText('Resource Launcher UI').length).toEqual(2);
|
24
|
+
});
|
25
|
+
|
26
|
+
it('onSourceSelect query', async () => {
|
27
|
+
const sources = [mockSourceWithPlugin({ plugin: defaultPlugin })];
|
28
|
+
const onSourceSelect = jest.fn();
|
29
|
+
render(<ResourceLauncher sources={sources} onSourceSelect={onSourceSelect} />);
|
30
|
+
|
31
|
+
// Get the first plugin function launcher components called props
|
32
|
+
const { onSearch } = (RenderResourceLauncher as jest.Mock).mock.calls[0][0];
|
33
|
+
|
34
|
+
// Invoke the onSearch handler
|
35
|
+
onSearch('test');
|
36
|
+
expect(onSourceSelect).toHaveBeenCalledWith(sources[0], { type: PluginLaunchModeType.Search, args: { query: 'test' } });
|
37
|
+
});
|
38
|
+
|
39
|
+
it('onSourceSelect browse', async () => {
|
40
|
+
const sources = [mockSourceWithPlugin({ plugin: defaultPlugin })];
|
41
|
+
const onSourceSelect = jest.fn();
|
42
|
+
render(<ResourceLauncher sources={sources} onSourceSelect={onSourceSelect} />);
|
43
|
+
|
44
|
+
// Get the first plugin function launcher components called props
|
45
|
+
const { onBrowse } = (RenderResourceLauncher as jest.Mock).mock.calls[0][0];
|
46
|
+
|
47
|
+
// Invoke the onBrowse handler
|
48
|
+
onBrowse();
|
49
|
+
expect(onSourceSelect).toHaveBeenCalledWith(sources[0], { type: PluginLaunchModeType.Browse, args: { browseTo: undefined } });
|
50
|
+
});
|
51
|
+
|
52
|
+
it('onSourceSelect browseTo', async () => {
|
53
|
+
const sources = [mockSourceWithPlugin({ plugin: defaultPlugin })];
|
54
|
+
const onSourceSelect = jest.fn();
|
55
|
+
render(<ResourceLauncher sources={sources} onSourceSelect={onSourceSelect} />);
|
56
|
+
|
57
|
+
// Get the first plugin function launcher components called props
|
58
|
+
const { onBrowse } = (RenderResourceLauncher as jest.Mock).mock.calls[0][0];
|
59
|
+
|
60
|
+
// Invoke the onBrowse handler
|
61
|
+
const browseTo = mockResource();
|
62
|
+
onBrowse(browseTo);
|
63
|
+
expect(onSourceSelect).toHaveBeenCalledWith(sources[0], { type: PluginLaunchModeType.Browse, args: { browseTo } });
|
64
|
+
});
|
65
|
+
});
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { ResourceBrowserSource, ResourceBrowserSourceWithPlugin, PluginLaunchMode, PluginLaunchModeType } from '../types';
|
3
|
+
|
4
|
+
interface ResourceLauncherProps {
|
5
|
+
sources: ResourceBrowserSourceWithPlugin[];
|
6
|
+
onSourceSelect(source: ResourceBrowserSource, mode: PluginLaunchMode): void;
|
7
|
+
}
|
8
|
+
|
9
|
+
function ResourceLauncher({ sources, onSourceSelect }: ResourceLauncherProps) {
|
10
|
+
return (
|
11
|
+
<div className="overflow-y-scroll 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">
|
12
|
+
<ul tabIndex={-1} aria-label={`sources list`} className="flex flex-col bg-gray-100 min-h-full focus-visible:outline-0">
|
13
|
+
{sources.map((source, index) => {
|
14
|
+
const SourceLauncher = source.plugin.renderResourceLauncher();
|
15
|
+
|
16
|
+
return (
|
17
|
+
<li key={index} className="flex items-stretch relative">
|
18
|
+
<SourceLauncher
|
19
|
+
source={source}
|
20
|
+
onSearch={(query) => onSourceSelect(source, { type: PluginLaunchModeType.Search, args: { query } })}
|
21
|
+
onBrowse={(browseTo) => onSourceSelect(source, { type: PluginLaunchModeType.Browse, args: { browseTo } })}
|
22
|
+
/>
|
23
|
+
</li>
|
24
|
+
);
|
25
|
+
})}
|
26
|
+
</ul>
|
27
|
+
</div>
|
28
|
+
);
|
29
|
+
}
|
30
|
+
|
31
|
+
export default ResourceLauncher;
|
@@ -15,7 +15,7 @@ const Template: StoryFn<typeof SourceDropdown> = (props) => {
|
|
15
15
|
const [selectedSource, setSelectedSource] = useState<ResourceBrowserSource>(sources[0]);
|
16
16
|
|
17
17
|
return (
|
18
|
-
<div className="m-3">
|
18
|
+
<div className="m-3 w-[200px]">
|
19
19
|
<SourceDropdown
|
20
20
|
selectedSource={selectedSource}
|
21
21
|
sources={sources}
|
@@ -26,5 +26,4 @@ const Template: StoryFn<typeof SourceDropdown> = (props) => {
|
|
26
26
|
};
|
27
27
|
|
28
28
|
export const Primary = Template.bind({});
|
29
|
-
|
30
29
|
Primary.args = {};
|
@@ -1,8 +1,9 @@
|
|
1
1
|
import React, { useRef, useState } from 'react';
|
2
2
|
|
3
|
-
import { ResourceBrowserSource } from '../types';
|
3
|
+
import { ResourceBrowserSource, PluginLaunchMode } from '../types';
|
4
4
|
import { Icon, IconOptions, uuid } from '@squiz/generic-browser-lib';
|
5
5
|
import { useFocusWithin, useKeyboard } from 'react-aria';
|
6
|
+
import { ArrowDownIcon } from '../Icons/ArrowDownIcon';
|
6
7
|
|
7
8
|
export default function SourceDropdown({
|
8
9
|
sources,
|
@@ -11,7 +12,7 @@ export default function SourceDropdown({
|
|
11
12
|
}: {
|
12
13
|
sources: ResourceBrowserSource[];
|
13
14
|
selectedSource: ResourceBrowserSource;
|
14
|
-
onSourceSelect(source: ResourceBrowserSource): void;
|
15
|
+
onSourceSelect(source: ResourceBrowserSource, mode?: PluginLaunchMode): void;
|
15
16
|
}) {
|
16
17
|
const [uniqueId] = useState(uuid());
|
17
18
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
@@ -45,7 +46,7 @@ export default function SourceDropdown({
|
|
45
46
|
}
|
46
47
|
|
47
48
|
return (
|
48
|
-
<div {...focusWithinProps} {...keyboardProps} className="relative w-
|
49
|
+
<div {...focusWithinProps} {...keyboardProps} className="relative w-full ">
|
49
50
|
<button
|
50
51
|
ref={buttonRef}
|
51
52
|
type="button"
|
@@ -53,17 +54,16 @@ export default function SourceDropdown({
|
|
53
54
|
aria-expanded={isOpen}
|
54
55
|
aria-controls={`${uniqueId}-button-menu`}
|
55
56
|
onClick={() => setIsOpen(!isOpen)}
|
56
|
-
className="relative flex items-center
|
57
|
+
className="relative flex items-center p-2 w-full rounded bg-blue-100 hover:bg-blue-150"
|
57
58
|
>
|
58
59
|
<span className="sr-only">current source </span>
|
59
|
-
<
|
60
|
-
<
|
61
|
-
<Icon icon={'arrow-down' as IconOptions} aria-hidden className="absolute right-3" />
|
60
|
+
<div className="truncate max-w-[200px] text-md font-semibold text-blue-400">{selectedSource.name}</div>
|
61
|
+
<ArrowDownIcon aria-hidden className="absolute right-2 fill-blue-300" />
|
62
62
|
</button>
|
63
63
|
<ul
|
64
64
|
id={`${uniqueId}-button-menu`}
|
65
65
|
aria-hidden={!isOpen}
|
66
|
-
className={`absolute z-50 top-[calc(100%+
|
66
|
+
className={`absolute z-50 top-[calc(100%+8px)] w-[100%] bg-gray-100 border-1 shadow-md rounded border-gray-300 p-2 pb-0 overflow-y-scroll max-h-80 ${
|
67
67
|
!isOpen ? 'hidden' : ''
|
68
68
|
}`}
|
69
69
|
>
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
3
|
+
import SourceDropdownContainer from './SourceDropdownContainer';
|
4
|
+
|
5
|
+
describe('SourceDropdownContainer', () => {
|
6
|
+
it('Render a dropdown container with child component(s)', async () => {
|
7
|
+
const children = <span id="dropdown-content">Hello world</span>;
|
8
|
+
|
9
|
+
const { baseElement } = render(
|
10
|
+
<SourceDropdownContainer isCollapsed={false} onExpand={jest.fn()}>
|
11
|
+
{children}
|
12
|
+
</SourceDropdownContainer>,
|
13
|
+
);
|
14
|
+
|
15
|
+
const content = baseElement.querySelector(`#dropdown-content`) as HTMLSpanElement;
|
16
|
+
expect(content).toBeTruthy();
|
17
|
+
});
|
18
|
+
|
19
|
+
it('Render collapsed state with single button to re-expand no children', async () => {
|
20
|
+
const children = <span id="dropdown-content">Hello world</span>;
|
21
|
+
|
22
|
+
const { baseElement } = render(
|
23
|
+
<SourceDropdownContainer isCollapsed={true} onExpand={jest.fn()}>
|
24
|
+
{children}
|
25
|
+
</SourceDropdownContainer>,
|
26
|
+
);
|
27
|
+
|
28
|
+
const content = baseElement.querySelector(`#dropdown-content`) as HTMLSpanElement;
|
29
|
+
expect(content).toBeFalsy();
|
30
|
+
|
31
|
+
const expandButton = screen.getByRole('button', { name: /Expand browse options/i });
|
32
|
+
expect(expandButton).toBeTruthy();
|
33
|
+
});
|
34
|
+
|
35
|
+
it('Will envoke onExpand callback function when expand button is pressed', async () => {
|
36
|
+
const onExpand = jest.fn();
|
37
|
+
const children = <span id="dropdown-content">Hello world</span>;
|
38
|
+
|
39
|
+
render(
|
40
|
+
<SourceDropdownContainer isCollapsed={true} onExpand={onExpand}>
|
41
|
+
{children}
|
42
|
+
</SourceDropdownContainer>,
|
43
|
+
);
|
44
|
+
|
45
|
+
const expandButton = screen.getByRole('button', { name: /Expand browse options/i });
|
46
|
+
fireEvent.click(expandButton);
|
47
|
+
|
48
|
+
expect(onExpand).toHaveBeenCalled();
|
49
|
+
});
|
50
|
+
});
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { StoryFn, Meta } from '@storybook/react';
|
3
|
+
import SourceDropdownContainer from './SourceDropdownContainer';
|
4
|
+
import sampleSources from '../__mocks__/sample-sources.json';
|
5
|
+
import SourceDropdown from '../SourceDropdown/SourceDropdown';
|
6
|
+
import { ResourceBrowserSource } from '../types';
|
7
|
+
|
8
|
+
export default {
|
9
|
+
title: 'Source dropdown container',
|
10
|
+
component: SourceDropdownContainer,
|
11
|
+
} as Meta<typeof SourceDropdownContainer>;
|
12
|
+
|
13
|
+
const Template: StoryFn<typeof SourceDropdownContainer> = (props) => {
|
14
|
+
return (
|
15
|
+
<div className="m-3">
|
16
|
+
<SourceDropdownContainer {...props}>{props.children}</SourceDropdownContainer>
|
17
|
+
</div>
|
18
|
+
);
|
19
|
+
};
|
20
|
+
|
21
|
+
export const Primary = Template.bind({});
|
22
|
+
Primary.args = {
|
23
|
+
isCollapsed: false,
|
24
|
+
onExpand: () => {
|
25
|
+
console.log('expanded');
|
26
|
+
},
|
27
|
+
children: (
|
28
|
+
<div className="flex items-center h-[36px]">
|
29
|
+
<span className="mr-3">Child component</span>
|
30
|
+
</div>
|
31
|
+
),
|
32
|
+
};
|
33
|
+
|
34
|
+
export const Collapsed = Template.bind({});
|
35
|
+
Collapsed.args = {
|
36
|
+
isCollapsed: true,
|
37
|
+
onExpand: () => {
|
38
|
+
console.log('expanded');
|
39
|
+
},
|
40
|
+
children: (
|
41
|
+
<div className="flex items-center h-[36px]">
|
42
|
+
<span className="mr-3">Child component</span>
|
43
|
+
</div>
|
44
|
+
),
|
45
|
+
};
|
46
|
+
|
47
|
+
export const DropdownExample = Template.bind({});
|
48
|
+
DropdownExample.args = {
|
49
|
+
isCollapsed: false,
|
50
|
+
onExpand: () => {
|
51
|
+
console.log('expanded');
|
52
|
+
},
|
53
|
+
children: (
|
54
|
+
<div className="border-l border-blue-200 pl-1 w-[204px]">
|
55
|
+
<SourceDropdown
|
56
|
+
selectedSource={sampleSources[0] as ResourceBrowserSource}
|
57
|
+
sources={sampleSources as ResourceBrowserSource[]}
|
58
|
+
onSourceSelect={(source) => alert(`Source Select: ${source.name}`)}
|
59
|
+
/>
|
60
|
+
</div>
|
61
|
+
),
|
62
|
+
};
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import React, { PropsWithChildren } from 'react';
|
2
|
+
import { AdsClickIcon } from '../Icons/AdsClickIcon';
|
3
|
+
|
4
|
+
export default function SourceDropdownContainer({
|
5
|
+
children,
|
6
|
+
isCollapsed,
|
7
|
+
onExpand,
|
8
|
+
}: PropsWithChildren<{ isCollapsed: boolean; onExpand: () => void }>) {
|
9
|
+
return (
|
10
|
+
<div className="inline-flex rounded-lg p-1 bg-blue-100 min-h-[44px]">
|
11
|
+
{isCollapsed && (
|
12
|
+
<button aria-label="Expand browse options" onClick={onExpand} className="flex items-center">
|
13
|
+
<AdsClickIcon aria-hidden className="mx-2 fill-blue-300" />
|
14
|
+
</button>
|
15
|
+
)}
|
16
|
+
{!isCollapsed && (
|
17
|
+
<>
|
18
|
+
<div className="flex items-center pr-1">
|
19
|
+
<AdsClickIcon aria-hidden className="mx-2 fill-blue-300" />
|
20
|
+
<span className="sr-only">Browse</span>
|
21
|
+
</div>
|
22
|
+
{children}
|
23
|
+
</>
|
24
|
+
)}
|
25
|
+
</div>
|
26
|
+
);
|
27
|
+
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { ResourceBrowserSource, ResourceBrowserResource, ResourceBrowserPlugin } from '../types';
|
1
|
+
import { ResourceBrowserSource, ResourceBrowserResource, ResourceBrowserPlugin, ResourceBrowserSourceWithPlugin } from '../types';
|
2
2
|
|
3
3
|
export type DeepPartial<T> = {
|
4
4
|
[P in keyof T]?: T[P] extends Array<infer U>
|
@@ -8,7 +8,7 @@ export type DeepPartial<T> = {
|
|
8
8
|
: DeepPartial<T[P]>;
|
9
9
|
};
|
10
10
|
|
11
|
-
export const mockSource = (properties:
|
11
|
+
export const mockSource = (properties: Partial<ResourceBrowserSource> = {}): ResourceBrowserSource => {
|
12
12
|
return {
|
13
13
|
id: '1',
|
14
14
|
name: 'Test source',
|
@@ -17,6 +17,16 @@ export const mockSource = (properties: DeepPartial<ResourceBrowserSource> = {}):
|
|
17
17
|
};
|
18
18
|
};
|
19
19
|
|
20
|
+
export const mockSourceWithPlugin = (properties: Partial<ResourceBrowserSourceWithPlugin> = {}): ResourceBrowserSourceWithPlugin => {
|
21
|
+
return {
|
22
|
+
id: '1',
|
23
|
+
name: 'Test source',
|
24
|
+
type: 'dam',
|
25
|
+
plugin: mockPlugin(),
|
26
|
+
...properties,
|
27
|
+
};
|
28
|
+
};
|
29
|
+
|
20
30
|
export const mockPlugin = (properties: DeepPartial<ResourceBrowserPlugin> = {}): ResourceBrowserPlugin => {
|
21
31
|
return {
|
22
32
|
type: 'dam',
|
@@ -26,6 +36,10 @@ export const mockPlugin = (properties: DeepPartial<ResourceBrowserPlugin> = {}):
|
|
26
36
|
renderSelectedResource: jest.fn(),
|
27
37
|
// @ts-ignore
|
28
38
|
resolveResource: jest.fn(),
|
39
|
+
// @ts-ignore
|
40
|
+
sourceSearchComponent: jest.fn(),
|
41
|
+
// @ts-ignore
|
42
|
+
renderResourceLauncher: jest.fn(),
|
29
43
|
...properties,
|
30
44
|
};
|
31
45
|
};
|
@@ -94,5 +94,13 @@ export default (): ResourceBrowserPlugin => {
|
|
94
94
|
isLoading: false,
|
95
95
|
};
|
96
96
|
},
|
97
|
+
|
98
|
+
// TODO: as they are worked out
|
99
|
+
sourceSearchComponent: () => {
|
100
|
+
return () => <></>;
|
101
|
+
},
|
102
|
+
renderResourceLauncher: () => {
|
103
|
+
return () => <></>;
|
104
|
+
},
|
97
105
|
};
|
98
106
|
};
|
@@ -22,7 +22,7 @@ export const createPlugins = (callbackWait: number, headerPortal = false): Resou
|
|
22
22
|
return (props) => {
|
23
23
|
return (
|
24
24
|
<div className="h-screen lg:h-[calc(100vh-9rem)] w-screen max-w-[52rem]">
|
25
|
-
<div>THIS IS A {type} PLUGIN</div>
|
25
|
+
<div>THIS IS A {type} BROWSE PLUGIN</div>
|
26
26
|
<button
|
27
27
|
onClick={() => {
|
28
28
|
props.onSelected({
|
@@ -38,6 +38,42 @@ export const createPlugins = (callbackWait: number, headerPortal = false): Resou
|
|
38
38
|
);
|
39
39
|
};
|
40
40
|
},
|
41
|
+
sourceSearchComponent: () => {
|
42
|
+
return (props) => {
|
43
|
+
return (
|
44
|
+
<div className="h-screen lg:h-[calc(100vh-9rem)] w-screen max-w-[52rem]">
|
45
|
+
<div>THIS IS A {type} SEARCH PLUGIN</div>
|
46
|
+
<div>Received query: {props.query}</div>
|
47
|
+
<button
|
48
|
+
onClick={() => {
|
49
|
+
props.onSelected({
|
50
|
+
id: '1f7a25b4-380f-4540-9555-8be2dcab4019',
|
51
|
+
source: props.source,
|
52
|
+
} as ResourceBrowserResource);
|
53
|
+
}}
|
54
|
+
>
|
55
|
+
Press to set resource
|
56
|
+
</button>
|
57
|
+
{headerPortal && props.headerPortal && createPortal(<div>HeadPortal</div>, props.headerPortal)}
|
58
|
+
</div>
|
59
|
+
);
|
60
|
+
};
|
61
|
+
},
|
62
|
+
renderResourceLauncher: () => {
|
63
|
+
return (props) => {
|
64
|
+
const [query, setQuery] = useState<string>('');
|
65
|
+
return (
|
66
|
+
<div className="">
|
67
|
+
<div>Search {type}</div>
|
68
|
+
<form>
|
69
|
+
<input onChange={(event) => setQuery(event.target.value)} />
|
70
|
+
<button onClick={() => props.onSearch(query)}>Search</button>
|
71
|
+
</form>
|
72
|
+
<button onClick={props.onBrowse}>Browse</button>
|
73
|
+
</div>
|
74
|
+
);
|
75
|
+
};
|
76
|
+
},
|
41
77
|
renderSelectedResource: (resource) => {
|
42
78
|
return new Promise((resolve, reject) => {
|
43
79
|
const fileSize = resource.squizImage?.imageVariations?.original?.byteSize;
|