4599-feat(front): Add Copy Button to Floating Inputs (#4789)

Closes #4599 

**Changes:**
- Added copy button to floating inputs of Text, Number, Phone, Link and
Email fields.

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Weiko <corentin@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Anchit Sinha
2024-05-14 20:32:53 +05:30
committed by GitHub
parent a53ce1c488
commit ce195826f5
12 changed files with 137 additions and 31 deletions

View File

@ -0,0 +1,37 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconCopy } from 'twenty-ui';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
const StyledButtonContainer = styled.div`
padding: 0 ${({ theme }) => theme.spacing(1)};
`;
export type LightCopyIconButtonProps = {
copyText: string;
};
export const LightCopyIconButton = ({ copyText }: LightCopyIconButtonProps) => {
const { enqueueSnackBar } = useSnackBar();
const theme = useTheme();
return (
<StyledButtonContainer>
<LightIconButton
className="copy-button"
Icon={IconCopy}
onClick={() => {
enqueueSnackBar('Text copied to clipboard', {
variant: 'success',
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
});
navigator.clipboard.writeText(copyText);
}}
aria-label="Copy to Clipboard"
/>
</StyledButtonContainer>
);
};

View File

@ -4,6 +4,7 @@ import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldMetadataType } from '~/generated/graphql';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useEmailField } from '../../../hooks/useEmailField';
@ -104,7 +105,7 @@ const meta: Meta = {
onTab: { control: false },
onShiftTab: { control: false },
},
decorators: [clearMocksDecorator],
decorators: [clearMocksDecorator, SnackBarDecorator],
parameters: {
clearMocks: true,
},

View File

@ -4,6 +4,7 @@ import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldMetadataType } from '~/generated/graphql';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useNumberField } from '../../../hooks/useNumberField';
@ -105,7 +106,7 @@ const meta: Meta = {
onTab: { control: false },
onShiftTab: { control: false },
},
decorators: [clearMocksDecorator],
decorators: [clearMocksDecorator, SnackBarDecorator],
parameters: {
clearMocks: true,
},

View File

@ -4,6 +4,7 @@ import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldMetadataType } from '~/generated/graphql';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { usePhoneField } from '../../../hooks/usePhoneField';
@ -105,7 +106,7 @@ const meta: Meta = {
onTab: { control: false },
onShiftTab: { control: false },
},
decorators: [clearMocksDecorator],
decorators: [clearMocksDecorator, SnackBarDecorator],
parameters: {
clearMocks: true,
},

View File

@ -4,6 +4,7 @@ import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldMetadataType } from '~/generated/graphql';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useTextField } from '../../../hooks/useTextField';
@ -104,7 +105,7 @@ const meta: Meta = {
onTab: { control: false },
onShiftTab: { control: false },
},
decorators: [clearMocksDecorator],
decorators: [clearMocksDecorator, SnackBarDecorator],
parameters: {
clearMocks: true,
},

View File

@ -6,6 +6,7 @@ import { isDefined } from '~/utils/isDefined';
export const useRegisterInputEvents = <T>({
inputRef,
copyRef,
inputValue,
onEscape,
onEnter,
@ -15,6 +16,7 @@ export const useRegisterInputEvents = <T>({
hotkeyScope,
}: {
inputRef: React.RefObject<any>;
copyRef?: React.RefObject<any>;
inputValue: T;
onEscape: (inputValue: T) => void;
onEnter: (inputValue: T) => void;
@ -24,10 +26,9 @@ export const useRegisterInputEvents = <T>({
hotkeyScope: string;
}) => {
useListenClickOutside({
refs: [inputRef],
refs: [inputRef, copyRef].filter(isDefined),
callback: (event) => {
event.stopImmediatePropagation();
onClickOutside?.(event, inputValue);
},
enabled: isDefined(onClickOutside),