- ESLint flat config + Prettier + EditorConfig - Markdown处理器LRU缓存 - Zustand选择器优化减少重渲染 - CodeMirror Compartment主题热切换 - Preview防抖(150ms) + IndexedDB去抖(500ms) - 组件拆分: App.tsx 310→104行, Sidebar.tsx 244→90行 - 统一错误处理 errorHandler.ts - ConfirmDialog替代原生confirm - LoadingSpinner加载状态 - Toast多条堆叠+类型区分 - 可访问性增强(ARIA属性、键盘导航) - Vitest测试框架(78个测试用例) - Git hooks(husky + lint-staged) - 项目文档(README.md, CONTRIBUTING.md)
71 lines
1.4 KiB
TypeScript
71 lines
1.4 KiB
TypeScript
import React from 'react'
|
|
|
|
interface LoadingSpinnerProps {
|
|
size?: 'small' | 'medium' | 'large'
|
|
label?: string
|
|
/** 是否全屏覆盖 */
|
|
overlay?: boolean
|
|
}
|
|
|
|
const sizeMap = {
|
|
small: 16,
|
|
medium: 24,
|
|
large: 36
|
|
}
|
|
|
|
/**
|
|
* UX-02: 通用加载指示器组件
|
|
*/
|
|
export const LoadingSpinner = React.memo(function LoadingSpinner({
|
|
size = 'medium',
|
|
label,
|
|
overlay = false
|
|
}: LoadingSpinnerProps) {
|
|
const px = sizeMap[size]
|
|
|
|
const spinner = (
|
|
<div
|
|
className={`loading-spinner loading-spinner-${size}`}
|
|
role="status"
|
|
aria-label={label || '加载中'}
|
|
>
|
|
<svg
|
|
className="loading-spinner-svg"
|
|
width={px}
|
|
height={px}
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
aria-hidden="true"
|
|
>
|
|
<circle
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="3"
|
|
strokeLinecap="round"
|
|
opacity="0.2"
|
|
/>
|
|
<path
|
|
d="M12 2a10 10 0 0 1 10 10"
|
|
stroke="currentColor"
|
|
strokeWidth="3"
|
|
strokeLinecap="round"
|
|
/>
|
|
</svg>
|
|
{label && <span className="loading-spinner-label">{label}</span>}
|
|
</div>
|
|
)
|
|
|
|
if (!overlay) return spinner
|
|
|
|
return (
|
|
<div className="loading-overlay" aria-busy="true">
|
|
{spinner}
|
|
</div>
|
|
)
|
|
})
|
|
|
|
LoadingSpinner.displayName = 'LoadingSpinner'
|