TextReplace: DND UX
This commit is contained in:
parent
3b617d4f02
commit
3b09e2cdac
2 changed files with 65 additions and 13 deletions
|
|
@ -178,9 +178,12 @@ function normalizeRule(rule: Rule) {
|
|||
rule.id ??= crypto.randomUUID();
|
||||
}
|
||||
|
||||
type DropIndicator = { index: number; position: "before" | "after"; };
|
||||
|
||||
function TextReplace({ title, description, rulesArray, isRegex = false }: TextReplaceProps) {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
||||
const [dropIndicator, setDropIndicator] = useState<DropIndicator | null>(null);
|
||||
|
||||
function onClickRemove(index: number) {
|
||||
rulesArray.splice(index, 1);
|
||||
|
|
@ -209,12 +212,35 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
|||
return acc;
|
||||
}, []);
|
||||
|
||||
const handleDrop = (index: number) => (e: React.DragEvent) => {
|
||||
const handleDragOver = (index: number) => (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
if (draggedIndex === null || draggedIndex === index || searchQuery) return;
|
||||
const draggedRule = rulesArray.splice(draggedIndex, 1)[0];
|
||||
rulesArray.splice(index, 0, draggedRule);
|
||||
if (draggedIndex === null || searchQuery) return;
|
||||
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const position = e.clientY - rect.top < rect.height / 2 ? "before" : "after";
|
||||
setDropIndicator(prev =>
|
||||
prev?.index === index && prev.position === position ? prev : { index, position }
|
||||
);
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
if (draggedIndex === null || !dropIndicator || searchQuery) {
|
||||
setDraggedIndex(null);
|
||||
setDropIndicator(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let targetIndex = dropIndicator.index + (dropIndicator.position === "after" ? 1 : 0);
|
||||
if (targetIndex > draggedIndex) targetIndex -= 1;
|
||||
|
||||
if (targetIndex !== draggedIndex) {
|
||||
const draggedRule = rulesArray.splice(draggedIndex, 1)[0];
|
||||
rulesArray.splice(targetIndex, 0, draggedRule);
|
||||
}
|
||||
|
||||
setDraggedIndex(null);
|
||||
setDropIndicator(null);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -222,11 +248,13 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
|||
<div>
|
||||
<HeadingSecondary>{title}</HeadingSecondary>
|
||||
<Paragraph>{description}</Paragraph>
|
||||
<TextInput
|
||||
placeholder="Search for a rule..."
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
<div className={cl("search-input")}>
|
||||
<TextInput
|
||||
placeholder="Search for a rule..."
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Flex flexDirection="column" style={{ gap: "0.5em", paddingBottom: "1.25em" }}>
|
||||
{!filteredRules.length && searchQuery && (
|
||||
|
|
@ -235,10 +263,16 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
|||
{filteredRules.map(({ rule, index }) =>
|
||||
<div
|
||||
key={rule.id}
|
||||
onDragOver={e => e.preventDefault()}
|
||||
onDrop={handleDrop(index)}
|
||||
style={{ opacity: draggedIndex === index ? 0.5 : 1 }}
|
||||
onDragOver={handleDragOver(index)}
|
||||
onDrop={handleDrop}
|
||||
style={{ opacity: draggedIndex === index ? 0.5 : 1, position: "relative" }}
|
||||
>
|
||||
{dropIndicator?.index === index && dropIndicator.position === "before" && (
|
||||
<div className={cl("drop-indicator")} style={{ top: 0 }} />
|
||||
)}
|
||||
{dropIndicator?.index === index && dropIndicator.position === "after" && (
|
||||
<div className={cl("drop-indicator")} style={{ bottom: 0 }} />
|
||||
)}
|
||||
<ExpandableSection
|
||||
renderContent={() => (
|
||||
<>
|
||||
|
|
@ -293,7 +327,10 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
|||
setDraggedIndex(index);
|
||||
e.dataTransfer.setData("text/plain", index.toString());
|
||||
}}
|
||||
onDragEnd={() => setDraggedIndex(null)}
|
||||
onDragEnd={() => {
|
||||
setDraggedIndex(null);
|
||||
setDropIndicator(null);
|
||||
}}
|
||||
style={{ cursor: searchQuery ? "default" : "grab", flex: 1 }}
|
||||
>
|
||||
<Paragraph weight="medium" size="md">
|
||||
|
|
|
|||
|
|
@ -8,4 +8,19 @@
|
|||
.vc-textReplace-delete-button {
|
||||
width: 100%;
|
||||
margin-block: 0.75em 0.5em
|
||||
}
|
||||
|
||||
.vc-textReplace-drop-indicator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--brand-experiment, #5865f2);
|
||||
border-radius: 1px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vc-textReplace-search-input {
|
||||
margin-top: 8px;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue