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:
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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),
|
||||
|
||||
Reference in New Issue
Block a user