@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.
Files changed (128) hide show
  1. package/README.md +4 -0
  2. package/lib/BrowseToSource/BrowseToSource.d.ts +8 -0
  3. package/lib/BrowseToSource/BrowseToSource.js +50 -0
  4. package/lib/Hooks/useAuth.js +11 -15
  5. package/lib/Hooks/useSelectedState.js +3 -7
  6. package/lib/Hooks/useSources.d.ts +2 -2
  7. package/lib/Hooks/useSources.js +19 -9
  8. package/lib/Icons/AdsClickIcon.d.ts +4 -0
  9. package/lib/Icons/AdsClickIcon.js +5 -0
  10. package/lib/Icons/ArrowDownIcon.d.ts +4 -0
  11. package/lib/Icons/ArrowDownIcon.js +5 -0
  12. package/lib/Icons/CircledLoopIcon.js +4 -11
  13. package/lib/MainContainer/MainContainer.d.ts +6 -4
  14. package/lib/MainContainer/MainContainer.js +33 -52
  15. package/lib/Plugin/Plugin.js +7 -14
  16. package/lib/ResourceBrowserContext/AuthProvider.js +9 -37
  17. package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +1 -0
  18. package/lib/ResourceBrowserContext/ResourceBrowserContext.js +10 -39
  19. package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +6 -4
  20. package/lib/ResourceBrowserInput/ResourceBrowserInput.js +5 -12
  21. package/lib/ResourceLauncher/ResourceLauncher.d.ts +8 -0
  22. package/lib/ResourceLauncher/ResourceLauncher.js +11 -0
  23. package/lib/ResourcePicker/ResourcePicker.js +20 -27
  24. package/lib/ResourcePicker/States/Error.js +6 -13
  25. package/lib/ResourcePicker/States/Loading.js +4 -11
  26. package/lib/ResourcePicker/States/Selected.js +12 -19
  27. package/lib/SourceDropdown/SourceDropdown.d.ts +2 -2
  28. package/lib/SourceDropdown/SourceDropdown.js +22 -48
  29. package/lib/SourceDropdownContainer/SourceDropdownContainer.d.ts +5 -0
  30. package/lib/SourceDropdownContainer/SourceDropdownContainer.js +12 -0
  31. package/lib/SourceList/SourceList.js +11 -16
  32. package/lib/index.css +102 -26
  33. package/lib/index.d.ts +4 -1
  34. package/lib/index.js +40 -66
  35. package/lib/types.d.ts +35 -3
  36. package/lib/types.js +5 -2
  37. package/lib/utils/authUtils.js +9 -16
  38. package/lib-esm/BrowseToSource/BrowseToSource.d.ts +8 -0
  39. package/lib-esm/BrowseToSource/BrowseToSource.js +50 -0
  40. package/lib-esm/Hooks/useAuth.d.ts +7 -0
  41. package/lib-esm/Hooks/useAuth.js +54 -0
  42. package/lib-esm/Hooks/useSelectedState.d.ts +15 -0
  43. package/lib-esm/Hooks/useSelectedState.js +12 -0
  44. package/lib-esm/Hooks/useSources.d.ts +14 -0
  45. package/lib-esm/Hooks/useSources.js +44 -0
  46. package/lib-esm/Icons/AdsClickIcon.d.ts +4 -0
  47. package/lib-esm/Icons/AdsClickIcon.js +5 -0
  48. package/lib-esm/Icons/ArrowDownIcon.d.ts +4 -0
  49. package/lib-esm/Icons/ArrowDownIcon.js +5 -0
  50. package/lib-esm/Icons/CircledLoopIcon.d.ts +4 -0
  51. package/lib-esm/Icons/CircledLoopIcon.js +5 -0
  52. package/lib-esm/MainContainer/MainContainer.d.ts +19 -0
  53. package/lib-esm/MainContainer/MainContainer.js +43 -0
  54. package/lib-esm/Plugin/Plugin.d.ts +13 -0
  55. package/lib-esm/Plugin/Plugin.js +12 -0
  56. package/lib-esm/ResourceBrowserContext/AuthProvider.d.ts +16 -0
  57. package/lib-esm/ResourceBrowserContext/AuthProvider.js +18 -0
  58. package/lib-esm/ResourceBrowserContext/ResourceBrowserContext.d.ts +15 -0
  59. package/lib-esm/ResourceBrowserContext/ResourceBrowserContext.js +26 -0
  60. package/lib-esm/ResourceBrowserInput/ResourceBrowserInput.d.ts +26 -0
  61. package/lib-esm/ResourceBrowserInput/ResourceBrowserInput.js +9 -0
  62. package/lib-esm/ResourceLauncher/ResourceLauncher.d.ts +8 -0
  63. package/lib-esm/ResourceLauncher/ResourceLauncher.js +11 -0
  64. package/lib-esm/ResourcePicker/ResourcePicker.d.ts +16 -0
  65. package/lib-esm/ResourcePicker/ResourcePicker.js +25 -0
  66. package/lib-esm/ResourcePicker/States/Error.d.ts +7 -0
  67. package/lib-esm/ResourcePicker/States/Error.js +6 -0
  68. package/lib-esm/ResourcePicker/States/Loading.d.ts +2 -0
  69. package/lib-esm/ResourcePicker/States/Loading.js +4 -0
  70. package/lib-esm/ResourcePicker/States/Selected.d.ts +15 -0
  71. package/lib-esm/ResourcePicker/States/Selected.js +20 -0
  72. package/lib-esm/SourceDropdown/SourceDropdown.d.ts +7 -0
  73. package/lib-esm/SourceDropdown/SourceDropdown.js +46 -0
  74. package/lib-esm/SourceDropdownContainer/SourceDropdownContainer.d.ts +5 -0
  75. package/lib-esm/SourceDropdownContainer/SourceDropdownContainer.js +12 -0
  76. package/lib-esm/SourceList/SourceList.d.ts +8 -0
  77. package/lib-esm/SourceList/SourceList.js +16 -0
  78. package/lib-esm/index.d.ts +18 -0
  79. package/lib-esm/index.js +79 -0
  80. package/lib-esm/types.d.ts +97 -0
  81. package/lib-esm/types.js +5 -0
  82. package/lib-esm/utils/authUtils.d.ts +5 -0
  83. package/lib-esm/utils/authUtils.js +31 -0
  84. package/package.json +18 -6
  85. package/src/BrowseToSource/BrowseToSource.spec.tsx +111 -0
  86. package/src/BrowseToSource/BrowseToSource.stories.tsx +29 -0
  87. package/src/BrowseToSource/BrowseToSource.tsx +111 -0
  88. package/src/Hooks/useSources.spec.ts +8 -4
  89. package/src/Hooks/useSources.ts +28 -13
  90. package/src/Icons/AdsClickIcon.tsx +11 -0
  91. package/src/Icons/ArrowDownIcon.tsx +11 -0
  92. package/src/MainContainer/MainContainer.spec.tsx +322 -108
  93. package/src/MainContainer/MainContainer.tsx +67 -27
  94. package/src/Plugin/Plugin.spec.tsx +2 -0
  95. package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +3 -0
  96. package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +2 -0
  97. package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +7 -0
  98. package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +16 -3
  99. package/src/ResourceLauncher/ResourceLauncher.spec.tsx +65 -0
  100. package/src/ResourceLauncher/ResourceLauncher.tsx +31 -0
  101. package/src/SourceDropdown/SourceDropdown.stories.tsx +1 -2
  102. package/src/SourceDropdown/SourceDropdown.tsx +8 -8
  103. package/src/SourceDropdownContainer/SourceDropdownContainer.spec.tsx +50 -0
  104. package/src/SourceDropdownContainer/SourceDropdownContainer.stories.tsx +62 -0
  105. package/src/SourceDropdownContainer/SourceDropdownContainer.tsx +27 -0
  106. package/src/__mocks__/MockModels.ts +16 -2
  107. package/src/__mocks__/PluginExample.tsx +8 -0
  108. package/src/__mocks__/StorybookHelpers.tsx +37 -1
  109. package/src/__mocks__/renderWithContext.tsx +1 -0
  110. package/src/index.spec.tsx +135 -41
  111. package/src/index.stories.tsx +12 -1
  112. package/src/index.tsx +45 -16
  113. package/src/types.ts +43 -3
  114. package/.eslintrc +0 -40
  115. package/.storybook/main.ts +0 -23
  116. package/.storybook/preview-body.html +0 -1
  117. package/.storybook/preview-head.html +0 -12
  118. package/.storybook/preview.ts +0 -16
  119. package/CHANGELOG.md +0 -244
  120. package/LICENSE.md +0 -15
  121. package/build.js +0 -21
  122. package/jest.config.ts +0 -30
  123. package/postcss.config.js +0 -21
  124. package/tailwind.config.cjs +0 -98
  125. package/tsconfig.json +0 -22
  126. package/tsconfig.storybook.json +0 -4
  127. package/tsconfig.test.json +0 -12
  128. package/vite.config.js +0 -20
package/package.json CHANGED
@@ -1,17 +1,30 @@
1
1
  {
2
2
  "name": "@squiz/resource-browser",
3
- "version": "2.4.12",
4
- "main": "lib/index.js",
3
+ "version": "3.0.0-pre-alpha.0",
5
4
  "types": "lib/index.d.ts",
5
+ "files": [
6
+ "lib/*",
7
+ "lib-esm/*",
8
+ "src/*"
9
+ ],
10
+ "exports": {
11
+ ".": {
12
+ "import": "./lib-esm/index.js",
13
+ "require": "./lib/index.js"
14
+ },
15
+ "./src/": "./src/",
16
+ "./lib/": "./lib/"
17
+ },
6
18
  "private": false,
7
19
  "publishConfig": {
8
20
  "access": "public"
9
21
  },
10
22
  "scripts": {
11
23
  "compile": "npm run compile:styles && npm run compile:code",
12
- "compile:code": "tsc",
24
+ "compile:code": "tsc -b ./tsconfig.json ./tsconfig.esm.json",
13
25
  "compile:styles": "node build.js",
14
26
  "storybook": "storybook dev -p 6006",
27
+ "storybook:build": "storybook build",
15
28
  "test": "npm run test:unit && npm run test:eslint",
16
29
  "test:unit": "jest",
17
30
  "test:eslint": "eslint .",
@@ -25,7 +38,7 @@
25
38
  "@react-types/shared": "^3.23.1",
26
39
  "@squiz/dx-json-schema-lib": "^1.67.0",
27
40
  "@squiz/generic-browser-lib": "1.67.2",
28
- "@squiz/resource-browser-ui-lib": "^0.10.12",
41
+ "@squiz/resource-browser-ui-lib": "^1.0.0-pre-alpha.0",
29
42
  "clsx": "^2.1.0",
30
43
  "expiry-map": "^2.0.0",
31
44
  "p-memoize": "^4.0.4",
@@ -84,6 +97,5 @@
84
97
  },
85
98
  "volta": {
86
99
  "node": "18.18.0"
87
- },
88
- "gitHead": "430b0077849af23f3264897687f2e57eb72656fc"
100
+ }
89
101
  }
@@ -0,0 +1,111 @@
1
+ import React from 'react';
2
+ import { screen, render, waitFor, act, fireEvent } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import BrowseToSource from './BrowseToSource';
5
+ import { mockSource } from '../__mocks__/MockModels';
6
+ import { ResourceBrowserSource } from '../types';
7
+
8
+ describe('BrowseToSource', () => {
9
+ it('Render empty fragment if no sources are provided', async () => {
10
+ const sources: Array<ResourceBrowserSource> = [];
11
+
12
+ const { baseElement } = render(<BrowseToSource sources={sources} selectedSource={mockSource()} onSourceSelect={() => {}} />);
13
+
14
+ await waitFor(() => {
15
+ const button = baseElement.querySelector(`button[aria-label="Source quick select"]`) as HTMLButtonElement;
16
+ expect(button).toBeFalsy();
17
+ });
18
+ });
19
+
20
+ it('Accepts a root className', async () => {
21
+ const sources: Array<ResourceBrowserSource> = [mockSource({ name: 'Source 1' })];
22
+
23
+ const { container } = render(
24
+ <BrowseToSource sources={sources} selectedSource={mockSource()} onSourceSelect={() => {}} className="test" />,
25
+ );
26
+
27
+ const className = container.querySelector('.test') as HTMLElement;
28
+ expect(className).toBeTruthy();
29
+ });
30
+
31
+ it('Sources are rendered when dropdown clicked', async () => {
32
+ const sources = [mockSource({ name: 'Source 1' }), mockSource({ id: '2', name: 'Source 2' })];
33
+
34
+ render(<BrowseToSource sources={sources} selectedSource={sources[0]} onSourceSelect={() => {}} />);
35
+
36
+ expect(screen.getByRole('button', { name: 'Source quick select' })).toHaveTextContent('Browse');
37
+
38
+ const user = userEvent.setup();
39
+
40
+ await act(() => user.click(screen.getByRole('button', { name: 'Source quick select' })));
41
+
42
+ await waitFor(() => {
43
+ expect(screen.getByRole('button', { name: 'Source quick select' })).toHaveTextContent('Browse to ...');
44
+ expect(screen.getByRole('button', { name: 'dam Source 1 selected' })).toBeTruthy();
45
+ expect(screen.getByRole('button', { name: 'dam Source 2' })).toBeTruthy();
46
+ });
47
+ });
48
+
49
+ it('Source menu closes on focus loss', async () => {
50
+ const sources = [mockSource({ name: 'Source 1' }), mockSource({ id: '2', name: 'Source 2' })];
51
+
52
+ render(<BrowseToSource sources={sources} selectedSource={sources[0]} onSourceSelect={() => {}} />);
53
+
54
+ const user = userEvent.setup();
55
+ const buttonDropdown = screen.getByRole('button', { name: 'Source quick select' });
56
+
57
+ await act(() => user.click(buttonDropdown));
58
+
59
+ await waitFor(() => {
60
+ expect(screen.queryByRole('button', { name: 'dam Source 1 selected' })).toBeTruthy();
61
+ expect(screen.queryByRole('button', { name: 'dam Source 2' })).toBeTruthy();
62
+ });
63
+
64
+ await act(() => fireEvent.blur(buttonDropdown));
65
+
66
+ await waitFor(() => {
67
+ expect(screen.queryByRole('button', { name: 'dam Source 1 selected' })).toBeFalsy();
68
+ expect(screen.queryByRole('button', { name: 'dam Source 2' })).toBeFalsy();
69
+ });
70
+ });
71
+
72
+ it('Source menu closes on {esc}', async () => {
73
+ const sources = [mockSource({ name: 'Source 1' }), mockSource({ id: '2', name: 'Source 2' })];
74
+
75
+ render(<BrowseToSource sources={sources} selectedSource={sources[0]} onSourceSelect={() => {}} />);
76
+
77
+ const user = userEvent.setup();
78
+ const buttonDropdown = screen.getByRole('button', { name: 'Source quick select' });
79
+
80
+ await act(() => user.click(buttonDropdown));
81
+
82
+ await waitFor(() => {
83
+ expect(screen.queryByRole('button', { name: 'dam Source 1 selected' })).toBeTruthy();
84
+ expect(screen.queryByRole('button', { name: 'dam Source 2' })).toBeTruthy();
85
+ });
86
+
87
+ await act(() => userEvent.keyboard('{escape}'));
88
+
89
+ await waitFor(() => {
90
+ expect(screen.queryByRole('button', { name: 'dam Source 1 selected' })).toBeFalsy();
91
+ expect(screen.queryByRole('button', { name: 'dam Source 2' })).toBeFalsy();
92
+ });
93
+ });
94
+
95
+ it('Selecting node calls onSourceSelect ', async () => {
96
+ const onSourceSelect = jest.fn();
97
+
98
+ const sources = [mockSource({ name: 'Source 1' }), mockSource({ id: '2', name: 'Source 2' })];
99
+
100
+ render(<BrowseToSource sources={sources} selectedSource={sources[0]} onSourceSelect={onSourceSelect} />);
101
+
102
+ const user = userEvent.setup();
103
+
104
+ await act(() => user.click(screen.getByRole('button', { name: 'Source quick select' })));
105
+ await act(() => user.click(screen.getByRole('button', { name: 'dam Source 2' })));
106
+
107
+ await waitFor(() => {
108
+ expect(onSourceSelect).toHaveBeenCalledWith(sources[1]);
109
+ });
110
+ });
111
+ });
@@ -0,0 +1,29 @@
1
+ import React, { useState } from 'react';
2
+ import { StoryFn, Meta } from '@storybook/react';
3
+ import BrowseToSource from './BrowseToSource';
4
+ import sampleSources from '../__mocks__/sample-sources.json';
5
+ import { ResourceBrowserSource } from '../types';
6
+
7
+ export default {
8
+ title: 'Browse to source',
9
+ component: BrowseToSource,
10
+ } as Meta<typeof BrowseToSource>;
11
+
12
+ const Template: StoryFn<typeof BrowseToSource> = (props) => {
13
+ const sources = sampleSources as ResourceBrowserSource[];
14
+
15
+ const [selectedSource, setSelectedSource] = useState<ResourceBrowserSource>(sources[0]);
16
+
17
+ return (
18
+ <div className="m-3">
19
+ <BrowseToSource
20
+ selectedSource={selectedSource}
21
+ sources={sources}
22
+ onSourceSelect={(source) => setSelectedSource(source)}
23
+ ></BrowseToSource>
24
+ </div>
25
+ );
26
+ };
27
+
28
+ export const Primary = Template.bind({});
29
+ Primary.args = {};
@@ -0,0 +1,111 @@
1
+ import React, { useRef, useState } from 'react';
2
+
3
+ import { ResourceBrowserSource, PluginLaunchMode } from '../types';
4
+ import { Icon, IconOptions, uuid } from '@squiz/generic-browser-lib';
5
+ import { useFocusWithin, useKeyboard } from 'react-aria';
6
+ import { AdsClickIcon } from '../Icons/AdsClickIcon';
7
+ import { ArrowDownIcon } from '../Icons/ArrowDownIcon';
8
+
9
+ export default function BrowseToSource({
10
+ sources,
11
+ selectedSource,
12
+ onSourceSelect,
13
+ className = '',
14
+ }: {
15
+ sources: ResourceBrowserSource[];
16
+ selectedSource: ResourceBrowserSource;
17
+ onSourceSelect(source: ResourceBrowserSource, mode?: PluginLaunchMode): void;
18
+ className?: string;
19
+ }): React.JSX.Element {
20
+ const [uniqueId] = useState(uuid());
21
+ const buttonRef = useRef<HTMLButtonElement>(null);
22
+ const [isOpen, setIsOpen] = useState(false);
23
+
24
+ // Watch the focus and blur on the menu and close if focus leaves the control
25
+ const { focusWithinProps } = useFocusWithin({
26
+ onBlurWithin: () => {
27
+ setIsOpen(false);
28
+ },
29
+ });
30
+
31
+ // Listen for Esc key within this element
32
+ const { keyboardProps } = useKeyboard({
33
+ onKeyDown: (e) => {
34
+ if (isOpen && e.key === 'Escape') {
35
+ setIsOpen(false);
36
+ buttonRef.current?.focus(); // Restore focus to the element which opened the menu
37
+ }
38
+ },
39
+ });
40
+
41
+ const handleSourceClick = (source: ResourceBrowserSource) => {
42
+ setIsOpen(false);
43
+ buttonRef.current?.focus();
44
+ onSourceSelect(source);
45
+ };
46
+
47
+ if (!sources.length) {
48
+ return <></>;
49
+ }
50
+
51
+ return (
52
+ <div
53
+ className={`inline-block ${isOpen ? 'flex items-center border-2 border-gray-200 rounded-lg px-1 py-0.5 w-[350px]' : 'p-0'} ${className}`}
54
+ >
55
+ {isOpen && <AdsClickIcon aria-hidden className="ml-1 mr-2 shrink-0" />}
56
+ {sources.length > 1 && (
57
+ <div {...focusWithinProps} {...keyboardProps} className={`relative w-full ${isOpen && 'border-l border-gray-400 pl-1 '}`}>
58
+ <button
59
+ ref={buttonRef}
60
+ type="button"
61
+ aria-label="Source quick select"
62
+ aria-expanded={isOpen}
63
+ aria-controls={`${uniqueId}-button-menu`}
64
+ onClick={() => setIsOpen(!isOpen)}
65
+ className={`relative flex items-center p-2 w-full rounded-lg ${!isOpen && 'border-2 border-gray-200 hover:bg-gray-100'}`}
66
+ >
67
+ {isOpen ? (
68
+ <>
69
+ <span className="text-gray-700 text-md leading-5 mr-2">Browse to ...</span>
70
+ <ArrowDownIcon aria-hidden className="absolute right-3" />
71
+ </>
72
+ ) : (
73
+ <>
74
+ <AdsClickIcon aria-hidden className="ml-[6px] mr-2" />
75
+ <span className="text-gray-700 text-md font-semibold leading-5 mr-[6px]">Browse</span>
76
+ </>
77
+ )}
78
+ </button>
79
+ <ul
80
+ id={`${uniqueId}-button-menu`}
81
+ aria-hidden={!isOpen}
82
+ className={`absolute z-50 top-[calc(100%+8px)] left-1 w-[calc(100%-2px)] bg-gray-100 border-1 shadow-md rounded border-gray-300 p-2 pb-0 overflow-y-auto max-h-80 ${
83
+ !isOpen ? 'hidden' : ''
84
+ }`}
85
+ >
86
+ {sources.map((source) => {
87
+ const { id, name, type } = source;
88
+ const isSelectedSource = id === selectedSource.id;
89
+
90
+ return (
91
+ <li key={id} className="flex items-center text-sm font-semibold mb-2 bg-white rounded">
92
+ <button
93
+ type="button"
94
+ onClick={() => handleSourceClick(source)}
95
+ className={`relative grow flex items-center p-2 border-1 border-white rounded hover:bg-gray-50 hover:border-gray-300 focus:bg-gray-100`}
96
+ >
97
+ <Icon icon={type as IconOptions} aria-label={type} className="shrink-0 mr-2.5" />
98
+ <span className="text-left mr-7">{name}</span>
99
+ {isSelectedSource && (
100
+ <Icon icon={'selected' as IconOptions} aria-label="selected" className="absolute right-4" />
101
+ )}
102
+ </button>
103
+ </li>
104
+ );
105
+ })}
106
+ </ul>
107
+ </div>
108
+ )}
109
+ </div>
110
+ );
111
+ }
@@ -3,7 +3,7 @@ import { mockSource, mockPlugin } from '../__mocks__/MockModels';
3
3
  import { useSources, UseSourcesProps } from './useSources';
4
4
 
5
5
  describe('useSources', () => {
6
- it('Should trigger and load the sources', async () => {
6
+ it('Should trigger, load the sources and attach plugin reference to source', async () => {
7
7
  const sources = [mockSource()];
8
8
  const plugins = [mockPlugin()];
9
9
  const onRequestSources = jest.fn().mockResolvedValue(sources);
@@ -16,7 +16,11 @@ describe('useSources', () => {
16
16
  await waitFor(() => expect(result.current.isLoading).toBe(false));
17
17
 
18
18
  expect(result.current.isLoading).toBe(false);
19
- expect(result.current.data).toEqual(sources);
19
+ expect(result.current.data).toEqual(
20
+ sources.map((source) => {
21
+ return { ...source, plugin: plugins[0] };
22
+ }),
23
+ );
20
24
  });
21
25
 
22
26
  it('Should filters sources that plugins dont exist for', async () => {
@@ -32,7 +36,7 @@ describe('useSources', () => {
32
36
  await waitFor(() => expect(result.current.isLoading).toBe(false));
33
37
 
34
38
  expect(result.current.isLoading).toBe(false);
35
- expect(result.current.data).toEqual([sources[0]]);
39
+ expect(result.current.data).toEqual([{ ...sources[0], plugin: plugins[0] }]);
36
40
  expect(result.current.data.length).toEqual(1);
37
41
  });
38
42
 
@@ -50,7 +54,7 @@ describe('useSources', () => {
50
54
 
51
55
  await waitFor(() => expect(result.current.isLoading).toBe(false));
52
56
  expect(result.current.isLoading).toBe(false);
53
- expect(result.current.data).toEqual(sources);
57
+ expect(result.current.data).toEqual([{ ...sources[0], plugin: plugins[0] }]);
54
58
 
55
59
  // Wont go back to loading data (plugin keys havent changed)
56
60
  rerender({ onRequestSources, plugins: [mockPlugin()] });
@@ -1,4 +1,4 @@
1
- import { ResourceBrowserSource, ResourceBrowserPlugin } from '../types';
1
+ import { ResourceBrowserSource, ResourceBrowserPlugin, ResourceBrowserSourceWithPlugin } from '../types';
2
2
  import { useAsync } from '@squiz/generic-browser-lib';
3
3
 
4
4
  export type UseSourcesProps = {
@@ -14,28 +14,43 @@ export const useSources = ({ onRequestSources, plugins }: UseSourcesProps) => {
14
14
  return useAsync(
15
15
  {
16
16
  callback: () => {
17
- return new Promise<ResourceBrowserSource[]>((resolve, reject) => {
17
+ return new Promise<ResourceBrowserSourceWithPlugin[]>((resolve, reject) => {
18
18
  // Get all the sources from the outer context
19
19
  onRequestSources()
20
20
  .then((sources: ResourceBrowserSource[]) => {
21
21
  // Remove any sources which we don't have a plugin to handle
22
- resolve(
23
- sources.filter((source) => {
24
- return !!plugins.find((plugin) => {
25
- if (plugin.type === source.type) {
26
- return true;
27
- }
28
- return false;
29
- });
30
- }),
31
- );
22
+ const filteredSources = sources.filter((source) => {
23
+ return !!plugins.find((plugin) => {
24
+ if (plugin.type === source.type) {
25
+ return true;
26
+ }
27
+ return false;
28
+ });
29
+ });
30
+
31
+ // Attach the plugin to the source so it can be quickly used later
32
+ const combinedSources = filteredSources.map((source) => {
33
+ const plugin = plugins.find((plugin) => {
34
+ if (plugin.type === source.type) {
35
+ return true;
36
+ }
37
+ return false;
38
+ });
39
+
40
+ return {
41
+ ...source,
42
+ plugin,
43
+ } as ResourceBrowserSourceWithPlugin;
44
+ });
45
+
46
+ resolve(combinedSources);
32
47
  })
33
48
  .catch((e: unknown) => {
34
49
  reject(e);
35
50
  });
36
51
  });
37
52
  },
38
- defaultValue: [] as ResourceBrowserSource[],
53
+ defaultValue: [] as ResourceBrowserSourceWithPlugin[],
39
54
  },
40
55
  [onRequestSources, pluginsKey],
41
56
  );
@@ -0,0 +1,11 @@
1
+ import React, { SVGAttributes } from 'react';
2
+
3
+ type AdsClickIconProps = SVGAttributes<SVGElement>;
4
+
5
+ export const AdsClickIcon = (props: AdsClickIconProps) => {
6
+ return (
7
+ <svg fill="#4f4f4f" height="24" width="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
8
+ <path d="M11.56 17.845C8.38001 17.695 5.85001 15.075 5.85001 11.855C5.85001 8.54501 8.54001 5.85501 11.85 5.85501C15.07 5.85501 17.69 8.38501 17.84 11.565L15.74 10.935C15.33 9.16501 13.74 7.85501 11.85 7.85501C9.64001 7.85501 7.85001 9.64501 7.85001 11.855C7.85001 13.745 9.16001 15.335 10.93 15.745L11.56 17.845ZM21.85 11.855C21.85 12.155 21.84 12.455 21.81 12.755L19.84 12.165C19.85 12.065 19.85 11.955 19.85 11.855C19.85 7.43501 16.27 3.85501 11.85 3.85501C7.43001 3.85501 3.85001 7.43501 3.85001 11.855C3.85001 16.275 7.43001 19.855 11.85 19.855C11.95 19.855 12.06 19.855 12.16 19.845L12.75 21.815C12.45 21.845 12.15 21.855 11.85 21.855C6.33001 21.855 1.85001 17.375 1.85001 11.855C1.85001 6.33501 6.33001 1.85501 11.85 1.85501C17.37 1.85501 21.85 6.33501 21.85 11.855ZM18.08 16.115L20.35 15.355C20.81 15.205 20.8 14.545 20.34 14.405L12.74 12.125C12.36 12.015 12 12.365 12.12 12.745L14.4 20.345C14.54 20.815 15.2 20.825 15.35 20.355L16.11 18.085L20.02 21.995C20.22 22.195 20.53 22.195 20.73 21.995L22 20.725C22.2 20.525 22.2 20.215 22 20.015L18.08 16.115Z" />
9
+ </svg>
10
+ );
11
+ };
@@ -0,0 +1,11 @@
1
+ import React, { SVGAttributes } from 'react';
2
+
3
+ type ArrowDownIconProps = SVGAttributes<SVGElement>;
4
+
5
+ export const ArrowDownIcon = (props: ArrowDownIconProps) => {
6
+ return (
7
+ <svg fill="#4f4f4f" height="25" width="24" viewBox="0 0 24 25" xmlns="http://www.w3.org/2000/svg" {...props}>
8
+ <path d="M8.12459 9.49953L12.0046 13.3795L15.8846 9.49953C16.2746 9.10953 16.9046 9.10953 17.2946 9.49953C17.6846 9.88953 17.6846 10.5195 17.2946 10.9095L12.7046 15.4995C12.3146 15.8895 11.6846 15.8895 11.2946 15.4995L6.70459 10.9095C6.51734 10.7227 6.41211 10.469 6.41211 10.2045C6.41211 9.94001 6.51734 9.68636 6.70459 9.49953C7.09459 9.11953 7.73459 9.10953 8.12459 9.49953Z" />
9
+ </svg>
10
+ );
11
+ };