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..."
pnpm lint:styles || exit 1
echo "Запуск Unit тестов..."
pnpm test:unit || exit 1
echo "Production сборка..."
pnpm build:prod || exit 1

View File

@@ -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"
},

View File

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

View File

@@ -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;

View File

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

View File

@@ -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);

View File

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

View File

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

View File

@@ -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;
}
@@ -160,3 +178,7 @@
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 { 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<HTMLSpanElement>(null)
const containerRef = useRef<HTMLDivElement>(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, {
gsap.fromTo(
startYearRef.current,
{
innerText: prevYearFromRef.current,
},
{
innerText: currentPeriod.yearFrom,
snap: { innerText: 1 },
duration: 1,
ease: 'power2.inOut',
})
...GSAP_ANIMATION_CONFIG,
}
)
}
if (endYearRef.current) {
gsap.to(endYearRef.current, {
gsap.fromTo(
endYearRef.current,
{
innerText: prevYearToRef.current,
},
{
innerText: currentPeriod.yearTo,
snap: { innerText: 1 },
duration: 1,
ease: 'power2.inOut',
})
...GSAP_ANIMATION_CONFIG,
}
)
}
prevYearFromRef.current = currentPeriod.yearFrom
prevYearToRef.current = currentPeriod.yearTo
}, containerRef)
return () => ctx.revert()
@@ -112,6 +127,7 @@ export const TimeFrameSlider = memo(() => {
<div className={styles.content}>
<div className={styles.centerDate}>
<span ref={startYearRef}>{currentPeriod.yearFrom}</span>
{'\u00A0'}
<span ref={endYearRef}>{currentPeriod.yearTo}</span>
</div>
@@ -137,7 +153,7 @@ export const TimeFrameSlider = memo(() => {
onClick={handlePrev}
aria-label='Предыдущий период'
>
<ChevronSvg width={6.25} height={12.5} stroke='#42567A' />
<ChevronSvg width={9} height={14} stroke='#42567A' />
</Button>
<Button
variant='round'
@@ -147,8 +163,8 @@ export const TimeFrameSlider = memo(() => {
aria-label='Следующий период'
>
<ChevronSvg
width={6.25}
height={12.5}
width={9}
height={14}
stroke='#42567A'
className={styles.rotated}
/>
@@ -157,8 +173,10 @@ export const TimeFrameSlider = memo(() => {
</div>
</div>
<div className={styles.eventCarousel}>
<EventsCarousel events={currentPeriod.events} visible />
</div>
</div>
)
})

View File

@@ -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