@squiz/resource-browser 3.1.0-rc.4 → 3.1.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 CHANGED
@@ -1,5 +1,29 @@
1
1
  # Change Log
2
2
 
3
+ ## 3.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 1ce2636: dont render select preview if switched to different source
8
+
9
+ ## 3.1.0
10
+
11
+ ### Patch Changes
12
+
13
+ - a89fb2d: Updated the peer dependencies and dependencies so that we use the deps published by Matrix'
14
+ - a89fb2d: Updated title for modal to be sentence case when displayed
15
+ - a89fb2d: Changed background colour of hovered source on source selector
16
+ - a89fb2d: Updated modal to use sds button, removed scroll bar from search panel
17
+ - 6503eba: clean selected mode on modal close
18
+ - a89fb2d: Version control via changesets added
19
+ - Updated dependencies [70ed1a8]
20
+ - Updated dependencies [9bcc4d2]
21
+ - Updated dependencies [a89fb2d]
22
+ - Updated dependencies [a065ca7]
23
+ - Updated dependencies [7a6a6fc]
24
+ - Updated dependencies [a89fb2d]
25
+ - @squiz/resource-browser-ui-lib@1.0.0
26
+
3
27
  ## 3.1.0-rc.4
4
28
 
5
29
  ### Patch Changes
@@ -1,15 +1,11 @@
1
1
  import React from 'react';
2
- import { ResourceBrowserPlugin, ResourceBrowserSource, ResourceBrowserUnresolvedResource, ResourceBrowserResource, ResourceBrowserSourceWithPlugin, PluginLaunchMode } from '../types';
2
+ import { ResourceBrowserPlugin, ResourceBrowserSource, ResourceBrowserUnresolvedResource, ResourceBrowserResource, ResourceBrowserSourceWithPlugin, PluginLaunchMode, useResolveResourceResponse } from '../types';
3
3
  export type ResourceBrowserInputProps = {
4
4
  modalTitle: string;
5
5
  allowedTypes?: string[];
6
6
  isDisabled?: boolean;
7
7
  value: ResourceBrowserUnresolvedResource | null;
8
- useResource(referenceId: string | null, source: ResourceBrowserSource | null): {
9
- data: ResourceBrowserResource | null;
10
- error: Error | null;
11
- isLoading: boolean;
12
- };
8
+ useResource(referenceId: string | null, source: ResourceBrowserSource | null): useResolveResourceResponse;
13
9
  onChange(resource: ResourceBrowserResource | null): void;
14
10
  onClear?(): void;
15
11
  plugin: ResourceBrowserPlugin | null;
@@ -18,9 +14,10 @@ export type ResourceBrowserInputProps = {
18
14
  source: ResourceBrowserSource | null;
19
15
  sources: ResourceBrowserSourceWithPlugin[];
20
16
  isLoading: boolean;
17
+ isOtherSourceValue: boolean;
21
18
  error: Error | null;
22
19
  setSource(source: ResourceBrowserSource, mode?: PluginLaunchMode): void;
23
20
  isModalOpen: boolean;
24
21
  onModalStateChange(isOpen: boolean): void;
25
22
  };
26
- export declare const ResourceBrowserInput: ({ modalTitle, allowedTypes, onChange, value, useResource, isDisabled, onClear, plugin, pluginMode, searchEnabled, source, sources, isLoading, error, setSource, isModalOpen, onModalStateChange, }: ResourceBrowserInputProps) => React.JSX.Element;
23
+ export declare const ResourceBrowserInput: ({ modalTitle, allowedTypes, onChange, value, useResource, isDisabled, onClear, plugin, pluginMode, searchEnabled, source, sources, isLoading, isOtherSourceValue, error, setSource, isModalOpen, onModalStateChange, }: ResourceBrowserInputProps) => React.JSX.Element;
@@ -7,10 +7,10 @@ exports.ResourceBrowserInput = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const MainContainer_1 = __importDefault(require("../MainContainer/MainContainer"));
9
9
  const ResourcePicker_1 = require("../ResourcePicker/ResourcePicker");
10
- const ResourceBrowserInput = ({ modalTitle, allowedTypes, onChange, value, useResource, isDisabled, onClear, plugin, pluginMode, searchEnabled, source, sources, isLoading, error, setSource, isModalOpen, onModalStateChange, }) => {
10
+ const ResourceBrowserInput = ({ modalTitle, allowedTypes, onChange, value, useResource, isDisabled, onClear, plugin, pluginMode, searchEnabled, source, sources, isLoading, isOtherSourceValue, error, setSource, isModalOpen, onModalStateChange, }) => {
11
11
  const { data: resource, error: resourceError, isLoading: isResourceLoading } = useResource(value?.resourceId || null, source);
12
12
  const defaultOnClear = () => onChange(null);
13
13
  const onClearFunction = onClear ?? defaultOnClear;
14
- return (react_1.default.createElement(ResourcePicker_1.ResourcePicker, { resource: resource, plugin: plugin, allowedTypes: allowedTypes, error: resourceError || error, isLoading: isResourceLoading || isLoading, isDisabled: isDisabled, onClear: onClearFunction, isModalOpen: isModalOpen, onModalStateChange: onModalStateChange }, (onClose, titleProps) => (react_1.default.createElement(MainContainer_1.default, { selectedSource: source, sources: sources, preselectedResource: resource, plugin: plugin, pluginMode: pluginMode, searchEnabled: searchEnabled, title: modalTitle, titleAriaProps: titleProps, allowedTypes: allowedTypes, onSourceSelect: setSource, onClose: onClose, onChange: onChange }))));
14
+ return (react_1.default.createElement(ResourcePicker_1.ResourcePicker, { resource: resource, plugin: plugin, allowedTypes: allowedTypes, error: resourceError || error, isLoading: isResourceLoading || isLoading, isOtherSourceValue: isOtherSourceValue, isDisabled: isDisabled, onClear: onClearFunction, isModalOpen: isModalOpen, onModalStateChange: onModalStateChange }, (onClose, titleProps) => (react_1.default.createElement(MainContainer_1.default, { selectedSource: source, sources: sources, preselectedResource: resource, plugin: plugin, pluginMode: pluginMode, searchEnabled: searchEnabled, title: modalTitle, titleAriaProps: titleProps, allowedTypes: allowedTypes, onSourceSelect: setSource, onClose: onClose, onChange: onChange }))));
15
15
  };
16
16
  exports.ResourceBrowserInput = ResourceBrowserInput;
@@ -7,10 +7,11 @@ export type ResourcePickerProps = {
7
7
  allowedTypes: string[] | undefined;
8
8
  error: Error | null;
9
9
  isLoading: boolean;
10
+ isOtherSourceValue: boolean;
10
11
  isDisabled?: boolean;
11
12
  children: (onClose: () => void, titleProps: DOMAttributes) => React.ReactElement;
12
13
  onClear: () => void;
13
14
  isModalOpen: boolean;
14
15
  onModalStateChange(isOpen: boolean): void;
15
16
  };
16
- export declare const ResourcePicker: ({ resource, plugin, allowedTypes, error: externalError, isLoading: isExternalLoading, isDisabled, children, onClear, isModalOpen, onModalStateChange, }: ResourcePickerProps) => React.JSX.Element;
17
+ export declare const ResourcePicker: ({ resource, plugin, allowedTypes, error: externalError, isLoading: isExternalLoading, isOtherSourceValue, isDisabled, children, onClear, isModalOpen, onModalStateChange, }: ResourcePickerProps) => React.JSX.Element;
@@ -14,16 +14,26 @@ const Loading_1 = require("./States/Loading");
14
14
  const Selected_1 = require("./States/Selected");
15
15
  const useSelectedState_1 = require("../Hooks/useSelectedState");
16
16
  const clsx_1 = __importDefault(require("clsx"));
17
- const ResourcePicker = ({ resource, plugin, allowedTypes, error: externalError, isLoading: isExternalLoading, isDisabled, children, onClear, isModalOpen, onModalStateChange, }) => {
17
+ const ResourcePicker = ({ resource, plugin, allowedTypes, error: externalError, isLoading: isExternalLoading, isOtherSourceValue, isDisabled, children, onClear, isModalOpen, onModalStateChange, }) => {
18
18
  const { data: selectedState, error, isLoading } = (0, useSelectedState_1.useSelectedState)({ resource, plugin });
19
19
  const isImagePicker = allowedTypes && allowedTypes.length === 1 && allowedTypes.includes('image');
20
20
  const isEmpty = resource === null && !isExternalLoading && !externalError;
21
21
  return (react_1.default.createElement("div", { className: (0, clsx_1.default)('resource-picker', isDisabled && 'resource-picker--disabled') },
22
22
  isImagePicker ? (react_1.default.createElement(PhotoLibraryRounded_1.default, { "aria-hidden": true, className: "w-6 h-6" })) : (react_1.default.createElement(AdsClickRounded_1.default, { "aria-hidden": true, className: "w-6 h-6" })),
23
- isEmpty ? (react_1.default.createElement(resource_browser_ui_lib_1.ModalTrigger, { overlayTriggerState: {
23
+ isOtherSourceValue && (
24
+ // User is selecting a new image from a different source, render a loading spinner as we can't resolve the value
25
+ react_1.default.createElement("div", { className: "resource-picker-info" },
26
+ react_1.default.createElement("div", { className: "resource-picker-info__layout" },
27
+ react_1.default.createElement(Loading_1.LoadingState, null),
28
+ react_1.default.createElement(resource_browser_ui_lib_1.ModalTrigger, { overlayTriggerState: {
29
+ isOpen: isModalOpen,
30
+ onOpenChange: onModalStateChange,
31
+ }, showLabel: false, label: "", isDisabled: true, scope: "squiz-rb-scope" }, children)))),
32
+ isEmpty && !isOtherSourceValue && (react_1.default.createElement(resource_browser_ui_lib_1.ModalTrigger, { overlayTriggerState: {
24
33
  isOpen: isModalOpen,
25
34
  onOpenChange: onModalStateChange,
26
- }, showLabel: true, label: isImagePicker ? `Choose image` : `Choose asset`, icon: react_1.default.createElement(AddCircleOutlineRounded_1.default, { "aria-hidden": true, className: `!w-4 !h-4 text-blue-300 ${isDisabled ? 'text-gray-600' : ''}` }), isDisabled: isDisabled, scope: "squiz-rb-scope" }, children)) : (react_1.default.createElement("div", { className: "resource-picker-info" },
35
+ }, showLabel: true, label: isImagePicker ? `Choose image` : `Choose asset`, icon: react_1.default.createElement(AddCircleOutlineRounded_1.default, { "aria-hidden": true, className: `!w-4 !h-4 text-blue-300 ${isDisabled ? 'text-gray-600' : ''}` }), isDisabled: isDisabled, scope: "squiz-rb-scope" }, children)),
36
+ !isEmpty && !isOtherSourceValue && (react_1.default.createElement("div", { className: "resource-picker-info" },
27
37
  react_1.default.createElement("div", { className: "resource-picker-info__layout" },
28
38
  (isExternalLoading || isLoading) && react_1.default.createElement(Loading_1.LoadingState, null),
29
39
  (externalError || error) && (react_1.default.createElement(Error_1.ErrorState, { error: externalError || error, isDisabled: isDisabled, onClear: onClear })),
package/lib/index.css CHANGED
@@ -5724,7 +5724,7 @@
5724
5724
  }
5725
5725
  .squiz-rb-scope .selection-list__item:not(.squiz-rb-plugin *) {
5726
5726
  display: flex;
5727
- margin: 0.5rem;
5727
+ margin: 0.25rem 0.5rem;
5728
5728
  border-bottom: 1px solid #e0e0e0;
5729
5729
  }
5730
5730
  .squiz-rb-scope .selection-list__item:last-child:not(.squiz-rb-plugin *) {
@@ -5732,6 +5732,7 @@
5732
5732
  }
5733
5733
  .squiz-rb-scope .selection-list__item-button:not(.squiz-rb-plugin *) {
5734
5734
  width: 100%;
5735
+ margin-bottom: 0.25rem;
5735
5736
  padding: 0.25rem 0.5rem;
5736
5737
  border: none;
5737
5738
  background: none;
@@ -5744,6 +5745,12 @@
5744
5745
  .squiz-rb-scope .selection-list__item-button:focus:not(.squiz-rb-plugin *) {
5745
5746
  background-color: #f5f5f5;
5746
5747
  }
5748
+ .squiz-rb-scope .selection-list__item--selected path:not(.squiz-rb-plugin *) {
5749
+ fill: #044985 !important;
5750
+ }
5751
+ .squiz-rb-scope .selection-list__item--selected .sds-text:not(.squiz-rb-plugin *) {
5752
+ color: #044985 !important;
5753
+ }
5747
5754
  .squiz-rb-scope .selection-list__item--selected .selection-list__item-button:not(.squiz-rb-plugin *) {
5748
5755
  background-color: #e6f1fa;
5749
5756
  }
@@ -5963,7 +5970,10 @@
5963
5970
  fill: #4f4f4f;
5964
5971
  }
5965
5972
  .squiz-rb-scope .searchListing:not(.squiz-rb-plugin *) {
5966
- padding: 1.25rem 1rem;
5973
+ padding: 1.25rem 0 1.25rem 1rem;
5974
+ }
5975
+ .squiz-rb-scope .searchListing__header:not(.squiz-rb-plugin *) {
5976
+ padding-right: 16px;
5967
5977
  }
5968
5978
  .squiz-rb-scope .searchListing__title-group:not(.squiz-rb-plugin *) {
5969
5979
  padding-bottom: 1rem;
@@ -6106,6 +6116,143 @@
6106
6116
  justify-content: center;
6107
6117
  align-items: center;
6108
6118
  }
6119
+ .squiz-rb-scope .image-info:not(.squiz-rb-plugin *) {
6120
+ align-items: center;
6121
+ border-top: 1px solid #e0e0e0;
6122
+ color: #707070;
6123
+ display: flex;
6124
+ flex-direction: column;
6125
+ height: calc(50vh - 60px);
6126
+ overflow-y: auto;
6127
+ }
6128
+ .squiz-rb-scope .image-info__image-container:not(.squiz-rb-plugin *) {
6129
+ position: relative;
6130
+ width: 250px;
6131
+ height: 150px;
6132
+ margin: 1.25rem;
6133
+ background-color: #ededed;
6134
+ }
6135
+ .squiz-rb-scope .checkered-bg:not(.squiz-rb-plugin *) {
6136
+ --tw-bg-opacity: 1;
6137
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
6138
+ background-size: 24px 24px;
6139
+ background-position: 0 0, 12px 12px;
6140
+ background-image:
6141
+ linear-gradient(
6142
+ 45deg,
6143
+ #e0e0e0 25%,
6144
+ transparent 25%,
6145
+ transparent 75%,
6146
+ #e0e0e0 75%,
6147
+ #e0e0e0),
6148
+ linear-gradient(
6149
+ 45deg,
6150
+ #e0e0e0 25%,
6151
+ transparent 25%,
6152
+ transparent 75%,
6153
+ #e0e0e0 75%,
6154
+ #e0e0e0);
6155
+ }
6156
+ .squiz-rb-scope .image-info__image-container img:not(.squiz-rb-plugin *) {
6157
+ position: absolute;
6158
+ top: 0;
6159
+ right: 0;
6160
+ bottom: 0;
6161
+ left: 0;
6162
+ max-width: 100%;
6163
+ max-height: 100%;
6164
+ margin: auto;
6165
+ }
6166
+ .squiz-rb-scope .image-info__title:not(.squiz-rb-plugin *) {
6167
+ margin: 1rem 0;
6168
+ color: #3d3d3d;
6169
+ }
6170
+ .squiz-rb-scope .image-info__details:not(.squiz-rb-plugin *) {
6171
+ margin: 1rem 0;
6172
+ color: #707070;
6173
+ }
6174
+ .squiz-rb-scope .image-info__details-list:not(.squiz-rb-plugin *) {
6175
+ display: flex;
6176
+ flex-direction: column;
6177
+ margin: 1rem 0;
6178
+ }
6179
+ .squiz-rb-scope .image-info__details-item:not(.squiz-rb-plugin *) {
6180
+ display: flex;
6181
+ margin-bottom: 0.5rem;
6182
+ }
6183
+ .squiz-rb-scope .image-info__details-label:not(.squiz-rb-plugin *) {
6184
+ color: #707070;
6185
+ width: 60px;
6186
+ display: flex;
6187
+ }
6188
+ .squiz-rb-scope .image-info__details-value:not(.squiz-rb-plugin *) {
6189
+ color: #3d3d3d;
6190
+ }
6191
+ .squiz-rb-scope .image-variant__list:not(.squiz-rb-plugin *) {
6192
+ width: 250px;
6193
+ margin-bottom: 1rem;
6194
+ }
6195
+ .squiz-rb-scope .image-variant__item:not(.squiz-rb-plugin *) {
6196
+ display: flex;
6197
+ flex-direction: row;
6198
+ align-items: center;
6199
+ width: 100%;
6200
+ justify-content: space-between;
6201
+ }
6202
+ .squiz-rb-scope .selection-list__item--selected .image-variant__checkmark:not(.squiz-rb-plugin *) {
6203
+ visibility: visible;
6204
+ }
6205
+ .squiz-rb-scope .image-variant__checkmark:not(.squiz-rb-plugin *) {
6206
+ visibility: hidden;
6207
+ margin-left: 0.5rem;
6208
+ }
6209
+ .squiz-rb-scope .image-variant__checkmark path:not(.squiz-rb-plugin *) {
6210
+ fill: #044985;
6211
+ }
6212
+ .squiz-rb-scope .image-variant__info:not(.squiz-rb-plugin *) {
6213
+ display: flex;
6214
+ align-items: center;
6215
+ color: #3d3d3d;
6216
+ }
6217
+ .squiz-rb-scope .image-info__footer:not(.squiz-rb-plugin *) {
6218
+ margin-top: auto;
6219
+ align-self: stretch;
6220
+ display: flex;
6221
+ flex-direction: column;
6222
+ align-items: flex-end;
6223
+ }
6224
+ .squiz-rb-scope .image-info__select-button:not(.squiz-rb-plugin *) {
6225
+ margin: 1.25rem;
6226
+ }
6227
+ .squiz-rb-scope .image-info__select:not(.squiz-rb-plugin *) {
6228
+ padding-top: 1.5rem;
6229
+ padding-bottom: 1rem;
6230
+ }
6231
+ .squiz-rb-scope .image-info__select svg:not(.squiz-rb-plugin *) {
6232
+ height: 72px;
6233
+ width: 72px;
6234
+ }
6235
+ .squiz-rb-scope .image-info__select svg path:not(.squiz-rb-plugin *) {
6236
+ fill: #bababa;
6237
+ }
6238
+ .squiz-rb-scope .image-info__empty-text:not(.squiz-rb-plugin *) {
6239
+ padding-left: 1.25rem;
6240
+ padding-right: 1.25rem;
6241
+ }
6242
+ .squiz-rb-scope .image-info__loading:not(.squiz-rb-plugin *) {
6243
+ margin-top: 80px;
6244
+ }
6245
+ @media (min-width: 768px) {
6246
+ .squiz-rb-scope .image-info:not(.squiz-rb-plugin *) {
6247
+ height: calc(100vh - 60px);
6248
+ }
6249
+ }
6250
+ @media (min-width: 1024px) {
6251
+ .squiz-rb-scope .image-info:not(.squiz-rb-plugin *) {
6252
+ height: calc(100vh - 14rem);
6253
+ overflow-y: auto;
6254
+ }
6255
+ }
6109
6256
  .squiz-rb-scope .resource-picker:not(.squiz-rb-plugin *) {
6110
6257
  display: grid;
6111
6258
  grid-template-columns: 24px 1fr;
package/lib/index.js CHANGED
@@ -98,12 +98,19 @@ const ResourceBrowser = (props) => {
98
98
  }, [setIsModalOpen]);
99
99
  // If the modal closes and we dont have a value clear the source state so it goes back to the launcher on re-open
100
100
  (0, react_1.useEffect)(() => {
101
+ // If modal is closed and we dont have a value
101
102
  if (!isModalOpen && !value && (sources?.length > 1 || searchEnabled)) {
102
103
  setSource(null);
103
104
  setMode(null);
104
105
  }
105
106
  else if (!isModalOpen) {
107
+ // Otherwise reset the RB mode
106
108
  setMode(null);
109
+ // If there is a value passed in, reset the source so the preselected asset preview renders correctly
110
+ if (value && value.sourceId !== source?.id) {
111
+ const source = sources.find((source) => source.id === value?.sourceId) || null;
112
+ setSource(source);
113
+ }
107
114
  }
108
115
  }, [sources, isModalOpen]);
109
116
  // Reset function to allow user manual reload if sources fail in inline usage
@@ -112,7 +119,7 @@ const ResourceBrowser = (props) => {
112
119
  }, [reloadSources]);
113
120
  // Render a default "plugin" and one for each item in the plugins array. They are conditionally rendered based on what is selected
114
121
  return (react_1.default.createElement("div", { className: "squiz-rb-scope" },
115
- react_1.default.createElement(Plugin_1.PluginRender, { key: "default", render: plugin === null, inline: !!inline, inlineType: inlineType, ...props, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: () => {
122
+ react_1.default.createElement(Plugin_1.PluginRender, { key: "default", render: plugin === null, inline: !!inline, inlineType: inlineType, ...props, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, isOtherSourceValue: false, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: () => {
116
123
  return {
117
124
  data: null,
118
125
  error: null,
@@ -120,7 +127,7 @@ const ResourceBrowser = (props) => {
120
127
  };
121
128
  }, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange, onRetry: handleReset }),
122
129
  plugins.map((thisPlugin) => {
123
- return (react_1.default.createElement(Plugin_1.PluginRender, { key: thisPlugin.type, render: thisPlugin === plugin, inline: !!inline, inlineType: inlineType, ...props, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: thisPlugin.useResolveResource, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange, onRetry: handleReset }));
130
+ return (react_1.default.createElement(Plugin_1.PluginRender, { key: thisPlugin.type, render: thisPlugin === plugin, inline: !!inline, inlineType: inlineType, ...props, value: value && source ? (value.sourceId === source.id ? value : null) : null, isOtherSourceValue: value && source ? (value.sourceId !== source.id ? true : false) : false, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: thisPlugin.useResolveResource, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange, onRetry: handleReset }));
124
131
  })));
125
132
  };
126
133
  exports.ResourceBrowser = ResourceBrowser;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/resource-browser",
3
- "version": "3.1.0-rc.4",
3
+ "version": "3.1.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.0.0-rc.8",
31
+ "@squiz/resource-browser-ui-lib": "^1.0.0",
32
32
  "clsx": "^2.1.0",
33
33
  "expiry-map": "^2.0.0",
34
34
  "p-memoize": "^4.0.4",
@@ -21,6 +21,7 @@ describe('Plugin', () => {
21
21
  const props = {
22
22
  modalTitle: 'Asset picker',
23
23
  value: null,
24
+ isOtherSourceValue: false,
24
25
  onChange: jest.fn(),
25
26
  onClear: jest.fn(),
26
27
  useResource: () => {
@@ -53,6 +54,7 @@ describe('Plugin', () => {
53
54
  const props = {
54
55
  modalTitle: 'Asset picker',
55
56
  value: null,
57
+ isOtherSourceValue: false,
56
58
  onChange: jest.fn(),
57
59
  onClear: jest.fn(),
58
60
  useResource: () => {
@@ -22,6 +22,7 @@ describe('Resource browser input', () => {
22
22
  <ResourceBrowserInput
23
23
  modalTitle="Asset picker"
24
24
  value={null}
25
+ isOtherSourceValue={false}
25
26
  useResource={mockUseResource}
26
27
  onChange={mockChange}
27
28
  plugin={null}
@@ -8,6 +8,7 @@ import {
8
8
  ResourceBrowserResource,
9
9
  ResourceBrowserSourceWithPlugin,
10
10
  PluginLaunchMode,
11
+ useResolveResourceResponse,
11
12
  } from '../types';
12
13
 
13
14
  export type ResourceBrowserInputProps = {
@@ -15,14 +16,7 @@ export type ResourceBrowserInputProps = {
15
16
  allowedTypes?: string[];
16
17
  isDisabled?: boolean;
17
18
  value: ResourceBrowserUnresolvedResource | null;
18
- useResource(
19
- referenceId: string | null,
20
- source: ResourceBrowserSource | null,
21
- ): {
22
- data: ResourceBrowserResource | null;
23
- error: Error | null;
24
- isLoading: boolean;
25
- };
19
+ useResource(referenceId: string | null, source: ResourceBrowserSource | null): useResolveResourceResponse;
26
20
  onChange(resource: ResourceBrowserResource | null): void;
27
21
  onClear?(): void;
28
22
  plugin: ResourceBrowserPlugin | null;
@@ -31,6 +25,7 @@ export type ResourceBrowserInputProps = {
31
25
  source: ResourceBrowserSource | null;
32
26
  sources: ResourceBrowserSourceWithPlugin[];
33
27
  isLoading: boolean;
28
+ isOtherSourceValue: boolean;
34
29
  error: Error | null;
35
30
  setSource(source: ResourceBrowserSource, mode?: PluginLaunchMode): void;
36
31
  isModalOpen: boolean;
@@ -51,6 +46,7 @@ export const ResourceBrowserInput = ({
51
46
  source,
52
47
  sources,
53
48
  isLoading,
49
+ isOtherSourceValue,
54
50
  error,
55
51
  setSource,
56
52
  isModalOpen,
@@ -68,6 +64,7 @@ export const ResourceBrowserInput = ({
68
64
  allowedTypes={allowedTypes}
69
65
  error={resourceError || error}
70
66
  isLoading={isResourceLoading || isLoading}
67
+ isOtherSourceValue={isOtherSourceValue}
71
68
  isDisabled={isDisabled}
72
69
  onClear={onClearFunction}
73
70
  isModalOpen={isModalOpen}
@@ -10,6 +10,7 @@ const defaultProps: any = {
10
10
  allowedTypes: undefined,
11
11
  isLoading: false,
12
12
  isError: false,
13
+ isOtherSourceValue: false,
13
14
  };
14
15
 
15
16
  describe('Resource picker', () => {
@@ -36,6 +37,15 @@ describe('Resource picker', () => {
36
37
  expect(loadingLabel).toBeInTheDocument();
37
38
  });
38
39
 
40
+ it('should render the loading state if isOtherSourceValue is set to true', () => {
41
+ render(<ResourcePicker {...defaultProps} isLoading={false} isOtherSourceValue={true} />);
42
+ const pickerLabel = screen.queryByText('Choose image');
43
+ const loadingLabel = screen.queryByLabelText('Loading selection');
44
+
45
+ expect(pickerLabel).not.toBeInTheDocument();
46
+ expect(loadingLabel).toBeInTheDocument();
47
+ });
48
+
39
49
  it('should render the error state if set to true', () => {
40
50
  const errorMessage = 'Failed to retrieve asset info due to a Component Service API key problem.';
41
51
 
@@ -18,6 +18,7 @@ export type ResourcePickerProps = {
18
18
  allowedTypes: string[] | undefined;
19
19
  error: Error | null;
20
20
  isLoading: boolean;
21
+ isOtherSourceValue: boolean;
21
22
  isDisabled?: boolean;
22
23
  children: (onClose: () => void, titleProps: DOMAttributes) => React.ReactElement;
23
24
  onClear: () => void;
@@ -31,6 +32,7 @@ export const ResourcePicker = ({
31
32
  allowedTypes,
32
33
  error: externalError,
33
34
  isLoading: isExternalLoading,
35
+ isOtherSourceValue,
34
36
  isDisabled,
35
37
  children,
36
38
  onClear,
@@ -49,7 +51,28 @@ export const ResourcePicker = ({
49
51
  ) : (
50
52
  <AdsClickRoundedIcon aria-hidden className="w-6 h-6" />
51
53
  )}
52
- {isEmpty ? (
54
+ {isOtherSourceValue && (
55
+ // User is selecting a new image from a different source, render a loading spinner as we can't resolve the value
56
+ <div className="resource-picker-info">
57
+ <div className="resource-picker-info__layout">
58
+ <LoadingState />
59
+ {/* Need to render the ModalTrigger still as it renders the <Modal/> */}
60
+ <ModalTrigger
61
+ overlayTriggerState={{
62
+ isOpen: isModalOpen,
63
+ onOpenChange: onModalStateChange,
64
+ }}
65
+ showLabel={false}
66
+ label=""
67
+ isDisabled={true}
68
+ scope="squiz-rb-scope"
69
+ >
70
+ {children}
71
+ </ModalTrigger>
72
+ </div>
73
+ </div>
74
+ )}
75
+ {isEmpty && !isOtherSourceValue && (
53
76
  <ModalTrigger
54
77
  overlayTriggerState={{
55
78
  isOpen: isModalOpen,
@@ -68,7 +91,8 @@ export const ResourcePicker = ({
68
91
  >
69
92
  {children}
70
93
  </ModalTrigger>
71
- ) : (
94
+ )}
95
+ {!isEmpty && !isOtherSourceValue && (
72
96
  <div className="resource-picker-info">
73
97
  <div className="resource-picker-info__layout">
74
98
  {(isExternalLoading || isLoading) && <LoadingState />}
@@ -93,6 +93,7 @@ export const createPlugins = (callbackWait: number, headerPortal = false, resour
93
93
  className="col-start-2 col-end-2 flex flex-row gap-1 justify-self-start items-center font-normal text-sm"
94
94
  >
95
95
  <div>
96
+ <span>{type} plugin</span>
96
97
  <dt className="hidden">Asset size</dt>
97
98
  <dd className="text-gray-600">
98
99
  {resource.squizImage && (
@@ -147,6 +148,9 @@ export const createPlugins = (callbackWait: number, headerPortal = false, resour
147
148
  },
148
149
  });
149
150
  }, callbackWait);
151
+ } else {
152
+ setResource(null);
153
+ setIsLoading(false);
150
154
  }
151
155
  }, [unresolvedResource, setResource, setIsLoading]);
152
156
 
@@ -154,7 +154,7 @@ describe('Resource browser input', () => {
154
154
  });
155
155
  });
156
156
 
157
- it('onSourceSelect will set mode is provided', async () => {
157
+ it('onSourceSelect will set mode if provided', async () => {
158
158
  const sourcesInput = [mockSource({ type: 'dam' }), mockSource()];
159
159
  const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
160
160
  mockRequestSources.mockResolvedValueOnce(sourcesInput);
@@ -214,6 +214,49 @@ describe('Resource browser input', () => {
214
214
  });
215
215
  });
216
216
 
217
+ it('Value will only be passed to ResourceBrowserInput if value source matches currently selected source', async () => {
218
+ const sourcesInput = [mockSource({ id: '1' }), mockSource({ id: '2' })];
219
+ const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
220
+ mockRequestSources.mockResolvedValueOnce(sourcesInput);
221
+
222
+ const value = { sourceId: sourcesInput[0].id, resourceId: '123456' };
223
+
224
+ renderComponent({ value });
225
+
226
+ await waitFor(() => {
227
+ expect(RBI.ResourceBrowserInput).toHaveBeenLastCalledWith(
228
+ expect.objectContaining({
229
+ value,
230
+ isOtherSourceValue: false,
231
+ source: calculatedSources[0],
232
+ }),
233
+ {},
234
+ );
235
+ });
236
+
237
+ const { onModalStateChange, setSource } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
238
+ // Invoke open the modal
239
+ act(() => {
240
+ onModalStateChange(true);
241
+ });
242
+
243
+ // Change the source
244
+ act(() => {
245
+ setSource(calculatedSources[1]);
246
+ });
247
+
248
+ await waitFor(() => {
249
+ expect(RBI.ResourceBrowserInput).toHaveBeenLastCalledWith(
250
+ expect.objectContaining({
251
+ value: null,
252
+ isOtherSourceValue: true,
253
+ source: calculatedSources[1],
254
+ }),
255
+ {},
256
+ );
257
+ });
258
+ });
259
+
217
260
  it('onModalStateChange called with false will reset the Source and Plugin', async () => {
218
261
  const sourcesInput = [mockSource({ type: 'dam' }), mockSource()];
219
262
  const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
@@ -252,7 +295,13 @@ describe('Resource browser input', () => {
252
295
 
253
296
  // Invoke modal close callback
254
297
  const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
255
- onModalStateChange(false);
298
+ // Invoke open and close the modal
299
+ act(() => {
300
+ onModalStateChange(true);
301
+ });
302
+ act(() => {
303
+ onModalStateChange(false);
304
+ });
256
305
 
257
306
  // Expect no source or plugin
258
307
  await waitFor(() => {
@@ -286,6 +335,9 @@ describe('Resource browser input', () => {
286
335
 
287
336
  // Invoke modal close callback
288
337
  const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
338
+ act(() => {
339
+ onModalStateChange(true);
340
+ });
289
341
  act(() => {
290
342
  onModalStateChange(false);
291
343
  });
@@ -339,7 +391,13 @@ describe('Resource browser input', () => {
339
391
 
340
392
  // Invoke modal close callback
341
393
  const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
342
- onModalStateChange(false);
394
+ // Invoke open and close the modal
395
+ act(() => {
396
+ onModalStateChange(true);
397
+ });
398
+ act(() => {
399
+ onModalStateChange(false);
400
+ });
343
401
 
344
402
  // Expect no source or plugin
345
403
  await waitFor(() => {
@@ -355,8 +413,8 @@ describe('Resource browser input', () => {
355
413
  });
356
414
  });
357
415
 
358
- it('onModalStateChange called with false will not reset the Source and Plugin if a value exists', async () => {
359
- const sourcesInput = [mockSource({ type: 'dam' }), mockSource()];
416
+ it('onModalStateChange called with false will reset the Source and Plugin to the value defaults if a value exists', async () => {
417
+ const sourcesInput = [mockSource({ id: '1' }), mockSource({ id: '2' })];
360
418
  const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
361
419
  mockRequestSources.mockResolvedValueOnce(sourcesInput);
362
420
 
@@ -373,8 +431,27 @@ describe('Resource browser input', () => {
373
431
  );
374
432
  });
375
433
 
376
- // Invoke modal close callback
377
- const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
434
+ const { onModalStateChange, setSource } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
435
+ // Invoke open and close the modal
436
+ act(() => {
437
+ onModalStateChange(true);
438
+ });
439
+
440
+ // Change the source
441
+ act(() => {
442
+ setSource(calculatedSources[1]);
443
+ });
444
+
445
+ await waitFor(() => {
446
+ expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
447
+ expect.objectContaining({
448
+ source: calculatedSources[1],
449
+ plugin: mockDamPlugin,
450
+ }),
451
+ {},
452
+ );
453
+ });
454
+
378
455
  act(() => {
379
456
  onModalStateChange(false);
380
457
  });
@@ -411,6 +488,9 @@ describe('Resource browser input', () => {
411
488
 
412
489
  // Invoke modal close callback
413
490
  const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
491
+ act(() => {
492
+ onModalStateChange(true);
493
+ });
414
494
  act(() => {
415
495
  onModalStateChange(false);
416
496
  });
package/src/index.tsx CHANGED
@@ -103,11 +103,19 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
103
103
 
104
104
  // If the modal closes and we dont have a value clear the source state so it goes back to the launcher on re-open
105
105
  useEffect(() => {
106
+ // If modal is closed and we dont have a value
106
107
  if (!isModalOpen && !value && (sources?.length > 1 || searchEnabled)) {
107
108
  setSource(null);
108
109
  setMode(null);
109
110
  } else if (!isModalOpen) {
111
+ // Otherwise reset the RB mode
110
112
  setMode(null);
113
+
114
+ // If there is a value passed in, reset the source so the preselected asset preview renders correctly
115
+ if (value && value.sourceId !== source?.id) {
116
+ const source = sources.find((source) => source.id === value?.sourceId) || null;
117
+ setSource(source);
118
+ }
111
119
  }
112
120
  }, [sources, isModalOpen]);
113
121
 
@@ -129,6 +137,7 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
129
137
  sources={sources}
130
138
  setSource={handleSourceSelect}
131
139
  isLoading={isLoading}
140
+ isOtherSourceValue={false}
132
141
  error={sourcesError || error}
133
142
  plugin={plugin}
134
143
  pluginMode={mode}
@@ -152,6 +161,8 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
152
161
  inline={!!inline}
153
162
  inlineType={inlineType}
154
163
  {...props}
164
+ value={value && source ? (value.sourceId === source.id ? value : null) : null} // Don't pass value if value isn't for selected source
165
+ isOtherSourceValue={value && source ? (value.sourceId !== source.id ? true : false) : false} // Block render of existing asset if not from this source
155
166
  source={source}
156
167
  sources={sources}
157
168
  setSource={handleSourceSelect}