various Validation Block buffs (#3919)

This commit is contained in:
Jonathan Dobson
2025-11-05 16:37:48 -05:00
committed by GitHub
parent 0ad6a7c0bd
commit f0172a22df
4 changed files with 114 additions and 19 deletions

View File

@@ -0,0 +1,92 @@
import { useEffect, useRef, useState } from "react";
interface Props {
trigger: "render" | "viewport";
}
function NoticeMe({ children, trigger }: React.PropsWithChildren<Props>) {
const [shouldAnimate, setShouldAnimate] = useState(trigger === "render");
const [shouldHide, setShouldHide] = useState(false);
const elementRef = useRef<HTMLDivElement>(null);
const hasExitedRef = useRef(false);
useEffect(() => {
if (trigger !== "viewport") return;
const element = elementRef.current;
if (!element) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Element is visible in viewport
if (hasExitedRef.current) {
// Force animation restart by removing then re-adding class
setShouldHide(false);
setShouldAnimate(false);
// Use setTimeout to ensure the class is removed before re-adding
setTimeout(() => {
setShouldAnimate(true);
}, 10);
hasExitedRef.current = false;
}
} else {
// Element is NOT visible in viewport (completely outside)
setShouldAnimate(false);
setShouldHide(true);
hasExitedRef.current = true;
}
});
},
{
threshold: 0,
rootMargin: "0px",
},
);
observer.observe(element);
return () => observer.disconnect();
}, [trigger]);
const getAnimationClass = () => {
if (shouldHide) return "notice-me-hidden";
if (shouldAnimate) return "notice-me-animate";
return "";
};
return (
<>
<div
ref={elementRef}
className={getAnimationClass()}
style={{ display: "flex" }}
>
{children}
</div>
<style>{`
.notice-me-hidden {
opacity: 0;
}
.notice-me-animate {
animation: notice-fade-up 0.5s ease-out;
}
@keyframes notice-fade-up {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
`}</style>
</>
);
}
export { NoticeMe };

View File

@@ -61,7 +61,7 @@ function MicroDropdown({ selections, selected, onChange }: Props) {
<div ref={dropdownRef} className="relative inline-block">
<div className="flex items-center gap-1">
<div
className="relative inline-flex p-0 text-xs text-slate-400"
className="relative inline-flex p-0 text-xs text-[#00d2ff]"
onClick={() => {
if (!isOpen && dropdownRef.current) {
const rect = dropdownRef.current.getBoundingClientRect();

View File

@@ -6,6 +6,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { getClient } from "@/api/AxiosClient";
import { ProxyLocation, Status } from "@/api/types";
import { NoticeMe } from "@/components/NoticeMe";
import { StatusBadge } from "@/components/StatusBadge";
import { toast } from "@/components/ui/use-toast";
import { useLogging } from "@/hooks/useLogging";
@@ -531,24 +532,26 @@ function NodeHeader({
<span className="text-xs text-slate-400">
{transmutations.blockTitle}
</span>
<MicroDropdown
selections={[
transmutations.self,
...transmutations.others.map((t) => t.label),
]}
selected={transmutations.self}
onChange={(label) => {
const transmutation = transmutations.others.find(
(t) => t.label === label,
);
<NoticeMe trigger="viewport">
<MicroDropdown
selections={[
transmutations.self,
...transmutations.others.map((t) => t.label),
]}
selected={transmutations.self}
onChange={(label) => {
const transmutation = transmutations.others.find(
(t) => t.label === label,
);
if (!transmutation) {
return;
}
if (!transmutation) {
return;
}
transmuteNodeCallback(nodeId, transmutation.nodeName);
}}
/>
transmuteNodeCallback(nodeId, transmutation.nodeName);
}}
/>
</NoticeMe>
</div>
) : (
<span className="text-xs text-slate-400">{blockTitle}</span>

View File

@@ -84,8 +84,8 @@ const nodeLibraryItems: Array<{
className="size-6"
/>
),
title: "Validation Block",
description: "Validate completion criteria",
title: "AI or Human Validation",
description: "Have an AI or Human validate the state of the screen",
},
/**
* The Human Interaction block can be had via a transmutation of the