From 53d7b90c72c9fba68135ddb92400f0633d5bc851 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 23 Nov 2025 15:38:19 +0300 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=D0=9F=D1=80=D0=B8=D0=B2=D0=B5?= =?UTF-8?q?=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B2=20=D1=81?= =?UTF-8?q?=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- babel.config.README.md | 19 ++++++++++++++++--- config/jest/setupTests.ts | 12 ++++++++++++ tsconfig.README.md | 21 +++++++++++++++++---- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/babel.config.README.md b/babel.config.README.md index ed5352c..6fdfb26 100644 --- a/babel.config.README.md +++ b/babel.config.README.md @@ -94,12 +94,22 @@ Babel используется через `babel-loader` в webpack конфиг ```typescript // config/build/loaders/buildBabelLoader.ts { - test: /\.(js|jsx|tsx)$/, - exclude: /node_modules/, + test: /\.(js|jsx|tsx|ts)$/, + exclude: [ + /node_modules/, + /\.test\.(ts|tsx)$/, // Исключаем тестовые файлы + /\.spec\.(ts|tsx)$/, // Исключаем spec файлы + /\.stories\.(ts|tsx)$/ // Исключаем Storybook файлы + ], use: { loader: 'babel-loader', options: { - presets: ['@babel/preset-env'], + cacheDirectory: true, // Кеширование для ускорения пересборки + presets: [ + '@babel/preset-env', + ['@babel/preset-react', { runtime: 'automatic' }], + '@babel/preset-typescript' // Компиляция TypeScript через Babel + ], plugins: [ isDev && require.resolve('react-refresh/babel') ].filter(Boolean) @@ -108,6 +118,9 @@ Babel используется через `babel-loader` в webpack конфиг } ``` +**Важно:** TypeScript компилируется через Babel, а не через `ts-loader`. +Проверка типов выполняется отдельно через `pnpm type-check` (tsc --noEmit). + ## React Refresh (только в dev режиме) В режиме разработки добавляется плагин `react-refresh/babel` для горячей перезагрузки React компонентов без потери состояния. diff --git a/config/jest/setupTests.ts b/config/jest/setupTests.ts index b5c24bc..004e08d 100644 --- a/config/jest/setupTests.ts +++ b/config/jest/setupTests.ts @@ -22,3 +22,15 @@ jest.mock('gsap', () => { Power2: gsapMock.Power2, // Экспортируем Power2 отдельно } }) + +// Глобальный мок для @gsap/react +jest.mock('@gsap/react', () => ({ + useGSAP: (fn: () => void) => { + // Выполняем функцию немедленно в тестах + // eslint-disable-next-line react-hooks/rules-of-hooks + const { useEffect } = require('react') + useEffect(() => { + fn() + }, []) + }, +})) diff --git a/tsconfig.README.md b/tsconfig.README.md index 6ec777c..bc247f3 100644 --- a/tsconfig.README.md +++ b/tsconfig.README.md @@ -34,10 +34,20 @@ - **paths**: Настройка алиасов для импортов ```json { + "@/*": ["./src/*"], "*": ["./src/*"] } ``` - Позволяет импортировать файлы из `src` без указания полного пути + Позволяет импортировать файлы из `src` с использованием `@/` или без префикса + +### Типы +- **types**: Глобальные типы для проекта + ```json + ["node", "jest", "@testing-library/jest-dom"] + ``` + - `node` - типы Node.js API + - `jest` - типы для Jest тестов + - `@testing-library/jest-dom` - типы для дополнительных матчеров Jest ## Настройки ts-node @@ -71,10 +81,13 @@ pnpm type-check ### Использование алиасов путей ```typescript // Вместо: -import { Button } from '../../../components/Button' +import { Button } from '../../../shared/ui/Button' -// Можно писать: -import { Button } from 'components/Button' +// Можно писать с @ алиасом: +import { Button } from '@/shared/ui/Button' + +// Или без префикса (для совместимости): +import { Button } from 'shared/ui/Button' ``` ### JSX без импорта React From c57957f15cfca47884cd0e226569ae521e5c8c4c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 23 Nov 2025 15:39:04 +0300 Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20=D0=9F=D0=B5=D1=80=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=BC=D0=BF?= =?UTF-8?q?=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=85=D1=83=D0=BA=D0=B0=20useGSAP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 14 ++++ .../ui/CircleTimeline/CircleTimeline.tsx | 84 ++++++++++--------- .../ui/EventsCarousel/EventsCarousel.tsx | 22 +++-- .../ui/TimeFrameSlider/TimeFrameSlider.tsx | 21 ++--- 5 files changed, 80 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index 2acf48a..a0592c5 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "author": "", "license": "ISC", "dependencies": { + "@gsap/react": "^2.1.2", "classnames": "^2.5.1", "gsap": "^3.13.0", "react": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 770ab62..49ec6d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@gsap/react': + specifier: ^2.1.2 + version: 2.1.2(gsap@3.13.0)(react@19.2.0) classnames: specifier: ^2.5.1 version: 2.5.1 @@ -1111,6 +1114,12 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@gsap/react@2.1.2': + resolution: {integrity: sha512-JqliybO1837UcgH2hVOM4VO+38APk3ECNrsuSM4MuXp+rbf+/2IG2K1YJiqfTcXQHH7XlA0m3ykniFYstfq0Iw==} + peerDependencies: + gsap: ^3.12.5 + react: '>=17' + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -6799,6 +6808,11 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@gsap/react@2.1.2(gsap@3.13.0)(react@19.2.0)': + dependencies: + gsap: 3.13.0 + react: 19.2.0 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': diff --git a/src/widgets/TimeFrameSlider/ui/CircleTimeline/CircleTimeline.tsx b/src/widgets/TimeFrameSlider/ui/CircleTimeline/CircleTimeline.tsx index f803983..a8f3dec 100644 --- a/src/widgets/TimeFrameSlider/ui/CircleTimeline/CircleTimeline.tsx +++ b/src/widgets/TimeFrameSlider/ui/CircleTimeline/CircleTimeline.tsx @@ -8,9 +8,10 @@ * Поддерживает клик по точкам для переключения периодов. */ +import { useGSAP } from '@gsap/react' import classNames from 'classnames' import { gsap } from 'gsap' -import { memo, useCallback, useEffect, useMemo, useRef } from 'react' +import { memo, useCallback, useMemo, useRef } from 'react' import styles from './CircleTimeline.module.scss' import { calculateCoordinates } from '../../lib/utils/calculateCoordinates/calculateCoordinates' @@ -71,52 +72,55 @@ export const CircleTimeline = memo(function CircleTimeline({ const titlesRef = useRef<(HTMLSpanElement | null)[]>([]) /** - * Эффект для анимации поворота круга и контр-поворота точек - * Запускается при изменении rotation + * Анимация поворота круга и контр-поворота точек + * Использует useGSAP hook для автоматической очистки анимаций */ - useEffect(() => { - // Анимация поворота контейнера круга - if (circleRef.current) { - gsap.to(circleRef.current, { - rotation, - duration: ANIMATION_DURATION, - ease: ANIMATION_EASE, - }) - } - - // Контр-поворот точек, чтобы текст оставался читаемым - pointsRef.current.forEach((point, index) => { - if (point) { - gsap.to(point, { - rotation: -rotation, - duration: 0, + useGSAP( + () => { + // Анимация поворота контейнера круга + if (circleRef.current) { + gsap.to(circleRef.current, { + rotation, + duration: ANIMATION_DURATION, ease: ANIMATION_EASE, }) + } - // Анимация заголовка - const title = titlesRef.current[index] - if (title) { - // Сбрасываем предыдущие анимации для этого элемента - gsap.killTweensOf(title) + // Контр-поворот точек, чтобы текст оставался читаемым + pointsRef.current.forEach((point, index) => { + if (point) { + gsap.to(point, { + rotation: -rotation, + duration: 0, + ease: ANIMATION_EASE, + }) - if (index === activeIndex) { - gsap.to(title, { - opacity: 1, - visibility: 'visible', - duration: 0.5, - delay: ANIMATION_DURATION, // Ждем окончания вращения - }) - } else { - gsap.to(title, { - opacity: 0, - visibility: 'hidden', - duration: 0.2, - }) + // Анимация заголовка + const title = titlesRef.current[index] + if (title) { + // Останавливаем предыдущие анимации для предотвращения конфликтов + gsap.killTweensOf(title) + + if (index === activeIndex) { + gsap.to(title, { + opacity: 1, + visibility: 'visible', + duration: 0.5, + delay: ANIMATION_DURATION, // Ждем окончания вращения + }) + } else { + gsap.to(title, { + opacity: 0, + visibility: 'hidden', + duration: 0.2, + }) + } } } - } - }) - }, [rotation, activeIndex]) + }) + }, + { dependencies: [rotation, activeIndex] } + ) /** * Мемоизированный расчет позиций точек на круге diff --git a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.tsx b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.tsx index 0e9c4df..54326ba 100644 --- a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.tsx +++ b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.tsx @@ -4,9 +4,10 @@ * Отображает список исторических событий в виде слайдера */ +import { useGSAP } from '@gsap/react' import classNames from 'classnames' import { gsap } from 'gsap' -import { memo, useEffect, useRef, useState } from 'react' +import { memo, useRef, useState } from 'react' import { Swiper, SwiperSlide } from 'swiper/react' import 'swiper/css' @@ -45,7 +46,7 @@ export interface EventsCarouselProps { * * Использует Swiper для создания слайдера с кастомной навигацией. * Поддерживает адаптивное количество слайдов на разных размерах экрана. - * Анимирует появление/исчезновение с помощью GSAP. + * Анимирует появление/исчезновение с помощью GSAP useGSAP hook. * * @example * ```tsx @@ -62,13 +63,11 @@ export const EventsCarousel = memo( const [isEnd, setIsEnd] = useState(false) /** - * Эффект для анимации появления/исчезновения карусели - * Использует GSAP для плавной анимации opacity и y-позиции + * Анимация появления/исчезновения карусели + * Использует useGSAP hook для автоматической очистки анимаций */ - useEffect(() => { - if (!containerRef.current) return - - const ctx = gsap.context(() => { + useGSAP( + () => { if (visible) { gsap.fromTo( containerRef.current, @@ -86,10 +85,9 @@ export const EventsCarousel = memo( duration: HIDE_DURATION, }) } - }, containerRef) - - return () => ctx.revert() - }, [visible, events]) + }, + { scope: containerRef, dependencies: [visible, events] } + ) /** * Обработчик инициализации Swiper diff --git a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx index 5293b6d..2bfc39b 100644 --- a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx +++ b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx @@ -3,6 +3,7 @@ * Главный компонент временной шкалы с круговой диаграммой и каруселью событий */ +import { useGSAP } from '@gsap/react' import classNames from 'classnames' import { gsap } from 'gsap' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' @@ -21,7 +22,7 @@ import { EventsCarousel } from '../EventsCarousel/EventsCarousel' * * Отображает исторические периоды на круговой диаграмме с возможностью * переключения между ними. Для каждого периода показывается карусель событий. - * Центральные даты анимируются при смене периода с помощью GSAP. + * Центральные даты анимируются при смене периода с помощью GSAP useGSAP hook. * * @example * ```tsx @@ -66,13 +67,11 @@ export const TimeFrameSlider = memo(() => { }, [activePeriod, anglePerPoint]) /** - * Анимация центральных дат с использованием GSAP + * Анимация центральных дат с использованием GSAP useGSAP hook * Плавно изменяет числа при смене периода */ - useEffect(() => { - if (!containerRef.current) return - - const ctx = gsap.context(() => { + useGSAP( + () => { if (startYearRef.current) { gsap.fromTo( startYearRef.current, @@ -109,10 +108,12 @@ export const TimeFrameSlider = memo(() => { { opacity: 1, visibility: 'visible', duration: 1 } ) } - }, containerRef) - - return () => ctx.revert() - }, [currentPeriod.yearFrom, currentPeriod.yearTo]) + }, + { + scope: containerRef, + dependencies: [currentPeriod.yearFrom, currentPeriod.yearTo], + } + ) /** * Переключение на предыдущий период From 92e0b474a4e4d0235025aaa714ef208881df6937 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 23 Nov 2025 16:08:14 +0300 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=D0=9F=D1=80=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++------------ config/build/buildLoaders.ts | 8 +++++--- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 6650a26..4ffaa7e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Only Task - Интерактивная временная шкала +# Only Task -Современное React-приложение с интерактивной круговой временной шкалой и каруселью исторических событий. +Тестовое задание для only.digital ## 🚀 Технологии @@ -218,14 +218,4 @@ Pre-push hook автоматически запускает: ISC -## 👨‍💻 Разработка - -Для разработки рекомендуется: -1. Запустить `pnpm dev` для dev-сервера -2. Использовать `pnpm storybook` для разработки компонентов в изоляции -3. Писать тесты для новых компонентов -4. Следовать существующей структуре проекта - ---- - **Приятной разработки! 🚀** diff --git a/config/build/buildLoaders.ts b/config/build/buildLoaders.ts index b08ae5e..2a3d3af 100644 --- a/config/build/buildLoaders.ts +++ b/config/build/buildLoaders.ts @@ -13,9 +13,11 @@ import { BuildOptions } from './types/config' * Текущий порядок: * 1. fileLoader - обрабатывает изображения и шрифты * 2. svgrLoader - преобразует SVG в React компоненты - * 3. babelLoader - транспилирует JS/JSX/TSX с React Refresh - * 4. typescriptLoader - компилирует TypeScript - * 5. cssLoader - обрабатывает CSS/SCSS с модулями + * 3. babelLoader - транспилирует JS/JSX/TS/TSX с помощью Babel (включая TypeScript) + * 4. cssLoader - обрабатывает CSS/SCSS с модулями + * + * Примечание: TypeScript компилируется через Babel (@babel/preset-typescript), + * а не через ts-loader. Проверка типов выполняется отдельно через `tsc --noEmit`. * * @param {BuildOptions} options - Опции сборки * @param {boolean} options.isDev - Флаг режима разработки From 174547791b0f4dfab5a3c7b621d59c78a9594df9 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 23 Nov 2025 16:09:23 +0300 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=D0=90=D0=B4=D0=B0=D0=BF=D1=82?= =?UTF-8?q?=D0=B8=D0=B2=20=D0=B2=D0=B8=D0=B4=D0=B6=D0=B5=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=20=D0=BD=D0=B0=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20container=20quries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EventsCarousel/EventsCarousel.module.scss | 18 +-- .../ui/EventsCarousel/constants.ts | 2 +- .../TimeFrameSlider.module.scss | 55 +++++--- .../ui/TimeFrameSlider/TimeFrameSlider.tsx | 126 +++++++++--------- 4 files changed, 107 insertions(+), 94 deletions(-) diff --git a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss index 06d766a..6b8db76 100644 --- a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss +++ b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss @@ -33,27 +33,19 @@ } :global(.swiper) { - @media (width <=768px) { - padding: 0 20px; + @container timeframe-slider (width <=768px) { + padding: 0 40px; } } -:global(.swiper-slide-next) { +:global(.swiper-slide-visible) { transition: opacity 0.3s ease; - @media (width <=768px) { + @container timeframe-slider (width <768px) { opacity: 0.4; } } -:global(.swiper-slide-prev) { - transition: opacity 0.3s ease; - - @media (width <=768px) { - opacity: 0.4; - } -} - -:global(.swiper-slide-active) { +:global(.swiper-slide-fully-visible) { opacity: 1; } \ No newline at end of file diff --git a/src/widgets/TimeFrameSlider/ui/EventsCarousel/constants.ts b/src/widgets/TimeFrameSlider/ui/EventsCarousel/constants.ts index 47ec773..0740065 100644 --- a/src/widgets/TimeFrameSlider/ui/EventsCarousel/constants.ts +++ b/src/widgets/TimeFrameSlider/ui/EventsCarousel/constants.ts @@ -16,7 +16,7 @@ export const EVENT_CAROUSEL_CONFIG: SwiperOptions = { }, breakpoints: { 576: { - slidesPerView: 2, + slidesPerView: 2.5, }, 768: { slidesPerView: 2, diff --git a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss index f75e634..a3644cd 100644 --- a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss +++ b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss @@ -1,3 +1,10 @@ +/* Wrapper для container queries - должен быть родителем контейнера */ +.wrapper { + /* Включаем container queries для адаптивности виджета */ + container-type: inline-size; + container-name: timeframe-slider; +} + .container { position: relative; @@ -23,11 +30,11 @@ overflow: hidden; - @media (width <=1024px) { + @container timeframe-slider (width <=1024px) { padding-top: 100px; } - @media (width <=768px) { + @container timeframe-slider (width <=576px) { padding: 60px 20px 20px; background-image: unset; @@ -51,20 +58,24 @@ border-image: var(--gradient-primary) 1; - @media (width <=1024px) { + @container timeframe-slider (width <=1024px) { top: 80px; font-size: 40px; } - @media (width <=768px) { + @container timeframe-slider (width <=768px) { + padding-left: 20px; + font-size: 34px; + } + + @container timeframe-slider (width <=576px) { position: relative; inset: unset; margin-bottom: 20px; padding-left: 0; - font-size: 20px; border: none; } @@ -85,7 +96,7 @@ background-position: center; background-size: 100% 1px; - @media (width <=768px) { + @container timeframe-slider (width <=576px) { position: unset; display: flex; @@ -111,19 +122,22 @@ transform-origin: left; - @media (width <=1024px) { + @container timeframe-slider (width <=1024px) { left: 100px; bottom: 40px; } - @media (width <=768px) { + @container timeframe-slider (width <=768px) { + left: 40px; + gap: 10px; + } + + @container timeframe-slider (width <=576px) { left: 20px; bottom: 13px; order: 2; - gap: 10px; - margin-top: 20px; padding: 0; } @@ -138,7 +152,7 @@ display: flex; gap: 20px; - @media (width <=768px) { + @container timeframe-slider (width <=768px) { gap: 8px; } } @@ -147,7 +161,7 @@ width: 9px; height: 14px; - @media (width <=768px) { + @container timeframe-slider (width <=576px) { width: 6px; height: 11.5px; } @@ -156,7 +170,7 @@ .dots { display: none; - @media (width <=768px) { + @container timeframe-slider (width <=576px) { position: absolute; left: 50%; bottom: 32px; @@ -214,14 +228,19 @@ pointer-events: none; - @media (width <=1024px) { + @container timeframe-slider (width <=1024px) { gap: 40px; font-size: 140px; line-height: 120px; } - @media (width <=768px) { + @container timeframe-slider (width <=768px) { + font-size: 100px; + line-height: 80px; + } + + @container timeframe-slider (width <=576px) { position: static; gap: 20px; @@ -246,7 +265,7 @@ .periodLabel { display: none; - @media (width <=768px) { + @container timeframe-slider (width <=576px) { order: 1; display: block; @@ -266,7 +285,7 @@ width: 100%; height: 100%; - @media (width <=768px) { + @container timeframe-slider (width <=576px) { display: none; } } @@ -274,7 +293,7 @@ .carouselContainer { padding: 55px 80px 105px; - @media (width <=768px) { + @container timeframe-slider (width <=768px) { width: calc(100% + 40px); margin: 0 -20px; padding: 0; diff --git a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx index 2bfc39b..aa7d3b6 100644 --- a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx +++ b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx @@ -132,74 +132,76 @@ export const TimeFrameSlider = memo(() => { }, [totalPeriods]) return ( -
-

Исторические даты

+
+
+

Исторические даты

-
-
- {currentPeriod.yearFrom} - {currentPeriod.yearTo} -
- -
- {currentPeriod.label} -
- -
- -
- -
-
- {String(activePeriod + 1).padStart(2, '0')}/ - {String(totalPeriods).padStart(2, '0')} +
+
+ {currentPeriod.yearFrom} + {currentPeriod.yearTo}
-
- - + +
+ {currentPeriod.label} +
+ +
+ +
+ +
+
+ {String(activePeriod + 1).padStart(2, '0')}/ + {String(totalPeriods).padStart(2, '0')} +
+
+ + +
-
-
- -
+
+ +
-
- {HISTORICAL_PERIODS.map((_, index) => ( -
) From dad6253877216af0cb0e5316f20ec6c44deb4816 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 23 Nov 2025 16:18:23 +0300 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BB=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B0,=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EventsCarousel/EventsCarousel.module.scss | 4 +- .../TimeFrameSlider.module.scss | 38 ++++++++++--------- .../ui/TimeFrameSlider/TimeFrameSlider.tsx | 1 + 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss index 6b8db76..c7b0618 100644 --- a/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss +++ b/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.module.scss @@ -33,7 +33,7 @@ } :global(.swiper) { - @container timeframe-slider (width <=768px) { + @container timeframe-slider (width <= 768px) { padding: 0 40px; } } @@ -41,7 +41,7 @@ :global(.swiper-slide-visible) { transition: opacity 0.3s ease; - @container timeframe-slider (width <768px) { + @container timeframe-slider (width < 768px) { opacity: 0.4; } } diff --git a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss index a3644cd..c478744 100644 --- a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss +++ b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.module.scss @@ -30,11 +30,11 @@ overflow: hidden; - @container timeframe-slider (width <=1024px) { + @container timeframe-slider (width <= 1024px) { padding-top: 100px; } - @container timeframe-slider (width <=576px) { + @container timeframe-slider (width <= 576px) { padding: 60px 20px 20px; background-image: unset; @@ -58,18 +58,19 @@ border-image: var(--gradient-primary) 1; - @container timeframe-slider (width <=1024px) { + @container timeframe-slider (width <= 1024px) { top: 80px; font-size: 40px; } - @container timeframe-slider (width <=768px) { + @container timeframe-slider (width <= 768px) { padding-left: 20px; + font-size: 34px; } - @container timeframe-slider (width <=576px) { + @container timeframe-slider (width <= 576px) { position: relative; inset: unset; @@ -96,7 +97,7 @@ background-position: center; background-size: 100% 1px; - @container timeframe-slider (width <=576px) { + @container timeframe-slider (width <= 576px) { position: unset; display: flex; @@ -122,17 +123,18 @@ transform-origin: left; - @container timeframe-slider (width <=1024px) { + @container timeframe-slider (width <= 1024px) { left: 100px; bottom: 40px; } - @container timeframe-slider (width <=768px) { + @container timeframe-slider (width <= 768px) { left: 40px; + gap: 10px; } - @container timeframe-slider (width <=576px) { + @container timeframe-slider (width <= 576px) { left: 20px; bottom: 13px; @@ -152,7 +154,7 @@ display: flex; gap: 20px; - @container timeframe-slider (width <=768px) { + @container timeframe-slider (width <= 768px) { gap: 8px; } } @@ -161,7 +163,7 @@ width: 9px; height: 14px; - @container timeframe-slider (width <=576px) { + @container timeframe-slider (width <= 576px) { width: 6px; height: 11.5px; } @@ -170,7 +172,7 @@ .dots { display: none; - @container timeframe-slider (width <=576px) { + @container timeframe-slider (width <= 576px) { position: absolute; left: 50%; bottom: 32px; @@ -228,19 +230,19 @@ pointer-events: none; - @container timeframe-slider (width <=1024px) { + @container timeframe-slider (width <= 1024px) { gap: 40px; font-size: 140px; line-height: 120px; } - @container timeframe-slider (width <=768px) { + @container timeframe-slider (width <= 768px) { font-size: 100px; line-height: 80px; } - @container timeframe-slider (width <=576px) { + @container timeframe-slider (width <= 576px) { position: static; gap: 20px; @@ -265,7 +267,7 @@ .periodLabel { display: none; - @container timeframe-slider (width <=576px) { + @container timeframe-slider (width <= 576px) { order: 1; display: block; @@ -285,7 +287,7 @@ width: 100%; height: 100%; - @container timeframe-slider (width <=576px) { + @container timeframe-slider (width <= 576px) { display: none; } } @@ -293,7 +295,7 @@ .carouselContainer { padding: 55px 80px 105px; - @container timeframe-slider (width <=768px) { + @container timeframe-slider (width <= 768px) { width: calc(100% + 40px); margin: 0 -20px; padding: 0; diff --git a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx index aa7d3b6..271a893 100644 --- a/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx +++ b/src/widgets/TimeFrameSlider/ui/TimeFrameSlider/TimeFrameSlider.tsx @@ -101,6 +101,7 @@ export const TimeFrameSlider = memo(() => { prevYearFromRef.current = currentPeriod.yearFrom prevYearToRef.current = currentPeriod.yearTo + // Анимация появления лейбла периода if (periodLabelRef.current) { gsap.fromTo( periodLabelRef.current,