From 350200c607171f6e8c123b8c2b7d2cf8bcab4f4e Mon Sep 17 00:00:00 2001 From: Adam Horowitz Date: Wed, 10 Sep 2025 15:32:30 -0400 Subject: [PATCH 1/7] fix(metadataeditor): portal metadata editor sidebar to preview container --- .../content-preview/ContentPreview.js | 149 ++++++++++-------- .../MetadataInstanceEditor.tsx | 7 +- 2 files changed, 85 insertions(+), 71 deletions(-) diff --git a/src/elements/content-preview/ContentPreview.js b/src/elements/content-preview/ContentPreview.js index 1518c6a20d..65dafc9720 100644 --- a/src/elements/content-preview/ContentPreview.js +++ b/src/elements/content-preview/ContentPreview.js @@ -26,6 +26,7 @@ import Internationalize from '../common/Internationalize'; import AsyncLoad from '../common/async-load'; // $FlowFixMe TypeScript file import ThemingStyles from '../common/theming'; +import PreviewContext from './PreviewContext'; import TokenService from '../../utils/TokenService'; import { isInputElement, focus } from '../../utils/dom'; import { getTypedFileId } from '../../utils/file'; @@ -212,6 +213,8 @@ class ContentPreview extends React.PureComponent { // Defines a generic type for ContentSidebar, since an import would interfere with code splitting contentSidebar: { current: null | { refresh: Function } } = React.createRef(); + previewBodyRef: { current: null | HTMLDivElement } = React.createRef(); + previewContainer: ?HTMLDivElement; mouseMoveTimeoutID: TimeoutID; @@ -1328,82 +1331,88 @@ class ContentPreview extends React.PureComponent { return ( - -
- - {hasHeader && ( - - )} -
-
+ + +
+ + {hasHeader && ( + + )} +
+
+ {file && ( + + {({ measureRef: previewRef }) => ( +
+ )} + + )} + + +
{file && ( - - {({ measureRef: previewRef }) => ( -
- )} - + )} - -
- {file && ( - )}
- {isReloadNotificationVisible && ( - - )} -
- + + ); diff --git a/src/elements/content-sidebar/MetadataInstanceEditor.tsx b/src/elements/content-sidebar/MetadataInstanceEditor.tsx index 46c294ee92..54792a44ed 100644 --- a/src/elements/content-sidebar/MetadataInstanceEditor.tsx +++ b/src/elements/content-sidebar/MetadataInstanceEditor.tsx @@ -5,7 +5,8 @@ import { type MetadataTemplateInstance, } from '@box/metadata-editor'; import { TaxonomyOptionsFetcher } from '@box/metadata-editor/lib/components/metadata-editor-fields/components/metadata-taxonomy-field/types.js'; -import React from 'react'; +import React, { useContext } from 'react'; +import PreviewContext, { type PreviewContextType } from '../content-preview/PreviewContext'; import { ERROR_CODE_METADATA_AUTOFILL_TIMEOUT, ERROR_CODE_UNKNOWN, @@ -51,6 +52,9 @@ const MetadataInstanceEditor: React.FC = ({ template, isAdvancedExtractAgentEnabled = false, }) => { + const previewContext: PreviewContextType | null = useContext(PreviewContext); + const customRef = previewContext?.previewBodyRef; + return ( = ({ setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen} taxonomyOptionsFetcher={taxonomyOptionsFetcher} isAdvancedExtractAgentEnabled={isAdvancedExtractAgentEnabled} + customRef={customRef} /> ); }; From 12f6672700c6613c98242bc4c3e7bdd4bd87ed30 Mon Sep 17 00:00:00 2001 From: Adam Horowitz Date: Wed, 10 Sep 2025 16:44:22 -0400 Subject: [PATCH 2/7] fix(metadataeditor): portal metadata editor sidebar to preview container --- .../content-preview/PreviewContext.ts | 9 + .../ContentPreview-metadata-visual.stories.js | 172 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 src/elements/content-preview/PreviewContext.ts create mode 100644 src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js diff --git a/src/elements/content-preview/PreviewContext.ts b/src/elements/content-preview/PreviewContext.ts new file mode 100644 index 0000000000..702bd48ece --- /dev/null +++ b/src/elements/content-preview/PreviewContext.ts @@ -0,0 +1,9 @@ +import React from 'react'; + +export interface PreviewContextType { + previewBodyRef: HTMLDivElement | null; +} + +const PreviewContext = React.createContext(null); + +export default PreviewContext; diff --git a/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js b/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js new file mode 100644 index 0000000000..7a4ba3bc0d --- /dev/null +++ b/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js @@ -0,0 +1,172 @@ +import { userEvent, within } from 'storybook/test'; +import { http, HttpResponse } from 'msw'; + +import { DEFAULT_HOSTNAME_API } from '../../../../constants'; +import { mockEventRequest, mockFileRequest, mockUserRequest } from '../../../common/__mocks__/mockRequests'; +import ContentPreview from '../../ContentPreview'; + +const apiV2Path = `${DEFAULT_HOSTNAME_API}/2.0`; +const fileIdWithMetadata = '415542803939'; + +// Mock file with metadata permissions +const mockFileWithMetadata = { + url: `${apiV2Path}/files/${fileIdWithMetadata}`, + response: { + type: 'file', + id: fileIdWithMetadata, + etag: '3', + extension: 'pdf', + name: 'Test Document.pdf', + permissions: { + can_download: true, + can_preview: true, + can_upload: true, + can_comment: true, + can_rename: true, + can_delete: true, + can_share: true, + can_set_share_access: true, + can_invite_collaborator: true, + can_annotate: true, + can_view_annotations_all: true, + can_view_annotations_self: true, + can_create_annotations: true, + can_view_annotations: true, + }, + }, +}; + +// Mock metadata template with very long dropdown options +const mockMetadataTemplateWithLongOptions = { + url: `${apiV2Path}/metadata_templates/enterprise`, + response: { + limit: 1000, + entries: [ + { + id: 'long-dropdown-template-id', + type: 'metadata_template', + templateKey: 'longDropdownTemplate', + scope: 'enterprise_173733877', + displayName: 'Long Dropdown Test Template', + hidden: false, + copyInstanceOnItemCopy: false, + fields: [ + { + id: 'long-dropdown-field-id', + type: 'enum', + key: 'longDropdownField', + displayName: 'Department Selection', + hidden: false, + description: 'Select your department from the dropdown', + options: [ + { + id: 'option-1', + key: 'Engineering - Software Development - Frontend React TypeScript Team Alpha Division', + }, + { + id: 'option-2', + key: 'Marketing - Digital Campaigns - Social Media Content Strategy Team Beta Division', + }, + { + id: 'option-3', + key: 'Human Resources - Talent Acquisition - Employee Experience Team Gamma Division', + }, + { + id: 'option-4', + key: 'Finance - Accounting - Budget Planning - Financial Analysis Team Delta Division', + }, + { + id: 'option-5', + key: 'Operations - Supply Chain - Logistics - Vendor Management Team Epsilon Division', + }, + { + id: 'option-6', + key: 'Legal - Compliance - Regulatory Affairs - Contract Management Team Zeta Division', + }, + ], + }, + ], + }, + ], + }, +}; + +// Mock metadata instance for the file +const mockMetadataInstances = { + url: `${apiV2Path}/files/${fileIdWithMetadata}/metadata`, + response: { + entries: [ + { + $id: 'long-dropdown-instance-id', + $version: 1, + $type: 'longDropdownTemplate-template-type', + $parent: `file_${fileIdWithMetadata}`, + $typeVersion: 1, + $template: 'longDropdownTemplate', + $scope: 'enterprise_173733877', + $templateKey: 'longDropdownTemplate', + longDropdownField: 'Engineering - Software Development - Frontend React TypeScript Team Alpha Division', + $canEdit: true, + }, + ], + limit: 100, + }, +}; + +const mockGlobalMetadataTemplates = { + url: `${apiV2Path}/metadata_templates/global`, + response: { + entries: [], + }, +}; + +export const metadataDropdownPositioning = { + parameters: { + msw: { + handlers: [ + http.get(mockFileWithMetadata.url, () => HttpResponse.json(mockFileWithMetadata.response)), + http.get(mockMetadataTemplateWithLongOptions.url, () => + HttpResponse.json(mockMetadataTemplateWithLongOptions.response), + ), + http.get(mockMetadataInstances.url, () => HttpResponse.json(mockMetadataInstances.response)), + http.get(mockGlobalMetadataTemplates.url, () => + HttpResponse.json(mockGlobalMetadataTemplates.response), + ), + mockFileRequest, + mockUserRequest, + mockEventRequest, + ], + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const editButton = await canvas.findByRole('button', { name: 'Edit Long Dropdown Test Template' }); + await userEvent.click(editButton); + + const dropdownField = await canvas.findByRole('combobox', { name: 'Department Selection' }); + await userEvent.click(dropdownField); + + await canvas.findByRole('option', { + name: 'Engineering - Software Development - Frontend React TypeScript Team Alpha Division', + }); + }, +}; + +export default { + title: 'Elements/ContentPreview/tests/visual/Metadata', + component: ContentPreview, + args: { + fileId: fileIdWithMetadata, + hasHeader: true, + contentSidebarProps: { + hasMetadata: true, + metadataSidebarProps: { + isFeatureEnabled: true, + }, + features: { + 'metadata.redesign.enabled': true, + }, + }, + }, +}; From e4728a5370a1d05dd267d23c25d30c0389c695eb Mon Sep 17 00:00:00 2001 From: Adam Horowitz Date: Thu, 11 Sep 2025 10:49:44 -0400 Subject: [PATCH 3/7] fix(metadataeditor): portal metadata editor sidebar to preview container --- src/elements/content-preview/ContentPreview.js | 5 +++-- src/elements/content-preview/PreviewContext.ts | 3 ++- src/elements/content-sidebar/MetadataInstanceEditor.tsx | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/elements/content-preview/ContentPreview.js b/src/elements/content-preview/ContentPreview.js index 65dafc9720..8fa3c7f7fa 100644 --- a/src/elements/content-preview/ContentPreview.js +++ b/src/elements/content-preview/ContentPreview.js @@ -26,6 +26,7 @@ import Internationalize from '../common/Internationalize'; import AsyncLoad from '../common/async-load'; // $FlowFixMe TypeScript file import ThemingStyles from '../common/theming'; +// $FlowFixMe TypeScript file import PreviewContext from './PreviewContext'; import TokenService from '../../utils/TokenService'; import { isInputElement, focus } from '../../utils/dom'; @@ -213,7 +214,7 @@ class ContentPreview extends React.PureComponent { // Defines a generic type for ContentSidebar, since an import would interfere with code splitting contentSidebar: { current: null | { refresh: Function } } = React.createRef(); - previewBodyRef: { current: null | HTMLDivElement } = React.createRef(); + previewBodyRef = React.createRef(); previewContainer: ?HTMLDivElement; @@ -1331,7 +1332,7 @@ class ContentPreview extends React.PureComponent { return ( - +
; } const PreviewContext = React.createContext(null); +PreviewContext.displayName = 'PreviewContext'; export default PreviewContext; diff --git a/src/elements/content-sidebar/MetadataInstanceEditor.tsx b/src/elements/content-sidebar/MetadataInstanceEditor.tsx index 54792a44ed..9d5e75949a 100644 --- a/src/elements/content-sidebar/MetadataInstanceEditor.tsx +++ b/src/elements/content-sidebar/MetadataInstanceEditor.tsx @@ -75,7 +75,7 @@ const MetadataInstanceEditor: React.FC = ({ setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen} taxonomyOptionsFetcher={taxonomyOptionsFetcher} isAdvancedExtractAgentEnabled={isAdvancedExtractAgentEnabled} - customRef={customRef} + customRef={customRef.current} /> ); }; From 4cf6d7e47a49ee811fd966fab127adf1ee24eba2 Mon Sep 17 00:00:00 2001 From: Adam Horowitz Date: Thu, 11 Sep 2025 10:57:47 -0400 Subject: [PATCH 4/7] fix(metadataeditor): portal metadata editor sidebar to preview container --- .../stories/tests/ContentPreview-metadata-visual.stories.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js b/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js index 7a4ba3bc0d..27e6c6248d 100644 --- a/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js +++ b/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js @@ -132,9 +132,9 @@ export const metadataDropdownPositioning = { http.get(mockGlobalMetadataTemplates.url, () => HttpResponse.json(mockGlobalMetadataTemplates.response), ), - mockFileRequest, - mockUserRequest, - mockEventRequest, + http.get(mockFileRequest.url, () => HttpResponse.json(mockFileRequest.response)), + +http.get(mockUserRequest.url, () => HttpResponse.json(mockUserRequest.response)), + +http.get(mockEventRequest.url, () => HttpResponse.json(mockEventRequest.response)), ], }, }, From 86f66485056763d24f24905cf896e764b0051269 Mon Sep 17 00:00:00 2001 From: Adam Horowitz Date: Thu, 11 Sep 2025 11:33:51 -0400 Subject: [PATCH 5/7] fix(metadataeditor): portal metadata editor sidebar to preview container --- .../stories/tests/ContentPreview-metadata-visual.stories.js | 4 ++-- src/elements/content-sidebar/MetadataInstanceEditor.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js b/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js index 27e6c6248d..93b20a95f2 100644 --- a/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js +++ b/src/elements/content-preview/stories/tests/ContentPreview-metadata-visual.stories.js @@ -133,8 +133,8 @@ export const metadataDropdownPositioning = { HttpResponse.json(mockGlobalMetadataTemplates.response), ), http.get(mockFileRequest.url, () => HttpResponse.json(mockFileRequest.response)), - +http.get(mockUserRequest.url, () => HttpResponse.json(mockUserRequest.response)), - +http.get(mockEventRequest.url, () => HttpResponse.json(mockEventRequest.response)), + http.get(mockUserRequest.url, () => HttpResponse.json(mockUserRequest.response)), + http.get(mockEventRequest.url, () => HttpResponse.json(mockEventRequest.response)), ], }, }, diff --git a/src/elements/content-sidebar/MetadataInstanceEditor.tsx b/src/elements/content-sidebar/MetadataInstanceEditor.tsx index 9d5e75949a..e7b61037d6 100644 --- a/src/elements/content-sidebar/MetadataInstanceEditor.tsx +++ b/src/elements/content-sidebar/MetadataInstanceEditor.tsx @@ -75,7 +75,7 @@ const MetadataInstanceEditor: React.FC = ({ setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen} taxonomyOptionsFetcher={taxonomyOptionsFetcher} isAdvancedExtractAgentEnabled={isAdvancedExtractAgentEnabled} - customRef={customRef.current} + customRef={customRef?.current} /> ); }; From a41ce3b9c6aefc4d9bbc9301a9b15cb74e0cd540 Mon Sep 17 00:00:00 2001 From: Adam Horowitz Date: Thu, 11 Sep 2025 11:46:52 -0400 Subject: [PATCH 6/7] fix(metadataeditor): portal metadata editor sidebar to preview container --- src/elements/content-sidebar/MetadataInstanceEditor.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/elements/content-sidebar/MetadataInstanceEditor.tsx b/src/elements/content-sidebar/MetadataInstanceEditor.tsx index e7b61037d6..36fc99df72 100644 --- a/src/elements/content-sidebar/MetadataInstanceEditor.tsx +++ b/src/elements/content-sidebar/MetadataInstanceEditor.tsx @@ -53,7 +53,7 @@ const MetadataInstanceEditor: React.FC = ({ isAdvancedExtractAgentEnabled = false, }) => { const previewContext: PreviewContextType | null = useContext(PreviewContext); - const customRef = previewContext?.previewBodyRef; + const customRef = previewContext?.previewBodyRef?.current; return ( = ({ setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen} taxonomyOptionsFetcher={taxonomyOptionsFetcher} isAdvancedExtractAgentEnabled={isAdvancedExtractAgentEnabled} - customRef={customRef?.current} + customRef={customRef} /> ); }; From 1c5dc1adc3eb6f0d1726c81d3af1ab6894bd4e23 Mon Sep 17 00:00:00 2001 From: Adam Horowitz Date: Thu, 11 Sep 2025 11:59:00 -0400 Subject: [PATCH 7/7] fix(metadataeditor): portal metadata editor sidebar to preview container --- src/elements/content-preview/ContentPreview.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/elements/content-preview/ContentPreview.js b/src/elements/content-preview/ContentPreview.js index 8fa3c7f7fa..b57ffa22e2 100644 --- a/src/elements/content-preview/ContentPreview.js +++ b/src/elements/content-preview/ContentPreview.js @@ -216,6 +216,8 @@ class ContentPreview extends React.PureComponent { previewBodyRef = React.createRef(); + previewContextValue = { previewBodyRef: this.previewBodyRef }; + previewContainer: ?HTMLDivElement; mouseMoveTimeoutID: TimeoutID; @@ -1332,7 +1334,7 @@ class ContentPreview extends React.PureComponent { return ( - +