Merge pull request #4 from e7f3/feature/minor-changes

Feature/minor changes
This commit is contained in:
Ilia Mashkov
2025-11-21 13:02:45 +03:00
committed by GitHub
11 changed files with 93 additions and 40 deletions

View File

@@ -12,6 +12,9 @@ pnpm lint || exit 1
echo "Проверка Stylelint..." echo "Проверка Stylelint..."
pnpm lint:styles || exit 1 pnpm lint:styles || exit 1
echo "Запуск Unit тестов..."
pnpm test:unit || exit 1
echo "Production сборка..." echo "Production сборка..."
pnpm build:prod || exit 1 pnpm build:prod || exit 1

View File

@@ -12,7 +12,7 @@
"test:unit": "jest --config ./config/jest/jest.config.ts", "test:unit": "jest --config ./config/jest/jest.config.ts",
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
"prepare": "husky", "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", "storybook": "storybook dev -p 6006 -c config/storybook",
"build-storybook": "storybook build -c config/storybook" "build-storybook": "storybook build -c config/storybook"
}, },

View File

@@ -1,3 +1,3 @@
@import './fonts'; @use 'fonts';
@import './variables'; @use 'variables';
@import './reset'; @use 'reset';

View File

@@ -9,7 +9,7 @@
--color-white: #FFF; --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; --font-family-main: 'PT Sans', sans-serif;

View File

@@ -1,5 +1,5 @@
.card { .card {
padding: 20px; padding: 20px 0;
} }
.title { .title {

View File

@@ -7,7 +7,7 @@
.prevButtonWrapper { .prevButtonWrapper {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: -25px; left: -60px;
z-index: 10; z-index: 10;
transform: translateY(-50%); transform: translateY(-50%);
@@ -18,7 +18,7 @@
.nextButtonWrapper { .nextButtonWrapper {
position: absolute; position: absolute;
top: 50%; top: 50%;
right: -25px; right: -60px;
z-index: 10; z-index: 10;
transform: translateY(-50%) rotate(180deg); transform: translateY(-50%) rotate(180deg);

View File

@@ -89,7 +89,7 @@ export const EventsCarousel = memo(
}, containerRef) }, containerRef)
return () => ctx.revert() return () => ctx.revert()
}, [visible]) }, [visible, events])
/** /**
* Обработчик инициализации Swiper * Обработчик инициализации Swiper

View File

@@ -1,4 +1,3 @@
import gsap from 'gsap'
import { Navigation } from 'swiper/modules' import { Navigation } from 'swiper/modules'
import type { SwiperOptions } from 'swiper/types' import type { SwiperOptions } from 'swiper/types'

View File

@@ -3,13 +3,12 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
width: 100%; width: 100%;
max-width: 1440px; max-width: 1440px;
min-height: 100vh; min-height: 100vh;
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding-top: 180px;
color: var(--color-text); color: var(--color-text);
font-family: var(--font-family-main); font-family: var(--font-family-main);
@@ -17,6 +16,11 @@
border-right: 1px solid var(--color-border); border-right: 1px solid var(--color-border);
border-left: 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; overflow: hidden;
@media (width <=768px) { @media (width <=768px) {
@@ -26,16 +30,22 @@
} }
.title { .title {
position: relative; position: absolute;
top: 170px;
left: 0;
z-index: 2; z-index: 2;
margin-bottom: 40px; max-width: 15ch;
padding-left: 60px; padding-left: 75px;
font-weight: 700; font-weight: 700;
font-size: 56px; font-size: 56px;
line-height: 120%; line-height: 120%;
border-left: 5px solid transparent;
border-image: var(--gradient-primary) 1;
@media (width <=768px) { @media (width <=768px) {
margin-bottom: 20px; margin-bottom: 20px;
padding-left: 0; padding-left: 0;
@@ -50,7 +60,14 @@
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
width: calc(100% + 40px);
height: 600px; 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) { @media (width <=768px) {
display: flex; display: flex;
@@ -62,14 +79,16 @@
.controls { .controls {
position: absolute; position: absolute;
left: 60px; left: 100px;
bottom: 50px; bottom: 0;
z-index: 10; z-index: 10;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
transform-origin: left;
@media (width <=768px) { @media (width <=768px) {
position: static; position: static;
@@ -81,8 +100,7 @@
} }
.pagination { .pagination {
margin-bottom: 10px; font-weight: 400;
font-size: 14px; font-size: 14px;
} }
@@ -160,3 +178,7 @@
display: none; display: none;
} }
} }
.eventCarousel {
padding: 55px 80px 105px;
}

View File

@@ -10,7 +10,7 @@ import { HISTORICAL_PERIODS } from '@/entities/TimePeriod'
import ChevronSvg from '@/shared/assets/chevron--left.svg' import ChevronSvg from '@/shared/assets/chevron--left.svg'
import { Button } from '@/shared/ui/Button' 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 styles from './TimeFrameSlider.module.scss'
import { CircleTimeline } from '../CircleTimeline/CircleTimeline' import { CircleTimeline } from '../CircleTimeline/CircleTimeline'
import { EventsCarousel } from '../EventsCarousel/EventsCarousel' import { EventsCarousel } from '../EventsCarousel/EventsCarousel'
@@ -35,16 +35,20 @@ export const TimeFrameSlider = memo(() => {
const endYearRef = useRef<HTMLSpanElement>(null) const endYearRef = useRef<HTMLSpanElement>(null)
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
// Мемоизированные константы
const totalPeriods = useMemo(() => HISTORICAL_PERIODS.length, [])
const anglePerPoint = useMemo(() => 360 / totalPeriods, [totalPeriods])
// Текущий период // Текущий период
const currentPeriod = useMemo( const currentPeriod = useMemo(
() => HISTORICAL_PERIODS[activePeriod], () => HISTORICAL_PERIODS[activePeriod],
[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(() => { const ctx = gsap.context(() => {
if (startYearRef.current) { if (startYearRef.current) {
gsap.to(startYearRef.current, { gsap.fromTo(
innerText: currentPeriod.yearFrom, startYearRef.current,
snap: { innerText: 1 }, {
duration: 1, innerText: prevYearFromRef.current,
ease: 'power2.inOut', },
}) {
innerText: currentPeriod.yearFrom,
...GSAP_ANIMATION_CONFIG,
}
)
} }
if (endYearRef.current) { if (endYearRef.current) {
gsap.to(endYearRef.current, { gsap.fromTo(
innerText: currentPeriod.yearTo, endYearRef.current,
snap: { innerText: 1 }, {
duration: 1, innerText: prevYearToRef.current,
ease: 'power2.inOut', },
}) {
innerText: currentPeriod.yearTo,
...GSAP_ANIMATION_CONFIG,
}
)
} }
prevYearFromRef.current = currentPeriod.yearFrom
prevYearToRef.current = currentPeriod.yearTo
}, containerRef) }, containerRef)
return () => ctx.revert() return () => ctx.revert()
@@ -112,6 +127,7 @@ export const TimeFrameSlider = memo(() => {
<div className={styles.content}> <div className={styles.content}>
<div className={styles.centerDate}> <div className={styles.centerDate}>
<span ref={startYearRef}>{currentPeriod.yearFrom}</span> <span ref={startYearRef}>{currentPeriod.yearFrom}</span>
{'\u00A0'}
<span ref={endYearRef}>{currentPeriod.yearTo}</span> <span ref={endYearRef}>{currentPeriod.yearTo}</span>
</div> </div>
@@ -137,7 +153,7 @@ export const TimeFrameSlider = memo(() => {
onClick={handlePrev} onClick={handlePrev}
aria-label='Предыдущий период' aria-label='Предыдущий период'
> >
<ChevronSvg width={6.25} height={12.5} stroke='#42567A' /> <ChevronSvg width={9} height={14} stroke='#42567A' />
</Button> </Button>
<Button <Button
variant='round' variant='round'
@@ -147,8 +163,8 @@ export const TimeFrameSlider = memo(() => {
aria-label='Следующий период' aria-label='Следующий период'
> >
<ChevronSvg <ChevronSvg
width={6.25} width={9}
height={12.5} height={14}
stroke='#42567A' stroke='#42567A'
className={styles.rotated} className={styles.rotated}
/> />
@@ -157,7 +173,9 @@ export const TimeFrameSlider = memo(() => {
</div> </div>
</div> </div>
<EventsCarousel events={currentPeriod.events} visible /> <div className={styles.eventCarousel}>
<EventsCarousel events={currentPeriod.events} visible />
</div>
</div> </div>
) )
}) })

View File

@@ -1,3 +1,5 @@
import { Power2 } from 'gsap'
/** /**
* Константы для компонента TimeFrameSlider * Константы для компонента TimeFrameSlider
*/ */
@@ -6,3 +8,12 @@
* Угол позиции активного элемента (верхний правый угол) * Угол позиции активного элемента (верхний правый угол)
*/ */
export const ACTIVE_POSITION_ANGLE = -60 export const ACTIVE_POSITION_ANGLE = -60
/**
* Конфигурация анимации для изменения значения года
*/
export const GSAP_ANIMATION_CONFIG: gsap.TweenVars = {
snap: { innerText: 1 },
duration: 1,
ease: Power2.easeInOut,
} as const