Skip to content
This repository has been archived by the owner on Sep 26, 2024. It is now read-only.

Commit

Permalink
feat(UIKIT-1772,CopyTypography): Реализован новый компонент CopyTypog…
Browse files Browse the repository at this point in the history
…raphy (#1142)
  • Loading branch information
pan1caisreal authored Sep 19, 2024
1 parent 658f37b commit d2a070e
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 0 deletions.
94 changes: 94 additions & 0 deletions packages/components/src/CopyTypography/CopyTypography.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { type Meta, type StoryObj } from '@storybook/react';

import { Typography } from '../Typography';
import { styled } from '../styles';
import { OverflowTypography } from '../OverflowTypography';

import { CopyTypography } from './CopyTypography';

/**
* ### [Figma]()
* ### [Guide]()
* Компонент позволяет скопировать содержимое в буфер обмена
*/

const meta: Meta<typeof CopyTypography> = {
title: 'Components/Data Display/CopyTypography',
component: CopyTypography,
};

export default meta;

type Story = StoryObj<typeof CopyTypography>;

export const Interaction: Story = {
args: {
children: <Typography>Швецова М. Д.</Typography>,
copyText: 'Швецова М. Д.',
},
parameters: {
docs: {
disable: true,
},
},
};

const Wrapper = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(4)};
`;

const OverflowWrapper = styled.div`
width: 150px;
`;

export const Example = () => {
return <CopyTypography>Швецова М. Д.</CopyTypography>;
};

/**
* prop `copyPosition` определяет расположение иконки(справа/слева от текста). По умолчанию справа
*/
export const CopyPosition = () => {
return (
<Wrapper>
<CopyTypography>Швецова М. Д.</CopyTypography>
<CopyTypography copyPosition="left">Швецова М. Д.</CopyTypography>
</Wrapper>
);
};

/**
* prop `copyText` указывает какой текст необходимо скопировать в буфер обмена.
* Необходим для копирования текста вложенных компонентов или когда копируемое содержимое
* должно отличаться от представления.
*/
export const CopyText = () => {
return (
<Wrapper>
<CopyTypography copyText="Швецова Мария Дмитриевна">
Швецова М. Д.
</CopyTypography>
<CopyTypography copyText="Швецова М. Д.">
<Typography>Швецова М. Д.</Typography>
</CopyTypography>
</Wrapper>
);
};

/**
* prop `isShowCopyText` показывает в тултипе текст, который будет скопирован.
* Необходимо отключать тултип у вложенных компонентов, при их наличии, для избежания их наложения
*/
export const IsShowCopyText = () => {
return (
<OverflowWrapper>
<CopyTypography copyText="Швецова Мария Дмитриевна" isShowCopyText>
<OverflowTypography tooltipProps={{ title: undefined }}>
Швецова Мария Дмитриевна
</OverflowTypography>
</CopyTypography>
</OverflowWrapper>
);
};
39 changes: 39 additions & 0 deletions packages/components/src/CopyTypography/CopyTypography.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, expect, it, vi } from 'vitest';
import { renderWithTheme, screen, userEvents } from '@astral/tests';

import { CopyTypography } from './CopyTypography';

describe('CopyTypography', () => {
it('Значение копируется в буфер обмена, при клике на компонент', async () => {
const copyText = 'it was copied';

const writeTextSpy = vi.fn(() => Promise.resolve());

Object.assign(navigator, { clipboard: { writeText: writeTextSpy } });
renderWithTheme(<CopyTypography>{copyText}</CopyTypography>);

const element = screen.getByText(copyText);

await userEvents.click(element);
expect(writeTextSpy).toBeCalled();
});

it('Значение копируется в буфер обмена, если children содержит ReactNode и заданном copyText', async () => {
const copyText = 'it was copied';

const writeTextSpy = vi.fn(() => Promise.resolve());

Object.assign(navigator, { clipboard: { writeText: writeTextSpy } });

renderWithTheme(
<CopyTypography copyText={copyText}>
<div>{copyText}</div>
</CopyTypography>,
);

const element = screen.getByText(copyText);

await userEvents.click(element);
expect(writeTextSpy).toBeCalled();
});
});
55 changes: 55 additions & 0 deletions packages/components/src/CopyTypography/CopyTypography.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { type TypographyProps } from '../Typography';
import { Tooltip } from '../Tooltip';

import { StyledCopyIcon, Wrapper } from './styles';
import { useLogic } from './useLogic';

export type CopyTypographyProps = TypographyProps & {
/**
* Текст, который будет скопирован. Перекрывает обычное копирование если children является строкой
*/
copyText?: string;
/**
* Отображает иконку слева или справа от текста
* @default right
*/
copyPosition?: 'right' | 'left';
/**
* Если `true`, в тултипе будет отображаться текст, который будет скопирован при нажатии
*/
isShowCopyText?: boolean;
};

export const CopyTypography = (props: CopyTypographyProps) => {
const {
children,
copyPosition = 'right',
copyText,
isShowCopyText,
color,
...restProps
} = props;

const renderIcon = () => (
<StyledCopyIcon $copyPosition={copyPosition} color={color as 'secondary'} />
);

const { handleMouseLeave, handleClick, tooltipTitle, isIconOnLeft } =
useLogic(props);

return (
<Tooltip title={tooltipTitle} disableInteractive placement="bottom">
<Wrapper
onMouseLeave={handleMouseLeave}
onClick={handleClick}
component="div"
color={color}
{...restProps}
>
{isIconOnLeft && renderIcon()}
{children}
{!isIconOnLeft && renderIcon()}
</Wrapper>
</Tooltip>
);
};
5 changes: 5 additions & 0 deletions packages/components/src/CopyTypography/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum CopyStatus {
Copied = 'Скопировано',
Error = 'Ошибка копирования',
CanCopy = 'Скопировать',
}
1 change: 1 addition & 0 deletions packages/components/src/CopyTypography/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './CopyTypography';
29 changes: 29 additions & 0 deletions packages/components/src/CopyTypography/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { CopyOutlineSm } from '@astral/icons';

import { styled } from '../styles';
import { Typography } from '../Typography';

export const Wrapper = styled(Typography)`
cursor: pointer;
display: flex;
align-items: center;
&:hover {
text-decoration: underline;
}
`;

export const StyledCopyIcon = styled(CopyOutlineSm, {
shouldForwardProp: (prop) => !['$copyPosition'].includes(prop),
})<{ $copyPosition: 'left' | 'right' }>`
margin-right: ${({ $copyPosition, theme }) =>
$copyPosition === 'left' ? theme.spacing(1) : ''};
margin-left: ${({ $copyPosition, theme }) =>
$copyPosition === 'right' ? theme.spacing(1) : ''};
/* Задаем размер иконки */
font-size: 16px;
fill: ${({ color }) => color};
`;
1 change: 1 addition & 0 deletions packages/components/src/CopyTypography/useLogic/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useLogic';
38 changes: 38 additions & 0 deletions packages/components/src/CopyTypography/useLogic/useLogic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { type SyntheticEvent, useState } from 'react';

import { type CopyTypographyProps } from '../CopyTypography';
import { CopyStatus } from '../enums';

type UseLogicParams = CopyTypographyProps;

export const useLogic = ({
children,
copyText,
isShowCopyText,
copyPosition,
}: UseLogicParams) => {
const [status, setStatus] = useState<CopyStatus>(CopyStatus.CanCopy);

const handleMouseLeave = () => {
if (status !== CopyStatus.CanCopy) {
setTimeout(() => {
setStatus(CopyStatus.CanCopy);
}, 100);
}
};

const handleClick = (event: SyntheticEvent<HTMLElement>) => {
event.stopPropagation();

navigator.clipboard
.writeText(copyText || (typeof children === 'string' ? children : ''))
.then(() => setStatus(CopyStatus.Copied))
.catch(() => setStatus(CopyStatus.Error));
};

const tooltipTitle = isShowCopyText ? `${status}: ${copyText}` : status;

const isIconOnLeft = copyPosition === 'left';

return { handleMouseLeave, handleClick, tooltipTitle, isIconOnLeft };
};
2 changes: 2 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export * from './ConfirmDialog';

export * from './ContentState';

export { CopyTypography, type CopyTypographyProps } from './CopyTypography';

export * from './DashboardLayout';

export {
Expand Down

0 comments on commit d2a070e

Please sign in to comment.