mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
Introduces two reusable loading components to standardize loading states: - Spinner: Base spinner with accessibility support (role, aria-label) - LoadingState: Semantic loading indicator combining spinner and label Updates loading patterns across the app: - Full-screen/block loading now uses LoadingState (App, router, files, file) - Inline button loading states show spinner + text with aria-busy - Message list loading replaced with MessageSkeleton component - Removes duplicate SpinnerIcon definition from PermissionFooter Improves UX and accessibility: - Loading screens centered and full-height - Consistent ellipsis character (…) - Semantic accessibility (role="status", aria-live, aria-busy) - Adds CSS variables for banner styling
5.0 KiB
5.0 KiB
Loading 元素精简优化计划
一、当前 Loading 元素汇总
| 位置 | 文件 | 当前样式 | 展示时机 |
|---|---|---|---|
| 同步横幅 | SyncingBanner.tsx |
顶部固定横幅 + 旋转符号 | isSyncing && isOnline |
| 权限按钮 | PermissionFooter.tsx:66-73 |
SVG SpinnerIcon | loading 状态 |
| 认证加载 | App.tsx:132-146 |
纯文本 "Loading…" / "Authorizing…" | isAuthSourceLoading / isAuthLoading |
| 会话加载 | router.tsx:100-106 |
纯文本 "Loading session…" | !session |
| 消息加载 | HappyThread.tsx:235-258 |
纯文本 "Loading..." + 按钮文本 | isLoadingMessages / isLoadingMoreMessages |
| Git 状态 | files.tsx:313-318 |
纯文本 "Loading Git status..." / "Loading files..." | gitLoading / searchResults.isLoading |
| 文件内容 | file.tsx:231-232 |
纯文本 "Loading file..." | diffQuery.isLoading || fileQuery.isLoading |
| 登录按钮 | LoginPrompt.tsx:77 |
按钮文本 "Signing in..." | isLoading |
| 创建会话 | NewSession.tsx:119-122, 216 |
"Loading machines..." / "Creating..." | isPending |
| 问卷提交 | AskUserQuestionFooter.tsx:385 |
按钮文本 "Submitting…" | loading |
二、问题
- 样式不统一 - 纯文本 vs SpinnerIcon vs 旋转符号
- SpinnerIcon 无法复用 - 定义在 PermissionFooter 内部
- 省略号不一致 - "..." vs "…"
三、精简方案
核心原则:Spinner 做基础元件,LoadingState 负责语义化加载
创建唯一的 Spinner 组件(可访问性就绪)
// web/src/components/Spinner.tsx
export function Spinner({
size = 'md',
className,
label = 'Loading',
}: {
size?: 'sm' | 'md' | 'lg'
className?: string
label?: string
}) {
const sizeClasses = { sm: 'h-4 w-4', md: 'h-5 w-5', lg: 'h-6 w-6' }
return (
<svg
className={cn(sizeClasses[size], 'animate-spin text-[var(--app-hint)]', className)}
viewBox="0 0 24 24"
fill="none"
role="status"
aria-label={label}
>
<circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2.5" opacity="0.25" />
<path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" opacity="0.75" />
</svg>
)
}
新增 LoadingState(Spinner + 可选文案)
// web/src/components/LoadingState.tsx
export function LoadingState({
label = 'Loading…',
className,
spinnerSize = 'md',
}: {
label?: string
className?: string
spinnerSize?: 'sm' | 'md' | 'lg'
}) {
return (
<div className={cn('inline-flex items-center gap-2 text-[var(--app-hint)]', className)} role="status" aria-live="polite">
<Spinner size={spinnerSize} label={label} />
<span>{label}</span>
</div>
)
}
SyncingBanner 处理
方案:不改,避免打扰用户
- 保留顶部横幅与
useSyncingState逻辑 - 若需要统一视觉,仅替换内部图标为新
Spinner
四、实施步骤
Step 1: 创建 Spinner 组件(可访问性)
- 创建
web/src/components/Spinner.tsx,包含role="status"与aria-label
Step 2: 新增 LoadingState 组件
- 创建
web/src/components/LoadingState.tsx - 统一使用
Loading…(省略号用…)
Step 3: 替换加载场景(保留语义)
App.tsx- 认证相关使用<LoadingState label="Authorizing…" />router.tsx- 会话加载使用<LoadingState label="Loading session…" />HappyThread.tsx- 消息加载用<LoadingState label="Loading…" />files.tsx- Git/文件列表加载用<LoadingState label="Loading files…" />file.tsx- 文件内容加载用<LoadingState label="Loading file…" />PermissionFooter.tsx- 使用新Spinner,删除内部SpinnerIcon
Step 4: 按钮加载保持可读标签
LoginPrompt.tsx/NewSession.tsx/AskUserQuestionFooter.tsx- 保留文字,旁边加Spinner size="sm"- 按钮添加
aria-busy,避免替换成纯图标
Step 5: 列表/内容区优先 Skeleton 或占位
HappyThread.tsx- 消息列表加载改为 skeleton/placeholderfiles.tsx- 文件列表加载改为 skeleton/placeholderfile.tsx- 文件内容加载改为 skeleton/placeholder
Step 6: SyncingBanner
- 保留
SyncingBanner.tsx - 可选:内部图标替换为新
Spinner,不改交互与布局
五、关键文件
新建:
web/src/components/Spinner.tsxweb/src/components/LoadingState.tsx
修改:
web/src/App.tsxweb/src/router.tsxweb/src/components/AssistantChat/HappyThread.tsxweb/src/routes/sessions/files.tsxweb/src/routes/sessions/file.tsxweb/src/components/ToolCard/PermissionFooter.tsxweb/src/components/LoginPrompt.tsxweb/src/components/NewSession.tsxweb/src/components/ToolCard/AskUserQuestionFooter.tsxweb/src/components/SyncingBanner.tsx