+
Расписание занятий
Выберите группу для просмотра расписания
- {[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 (
-
- toggleCourse(course)}
- >
-
-
- {course} курс
-
-
-
-
- {isOpen && (
-
-
- {courseGroups.map(({ id, name }) => (
-
-
- {name}
-
-
- ))}
+
+
+ toggleCourse(course)}
+ >
+
+
+ {course} курс
+
+
-
- )}
-
+
+ {isOpen && (
+
+
+ {courseGroups.map(({ id, name }, groupIndex) => {
+ // Последовательная анимация: каждый следующий элемент с задержкой
+ // courseOffset - это количество групп во всех предыдущих курсах
+ // groupIndex - это индекс в текущем курсе
+ // Итого: последовательный счетчик для всех групп подряд
+ const globalIndex = courseOffset + groupIndex
+ const delay = 0.15 + globalIndex * 0.04
+ return (
+
+
+
+ {name}
+
+
+
+ )
+ })}
+
+
+ )}
+
+
)
})}
@@ -112,26 +148,39 @@ export default function HomePage({ groups, groupsByCourse }: HomePageProps) {
)}
-
setAddGroupDialogOpen(true)}
- className="gap-2"
+
+ setAddGroupDialogOpen(true)}
+ className="gap-2"
+ >
+
+ Добавить группу
+
+
+
-
- Добавить группу
-
-
Тема
-
-
-
- GitHub
-
-
+
+
+
+
+ GitHub
+
+
+
diff --git a/src/shared/context/loading-context.tsx b/src/shared/context/loading-context.tsx
new file mode 100644
index 0000000..217a3be
--- /dev/null
+++ b/src/shared/context/loading-context.tsx
@@ -0,0 +1,46 @@
+import React from 'react'
+import { useRouter } from 'next/router'
+
+interface LoadingContextType {
+ isLoading: boolean
+}
+
+export const LoadingContext = React.createContext
({
+ isLoading: false,
+})
+
+export function LoadingContextProvider({ children }: React.PropsWithChildren) {
+ const [isLoading, setIsLoading] = React.useState(false)
+ const router = useRouter()
+
+ React.useEffect(() => {
+ const handleRouteChangeStart = () => {
+ setIsLoading(true)
+ }
+
+ const handleRouteChangeComplete = () => {
+ setIsLoading(false)
+ }
+
+ const handleRouteChangeError = () => {
+ setIsLoading(false)
+ }
+
+ router.events.on('routeChangeStart', handleRouteChangeStart)
+ router.events.on('routeChangeComplete', handleRouteChangeComplete)
+ router.events.on('routeChangeError', handleRouteChangeError)
+
+ return () => {
+ router.events.off('routeChangeStart', handleRouteChangeStart)
+ router.events.off('routeChangeComplete', handleRouteChangeComplete)
+ router.events.off('routeChangeError', handleRouteChangeError)
+ }
+ }, [router])
+
+ return (
+
+ {children}
+
+ )
+}
+
diff --git a/src/shared/styles/globals.css b/src/shared/styles/globals.css
index 8a3c148..49576be 100644
--- a/src/shared/styles/globals.css
+++ b/src/shared/styles/globals.css
@@ -146,4 +146,37 @@
height: auto;
}
}
+}
+
+/* Page transition animations */
+@layer utilities {
+ .page-transition-wrapper {
+ animation: fadeIn 0.3s ease-in-out;
+ }
+
+ @keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+
+ /* Stagger animation for cards - works on all devices */
+ .stagger-card {
+ animation: fadeInSlideUp 0.3s ease-out forwards;
+ opacity: 0;
+ }
+
+ @keyframes fadeInSlideUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/shared/ui/loading-overlay.tsx b/src/shared/ui/loading-overlay.tsx
new file mode 100644
index 0000000..97fc510
--- /dev/null
+++ b/src/shared/ui/loading-overlay.tsx
@@ -0,0 +1,32 @@
+import React from 'react'
+import { Spinner } from './spinner'
+import { cn } from '@/shared/utils'
+
+interface LoadingOverlayProps {
+ isLoading: boolean
+}
+
+export function LoadingOverlay({ isLoading }: LoadingOverlayProps) {
+ return (
+
+ {isLoading && (
+
+ )}
+
+ )
+}
+
diff --git a/src/shared/ui/spinner.tsx b/src/shared/ui/spinner.tsx
index cdb3f37..0096198 100644
--- a/src/shared/ui/spinner.tsx
+++ b/src/shared/ui/spinner.tsx
@@ -1,7 +1,19 @@
import styles from './styles.module.scss'
+import { cn } from '@/shared/utils'
-export function Spinner() {
+interface SpinnerProps {
+ size?: 'small' | 'large'
+ className?: string
+}
+
+export function Spinner({ size = 'small', className }: SpinnerProps) {
return (
-
+
)
}
\ No newline at end of file
diff --git a/src/shared/ui/styles.module.scss b/src/shared/ui/styles.module.scss
index 04917c2..6757d48 100644
--- a/src/shared/ui/styles.module.scss
+++ b/src/shared/ui/styles.module.scss
@@ -9,6 +9,16 @@
margin-right: 8px;
}
+.spinnerLarge {
+ width: 64px;
+ height: 64px;
+ border-width: 4px;
+ margin-right: 0;
+ border-color: hsl(var(--foreground) / 0.3);
+ border-left-color: hsl(var(--foreground));
+ border-top-color: hsl(var(--foreground));
+}
+
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
diff --git a/src/widgets/navbar/index.tsx b/src/widgets/navbar/index.tsx
index 20280e3..6a55665 100644
--- a/src/widgets/navbar/index.tsx
+++ b/src/widgets/navbar/index.tsx
@@ -116,7 +116,7 @@ function NavBarItem({ url, children }: React.PropsWithChildren<{
)
return (
-
+ <>
{isLoading && isLoading === url ? (
button
) : (
@@ -124,6 +124,6 @@ function NavBarItem({ url, children }: React.PropsWithChildren<{
{button}
)}
-
+ >
)
}
\ No newline at end of file
diff --git a/src/widgets/schedule/day.tsx b/src/widgets/schedule/day.tsx
index e0c7db0..7cbb9a2 100644
--- a/src/widgets/schedule/day.tsx
+++ b/src/widgets/schedule/day.tsx
@@ -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 }: {
{day.lessons.map((lesson, i) => (
))}
diff --git a/src/widgets/schedule/index.tsx b/src/widgets/schedule/index.tsx
index e633d37..58971de 100644
--- a/src/widgets/schedule/index.tsx
+++ b/src/widgets/schedule/index.tsx
@@ -50,7 +50,15 @@ export function Schedule({ days }: {
return (
{days.map((day, i) => (
-
+
+
+
))}
)
diff --git a/src/widgets/schedule/lesson.tsx b/src/widgets/schedule/lesson.tsx
index 6aa4ded..5c72d1d 100644
--- a/src/widgets/schedule/lesson.tsx
+++ b/src/widgets/schedule/lesson.tsx
@@ -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 (
-
+
{lesson.isChange &&
}