feat: Standardize border radius usage across components and implement View Transition API for theme changes

This commit is contained in:
kuekhaoyang
2025-11-18 15:43:58 +08:00
parent db40601649
commit e007899f9e
10 changed files with 48 additions and 47 deletions
+5 -11
View File
@@ -793,12 +793,12 @@ export const announceToScreenReader = (message: string) => {
### 🔵 Low Priority (低优先级,时间允许时完成)
#### 13. **统一圆角使用方式** (预计 1 小时)
- [ ] 全局搜索 `style={{ borderRadius: 'var(--radius-`
- [ ] 统一改为 `className="rounded-[var(--radius-2xl)]"`
- [ ] 或者相反,全部统一使用 `style`
- [] 全局搜索 `style={{ borderRadius: 'var(--radius-`
- [] 统一改为 `className="rounded-[var(--radius-2xl)]"`
- [] 或者相反,全部统一使用 `style`
#### 14. **添加暗色模式动画过渡** (预计 1 小时)
- [ ] 在 `ThemeProvider.tsx` 中添加 View Transition API
- [] 在 `ThemeProvider.tsx` 中添加 View Transition API
```tsx
if (document.startViewTransition) {
document.startViewTransition(() => {
@@ -824,13 +824,7 @@ export const announceToScreenReader = (message: string) => {
```
- [ ] 更新文档
#### 17. **添加组件使用文档** (预计 4 小时)
- [ ] 创建 `docs/COMPONENTS.md`
- [ ] 为每个组件编写使用示例
- [ ] 添加 Props API 文档
- [ ] 添加可访问性指南
#### 18. **创建 Storybook** (预计 8 小时)
#### 17. **创建 Storybook** (预计 8 小时)
- [ ] 安装 Storybook 7.x
- [ ] 为所有 UI 组件创建 stories
- [ ] 添加 A11y addon
+1 -1
View File
@@ -89,7 +89,7 @@ function HomePage() {
{/* Glass Navbar */}
<nav className="sticky top-0 z-50 pt-4 pb-2" style={{ transform: 'translateZ(0)' }}>
<div className="max-w-7xl mx-auto px-4">
<div className="bg-[var(--glass-bg)] backdrop-blur-[25px] saturate-[180%] [-webkit-backdrop-filter:blur(25px)_saturate(180%)] border border-[var(--glass-border)] shadow-[var(--shadow-md)] px-6 py-4 transition-all duration-[var(--transition-fluid)]" style={{ borderRadius: 'var(--radius-2xl)' }}>
<div className="bg-[var(--glass-bg)] backdrop-blur-[25px] saturate-[180%] [-webkit-backdrop-filter:blur(25px)_saturate(180%)] border border-[var(--glass-border)] shadow-[var(--shadow-md)] px-6 py-4 transition-all duration-[var(--transition-fluid)] rounded-[var(--radius-2xl)]">
<div className="flex items-center justify-between">
<Link
href="/"
+2 -4
View File
@@ -52,14 +52,12 @@ export function SearchLoadingAnimation({
{/* Progress Bar - Unified 0-100% */}
<div className="w-full">
<div
className="h-1 bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden"
style={{ borderRadius: 'var(--radius-full)' }}
className="h-1 bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden rounded-[var(--radius-full)]"
>
<div
className="h-full bg-[var(--accent-color)] transition-all duration-500 ease-out relative will-change-[width]"
className="h-full bg-[var(--accent-color)] transition-all duration-500 ease-out relative will-change-[width] rounded-[var(--radius-full)]"
style={{
width: `${progress}%`,
borderRadius: 'var(--radius-full)',
transform: 'translateZ(0)'
}}
>
+28 -10
View File
@@ -25,24 +25,42 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
}, []);
useEffect(() => {
const applyTheme = () => {
if (theme === 'system') {
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setActualTheme(systemPrefersDark ? 'dark' : 'light');
document.documentElement.classList.toggle('dark', systemPrefersDark);
const applyTheme = (newTheme?: 'light' | 'dark') => {
const themeToApply = newTheme || (theme === 'system'
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
: theme);
setActualTheme(themeToApply);
document.documentElement.classList.toggle('dark', themeToApply === 'dark');
};
const applyThemeWithTransition = () => {
// Check if View Transition API is supported
// @ts-ignore - View Transition API is experimental
if (typeof document.startViewTransition === 'function') {
// @ts-ignore
document.startViewTransition(() => {
applyTheme();
});
} else {
setActualTheme(theme);
document.documentElement.classList.toggle('dark', theme === 'dark');
// Fallback for browsers that don't support View Transition API
applyTheme();
}
};
applyTheme();
applyThemeWithTransition();
localStorage.setItem('theme', theme);
// Listen for system theme changes
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', applyTheme);
return () => mediaQuery.removeEventListener('change', applyTheme);
const handleSystemThemeChange = () => {
if (theme === 'system') {
applyThemeWithTransition();
}
};
mediaQuery.addEventListener('change', handleSystemThemeChange);
return () => mediaQuery.removeEventListener('change', handleSystemThemeChange);
}, [theme]);
return (
+3 -5
View File
@@ -32,13 +32,12 @@ export function MovieCard({ movie, onMovieClick }: MovieCardProps) {
className="group cursor-pointer"
>
<Card hover className="overflow-hidden p-0 h-full">
<div className="relative aspect-[2/3] overflow-hidden bg-[var(--glass-bg)]" style={{ borderRadius: 'var(--radius-2xl)' }}>
<div className="relative aspect-[2/3] overflow-hidden bg-[var(--glass-bg)] rounded-[var(--radius-2xl)]">
<Image
src={movie.cover}
alt={movie.title}
fill
className="object-cover transition-transform duration-500 group-hover:scale-110 will-change-transform"
style={{ borderRadius: 'var(--radius-2xl)' }}
className="object-cover transition-transform duration-500 group-hover:scale-110 will-change-transform rounded-[var(--radius-2xl)]"
sizes="(max-width: 640px) 50vw, (max-width: 768px) 33vw, (max-width: 1024px) 25vw, 20vw"
onError={(e) => {
const target = e.currentTarget as HTMLImageElement;
@@ -55,8 +54,7 @@ export function MovieCard({ movie, onMovieClick }: MovieCardProps) {
/>
{movie.rate && parseFloat(movie.rate) > 0 && (
<div
className="absolute top-2 right-2 bg-black/70 backdrop-blur-sm px-2.5 py-1.5 flex items-center gap-1.5"
style={{ borderRadius: 'var(--radius-full)' }}
className="absolute top-2 right-2 bg-black/70 backdrop-blur-sm px-2.5 py-1.5 flex items-center gap-1.5 rounded-[var(--radius-full)]"
>
<Icons.Star size={12} className="text-yellow-400 fill-yellow-400" />
<span className="text-xs font-bold text-white">
+4 -8
View File
@@ -71,13 +71,11 @@ export function TagManager({
onChange={(e) => onNewTagInputChange(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && onAddTag()}
placeholder="添加自定义标签..."
className="flex-1 bg-[var(--glass-bg)] backdrop-blur-xl border border-[var(--glass-border)] text-[var(--text-color)] px-4 py-2 focus:outline-none focus:border-[var(--accent-color)] transition-colors"
style={{ borderRadius: 'var(--radius-2xl)' }}
className="flex-1 bg-[var(--glass-bg)] backdrop-blur-xl border border-[var(--glass-border)] text-[var(--text-color)] px-4 py-2 focus:outline-none focus:border-[var(--accent-color)] transition-colors rounded-[var(--radius-2xl)]"
/>
<button
onClick={onAddTag}
className="px-6 py-2 bg-[var(--accent-color)] text-white font-semibold hover:opacity-90 transition-opacity"
style={{ borderRadius: 'var(--radius-2xl)' }}
className="px-6 py-2 bg-[var(--accent-color)] text-white font-semibold hover:opacity-90 transition-opacity rounded-[var(--radius-2xl)]"
>
</button>
@@ -91,13 +89,12 @@ export function TagManager({
<button
onClick={() => onTagSelect(tag.id)}
className={`
px-6 py-2.5 text-sm font-semibold transition-all whitespace-nowrap will-change-transform
px-6 py-2.5 text-sm font-semibold transition-all whitespace-nowrap will-change-transform rounded-[var(--radius-full)]
${selectedTag === tag.id
? 'bg-[var(--accent-color)] text-white shadow-md scale-105'
: 'bg-[var(--glass-bg)] backdrop-blur-xl text-[var(--text-color)] border border-[var(--glass-border)] hover:border-[var(--accent-color)] hover:scale-105'
}
`}
style={{ borderRadius: 'var(--radius-full)' }}
>
{tag.label}
</button>
@@ -107,8 +104,7 @@ export function TagManager({
e.stopPropagation();
onTagDelete(tag.id);
}}
className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white flex items-center justify-center hover:bg-red-600 transition-colors"
style={{ borderRadius: 'var(--radius-full)' }}
className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white flex items-center justify-center hover:bg-red-600 transition-colors rounded-[var(--radius-full)]"
>
<Icons.X size={14} />
</button>
+1 -2
View File
@@ -8,8 +8,7 @@ export function EmptyState() {
<div className="text-center py-20 animate-fade-in">
<div className="mb-8">
<div
className="inline-flex items-center justify-center w-32 h-32 bg-[var(--glass-bg)] backdrop-blur-xl border border-[var(--glass-border)] mb-6"
style={{ borderRadius: 'var(--radius-full)' }}
className="inline-flex items-center justify-center w-32 h-32 bg-[var(--glass-bg)] backdrop-blur-xl border border-[var(--glass-border)] mb-6 rounded-[var(--radius-full)]"
>
<Icons.Film size={64} className="text-[var(--text-color-secondary)]" />
</div>
+1 -2
View File
@@ -11,8 +11,7 @@ export function NoResults({ onReset }: NoResultsProps) {
return (
<div className="text-center py-20 animate-fade-in">
<div
className="inline-flex items-center justify-center w-32 h-32 bg-[var(--glass-bg)] backdrop-blur-xl border border-[var(--glass-border)] mb-6"
style={{ borderRadius: 'var(--radius-full)' }}
className="inline-flex items-center justify-center w-32 h-32 bg-[var(--glass-bg)] backdrop-blur-xl border border-[var(--glass-border)] mb-6 rounded-[var(--radius-full)]"
>
<Icons.Search size={64} className="text-[var(--text-color-secondary)]" />
</div>
+1 -1
View File
@@ -51,8 +51,8 @@ export function TypeBadgeItem({
? 'ring-2 ring-[var(--accent-color)] ring-offset-2'
: ''
}
rounded-[var(--radius-full)]
`}
style={{ borderRadius: 'var(--radius-full)' }}
>
<span>{type}</span>
<span className={`
+2 -3
View File
@@ -122,13 +122,12 @@ export function VideoGrid({ videos, className = '' }: VideoGridProps) {
}`}
>
{/* Poster */}
<div className="relative aspect-[2/3] bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden" style={{ borderRadius: 'var(--radius-2xl)' }}>
<div className="relative aspect-[2/3] bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden rounded-[var(--radius-2xl)]">
{video.vod_pic ? (
<img
src={video.vod_pic}
alt={video.vod_name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500 will-change-transform"
style={{ borderRadius: 'var(--radius-2xl)' }}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500 will-change-transform rounded-[var(--radius-2xl)]"
loading="lazy"
onError={(e) => {
e.currentTarget.src = '/placeholder-poster.svg';