feat: copy and paste fields (#1193)
Adds keyboard shortcuts for copying and pasting fields, additionally adds the ability to duplicate a field via the UI.
This commit is contained in:
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Settings2, Trash } from 'lucide-react';
|
||||
import { CopyPlus, Settings2, Trash } from 'lucide-react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Rnd } from 'react-rnd';
|
||||
import { match } from 'ts-pattern';
|
||||
@@ -38,7 +38,10 @@ export type FieldItemProps = {
|
||||
onResize?: (_node: HTMLElement) => void;
|
||||
onMove?: (_node: HTMLElement) => void;
|
||||
onRemove?: () => void;
|
||||
onDuplicate?: () => void;
|
||||
onAdvancedSettings?: () => void;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
recipientIndex?: number;
|
||||
hideRecipients?: boolean;
|
||||
};
|
||||
@@ -52,6 +55,9 @@ export const FieldItem = ({
|
||||
onResize,
|
||||
onMove,
|
||||
onRemove,
|
||||
onDuplicate,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onAdvancedSettings,
|
||||
recipientIndex = 0,
|
||||
hideRecipients = false,
|
||||
@@ -115,18 +121,29 @@ export const FieldItem = ({
|
||||
};
|
||||
}, [calculateCoords]);
|
||||
|
||||
const handleClickOutsideField = (event: MouseEvent) => {
|
||||
if (settingsActive && $el.current && !event.composedPath().includes($el.current)) {
|
||||
setSettingsActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.body.addEventListener('click', handleClickOutsideField);
|
||||
return () => {
|
||||
document.body.removeEventListener('click', handleClickOutsideField);
|
||||
const onClickOutsideOfField = (event: MouseEvent) => {
|
||||
const isOutsideOfField = $el.current && !event.composedPath().includes($el.current);
|
||||
|
||||
setSettingsActive((active) => {
|
||||
if (active && isOutsideOfField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return active;
|
||||
});
|
||||
|
||||
if (isOutsideOfField) {
|
||||
onBlur?.();
|
||||
}
|
||||
};
|
||||
}, [settingsActive]);
|
||||
|
||||
document.body.addEventListener('click', onClickOutsideOfField);
|
||||
|
||||
return () => {
|
||||
document.body.removeEventListener('click', onClickOutsideOfField);
|
||||
};
|
||||
}, [onBlur]);
|
||||
|
||||
const hasFieldMetaValues = (
|
||||
fieldType: string,
|
||||
@@ -189,6 +206,7 @@ export const FieldItem = ({
|
||||
)}
|
||||
onClick={() => {
|
||||
setSettingsActive((prev) => !prev);
|
||||
onFocus?.();
|
||||
}}
|
||||
ref={$el}
|
||||
>
|
||||
@@ -224,7 +242,7 @@ export const FieldItem = ({
|
||||
|
||||
{!disabled && settingsActive && (
|
||||
<div className="mt-1 flex justify-center">
|
||||
<div className="dark:bg-background group flex items-center justify-evenly rounded-md border gap-x-1 bg-gray-900 p-0.5">
|
||||
<div className="dark:bg-background group flex items-center justify-evenly gap-x-1 rounded-md border bg-gray-900 p-0.5">
|
||||
{advancedField && (
|
||||
<button
|
||||
className="dark:text-muted-foreground/50 dark:hover:text-muted-foreground dark:hover:bg-foreground/10 rounded-sm p-1.5 text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-100"
|
||||
@@ -234,6 +252,15 @@ export const FieldItem = ({
|
||||
<Settings2 className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
className="dark:text-muted-foreground/50 dark:hover:text-muted-foreground dark:hover:bg-foreground/10 rounded-sm p-1.5 text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-100"
|
||||
onClick={onDuplicate}
|
||||
onTouchEnd={onDuplicate}
|
||||
>
|
||||
<CopyPlus className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="dark:text-muted-foreground/50 dark:hover:text-muted-foreground dark:hover:bg-foreground/10 rounded-sm p-1.5 text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-100"
|
||||
onClick={onRemove}
|
||||
|
||||
Reference in New Issue
Block a user