feat(ui): 强化 AI 工作中动效——构思占位/流式进度条/波动三点/转圈
- 动效基元:ai-dot 波动(三点竖跳)、ai-stream 不确定进度条、ai-breathe 呼吸微光(均带 reduced-motion 回退) - ThinkingIndicator 升级为明显波动;新增 Spinner / StreamingBar / GenerationSkeleton 原子件;Button 加 loading 态 - 写作台:首 token 前正文区『AI 正在构思本章…』占位(补最大缺口,不再空白像卡死)+ 流式顶部进度条 - 整章重写流式进度条;续写/润色忙碌骨架 + 触发键转圈;生成器/角色/世界观改用 Button loading
This commit is contained in:
@@ -5,11 +5,14 @@ import {
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
} from "@/lib/ui/variants";
|
||||
import { Spinner } from "./Spinner";
|
||||
|
||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: ReactNode;
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
// 忙碌态:显示转圈 + 禁用 + aria-busy,统一各处"处理中"的过程感。
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
||||
@@ -19,6 +22,8 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
||||
variant,
|
||||
size,
|
||||
type = "button",
|
||||
loading = false,
|
||||
disabled,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
@@ -28,8 +33,11 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
||||
ref={ref}
|
||||
type={type}
|
||||
className={buttonClass({ variant, size, className })}
|
||||
disabled={disabled || loading}
|
||||
aria-busy={loading || undefined}
|
||||
{...props}
|
||||
>
|
||||
{loading ? <Spinner className="h-4 w-4" /> : null}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
49
apps/web/components/ui/GenerationSkeleton.tsx
Normal file
49
apps/web/components/ui/GenerationSkeleton.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ThinkingIndicator } from "@/components/ThinkingIndicator";
|
||||
import { cn } from "@/lib/ui/variants";
|
||||
|
||||
interface GenerationSkeletonProps {
|
||||
// 状态文案(如「续写中」「AI 正在构思本章…」)。
|
||||
label: string;
|
||||
// 呼吸微光占位行数(默认 3)。
|
||||
lines?: number;
|
||||
// sm:面板内小占位;lg:正文区大占位(更粗行 + 稍大标签)。
|
||||
size?: "sm" | "lg";
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// 占位行宽度序列(错落感,示意即将落笔的段落)。
|
||||
const LINE_WIDTHS = ["90%", "78%", "84%", "68%", "72%"];
|
||||
|
||||
// AI 同步/构思等待占位:三点波动标签 + `ai-breathe` 呼吸微光占位行,
|
||||
// 比单三点更明显地传达「正在生成」。占位行 aria-hidden;role=status/aria-live 由
|
||||
// ThinkingIndicator 提供,读屏可知在生成。ai-breathe 自带 reduced-motion 回退。
|
||||
export function GenerationSkeleton({
|
||||
label,
|
||||
lines = 3,
|
||||
size = "sm",
|
||||
className,
|
||||
}: GenerationSkeletonProps) {
|
||||
return (
|
||||
<div className={cn("space-y-2.5", className)}>
|
||||
<ThinkingIndicator
|
||||
label={label}
|
||||
className={cn("text-cinnabar", size === "lg" ? "text-sm" : "text-xs")}
|
||||
/>
|
||||
<div className="space-y-2" aria-hidden="true">
|
||||
{Array.from({ length: lines }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
"ai-breathe rounded bg-line/60",
|
||||
size === "lg" ? "h-3.5" : "h-3",
|
||||
)}
|
||||
style={{
|
||||
width: LINE_WIDTHS[i % LINE_WIDTHS.length],
|
||||
animationDelay: `${i * 160}ms`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
apps/web/components/ui/Spinner.tsx
Normal file
18
apps/web/components/ui/Spinner.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { cn } from "@/lib/ui/variants";
|
||||
|
||||
interface SpinnerProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// 转圈忙碌指示:环形(顶部留缺口)motion-safe 旋转;reduced-motion 下为静止环。
|
||||
export function Spinner({ className }: SpinnerProps) {
|
||||
return (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"inline-block h-4 w-4 shrink-0 rounded-full border-2 border-current border-t-transparent motion-safe:animate-spin",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
18
apps/web/components/ui/StreamingBar.tsx
Normal file
18
apps/web/components/ui/StreamingBar.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { cn } from "@/lib/ui/variants";
|
||||
|
||||
interface StreamingBarProps {
|
||||
label?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// AI 流式/处理中·不确定进度条:细朱砂条来回扫,明确传达"正在工作"。
|
||||
// 显隐由调用方控制(只在生成时挂载);reduced-motion 下为静态朱砂条(见 globals.css)。
|
||||
export function StreamingBar({ label = "AI 生成中", className }: StreamingBarProps) {
|
||||
return (
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-label={label}
|
||||
className={cn("ai-stream-track h-0.5 w-full rounded-full", className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user