From cdad55f08d812b36e42b894aeb8b4acb2514c1d0 Mon Sep 17 00:00:00 2001 From: zhirongwang Date: Wed, 9 Sep 2026 17:49:07 -0700 Subject: [PATCH 1/6] feat(content-preview): Add host-owned comparison banner slots Hosts can render overlay banners on each pane and set pane fill via CSS variables, without ContentPreview owning product chrome. Co-authored-by: Cursor --- .../content-preview/ContentPreview.js | 10 +++- .../content-preview/ContentPreview.scss | 39 ++++++++++++++ .../__tests__/ContentPreview.test.js | 51 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/elements/content-preview/ContentPreview.js b/src/elements/content-preview/ContentPreview.js index 7bf6b8f0e3..5bd13745b9 100644 --- a/src/elements/content-preview/ContentPreview.js +++ b/src/elements/content-preview/ContentPreview.js @@ -137,7 +137,12 @@ type Props = { hasProviders?: boolean, hideSidebar?: boolean, isComparing?: boolean, + // Arbitrary host content rendered at the top of this pane, above the viewer. + // ContentPreview renders it verbatim; consumers own its markup and behavior. + banner?: React.Node, comparedSlotRef?: (?HTMLDivElement) => mixed, + // Host content for the compared pane's banner; forwarded as that pane's `banner`. + comparedBanner?: React.Node, comparedVersion?: BoxItemVersion, isLarge: boolean, isVeryLarge?: boolean, @@ -1727,6 +1732,7 @@ class ContentPreview extends React.PureComponent { hasHeader, hasProviders, hideSidebar, + banner, comparedSlotRef, isComparing, history, @@ -1830,6 +1836,7 @@ class ContentPreview extends React.PureComponent { onMouseMove={this.onMouseMove} ref={this.containerRef} > + {banner &&
{banner}
} {file && ( {({ measureRef: previewRef }) => { @@ -1932,7 +1939,7 @@ const ConnectedContentPreview = flow([ const MemoConnectedContentPreview = React.memo(ConnectedContentPreview); function ContentPreviewWithComparison(props: ContentPreviewProps) { - const { comparedVersion, ...rest } = props; + const { comparedBanner, comparedVersion, ...rest } = props; const [comparedSlot, setComparedSlot] = React.useState(null); const comparedVersionId = comparedVersion && comparedVersion.id; const isComparing = comparedVersionId != null && comparedVersionId !== ''; @@ -1953,6 +1960,7 @@ function ContentPreviewWithComparison(props: ContentPreviewProps) { accessPattern={undefined} advancedContentInsights={undefined} autoFocus={false} + banner={comparedBanner} boxAnnotations={undefined} collection={EMPTY_COLLECTION} componentRef={undefined} diff --git a/src/elements/content-preview/ContentPreview.scss b/src/elements/content-preview/ContentPreview.scss index 23c9c81fd0..3adf7f77ea 100644 --- a/src/elements/content-preview/ContentPreview.scss +++ b/src/elements/content-preview/ContentPreview.scss @@ -13,6 +13,14 @@ &--comparing > .bcpr-container { flex: 1 1 50%; min-width: 0; + + // Hosts set --bcpr-*-pane-background (e.g. preview-client). + // Do not paint over .bp-dark (video / 3D); those stay black. + .bp:not(.bp-dark), + .bp-doc, + .bp-document-preload-wrapper { + background-color: var(--bcpr-current-pane-background, #f5f5f5); + } } } @@ -21,6 +29,7 @@ flex: 1 1 50%; min-width: 0; min-height: 0; + background-color: var(--bcpr-compared-pane-background, transparent); &:not(:empty) { display: flex; @@ -31,16 +40,46 @@ min-width: 0; min-height: 0; } + + .bp:not(.bp-dark), + .bp-doc, + .bp-document-preload-wrapper { + background-color: var(--bcpr-compared-pane-background, #f5f5f5); + } } .bcpr-container { position: relative; display: flex; flex: 1; + flex-direction: column; + min-height: 0; + } + + .bcpr-banner { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + } + + // Overlay banners cover the top of the viewer. Add scroll room so the first + // page / thumbnail can sit below the banner. `.pdfViewer` is PDF.js; + // `.bp-vs-list` is box-content-preview's thumbnail VirtualScroller. + .bcpr-container:has(.bcpr-banner) { + .pdfViewer { + padding-top: 4.5rem; + } + + .bp-vs-list { + margin-top: 4.5rem; + } } .bcpr-content { flex: 1; + min-height: 0; } .bp-navigate { diff --git a/src/elements/content-preview/__tests__/ContentPreview.test.js b/src/elements/content-preview/__tests__/ContentPreview.test.js index f919cf17ba..830109a970 100644 --- a/src/elements/content-preview/__tests__/ContentPreview.test.js +++ b/src/elements/content-preview/__tests__/ContentPreview.test.js @@ -2802,6 +2802,57 @@ describe('elements/content-preview/ContentPreview', () => { expect(bodyDiv.find('.bcpr-compared-slot').exists()).toBe(false); }); + test('should render the banner above the viewer content when provided', () => { + const wrapper = getWrapper({ + banner:
v12
, + fileId: '123', + }); + wrapper.setState({ + currentFileId: '123', + file: { id: '123', name: 'test.pdf' }, + }); + + const container = wrapper.find('.bcpr-container'); + const bannerDiv = container.children().at(0); + expect(bannerDiv.hasClass('bcpr-banner')).toBe(true); + expect(bannerDiv.find('.test-banner').exists()).toBe(true); + }); + + test('should not render a banner element when the banner prop is omitted', () => { + const wrapper = getWrapper({ + fileId: '123', + }); + wrapper.setState({ + currentFileId: '123', + file: { id: '123', name: 'test.pdf' }, + }); + + expect(wrapper.find('.bcpr-banner').exists()).toBe(false); + }); + + test('should forward comparedBanner as the compared instance banner only', () => { + const banner =
; + const comparedBanner =
; + const wrapper = shallow( + , + ); + + wrapper.childAt(0).props().comparedSlotRef(document.createElement('div')); + wrapper.update(); + + const mainProps = wrapper.childAt(0).props(); + const comparedProps = wrapper.childAt(1).props().children.props; + expect(mainProps.banner).toBe(banner); + expect(mainProps.comparedBanner).toBeUndefined(); + expect(comparedProps.banner).toBe(comparedBanner); + }); + test('should not render PreviewNavigation when isComparing', () => { const wrapper = getWrapper({ fileId: '456', From cf7a1f46542c84185f9c0f619360522679fbb99a Mon Sep 17 00:00:00 2001 From: zhirongwang Date: Thu, 10 Sep 2026 10:33:10 -0700 Subject: [PATCH 2/6] feat(content-preview): require consumer to provide banner height --- src/elements/content-preview/ContentPreview.scss | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/elements/content-preview/ContentPreview.scss b/src/elements/content-preview/ContentPreview.scss index 3adf7f77ea..dd22560a61 100644 --- a/src/elements/content-preview/ContentPreview.scss +++ b/src/elements/content-preview/ContentPreview.scss @@ -64,16 +64,17 @@ z-index: 1; } - // Overlay banners cover the top of the viewer. Add scroll room so the first - // page / thumbnail can sit below the banner. `.pdfViewer` is PDF.js; - // `.bp-vs-list` is box-content-preview's thumbnail VirtualScroller. + // Overlay banners cover the top of the viewer. Hosts must set + // --bcpr-banner-height to the overlay height so the first page / + // thumbnail can sit below it. `.pdfViewer` is PDF.js; `.bp-vs-list` + // is box-content-preview's thumbnail VirtualScroller. .bcpr-container:has(.bcpr-banner) { .pdfViewer { - padding-top: 4.5rem; + padding-top: var(--bcpr-banner-height); } .bp-vs-list { - margin-top: 4.5rem; + margin-top: var(--bcpr-banner-height); } } From 6a2be8518edfaa5ec2f2f558cce978ae09dee45e Mon Sep 17 00:00:00 2001 From: zhirongwang Date: Thu, 10 Sep 2026 14:12:58 -0700 Subject: [PATCH 3/6] fix(content-preview): Use transparent fallback for compared pane viewers Leave compared viewers unpainted unless a host sets the pane background variable. Co-authored-by: Cursor --- src/elements/content-preview/ContentPreview.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/content-preview/ContentPreview.scss b/src/elements/content-preview/ContentPreview.scss index dd22560a61..8bdeb614c7 100644 --- a/src/elements/content-preview/ContentPreview.scss +++ b/src/elements/content-preview/ContentPreview.scss @@ -44,7 +44,7 @@ .bp:not(.bp-dark), .bp-doc, .bp-document-preload-wrapper { - background-color: var(--bcpr-compared-pane-background, #f5f5f5); + background-color: var(--bcpr-compared-pane-background, transparent); } } From 84fd6c6beb710db01e4251024feda36f524c2908 Mon Sep 17 00:00:00 2001 From: zhirongwang Date: Thu, 10 Sep 2026 14:18:05 -0700 Subject: [PATCH 4/6] refactor(content-preview): Drop pane-background CSS fallbacks Hosts that omit the variables keep the BCP default fill. Co-authored-by: Cursor --- src/elements/content-preview/ContentPreview.js | 3 --- src/elements/content-preview/ContentPreview.scss | 11 ++++------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/elements/content-preview/ContentPreview.js b/src/elements/content-preview/ContentPreview.js index 5bd13745b9..a23f5360b0 100644 --- a/src/elements/content-preview/ContentPreview.js +++ b/src/elements/content-preview/ContentPreview.js @@ -137,11 +137,8 @@ type Props = { hasProviders?: boolean, hideSidebar?: boolean, isComparing?: boolean, - // Arbitrary host content rendered at the top of this pane, above the viewer. - // ContentPreview renders it verbatim; consumers own its markup and behavior. banner?: React.Node, comparedSlotRef?: (?HTMLDivElement) => mixed, - // Host content for the compared pane's banner; forwarded as that pane's `banner`. comparedBanner?: React.Node, comparedVersion?: BoxItemVersion, isLarge: boolean, diff --git a/src/elements/content-preview/ContentPreview.scss b/src/elements/content-preview/ContentPreview.scss index 8bdeb614c7..5af3291ba3 100644 --- a/src/elements/content-preview/ContentPreview.scss +++ b/src/elements/content-preview/ContentPreview.scss @@ -19,7 +19,7 @@ .bp:not(.bp-dark), .bp-doc, .bp-document-preload-wrapper { - background-color: var(--bcpr-current-pane-background, #f5f5f5); + background-color: var(--bcpr-current-pane-background); } } } @@ -29,7 +29,7 @@ flex: 1 1 50%; min-width: 0; min-height: 0; - background-color: var(--bcpr-compared-pane-background, transparent); + background-color: var(--bcpr-compared-pane-background); &:not(:empty) { display: flex; @@ -44,7 +44,7 @@ .bp:not(.bp-dark), .bp-doc, .bp-document-preload-wrapper { - background-color: var(--bcpr-compared-pane-background, transparent); + background-color: var(--bcpr-compared-pane-background); } } @@ -64,10 +64,7 @@ z-index: 1; } - // Overlay banners cover the top of the viewer. Hosts must set - // --bcpr-banner-height to the overlay height so the first page / - // thumbnail can sit below it. `.pdfViewer` is PDF.js; `.bp-vs-list` - // is box-content-preview's thumbnail VirtualScroller. + // Hosts set --bcpr-banner-height so the overlay does not cover the first page or thumbnail. .bcpr-container:has(.bcpr-banner) { .pdfViewer { padding-top: var(--bcpr-banner-height); From df35272f12d3fe35c953d8745c4e2119882fa89c Mon Sep 17 00:00:00 2001 From: zhirongwang Date: Thu, 10 Sep 2026 16:21:02 -0700 Subject: [PATCH 5/6] fix(content-preview): Keep banner containing block on narrow viewports The compared instance is not --comparing, so the mobile static container rule let the banner pin to the overlay. Also correct the .bp-dark comment. Co-authored-by: Cursor --- src/elements/content-preview/ContentPreview.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/elements/content-preview/ContentPreview.scss b/src/elements/content-preview/ContentPreview.scss index 5af3291ba3..a38390efdc 100644 --- a/src/elements/content-preview/ContentPreview.scss +++ b/src/elements/content-preview/ContentPreview.scss @@ -15,7 +15,7 @@ min-width: 0; // Hosts set --bcpr-*-pane-background (e.g. preview-client). - // Do not paint over .bp-dark (video / 3D); those stay black. + // Do not paint over .bp-dark (video). 3D is .bp-box3d and gets the host fill. .bp:not(.bp-dark), .bp-doc, .bp-document-preload-wrapper { @@ -142,6 +142,11 @@ position: static; } + // Compared instance is not --comparing. Keep the banner's containing block. + .bcpr-body:not(.bcpr-body--comparing) .bcpr-container:has(.bcpr-banner) { + position: relative; + } + &.bcpr-thumbnails-open .bcpr-navigate-left { left: 0; } From 33ecf59d118357b83f0d244be48053b57864eeb6 Mon Sep 17 00:00:00 2001 From: zhirongwang Date: Thu, 10 Sep 2026 16:29:29 -0700 Subject: [PATCH 6/6] fix(content-preview): Fall back pane fills to BCP default gray Unset --bcpr-*-pane-background keeps .bp #f5f5f5 instead of transparent. Co-authored-by: Cursor --- src/elements/content-preview/ContentPreview.scss | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/elements/content-preview/ContentPreview.scss b/src/elements/content-preview/ContentPreview.scss index a38390efdc..a9ba9b9126 100644 --- a/src/elements/content-preview/ContentPreview.scss +++ b/src/elements/content-preview/ContentPreview.scss @@ -15,11 +15,12 @@ min-width: 0; // Hosts set --bcpr-*-pane-background (e.g. preview-client). - // Do not paint over .bp-dark (video). 3D is .bp-box3d and gets the host fill. + // Unset vars keep BCP's .bp default (#f5f5f5). Skip .bp-dark (video). + // 3D is .bp-box3d and gets the host fill. .bp:not(.bp-dark), .bp-doc, .bp-document-preload-wrapper { - background-color: var(--bcpr-current-pane-background); + background-color: var(--bcpr-current-pane-background, #f5f5f5); } } } @@ -29,7 +30,7 @@ flex: 1 1 50%; min-width: 0; min-height: 0; - background-color: var(--bcpr-compared-pane-background); + background-color: var(--bcpr-compared-pane-background, #f5f5f5); &:not(:empty) { display: flex; @@ -44,7 +45,7 @@ .bp:not(.bp-dark), .bp-doc, .bp-document-preload-wrapper { - background-color: var(--bcpr-compared-pane-background); + background-color: var(--bcpr-compared-pane-background, #f5f5f5); } }