feat: добавлены анимации загрузки и переходов между разделами

- Добавлен полноэкранный индикатор загрузки с размытием фона (LoadingOverlay)
- Реализован глобальный контекст загрузки для отслеживания переходов между страницами
- Добавлены плавные fade-анимации при переходах между страницами
- Реализованы поочередные (stagger) анимации для карточек:
  * Карточки дней на странице расписания
  * Карточки уроков внутри каждого дня
  * Карточки групп на главной странице
- Анимации работают на всех устройствах, включая мобильные
- Улучшен компонент Spinner с поддержкой разных размеров
- Исправлена ошибка гидратации с вложенными <li> элементами в навигации
- Оптимизированы задержки анимаций для более быстрого отображения контента
This commit is contained in:
kilyabin
2025-11-23 01:29:09 +04:00
parent e5262f8203
commit cf0137a8d6
11 changed files with 276 additions and 60 deletions

View File

@@ -1,3 +1,4 @@
import React from 'react'
import type { Day as DayType } from '@/shared/model/day'
import { getDayOfWeek } from '@/shared/utils'
import { Lesson } from '@/widgets/schedule/lesson'
@@ -48,9 +49,10 @@ export function Day({ day }: {
<div className='snap-start hidden md:block' style={{ flex: '0 0 3rem' }} />
{day.lessons.map((lesson, i) => (
<Lesson
key={i}
width={longNames ? 450 : 350}
lesson={lesson}
key={i}
lesson={lesson}
animationDelay={i * 0.08}
/>
))}
<div className='snap-start hidden md:block' style={{ flex: `0 0 calc(100vw - 4rem - ${longNames ? 450 : 350}px - 1rem)` }} />

View File

@@ -50,7 +50,15 @@ export function Schedule({ days }: {
return (
<div className="flex flex-col p-4 md:p-8 lg:p-16 gap-6 md:gap-12 lg:gap-14">
{days.map((day, i) => (
<Day day={day} key={`${group}_day${i}`} />
<div
key={`${group}_day${i}`}
className="stagger-card"
style={{
animationDelay: `${i * 0.1}s`,
} as React.CSSProperties}
>
<Day day={day} />
</div>
))}
</div>
)

View File

@@ -22,9 +22,10 @@ import { BsFillGeoAltFill } from 'react-icons/bs'
import { RiGroup2Fill } from 'react-icons/ri'
import { ResourcesDialog } from '@/widgets/schedule/resources-dialog'
export function Lesson({ lesson, width = 350 }: {
export function Lesson({ lesson, width = 350, animationDelay }: {
lesson: LessonType
width: number
animationDelay?: number
}) {
const [resourcesDialogOpened, setResourcesDialogOpened] = React.useState(false)
@@ -63,7 +64,12 @@ export function Lesson({ lesson, width = 350 }: {
}
return (
<Card className={`w-full ${width === 450 ? 'md:w-[450px] md:min-w-[450px] md:max-w-[450px]' : 'md:w-[350px] md:min-w-[350px] md:max-w-[350px]'} flex flex-col relative overflow-hidden snap-start scroll-ml-16 shrink-0`}>
<Card
className={`w-full ${width === 450 ? 'md:w-[450px] md:min-w-[450px] md:max-w-[450px]' : 'md:w-[350px] md:min-w-[350px] md:max-w-[350px]'} flex flex-col relative overflow-hidden snap-start scroll-ml-16 shrink-0 stagger-card`}
style={animationDelay !== undefined ? {
animationDelay: `${animationDelay}s`,
} as React.CSSProperties : undefined}
>
{lesson.isChange && <div className='absolute top-0 left-0 w-full h-full bg-gradient-to-br from-[#ffc60026] to-[#95620026] pointer-events-none'></div>}
<CardHeader>
<div className='flex gap-2 md:gap-4'>