diff --git a/.husky/pre-push b/.husky/pre-push index 15fcede..4decfec 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -12,6 +12,9 @@ pnpm lint || exit 1 echo "Проверка Stylelint..." pnpm lint:styles || exit 1 +echo "Запуск Unit тестов..." +pnpm test:unit || exit 1 + echo "Production сборка..." pnpm build:prod || exit 1 diff --git a/package.json b/package.json index 69fde29..40e5551 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test:unit": "jest --config ./config/jest/jest.config.ts", "type-check": "tsc --noEmit", "prepare": "husky", - "pre-push": "pnpm type-check && pnpm lint && pnpm lint:styles && pnpm build:prod", + "pre-push": "pnpm type-check && pnpm lint && pnpm lint:styles && pnpm test:unit && pnpm build:prod", "storybook": "storybook dev -p 6006 -c config/storybook", "build-storybook": "storybook build -c config/storybook" }, diff --git a/src/app/styles/index.scss b/src/app/styles/index.scss index a69cfda..0b8e49f 100644 --- a/src/app/styles/index.scss +++ b/src/app/styles/index.scss @@ -1,3 +1,3 @@ -@import './fonts'; -@import './variables'; -@import './reset'; \ No newline at end of file +@use 'fonts'; +@use 'variables'; +@use 'reset'; \ No newline at end of file diff --git a/src/app/styles/variables.scss b/src/app/styles/variables.scss index 5ee1974..575989b 100644 --- a/src/app/styles/variables.scss +++ b/src/app/styles/variables.scss @@ -9,7 +9,7 @@ --color-white: #FFF; // Градиенты - --gradient-primary: linear-gradient(to right, #3877EE, #EF5DA8); + --gradient-primary: linear-gradient(to bottom, #3877EE, #EF5DA8); // Типографика --font-family-main: 'PT Sans', sans-serif; diff --git a/src/shared/ui/Card/Card.module.scss b/src/shared/ui/Card/Card.module.scss index f580337..afeb97b 100644 --- a/src/shared/ui/Card/Card.module.scss +++ b/src/shared/ui/Card/Card.module.scss @@ -1,5 +1,5 @@ .card { - padding: 20px; + padding: 20px 0; } .title { diff --git a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss index f48d676..68c1485 100644 --- a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss +++ b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss @@ -7,7 +7,7 @@ .prevButtonWrapper { position: absolute; top: 50%; - left: -25px; + left: -60px; z-index: 10; transform: translateY(-50%); @@ -18,7 +18,7 @@ .nextButtonWrapper { position: absolute; top: 50%; - right: -25px; + right: -60px; z-index: 10; transform: translateY(-50%) rotate(180deg); diff --git a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.tsx b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.tsx index baf628c..f6e7d00 100644 --- a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.tsx +++ b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.tsx @@ -89,7 +89,7 @@ export const EventsCarousel = memo( }, containerRef) return () => ctx.revert() - }, [visible]) + }, [visible, events]) /** * Обработчик инициализации Swiper diff --git a/src/widgets/TimeFrameSlider/ui/EventsCarousel/constants.ts b/src/widgets/TimeFrameSlider/ui/EventsCarousel/constants.ts index 3765c66..b6149e8 100644 --- a/src/widgets/TimeFrameSlider/ui/EventsCarousel/constants.ts +++ b/src/widgets/TimeFrameSlider/ui/EventsCarousel/constants.ts @@ -1,4 +1,3 @@ -import gsap from 'gsap' import { Navigation } from 'swiper/modules' import type { SwiperOptions } from 'swiper/types' diff --git a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss index b8c5bdb..ead27c0 100644 --- a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss +++ b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss @@ -3,13 +3,12 @@ display: flex; flex-direction: column; - justify-content: center; width: 100%; max-width: 1440px; min-height: 100vh; margin: 0 auto; - padding: 0 20px; + padding-top: 180px; color: var(--color-text); font-family: var(--font-family-main); @@ -17,6 +16,11 @@ border-right: 1px solid var(--color-border); border-left: 1px solid var(--color-border); + background-image: linear-gradient(to right, rgba(#42567A, 0.1) 1px, transparent 1px); + background-repeat: no-repeat; + background-position: center top; + background-size: 1px 100%; + overflow: hidden; @media (width <=768px) { @@ -26,16 +30,22 @@ } .title { - position: relative; + position: absolute; + top: 170px; + left: 0; z-index: 2; - margin-bottom: 40px; - padding-left: 60px; + max-width: 15ch; + padding-left: 75px; font-weight: 700; font-size: 56px; line-height: 120%; + border-left: 5px solid transparent; + border-image: var(--gradient-primary) 1; + + @media (width <=768px) { margin-bottom: 20px; padding-left: 0; @@ -50,7 +60,14 @@ display: grid; grid-template-columns: 1fr; + width: calc(100% + 40px); height: 600px; + margin: 0 -20px; + + background-image: linear-gradient(to bottom, rgba(#42567A, 0.1) 1px, transparent 1px); + background-repeat: no-repeat; + background-position: center; + background-size: 100% 1px; @media (width <=768px) { display: flex; @@ -62,14 +79,16 @@ .controls { position: absolute; - left: 60px; - bottom: 50px; + left: 100px; + bottom: 0; z-index: 10; display: flex; flex-direction: column; gap: 20px; + transform-origin: left; + @media (width <=768px) { position: static; @@ -81,8 +100,7 @@ } .pagination { - margin-bottom: 10px; - + font-weight: 400; font-size: 14px; } @@ -159,4 +177,8 @@ @media (width <=768px) { display: none; } +} + +.eventCarousel { + padding: 55px 80px 105px; } \ No newline at end of file diff --git a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx index 9ff0d38..2cc1e4e 100644 --- a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx +++ b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx @@ -10,7 +10,7 @@ import { HISTORICAL_PERIODS } from '@/entities/TimePeriod' import ChevronSvg from '@/shared/assets/chevron--left.svg' import { Button } from '@/shared/ui/Button' -import { ACTIVE_POSITION_ANGLE } from './constants' +import { ACTIVE_POSITION_ANGLE, GSAP_ANIMATION_CONFIG } from './constants' import styles from './TimeFrameSlider.module.scss' import { CircleTimeline } from '../CircleTimeline/CircleTimeline' import { EventsCarousel } from '../EventsCarousel/EventsCarousel' @@ -35,16 +35,20 @@ export const TimeFrameSlider = memo(() => { const endYearRef = useRef(null) const containerRef = useRef(null) - // Мемоизированные константы - const totalPeriods = useMemo(() => HISTORICAL_PERIODS.length, []) - const anglePerPoint = useMemo(() => 360 / totalPeriods, [totalPeriods]) - // Текущий период const currentPeriod = useMemo( () => HISTORICAL_PERIODS[activePeriod], [activePeriod] ) + // Мемоизированные константы + const totalPeriods = useMemo(() => HISTORICAL_PERIODS.length, []) + const anglePerPoint = useMemo(() => 360 / totalPeriods, [totalPeriods]) + + // Рефы для предыдущих значений периода + const prevYearFromRef = useRef(currentPeriod.yearFrom) + const prevYearToRef = useRef(currentPeriod.yearTo) + /** * Расчет поворота при изменении активного периода * Использует кратчайший путь для анимации @@ -68,22 +72,33 @@ export const TimeFrameSlider = memo(() => { const ctx = gsap.context(() => { if (startYearRef.current) { - gsap.to(startYearRef.current, { - innerText: currentPeriod.yearFrom, - snap: { innerText: 1 }, - duration: 1, - ease: 'power2.inOut', - }) + gsap.fromTo( + startYearRef.current, + { + innerText: prevYearFromRef.current, + }, + { + innerText: currentPeriod.yearFrom, + ...GSAP_ANIMATION_CONFIG, + } + ) } if (endYearRef.current) { - gsap.to(endYearRef.current, { - innerText: currentPeriod.yearTo, - snap: { innerText: 1 }, - duration: 1, - ease: 'power2.inOut', - }) + gsap.fromTo( + endYearRef.current, + { + innerText: prevYearToRef.current, + }, + { + innerText: currentPeriod.yearTo, + ...GSAP_ANIMATION_CONFIG, + } + ) } + + prevYearFromRef.current = currentPeriod.yearFrom + prevYearToRef.current = currentPeriod.yearTo }, containerRef) return () => ctx.revert() @@ -112,6 +127,7 @@ export const TimeFrameSlider = memo(() => {
{currentPeriod.yearFrom} + {'\u00A0'} {currentPeriod.yearTo}
@@ -137,7 +153,7 @@ export const TimeFrameSlider = memo(() => { onClick={handlePrev} aria-label='Предыдущий период' > - +
- +
+ +
) }) diff --git a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/constants.ts b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/constants.ts index 737ede5..4f61b6a 100644 --- a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/constants.ts +++ b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/constants.ts @@ -1,3 +1,5 @@ +import { Power2 } from 'gsap' + /** * Константы для компонента TimeFrameSlider */ @@ -6,3 +8,12 @@ * Угол позиции активного элемента (верхний правый угол) */ export const ACTIVE_POSITION_ANGLE = -60 + +/** + * Конфигурация анимации для изменения значения года + */ +export const GSAP_ANIMATION_CONFIG: gsap.TweenVars = { + snap: { innerText: 1 }, + duration: 1, + ease: Power2.easeInOut, +} as const