diff --git a/.prettierignore b/.prettierignore
index b43bf86b50..1604bd4b3f 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1 +1,2 @@
README.md
+flow-typed/*
diff --git a/flow-typed/npm/react-intl_v2.x.x.js b/flow-typed/npm/react-intl_v2.x.x.js
index bb739aac5a..e778581a15 100644
--- a/flow-typed/npm/react-intl_v2.x.x.js
+++ b/flow-typed/npm/react-intl_v2.x.x.js
@@ -260,4 +260,5 @@ declare module "react-intl" {
> {}
declare type IntlShape = $npm$ReactIntl$IntlShape;
declare type MessageDescriptor = $npm$ReactIntl$MessageDescriptor;
+ declare function useIntl(): $npm$ReactIntl$IntlShape;
}
diff --git a/src/elements/content-explorer/PreviewDialog.js b/src/elements/content-explorer/PreviewDialog.js
deleted file mode 100644
index 34daacdccf..0000000000
--- a/src/elements/content-explorer/PreviewDialog.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * @flow
- * @file Content Explorer Preview Dialog
- * @author Box
- */
-
-import * as React from 'react';
-import Modal from 'react-modal';
-import { injectIntl } from 'react-intl';
-import type { IntlShape } from 'react-intl';
-import cloneDeep from 'lodash/cloneDeep';
-import messages from '../common/messages';
-import ContentPreview from '../content-preview';
-import { TYPE_FILE, CLASS_MODAL_CONTENT_FULL_BLEED, CLASS_MODAL_OVERLAY, CLASS_MODAL } from '../../constants';
-import type { Token, BoxItem, Collection } from '../../common/types/core';
-import type APICache from '../../utils/Cache';
-
-type Props = {
- apiHost: string,
- appElement: HTMLElement,
- appHost: string,
- cache: APICache,
- canDownload: boolean,
- contentPreviewProps: ContentPreviewProps,
- currentCollection: Collection,
- intl: IntlShape,
- isOpen: boolean,
- isTouch: boolean,
- item: BoxItem,
- onCancel: Function,
- onDownload: Function,
- onPreview: Function,
- parentElement: HTMLElement,
- previewLibraryVersion: string,
- requestInterceptor?: Function,
- responseInterceptor?: Function,
- sharedLink?: string,
- sharedLinkPassword?: string,
- staticHost: string,
- staticPath: string,
- token: Token,
-};
-
-const PreviewDialog = ({
- item,
- isOpen,
- parentElement,
- appElement,
- token,
- cache,
- currentCollection,
- canDownload,
- onCancel,
- onPreview,
- onDownload,
- apiHost,
- appHost,
- staticHost,
- staticPath,
- previewLibraryVersion,
- sharedLink,
- sharedLinkPassword,
- contentPreviewProps,
- requestInterceptor,
- responseInterceptor,
- intl,
-}: Props) => {
- const { items }: Collection = currentCollection;
- const onLoad = (data: any): void => {
- onPreview(cloneDeep(data));
- };
-
- if (!item || !items) {
- return null;
- }
-
- const files: BoxItem[] = items.filter(({ type }) => type === TYPE_FILE);
- return (
- parentElement}
- portalClassName={`${CLASS_MODAL} be-modal-preview`}
- className={CLASS_MODAL_CONTENT_FULL_BLEED}
- overlayClassName={CLASS_MODAL_OVERLAY}
- contentLabel={intl.formatMessage(messages.preview)}
- onRequestClose={onCancel}
- appElement={appElement}
- >
-
-
- );
-};
-
-export default injectIntl(PreviewDialog);
diff --git a/src/elements/content-explorer/PreviewDialog.js.flow b/src/elements/content-explorer/PreviewDialog.js.flow
new file mode 100644
index 0000000000..ff79a466fe
--- /dev/null
+++ b/src/elements/content-explorer/PreviewDialog.js.flow
@@ -0,0 +1,110 @@
+/**
+ * @flow
+ * @file Content Explorer Preview Dialog
+ * @author Box
+ */
+
+import * as React from 'react';
+import { useIntl } from 'react-intl';
+import { Modal } from '@box/blueprint-web';
+import cloneDeep from 'lodash/cloneDeep';
+
+import ContentPreview from '../content-preview';
+import { TYPE_FILE } from '../../constants';
+import type { ContentPreviewProps } from '../content-preview';
+import type { Token, BoxItem, Collection } from '../../common/types/core';
+import type APICache from '../../utils/Cache';
+
+import messages from '../common/messages';
+
+type PreviewDialogProps = {
+ apiHost: string,
+ appHost: string,
+ cache: APICache,
+ canDownload: boolean,
+ contentPreviewProps: ContentPreviewProps,
+ currentCollection: Collection,
+ isOpen: boolean,
+ isTouch: boolean,
+ item: BoxItem,
+ onCancel: any,
+ onDownload: any,
+ onPreview: any,
+ parentElement: HTMLElement,
+ previewLibraryVersion: string,
+ responseInterceptor?: any,
+ requestInterceptor?: any,
+ sharedLink?: string,
+ sharedLinkPassword?: string,
+ staticHost: string,
+ staticPath: string,
+ token: Token,
+};
+
+const PreviewDialog = ({
+ apiHost,
+ appHost,
+ cache,
+ canDownload,
+ contentPreviewProps,
+ currentCollection,
+ isOpen,
+ item,
+ onCancel,
+ onDownload,
+ onPreview,
+ parentElement,
+ previewLibraryVersion,
+ requestInterceptor,
+ responseInterceptor,
+ sharedLink,
+ sharedLinkPassword,
+ staticHost,
+ staticPath,
+ token,
+}: PreviewDialogProps) => {
+ const { formatMessage } = useIntl();
+ const { items }: Collection = currentCollection;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const onLoad = (data: any): void => {
+ if (onPreview) {
+ onPreview(cloneDeep(data));
+ }
+ };
+
+ if (!item || !items) {
+ return null;
+ }
+
+ const files: BoxItem[] = items.filter(({ type }) => type === TYPE_FILE);
+ return (
+
+
+
+
+
+ );
+};
+
+export default PreviewDialog;
diff --git a/src/elements/content-explorer/PreviewDialog.scss b/src/elements/content-explorer/PreviewDialog.scss
new file mode 100644
index 0000000000..4b2c5e34eb
--- /dev/null
+++ b/src/elements/content-explorer/PreviewDialog.scss
@@ -0,0 +1,16 @@
+[class^='bp_modal_module_content'].bce-PreviewDialog {
+ position: fixed;
+ top: 0;
+ right: 0;
+ z-index: 1000;
+ display: grid;
+ max-width: 100%;
+ max-height: 100%;
+ margin: 0;
+ inset: 0;
+ border-radius: 0;
+
+ .be.bcpr {
+ height: 100vh;
+ }
+}
diff --git a/src/elements/content-explorer/PreviewDialog.tsx b/src/elements/content-explorer/PreviewDialog.tsx
new file mode 100644
index 0000000000..9df486d07b
--- /dev/null
+++ b/src/elements/content-explorer/PreviewDialog.tsx
@@ -0,0 +1,106 @@
+import * as React from 'react';
+import { useIntl } from 'react-intl';
+import { Modal } from '@box/blueprint-web';
+import cloneDeep from 'lodash/cloneDeep';
+
+import ContentPreview, { ContentPreviewProps } from '../content-preview';
+import { TYPE_FILE } from '../../constants';
+import type { Token, BoxItem, Collection } from '../../common/types/core';
+import type APICache from '../../utils/Cache';
+
+import messages from '../common/messages';
+
+import './PreviewDialog.scss';
+
+export interface PreviewDialogProps {
+ apiHost: string;
+ appHost: string;
+ cache: APICache;
+ canDownload: boolean;
+ contentPreviewProps: ContentPreviewProps;
+ currentCollection: Collection;
+ isOpen: boolean;
+ isTouch: boolean;
+ item: BoxItem;
+ onCancel: () => void;
+ onDownload: () => void;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ onPreview: (data: any) => void;
+ parentElement: HTMLElement;
+ previewLibraryVersion: string;
+ requestInterceptor?: () => void;
+ responseInterceptor?: () => void;
+ sharedLink?: string;
+ sharedLinkPassword?: string;
+ staticHost: string;
+ staticPath: string;
+ token: Token;
+}
+
+const PreviewDialog = ({
+ apiHost,
+ appHost,
+ cache,
+ canDownload,
+ contentPreviewProps,
+ currentCollection,
+ isOpen,
+ item,
+ onCancel,
+ onDownload,
+ onPreview,
+ parentElement,
+ previewLibraryVersion,
+ requestInterceptor,
+ responseInterceptor,
+ sharedLink,
+ sharedLinkPassword,
+ staticHost,
+ staticPath,
+ token,
+}: PreviewDialogProps) => {
+ const { formatMessage } = useIntl();
+ const { items }: Collection = currentCollection;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const onLoad = (data: any): void => {
+ if (onPreview) {
+ onPreview(cloneDeep(data));
+ }
+ };
+
+ if (!item || !items) {
+ return null;
+ }
+
+ const files: BoxItem[] = items.filter(({ type }) => type === TYPE_FILE);
+ return (
+
+
+
+
+
+ );
+};
+
+export default PreviewDialog;
diff --git a/src/elements/content-explorer/__tests__/PreviewDialog.test.tsx b/src/elements/content-explorer/__tests__/PreviewDialog.test.tsx
new file mode 100644
index 0000000000..347e748a59
--- /dev/null
+++ b/src/elements/content-explorer/__tests__/PreviewDialog.test.tsx
@@ -0,0 +1,67 @@
+import * as React from 'react';
+import userEvent from '@testing-library/user-event';
+import { render, screen, waitFor } from '../../../test-utils/testing-library';
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import APICache from '../../../utils/Cache';
+
+import PreviewDialog, { PreviewDialogProps } from '../PreviewDialog';
+
+describe('elements/content-explorer/PreviewDialog', () => {
+ const defaultProps = {
+ apiHost: 'https://api.box.com',
+ appHost: 'https://app.box.com',
+ cache: new APICache(),
+ canDownload: true,
+ contentPreviewProps: {},
+ currentCollection: { items: [{ id: '1', type: 'file' }] },
+ isOpen: true,
+ isTouch: false,
+ item: { id: '1', type: 'file' },
+ onCancel: jest.fn(),
+ onDownload: jest.fn(),
+ onPreview: jest.fn(),
+ parentElement: document.body,
+ previewLibraryVersion: '1.0.0',
+ staticHost: 'https://static.box.com',
+ staticPath: '/static',
+ token: 'token',
+ };
+
+ const renderComponent = (props?: Partial) =>
+ render();
+
+ test('renders', () => {
+ renderComponent();
+ expect(screen.getByLabelText('Preview')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Close' }));
+ });
+
+ test('calls onCancel when modal is closed', async () => {
+ renderComponent();
+ const closeButton = screen.getByRole('button', { name: 'Close' });
+ await userEvent.click(closeButton);
+ expect(defaultProps.onCancel).toHaveBeenCalled();
+ });
+
+ test('does not render when item is null', () => {
+ const props = { item: null };
+ const { container } = renderComponent(props);
+ expect(container.firstChild).toBeNull();
+ });
+
+ test('does not render when items are null', () => {
+ const props = { currentCollection: { items: null } };
+ const { container } = renderComponent(props);
+ expect(container.firstChild).toBeNull();
+ });
+
+ test('calls onPreview with cloned data on load', () => {
+ const data = { id: '1', type: 'file' };
+ renderComponent();
+ waitFor(() => {
+ expect(defaultProps.onPreview).toHaveBeenCalledWith(expect.objectContaining(data));
+ });
+ });
+});
diff --git a/src/elements/content-explorer/stories/PreviewDialog.stories.tsx b/src/elements/content-explorer/stories/PreviewDialog.stories.tsx
new file mode 100644
index 0000000000..fbd23cd701
--- /dev/null
+++ b/src/elements/content-explorer/stories/PreviewDialog.stories.tsx
@@ -0,0 +1,79 @@
+import * as React from 'react';
+import { useArgs } from '@storybook/preview-api';
+import { Button } from '@box/blueprint-web';
+
+import { addRootElement } from '../../../utils/storybook';
+
+import PreviewDialog from '../PreviewDialog';
+
+export const previewDialog = {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ render: (args: any) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const [, setArgs] = useArgs();
+
+ const handleOpenModal = () => setArgs({ isOpen: true });
+
+ const handleCloseModal = () => {
+ setArgs({ isOpen: false });
+ };
+
+ const { rootElement } = addRootElement();
+
+ return (
+
+
+
+
+
+ );
+ },
+};
+
+export default {
+ title: 'Elements/ContentExplorer',
+ component: PreviewDialog,
+ args: {
+ isLoading: false,
+ isOpen: false,
+ token: global.TOKEN,
+ },
+};