dramaclaw-spec-render
v0.1.1
Published
JSON-spec driven UI renderer — turn flat { root, elements } trees into React components.
Maintainers
Readme
dramaclaw-spec-render
把扁平 JSON Spec({ root, elements })渲染为 React UI 的库。内置 6 种专用渲染器(角色展示、关键帧视频、草图画廊、剧集拆分、脚本概览、长篇故事)+ 通用树渲染兜底。
原始代码从 DramaClaw / schat 项目抽取,解耦了 store / presign / 弹窗回调,改由 Context 注入。
安装
npm install dramaclaw-spec-render
# peer deps
npm install react react-dom framer-motion lucide-react @radix-ui/react-dialog最小用法
import { SpecRenderer, SpecRendererProvider } from "dramaclaw-spec-render";
import "dramaclaw-spec-render/style.css";
export function App() {
return (
<SpecRendererProvider
// 把内部路径(如 /static/xxx/image.png)解析为可访问 URL
resolveMediaUrl={async (src) => await presign(src)}
// 判断哪些 URL 需要解析(返回 null 表示无需处理)
parseMediaUrl={(src) => /^(?:https?:\/\/[^/]+)?\/static\//.test(src) ? src : null}
// 用户点候选图时的回调(可选)
onCandidateSelect={(candidate) => sendWs(`我选择了「${candidate.label}」的图片`)}
// toast 回调(可选)
onToast={(msg, level) => toast[level](msg)}
>
<SpecRenderer spec={spec} mediaMaxWidth={720} />
</SpecRendererProvider>
);
}样式前置条件
组件用了 Tailwind class + 一份 jr-* 前缀的 CSS。包内自带预编译的
Tailwind utilities(扫描过组件源码,所有 w-[148px]、h-[198px] 这类
arbitrary 值都已打进 CSS),消费端无需再配 content / @source:
import "dramaclaw-spec-render/style.css"; // jr-* 自定义样式
import "dramaclaw-spec-render/tailwind.css"; // 预编译 Tailwind utilities预编译产物只含 theme + utilities 两层,不带 preflight/base,不会污染
消费端。消费端自身仍然可以用 Tailwind v3 或 v4(或完全不用)。
另外需要 定义 CSS 变量(shadcn 风格):--background, --foreground,
--border, --muted, --muted-foreground, --card, --primary,
--secondary, --secondary-foreground, --radius 等。直接用 shadcn 默认
主题即可。
如果你更想自己扫描包内类名(比如要 tree-shake 或保持全局主题一致), 可以跳过
tailwind.css导入,改为:
- Tailwind v3:在
tailwind.config.*的content里加"./node_modules/dramaclaw-spec-render/dist/**/*.{js,cjs}"- Tailwind v4:在入口 CSS 加
@source "../node_modules/dramaclaw-spec-render/dist";(注意 v4 默认 会排除 gitignored 路径,如有遗漏可直接改扫src/**/*.{ts,tsx},或保持 使用自带的tailwind.css)
导出
// 入口组件
export { SpecRenderer, SpecRendererProvider, useSpecRendererContext };
// 类型
export type { Spec, UIElement, ContentSegment };
export type { RenderContext, RootRendererFn, ComponentFn, Props };
export type { ImageCandidate, VideoDetailSection };
// 标准化 & 提取
export { normalizeSpec, flattenNestedSpec, normalizeSpecType };
// 底层渲染(可单独使用)
export { DefaultRootRenderer, renderElement, renderRootWithOverrides, renderElementWithOverrides };
// 专用渲染器注册表
export { SPEC_RENDERERS };
// 内部弹窗(调用方可直接用)
export { ImageDetailModal, VideoDetailModal };Context API
type SpecRendererContextValue = {
resolveMediaUrl?: (src: string) => Promise<string>;
parseMediaUrl?: (src: string) => string | null;
onCandidateSelect?: (candidate: ImageCandidate) => void;
onToast?: (message: string, level: "success" | "error" | "info") => void;
/**
* 占位视频 URL。未传时自动使用包内打包的 loading.mp4;
* 传 "" 可强制回退到 CSS 闪烁。
*/
loadingVideoUrl?: string;
};内置 loading 视频
包会把 src/assets/loading.mp4 打包到 dist/assets/loading.mp4,并通过
new URL("./assets/loading.mp4", import.meta.url) 模式引用。现代 bundler
(webpack 5 / turbopack / vite)会识别这个模式,自动把 mp4 从 node_modules
拷到消费端产物目录,无需手动配置。
想用自己的占位视频,仍可通过 loadingVideoUrl 覆盖:
import { SpecRendererProvider, LOADING_VIDEO_URL } from "dramaclaw-spec-render";
// 用自己的 CDN 视频
<SpecRendererProvider loadingVideoUrl="https://cdn.example.com/loading.mp4">...
// 或者显式引用包内资源
<SpecRendererProvider loadingVideoUrl={LOADING_VIDEO_URL}>...
// 关闭占位视频(回退 CSS 闪烁)
<SpecRendererProvider loadingVideoUrl="">...也可以通过 subpath 导入直接拿到 mp4 的静态 URL(需消费端 bundler 支持 .mp4
import,如 Vite 的 ?url):
import loadingMp4 from "dramaclaw-spec-render/loading.mp4";自动类型检测
当 spec.type 未指定或不在 SPEC_RENDERERS 中时,按规则自动推断:
- Video ≥1 →
keyframe_video - Image ≥2 + Stack 根 / overlay 属性 →
character_showcase - Image ≥2 + Card ≥2 →
sketch_gallery - List + Card →
episode_breakdown - Card ≥3 →
script_overview - Text ≥3 + 无媒体 →
longform_story - 都不匹配 →
DefaultRootRenderer(通用树渲染)
已知限制
- Tailwind 强绑定,暂不支持非 Tailwind 消费方
framer-motion/lucide-react/@radix-ui/react-dialog是 peer dep,消费端需自己安装- 图片裁剪/编辑 UI 依赖 shadcn CSS 变量
