Skip to content

Commit

Permalink
Add onCopy property to Copiable component (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
badiuoanaalexandra authored Nov 21, 2024
1 parent d9d81fd commit 25a0920
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 17 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Changelog

## 2.12.0
- [feature] add possibility to pass onCopy to Copiable component

## 2.11.0
- [feature] support onOpenChange handler for contextMenu

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mapbox/mr-ui",
"version": "2.11.0",
"version": "2.12.0",
"description": "UI components for Mapbox projects",
"main": "index.js",
"homepage": "./",
Expand Down
48 changes: 41 additions & 7 deletions src/components/copiable/copiable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { act, render, screen, waitFor } from '@testing-library/react';
import CopyButton from '../copy-button';
import Copiable from './copiable';
import select from 'select';
import userEvent from '@testing-library/user-event';

const FEEDBACK_TIME = 2000;

Expand All @@ -13,20 +14,20 @@ jest.mock('os-key', () =>
);

describe('Copiable', () => {

describe('basic', () => {
const props = {
value: 'thetextyoucopythetextyoucopythetextyoucopythetext you copy the text you copy '
value:
'thetextyoucopythetextyoucopythetextyoucopythetext you copy the text you copy '
};

test('renders as expected', () => {
const { baseElement } = render(<Copiable {...props} />)
const { baseElement } = render(<Copiable {...props} />);
expect(baseElement).toMatchSnapshot();
});

test('calls select library with element holding text to copy', async () => {
jest.spyOn(CopyButton, 'isCopySupported').mockImplementation(() => true);
render(<Copiable {...props} />)
render(<Copiable {...props} />);

act(() => {
screen.getByTestId('copiable-text-el').focus();
Expand All @@ -39,7 +40,7 @@ describe('Copiable', () => {

test('shows copy hint when focused', async () => {
jest.spyOn(CopyButton, 'isCopySupported').mockImplementation(() => true);
render(<Copiable {...props} />)
render(<Copiable {...props} />);

act(() => {
screen.getByTestId('copiable-text-el').focus();
Expand All @@ -58,12 +59,45 @@ describe('Copiable', () => {
describe('truncated', () => {
const props = {
truncated: true,
value: 'the text you copy the text you copy the text you copy the text you copy the text you copy '
value:
'the text you copy the text you copy the text you copy the text you copy the text you copy '
};

test('renders as expected', () => {
const { baseElement } = render(<Copiable {...props} />)
const { baseElement } = render(<Copiable {...props} />);
expect(baseElement).toMatchSnapshot();
});
});

describe('onCopy', () => {
const mockOnCopy = jest.fn();
const text =
'the text you copy the text you copy the text you copy the text you copy the text you copy';

const props = {
onCopy: mockOnCopy,
value: text
};

test('calls onCopy', async () => {
jest.spyOn(CopyButton, 'isCopySupported').mockImplementation(() => true);
render(<Copiable {...props} />);

await userEvent.click(screen.getByTestId('copy-button'));

expect(mockOnCopy).toHaveBeenCalledTimes(1);
expect(mockOnCopy).toHaveBeenCalledWith(text);
});

test('calls onCopy when text is truncated', async () => {
jest.spyOn(CopyButton, 'isCopySupported').mockImplementation(() => true);
const propsWithTruncated = { ...props, truncated: true };
render(<Copiable {...propsWithTruncated} />);

await userEvent.click(screen.getByTestId('copy-button'));

expect(mockOnCopy).toHaveBeenCalledTimes(1);
expect(mockOnCopy).toHaveBeenCalledWith(text);
});
});
});
32 changes: 25 additions & 7 deletions src/components/copiable/copiable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { ReactElement, useState, useEffect, useRef, CSSProperties } from 'react';
import React, {
ReactElement,
useState,
useEffect,
useRef,
CSSProperties
} from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import CopyButton from '../copy-button';
Expand All @@ -25,6 +31,7 @@ interface Props {
value: string;
focusTrapPaused?: boolean;
truncated?: boolean;
onCopy?: (text: string) => void;
}

/**
Expand All @@ -39,6 +46,7 @@ interface Props {
export default function Copiable({
value,
focusTrapPaused,
onCopy,
truncated = false
}: Props): ReactElement {
const textEl = useRef(null);
Expand All @@ -58,7 +66,7 @@ export default function Copiable({
}, [copyTooltipActive]);

const handleTextFocus = () => {
if (typeof window === 'undefined') return
if (typeof window === 'undefined') return;
if (window.innerWidth < DISABLE_CLICK_TO_SELECT_THRESHOLD) return;
select(() => textEl.current);
setCopyTooltipActive(true);
Expand All @@ -76,12 +84,13 @@ export default function Copiable({
text={value}
block={true}
focusTrapPaused={focusTrapPaused}
onCopy={onCopy}
/>
</div>
);

const renderCopyHintText = () => {
if (typeof window === 'undefined') return
if (typeof window === 'undefined') return;

return (
<span>
Expand All @@ -90,8 +99,8 @@ export default function Copiable({
</span>{' '}
to copy
</span>
)
}
);
};

const textClasses = classnames('my3 txt-mono txt-s mr24', {
'txt-truncate': truncated
Expand All @@ -106,7 +115,11 @@ export default function Copiable({
<div className="relative clearfix bg-darken5 round">
{showCopyButton.current && renderCopyButton}
<Popover
content={<div className="txt-s">{showCopyButton.current && renderCopyHintText}</div>}
content={
<div className="txt-s">
{showCopyButton.current && renderCopyHintText}
</div>
}
active={copyTooltipActive}
placement="top"
alignment="center"
Expand Down Expand Up @@ -158,5 +171,10 @@ Copiable.propTypes = {
* Horizontal scrolling is not an option because of things end up getting
* pretty gross across browsers.
*/
truncated: PropTypes.bool
truncated: PropTypes.bool,
/**
* Invoked when the button is clicked.
* Passed one argument: the `text` prop.
*/
onCopy: PropTypes.func
};

0 comments on commit 25a0920

Please sign in to comment.