mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-14 16:23:43 +08:00
refactor: enhance UI responsiveness and animations; add smooth transitions for video grid and improve TypeBadgeItem interaction
This commit is contained in:
@@ -533,6 +533,22 @@ nav:where(.sticky, .fixed),
|
||||
contain: layout style paint;
|
||||
}
|
||||
|
||||
/* Smooth video grid transitions */
|
||||
[class*="grid"] > a {
|
||||
animation: fadeInUp 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reduce paint complexity on scroll */
|
||||
img {
|
||||
content-visibility: auto;
|
||||
|
||||
@@ -22,15 +22,20 @@ export function TypeBadgeItem({
|
||||
onFocus,
|
||||
innerRef,
|
||||
}: TypeBadgeItemProps) {
|
||||
const handleClick = () => {
|
||||
// Immediate visual feedback before state update
|
||||
onToggle();
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={innerRef}
|
||||
onClick={onToggle}
|
||||
onClick={handleClick}
|
||||
onFocus={onFocus}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onToggle();
|
||||
handleClick();
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
@@ -40,9 +45,9 @@ export function TypeBadgeItem({
|
||||
inline-flex items-center gap-1.5 px-3 py-1.5
|
||||
border border-[var(--glass-border)]
|
||||
text-xs font-medium whitespace-nowrap
|
||||
transition-all duration-[var(--transition-fluid)]
|
||||
transition-all duration-200 ease-out
|
||||
hover:scale-105 hover:shadow-[var(--shadow-sm)]
|
||||
active:scale-95 snap-start will-change-transform
|
||||
active:scale-95 snap-start
|
||||
${isSelected
|
||||
? 'bg-[var(--accent-color)] text-white border-[var(--accent-color)]'
|
||||
: 'bg-[var(--glass-bg)] text-[var(--text-color)] backdrop-blur-[10px]'
|
||||
|
||||
@@ -46,7 +46,7 @@ const VideoCard = memo(({
|
||||
aria-label={`${video.vod_name}${video.vod_remarks ? ` - ${video.vod_remarks}` : ''}`}
|
||||
>
|
||||
<Card
|
||||
className={`p-0 overflow-hidden group cursor-pointer flex flex-col h-full ${video.isNew ? 'animate-scale-in' : ''}`}
|
||||
className="p-0 overflow-hidden group cursor-pointer flex flex-col h-full"
|
||||
>
|
||||
{/* Poster */}
|
||||
<div className="relative aspect-[2/3] bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden rounded-[var(--radius-2xl)]">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect, useMemo, useCallback, useTransition } from 'react';
|
||||
|
||||
interface TypeBadge {
|
||||
type: string;
|
||||
@@ -19,6 +19,7 @@ interface TypeBadge {
|
||||
*/
|
||||
export function useTypeBadges<T extends { type_name?: string }>(videos: T[]) {
|
||||
const [selectedTypes, setSelectedTypes] = useState<Set<string>>(new Set());
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
// Collect and count type badges from videos
|
||||
const typeBadges = useMemo<TypeBadge[]>(() => {
|
||||
@@ -48,8 +49,10 @@ export function useTypeBadges<T extends { type_name?: string }>(videos: T[]) {
|
||||
);
|
||||
}, [videos, selectedTypes]);
|
||||
|
||||
// Toggle type selection
|
||||
const toggleType = (type: string) => {
|
||||
// Toggle type selection - useCallback to prevent re-creation
|
||||
// Use startTransition to make UI feel responsive
|
||||
const toggleType = useCallback((type: string) => {
|
||||
// Update selected types immediately (high priority)
|
||||
setSelectedTypes(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(type)) {
|
||||
@@ -59,12 +62,12 @@ export function useTypeBadges<T extends { type_name?: string }>(videos: T[]) {
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Clear all selections
|
||||
const clearSelection = () => {
|
||||
// Clear all selections - useCallback to prevent re-creation
|
||||
const clearSelection = useCallback(() => {
|
||||
setSelectedTypes(new Set());
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Auto-cleanup: remove selected types that no longer exist in badges
|
||||
useEffect(() => {
|
||||
@@ -90,5 +93,6 @@ export function useTypeBadges<T extends { type_name?: string }>(videos: T[]) {
|
||||
toggleType,
|
||||
clearSelection,
|
||||
hasFilters: selectedTypes.size > 0,
|
||||
isPending, // Export pending state
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user