feat(studio): show Add button for components, Ask agent for all

Components (hyperframes:component) get both "Add" and "Ask agent"
buttons since they work as drop-in overlays. Blocks (scenes, data,
VFX, transitions) show only "Ask agent" since they need agent-guided
customization.
This commit is contained in:
Miguel Ángel
2026-05-21 19:31:14 -04:00
parent fb48b8d9b4
commit 8c698d7f2c
2 changed files with 60 additions and 22 deletions
@@ -15,11 +15,12 @@ export interface BlockPreviewInfo {
}
interface BlocksTabProps {
onAddBlock?: (blockName: string) => void;
onPreviewBlock?: (preview: BlockPreviewInfo | null) => void;
}
// fallow-ignore-next-line complexity
export const BlocksTab = memo(function BlocksTab({ onPreviewBlock }: BlocksTabProps) {
export const BlocksTab = memo(function BlocksTab({ onAddBlock, onPreviewBlock }: BlocksTabProps) {
const { loading, error, search, setSearch, category, setCategory, filteredBlocks } =
useBlockCatalog();
@@ -117,6 +118,11 @@ export const BlocksTab = memo(function BlocksTab({ onPreviewBlock }: BlocksTabPr
posterUrl={block.preview?.poster}
videoUrl={block.preview?.video}
onPreview={onPreviewBlock}
onAdd={
block.type === "hyperframes:component"
? () => onAddBlock?.(block.name)
: undefined
}
/>
);
})}
@@ -280,9 +286,11 @@ function BlockCard({
tags?: string[];
posterUrl?: string;
videoUrl?: string;
onAdd?: () => void;
onPreview?: (preview: BlockPreviewInfo | null) => void;
}) {
const [hovered, setHovered] = useState(false);
const [adding, setAdding] = useState(false);
const [copied, setCopied] = useState(false);
const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const colors = getCategoryColors(category);
@@ -310,6 +318,17 @@ function BlockCard({
};
}, []);
const handleAdd = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
if (adding || !onAdd) return;
setAdding(true);
onAdd();
setTimeout(() => setAdding(false), 1000);
},
[adding],
);
const { activeCompPath, compositionDimensions } = useStudioContext();
const handleCopyPrompt = useCallback(
@@ -372,28 +391,46 @@ function BlockCard({
</div>
)}
{/* Ask agent overlay */}
<button
type="button"
onClick={handleCopyPrompt}
className="absolute inset-0 flex items-center justify-center gap-1.5 bg-black/60 opacity-0 group-hover/card:opacity-100 transition-opacity"
>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="text-white"
{/* Action overlay */}
<div className="absolute inset-0 flex flex-col items-center justify-center gap-1.5 bg-black/60 opacity-0 group-hover/card:opacity-100 transition-opacity">
{onAdd && (
<button
type="button"
onClick={handleAdd}
className="flex items-center gap-1 px-3 py-1.5 rounded-md bg-white text-black text-[10px] font-semibold hover:bg-neutral-200 transition-colors"
>
<svg
width="10"
height="10"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
>
<path d="M12 5v14M5 12h14" />
</svg>
{adding ? "Added" : "Add"}
</button>
)}
<button
type="button"
onClick={handleCopyPrompt}
className={`flex items-center gap-1 px-3 ${onAdd ? "py-1" : "py-1.5"} rounded-md ${onAdd ? "bg-white/15 text-white/90 text-[9px]" : "bg-white text-black text-[10px] font-semibold"} hover:bg-white/25 transition-colors`}
>
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
<span className="text-[10px] font-semibold text-white">
<svg
width={onAdd ? 9 : 12}
height={onAdd ? 9 : 12}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
{copied ? "Copied!" : "Ask agent"}
</span>
</button>
</button>
</div>
{/* Badges */}
<div className="absolute top-1 right-1 flex items-center gap-0.5 pointer-events-none">
@@ -84,6 +84,7 @@ export const LeftSidebar = memo(
onLint,
linting,
onToggleCollapse,
onAddBlock,
onPreviewBlock,
takeoverContent,
},
@@ -246,7 +247,7 @@ export const LeftSidebar = memo(
)}
{STUDIO_BLOCKS_PANEL_ENABLED && tab === "blocks" && (
<BlocksTab onPreviewBlock={onPreviewBlock} />
<BlocksTab onAddBlock={onAddBlock} onPreviewBlock={onPreviewBlock} />
)}
{/* Lint button pinned at the bottom */}