feat: добавлены анимации загрузки и переходов между разделами
- Добавлен полноэкранный индикатор загрузки с размытием фона (LoadingOverlay) - Реализован глобальный контекст загрузки для отслеживания переходов между страницами - Добавлены плавные fade-анимации при переходах между страницами - Реализованы поочередные (stagger) анимации для карточек: * Карточки дней на странице расписания * Карточки уроков внутри каждого дня * Карточки групп на главной странице - Анимации работают на всех устройствах, включая мобильные - Улучшен компонент Spinner с поддержкой разных размеров - Исправлена ошибка гидратации с вложенными <li> элементами в навигации - Оптимизированы задержки анимаций для более быстрого отображения контента
This commit is contained in:
@@ -1,9 +1,25 @@
|
||||
import '@/shared/styles/globals.css'
|
||||
import type { AppProps } from 'next/app'
|
||||
import { ThemeProvider } from '@/shared/providers/theme-provider'
|
||||
import { LoadingContextProvider, LoadingContext } from '@/shared/context/loading-context'
|
||||
import { LoadingOverlay } from '@/shared/ui/loading-overlay'
|
||||
import Head from 'next/head'
|
||||
import React from 'react'
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
function AppContent({ Component, pageProps }: AppProps) {
|
||||
const { isLoading } = React.useContext(LoadingContext)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="page-transition-wrapper">
|
||||
<Component {...pageProps} />
|
||||
</div>
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function App(props: AppProps) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
@@ -15,7 +31,9 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
<LoadingContextProvider>
|
||||
<AppContent {...props} />
|
||||
</LoadingContextProvider>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -30,6 +30,17 @@ export default function HomePage({ groups, groupsByCourse }: HomePageProps) {
|
||||
const [openCourses, setOpenCourses] = React.useState<Set<number>>(new Set([1]))
|
||||
const [addGroupDialogOpen, setAddGroupDialogOpen] = React.useState(false)
|
||||
|
||||
// Подсчитываем смещения для каждого курса для последовательной анимации
|
||||
const courseOffsets = React.useMemo(() => {
|
||||
const offsets: { [course: number]: number } = {}
|
||||
let totalGroups = 0
|
||||
for (const course of [1, 2, 3, 4, 5]) {
|
||||
offsets[course] = totalGroups
|
||||
totalGroups += (groupsByCourse[course] || []).length
|
||||
}
|
||||
return { offsets, totalGroups }
|
||||
}, [groupsByCourse])
|
||||
|
||||
const toggleCourse = (course: number) => {
|
||||
setOpenCourses(prev => {
|
||||
const next = new Set(prev)
|
||||
@@ -50,55 +61,80 @@ export default function HomePage({ groups, groupsByCourse }: HomePageProps) {
|
||||
</Head>
|
||||
<div className="min-h-screen p-4 md:p-8">
|
||||
<div className="max-w-4xl mx-auto space-y-4">
|
||||
<div className="text-center space-y-2 mb-8">
|
||||
<div className="text-center space-y-2 mb-8 stagger-card" style={{ animationDelay: '0.05s' } as React.CSSProperties}>
|
||||
<h1 className="text-3xl md:text-4xl font-bold">Расписание занятий</h1>
|
||||
<p className="text-muted-foreground">Выберите группу для просмотра расписания</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{[1, 2, 3, 4, 5].map(course => {
|
||||
{[1, 2, 3, 4, 5].map((course, courseIndex) => {
|
||||
const courseGroups = groupsByCourse[course] || []
|
||||
const isOpen = openCourses.has(course)
|
||||
const courseOffset = courseOffsets.offsets[course]
|
||||
|
||||
if (courseGroups.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Card key={course}>
|
||||
<CardHeader
|
||||
className="cursor-pointer"
|
||||
onClick={() => toggleCourse(course)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl">
|
||||
{course} курс
|
||||
</CardTitle>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-5 w-5 transition-transform duration-200",
|
||||
isOpen && "transform rotate-180"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
{isOpen && (
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-3 sm:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
{courseGroups.map(({ id, name }) => (
|
||||
<Link key={id} href={`/${id}`}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-center h-auto py-3 px-2 sm:px-4 text-sm sm:text-base whitespace-nowrap"
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
</Link>
|
||||
))}
|
||||
<div
|
||||
key={course}
|
||||
className="stagger-card"
|
||||
style={{
|
||||
animationDelay: `${0.1 + courseIndex * 0.05}s`,
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<Card>
|
||||
<CardHeader
|
||||
className="cursor-pointer"
|
||||
onClick={() => toggleCourse(course)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl">
|
||||
{course} курс
|
||||
</CardTitle>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-5 w-5 transition-transform duration-200",
|
||||
isOpen && "transform rotate-180"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
</CardHeader>
|
||||
{isOpen && (
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-3 sm:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
{courseGroups.map(({ id, name }, groupIndex) => {
|
||||
// Последовательная анимация: каждый следующий элемент с задержкой
|
||||
// courseOffset - это количество групп во всех предыдущих курсах
|
||||
// groupIndex - это индекс в текущем курсе
|
||||
// Итого: последовательный счетчик для всех групп подряд
|
||||
const globalIndex = courseOffset + groupIndex
|
||||
const delay = 0.15 + globalIndex * 0.04
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className="stagger-card"
|
||||
style={{
|
||||
animationDelay: `${delay}s`,
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<Link href={`/${id}`}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-center h-auto py-3 px-2 sm:px-4 text-sm sm:text-base whitespace-nowrap"
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
@@ -112,26 +148,39 @@ export default function HomePage({ groups, groupsByCourse }: HomePageProps) {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 mt-8">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => setAddGroupDialogOpen(true)}
|
||||
className="gap-2"
|
||||
<div
|
||||
className="stagger-card"
|
||||
style={{ animationDelay: `${0.15 + courseOffsets.totalGroups * 0.04 + 0.05}s` } as React.CSSProperties}
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => setAddGroupDialogOpen(true)}
|
||||
className="gap-2"
|
||||
>
|
||||
<MdAdd className="h-4 w-4" />
|
||||
Добавить группу
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className="relative stagger-card"
|
||||
style={{ animationDelay: `${0.15 + courseOffsets.totalGroups * 0.04 + 0.08}s` } as React.CSSProperties}
|
||||
>
|
||||
<MdAdd className="h-4 w-4" />
|
||||
Добавить группу
|
||||
</Button>
|
||||
<div className="relative">
|
||||
<ThemeSwitcher />
|
||||
<span className="absolute -bottom-5 left-1/2 transform -translate-x-1/2 text-xs text-muted-foreground whitespace-nowrap sm:hidden">
|
||||
Тема
|
||||
</span>
|
||||
</div>
|
||||
<Link href={GITHUB_REPO_URL} target="_blank" rel="noopener noreferrer">
|
||||
<Button variant="outline" className="gap-2">
|
||||
<FaGithub className="h-4 w-4" />
|
||||
GitHub
|
||||
</Button>
|
||||
</Link>
|
||||
<div
|
||||
className="stagger-card"
|
||||
style={{ animationDelay: `${0.15 + courseOffsets.totalGroups * 0.04 + 0.11}s` } as React.CSSProperties}
|
||||
>
|
||||
<Link href={GITHUB_REPO_URL} target="_blank" rel="noopener noreferrer">
|
||||
<Button variant="outline" className="gap-2">
|
||||
<FaGithub className="h-4 w-4" />
|
||||
GitHub
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user