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();
|
rule.id ??= crypto.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DropIndicator = { index: number; position: "before" | "after"; };
|
||||||
|
|
||||||
function TextReplace({ title, description, rulesArray, isRegex = false }: TextReplaceProps) {
|
function TextReplace({ title, description, rulesArray, isRegex = false }: TextReplaceProps) {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
||||||
|
const [dropIndicator, setDropIndicator] = useState<DropIndicator | null>(null);
|
||||||
|
|
||||||
function onClickRemove(index: number) {
|
function onClickRemove(index: number) {
|
||||||
rulesArray.splice(index, 1);
|
rulesArray.splice(index, 1);
|
||||||
|
|
@ -209,12 +212,35 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDrop = (index: number) => (e: React.DragEvent) => {
|
const handleDragOver = (index: number) => (e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (draggedIndex === null || draggedIndex === index || searchQuery) return;
|
if (draggedIndex === null || searchQuery) return;
|
||||||
const draggedRule = rulesArray.splice(draggedIndex, 1)[0];
|
|
||||||
rulesArray.splice(index, 0, draggedRule);
|
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);
|
setDraggedIndex(null);
|
||||||
|
setDropIndicator(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -222,11 +248,13 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
||||||
<div>
|
<div>
|
||||||
<HeadingSecondary>{title}</HeadingSecondary>
|
<HeadingSecondary>{title}</HeadingSecondary>
|
||||||
<Paragraph>{description}</Paragraph>
|
<Paragraph>{description}</Paragraph>
|
||||||
<TextInput
|
<div className={cl("search-input")}>
|
||||||
placeholder="Search for a rule..."
|
<TextInput
|
||||||
value={searchQuery}
|
placeholder="Search for a rule..."
|
||||||
onChange={setSearchQuery}
|
value={searchQuery}
|
||||||
/>
|
onChange={setSearchQuery}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Flex flexDirection="column" style={{ gap: "0.5em", paddingBottom: "1.25em" }}>
|
<Flex flexDirection="column" style={{ gap: "0.5em", paddingBottom: "1.25em" }}>
|
||||||
{!filteredRules.length && searchQuery && (
|
{!filteredRules.length && searchQuery && (
|
||||||
|
|
@ -235,10 +263,16 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
||||||
{filteredRules.map(({ rule, index }) =>
|
{filteredRules.map(({ rule, index }) =>
|
||||||
<div
|
<div
|
||||||
key={rule.id}
|
key={rule.id}
|
||||||
onDragOver={e => e.preventDefault()}
|
onDragOver={handleDragOver(index)}
|
||||||
onDrop={handleDrop(index)}
|
onDrop={handleDrop}
|
||||||
style={{ opacity: draggedIndex === index ? 0.5 : 1 }}
|
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
|
<ExpandableSection
|
||||||
renderContent={() => (
|
renderContent={() => (
|
||||||
<>
|
<>
|
||||||
|
|
@ -293,7 +327,10 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
||||||
setDraggedIndex(index);
|
setDraggedIndex(index);
|
||||||
e.dataTransfer.setData("text/plain", index.toString());
|
e.dataTransfer.setData("text/plain", index.toString());
|
||||||
}}
|
}}
|
||||||
onDragEnd={() => setDraggedIndex(null)}
|
onDragEnd={() => {
|
||||||
|
setDraggedIndex(null);
|
||||||
|
setDropIndicator(null);
|
||||||
|
}}
|
||||||
style={{ cursor: searchQuery ? "default" : "grab", flex: 1 }}
|
style={{ cursor: searchQuery ? "default" : "grab", flex: 1 }}
|
||||||
>
|
>
|
||||||
<Paragraph weight="medium" size="md">
|
<Paragraph weight="medium" size="md">
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,19 @@
|
||||||
.vc-textReplace-delete-button {
|
.vc-textReplace-delete-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-block: 0.75em 0.5em
|
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