Skip to content

Commit

Permalink
chore(content-explorer): Migrate shareDialog
Browse files Browse the repository at this point in the history
  • Loading branch information
greg-in-a-box committed Nov 12, 2024
1 parent b64fefa commit 1728cfc
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 92 deletions.
11 changes: 11 additions & 0 deletions src/elements/common/modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,22 @@
background-color: $darker-black;
}

.be-modal-share .be-modal-dialog-content,
.be-modal-delete .be-modal-dialog-content {
padding: 0;
border-radius: tokens.$radius-4;
}

.be-modal-share {
.be-modal-input-group {
justify-content: inherit;

input {
width: 100%;
}
}
}

.be-modal-dialog-content {
@extend .be-modal-wrapper-content;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,39 @@
/**
* @flow
* @file Content Explorer Share Dialog
* @author Box
*/

import * as React from 'react';
import Modal from 'react-modal';
import noop from 'lodash/noop';
import { injectIntl, FormattedMessage } from 'react-intl';
import type { IntlShape } from 'react-intl';
import PrimaryButton from '../../components/primary-button/PrimaryButton';
import Button from '../../components/button/Button';
import messages from '../common/messages';
import { FormattedMessage, useIntl } from 'react-intl';
import { Button, Text } from "@box/blueprint-web";

import ShareAccessSelect from '../common/share-access-select';
import { CLASS_MODAL_CONTENT, CLASS_MODAL_OVERLAY, CLASS_MODAL } from '../../constants';
import type { BoxItem } from '../../common/types/core';

import messages from '../common/messages';

import './ShareDialog.scss';

type Props = {
appElement: HTMLElement,
canSetShareAccess: boolean,
intl: IntlShape,
isLoading: boolean,
isOpen: boolean,
item: BoxItem,
onCancel: Function,
onShareAccessChange: Function,
parentElement: HTMLElement,
onCancel: any,
onShareAccessChange: any,
parentElement: HTMLElement
};

const ShareDialog = ({
isOpen,
canSetShareAccess,
onShareAccessChange,
onCancel,
item,
isLoading,
parentElement,
appElement,
intl,
}: Props) => {
appElement,
canSetShareAccess,
isLoading,
isOpen,
item,
onCancel,
onShareAccessChange,
parentElement,
}: Props) => {
const { formatMessage } = useIntl();
let textInput = null;

const copy = () => {
Expand All @@ -52,25 +45,24 @@ const ShareDialog = ({

const { shared_link: sharedLink }: BoxItem = item;
const { url } = sharedLink || {
url: intl.formatMessage(messages.shareDialogNone),
url: formatMessage(messages.shareDialogNone),
};

/* eslint-disable jsx-a11y/label-has-for */
return (
<Modal
appElement={appElement}
className={CLASS_MODAL_CONTENT}
contentLabel={intl.formatMessage(messages.shareDialogLabel)}
contentLabel={formatMessage(messages.shareDialogLabel)}
isOpen={isOpen}
onRequestClose={onCancel}
overlayClassName={CLASS_MODAL_OVERLAY}
parentSelector={() => parentElement}
portalClassName={`${CLASS_MODAL} be-modal-share`}
>
<div className="be-modal-content">
<label>
<Text as="label">
<FormattedMessage tagName="div" {...messages.shareDialogText} />
</label>
</Text>
<div className="be-modal-input-group">
<input
ref={input => {
Expand All @@ -80,9 +72,9 @@ const ShareDialog = ({
type="text"
value={url}
/>
<PrimaryButton autoFocus className="be-modal-button-copy" onClick={copy} type="button">
<FormattedMessage {...messages.copy} />
</PrimaryButton>
<Button autoFocus className="be-modal-button-copy" onClick={copy} variant="primary">
{formatMessage(messages.copy)}
</Button>
</div>
</div>
<div className="be-modal-btns">
Expand All @@ -92,12 +84,18 @@ const ShareDialog = ({
item={item}
onChange={onShareAccessChange}
/>
<Button isLoading={isLoading} onClick={onCancel} type="button">
<FormattedMessage {...messages.close} />
<Button
loading={isLoading}
loadingAriaLabel={formatMessage(messages.loading)}
onClick={onCancel}
size="large"
variant="secondary"
>
{formatMessage(messages.close)}
</Button>
</div>
</Modal>
);
};

export default injectIntl(ShareDialog);
export default ShareDialog;
102 changes: 102 additions & 0 deletions src/elements/content-explorer/ShareDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import * as React from 'react';
import Modal from 'react-modal';
import noop from 'lodash/noop';
import { FormattedMessage, useIntl } from 'react-intl';
import { Button, Modal as BlueprintModal, Text } from '@box/blueprint-web';

import ShareAccessSelect from '../common/share-access-select';
import { CLASS_MODAL_CONTENT, CLASS_MODAL_OVERLAY, CLASS_MODAL } from '../../constants';
import type { BoxItem } from '../../common/types/core';

import messages from '../common/messages';

import './ShareDialog.scss';

export interface ShareDialogProps {
appElement: HTMLElement;
canSetShareAccess: boolean;
isLoading: boolean;
isOpen: boolean;
item: BoxItem;
onCancel: () => void;
onShareAccessChange: () => void;
parentElement: HTMLElement;
}

const ShareDialog = ({
appElement,
canSetShareAccess,
isLoading,
isOpen,
item,
onCancel,
onShareAccessChange,
parentElement,
}: ShareDialogProps) => {
const { formatMessage } = useIntl();
let textInput = null;

const copy = () => {
if (textInput instanceof HTMLInputElement) {
textInput.select();
document.execCommand('copy');
}
};

const { shared_link: sharedLink }: BoxItem = item;
const { url } = sharedLink || {
url: formatMessage(messages.shareDialogNone),
};

return (
<Modal
appElement={appElement}
className={CLASS_MODAL_CONTENT}
contentLabel={formatMessage(messages.shareDialogLabel)}
isOpen={isOpen}
onRequestClose={onCancel}
overlayClassName={CLASS_MODAL_OVERLAY}
parentSelector={() => parentElement}
portalClassName={`${CLASS_MODAL} be-modal-share`}
>
<BlueprintModal.Body>
<Text as="label">
<FormattedMessage {...messages.shareDialogText} />
</Text>
<div className="be-modal-input-group">
<input
ref={input => {
textInput = input;
}}
onChange={noop}
type="text"
value={url}
/>
<Button autoFocus className="be-modal-button-copy" onClick={copy} variant="primary">
{formatMessage(messages.copy)}
</Button>
</div>

<BlueprintModal.Footer className="be-modal-btns">
<ShareAccessSelect
canSetShareAccess={canSetShareAccess}
className="bce-shared-access-select"
item={item}
onChange={onShareAccessChange}
/>
<Button
loading={isLoading}
loadingAriaLabel={formatMessage(messages.loading)}
onClick={onCancel}
size="large"
variant="secondary"
>
{formatMessage(messages.close)}
</Button>
</BlueprintModal.Footer>
</BlueprintModal.Body>
</Modal>
);
};

export default ShareDialog;

This file was deleted.

53 changes: 53 additions & 0 deletions src/elements/content-explorer/__tests__/ShareDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen, waitFor } from '../../../test-utils/testing-library';
import ShareDialog, { ShareDialogProps } from '../ShareDialog';

jest.mock('react-modal', () => {
return jest.fn(({ children }) => <div aria-label="Share">{children}</div>);
});

describe('elements/content-explorer/ShareDialog', () => {
const defaultProps = {
appElement: document.createElement('div'),
canSetShareAccess: true,
isLoading: false,
isOpen: true,
item: { shared_link: { url: 'http://example.com' } },
onCancel: jest.fn(),
onShareAccessChange: jest.fn(),
parentElement: document.createElement('div'),
};

const renderComponent = (props?: Partial<ShareDialogProps>) => render(<ShareDialog {...defaultProps} {...props} />);

test('renders', async () => {
renderComponent();
expect(await screen.findByLabelText('Share')).toBeInTheDocument();
expect(await screen.findByRole('button', { name: 'Close' }));
});

test('calls onCancel when Close button is clicked', async () => {
renderComponent();
userEvent.click(await screen.findByRole('button', { name: 'Close' }));
await waitFor(() => expect(defaultProps.onCancel).toHaveBeenCalled());
});

test('copies URL to clipboard when Copy button is clicked', async () => {
renderComponent();
const copyButton = await screen.findByRole('button', { name: 'Copy' });
document.execCommand = jest.fn();
userEvent.click(copyButton);
await waitFor(() => expect(document.execCommand).toHaveBeenCalledWith('copy'));
});

test('renders loading state when isLoading is true', async () => {
renderComponent({ isLoading: true });
expect(await screen.findByRole('status', { name: 'Loading' })).toBeInTheDocument();
});

test('renders with empty input when item has no shared link', async () => {
renderComponent({ item: { shared_link: null } });
expect(await screen.findByRole('textbox')).toHaveValue('None');
});
});
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
// @flow
import * as React from 'react';
import { useArgs } from '@storybook/preview-api';
import { Button } from '@box/blueprint-web';

import PrimaryButton from '../../../components/primary-button/PrimaryButton';
import { ACCESS_OPEN } from '../../../constants';
import { ACCESS_COLLAB, ACCESS_COMPANY, ACCESS_OPEN } from '../../../constants';
import { addRootElement } from '../../../utils/storybook';

import ShareDialog from '../ShareDialog';
import ShareDialog, { ShareDialogProps } from '../ShareDialog';

// need to import this into the story because it's usually in ContentExplorer
import '../../common/modal.scss';

export const shareDialog = {
render: (args: any) => {
render: (args: ShareDialogProps) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [, setArgs] = useArgs();

Expand All @@ -37,7 +36,10 @@ export const shareDialog = {
parentElement={rootElement}
{...args}
/>
<PrimaryButton onClick={handleOpenModal}>Launch ShareDialog</PrimaryButton>

<Button onClick={handleOpenModal} variant="primary">
Launch ShareDialog
</Button>
</div>
);
},
Expand All @@ -47,8 +49,19 @@ export default {
title: 'Elements/ContentExplorer',
component: ShareDialog,
args: {
canSetShareAccess: false,
canSetShareAccess: true,
isLoading: false,
isOpen: false,
item: {
allowed_shared_link_access_levels: [ACCESS_COLLAB, ACCESS_COMPANY, ACCESS_OPEN],
id: 'abcdefg',
permissions: {
can_set_share_access: true,
},
shared_link: {
access: ACCESS_OPEN,
url: 'https://cloud.box.com/s/abcdefg',
},
},
},
};
Loading

0 comments on commit 1728cfc

Please sign in to comment.