mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
* feat(gsap): add innerText support to GSAP inspector for counter animations (#1244) Adds 'innerText' as a supported GSAP property so number roll-up animations (count-up from 0 to some value) are visible and editable in the GSAP inspector panel. - Add 'innerText' to SUPPORTED_PROPS in gsapConstants.ts - Add label 'Counter Value', tooltip, and step constraint (1) in gsapAnimationConstants.ts The snap modifier that controls integer rounding is already preserved verbatim via the EXTRAS_KEYS round-trip, so rounding behavior survives edits without any additional UI changes. Closes #1179 * feat(registry): add text-effects catalog section and morph-text component Introduces a new "Text Effects" catalog section (below Effects) for text-focused visual components. - Add `text-effects` BlockCategory to core registry types with violet color - Add `text-effect` tag resolver in resolveBlockCategory (checked before generic `effect` tag) - Tag caption-blend-difference, texture-mask-text, and morph-text with `text-effect` - Update studio catalog order and color map to include text-effects - Add morph-text component: gooey SVG threshold morph cycling through editable statements using GSAP seekable proxy pattern for deterministic/seekable rendering Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(registry): add morph-text preview video Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(registry): fix morph-text.html formatting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(catalog): add Text Effects section and morph-text page Moves caption-blend-difference and texture-mask-text out of Effects into a new "Text Effects" section below it. Adds morph-text component page with install instructions and preview video. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(registry): add demo.html for morph-text catalog preview rendering Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(registry): address PR review feedback on morph-text and text-effects - Restore `effect` tag on caption-blend-difference and texture-mask-text alongside `text-effect` so existing tag-equality searches/analytics still match - Fix morphPause script fallback from "0.25" to "1.5" to match data attribute default - Add Math.max(0, ...) guard to blur values (intent clarity) - Add prefers-reduced-motion: skip morph and show first word statically - Remove CATEGORY_ORDER record from useBlockCatalog; derive order from BLOCK_CATEGORIES array (single source of truth, no drift) - Add comment to demo.html documenting its purpose (catalog preview script only) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
78 lines
2.2 KiB
TypeScript
78 lines
2.2 KiB
TypeScript
import { useState, useEffect, useMemo } from "react";
|
|
import type { RegistryItem } from "@hyperframes/core/registry";
|
|
import {
|
|
BLOCK_CATEGORIES,
|
|
type BlockCategory,
|
|
resolveBlockCategory,
|
|
} from "../utils/blockCategories";
|
|
|
|
export type CatalogItem = RegistryItem & {
|
|
category: BlockCategory;
|
|
};
|
|
|
|
export function useBlockCatalog() {
|
|
const [blocks, setBlocks] = useState<CatalogItem[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [search, setSearch] = useState("");
|
|
const [category, setCategory] = useState<BlockCategory | null>(null);
|
|
|
|
// fallow-ignore-next-line complexity
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
(async () => {
|
|
try {
|
|
const res = await fetch("/api/registry/blocks");
|
|
if (!res.ok) throw new Error("Failed to load catalog");
|
|
const data = (await res.json()) as RegistryItem[];
|
|
if (cancelled) return;
|
|
const items = data
|
|
.map((b) => ({ ...b, category: resolveBlockCategory(b.tags) }))
|
|
.sort((a, b) => {
|
|
const ia = BLOCK_CATEGORIES.findIndex((c) => c.id === a.category);
|
|
const ib = BLOCK_CATEGORIES.findIndex((c) => c.id === b.category);
|
|
return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
|
|
});
|
|
setBlocks(items);
|
|
} catch (err) {
|
|
if (cancelled) return;
|
|
setError(err instanceof Error ? err.message : "Failed to load catalog");
|
|
} finally {
|
|
if (!cancelled) setLoading(false);
|
|
}
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, []);
|
|
|
|
const filteredBlocks = useMemo(() => {
|
|
let result = blocks;
|
|
if (category) {
|
|
result = result.filter((b) => b.category === category);
|
|
}
|
|
if (search.trim()) {
|
|
const q = search.toLowerCase();
|
|
result = result.filter(
|
|
(b) =>
|
|
b.title.toLowerCase().includes(q) ||
|
|
b.description.toLowerCase().includes(q) ||
|
|
b.category.toLowerCase().includes(q) ||
|
|
b.tags?.some((t) => t.toLowerCase().includes(q)),
|
|
);
|
|
}
|
|
return result;
|
|
}, [blocks, category, search]);
|
|
|
|
return {
|
|
blocks,
|
|
loading,
|
|
error,
|
|
search,
|
|
setSearch,
|
|
category,
|
|
setCategory,
|
|
filteredBlocks,
|
|
};
|
|
}
|