From 76eb7a276f232910572c4998562edab24bb67405 Mon Sep 17 00:00:00 2001 From: Krotov Petr <83280920+KrotovPetr@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:13:30 +0300 Subject: [PATCH 1/5] feat: add use client directive for RSC --- .agents/docs/rsc-support-audit.md | 490 ++++++++++++++++++ package.json | 16 + src/blocks/CardLayout/CardLayout.tsx | 2 + src/blocks/Companies/Companies.tsx | 2 + src/blocks/ContentLayout/ContentLayout.tsx | 2 + .../ExtendedFeatures/ExtendedFeatures.tsx | 2 + src/blocks/FilterBlock/FilterBlock.tsx | 2 + src/blocks/FoldableList/FoldableList.tsx | 2 + .../FoldableListBlockItem.tsx | 2 + src/blocks/Footer/Footer.tsx | 2 + .../CopyrightFloor/CopyrightFloor.tsx | 2 + .../components/LangSwitcher/LangSwitcher.tsx | 2 + .../NavigationFloor/NavigationFloor.tsx | 2 + .../components/SocialFloor/SocialFloor.tsx | 2 + src/blocks/Footer/hooks/useLogoImageProps.ts | 2 + .../Footer/hooks/useOverflowListItems.ts | 2 + src/blocks/Form/Form.tsx | 2 + src/blocks/Header/Header.tsx | 2 + src/blocks/HeaderSlider/HeaderSlider.tsx | 2 + src/blocks/Hero/Hero.tsx | 2 + src/blocks/Hero/hooks.ts | 2 + src/blocks/Icons/Icons.tsx | 2 + src/blocks/Info/Info.tsx | 2 + src/blocks/Media/Media.tsx | 2 + .../PromoFeaturesBlock/PromoFeaturesBlock.tsx | 2 + .../QuestionBlockItem/QuestionBlockItem.tsx | 2 + src/blocks/Questions/Questions.tsx | 2 + src/blocks/Share/Share.tsx | 2 + src/blocks/Slider/Arrow/Arrow.tsx | 2 + src/blocks/Slider/Slider.tsx | 2 + src/blocks/Slider/useSlider.tsx | 2 + src/blocks/SliderOld/Arrow/Arrow.tsx | 2 + src/blocks/SliderOld/SliderOld.tsx | 2 + src/blocks/Table/Table.tsx | 2 + src/blocks/Tabs/TabContent/TabContent.tsx | 2 + src/blocks/Tabs/Tabs.tsx | 2 + src/components/AnimateBlock/AnimateBlock.tsx | 2 + src/components/BackLink/BackLink.tsx | 2 + .../BackgroundMedia/BackgroundMedia.tsx | 2 + .../BalancedMasonry/BalancedMasonry.tsx | 2 + src/components/BrandFooter/BrandFooter.tsx | 2 + src/components/Button/Button.tsx | 2 + src/components/ButtonTabs/ButtonTabs.tsx | 2 + src/components/CardBase/CardBase.tsx | 2 + src/components/ContentIcon/ContentIcon.tsx | 2 + src/components/Control/Control.tsx | 2 + src/components/DefaultVideo/DefaultVideo.tsx | 2 + src/components/ErrorWrapper/ErrorWrapper.tsx | 2 + src/components/FileLink/FileLink.tsx | 2 + src/components/Foldable/Foldable.tsx | 2 + .../FullWidthBackground.tsx | 2 + .../FullscreenImage/FullscreenImage.tsx | 2 + .../FullscreenMedia/FullscreenMedia.tsx | 2 + src/components/HTML/HTML.tsx | 2 + .../HeaderBreadcrumbs/HeaderBreadcrumbs.tsx | 2 + src/components/Image/Image.tsx | 2 + src/components/ImageBase/ImageBase.tsx | 2 + src/components/InnerForm/InnerForm.tsx | 2 + src/components/Link/Link.tsx | 2 + src/components/Map/GoogleMap.tsx | 2 + src/components/Map/Map.tsx | 2 + src/components/Map/YMap/YMap.ts | 2 + src/components/Map/YMap/YandexMap.tsx | 2 + src/components/Media/DataLens/DataLens.tsx | 2 + src/components/Media/Image/Image.tsx | 2 + src/components/Media/Media.tsx | 2 + src/components/Media/Video/Video.tsx | 2 + src/components/MediaBase/MediaBase.tsx | 2 + src/components/OutsideClick/OutsideClick.tsx | 2 + .../OverflowScroller/OverflowScroller.tsx | 2 + src/components/ReactPlayer/CircleProgress.tsx | 2 + .../ReactPlayer/CustomBarControls.tsx | 2 + src/components/ReactPlayer/ReactPlayer.tsx | 2 + src/components/RootCn/index.tsx | 2 + src/components/RouterLink/RouterLink.tsx | 2 + src/components/Table/Table.tsx | 2 + src/components/Title/TitleItem.tsx | 2 + src/components/VideoBlock/VideoBlock.tsx | 2 + src/components/YandexForm/YandexForm.tsx | 2 + src/containers/Loadable/Loadable.tsx | 2 + .../PageConstructor/PageConstructor.tsx | 2 + src/containers/PageConstructor/Provider.tsx | 2 + .../ConstructorBlock/ConstructorBlock.tsx | 2 + .../ConstructorBlocks/ConstructorBlocks.tsx | 2 + .../ConstructorItem/ConstructorItem.tsx | 2 + .../ConstructorLoadable.tsx | 2 + .../analyticsContext/analyticsContext.tsx | 2 + src/context/animateContext/AnimateContext.tsx | 2 + src/context/blockIdContext/blockIdContext.ts | 2 + src/context/formsContext/FormsContext.ts | 2 + src/context/imageContext/imageContext.ts | 2 + src/context/innerContext/InnerContext.ts | 2 + src/context/localeContext/localeContext.ts | 2 + .../locationContext/locationContext.ts | 2 + src/context/mapsContext/mapsContext.ts | 2 + src/context/mapsContext/mapsProvider.tsx | 2 + src/context/mapsContext/useMap.ts | 2 + src/context/mobileContext/MobileContext.ts | 2 + .../ProjectSettingsContext.ts | 2 + src/context/ssrContext/SSRContext.ts | 2 + src/context/stylesContext/StylesContext.ts | 2 + src/context/theme/ThemeContext.ts | 2 + src/context/theme/useTheme.ts | 2 + src/context/theme/withTheme.tsx | 2 + src/context/videoContext/VideoContext.ts | 2 + .../windowWidthContext/WindowWidthContext.tsx | 2 + src/editor/components/AddBlock/AddBlock.tsx | 2 + src/editor/components/BlockForm/BlockForm.tsx | 2 + .../components/CodeEditor/CodeEditor.tsx | 2 + .../components/ControlPanel/ControlPanel.tsx | 2 + .../DeviceEmulationMobile.tsx | 2 + src/editor/components/EditBlock/EditBlock.tsx | 2 + .../ErrorBoundary/ErrorBoundary.tsx | 2 + .../components/PageSettings/PageSettings.tsx | 2 + src/editor/containers/Editor/Editor.tsx | 2 + src/editor/containers/Editor/hooks/useCode.ts | 2 + .../Editor/hooks/useEditorState.tsx | 2 + src/editor/containers/Form/Form.tsx | 2 + src/editor/context.ts | 2 + .../components/OneOfCustom/OneOfCustom.tsx | 2 + .../dynamic-forms-custom/hooks/useOneOf.tsx | 2 + src/editor/hooks/useCodeValidator.ts | 2 + src/editor/hooks/useFormSpec.ts | 2 + src/editor/hooks/usePreviousValue.ts | 2 + src/editor/store/main/index.ts | 2 + src/editor/store/settings/index.ts | 2 + src/grid/Row/Row.tsx | 2 + src/hooks/hubspot.ts | 2 + src/hooks/useAnalytics.ts | 2 + src/hooks/useDeviceValue.ts | 2 + src/hooks/useFocus.ts | 2 + src/hooks/useHeightCalculator.ts | 2 + src/hooks/useImageSize.ts | 2 + src/hooks/useMount.ts | 2 + src/hooks/useWindowBreakpoint.ts | 2 + src/models/constructor-items/blocks.ts | 2 +- src/models/constructor-items/common.ts | 2 +- .../DesktopNavigation/DesktopNavigation.tsx | 2 + src/navigation/components/Logo/Logo.tsx | 2 + .../MobileMenuButton/MobileMenuButton.tsx | 2 + .../MobileNavigation/MobileNavigation.tsx | 2 + .../components/Navigation/Navigation.tsx | 2 + .../NavigationItem/NavigationItem.tsx | 2 + .../ContentWrapper/ContentWrapper.tsx | 2 + .../components/GithubButton/GithubButton.tsx | 2 + .../NavigationButton/NavigationButton.tsx | 2 + .../NavigationDropdown/NavigationDropdown.tsx | 2 + .../NavigationLink/NavigationLink.tsx | 2 + .../hooks/useNavigationItemMap.ts | 2 + .../NavigationListItem/NavigationListItem.tsx | 2 + src/navigation/hooks/useActiveNavItem.ts | 2 + src/navigation/hooks/useShowBorder.ts | 2 + .../BackgroundCard/BackgroundCard.tsx | 2 + src/sub-blocks/BannerCard/BannerCard.tsx | 2 + src/sub-blocks/BasicCard/BasicCard.tsx | 2 + src/sub-blocks/Content/Content.tsx | 2 + .../HubspotForm/HubspotFormContainer.tsx | 2 + src/sub-blocks/HubspotForm/index.tsx | 2 + .../HubspotForm/loadHubspotScript.ts | 2 + src/sub-blocks/ImageCard/ImageCard.tsx | 2 + src/sub-blocks/LayoutItem/LayoutItem.tsx | 2 + .../CombinedPriceDetailed.tsx | 2 + .../PriceDescription/PriceDescription.tsx | 2 + .../PriceDetails/PriceDetails.tsx | 2 + src/sub-blocks/Quote/Quote.tsx | 2 + src/utils/icons.ts | 2 +- 166 files changed, 831 insertions(+), 3 deletions(-) create mode 100644 .agents/docs/rsc-support-audit.md diff --git a/.agents/docs/rsc-support-audit.md b/.agents/docs/rsc-support-audit.md new file mode 100644 index 0000000000..d99b25efab --- /dev/null +++ b/.agents/docs/rsc-support-audit.md @@ -0,0 +1,490 @@ +# RSC support — аудит и спайк сборки (S1 + S2) + +> Подготовка `@gravity-ui/page-constructor` к работе с React Server Components (Next.js 14 App Router, React 18). +> Документ фиксирует результаты двух исследовательских задач: **S1** — классификация модулей (client vs server-safe), **S2** — проверка, что директива `'use client'` переживает сборку. + +## Контекст + +React 18 **как версия уже поддержан** — в `peerDependencies` стоит +`react: "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"`. + +Реальная задача — **совместимость с RSC**: сейчас в `src/` **ноль** директив `'use client'`, а библиотека построена на `createContext` (17 модулей), хуках (108 файлов) и обработчиках событий. Любой импорт `PageConstructor` в серверный компонент падает, потому что React по умолчанию считает каждый модуль без `'use client'` серверным. + +**Цель:** расставить границы `'use client'` так, чтобы серверный компонент мог импортировать `PageConstructor`/`PageConstructorProvider` «из коробки», при этом `/server` остаётся серверным, а `models`/`schema`/`utils` — импортируемыми из серверного кода. + +--- + +## S2 — Вердикт по сборке ✅ + +**`'use client'` переживает сборку без изменений в тулинге. Фикс не требуется.** + +TypeScript распознаёт ведущий строковый литерал `'use client';` как **directive prologue** и сохраняет его первой инструкцией модуля во всём выводе — даже поднимая выше авто-инжектнутого `react/jsx-runtime` import и выше `"use strict"` в CJS. Кастомные трансформеры (`transformScssImports`, `transformLocalModules`) и шаги `replace`/sourcemaps в gulp директиву не трогают. + +### Доказательства (первые строки артефактов) + +```js +// build/esm/context/theme/ThemeContext.js +'use client'; +import * as React from 'react'; +import {DEFAULT_THEME} from '../../components/constants.js'; +``` + +```js +// build/cjs/context/theme/ThemeContext.js +'use client'; +'use strict'; +Object.defineProperty(exports, '__esModule', {value: true}); +``` + +```js +// build/esm/components/Button/Button.js +// (директива остаётся выше авто-инжектнутого jsx-runtime import) +'use client'; +import {jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'; +import * as React from 'react'; +``` + +### Ключевые находки + +- **esm:** `'use client'` — строка 1, до всех импортов. ✅ +- **cjs:** `'use client'` — строка 1, выше инжектнутого `"use strict"` и всех `require()`. ✅ Это ровно то, что требует RSC. +- **esm vs cjs:** различие только в том, что cjs добавляет `"use strict"` второй директивой; в обоих `'use client'` остаётся первой. Для RSC разницы в поведении нет. + +### Команда и время + +- Быстрый путь: `npx gulp compile-to-esm compile-to-cjs` (только TS-компиляция, параллельно) — **~10 c**. +- Полный `npm run build:client` дополнительно делает clean, бандл swiper, копирование деклараций/json и sass — на `.js`-директиву не влияет. + +### Рекомендации при массовой расстановке + +1. Директива должна быть **голым строковым литералом на первой строке** (до любого импорта). Лицензионный коммент-баннер сверху не мешает (комментарии — не инструкции), но любой код/импорт до директивы её сломает. +2. «Ремни безопасности» не нужны, но при желании `createTypescriptProject` даёт хук `customTransformers` (уже используется для `before`/`afterDeclarations`) — туда можно добавить `before`-трансформер, инжектящий directive prologue. Эмпирически это избыточно. + +--- + +## S1 — Вердикт по классификации модулей + +Критерий: «использует ли **сам модуль** клиентскую фичу — хуки, `createContext`, ``, события, браузерные глобалы, интерактивные виджеты, классовые компоненты?». Модуль без таких фич импорт-безопасен из серверного компонента, даже если рендерит клиентских детей. + +Область: исключены `__tests__`, `__stories__`, `src/stories/`, `*.scss`, `src/widget/`. + +### Сводка + +| Вердикт | Кол-во (≈) | Где | +| -------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **CLIENT** (нужен `'use client'`) | ~190 | весь `src/context`, весь `src/hooks`, весь `src/editor`, `src/customization`, и ~119 из ~194 impl-файлов в `blocks`/`sub-blocks`/`components`/`containers`/`grid`/`navigation` | +| **SERVER-SAFE** (импорт без директивы) | ~110 | весь `src/models`, `src/schema`, `src/text-transform`, `src/server.ts`, `src/constants.ts`, весь `src/utils`, все `i18n`-кейсеты, все per-component `schema.ts`, плюс ~75 презентационных/барел/util-файлов | +| **BORDERLINE** (помечены) | 6 групп | `utils/common.ts`, `utils/hubspot.ts`, value-импорты uikit в `models`, корневые барелы `index.ts` + `constructor-items.ts`, side-effect импорты в `i18n` | + +### Разбор по директориям + +| Директория | Вердикт | Заметки | +| --------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `src/context/*` | **CLIENT** (без исключений) | каждый вызывает `createContext` и/или экспортит хуки; `theme/withTheme.tsx` — классовый компонент | +| `src/hooks/*` | **CLIENT** (blanket) | все зовут хуки/браузерные глобалы; `useMetrika.ts` и `index.ts` — ре-экспорты, но осмысленны только внутри клиента | +| `src/editor/*` | **CLIENT** (blanket) | Monaco, `@gravity-ui/dynamic-forms`, store, `ErrorBoundary` (class). Отдельный вход `./editor`, не для сервера | +| `src/models/*` | **SERVER-SAFE** (blanket) | чистые типы/энамы/guards. См. риск #2 про value-импорты uikit | +| `src/schema/*` | **SERVER-SAFE** (blanket) | чистый JSON Schema, включая каждый per-block/-component `schema.ts` | +| `src/text-transform/*` | **SERVER-SAFE** (blanket) | изоморфно: `@diplodoc/transform`, `sanitize-html`, `typograf`, `lodash` | +| `src/server.ts` | **SERVER-SAFE / server-only** | единственная строка `export * from './text-transform'`; держать серверным | +| `src/constants.ts` | **SERVER-SAFE** | плоские константы (`BREAKPOINTS` и т.п.) | +| `src/utils/*` | **SERVER-SAFE** | все импорт-безопасны; `common.ts` (`loadScript` трогает `document`) и `hubspot.ts` (`window`) — только внутри тел функций | +| `src/grid/*` | **в основном SERVER-SAFE** | `Grid`, `Col`, `Break`, `types`, `utils` — server-safe. **Исключение: `Row/Row.tsx` = CLIENT** (хуки) | +| `src/customization/BlockDecoration.tsx` | **CLIENT** | `React.useContext(InnerContext)` | +| `src/blocks/*` | **преимущественно CLIENT** | server-safe листья: `Banner/Banner.tsx`, `Map/Map.tsx`, `Tabs/TabsTextContent`, `Footer/.../DisclaimerFloor`, + чистые `*/utils.ts`, `*/models.ts`, `blocks/validators.ts`, барел | +| `src/sub-blocks/*` | **смешанно** | см. списки ниже | +| `src/components/*` | **смешанно** | см. списки ниже | +| `src/containers/*` | **CLIENT-ядро** | server-safe только `ConstructorRow/ConstructorRow.tsx` и барелы | +| `src/navigation/*` | **преимущественно CLIENT** | server-safe: `NavigationList`, `NavigationPopup`, `SocialIcon`, `Standalone`, `containers/Layout/Layout.tsx`, `utils.ts`, `models.ts`, `schema`, барелы | + +### Server-safe модули (можно импортировать в серверный компонент уже сегодня) + +- **Целые директории:** `src/models/**`, `src/schema/**` (вкл. каждый `schema.ts`), `src/text-transform/**`. +- **Отдельные файлы:** `src/server.ts`, `src/constants.ts`, весь `src/utils/**` (13 файлов), все `i18n/index.ts` кейсеты. +- **Презентационные листья** (импорт-безопасны, рендерят клиентских детей): + - grid: `Grid.tsx`, `Col.tsx`, `Break.tsx`; + - blocks: `Banner/Banner.tsx`, `Map/Map.tsx`, `Tabs/TabsTextContent/TabsTextContent.tsx`; + - sub-blocks: `Content`, `Divider`, `MediaCard`, `PriceCard`, `PriceDetailed` (+ `SeparatePriceDetailed`, `PriceDetails/Details/List`, `Settings`); + - components: `Anchor`, `Author`, `BackgroundImage`, `BlockBase`, `Buttons`, `ContentList`, `Icon`, `IconWrapper`, `Links`, `MetaInfo`, `Title/Title.tsx`, `ToggleArrow`, `UnpublishedLabel`, `YFMWrapper`, `Media/Iframe/Iframe.tsx`, `Media/FullscreenVideo/FullscreenVideo.tsx`, `MediaBase/MediaBaseContent.tsx`; + - containers: `.../ConstructorRow/ConstructorRow.tsx`; + - navigation: `NavigationList`, `NavigationPopup`, `SocialIcon`, `Standalone`, `containers/Layout/Layout.tsx`. + +> **Оговорка:** эти листья импорт-безопасны на сервере, но в рантайме почти всегда живут внутри клиентского поддерева `PageConstructor` и полагаются на провайдеры — на практике они рендерятся ниже клиентской границы. + +### Must be client (по зонам) + +- **Все контексты** — `src/context/**` (`createContext` + хуки). +- **Все хуки** — `src/hooks/**`. +- **Провайдер/движок** — `containers/PageConstructor/Provider.tsx`, `PageConstructor.tsx`, `ConstructorBlock/Blocks/Item/Loadable`, `Loadable/Loadable.tsx`. +- **Интерактивные виджеты** — `blocks/Slider` (Swiper), `blocks/SliderOld` (react-slick), `components/ReactPlayer/*`, `components/Media/Video`, `components/VideoBlock`, `components/Map/*` (+ `YMap/*`) и `context/mapsContext`, `sub-blocks/HubspotForm/*`, `components/YandexForm`, `components/InnerForm` (dynamic-forms), весь `src/editor/**` (Monaco). +- **Компоненты с хуками/событиями/браузерными API** — CLIENT-списки в `blocks`, `sub-blocks`, `components`, `navigation` + `customization/BlockDecoration.tsx`. +- **Классовые компоненты** — `context/theme/withTheme.tsx`, `components/Table/Table.tsx`, `components/OutsideClick`, `components/OverflowScroller`, `components/FullWidthBackground`, editor `ErrorBoundary`. +- **Рендерят `` (client, несмотря на отсутствие хуков)** — `containers/PageConstructor/Provider.tsx`, `containers/.../ConstructorItem.tsx`, `ConstructorLoadable.tsx`, `navigation/.../NavigationItem.tsx`, `navigation/.../NavigationButton/NavigationButton.tsx`, `grid/Row/Row.tsx`. + +#### Client-листья: sub-blocks + +`BackgroundCard`, `BannerCard`, `BasicCard`, `HubspotForm/*`, `ImageCard`, `LayoutItem/LayoutItem.tsx`, `Quote/Quote.tsx`, `PriceDetailed/CombinedPriceDetailed`, `PriceDetailed/PriceDescription`, `PriceDetailed/PriceDetails/PriceDetails.tsx`. + +#### Client-листья: components + +`AnimateBlock`, `BackLink`, `BackgroundMedia`, `BalancedMasonry`, `Button`, `ButtonTabs`, `DefaultVideo`, `FileLink`, `Foldable`, `FullWidthBackground` (class), `FullscreenImage`, `FullscreenMedia`, `HTML`, `HeaderBreadcrumbs`, `Image/Image.tsx`, `ImageBase`, `InnerForm`, `Link`, `Map/*` (GoogleMap, YandexMap, YMap), `Media/DataLens`, `Media/Media.tsx`, `Media/Image/Image.tsx`, `Media/Video/Video.tsx`, `MediaBase/MediaBase.tsx`, `OutsideClick` (class), `OverflowScroller` (class), `ReactPlayer/*`, `Table/Table.tsx` (class), `Title/TitleItem.tsx`, `VideoBlock`, `YandexForm`, `RouterLink`. + +### Риски / сюрпризы + +1. **Client-без-хуков легко пропустить.** `Provider.tsx`, `NavigationButton.tsx`, `ConstructorItem.tsx`, `grid/Row/Row.tsx` не используют хуки, но обязаны быть клиентскими, потому что `` нельзя создать в серверном компоненте. Grep «есть хук → client» их **пропустит** — нужен доп. критерий «рендерит `.Provider`». +2. **`models` тянут `@gravity-ui/uikit` через value-import.** `models/constructor-items/common.ts` и `blocks.ts` делают `import {ButtonView, IconData, ButtonProps, ButtonSize} from '@gravity-ui/uikit'` (не `import type`). Это только типы, но без `import type` бандлер может затащить клиентский uikit в server-safe граф `models`. **Фикс:** перевести на `import type`. (`constructor-items/common.ts` также делает `import * as icons from '@gravity-ui/icons'` — чистые данные, безопасно.) +3. **Корневые барелы смешанные.** `src/index.ts` ре-экспортит контексты/PageConstructor/blocks (client) вместе с `models`/`schema`/`utils`/`constants` (server-safe); `src/constructor-items.ts` мапит типы блоков на клиентские компоненты. Импорт «из корня» в серверном компоненте транзитивно тянет клиент. **Директиву на барелы не ставить;** отдельно решить — добавлять ли subpath-экспорты (`./models`, `./utils`), т.к. сейчас в `exports` их нет. +4. **`server.ts` чистый** — ре-экспортит только `text-transform` (изоморфно). Держать изолированным. +5. **`utils/common.ts` и `utils/hubspot.ts`** трогают `document`/`window` только внутри тел функций — импорт-безопасны, но по сути клиентские рантайм-хелперы. Не «повышать» их до общих серверных утилит. +6. **`i18n`-кейсеты выполняют side-effect при импорте** (`addComponentKeysets`). Изоморфно и безопасно, но регистрация исполняется и на сервере — это не нулевой по стоимости импорт типов. + +--- + +## Итог и следующий шаг + +- **Тулинг сборки готов** (S2): достаточно ставить `'use client';` первой строкой исходника. +- **Карта границ есть** (S1): помечаем директивой только настоящие клиентские модули (вкл. рендерящие `` без хуков), чистые слои оставляем server-safe. Это канонический RSC-паттерн и даёт «работает из коробки». +- **Перед массовой правкой** — решить две развилки: (а) гранулярность (листья vs под-барелы; рекомендуется листья); (б) добавлять ли subpath-экспорты для серверных модулей. Плюс точечный фикс `import type` в `models` (риск #2). + +Следующие задачи плана: **S3/S4** — расстановка `'use client'`; **S5** — editor + проверка чистоты `/server`; **S6** — eslint-правило + build-assert; **S7** — Next.js 14 пример; **S8** — доки/релиз. + +--- + +## План развития (S3–S9) + +Ниже — продолжение плана после исследовательских S1/S2. Стратегия по умолчанию: **точечная граница** (`'use client'` только на настоящих клиентских листьях + на модулях, рендерящих ``), барелы и чистые слои — без директивы. + +### Фазы + +| Фаза | Что | Статус | +| --------------------------- | ------------------------------------------------------------------------------------------- | --------------- | +| **0. Спайк сборки** | Доказать, что `'use client'` переживает gulp-компиляцию (esm+cjs) и остаётся первой строкой | ✅ сделано (S2) | +| **1. Карта границ** | Классифицировать каждый модуль client vs server-safe | ✅ сделано (S1) | +| **2. Расстановка директив** | `'use client'` по списку: ядро + периферия | S3, S4 | +| **3. Защита пайплайна** | Eslint-правило + build-assert тест на артефакты | S6 | +| **4. Пример + верификация** | Next.js 14 App Router: серверный компонент рендерит `PageConstructor` — критерий успеха | S7 | +| **5. Доки и релиз** | README/migration, changelog, semver (minor/feat) | S8 | +| **6. blog-constructor** | Бамп зависимости + то же самое в отдельном репозитории | S9 | + +### Субагенты + +Порядок: **S3/S4** — основная правка (в worktree, чтобы не мешать друг другу и main). **S5–S8** — после расстановки. **S9** — отдельный репозиторий. + +| # | Субагент | Задача | Зависит от | +| ------ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| **S3** | `general-purpose` (worktree) | **Ядро.** `'use client'` в `Provider.tsx`, `PageConstructor.tsx`, всём `src/context/*`, `src/hooks/*`, `src/containers/*`, `src/grid/Row/Row.tsx`. Прогнать `typecheck`. | S1, S2 | +| **S4** | `general-purpose` (worktree) | **Периферия.** `'use client'` в клиентских листьях `blocks`, `sub-blocks`, `components`, `navigation/*` (по спискам «Must be client» выше). Чистые презентационные листья не трогать. | S1, S2 | +| **S5** | `general-purpose` | **Editor + чистота `server`.** Директива на границе `/editor`; проверить, что `src/server.ts` и весь `/server`-граф остались **без** директив и не тянут клиентские модули. Точечный фикс `import type` в `models` (риск #2). | S1 | +| **S6** | `test-writer` | **Guardrails.** Eslint-правило (обязательность директивы на клиентских зонах, запрет на `src/server.ts`/чистых утилях) + jest/скрипт-ассерт, грепающий `build/esm` и `build/cjs` на директиву в ключевых файлах. | S2, S3, S4 | +| **S7** | `general-purpose` | **Next.js 14 пример + верификация.** App-Router приложение: серверный компонент рендерит `PageConstructor`, контент — через server action. Подтвердить рендер без ошибок RSC. | S3, S4 | +| **S8** | `doc-writer` | **Доки/релиз.** Раздел про RSC в README, migration-заметка, changelog. | S3, S4, S7 | +| **S9** | отдельный репозиторий | **blog-constructor.** Бамп `page-constructor` + `'use client'` для собственных компонентов blog-constructor + свой Next-пример. Из этого воркспейса недоступно — параллельный поток. | S3–S8 | + +### Открытые развилки (решить перед S3/S4) + +1. **Гранулярность границы:** листья (рекомендуется) vs под-барелы (`blocks/index.ts` и т.п.). Листья = канонический RSC-паттерн, лучший tree-shaking; под-барелы проще, но затягивают презентационные листья в клиентский граф. +2. **Subpath-экспорты для серверных модулей:** добавлять ли `./models`, `./utils`, `./schema-ts` в `package.json#exports`, чтобы серверные компоненты импортировали типы/утилиты без пересечения клиентской границы. Сейчас их нет — импорт «из корня» тянет клиент. + +### Риски + +- **Client-без-хуков** (`Provider.tsx`, `NavigationButton`, `ConstructorItem`, `grid/Row/Row.tsx`) — grep по хукам их пропустит; в S3/S4 использовать доп. критерий «рендерит ``». +- **CJS-артефакт:** `'use client'` в CJS безвреден (Next смотрит на ESM), но проверить в S6. +- **Загружаемые блоки** (`Loadable`, `Map`, формы, `React.lazy`) — убедиться, что граница не рвёт динамическую загрузку (S3/S7). +- **`SSRContext`/`useMount`** — логика «isServer» должна остаться консистентной внутри клиентской границы. + +--- + +## План работ — 2 этапа (актуальный, с разбивкой на субагентов) + +> Реконсиляция S-плана под целевую двухэтапную рамку: +> **Этап 1 = «встраивается в Next.js без костылей»** (бывшие S3–S8) — обязательный, разблокирует тикет. +> **Этап 2 = «сдвигаем компоненты в RSC, где реально»** (бывшая Фаза 7) — опциональная оптимизация, требует согласования инварианта #8. +> Списки конкретных модулей не дублируются — берутся из разделов **S1** («Must be client», «Server-safe модули») и **Фаза 7** («Карта контекстов»). + +### Этап 1 — граница `'use client'`, работает из коробки + +**Критерий приёмки этапа:** на smoke-проекте `page-constructor-next-rsc` `npm run build` проходит **без** `Failed to collect page data`; консюмер не пишет ни одной обёртки. Стратегия — точечная граница (листья + модули, рендерящие ``), барелы и чистые слои без директивы. + +| Шаг | Субагент | Задача | Зависит от | Критерий шага | +| -------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | --------------------------------------------------------------------------------------------- | +| **1.1 Ядро** | `general-purpose` (worktree A) | `'use client'` на `context/**`, `hooks/**`, `containers/**`, `grid/Row/Row.tsx`, `Provider.tsx`, `PageConstructor.tsx`. Критерий отбора: хук **или** рендер ``. Прогнать `typecheck` | S1, S2 | E1 уходит; `typecheck` зелёный | +| **1.2 Периферия** | `general-purpose` (worktree B) | `'use client'` на клиентских листьях `blocks`/`sub-blocks`/`components`/`navigation` (по client-спискам S1). Чистые презентационные листья не трогать | S1, S2 | сборка не падает на блоках | +| **1.3 Editor + чистота `/server` + exports** | `general-purpose` | Директива на входе `./editor`; проверить, что `server.ts` и весь `/server`-граф **без** директив; фикс `import type` в `models/constructor-items/{common,blocks}.ts` (риск #2); добавить `./models`, `./utils` в `package.json#exports` (развилка 2 — решена «да») | S1 | grep `/server`-графа чист; `models` без value-import uikit; новые subpath-экспорты резолвятся | +| **1.4 Guardrails** | `test-writer` | Eslint-правило (обязательность директивы в клиентских зонах + запрет на `server.ts`/чистых утилях) + build-assert тест: греп `build/esm`+`build/cjs` на директиву в ключевых файлах | 1.1, 1.2, S2 | тест краснеет, если директиву сняли/срезали | +| **1.5 Verify** | `general-purpose` | Пересобрать `npm pack` → поставить tarball в smoke-проект → `next build` (цикл ретеста из раздела «Верификационный проект») | 1.1–1.3 | build проходит, `/` рендерится на сервере | +| **1.6 Доки/релиз** | `doc-writer` | Раздел RSC в README + migration-заметка + changelog (**minor / feat**) | 1.5 | релиз-нота готова | + +**Фан-аут Этапа 1:** шаги **1.1 и 1.2 запускаются параллельно** в разных worktree — они правят непересекающиеся директории (ядро vs периферия). 1.3 может идти параллельно 1.1/1.2 (директории `editor`/`server`/`models` disjoint), но фикс `import type` в `models` должен приземлиться до 1.5. 1.4 — только после 1.1+1.2 (нужны расставленные директивы для ассертов). 1.5 сводит все worktree воедино. 1.6 — после зелёного 1.5. + +### Этап 2 — рост серверной доли (инверсия `context → props`) + +**Критерий приёмки этапа:** First Load JS страницы из статических блоков падает (`next build` до/после), интерактив цел (острова), нет hydration mismatch, `playwright` зелёный. +**Механика (без костылей):** ставим на Рычаг 2 (инверсия `context → props`), изоморфный шим (Рычаг 1) — не используем. Читаем server-резолвимые контексты один раз на клиентской границе, раздаём пропами; листья становятся server. + +| Шаг | Субагент | Задача | Зависит от | +| --------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| **2.0 Baseline** | `general-purpose` | Зафиксировать First Load JS на странице из статических блоков (`next build`) | Этап 1 зарелижен | +| **2.1 Отбор кандидатов** | `Explore` | Статичные листья, читающие только server-резолвимые контексты (`theme`/`locale`/`isMobile`/`ssr`/`projectSettings` — колонка «да» карты контекстов). Кандидаты: `Content`, `Title`, `Banner`, карточки | 2.0 | +| **2.2–2.4 Конверсия блока** | `general-purpose` (worktree, **один агент на блок**) | Ввести server-shell (резолв контекстов на границе); в листе убрать `useTheme`/`useContext`, принять `theme`/`locale` пропами, снять `'use client'`; `getThemedValue(x, theme)` без изменений. Проверить: интерактив-острова целы, нет mismatch, JS упал | 2.1 | +| **2.5 Итерация/замер** | `general-purpose` | Свести замеры по блокам, остановиться там, где выгода < риска; островные контексты (`image`/`forms`/`maps`/`analytics`/`windowWidth`/`animate`) не трогать | 2.2–2.4 | + +**Фан-аут Этапа 2:** блоки независимы → шаги **2.2–2.4 фанаутятся по одному агенту на блок** (worktree на блок, чтобы правки не конфликтовали), после каждого — свой замер и `playwright`. Пайплайн: `отбор → [конверсия+verify]×N блоков → сводный замер`. + +### Граф зависимостей + +``` +ЭТАП 1 (обязательный) + ┌─ 1.1 Ядро ─────┐ + │ ├─► 1.4 Guardrails ─┐ + ├─ 1.2 Периферия ┘ ├─► 1.5 Verify ─► 1.6 Доки/релиз + └─ 1.3 Editor+server+exports ────────┘ + (параллельно; import type в models — до 1.5) + +ЭТАП 2 (опциональный, требует ок по инварианту #8) + 2.0 Baseline ─► 2.1 Отбор ─► [2.2–2.4 Конверсия+verify] × N блоков ─► 2.5 Замер +``` + +### Развилки к закрытию + +1. **Subpath-экспорты `./models`/`./utils`** — решено «да», исполняется в шаге 1.3 (обязательно для Этапа 2, полезно уже на Этапе 1). +2. **Локальное ослабление инварианта #8** (`contexts over prop drilling` на **листовом** слое) — согласовать до старта 2.x. Без «ок» Этап 2 не делаем; Этап 1 инвариант не задевает. + +### Явно вне scope + +- Этап 1: никаких шимов/форк-реализаций/переноса в server — только границы. Граница ложится на `PageConstructor` как единый client-остров. +- Этап 2: не трогаем интерактивные блоки; не «дошимиваем» контексты, которым нужно рантайм-значение (тема после переключения в браузере, ширина окна); `react-server` export condition — опционально и только для честно форкаемых чисто-данных модулей. + +--- + +## Верификационный проект (Next 14) и baseline-ошибки + +Для S7 создан отдельный smoke-test проект **вне** репозитория библиотеки, чтобы не задевать её lint/build-globs. + +### Расположение и стек + +- **Путь:** `/Users/peterkrotov/page-constructor-next-rsc` (sibling к `page-constructor-1`). +- **Стек:** Next.js **14.2.35** (App Router, TypeScript, без Tailwind/ESLint), React **18.3.1**. +- **Как поставлена библиотека:** локальная сборка через `npm pack` (а не npm-версия), чтобы проект тестировал наши правки. Установлен tarball `gravity-ui-page-constructor-8.13.2.tgz` + peer-зависимости `@gravity-ui/uikit@^7`, `@diplodoc/transform@^4`. +- **Файлы:** + - `app/page.tsx` — **серверный компонент** (без `use client`), рендерит `PageConstructorProvider` + `PageConstructor` с одним `header-block`. Это и есть проверяемый сценарий. + - `app/layout.tsx` — подключает стили `@gravity-ui/uikit/styles/*` + `@gravity-ui/page-constructor/styles/styles.css`, ставит класс `g-root g-root_theme_light`. + +### Baseline (до S3/S4) — воспроизведённая ошибка ❌ + +Команда: `cd /Users/peterkrotov/page-constructor-next-rsc && npm run build` + +Компиляция проходит (`✓ Compiled successfully`), падение — на этапе `Collecting page data`: + +``` +TypeError: ef.createContext is not a function + at 62866 (.next/server/app/page.js …) + … +Build error occurred +Error: Failed to collect page data for / +``` + +**Интерпретация:** это ровно проблема из тикета. При рендере страницы-серверного-компонента модули `src/context/*` вызывают `React.createContext`, которого нет в серверном рантайме React (RSC-сборка не экспортирует `createContext`). Барел `@gravity-ui/page-constructor` без `'use client'` затягивает контексты в серверный граф → падение. + +> Важно: ошибка приходит **на этапе `Collecting page data`** (server prerender), а не при компиляции. Значит блокирует именно отсутствие клиентской границы, а не типы/бандлинг. + +### Целевое состояние (после S3/S4) — ожидаемо ✅ + +После того как библиотека получит границы `'use client'`, цикл ретеста: + +```sh +# 1) пересобрать и переупаковать библиотеку +cd /Users/peterkrotov/page-constructor-1 +npm run build:client && npm run build:server && npm pack +# 2) переустановить tarball в smoke-test проект +cd /Users/peterkrotov/page-constructor-next-rsc +npm i /Users/peterkrotov/page-constructor-1/gravity-ui-page-constructor-8.13.2.tgz +# 3) собрать заново — ожидаем успешный prerender '/' +npm run build +``` + +Критерий успеха S7: `npm run build` проходит без `Failed to collect page data`, страница `/` рендерится на сервере. + +### Реестр блокирующих ошибок + +| # | Ошибка | Где всплывает | Причина | Статус / фикс | +| --- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| E1 | `TypeError: .createContext is not a function` → `Failed to collect page data for /` | `next build`, этап Collecting page data (server prerender) | контекст-модули без `'use client'` попадают в **серверный граф** и вычисляют `createContext` на eval | ✅ **Решена.** Граница `'use client'` (обёртка ИЛИ директивы S3/S4) → контексты исполняются в клиентском бандле | +| E2 | `ReferenceError: document is not defined` | `next build`, этап Generating static pages (**SSR** пререндер) | модуль в клиентском бандле трогает `document`/`window` **на верхнем уровне**; `PageConstructor` тянет `blockMap` из `constructor-items.ts`, который статически импортит **все** блоки → в бандл попадают Share (`github-buttons`), Slider (`swiper`), Map-загрузчики и т.п. | ⚠️ **Обойдена** через `dynamic(..., {ssr:false})`. Правильный фикс — SSR-safety: гард top-level доступа к браузеру в библиотеке / ленивая загрузка тяжёлых блоков | + +### Эксперимент: обёртка vs директивы (проверено в Next-проекте) + +**E1 чинится границей `'use client'` — двумя способами:** + +| Подход | Что делает | Плюсы | Минусы | +| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| **A. Обёртка** (`app/pc-client.tsx` с `'use client'`, ре-экспорт/`dynamic`) | одна клиентская граница на стороне консюмера или отдельным входом библиотеки | 1 файл, «из коробки» без правки 190 модулей | **всё** поддерево становится client; главный барел остаётся серверным только для типов | +| **B. Директивы S3/S4** | `'use client'` на настоящих клиентских модулях | главный барел server-importable (типы/schema/utils), задел под острова (Фаза 7) | много файлов (скриптуется) | + +> Важно: **E2 не зависит от выбора A/B.** Next всё равно SSR-ит клиентские компоненты при пререндере, поэтому top-level доступ к `document`/`window` падает и при обёртке, и при директивах. Значит для «из коробки **с SSR**» библиотеке в любом случае нужна SSR-safety (гард browser-доступа) ИЛИ потребитель отключает SSR через `ssr:false`. + +**Достигнутое состояние (обёртка + `ssr:false`):** `next build` зелёный, страница `/` — серверный компонент, пререндерится статикой, конструктор рендерится на клиенте. Файлы примера: `app/page.tsx` (RSC), `app/PageView.tsx` (`'use client'` композиция), `app/pc-client.tsx` (`dynamic`, `ssr:false`). + +### Следующие блокеры (ожидаемые) + +- После включения SSR (без `ssr:false`) E2 нужно закрыть в библиотеке: найти top-level `document`/`window` (кандидаты — `github-buttons`, `swiper`, `react-slick`, загрузчики карт) и обернуть в `typeof window !== 'undefined'` или ленивую загрузку. +- Возможны E3+ (другие браузерные API при SSR, `React.lazy`/`Loadable`) — фиксируем сюда же по мере всплытия. + +--- + +## Как растить серверную долю (Фаза 7, опциональная оптимизация) + +> Отдельная тема **после** того, как «из коробки» заведётся (S3–S7). Цель — перевести часть компонентов из client в server, чтобы отдавать больше HTML с сервера и грузить меньше JS. Ниже — что реально возможно, потолок каждого подхода и карта, где двигать границу. + +### Жёсткое ограничение (почему контекст нельзя «дошимить» до конца) + +Контекст в RSC — **клиентская механика**. Ошибка E1 (`createContext is not a function`) прилетает **на eval модуля**, а не на рендере: в серверной сборке React `createContext` = `undefined`, а `useContext` не видит стек провайдеров (их на сервере нет). Отсюда: + +- Хелпер **может** сделать модуль контекста import-safe и вернуть **дефолт**. +- Хелпер **не может** вернуть «живое» значение провайдера на сервере (переключённая юзером тема, ширина окна, `isMobile` по `matchMedia`) — этого значения на сервере физически нет. + +Вывод: шим спасает только компоненты, которым достаточно дефолта/статики, а не рантайм-значения. + +### Рычаг 1 — изоморфный context-хелпер (guard + server default) + +Обёртка: на клиенте — настоящий контекст, на сервере — пассивная заглушка с дефолтом. + +```ts +// src/utils/isomorphicContext.ts (server-safe, БЕЗ 'use client') +import * as React from 'react'; + +const isServer = typeof React.createContext !== 'function'; + +export function createIsomorphicContext(defaultValue: T) { + if (isServer) { + // сервер: не трогаем React-контекст, отдаём пассивную заглушку + const ctx = {Provider: ({children}: any) => children, _default: defaultValue}; + return {Context: ctx as any, useValue: () => defaultValue}; + } + const Context = React.createContext(defaultValue); + return {Context, useValue: () => React.useContext(Context)}; +} +``` + +- **Даёт:** компонент, читающий контекст только ради дефолта, становится server-safe. +- **Не даёт:** компоненту, которому нужно настоящее значение провайдера, всё равно быть client. +- **Итог:** сам по себе сдвигает границу мало; полезен в связке с рычагом 2. + +### Рычаг 2 (сильный) — инверсия `context → props` + острова + +Читать контексты **один раз на клиентской границе** и прокидывать разрезолвленные значения вниз пропами → листья становятся чистыми `(props) => JSX` и переезжают в server. Интерактив (`Slider`, `Map`, формы, видео) остаётся «островами» client. + +В page-constructor это идеально ложится на `getThemedValue` — она **уже чистая**, берёт тему аргументом: + +```tsx +// сейчас (client): завязка на контекст +const theme = useTheme(); // useContext → client +const media = getThemedValue(props.media, theme); + +// целевое (server): тема приходит пропом от клиентской границы +function Card({media, theme}: Props) { + // без useContext + const resolved = getThemedValue(media, theme); // остаётся чистой +} +``` + +Тогда `Content`, `Title`, `Banner`, карточки становятся серверными; в бандл уезжает меньше JS. + +### Карта контекстов: что резолвится на серверной границе + +Ключ — разделить контексты на **конфиг, известный на сервере**, и **client-онли рантайм**: + +| Контекст | Резолвится на сервере? | Источник значения | +| ---------------------------- | ---------------------- | ---------------------------- | +| `theme` | да | cookie / проп консюмера | +| `localeContext` | да | заголовки / роут | +| `mobileContext` (`isMobile`) | да | User-Agent / headers | +| `ssrContext` | да | по определению | +| `projectSettingsContext` | да | статический конфиг консюмера | +| `imageContext` | нет | нужен loader/handlers | +| `formsContext` | нет | нужны handlers | +| `mapsContext` | нет | ключи + браузерный API | +| `analyticsContext` | нет | handlers/браузер | +| `windowWidthContext` | нет | `resize` / `matchMedia` | +| `animateContext` | нет | скролл в браузере | + +Всё из колонки «да» консюмер уже имеет на серверной границе → эти значения передаются пропами, и с потребляющих их листьев снимается клиентская метка. Всё «нет» — остаётся островами. + +### Рекомендация и границы применения + +- **Не делать это в S3/S4.** Там задача — просто расставить `'use client'` и разблокировать тикет минимальным риском (граница ляжет на `PageConstructor`). +- **Фаза 7 (после S7):** ввести рычаг 2 точечно — server-shell для статических блоков (`Header`, `Banner`, `Content`) с темой/locale через пропы, интерактив — острова. Начать с блоков без интерактива (см. server-safe листья в разделе S1). +- **Связь с инвариантом:** это **локальное** ослабление правила «contexts over prop drilling» из `architecture.md` — только на **листовом** слое. Кросс-катящий рантайм-конфиг остаётся в контекстах на клиентской границе; презентационные листья переводятся на пропы. Осознанный точечный компромисс, а не отмена инварианта. + +### Метрика успеха Фазы 7 + +- Доля server-компонентов в дереве `PageConstructor` растёт (меньше `'use client'`-модулей в критическом пути статического блока). +- Размер клиентского JS для страницы из статических блоков падает (замер `next build` → First Load JS до/после). +- Ни один блок не теряет интерактив (острова остаются client). + +--- + +## Учебный трек — как изучить тему (пошагово) + +> Куратор-план для погружения в RSC под нашу задачу. Порядок модулей = порядок изучения: от ментальной модели → к директивам → к композиции → к специфике **авторов библиотек** (наш случай) → к верификации. Каждый урок привязан к фазе плана (S1–S9) и/или к конкретной находке аудита выше. +> +> Легенда приоритета: ⭐ обязательно · 📘 углубление · 🛠 практика/эталон конфигурации. + +### Модуль 0 — Ментальная модель RSC + +| # | Урок (цель) | Ссылки | Зачем нам | +| ------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| 0.1 ⭐ | Что такое Server Components: две среды (server/client), что «RSC ≠ SSR» | [react.dev · Server Components](https://react.dev/reference/rsc/server-components) | База для всего документа; объясняет, почему модуль без `'use client'` считается серверным (см. «Контекст») | +| 0.2 ⭐ | Целостная картина: props, ре-рендеры, связка с Suspense/Streaming SSR | [Josh Comeau · Making Sense of RSC](https://www.joshwcomeau.com/react/server-components/) | Лучшее «человеческое» объяснение модели; снимает путаницу RSC vs SSR | +| 0.3 📘 | RSC «с нуля»: как устроен payload, сериализация client-компонентов | [Dan Abramov · RSC From Scratch](https://github.com/reactwg/server-components/discussions/5) | Понимание, почему `'use client'` — это граница графа модулей, а не «флаг компонента» | + +### Модуль 1 — Директивы `'use client'` / `'use server'` + +| # | Урок (цель) | Ссылки | Зачем нам | +| ------ | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| 1.1 ⭐ | `'use client'`: семантика границы, транзитивность импортов, правила синтаксиса (первая строка, кавычки) | [react.dev · 'use client'](https://react.dev/reference/rsc/use-client) | Прямо обосновывает вывод **S2** (директива = голый литерал первой строкой) и стратегию «границы на листьях» | +| 1.2 📘 | Обзор всех директив разом | [react.dev · Directives](https://react.dev/reference/rsc/directives) | Быстрый референс, чтобы не путать `'use client'` и `'use server'` | +| 1.3 📘 | `'use server'` и Server Functions (это НЕ «пометка серверного компонента») | [react.dev · 'use server'](https://react.dev/reference/rsc/use-server) · [Server Functions](https://react.dev/reference/rsc/server-functions) | Развеивает частую ошибку «для сервера нужен `use server`»; пригодится в примере S7 (контент через server action) | + +### Модуль 2 — Композиция client ↔ server (Next.js App Router) + +| # | Урок (цель) | Ссылки | Зачем нам | +| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| 2.1 ⭐ | Server и Client Components в App Router: серверный может рендерить клиентского ребёнка, но не наоборот | [Next.js · Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components) | Наш целевой сценарий: серверная `page.tsx` рендерит `PageConstructor` (S7) | +| 2.2 ⭐ | Паттерны композиции: props должны быть сериализуемы; Server Components как `children`/props клиентского компонента; «граница каскадит по импортам, не по props» | [Next.js 14 · Composition Patterns](https://nextjs.org/docs/14/app/building-your-application/rendering/composition-patterns) | Фундамент для **Рычага 2** (инверсия `context → props`, острова) из Фазы 7 | +| 2.3 🛠 | Минимизация клиентского бандла: `'use client'` на узких интерактивных листьях, а не на крупных поддеревьях | тот же раздел, блок «Reducing bundle size» в [Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components) | Обоснование развилки «листья vs под-барелы» (рекомендация — листья) | + +### Модуль 3 — Контекст в RSC (наш E1) + +| # | Урок (цель) | Ссылки | Зачем нам | +| ------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| 3.1 ⭐ | Почему `createContext` недоступен в серверном компоненте — официальная страница ошибки | [Next.js · createContext in a Server Component](https://nextjs.org/docs/messages/context-in-server-component) | Дословно наш **E1** (`createContext is not a function` → `Failed to collect page data`) | +| 3.2 📘 | Как контекст работает в React (что именно ломается на сервере) | [react.dev · createContext](https://react.dev/reference/react/createContext) | Объясняет «жёсткое ограничение» из Фазы 7: шим отдаёт только дефолт, не рантайм-значение провайдера | +| 3.3 ⭐ | Оборачивание сторонних провайдеров в client-компонент (root layout + `{children}`) | раздел «Context providers» в [Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components) | Паттерн для `PageConstructorProvider`; закрывает E1 через S3 | + +### Модуль 4 — Авторам библиотек (ядро нашей задачи) + +| # | Урок (цель) | Ссылки | Зачем нам | +| ------ | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| 4.1 ⭐ | Полный гайд «как поддержать RSC в своей библиотеке»: где ставить `'use client'`, риск срезания директивы бандлером, banner-инъекция | [Bekk · Support RSC in your library](https://www.bekk.christmas/post/2023/19/keep-up-with-react-server-components-how-to-support-it-in-your-library) | Наиболее близкий к нашей задаче материал; сверить с выводом S2 (у нас gulp/tsc директиву НЕ срезает) | +| 4.2 📘 | RFC 0227 «Server Module Conventions» — первоисточник семантики директив и границ | [reactjs/rfcs · 0227](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md) | Первоисточник правила «помечай только точки, потребляемые сервером напрямую» → наша стратегия «точечная граница» | +| 4.3 🛠 | Бандлинг библиотеки с RSC на Rollup: `preserveModules`, banner только на client-чанк, `terser {compress:{directives:false}}` | [DEV · RSC + Client Components with Rollup](https://dev.to/mryechkin/react-server-components-and-client-components-with-rollup-3c05) | Эталон на случай, если S6 покажет, что директиву где-то срезает; у нас пока не требуется | +| 4.4 📘 | Дискуссия мейнтейнеров Next: как паковать server-пакеты с client-компонентами | [vercel/next.js · Discussion #62231](https://github.com/vercel/next.js/discussions/62231) | Кейсы граничных случаев (смешанные чанки, subpath-экспорты) → развилка про `./models`/`./utils` | +| 4.5 📘 | `react-server` export condition: когда `'use client'` мало и нужна форк-реализация модуля | [react.dev · 'use client' (раздел про bundler requirements)](https://react.dev/reference/rsc/use-client) · кейс [lucide-icons/lucide#4200](https://github.com/lucide-icons/lucide/issues/4200) | Альтернатива **Рычагу 1**: server-форк модуля контекста через `exports["react-server"]` вместо изоморфного шима (риск #2, `models` + uikit) | + +### Модуль 5 — Верификация и отладка + +| # | Урок (цель) | Ссылки | Зачем нам | +| ------ | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| 5.1 🛠 | Как ретестить: `next build` → этап «Collecting page data» ловит серверные ошибки эвала модуля | [Next.js · Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components) | Наш критерий успеха S7 (сборка без `Failed to collect page data`) | +| 5.2 📘 | Индекс ошибок Next.js (быстрый лукап вторичных падений после S3) | [Next.js · context-in-server-component](https://nextjs.org/docs/messages/context-in-server-component) | При появлении E2/E3 (хуки в блоках, `window` при импорте, `React.lazy`) — искать по этому индексу | + +### Маппинг уроков на фазы плана + +| Фаза / субагент | Обязательные уроки перед стартом | +| ------------------------------------------------------------- | --------------------------------- | +| Понимание модели (все) | 0.1, 0.2, 1.1 | +| **S3/S4** — расстановка `'use client'` | 1.1, 2.1, 2.3, 3.1, 3.3, 4.1, 4.2 | +| **S5** — editor + чистота `/server`, `import type` в `models` | 4.2, 4.5 | +| **S6** — guardrails (eslint + build-assert) | 1.1, 4.1, 4.3 | +| **S7** — Next.js 14 пример | 2.1, 2.2, 3.1, 5.1 | +| **Фаза 7** — рост серверной доли (`context → props`, острова) | 2.2, 3.2, 4.5 | diff --git a/package.json b/package.json index 4280762255..65e2176164 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,16 @@ "require": "./server/index.js", "import": "./server/index.js" }, + "./models": { + "types": "./build/esm/models/index.d.ts", + "require": "./build/cjs/models/index.js", + "import": "./build/esm/models/index.js" + }, + "./utils": { + "types": "./build/esm/utils/index.d.ts", + "require": "./build/cjs/utils/index.js", + "import": "./build/esm/utils/index.js" + }, "./styles/*": "./styles/*", "./widget/*": "./widget/*", "./schema/*": "./schema/*" @@ -46,6 +56,12 @@ ], "server": [ "./server/index.d.ts" + ], + "models": [ + "./build/esm/models/index.d.ts" + ], + "utils": [ + "./build/esm/utils/index.d.ts" ] } }, diff --git a/src/blocks/CardLayout/CardLayout.tsx b/src/blocks/CardLayout/CardLayout.tsx index ad6294dfc2..9a007f86d0 100644 --- a/src/blocks/CardLayout/CardLayout.tsx +++ b/src/blocks/CardLayout/CardLayout.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import isEmpty from 'lodash/isEmpty'; diff --git a/src/blocks/Companies/Companies.tsx b/src/blocks/Companies/Companies.tsx index d16d78a05e..823efc4242 100644 --- a/src/blocks/Companies/Companies.tsx +++ b/src/blocks/Companies/Companies.tsx @@ -1,3 +1,5 @@ +'use client'; + import {Image, Title} from '../../components'; import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import {useTheme} from '../../context/theme'; diff --git a/src/blocks/ContentLayout/ContentLayout.tsx b/src/blocks/ContentLayout/ContentLayout.tsx index a6cfd666c6..645850c2e9 100644 --- a/src/blocks/ContentLayout/ContentLayout.tsx +++ b/src/blocks/ContentLayout/ContentLayout.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BackgroundImage, FileLink} from '../../components'; diff --git a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx index bc5e0b8fd8..84f8b8e175 100644 --- a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx +++ b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx @@ -1,3 +1,5 @@ +'use client'; + import {AnimateBlock, Title, YFMWrapper} from '../../components/'; import Image from '../../components/Image/Image'; import {getMediaImage} from '../../components/Media/Image/utils'; diff --git a/src/blocks/FilterBlock/FilterBlock.tsx b/src/blocks/FilterBlock/FilterBlock.tsx index 8eb4a81ed4..30f1d52a0d 100644 --- a/src/blocks/FilterBlock/FilterBlock.tsx +++ b/src/blocks/FilterBlock/FilterBlock.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {CardLayoutBlock} from '..'; diff --git a/src/blocks/FoldableList/FoldableList.tsx b/src/blocks/FoldableList/FoldableList.tsx index 86dfafd465..4f406347fa 100644 --- a/src/blocks/FoldableList/FoldableList.tsx +++ b/src/blocks/FoldableList/FoldableList.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Col, Row} from '../../grid'; diff --git a/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx b/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx index 97fb3319a2..d42913cb15 100644 --- a/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx +++ b/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useActionHandlers} from '@gravity-ui/uikit'; import {Foldable, ToggleArrow, YFMWrapper} from '../../../components'; diff --git a/src/blocks/Footer/Footer.tsx b/src/blocks/Footer/Footer.tsx index 3d83ae1207..60416b978a 100644 --- a/src/blocks/Footer/Footer.tsx +++ b/src/blocks/Footer/Footer.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Link} from '@gravity-ui/uikit'; diff --git a/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx b/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx index 3c7deaf777..1b116a12cc 100644 --- a/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx +++ b/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {DropdownMenu, DropdownMenuItem, Link, useResizeObserver} from '@gravity-ui/uikit'; diff --git a/src/blocks/Footer/components/LangSwitcher/LangSwitcher.tsx b/src/blocks/Footer/components/LangSwitcher/LangSwitcher.tsx index 98d653799c..929a1a4460 100644 --- a/src/blocks/Footer/components/LangSwitcher/LangSwitcher.tsx +++ b/src/blocks/Footer/components/LangSwitcher/LangSwitcher.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {InnerContext} from '../../../../context/innerContext'; diff --git a/src/blocks/Footer/components/NavigationFloor/NavigationFloor.tsx b/src/blocks/Footer/components/NavigationFloor/NavigationFloor.tsx index f45a39376c..4a552710e5 100644 --- a/src/blocks/Footer/components/NavigationFloor/NavigationFloor.tsx +++ b/src/blocks/Footer/components/NavigationFloor/NavigationFloor.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Link} from '@gravity-ui/uikit'; diff --git a/src/blocks/Footer/components/SocialFloor/SocialFloor.tsx b/src/blocks/Footer/components/SocialFloor/SocialFloor.tsx index 9305778a8d..1f5c2724b4 100644 --- a/src/blocks/Footer/components/SocialFloor/SocialFloor.tsx +++ b/src/blocks/Footer/components/SocialFloor/SocialFloor.tsx @@ -1,3 +1,5 @@ +'use client'; + import {Flex, Link, Tooltip} from '@gravity-ui/uikit'; import {Image} from '../../../../components'; diff --git a/src/blocks/Footer/hooks/useLogoImageProps.ts b/src/blocks/Footer/hooks/useLogoImageProps.ts index a06790efb7..acd92f2cbe 100644 --- a/src/blocks/Footer/hooks/useLogoImageProps.ts +++ b/src/blocks/Footer/hooks/useLogoImageProps.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {getMediaImage} from '../../../components/Media/Image/utils'; diff --git a/src/blocks/Footer/hooks/useOverflowListItems.ts b/src/blocks/Footer/hooks/useOverflowListItems.ts index 245c6b47f2..c3544aaf8a 100644 --- a/src/blocks/Footer/hooks/useOverflowListItems.ts +++ b/src/blocks/Footer/hooks/useOverflowListItems.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {DropdownMenuItem, useResizeObserver} from '@gravity-ui/uikit'; diff --git a/src/blocks/Form/Form.tsx b/src/blocks/Form/Form.tsx index c5c20dae49..7e0902fb8d 100644 --- a/src/blocks/Form/Form.tsx +++ b/src/blocks/Form/Form.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BackgroundImage, Title} from '../../components'; diff --git a/src/blocks/Header/Header.tsx b/src/blocks/Header/Header.tsx index ccb9369c83..dc0cd089ef 100644 --- a/src/blocks/Header/Header.tsx +++ b/src/blocks/Header/Header.tsx @@ -1,3 +1,5 @@ +'use client'; + /* eslint-disable complexity */ import * as React from 'react'; diff --git a/src/blocks/HeaderSlider/HeaderSlider.tsx b/src/blocks/HeaderSlider/HeaderSlider.tsx index 443f76dc97..b45a066930 100644 --- a/src/blocks/HeaderSlider/HeaderSlider.tsx +++ b/src/blocks/HeaderSlider/HeaderSlider.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {SliderBlock} from '../../blocks'; diff --git a/src/blocks/Hero/Hero.tsx b/src/blocks/Hero/Hero.tsx index 7b0da2c601..9bda58ff0a 100644 --- a/src/blocks/Hero/Hero.tsx +++ b/src/blocks/Hero/Hero.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {HeaderBreadcrumbs, Media, YFMWrapper} from '../../components'; diff --git a/src/blocks/Hero/hooks.ts b/src/blocks/Hero/hooks.ts index 013dc56d03..928d368713 100644 --- a/src/blocks/Hero/hooks.ts +++ b/src/blocks/Hero/hooks.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import throttle from 'lodash/throttle'; diff --git a/src/blocks/Icons/Icons.tsx b/src/blocks/Icons/Icons.tsx index e6ce8c667d..30c0180dc1 100644 --- a/src/blocks/Icons/Icons.tsx +++ b/src/blocks/Icons/Icons.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Image, Title} from '../../components'; diff --git a/src/blocks/Info/Info.tsx b/src/blocks/Info/Info.tsx index fdca648b60..f0b24a464a 100644 --- a/src/blocks/Info/Info.tsx +++ b/src/blocks/Info/Info.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useTheme} from '../../context/theme'; import {Col, Grid, Row} from '../../grid'; import {ContentTheme, InfoBlockProps, LinkTheme} from '../../models'; diff --git a/src/blocks/Media/Media.tsx b/src/blocks/Media/Media.tsx index e8e5720dbf..da9ba76088 100644 --- a/src/blocks/Media/Media.tsx +++ b/src/blocks/Media/Media.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import Media from '../../components/Media/Media'; diff --git a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx index 4c34e6a958..98bfe6abaa 100644 --- a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx +++ b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx @@ -1,3 +1,5 @@ +'use client'; + import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import BalancedMasonry from '../../components/BalancedMasonry/BalancedMasonry'; import FullWidthBackground from '../../components/FullWidthBackground/FullWidthBackground'; diff --git a/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx b/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx index c30db81f0f..2478a22569 100644 --- a/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx +++ b/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useActionHandlers} from '@gravity-ui/uikit'; import {Foldable, ToggleArrow, YFMWrapper} from '../../../components'; diff --git a/src/blocks/Questions/Questions.tsx b/src/blocks/Questions/Questions.tsx index a0160a5df0..2069b3058b 100644 --- a/src/blocks/Questions/Questions.tsx +++ b/src/blocks/Questions/Questions.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Col, Row} from '../../grid'; diff --git a/src/blocks/Share/Share.tsx b/src/blocks/Share/Share.tsx index 0c17d7dd88..d908414730 100644 --- a/src/blocks/Share/Share.tsx +++ b/src/blocks/Share/Share.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Button, Icon} from '@gravity-ui/uikit'; diff --git a/src/blocks/Slider/Arrow/Arrow.tsx b/src/blocks/Slider/Arrow/Arrow.tsx index ccfd05812a..8f2890a1bf 100644 --- a/src/blocks/Slider/Arrow/Arrow.tsx +++ b/src/blocks/Slider/Arrow/Arrow.tsx @@ -1,3 +1,5 @@ +'use client'; + import ToggleArrow from '../../../components/ToggleArrow/ToggleArrow'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; diff --git a/src/blocks/Slider/Slider.tsx b/src/blocks/Slider/Slider.tsx index 55618298c8..dd79430939 100644 --- a/src/blocks/Slider/Slider.tsx +++ b/src/blocks/Slider/Slider.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import 'swiper/css'; diff --git a/src/blocks/Slider/useSlider.tsx b/src/blocks/Slider/useSlider.tsx index 3b11483b25..8f7cecf882 100644 --- a/src/blocks/Slider/useSlider.tsx +++ b/src/blocks/Slider/useSlider.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import type {Swiper} from 'swiper'; diff --git a/src/blocks/SliderOld/Arrow/Arrow.tsx b/src/blocks/SliderOld/Arrow/Arrow.tsx index b23e6e0640..9a1e13e2ef 100644 --- a/src/blocks/SliderOld/Arrow/Arrow.tsx +++ b/src/blocks/SliderOld/Arrow/Arrow.tsx @@ -1,3 +1,5 @@ +'use client'; + import ToggleArrow from '../../../components/ToggleArrow/ToggleArrow'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; diff --git a/src/blocks/SliderOld/SliderOld.tsx b/src/blocks/SliderOld/SliderOld.tsx index e6eea21d0b..688e8c3cef 100644 --- a/src/blocks/SliderOld/SliderOld.tsx +++ b/src/blocks/SliderOld/SliderOld.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {useUniqId} from '@gravity-ui/uikit'; diff --git a/src/blocks/Table/Table.tsx b/src/blocks/Table/Table.tsx index b292709574..acd9e77d77 100644 --- a/src/blocks/Table/Table.tsx +++ b/src/blocks/Table/Table.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Table, YFMWrapper} from '../../components'; diff --git a/src/blocks/Tabs/TabContent/TabContent.tsx b/src/blocks/Tabs/TabContent/TabContent.tsx index bb503316f6..624fceb83a 100644 --- a/src/blocks/Tabs/TabContent/TabContent.tsx +++ b/src/blocks/Tabs/TabContent/TabContent.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {useUniqId} from '@gravity-ui/uikit'; diff --git a/src/blocks/Tabs/Tabs.tsx b/src/blocks/Tabs/Tabs.tsx index 24fcb535e4..9dc931881d 100644 --- a/src/blocks/Tabs/Tabs.tsx +++ b/src/blocks/Tabs/Tabs.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {getUniqId} from '@gravity-ui/uikit'; diff --git a/src/components/AnimateBlock/AnimateBlock.tsx b/src/components/AnimateBlock/AnimateBlock.tsx index 6d3c381f90..db880cd3a0 100644 --- a/src/components/AnimateBlock/AnimateBlock.tsx +++ b/src/components/AnimateBlock/AnimateBlock.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Waypoint} from 'react-waypoint'; diff --git a/src/components/BackLink/BackLink.tsx b/src/components/BackLink/BackLink.tsx index e5fdd82073..ba43aede9b 100644 --- a/src/components/BackLink/BackLink.tsx +++ b/src/components/BackLink/BackLink.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ArrowLeft} from '@gravity-ui/icons'; diff --git a/src/components/BackgroundMedia/BackgroundMedia.tsx b/src/components/BackgroundMedia/BackgroundMedia.tsx index b676147cc0..80e08461bc 100644 --- a/src/components/BackgroundMedia/BackgroundMedia.tsx +++ b/src/components/BackgroundMedia/BackgroundMedia.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {MobileContext} from '../../context/mobileContext'; diff --git a/src/components/BalancedMasonry/BalancedMasonry.tsx b/src/components/BalancedMasonry/BalancedMasonry.tsx index aaf1367a87..b1c6ddb33f 100644 --- a/src/components/BalancedMasonry/BalancedMasonry.tsx +++ b/src/components/BalancedMasonry/BalancedMasonry.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import debounce from 'lodash/debounce'; diff --git a/src/components/BrandFooter/BrandFooter.tsx b/src/components/BrandFooter/BrandFooter.tsx index 6f6120c50c..0aac0fe332 100644 --- a/src/components/BrandFooter/BrandFooter.tsx +++ b/src/components/BrandFooter/BrandFooter.tsx @@ -1,3 +1,5 @@ +'use client'; + import {Link} from '@gravity-ui/uikit'; import {useTheme} from '../../context/theme'; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 1c73da4c0d..a1ab932783 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {StoreBadge} from '@gravity-ui/components'; diff --git a/src/components/ButtonTabs/ButtonTabs.tsx b/src/components/ButtonTabs/ButtonTabs.tsx index 585b3c8953..f2272b7334 100644 --- a/src/components/ButtonTabs/ButtonTabs.tsx +++ b/src/components/ButtonTabs/ButtonTabs.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ButtonSize} from '@gravity-ui/uikit'; diff --git a/src/components/CardBase/CardBase.tsx b/src/components/CardBase/CardBase.tsx index dd34f4a4ad..1d4aefdb99 100644 --- a/src/components/CardBase/CardBase.tsx +++ b/src/components/CardBase/CardBase.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Link} from '@gravity-ui/uikit'; diff --git a/src/components/ContentIcon/ContentIcon.tsx b/src/components/ContentIcon/ContentIcon.tsx index 8700991f66..e6afbfc60b 100644 --- a/src/components/ContentIcon/ContentIcon.tsx +++ b/src/components/ContentIcon/ContentIcon.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useTheme} from '../../context/theme'; import {ClassNameProps, GravityIconProps, ImageProps, QAProps, SVGIcon} from '../../models'; import {ThemeSupporting, getThemedValue} from '../../utils'; diff --git a/src/components/Control/Control.tsx b/src/components/Control/Control.tsx index 56838c13de..b6bfe25029 100644 --- a/src/components/Control/Control.tsx +++ b/src/components/Control/Control.tsx @@ -1,3 +1,5 @@ +'use client'; + import {Icon, IconData} from '@gravity-ui/uikit'; import {block} from '../../utils'; diff --git a/src/components/DefaultVideo/DefaultVideo.tsx b/src/components/DefaultVideo/DefaultVideo.tsx index 84c4e84b6e..71dcbf3dd6 100644 --- a/src/components/DefaultVideo/DefaultVideo.tsx +++ b/src/components/DefaultVideo/DefaultVideo.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {CustomControlsType, MediaVideoControlsType, MediaVideoProps} from '../../models'; diff --git a/src/components/ErrorWrapper/ErrorWrapper.tsx b/src/components/ErrorWrapper/ErrorWrapper.tsx index c35b0245df..2227642b59 100644 --- a/src/components/ErrorWrapper/ErrorWrapper.tsx +++ b/src/components/ErrorWrapper/ErrorWrapper.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Button} from '@gravity-ui/uikit'; diff --git a/src/components/FileLink/FileLink.tsx b/src/components/FileLink/FileLink.tsx index eb06a122d6..4bc115f292 100644 --- a/src/components/FileLink/FileLink.tsx +++ b/src/components/FileLink/FileLink.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Label, LabelProps} from '@gravity-ui/uikit'; diff --git a/src/components/Foldable/Foldable.tsx b/src/components/Foldable/Foldable.tsx index 81ed7838cd..d9f5dce24a 100644 --- a/src/components/Foldable/Foldable.tsx +++ b/src/components/Foldable/Foldable.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import useHeightCalculator from '../../hooks/useHeightCalculator'; diff --git a/src/components/FullWidthBackground/FullWidthBackground.tsx b/src/components/FullWidthBackground/FullWidthBackground.tsx index 60c6fb31a1..db51ec3919 100644 --- a/src/components/FullWidthBackground/FullWidthBackground.tsx +++ b/src/components/FullWidthBackground/FullWidthBackground.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import debounce from 'lodash/debounce'; diff --git a/src/components/FullscreenImage/FullscreenImage.tsx b/src/components/FullscreenImage/FullscreenImage.tsx index 7fb6c4c689..51a36bd14a 100644 --- a/src/components/FullscreenImage/FullscreenImage.tsx +++ b/src/components/FullscreenImage/FullscreenImage.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ChevronsExpandUpRight, Xmark} from '@gravity-ui/icons'; diff --git a/src/components/FullscreenMedia/FullscreenMedia.tsx b/src/components/FullscreenMedia/FullscreenMedia.tsx index 4d73ba3535..0bcf60e13e 100644 --- a/src/components/FullscreenMedia/FullscreenMedia.tsx +++ b/src/components/FullscreenMedia/FullscreenMedia.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ChevronsExpandUpRight, Xmark} from '@gravity-ui/icons'; diff --git a/src/components/HTML/HTML.tsx b/src/components/HTML/HTML.tsx index bb8cb3084b..3fa6cb280e 100644 --- a/src/components/HTML/HTML.tsx +++ b/src/components/HTML/HTML.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ClassNameProps, QAProps, TagName} from '../../models/common'; diff --git a/src/components/HeaderBreadcrumbs/HeaderBreadcrumbs.tsx b/src/components/HeaderBreadcrumbs/HeaderBreadcrumbs.tsx index 8950246f26..bb4b4f00de 100644 --- a/src/components/HeaderBreadcrumbs/HeaderBreadcrumbs.tsx +++ b/src/components/HeaderBreadcrumbs/HeaderBreadcrumbs.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {useAnalytics} from '../../hooks'; diff --git a/src/components/Image/Image.tsx b/src/components/Image/Image.tsx index 5031e546b4..2cf95bba42 100644 --- a/src/components/Image/Image.tsx +++ b/src/components/Image/Image.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BREAKPOINTS} from '../../constants'; diff --git a/src/components/ImageBase/ImageBase.tsx b/src/components/ImageBase/ImageBase.tsx index 09b5d18199..0e86752e11 100644 --- a/src/components/ImageBase/ImageBase.tsx +++ b/src/components/ImageBase/ImageBase.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ImageContext} from '../../context/imageContext/imageContext'; diff --git a/src/components/InnerForm/InnerForm.tsx b/src/components/InnerForm/InnerForm.tsx index d9c5249424..e6b7afee86 100644 --- a/src/components/InnerForm/InnerForm.tsx +++ b/src/components/InnerForm/InnerForm.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {YandexForm} from '..'; diff --git a/src/components/Link/Link.tsx b/src/components/Link/Link.tsx index 6b9e63303c..bb70bfb2f6 100644 --- a/src/components/Link/Link.tsx +++ b/src/components/Link/Link.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ChevronRight} from '@gravity-ui/icons'; diff --git a/src/components/Map/GoogleMap.tsx b/src/components/Map/GoogleMap.tsx index 57065e4907..f9655c0ff3 100644 --- a/src/components/Map/GoogleMap.tsx +++ b/src/components/Map/GoogleMap.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Lang} from '@gravity-ui/uikit'; diff --git a/src/components/Map/Map.tsx b/src/components/Map/Map.tsx index 30635807d6..17e31baead 100644 --- a/src/components/Map/Map.tsx +++ b/src/components/Map/Map.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {MapType, MapsContext} from '../../context/mapsContext/mapsContext'; diff --git a/src/components/Map/YMap/YMap.ts b/src/components/Map/YMap/YMap.ts index a6439f43e9..7ed6b097e0 100644 --- a/src/components/Map/YMap/YMap.ts +++ b/src/components/Map/YMap/YMap.ts @@ -1,3 +1,5 @@ +'use client'; + import {YMapMarkerLabelPrivate, YMapMarkerPrivate, YMapProps} from '../../../models'; import {Coordinate} from '../../../models/constructor-items/common'; diff --git a/src/components/Map/YMap/YandexMap.tsx b/src/components/Map/YMap/YandexMap.tsx index d24167b8ab..6d3cbe0bdc 100644 --- a/src/components/Map/YMap/YandexMap.tsx +++ b/src/components/Map/YMap/YandexMap.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Spin} from '@gravity-ui/uikit'; diff --git a/src/components/Media/DataLens/DataLens.tsx b/src/components/Media/DataLens/DataLens.tsx index 25a1b59175..fbd9bc36ed 100644 --- a/src/components/Media/DataLens/DataLens.tsx +++ b/src/components/Media/DataLens/DataLens.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {LocaleContext} from '../../../context/localeContext'; diff --git a/src/components/Media/Image/Image.tsx b/src/components/Media/Image/Image.tsx index 896dc2d97b..1f1b64bd94 100644 --- a/src/components/Media/Image/Image.tsx +++ b/src/components/Media/Image/Image.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Interpolation, animated, config, useSpring} from '@react-spring/web'; diff --git a/src/components/Media/Media.tsx b/src/components/Media/Media.tsx index b1fb71a9f7..294dfba95a 100644 --- a/src/components/Media/Media.tsx +++ b/src/components/Media/Media.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {InnerContext} from '../../context/innerContext'; diff --git a/src/components/Media/Video/Video.tsx b/src/components/Media/Video/Video.tsx index 89f9541f1a..cf4f72efc0 100644 --- a/src/components/Media/Video/Video.tsx +++ b/src/components/Media/Video/Video.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {MediaComponentVideoProps, MediaVideoType, PlayButtonProps, QAProps} from '../../../models'; diff --git a/src/components/MediaBase/MediaBase.tsx b/src/components/MediaBase/MediaBase.tsx index 3d94b538da..04fa30de0b 100644 --- a/src/components/MediaBase/MediaBase.tsx +++ b/src/components/MediaBase/MediaBase.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; diff --git a/src/components/OutsideClick/OutsideClick.tsx b/src/components/OutsideClick/OutsideClick.tsx index a1a6191be9..48692726ea 100644 --- a/src/components/OutsideClick/OutsideClick.tsx +++ b/src/components/OutsideClick/OutsideClick.tsx @@ -1,3 +1,5 @@ +'use client'; + //TODO move into cloud components? import * as React from 'react'; diff --git a/src/components/OverflowScroller/OverflowScroller.tsx b/src/components/OverflowScroller/OverflowScroller.tsx index 8e15549192..490b0f58b2 100644 --- a/src/components/OverflowScroller/OverflowScroller.tsx +++ b/src/components/OverflowScroller/OverflowScroller.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import debounce from 'lodash/debounce'; diff --git a/src/components/ReactPlayer/CircleProgress.tsx b/src/components/ReactPlayer/CircleProgress.tsx index 487a9af87a..6a5307e117 100644 --- a/src/components/ReactPlayer/CircleProgress.tsx +++ b/src/components/ReactPlayer/CircleProgress.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ClassNameProps} from '../../models'; diff --git a/src/components/ReactPlayer/CustomBarControls.tsx b/src/components/ReactPlayer/CustomBarControls.tsx index 385ca3d76a..4f392b6ea7 100644 --- a/src/components/ReactPlayer/CustomBarControls.tsx +++ b/src/components/ReactPlayer/CustomBarControls.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Pause, Play, VolumeFill, VolumeLow, VolumeXmark} from '@gravity-ui/icons'; diff --git a/src/components/ReactPlayer/ReactPlayer.tsx b/src/components/ReactPlayer/ReactPlayer.tsx index 0b41506e81..0669432d7e 100644 --- a/src/components/ReactPlayer/ReactPlayer.tsx +++ b/src/components/ReactPlayer/ReactPlayer.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {PlayFill} from '@gravity-ui/icons'; diff --git a/src/components/RootCn/index.tsx b/src/components/RootCn/index.tsx index fa923b6365..60f7ee5f4d 100644 --- a/src/components/RootCn/index.tsx +++ b/src/components/RootCn/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {useTheme} from '../../context/theme'; diff --git a/src/components/RouterLink/RouterLink.tsx b/src/components/RouterLink/RouterLink.tsx index ba80ca4aec..63a2716600 100644 --- a/src/components/RouterLink/RouterLink.tsx +++ b/src/components/RouterLink/RouterLink.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {LocationContext} from '../../context/locationContext'; diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 57be612ef4..740a85d55e 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Check, Minus} from '@gravity-ui/icons'; diff --git a/src/components/Title/TitleItem.tsx b/src/components/Title/TitleItem.tsx index 717ab76e73..10506f3008 100644 --- a/src/components/Title/TitleItem.tsx +++ b/src/components/Title/TitleItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ToggleArrow, YFMWrapper} from '../'; diff --git a/src/components/VideoBlock/VideoBlock.tsx b/src/components/VideoBlock/VideoBlock.tsx index 1da63b40a8..0101fce676 100644 --- a/src/components/VideoBlock/VideoBlock.tsx +++ b/src/components/VideoBlock/VideoBlock.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {PlayFill} from '@gravity-ui/icons'; diff --git a/src/components/YandexForm/YandexForm.tsx b/src/components/YandexForm/YandexForm.tsx index 213de82827..862e9ca083 100644 --- a/src/components/YandexForm/YandexForm.tsx +++ b/src/components/YandexForm/YandexForm.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {LocaleContext} from '../../context/localeContext'; diff --git a/src/containers/Loadable/Loadable.tsx b/src/containers/Loadable/Loadable.tsx index cec6db89fc..e223c03561 100644 --- a/src/containers/Loadable/Loadable.tsx +++ b/src/containers/Loadable/Loadable.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Spin} from '@gravity-ui/uikit'; diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index 3a9713453f..c612a0a1b7 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import BackgroundMedia from '../../components/BackgroundMedia/BackgroundMedia'; diff --git a/src/containers/PageConstructor/Provider.tsx b/src/containers/PageConstructor/Provider.tsx index 7e3203039a..0f71ef2465 100644 --- a/src/containers/PageConstructor/Provider.tsx +++ b/src/containers/PageConstructor/Provider.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {DEFAULT_THEME} from '../../components/constants'; diff --git a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx index b41b5bc182..8ecce95f68 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import pick from 'lodash/pick'; diff --git a/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx b/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx index 5ce480b4b2..ab10dee27a 100644 --- a/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import get from 'lodash/get'; diff --git a/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx b/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx index ce6af6d3f6..cce5bf7b69 100644 --- a/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx +++ b/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BlockIdContext} from '../../../../context/blockIdContext'; diff --git a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx index 4488dd6f2c..9264548a01 100644 --- a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx +++ b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BlockIdContext} from '../../../../context/blockIdContext'; diff --git a/src/context/analyticsContext/analyticsContext.tsx b/src/context/analyticsContext/analyticsContext.tsx index 1d1838e740..db1e56d290 100644 --- a/src/context/analyticsContext/analyticsContext.tsx +++ b/src/context/analyticsContext/analyticsContext.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {AnalyticsEvent} from '../../models'; diff --git a/src/context/animateContext/AnimateContext.tsx b/src/context/animateContext/AnimateContext.tsx index 427d810357..b56a4cae73 100644 --- a/src/context/animateContext/AnimateContext.tsx +++ b/src/context/animateContext/AnimateContext.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; export interface AnimateContextProps { diff --git a/src/context/blockIdContext/blockIdContext.ts b/src/context/blockIdContext/blockIdContext.ts index 289ed964df..f4d379a0f5 100644 --- a/src/context/blockIdContext/blockIdContext.ts +++ b/src/context/blockIdContext/blockIdContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; export type BlockIdContextProp = string; diff --git a/src/context/formsContext/FormsContext.ts b/src/context/formsContext/FormsContext.ts index 1b71d016f2..f51cbacf12 100644 --- a/src/context/formsContext/FormsContext.ts +++ b/src/context/formsContext/FormsContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {YandexFormProps} from '../../models/constructor-items/common'; diff --git a/src/context/imageContext/imageContext.ts b/src/context/imageContext/imageContext.ts index 8a6240a3a9..2f2a5fc2a1 100644 --- a/src/context/imageContext/imageContext.ts +++ b/src/context/imageContext/imageContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import type {ImageBaseProps} from '../../components'; diff --git a/src/context/innerContext/InnerContext.ts b/src/context/innerContext/InnerContext.ts index 404fa02c55..2a1369e66c 100644 --- a/src/context/innerContext/InnerContext.ts +++ b/src/context/innerContext/InnerContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ItemMap, NavItemMap} from '../../containers/PageConstructor/PageConstructor'; diff --git a/src/context/localeContext/localeContext.ts b/src/context/localeContext/localeContext.ts index 138a503a85..3905fba01c 100644 --- a/src/context/localeContext/localeContext.ts +++ b/src/context/localeContext/localeContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Lang} from '@gravity-ui/uikit'; diff --git a/src/context/locationContext/locationContext.ts b/src/context/locationContext/locationContext.ts index 9b40725ce3..aafc897e56 100644 --- a/src/context/locationContext/locationContext.ts +++ b/src/context/locationContext/locationContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {RouterLinkProps} from '../../components/RouterLink/RouterLink'; diff --git a/src/context/mapsContext/mapsContext.ts b/src/context/mapsContext/mapsContext.ts index 19590662f9..256232a37f 100644 --- a/src/context/mapsContext/mapsContext.ts +++ b/src/context/mapsContext/mapsContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; export enum MapType { diff --git a/src/context/mapsContext/mapsProvider.tsx b/src/context/mapsContext/mapsProvider.tsx index 431a9140c7..246f017140 100644 --- a/src/context/mapsContext/mapsProvider.tsx +++ b/src/context/mapsContext/mapsProvider.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {MapType, MapsContext} from './mapsContext'; diff --git a/src/context/mapsContext/useMap.ts b/src/context/mapsContext/useMap.ts index 4cff63e489..6e83465eb1 100644 --- a/src/context/mapsContext/useMap.ts +++ b/src/context/mapsContext/useMap.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {MapsContext, MapsContextType} from './mapsContext'; diff --git a/src/context/mobileContext/MobileContext.ts b/src/context/mobileContext/MobileContext.ts index 154ab58277..ecc7bf129f 100644 --- a/src/context/mobileContext/MobileContext.ts +++ b/src/context/mobileContext/MobileContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; export const MobileContext = React.createContext(false); diff --git a/src/context/projectSettingsContext/ProjectSettingsContext.ts b/src/context/projectSettingsContext/ProjectSettingsContext.ts index 1a753aeb86..28a9595c17 100644 --- a/src/context/projectSettingsContext/ProjectSettingsContext.ts +++ b/src/context/projectSettingsContext/ProjectSettingsContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; export interface ProjectSettingsContextProps { disableCompress?: boolean; diff --git a/src/context/ssrContext/SSRContext.ts b/src/context/ssrContext/SSRContext.ts index ca50b1bb67..b34b4b7e70 100644 --- a/src/context/ssrContext/SSRContext.ts +++ b/src/context/ssrContext/SSRContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; export interface SSRContextProps { isServer?: boolean; diff --git a/src/context/stylesContext/StylesContext.ts b/src/context/stylesContext/StylesContext.ts index e4cb6afd6b..a081412453 100644 --- a/src/context/stylesContext/StylesContext.ts +++ b/src/context/stylesContext/StylesContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; export interface StylesContextProps { setStyles: (newValues: Record) => void; diff --git a/src/context/theme/ThemeContext.ts b/src/context/theme/ThemeContext.ts index 52d2ae1964..875136ebc1 100644 --- a/src/context/theme/ThemeContext.ts +++ b/src/context/theme/ThemeContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {DEFAULT_THEME} from '../../components/constants'; diff --git a/src/context/theme/useTheme.ts b/src/context/theme/useTheme.ts index 72436bf0d1..f599871174 100644 --- a/src/context/theme/useTheme.ts +++ b/src/context/theme/useTheme.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ThemeContext, ThemeContextProps} from './ThemeContext'; diff --git a/src/context/theme/withTheme.tsx b/src/context/theme/withTheme.tsx index 043efac7de..9d9f82b60e 100644 --- a/src/context/theme/withTheme.tsx +++ b/src/context/theme/withTheme.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Subtract} from 'utility-types'; diff --git a/src/context/videoContext/VideoContext.ts b/src/context/videoContext/VideoContext.ts index 34e1cff34d..7db84a3bfc 100644 --- a/src/context/videoContext/VideoContext.ts +++ b/src/context/videoContext/VideoContext.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; export interface VideoContextProps { diff --git a/src/context/windowWidthContext/WindowWidthContext.tsx b/src/context/windowWidthContext/WindowWidthContext.tsx index 388b76477e..27c2f13b54 100644 --- a/src/context/windowWidthContext/WindowWidthContext.tsx +++ b/src/context/windowWidthContext/WindowWidthContext.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import throttle from 'lodash/throttle'; diff --git a/src/editor/components/AddBlock/AddBlock.tsx b/src/editor/components/AddBlock/AddBlock.tsx index 1211b41a83..2a1249b90b 100644 --- a/src/editor/components/AddBlock/AddBlock.tsx +++ b/src/editor/components/AddBlock/AddBlock.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Plus} from '@gravity-ui/icons'; diff --git a/src/editor/components/BlockForm/BlockForm.tsx b/src/editor/components/BlockForm/BlockForm.tsx index 44686ebfa2..1982857db5 100644 --- a/src/editor/components/BlockForm/BlockForm.tsx +++ b/src/editor/components/BlockForm/BlockForm.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {DynamicField, SimpleVerticalAccordeon, Spec} from '@gravity-ui/dynamic-forms'; diff --git a/src/editor/components/CodeEditor/CodeEditor.tsx b/src/editor/components/CodeEditor/CodeEditor.tsx index de0978a5c4..4ebcf739b9 100644 --- a/src/editor/components/CodeEditor/CodeEditor.tsx +++ b/src/editor/components/CodeEditor/CodeEditor.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ChevronsCollapseUpRight, ChevronsExpandUpRight} from '@gravity-ui/icons'; diff --git a/src/editor/components/ControlPanel/ControlPanel.tsx b/src/editor/components/ControlPanel/ControlPanel.tsx index be4158f796..f8c8d3b246 100644 --- a/src/editor/components/ControlPanel/ControlPanel.tsx +++ b/src/editor/components/ControlPanel/ControlPanel.tsx @@ -1,3 +1,5 @@ +'use client'; + import {Code, Display, Eye, Smartphone, SquareDashedText} from '@gravity-ui/icons'; import {SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; diff --git a/src/editor/components/DeviceEmulation/DeviceEmulationMobile/DeviceEmulationMobile.tsx b/src/editor/components/DeviceEmulation/DeviceEmulationMobile/DeviceEmulationMobile.tsx index 2c3dbdbb66..f1d0c8640f 100644 --- a/src/editor/components/DeviceEmulation/DeviceEmulationMobile/DeviceEmulationMobile.tsx +++ b/src/editor/components/DeviceEmulation/DeviceEmulationMobile/DeviceEmulationMobile.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {block} from '../../../../utils'; diff --git a/src/editor/components/EditBlock/EditBlock.tsx b/src/editor/components/EditBlock/EditBlock.tsx index 100d392617..fb0bd4d21c 100644 --- a/src/editor/components/EditBlock/EditBlock.tsx +++ b/src/editor/components/EditBlock/EditBlock.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ChevronDown, ChevronUp, Copy as CopyIcon, TrashBin} from '@gravity-ui/icons'; diff --git a/src/editor/components/ErrorBoundary/ErrorBoundary.tsx b/src/editor/components/ErrorBoundary/ErrorBoundary.tsx index c901863400..ad69b52b35 100644 --- a/src/editor/components/ErrorBoundary/ErrorBoundary.tsx +++ b/src/editor/components/ErrorBoundary/ErrorBoundary.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BlockDecorationProps} from '../../../models'; diff --git a/src/editor/components/PageSettings/PageSettings.tsx b/src/editor/components/PageSettings/PageSettings.tsx index 06b0758493..f991280961 100644 --- a/src/editor/components/PageSettings/PageSettings.tsx +++ b/src/editor/components/PageSettings/PageSettings.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {DynamicField, Spec} from '@gravity-ui/dynamic-forms'; diff --git a/src/editor/containers/Editor/Editor.tsx b/src/editor/containers/Editor/Editor.tsx index b19e2d1dd1..d9905805c8 100644 --- a/src/editor/containers/Editor/Editor.tsx +++ b/src/editor/containers/Editor/Editor.tsx @@ -1,3 +1,5 @@ +'use client'; + import {PageConstructor, PageConstructorProvider} from '../../../containers/PageConstructor'; import {block} from '../../../utils'; import AddBlock from '../../components/AddBlock/AddBlock'; diff --git a/src/editor/containers/Editor/hooks/useCode.ts b/src/editor/containers/Editor/hooks/useCode.ts index 2c33b46032..fe56ff3850 100644 --- a/src/editor/containers/Editor/hooks/useCode.ts +++ b/src/editor/containers/Editor/hooks/useCode.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import yaml from 'js-yaml'; diff --git a/src/editor/containers/Editor/hooks/useEditorState.tsx b/src/editor/containers/Editor/hooks/useEditorState.tsx index 3f2824b296..7504acacee 100644 --- a/src/editor/containers/Editor/hooks/useEditorState.tsx +++ b/src/editor/containers/Editor/hooks/useEditorState.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BlockDecorationProps} from '../../../../models'; diff --git a/src/editor/containers/Form/Form.tsx b/src/editor/containers/Form/Form.tsx index 9d03b3a4dc..1a0c304005 100644 --- a/src/editor/containers/Form/Form.tsx +++ b/src/editor/containers/Form/Form.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Text} from '@gravity-ui/uikit'; diff --git a/src/editor/context.ts b/src/editor/context.ts index 4d16e74ff9..f15ddd1fdd 100644 --- a/src/editor/context.ts +++ b/src/editor/context.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {PageConstructorProps, PageConstructorProviderProps} from '../containers/PageConstructor'; diff --git a/src/editor/dynamic-forms-custom/components/OneOfCustom/OneOfCustom.tsx b/src/editor/dynamic-forms-custom/components/OneOfCustom/OneOfCustom.tsx index ee7a2f400c..5776146dd3 100644 --- a/src/editor/dynamic-forms-custom/components/OneOfCustom/OneOfCustom.tsx +++ b/src/editor/dynamic-forms-custom/components/OneOfCustom/OneOfCustom.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import { diff --git a/src/editor/dynamic-forms-custom/hooks/useOneOf.tsx b/src/editor/dynamic-forms-custom/hooks/useOneOf.tsx index 10cd043ff1..7820cff49a 100644 --- a/src/editor/dynamic-forms-custom/hooks/useOneOf.tsx +++ b/src/editor/dynamic-forms-custom/hooks/useOneOf.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import { diff --git a/src/editor/hooks/useCodeValidator.ts b/src/editor/hooks/useCodeValidator.ts index 290cec25b4..6aa19580a9 100644 --- a/src/editor/hooks/useCodeValidator.ts +++ b/src/editor/hooks/useCodeValidator.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {JSONSchema4} from 'json-schema'; diff --git a/src/editor/hooks/useFormSpec.ts b/src/editor/hooks/useFormSpec.ts index 8011f510bc..7e2acb032b 100644 --- a/src/editor/hooks/useFormSpec.ts +++ b/src/editor/hooks/useFormSpec.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {JSONSchema4} from 'json-schema'; diff --git a/src/editor/hooks/usePreviousValue.ts b/src/editor/hooks/usePreviousValue.ts index fdbf6bcc48..9ee1cbf4fc 100644 --- a/src/editor/hooks/usePreviousValue.ts +++ b/src/editor/hooks/usePreviousValue.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; export default function usePreviousValue(value: T) { diff --git a/src/editor/store/main/index.ts b/src/editor/store/main/index.ts index ac2874cffd..3ad4eb0b47 100644 --- a/src/editor/store/main/index.ts +++ b/src/editor/store/main/index.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {DEFAULT_THEME} from '../../../components/constants'; diff --git a/src/editor/store/settings/index.ts b/src/editor/store/settings/index.ts index 0fecbd93f0..c2869ebe67 100644 --- a/src/editor/store/settings/index.ts +++ b/src/editor/store/settings/index.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Theme} from '../../../models'; diff --git a/src/grid/Row/Row.tsx b/src/grid/Row/Row.tsx index 7288aad228..d850ccdc6a 100644 --- a/src/grid/Row/Row.tsx +++ b/src/grid/Row/Row.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {AriaProps, Roleable} from '../../models'; diff --git a/src/hooks/hubspot.ts b/src/hooks/hubspot.ts index 2a05de598e..d6e01be9a9 100644 --- a/src/hooks/hubspot.ts +++ b/src/hooks/hubspot.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {HubspotEventHandlers, handleHubspotEvents, loopBackHabspotEvents} from '../utils/hubspot'; diff --git a/src/hooks/useAnalytics.ts b/src/hooks/useAnalytics.ts index 5eb1768d9d..bacb627a09 100644 --- a/src/hooks/useAnalytics.ts +++ b/src/hooks/useAnalytics.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {AnalyticsContext} from '../context/analyticsContext'; diff --git a/src/hooks/useDeviceValue.ts b/src/hooks/useDeviceValue.ts index b5b93ad544..16d596e585 100644 --- a/src/hooks/useDeviceValue.ts +++ b/src/hooks/useDeviceValue.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BREAKPOINTS} from '../constants'; diff --git a/src/hooks/useFocus.ts b/src/hooks/useFocus.ts index 6238bf300f..abec0ac18f 100644 --- a/src/hooks/useFocus.ts +++ b/src/hooks/useFocus.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import noop from 'lodash/noop'; diff --git a/src/hooks/useHeightCalculator.ts b/src/hooks/useHeightCalculator.ts index 6017e4b2e3..64c8b5a181 100644 --- a/src/hooks/useHeightCalculator.ts +++ b/src/hooks/useHeightCalculator.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import debounce from 'lodash/debounce'; diff --git a/src/hooks/useImageSize.ts b/src/hooks/useImageSize.ts index f9d3dd8295..e9bfd40e96 100644 --- a/src/hooks/useImageSize.ts +++ b/src/hooks/useImageSize.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {MediaAllProps} from '../components/Media/Media'; diff --git a/src/hooks/useMount.ts b/src/hooks/useMount.ts index 53735c8375..fd5be48c24 100644 --- a/src/hooks/useMount.ts +++ b/src/hooks/useMount.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; type Unsubscribe = () => void; diff --git a/src/hooks/useWindowBreakpoint.ts b/src/hooks/useWindowBreakpoint.ts index 6a1e8d4a40..51ca96b145 100644 --- a/src/hooks/useWindowBreakpoint.ts +++ b/src/hooks/useWindowBreakpoint.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import debounce from 'lodash/debounce'; diff --git a/src/models/constructor-items/blocks.ts b/src/models/constructor-items/blocks.ts index 120756cd87..dcffb17641 100644 --- a/src/models/constructor-items/blocks.ts +++ b/src/models/constructor-items/blocks.ts @@ -1,6 +1,6 @@ import * as React from 'react'; -import {ButtonSize} from '@gravity-ui/uikit'; +import type {ButtonSize} from '@gravity-ui/uikit'; import {GridColumnSize, GridColumnSizesType, IndentValue} from '../../grid/types'; import {ThemeSupporting} from '../../utils'; diff --git a/src/models/constructor-items/common.ts b/src/models/constructor-items/common.ts index 71f4bcdb1b..33770aa819 100644 --- a/src/models/constructor-items/common.ts +++ b/src/models/constructor-items/common.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import * as icons from '@gravity-ui/icons'; -import {ButtonView, IconData, ButtonProps as UikitButtonProps} from '@gravity-ui/uikit'; +import type {ButtonView, IconData, ButtonProps as UikitButtonProps} from '@gravity-ui/uikit'; import {ThemeSupporting} from '../../utils'; import {AnalyticsEventsBase, AnalyticsEventsProp, ClassNameProps, QAProps} from '../common'; diff --git a/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx b/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx index ccc88837d3..7e2fb2a578 100644 --- a/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx +++ b/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx @@ -1,3 +1,5 @@ +'use client'; + import OverflowScroller from '../../../components/OverflowScroller/OverflowScroller'; import {block} from '../../../utils'; import {DesktopNavigationProps, ItemColumnName, NavigationLayout} from '../../models'; diff --git a/src/navigation/components/Logo/Logo.tsx b/src/navigation/components/Logo/Logo.tsx index c6e474f909..618de85263 100644 --- a/src/navigation/components/Logo/Logo.tsx +++ b/src/navigation/components/Logo/Logo.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Image} from '../../../components'; diff --git a/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx b/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx index 3d912276b3..a262afd813 100644 --- a/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx +++ b/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Bars, Xmark} from '@gravity-ui/icons'; diff --git a/src/navigation/components/MobileNavigation/MobileNavigation.tsx b/src/navigation/components/MobileNavigation/MobileNavigation.tsx index 6ed7609d61..8121f76fee 100644 --- a/src/navigation/components/MobileNavigation/MobileNavigation.tsx +++ b/src/navigation/components/MobileNavigation/MobileNavigation.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Portal} from '@gravity-ui/uikit'; diff --git a/src/navigation/components/Navigation/Navigation.tsx b/src/navigation/components/Navigation/Navigation.tsx index 6116f80ac5..7cbe96b59b 100644 --- a/src/navigation/components/Navigation/Navigation.tsx +++ b/src/navigation/components/Navigation/Navigation.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import OutsideClick from '../../../components/OutsideClick/OutsideClick'; diff --git a/src/navigation/components/NavigationItem/NavigationItem.tsx b/src/navigation/components/NavigationItem/NavigationItem.tsx index fb6db07fe1..d9e31af921 100644 --- a/src/navigation/components/NavigationItem/NavigationItem.tsx +++ b/src/navigation/components/NavigationItem/NavigationItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import omit from 'lodash/omit'; diff --git a/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx b/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx index a89f01f164..e493086408 100644 --- a/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx +++ b/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Image} from '../../../../../components'; diff --git a/src/navigation/components/NavigationItem/components/GithubButton/GithubButton.tsx b/src/navigation/components/NavigationItem/components/GithubButton/GithubButton.tsx index 7d3a5e3cff..0452671509 100644 --- a/src/navigation/components/NavigationItem/components/GithubButton/GithubButton.tsx +++ b/src/navigation/components/NavigationItem/components/GithubButton/GithubButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {NavigationGithubButton, NavigationGithubButtonIcon} from '../../../../../models'; diff --git a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx index 0eded43742..79ae8ce704 100644 --- a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import {Button, RouterLink} from '../../../../../components'; import {BlockIdContext} from '../../../../../context/blockIdContext'; import {ButtonProps} from '../../../../../models'; diff --git a/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx b/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx index e6d4813fb1..68682db865 100644 --- a/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ToggleArrow} from '../../../../../components'; diff --git a/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx b/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx index e47665c29a..50913927e3 100644 --- a/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {RouterLink} from '../../../../../components'; diff --git a/src/navigation/components/NavigationItem/hooks/useNavigationItemMap.ts b/src/navigation/components/NavigationItem/hooks/useNavigationItemMap.ts index da17c34e9e..769a8a416e 100644 --- a/src/navigation/components/NavigationItem/hooks/useNavigationItemMap.ts +++ b/src/navigation/components/NavigationItem/hooks/useNavigationItemMap.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import isEmpty from 'lodash/isEmpty'; diff --git a/src/navigation/components/NavigationListItem/NavigationListItem.tsx b/src/navigation/components/NavigationListItem/NavigationListItem.tsx index ff6fb05908..4bd9eb3d15 100644 --- a/src/navigation/components/NavigationListItem/NavigationListItem.tsx +++ b/src/navigation/components/NavigationListItem/NavigationListItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import {NavigationListItemProps} from '../../models'; import {getItemClickHandler} from '../../utils'; import NavigationItem from '../NavigationItem'; diff --git a/src/navigation/hooks/useActiveNavItem.ts b/src/navigation/hooks/useActiveNavItem.ts index ce4001338d..c9a9f02c04 100644 --- a/src/navigation/hooks/useActiveNavItem.ts +++ b/src/navigation/hooks/useActiveNavItem.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {NavigationItemModel} from '../../models'; diff --git a/src/navigation/hooks/useShowBorder.ts b/src/navigation/hooks/useShowBorder.ts index ad552e66dd..4685600963 100644 --- a/src/navigation/hooks/useShowBorder.ts +++ b/src/navigation/hooks/useShowBorder.ts @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import debounce from 'lodash/debounce'; diff --git a/src/sub-blocks/BackgroundCard/BackgroundCard.tsx b/src/sub-blocks/BackgroundCard/BackgroundCard.tsx index 209cd9b4a8..9dca9fa8ee 100644 --- a/src/sub-blocks/BackgroundCard/BackgroundCard.tsx +++ b/src/sub-blocks/BackgroundCard/BackgroundCard.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useUniqId} from '@gravity-ui/uikit'; import {BackgroundImage, CardBase} from '../../components/'; diff --git a/src/sub-blocks/BannerCard/BannerCard.tsx b/src/sub-blocks/BannerCard/BannerCard.tsx index 75da986611..4afa0ccb4b 100644 --- a/src/sub-blocks/BannerCard/BannerCard.tsx +++ b/src/sub-blocks/BannerCard/BannerCard.tsx @@ -1,3 +1,5 @@ +'use client'; + import {BackgroundImage, Button, RouterLink, YFMWrapper} from '../../components'; import {useTheme} from '../../context/theme'; import {BannerCardProps} from '../../models'; diff --git a/src/sub-blocks/BasicCard/BasicCard.tsx b/src/sub-blocks/BasicCard/BasicCard.tsx index 9125cb6dcc..619bd3ecfd 100644 --- a/src/sub-blocks/BasicCard/BasicCard.tsx +++ b/src/sub-blocks/BasicCard/BasicCard.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useUniqId} from '@gravity-ui/uikit'; import {CardBase, IconWrapper} from '../../components'; diff --git a/src/sub-blocks/Content/Content.tsx b/src/sub-blocks/Content/Content.tsx index 9b9238c24e..a0ef522bc2 100644 --- a/src/sub-blocks/Content/Content.tsx +++ b/src/sub-blocks/Content/Content.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useUniqId} from '@gravity-ui/uikit'; import {Buttons, ContentList, Links, Title, YFMWrapper} from '../../components'; diff --git a/src/sub-blocks/HubspotForm/HubspotFormContainer.tsx b/src/sub-blocks/HubspotForm/HubspotFormContainer.tsx index 084e46730f..32e5f9abf3 100644 --- a/src/sub-blocks/HubspotForm/HubspotFormContainer.tsx +++ b/src/sub-blocks/HubspotForm/HubspotFormContainer.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {useMount} from '../../hooks'; diff --git a/src/sub-blocks/HubspotForm/index.tsx b/src/sub-blocks/HubspotForm/index.tsx index 54a94e431c..51f3ef581c 100644 --- a/src/sub-blocks/HubspotForm/index.tsx +++ b/src/sub-blocks/HubspotForm/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {MobileContext} from '../../context/mobileContext'; diff --git a/src/sub-blocks/HubspotForm/loadHubspotScript.ts b/src/sub-blocks/HubspotForm/loadHubspotScript.ts index 3b45f743c1..c522766512 100644 --- a/src/sub-blocks/HubspotForm/loadHubspotScript.ts +++ b/src/sub-blocks/HubspotForm/loadHubspotScript.ts @@ -1,3 +1,5 @@ +'use client'; + let scriptPromise: Promise; const loadHubspotScript = async () => { diff --git a/src/sub-blocks/ImageCard/ImageCard.tsx b/src/sub-blocks/ImageCard/ImageCard.tsx index e47285f181..7b51057fef 100644 --- a/src/sub-blocks/ImageCard/ImageCard.tsx +++ b/src/sub-blocks/ImageCard/ImageCard.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Link, useUniqId} from '@gravity-ui/uikit'; diff --git a/src/sub-blocks/LayoutItem/LayoutItem.tsx b/src/sub-blocks/LayoutItem/LayoutItem.tsx index 8bd78976a7..0295e7adce 100644 --- a/src/sub-blocks/LayoutItem/LayoutItem.tsx +++ b/src/sub-blocks/LayoutItem/LayoutItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {useUniqId} from '@gravity-ui/uikit'; diff --git a/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx b/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx index 787a3668ce..42ca8ad2d3 100644 --- a/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx +++ b/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import chunk from 'lodash/chunk'; diff --git a/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx b/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx index 5e56fbf63a..2a5a1a408b 100644 --- a/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Label, LabelProps} from '@gravity-ui/uikit'; diff --git a/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx b/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx index dac4d2a6e6..de1fa81100 100644 --- a/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Foldable, ToggleArrow} from '../../../components'; diff --git a/src/sub-blocks/Quote/Quote.tsx b/src/sub-blocks/Quote/Quote.tsx index 38a65cfeb2..3149981f32 100644 --- a/src/sub-blocks/Quote/Quote.tsx +++ b/src/sub-blocks/Quote/Quote.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Author, Button, Image, YFMWrapper} from '../../components'; diff --git a/src/utils/icons.ts b/src/utils/icons.ts index cbc1112f4a..5467a8369f 100644 --- a/src/utils/icons.ts +++ b/src/utils/icons.ts @@ -1,5 +1,5 @@ import * as GravityIcons from '@gravity-ui/icons'; -import {IconData} from '@gravity-ui/uikit'; +import type {IconData} from '@gravity-ui/uikit'; export function getGravityIcon(iconName: string): IconData | undefined { if (hasGravityIcon(iconName)) { From 32b6283ede52e601c4f87117d4276fa7c07ae7c3 Mon Sep 17 00:00:00 2001 From: Krotov Petr <83280920+KrotovPetr@users.noreply.github.com> Date: Sat, 11 Jul 2026 15:04:23 +0300 Subject: [PATCH 2/5] feat: add some components to RSC --- src/blocks/Banner/Banner.tsx | 2 +- src/blocks/CardLayout/CardLayout.tsx | 6 ++--- src/blocks/Companies/Companies.tsx | 5 ++-- src/blocks/ContentLayout/ContentLayout.tsx | 5 ++-- .../ExtendedFeatures/ExtendedFeatures.tsx | 8 +++--- src/blocks/FilterBlock/FilterBlock.tsx | 5 ++-- src/blocks/FoldableList/FoldableList.tsx | 2 +- .../FoldableListBlockItem.tsx | 4 ++- src/blocks/Footer/Footer.tsx | 5 ++-- .../CopyrightFloor/CopyrightFloor.tsx | 6 ++++- .../DisclaimerFloor/DisclaimerFloor.tsx | 2 +- .../components/SocialFloor/SocialFloor.tsx | 4 +-- src/blocks/Footer/hooks/useLogoImageProps.ts | 26 +++++++------------ src/blocks/Form/Form.tsx | 5 ++-- src/blocks/Header/Header.tsx | 4 ++- src/blocks/HeaderSlider/HeaderSlider.tsx | 10 +++---- src/blocks/Hero/Hero.tsx | 6 +++-- src/blocks/Icons/Icons.tsx | 3 ++- src/blocks/Info/Info.tsx | 2 -- .../PromoFeaturesBlock/PromoFeaturesBlock.tsx | 2 -- .../QuestionBlockItem/QuestionBlockItem.tsx | 4 ++- src/blocks/Questions/Questions.tsx | 2 +- src/blocks/Share/Share.tsx | 2 +- src/blocks/Table/Table.tsx | 3 ++- src/blocks/Tabs/TabContent/TabContent.tsx | 3 ++- .../Tabs/TabsTextContent/TabsTextContent.tsx | 2 +- src/components/Author/Author.tsx | 2 +- .../BackgroundMedia/BackgroundMedia.tsx | 8 ++---- src/components/BrandFooter/BrandFooter.tsx | 2 -- src/components/ButtonTabs/ButtonTabs.tsx | 2 +- src/components/ContentIcon/ContentIcon.tsx | 2 -- .../FullscreenImage/FullscreenImage.tsx | 2 +- src/components/InnerForm/InnerForm.tsx | 4 +-- src/components/Media/DataLens/DataLens.tsx | 8 ++---- src/components/Media/Image/Image.tsx | 2 +- src/components/MediaBase/MediaBaseContent.tsx | 2 +- src/components/MetaInfo/MetaInfo.tsx | 2 +- .../OverflowScroller/OverflowScroller.tsx | 2 +- src/components/RootCn/index.tsx | 2 -- src/components/RouterLink/RouterLink.tsx | 6 ++--- src/components/Table/Table.tsx | 2 +- src/components/Title/TitleItem.tsx | 3 ++- src/components/YFMWrapper/YFMWrapper.tsx | 3 +-- src/context/localeContext/index.ts | 2 ++ src/context/localeContext/localeContext.ts | 10 ++++--- src/context/localeContext/serverLocale.ts | 24 +++++++++++++++++ src/context/localeContext/useLocale.ts | 15 +++++++++++ src/context/locationContext/index.ts | 2 ++ .../locationContext/locationContext.ts | 11 +++++--- src/context/locationContext/serverLocation.ts | 24 +++++++++++++++++ src/context/locationContext/useLocation.ts | 15 +++++++++++ src/context/mobileContext/MobileContext.ts | 8 +++--- src/context/mobileContext/index.ts | 2 ++ src/context/mobileContext/serverMobile.ts | 22 ++++++++++++++++ src/context/mobileContext/useMobile.ts | 15 +++++++++++ src/context/theme/ThemeContext.ts | 8 +++--- src/context/theme/index.ts | 1 + src/context/theme/serverTheme.ts | 25 ++++++++++++++++++ src/context/theme/useTheme.ts | 12 ++++++--- src/navigation/components/Logo/Logo.tsx | 8 +++--- .../MobileMenuButton/MobileMenuButton.tsx | 2 +- .../ContentWrapper/ContentWrapper.tsx | 2 +- .../NavigationButton/NavigationButton.tsx | 3 ++- .../NavigationDropdown/NavigationDropdown.tsx | 2 +- .../NavigationLink/NavigationLink.tsx | 2 +- .../components/SocialIcon/SocialIcon.tsx | 2 +- src/server-context.ts | 4 +++ .../BackgroundCard/BackgroundCard.tsx | 9 +++---- src/sub-blocks/BannerCard/BannerCard.tsx | 7 ++--- src/sub-blocks/BasicCard/BasicCard.tsx | 12 ++++----- src/sub-blocks/Content/Content.tsx | 6 ++++- src/sub-blocks/ImageCard/ImageCard.tsx | 2 +- src/sub-blocks/LayoutItem/LayoutItem.tsx | 15 +++++------ src/sub-blocks/MediaCard/MediaCard.tsx | 3 ++- src/sub-blocks/PriceCard/PriceCard.tsx | 7 ++++- .../CombinedPriceDetailed.tsx | 2 +- .../PriceDescription/PriceDescription.tsx | 2 +- .../PriceDetails/Details/List.tsx | 2 +- .../PriceDetails/Details/Settings.tsx | 2 +- .../PriceDetails/PriceDetails.tsx | 3 ++- .../SeparatePriceDetailed.tsx | 2 +- src/sub-blocks/Quote/Quote.tsx | 5 +++- 82 files changed, 320 insertions(+), 162 deletions(-) create mode 100644 src/context/localeContext/serverLocale.ts create mode 100644 src/context/localeContext/useLocale.ts create mode 100644 src/context/locationContext/serverLocation.ts create mode 100644 src/context/locationContext/useLocation.ts create mode 100644 src/context/mobileContext/serverMobile.ts create mode 100644 src/context/mobileContext/useMobile.ts create mode 100644 src/context/theme/serverTheme.ts create mode 100644 src/server-context.ts diff --git a/src/blocks/Banner/Banner.tsx b/src/blocks/Banner/Banner.tsx index 89a7906fc7..cd3b72a69f 100644 --- a/src/blocks/Banner/Banner.tsx +++ b/src/blocks/Banner/Banner.tsx @@ -1,6 +1,6 @@ import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import {BannerBlockProps} from '../../models'; -import {BannerCard} from '../../sub-blocks'; +import BannerCard from '../../sub-blocks/BannerCard/BannerCard'; import {block} from '../../utils'; import './Banner.scss'; diff --git a/src/blocks/CardLayout/CardLayout.tsx b/src/blocks/CardLayout/CardLayout.tsx index 9a007f86d0..ad9b6312ed 100644 --- a/src/blocks/CardLayout/CardLayout.tsx +++ b/src/blocks/CardLayout/CardLayout.tsx @@ -1,10 +1,10 @@ -'use client'; - import * as React from 'react'; import isEmpty from 'lodash/isEmpty'; -import {AnimateBlock, BackgroundImage, Title} from '../../components'; +import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; +import BackgroundImage from '../../components/BackgroundImage/BackgroundImage'; +import Title from '../../components/Title/Title'; import {useTheme} from '../../context/theme'; import {Col, GridColumnSizesType, GridJustifyContent, Row} from '../../grid'; import {CardLayoutBlockProps as CardLayoutBlockParams, ClassNameProps} from '../../models'; diff --git a/src/blocks/Companies/Companies.tsx b/src/blocks/Companies/Companies.tsx index 823efc4242..567039f52b 100644 --- a/src/blocks/Companies/Companies.tsx +++ b/src/blocks/Companies/Companies.tsx @@ -1,7 +1,6 @@ -'use client'; - -import {Image, Title} from '../../components'; import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; +import Image from '../../components/Image/Image'; +import Title from '../../components/Title/Title'; import {useTheme} from '../../context/theme'; import {CompaniesBlockProps} from '../../models'; import {block, getThemedValue} from '../../utils'; diff --git a/src/blocks/ContentLayout/ContentLayout.tsx b/src/blocks/ContentLayout/ContentLayout.tsx index 645850c2e9..13e8277c04 100644 --- a/src/blocks/ContentLayout/ContentLayout.tsx +++ b/src/blocks/ContentLayout/ContentLayout.tsx @@ -2,13 +2,14 @@ import * as React from 'react'; -import {BackgroundImage, FileLink} from '../../components'; +import BackgroundImage from '../../components/BackgroundImage/BackgroundImage'; +import FileLink from '../../components/FileLink/FileLink'; import {BREAKPOINTS} from '../../constants'; import {useTheme} from '../../context/theme'; import {useWindowWidth} from '../../context/windowWidthContext'; import {Col} from '../../grid'; import {ContentLayoutBlockProps, ContentSize, ContentTextSize} from '../../models'; -import {Content} from '../../sub-blocks'; +import Content from '../../sub-blocks/Content/Content'; import {block, getThemedValue} from '../../utils'; import './ContentLayout.scss'; diff --git a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx index 84f8b8e175..56c7df34cf 100644 --- a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx +++ b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx @@ -1,12 +1,12 @@ -'use client'; - -import {AnimateBlock, Title, YFMWrapper} from '../../components/'; +import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import Image from '../../components/Image/Image'; import {getMediaImage} from '../../components/Media/Image/utils'; +import Title from '../../components/Title/Title'; +import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {useTheme} from '../../context/theme'; import {Col, Row} from '../../grid'; import {ExtendedFeaturesProps} from '../../models'; -import {Content} from '../../sub-blocks'; +import Content from '../../sub-blocks/Content/Content'; import {block, getThemedValue} from '../../utils'; import './ExtendedFeatures.scss'; diff --git a/src/blocks/FilterBlock/FilterBlock.tsx b/src/blocks/FilterBlock/FilterBlock.tsx index 30f1d52a0d..41af837827 100644 --- a/src/blocks/FilterBlock/FilterBlock.tsx +++ b/src/blocks/FilterBlock/FilterBlock.tsx @@ -2,14 +2,15 @@ import * as React from 'react'; -import {CardLayoutBlock} from '..'; -import {AnimateBlock, Title} from '../../components'; +import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import ButtonTabs, {ButtonTabsItemProps} from '../../components/ButtonTabs/ButtonTabs'; +import Title from '../../components/Title/Title'; import {ConstructorItem} from '../../containers/PageConstructor/components/ConstructorItem'; import {Col, Row} from '../../grid'; import {useAnalytics} from '../../hooks'; import {FilterBlockProps, FilterItem} from '../../models'; import {block, getBlockKey} from '../../utils'; +import CardLayoutBlock from '../CardLayout/CardLayout'; import {i18n} from './i18n'; diff --git a/src/blocks/FoldableList/FoldableList.tsx b/src/blocks/FoldableList/FoldableList.tsx index 4f406347fa..e46a2b61e0 100644 --- a/src/blocks/FoldableList/FoldableList.tsx +++ b/src/blocks/FoldableList/FoldableList.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {Col, Row} from '../../grid'; import {FoldableListProps} from '../../models'; -import {Content} from '../../sub-blocks'; +import Content from '../../sub-blocks/Content/Content'; import {block} from '../../utils'; import {FoldableListBlockItem} from './FoldableListBlockItem/FoldableListBlockItem'; diff --git a/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx b/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx index d42913cb15..e4e1b517f7 100644 --- a/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx +++ b/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx @@ -2,8 +2,10 @@ import {useActionHandlers} from '@gravity-ui/uikit'; -import {Foldable, ToggleArrow, YFMWrapper} from '../../../components'; +import Foldable from '../../../components/Foldable/Foldable'; import Link from '../../../components/Link/Link'; +import ToggleArrow from '../../../components/ToggleArrow/ToggleArrow'; +import YFMWrapper from '../../../components/YFMWrapper/YFMWrapper'; import {FoldableListBlockItemProps} from '../../../models'; import {block} from '../../../utils'; diff --git a/src/blocks/Footer/Footer.tsx b/src/blocks/Footer/Footer.tsx index 60416b978a..5387974eea 100644 --- a/src/blocks/Footer/Footer.tsx +++ b/src/blocks/Footer/Footer.tsx @@ -1,10 +1,9 @@ -'use client'; - import * as React from 'react'; import {Link} from '@gravity-ui/uikit'; -import {BrandFooter, Image} from '../../components'; +import BrandFooter from '../../components/BrandFooter/BrandFooter'; +import Image from '../../components/Image/Image'; import {useTheme} from '../../context/theme'; import {Col, Grid, Row} from '../../grid'; import {ClassNameProps, FooterBlockProps} from '../../models'; diff --git a/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx b/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx index 1b116a12cc..00bace2091 100644 --- a/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx +++ b/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {DropdownMenu, DropdownMenuItem, Link, useResizeObserver} from '@gravity-ui/uikit'; -import {Image} from '../../../../components'; +import Image from '../../../../components/Image/Image'; import {BREAKPOINTS} from '../../../../constants'; import {SSRContext} from '../../../../context/ssrContext'; import {useTheme} from '../../../../context/theme'; @@ -196,6 +196,10 @@ export const CopyrightFloor = ({copyright}: CopyrightFloorProps) => { updateSideColumnWidth(); }, [updateSideColumnWidth]); + React.useEffect(() => { + updateFloorSize(); + }, [updateFloorSize]); + useResizeObserver({ ref: documentElementRef, onResize: updateFloorSize, diff --git a/src/blocks/Footer/components/DisclaimerFloor/DisclaimerFloor.tsx b/src/blocks/Footer/components/DisclaimerFloor/DisclaimerFloor.tsx index 102d0b24ff..1e7c4eb18e 100644 --- a/src/blocks/Footer/components/DisclaimerFloor/DisclaimerFloor.tsx +++ b/src/blocks/Footer/components/DisclaimerFloor/DisclaimerFloor.tsx @@ -1,4 +1,4 @@ -import {YFMWrapper} from '../../../../components'; +import YFMWrapper from '../../../../components/YFMWrapper/YFMWrapper'; import {Col} from '../../../../grid'; import {FooterBlockProps} from '../../../../models'; import {block} from '../../../../utils'; diff --git a/src/blocks/Footer/components/SocialFloor/SocialFloor.tsx b/src/blocks/Footer/components/SocialFloor/SocialFloor.tsx index 1f5c2724b4..202d81c342 100644 --- a/src/blocks/Footer/components/SocialFloor/SocialFloor.tsx +++ b/src/blocks/Footer/components/SocialFloor/SocialFloor.tsx @@ -1,8 +1,6 @@ -'use client'; - import {Flex, Link, Tooltip} from '@gravity-ui/uikit'; -import {Image} from '../../../../components'; +import Image from '../../../../components/Image/Image'; import {useTheme} from '../../../../context/theme'; import {Col} from '../../../../grid'; import type {FooterBlockProps, FooterContactItem} from '../../../../models'; diff --git a/src/blocks/Footer/hooks/useLogoImageProps.ts b/src/blocks/Footer/hooks/useLogoImageProps.ts index acd92f2cbe..1e17734a0a 100644 --- a/src/blocks/Footer/hooks/useLogoImageProps.ts +++ b/src/blocks/Footer/hooks/useLogoImageProps.ts @@ -1,25 +1,19 @@ -'use client'; - -import * as React from 'react'; - import {getMediaImage} from '../../../components/Media/Image/utils'; import type {ImageProps as ModelImageProps} from '../../../models'; export function useLogoImageProps( logoImage?: ModelImageProps | null, ): Record | null { - return React.useMemo(() => { - if (!logoImage) { - return null; - } + if (!logoImage) { + return null; + } - const resolved = getMediaImage(logoImage); - if (typeof resolved === 'string') { - return {src: resolved}; - } + const resolved = getMediaImage(logoImage); + if (typeof resolved === 'string') { + return {src: resolved}; + } - return resolved && typeof resolved === 'object' - ? (resolved as unknown as Record) - : null; - }, [logoImage]); + return resolved && typeof resolved === 'object' + ? (resolved as unknown as Record) + : null; } diff --git a/src/blocks/Form/Form.tsx b/src/blocks/Form/Form.tsx index 7e0902fb8d..b07d878600 100644 --- a/src/blocks/Form/Form.tsx +++ b/src/blocks/Form/Form.tsx @@ -2,8 +2,9 @@ import * as React from 'react'; -import {BackgroundImage, Title} from '../../components'; +import BackgroundImage from '../../components/BackgroundImage/BackgroundImage'; import InnerForm from '../../components/InnerForm/InnerForm'; +import Title from '../../components/Title/Title'; import {MobileContext} from '../../context/mobileContext'; import {useTheme} from '../../context/theme'; import {Col, Grid, GridAlignItems, GridColumnSize, Row} from '../../grid'; @@ -15,7 +16,7 @@ import { isHubspotDataForm, isYandexDataForm, } from '../../models'; -import {Content} from '../../sub-blocks'; +import Content from '../../sub-blocks/Content/Content'; import {block, getThemedValue} from '../../utils'; import {hasBackgroundCSS} from './utils'; diff --git a/src/blocks/Header/Header.tsx b/src/blocks/Header/Header.tsx index dc0cd089ef..9fbdadf673 100644 --- a/src/blocks/Header/Header.tsx +++ b/src/blocks/Header/Header.tsx @@ -5,9 +5,11 @@ import * as React from 'react'; import {useUniqId} from '@gravity-ui/uikit'; -import {Button, Media, RouterLink} from '../../components'; +import Button from '../../components/Button/Button'; import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs/HeaderBreadcrumbs'; import {getMediaImage} from '../../components/Media/Image/utils'; +import Media from '../../components/Media/Media'; +import RouterLink from '../../components/RouterLink/RouterLink'; import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {BREAKPOINTS} from '../../constants'; import {useTheme} from '../../context/theme'; diff --git a/src/blocks/HeaderSlider/HeaderSlider.tsx b/src/blocks/HeaderSlider/HeaderSlider.tsx index b45a066930..5948ea0981 100644 --- a/src/blocks/HeaderSlider/HeaderSlider.tsx +++ b/src/blocks/HeaderSlider/HeaderSlider.tsx @@ -1,9 +1,5 @@ -'use client'; - -import * as React from 'react'; - -import {SliderBlock} from '../../blocks'; -import {MobileContext} from '../../context/mobileContext'; +import SliderBlock from '../../blocks/Slider/Slider'; +import {useMobile} from '../../context/mobileContext'; import {HeaderSliderBlockProps, SliderType} from '../../models'; import {block} from '../../utils'; import Header from '../Header/Header'; @@ -13,7 +9,7 @@ import './HeaderSlider.scss'; const b = block('header-slider-block'); export const HeaderSliderBlock = ({items, arrows, ...props}: HeaderSliderBlockProps) => { - const isMobile = React.useContext(MobileContext); + const isMobile = useMobile(); const showArrows = isMobile ? false : arrows; return ( diff --git a/src/blocks/Hero/Hero.tsx b/src/blocks/Hero/Hero.tsx index 9bda58ff0a..e4e9d7fa95 100644 --- a/src/blocks/Hero/Hero.tsx +++ b/src/blocks/Hero/Hero.tsx @@ -2,13 +2,15 @@ import * as React from 'react'; -import {HeaderBreadcrumbs, Media, YFMWrapper} from '../../components'; +import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs/HeaderBreadcrumbs'; +import Media from '../../components/Media/Media'; +import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {BREAKPOINTS} from '../../constants'; import {useTheme} from '../../context/theme'; import {useWindowWidth} from '../../context/windowWidthContext'; import {Grid} from '../../grid'; import {ButtonProps, HeroBlockProps, Theme} from '../../models'; -import {Content} from '../../sub-blocks'; +import Content from '../../sub-blocks/Content/Content'; import {block, getQaAttrubutes, getThemedValue} from '../../utils'; import {useContainerAspectRatio} from './hooks'; diff --git a/src/blocks/Icons/Icons.tsx b/src/blocks/Icons/Icons.tsx index 30c0180dc1..93bad77ab8 100644 --- a/src/blocks/Icons/Icons.tsx +++ b/src/blocks/Icons/Icons.tsx @@ -2,7 +2,8 @@ import * as React from 'react'; -import {Image, Title} from '../../components'; +import Image from '../../components/Image/Image'; +import Title from '../../components/Title/Title'; import {LocationContext} from '../../context/locationContext'; import {useTheme} from '../../context/theme'; import {useAnalytics} from '../../hooks'; diff --git a/src/blocks/Info/Info.tsx b/src/blocks/Info/Info.tsx index f0b24a464a..fdca648b60 100644 --- a/src/blocks/Info/Info.tsx +++ b/src/blocks/Info/Info.tsx @@ -1,5 +1,3 @@ -'use client'; - import {useTheme} from '../../context/theme'; import {Col, Grid, Row} from '../../grid'; import {ContentTheme, InfoBlockProps, LinkTheme} from '../../models'; diff --git a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx index 98bfe6abaa..4c34e6a958 100644 --- a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx +++ b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx @@ -1,5 +1,3 @@ -'use client'; - import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import BalancedMasonry from '../../components/BalancedMasonry/BalancedMasonry'; import FullWidthBackground from '../../components/FullWidthBackground/FullWidthBackground'; diff --git a/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx b/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx index 2478a22569..9706e54d82 100644 --- a/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx +++ b/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx @@ -2,8 +2,10 @@ import {useActionHandlers} from '@gravity-ui/uikit'; -import {Foldable, ToggleArrow, YFMWrapper} from '../../../components'; +import Foldable from '../../../components/Foldable/Foldable'; import Link from '../../../components/Link/Link'; +import ToggleArrow from '../../../components/ToggleArrow/ToggleArrow'; +import YFMWrapper from '../../../components/YFMWrapper/YFMWrapper'; import {QuestionBlockItemProps} from '../../../models'; import {block} from '../../../utils'; diff --git a/src/blocks/Questions/Questions.tsx b/src/blocks/Questions/Questions.tsx index 2069b3058b..71b4fc0a8d 100644 --- a/src/blocks/Questions/Questions.tsx +++ b/src/blocks/Questions/Questions.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {Col, Row} from '../../grid'; import {QuestionsProps} from '../../models'; -import {Content} from '../../sub-blocks'; +import Content from '../../sub-blocks/Content/Content'; import {block} from '../../utils'; import {sanitizeMicrodata} from '../../utils/microdata'; diff --git a/src/blocks/Share/Share.tsx b/src/blocks/Share/Share.tsx index d908414730..a799fa3fa6 100644 --- a/src/blocks/Share/Share.tsx +++ b/src/blocks/Share/Share.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {Button, Icon} from '@gravity-ui/uikit'; -import {YFMWrapper} from '../../components'; +import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {LocationContext} from '../../context/locationContext'; import {useAnalytics} from '../../hooks'; import {Facebook} from '../../icons/Facebook'; diff --git a/src/blocks/Table/Table.tsx b/src/blocks/Table/Table.tsx index acd9e77d77..80fd45fb0e 100644 --- a/src/blocks/Table/Table.tsx +++ b/src/blocks/Table/Table.tsx @@ -2,7 +2,8 @@ import * as React from 'react'; -import {Table, YFMWrapper} from '../../components'; +import Table from '../../components/Table/Table'; +import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {Col, Grid, GridColumnSize, Row} from '../../grid'; import {TableBlockProps} from '../../models'; import {block} from '../../utils'; diff --git a/src/blocks/Tabs/TabContent/TabContent.tsx b/src/blocks/Tabs/TabContent/TabContent.tsx index 624fceb83a..7e13dd88ba 100644 --- a/src/blocks/Tabs/TabContent/TabContent.tsx +++ b/src/blocks/Tabs/TabContent/TabContent.tsx @@ -4,10 +4,11 @@ import * as React from 'react'; import {useUniqId} from '@gravity-ui/uikit'; -import {FullscreenImage, YFMWrapper} from '../../../components'; +import FullscreenImage from '../../../components/FullscreenImage/FullscreenImage'; import {getMediaImage} from '../../../components/Media/Image/utils'; import Media from '../../../components/Media/Media'; import {getHeight} from '../../../components/VideoBlock/VideoBlock'; +import YFMWrapper from '../../../components/YFMWrapper/YFMWrapper'; import {ProjectSettingsContext} from '../../../context/projectSettingsContext'; import {useTheme} from '../../../context/theme'; import {Col, GridColumnOrderClasses, Row} from '../../../grid'; diff --git a/src/blocks/Tabs/TabsTextContent/TabsTextContent.tsx b/src/blocks/Tabs/TabsTextContent/TabsTextContent.tsx index 33850e05b5..442da34a75 100644 --- a/src/blocks/Tabs/TabsTextContent/TabsTextContent.tsx +++ b/src/blocks/Tabs/TabsTextContent/TabsTextContent.tsx @@ -1,6 +1,6 @@ import {Col} from '../../../grid'; import {ImageDeviceProps, ImageObjectProps, TabsBlockItem, TabsBlockProps} from '../../../models'; -import {Content} from '../../../sub-blocks'; +import Content from '../../../sub-blocks/Content/Content'; import {block} from '../../../utils'; import './TabsTextContent.scss'; diff --git a/src/components/Author/Author.tsx b/src/components/Author/Author.tsx index 4d0ca4e3a4..5cbe201f6d 100644 --- a/src/components/Author/Author.tsx +++ b/src/components/Author/Author.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import {AuthorProps, AuthorType, ImageProps} from '../../models'; import {block} from '../../utils'; +import Image from '../Image/Image'; import {getMediaImage} from '../Media/Image/utils'; -import {Image} from '../index'; import './Author.scss'; diff --git a/src/components/BackgroundMedia/BackgroundMedia.tsx b/src/components/BackgroundMedia/BackgroundMedia.tsx index 80e08461bc..d74d0ed2f1 100644 --- a/src/components/BackgroundMedia/BackgroundMedia.tsx +++ b/src/components/BackgroundMedia/BackgroundMedia.tsx @@ -1,8 +1,4 @@ -'use client'; - -import * as React from 'react'; - -import {MobileContext} from '../../context/mobileContext'; +import {useMobile} from '../../context/mobileContext'; import {BackgroundMediaProps} from '../../models'; import {block, getQaAttrubutes} from '../../utils'; import AnimateBlock from '../AnimateBlock/AnimateBlock'; @@ -23,7 +19,7 @@ const BackgroundMedia = ({ qa, ...props }: BackgroundMediaProps) => { - const isMobile = React.useContext(MobileContext); + const isMobile = useMobile(); const qaAttributes = getQaAttrubutes(qa, 'media'); return ( diff --git a/src/components/BrandFooter/BrandFooter.tsx b/src/components/BrandFooter/BrandFooter.tsx index 0aac0fe332..6f6120c50c 100644 --- a/src/components/BrandFooter/BrandFooter.tsx +++ b/src/components/BrandFooter/BrandFooter.tsx @@ -1,5 +1,3 @@ -'use client'; - import {Link} from '@gravity-ui/uikit'; import {useTheme} from '../../context/theme'; diff --git a/src/components/ButtonTabs/ButtonTabs.tsx b/src/components/ButtonTabs/ButtonTabs.tsx index f2272b7334..218398738f 100644 --- a/src/components/ButtonTabs/ButtonTabs.tsx +++ b/src/components/ButtonTabs/ButtonTabs.tsx @@ -6,7 +6,7 @@ import {ButtonSize} from '@gravity-ui/uikit'; import {AnalyticsEventsProp, ButtonProps, QAProps} from '../../models'; import {block} from '../../utils'; -import {Button} from '../index'; +import Button from '../Button/Button'; import './ButtonTabs.scss'; diff --git a/src/components/ContentIcon/ContentIcon.tsx b/src/components/ContentIcon/ContentIcon.tsx index e6afbfc60b..8700991f66 100644 --- a/src/components/ContentIcon/ContentIcon.tsx +++ b/src/components/ContentIcon/ContentIcon.tsx @@ -1,5 +1,3 @@ -'use client'; - import {useTheme} from '../../context/theme'; import {ClassNameProps, GravityIconProps, ImageProps, QAProps, SVGIcon} from '../../models'; import {ThemeSupporting, getThemedValue} from '../../utils'; diff --git a/src/components/FullscreenImage/FullscreenImage.tsx b/src/components/FullscreenImage/FullscreenImage.tsx index 51a36bd14a..a30f66444f 100644 --- a/src/components/FullscreenImage/FullscreenImage.tsx +++ b/src/components/FullscreenImage/FullscreenImage.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import {ChevronsExpandUpRight, Xmark} from '@gravity-ui/icons'; import {Icon, Modal} from '@gravity-ui/uikit'; -import {SliderBlock} from '../../blocks'; +import SliderBlock from '../../blocks/Slider/Slider'; import {ImageProps as ModelImageProps, SliderType} from '../../models'; import {block} from '../../utils'; import Image, {ImageProps} from '../Image/Image'; diff --git a/src/components/InnerForm/InnerForm.tsx b/src/components/InnerForm/InnerForm.tsx index e6b7afee86..c04a113daf 100644 --- a/src/components/InnerForm/InnerForm.tsx +++ b/src/components/InnerForm/InnerForm.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; -import {YandexForm} from '..'; import { FormsContext, HubspotFormsContextProps, @@ -10,8 +9,9 @@ import { } from '../../context/formsContext/FormsContext'; import {useTheme} from '../../context/theme'; import {FormBlockData, isHubspotDataForm, isYandexDataForm} from '../../models'; -import {HubspotForm} from '../../sub-blocks'; +import HubspotForm from '../../sub-blocks/HubspotForm'; import {getThemedValue} from '../../utils'; +import YandexForm from '../YandexForm/YandexForm'; interface InnerFormProps { formData: FormBlockData; diff --git a/src/components/Media/DataLens/DataLens.tsx b/src/components/Media/DataLens/DataLens.tsx index fbd9bc36ed..3592aed501 100644 --- a/src/components/Media/DataLens/DataLens.tsx +++ b/src/components/Media/DataLens/DataLens.tsx @@ -1,8 +1,4 @@ -'use client'; - -import * as React from 'react'; - -import {LocaleContext} from '../../../context/localeContext'; +import {useLocale} from '../../../context/localeContext'; import {MediaComponentDataLensProps} from '../../../models'; import {block} from '../../../utils'; @@ -16,7 +12,7 @@ const b = block('media-component-data-lens'); const DataLens = (props: MediaComponentDataLensProps) => { const {dataLens} = props; const dataLensData = unifyDataLensToObject(dataLens); - const locale = React.useContext(LocaleContext); + const locale = useLocale(); return dataLens ? (
diff --git a/src/components/Media/Image/Image.tsx b/src/components/Media/Image/Image.tsx index 1f1b64bd94..f6417adfc6 100644 --- a/src/components/Media/Image/Image.tsx +++ b/src/components/Media/Image/Image.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import {Interpolation, animated, config, useSpring} from '@react-spring/web'; import debounce from 'lodash/debounce'; -import {SliderBlock} from '../../../blocks'; +import SliderBlock from '../../../blocks/Slider/Slider'; import {ImageProps, MediaComponentImageProps, QAProps, SliderType} from '../../../models'; import {block, getQaAttrubutes} from '../../../utils'; import BackgroundImage from '../../BackgroundImage/BackgroundImage'; diff --git a/src/components/MediaBase/MediaBaseContent.tsx b/src/components/MediaBase/MediaBaseContent.tsx index 67598c3d5b..3e4b39de5b 100644 --- a/src/components/MediaBase/MediaBaseContent.tsx +++ b/src/components/MediaBase/MediaBaseContent.tsx @@ -1,7 +1,7 @@ import {ButtonSize} from '@gravity-ui/uikit'; import {MediaContentProps} from '../../models'; -import {Content} from '../../sub-blocks'; +import Content from '../../sub-blocks/Content/Content'; import {block} from '../../utils'; import './MediaBaseContent.scss'; diff --git a/src/components/MetaInfo/MetaInfo.tsx b/src/components/MetaInfo/MetaInfo.tsx index 658cee7866..2692890399 100644 --- a/src/components/MetaInfo/MetaInfo.tsx +++ b/src/components/MetaInfo/MetaInfo.tsx @@ -1,6 +1,6 @@ import {ClassNameProps} from '../../models'; import {block} from '../../utils'; -import {YFMWrapper} from '../index'; +import YFMWrapper from '../YFMWrapper/YFMWrapper'; import './MetaInfo.scss'; diff --git a/src/components/OverflowScroller/OverflowScroller.tsx b/src/components/OverflowScroller/OverflowScroller.tsx index 490b0f58b2..3de303e150 100644 --- a/src/components/OverflowScroller/OverflowScroller.tsx +++ b/src/components/OverflowScroller/OverflowScroller.tsx @@ -4,8 +4,8 @@ import * as React from 'react'; import debounce from 'lodash/debounce'; -import {ToggleArrow} from '..'; import {block} from '../../utils'; +import ToggleArrow from '../ToggleArrow/ToggleArrow'; import {i18n} from './i18n'; diff --git a/src/components/RootCn/index.tsx b/src/components/RootCn/index.tsx index 60f7ee5f4d..fa923b6365 100644 --- a/src/components/RootCn/index.tsx +++ b/src/components/RootCn/index.tsx @@ -1,5 +1,3 @@ -'use client'; - import * as React from 'react'; import {useTheme} from '../../context/theme'; diff --git a/src/components/RouterLink/RouterLink.tsx b/src/components/RouterLink/RouterLink.tsx index 63a2716600..fc9b783e46 100644 --- a/src/components/RouterLink/RouterLink.tsx +++ b/src/components/RouterLink/RouterLink.tsx @@ -1,8 +1,6 @@ -'use client'; - import * as React from 'react'; -import {LocationContext} from '../../context/locationContext'; +import {useLocation} from '../../context/locationContext'; export interface RouterLinkProps { href: string; @@ -10,7 +8,7 @@ export interface RouterLinkProps { } const RouterLink = ({href, children}: React.PropsWithChildren) => { - const {Link} = React.useContext(LocationContext); + const {Link} = useLocation(); return Link ? {children} : {children}; }; diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 740a85d55e..3a5cd97678 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -5,9 +5,9 @@ import * as React from 'react'; import {Check, Minus} from '@gravity-ui/icons'; import {Icon} from '@gravity-ui/uikit'; -import {YFMWrapper} from '../'; import {ClassNameProps, Justify, LegendTableMarkerType, TableProps} from '../../models'; import {block} from '../../utils'; +import YFMWrapper from '../YFMWrapper/YFMWrapper'; import './Table.scss'; diff --git a/src/components/Title/TitleItem.tsx b/src/components/Title/TitleItem.tsx index 10506f3008..162c63db23 100644 --- a/src/components/Title/TitleItem.tsx +++ b/src/components/Title/TitleItem.tsx @@ -2,13 +2,14 @@ import * as React from 'react'; -import {ToggleArrow, YFMWrapper} from '../'; import {LocationContext} from '../../context/locationContext'; import {MobileContext} from '../../context/mobileContext'; import {useAnalytics} from '../../hooks'; import {AnalyticsEventsBase, QAProps, TextSize, TitleItemProps} from '../../models'; import {block, getHeaderTag, getLinkProps} from '../../utils'; import Anchor from '../Anchor/Anchor'; +import ToggleArrow from '../ToggleArrow/ToggleArrow'; +import YFMWrapper from '../YFMWrapper/YFMWrapper'; import './TitleItem.scss'; diff --git a/src/components/YFMWrapper/YFMWrapper.tsx b/src/components/YFMWrapper/YFMWrapper.tsx index 1d7f3cce96..22ba885087 100644 --- a/src/components/YFMWrapper/YFMWrapper.tsx +++ b/src/components/YFMWrapper/YFMWrapper.tsx @@ -4,11 +4,10 @@ import * as React from 'react'; import toSnakeCase from 'snakecase-keys'; -import {HTML} from '../../components'; import {ClassNameProps, Modifiers} from '../../models'; import {QAProps} from '../../models/common'; import {cn} from '../../utils'; -import {HTMLExtraProps} from '../HTML/HTML'; +import HTML, {HTMLExtraProps} from '../HTML/HTML'; const yfm = cn('yfm'); diff --git a/src/context/localeContext/index.ts b/src/context/localeContext/index.ts index c80490a205..ebfd0af8af 100644 --- a/src/context/localeContext/index.ts +++ b/src/context/localeContext/index.ts @@ -1 +1,3 @@ export * from './localeContext'; +export * from './useLocale'; +export * from './serverLocale'; diff --git a/src/context/localeContext/localeContext.ts b/src/context/localeContext/localeContext.ts index 3905fba01c..8e7ee91d62 100644 --- a/src/context/localeContext/localeContext.ts +++ b/src/context/localeContext/localeContext.ts @@ -1,5 +1,3 @@ -'use client'; - import * as React from 'react'; import {Lang} from '@gravity-ui/uikit'; @@ -9,4 +7,10 @@ export type LocaleContextProps = { tld?: string; }; -export const LocaleContext = React.createContext({lang: Lang.Ru, tld: '.ru'}); +export const initialLocale: LocaleContextProps = {lang: Lang.Ru, tld: '.ru'}; + +const isRscServer = typeof React.createContext !== 'function'; + +export const LocaleContext = ( + isRscServer ? null : React.createContext(initialLocale) +) as React.Context; diff --git a/src/context/localeContext/serverLocale.ts b/src/context/localeContext/serverLocale.ts new file mode 100644 index 0000000000..d763721913 --- /dev/null +++ b/src/context/localeContext/serverLocale.ts @@ -0,0 +1,24 @@ +import * as React from 'react'; + +import {LocaleContextProps, initialLocale} from './localeContext'; + +type Store = {current: LocaleContextProps}; + +const createStore = (): (() => Store) => { + const cache = (React as unknown as {cache?: (fn: () => T) => () => T}).cache; + if (typeof cache === 'function') { + return cache(() => ({current: initialLocale})); + } + const singleton: Store = {current: initialLocale}; + return () => singleton; +}; + +const store = createStore(); + +export function getServerLocale(): LocaleContextProps { + return store().current; +} + +export function setServerLocale(locale: LocaleContextProps): void { + store().current = locale; +} diff --git a/src/context/localeContext/useLocale.ts b/src/context/localeContext/useLocale.ts new file mode 100644 index 0000000000..972e8da601 --- /dev/null +++ b/src/context/localeContext/useLocale.ts @@ -0,0 +1,15 @@ +import * as React from 'react'; + +import {LocaleContext, LocaleContextProps} from './localeContext'; +import {getServerLocale} from './serverLocale'; + +const isRscServer = typeof React.createContext !== 'function'; + +export function useLocale(): LocaleContextProps { + if (isRscServer) { + return getServerLocale(); + } + + // eslint-disable-next-line react-hooks/rules-of-hooks + return React.useContext(LocaleContext); +} diff --git a/src/context/locationContext/index.ts b/src/context/locationContext/index.ts index edb62d44d4..b2bdb6ab27 100644 --- a/src/context/locationContext/index.ts +++ b/src/context/locationContext/index.ts @@ -1 +1,3 @@ export * from './locationContext'; +export * from './useLocation'; +export * from './serverLocation'; diff --git a/src/context/locationContext/locationContext.ts b/src/context/locationContext/locationContext.ts index aafc897e56..40db96c2a3 100644 --- a/src/context/locationContext/locationContext.ts +++ b/src/context/locationContext/locationContext.ts @@ -1,5 +1,3 @@ -'use client'; - import * as React from 'react'; import {RouterLinkProps} from '../../components/RouterLink/RouterLink'; @@ -30,4 +28,11 @@ export type LocationContextProps = { asPath?: string; }; -export const LocationContext = React.createContext({}); +export const initialLocation: LocationContextProps = {}; + +// Изоморфный контекст (без 'use client') — см. context/theme/ThemeContext. +const isRscServer = typeof React.createContext !== 'function'; + +export const LocationContext = ( + isRscServer ? null : React.createContext(initialLocation) +) as React.Context; diff --git a/src/context/locationContext/serverLocation.ts b/src/context/locationContext/serverLocation.ts new file mode 100644 index 0000000000..bfed9a7105 --- /dev/null +++ b/src/context/locationContext/serverLocation.ts @@ -0,0 +1,24 @@ +import * as React from 'react'; + +import {LocationContextProps, initialLocation} from './locationContext'; + +type Store = {current: LocationContextProps}; + +const createStore = (): (() => Store) => { + const cache = (React as unknown as {cache?: (fn: () => T) => () => T}).cache; + if (typeof cache === 'function') { + return cache(() => ({current: initialLocation})); + } + const singleton: Store = {current: initialLocation}; + return () => singleton; +}; + +const store = createStore(); + +export function getServerLocation(): LocationContextProps { + return store().current; +} + +export function setServerLocation(location: LocationContextProps): void { + store().current = location; +} diff --git a/src/context/locationContext/useLocation.ts b/src/context/locationContext/useLocation.ts new file mode 100644 index 0000000000..3bba84156e --- /dev/null +++ b/src/context/locationContext/useLocation.ts @@ -0,0 +1,15 @@ +import * as React from 'react'; + +import {LocationContext, LocationContextProps} from './locationContext'; +import {getServerLocation} from './serverLocation'; + +const isRscServer = typeof React.createContext !== 'function'; + +export function useLocation(): LocationContextProps { + if (isRscServer) { + return getServerLocation(); + } + + // eslint-disable-next-line react-hooks/rules-of-hooks + return React.useContext(LocationContext); +} diff --git a/src/context/mobileContext/MobileContext.ts b/src/context/mobileContext/MobileContext.ts index ecc7bf129f..5028af5355 100644 --- a/src/context/mobileContext/MobileContext.ts +++ b/src/context/mobileContext/MobileContext.ts @@ -1,5 +1,7 @@ -'use client'; - import * as React from 'react'; -export const MobileContext = React.createContext(false); +const isRscServer = typeof React.createContext !== 'function'; + +export const MobileContext = ( + isRscServer ? null : React.createContext(false) +) as React.Context; diff --git a/src/context/mobileContext/index.ts b/src/context/mobileContext/index.ts index c9a921327a..d9b10abe46 100644 --- a/src/context/mobileContext/index.ts +++ b/src/context/mobileContext/index.ts @@ -1 +1,3 @@ export * from './MobileContext'; +export * from './useMobile'; +export * from './serverMobile'; diff --git a/src/context/mobileContext/serverMobile.ts b/src/context/mobileContext/serverMobile.ts new file mode 100644 index 0000000000..5c761ae61d --- /dev/null +++ b/src/context/mobileContext/serverMobile.ts @@ -0,0 +1,22 @@ +import * as React from 'react'; + +type Store = {current: boolean}; + +const createStore = (): (() => Store) => { + const cache = (React as unknown as {cache?: (fn: () => T) => () => T}).cache; + if (typeof cache === 'function') { + return cache(() => ({current: false})); + } + const singleton: Store = {current: false}; + return () => singleton; +}; + +const store = createStore(); + +export function getServerMobile(): boolean { + return store().current; +} + +export function setServerMobile(isMobile: boolean): void { + store().current = isMobile; +} diff --git a/src/context/mobileContext/useMobile.ts b/src/context/mobileContext/useMobile.ts new file mode 100644 index 0000000000..d551d357a8 --- /dev/null +++ b/src/context/mobileContext/useMobile.ts @@ -0,0 +1,15 @@ +import * as React from 'react'; + +import {MobileContext} from './MobileContext'; +import {getServerMobile} from './serverMobile'; + +const isRscServer = typeof React.createContext !== 'function'; + +export function useMobile(): boolean { + if (isRscServer) { + return getServerMobile(); + } + + // eslint-disable-next-line react-hooks/rules-of-hooks + return React.useContext(MobileContext); +} diff --git a/src/context/theme/ThemeContext.ts b/src/context/theme/ThemeContext.ts index 875136ebc1..54888659af 100644 --- a/src/context/theme/ThemeContext.ts +++ b/src/context/theme/ThemeContext.ts @@ -1,5 +1,3 @@ -'use client'; - import * as React from 'react'; import {DEFAULT_THEME} from '../../components/constants'; @@ -13,4 +11,8 @@ export const initialValue: ThemeContextProps = { theme: DEFAULT_THEME, }; -export const ThemeContext = React.createContext(initialValue); +const isRscServer = typeof React.createContext !== 'function'; + +export const ThemeContext = ( + isRscServer ? null : React.createContext(initialValue) +) as React.Context; diff --git a/src/context/theme/index.ts b/src/context/theme/index.ts index cb7dcc6635..40a954e48a 100644 --- a/src/context/theme/index.ts +++ b/src/context/theme/index.ts @@ -1,3 +1,4 @@ export * from './ThemeContext'; export * from './useTheme'; +export * from './serverTheme'; export * from './withTheme'; diff --git a/src/context/theme/serverTheme.ts b/src/context/theme/serverTheme.ts new file mode 100644 index 0000000000..89daff2da5 --- /dev/null +++ b/src/context/theme/serverTheme.ts @@ -0,0 +1,25 @@ +import * as React from 'react'; + +import {DEFAULT_THEME} from '../../components/constants'; +import type {Theme} from '../../models'; + +type ThemeStore = {current: Theme}; + +const createStore = (): (() => ThemeStore) => { + const cache = (React as unknown as {cache?: (fn: () => T) => () => T}).cache; + if (typeof cache === 'function') { + return cache(() => ({current: DEFAULT_THEME})); + } + const singleton: ThemeStore = {current: DEFAULT_THEME}; + return () => singleton; +}; + +const store = createStore(); + +export function getServerTheme(): Theme { + return store().current; +} + +export function setServerTheme(theme: Theme): void { + store().current = theme; +} diff --git a/src/context/theme/useTheme.ts b/src/context/theme/useTheme.ts index f599871174..fe56266bc1 100644 --- a/src/context/theme/useTheme.ts +++ b/src/context/theme/useTheme.ts @@ -1,11 +1,15 @@ -'use client'; - import * as React from 'react'; import {ThemeContext, ThemeContextProps} from './ThemeContext'; +import {getServerTheme} from './serverTheme'; + +const isRscServer = typeof React.createContext !== 'function'; export function useTheme(): ThemeContextProps['theme'] { - const {theme} = React.useContext(ThemeContext); + if (isRscServer) { + return getServerTheme(); + } - return theme; + // eslint-disable-next-line react-hooks/rules-of-hooks + return React.useContext(ThemeContext).theme; } diff --git a/src/navigation/components/Logo/Logo.tsx b/src/navigation/components/Logo/Logo.tsx index 618de85263..b1e6ffbc4a 100644 --- a/src/navigation/components/Logo/Logo.tsx +++ b/src/navigation/components/Logo/Logo.tsx @@ -1,11 +1,9 @@ -'use client'; - import * as React from 'react'; -import {Image} from '../../../components'; +import Image from '../../../components/Image/Image'; import {getMediaImage} from '../../../components/Media/Image/utils'; import RouterLink from '../../../components/RouterLink/RouterLink'; -import {LocationContext} from '../../../context/locationContext'; +import {useLocation} from '../../../context/locationContext'; import {useTheme} from '../../../context/theme'; import {ThemedNavigationLogoData} from '../../../models'; import {block, getLinkProps, getThemedValue} from '../../../utils'; @@ -23,7 +21,7 @@ export type LogoProps = ThemedNavigationLogoData & { export const Logo = ({alt = i18n('image-alt'), ...restProps}: LogoProps) => { const props: LogoProps = {...restProps, alt}; - const {hostname, Link} = React.useContext(LocationContext); + const {hostname, Link} = useLocation(); const theme = useTheme(); const themedLogoProps = getThemedValue(props, theme) || props; const imageData = getMediaImage(themedLogoProps.icon || props.icon); diff --git a/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx b/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx index a262afd813..9ce86e30ce 100644 --- a/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx +++ b/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {Bars, Xmark} from '@gravity-ui/icons'; -import {Control} from '../../../components'; +import Control from '../../../components/Control/Control'; import {block} from '../../../utils'; import {MobileMenuButtonProps} from '../../models'; diff --git a/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx b/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx index e493086408..f0a2cdc104 100644 --- a/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx +++ b/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; -import {Image} from '../../../../../components'; +import Image from '../../../../../components/Image/Image'; import {ImageProps} from '../../../../../models'; import {block} from '../../../../../utils'; diff --git a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx index 79ae8ce704..3341b86542 100644 --- a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx @@ -1,6 +1,7 @@ 'use client'; -import {Button, RouterLink} from '../../../../../components'; +import Button from '../../../../../components/Button/Button'; +import RouterLink from '../../../../../components/RouterLink/RouterLink'; import {BlockIdContext} from '../../../../../context/blockIdContext'; import {ButtonProps} from '../../../../../models'; import {block} from '../../../../../utils'; diff --git a/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx b/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx index 68682db865..cda699567e 100644 --- a/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; -import {ToggleArrow} from '../../../../../components'; import {getMediaImage} from '../../../../../components/Media/Image/utils'; +import ToggleArrow from '../../../../../components/ToggleArrow/ToggleArrow'; import {NavigationDropdownItem} from '../../../../../models'; import {block} from '../../../../../utils'; import {NavigationItemProps} from '../../../../models'; diff --git a/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx b/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx index 50913927e3..78858d7ca4 100644 --- a/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; -import {RouterLink} from '../../../../../components'; import {getMediaImage} from '../../../../../components/Media/Image/utils'; +import RouterLink from '../../../../../components/RouterLink/RouterLink'; import {LocationContext} from '../../../../../context/locationContext'; import {useAnalytics} from '../../../../../hooks'; import {NavigationArrow} from '../../../../../icons'; diff --git a/src/navigation/components/SocialIcon/SocialIcon.tsx b/src/navigation/components/SocialIcon/SocialIcon.tsx index 8d39a99df1..f42bdc299c 100644 --- a/src/navigation/components/SocialIcon/SocialIcon.tsx +++ b/src/navigation/components/SocialIcon/SocialIcon.tsx @@ -1,4 +1,4 @@ -import {Image} from '../../../components'; +import Image from '../../../components/Image/Image'; import {getMediaImage} from '../../../components/Media/Image/utils'; import {NavigationSocialItem} from '../../../models'; import {block} from '../../../utils'; diff --git a/src/server-context.ts b/src/server-context.ts new file mode 100644 index 0000000000..b7e7ea8905 --- /dev/null +++ b/src/server-context.ts @@ -0,0 +1,4 @@ +export {getServerTheme, setServerTheme} from './context/theme/serverTheme'; +export {getServerMobile, setServerMobile} from './context/mobileContext/serverMobile'; +export {getServerLocale, setServerLocale} from './context/localeContext/serverLocale'; +export {getServerLocation, setServerLocation} from './context/locationContext/serverLocation'; diff --git a/src/sub-blocks/BackgroundCard/BackgroundCard.tsx b/src/sub-blocks/BackgroundCard/BackgroundCard.tsx index 9dca9fa8ee..35aab957fd 100644 --- a/src/sub-blocks/BackgroundCard/BackgroundCard.tsx +++ b/src/sub-blocks/BackgroundCard/BackgroundCard.tsx @@ -1,8 +1,5 @@ -'use client'; - -import {useUniqId} from '@gravity-ui/uikit'; - -import {BackgroundImage, CardBase} from '../../components/'; +import BackgroundImage from '../../components/BackgroundImage/BackgroundImage'; +import CardBase from '../../components/CardBase/CardBase'; import {useTheme} from '../../context/theme'; import {BackgroundCardProps} from '../../models'; import {block, getThemedValue} from '../../utils'; @@ -33,7 +30,7 @@ const BackgroundCard = (props: BackgroundCardProps) => { size = 's', } = props; - const titleId = useUniqId(); + const titleId = `pc-background-card-title-${typeof title === 'string' ? title : (title?.text ?? '')}`; const theme = useTheme(); const hasBackgroundColor = backgroundColor || cardTheme !== 'default'; diff --git a/src/sub-blocks/BannerCard/BannerCard.tsx b/src/sub-blocks/BannerCard/BannerCard.tsx index 4afa0ccb4b..22396887ab 100644 --- a/src/sub-blocks/BannerCard/BannerCard.tsx +++ b/src/sub-blocks/BannerCard/BannerCard.tsx @@ -1,6 +1,7 @@ -'use client'; - -import {BackgroundImage, Button, RouterLink, YFMWrapper} from '../../components'; +import BackgroundImage from '../../components/BackgroundImage/BackgroundImage'; +import Button from '../../components/Button/Button'; +import RouterLink from '../../components/RouterLink/RouterLink'; +import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {useTheme} from '../../context/theme'; import {BannerCardProps} from '../../models'; import {block, getThemedValue} from '../../utils'; diff --git a/src/sub-blocks/BasicCard/BasicCard.tsx b/src/sub-blocks/BasicCard/BasicCard.tsx index 619bd3ecfd..1172359690 100644 --- a/src/sub-blocks/BasicCard/BasicCard.tsx +++ b/src/sub-blocks/BasicCard/BasicCard.tsx @@ -1,8 +1,5 @@ -'use client'; - -import {useUniqId} from '@gravity-ui/uikit'; - -import {CardBase, IconWrapper} from '../../components'; +import CardBase from '../../components/CardBase/CardBase'; +import IconWrapper from '../../components/IconWrapper/IconWrapper'; import {useTheme} from '../../context/theme'; import {BasicCardProps} from '../../models'; import {IconPosition} from '../../models/constructor-items/sub-blocks'; @@ -30,8 +27,9 @@ const BasicCard = (props: BasicCardProps) => { hoverBackgroundColor, ...cardParams } = props; - const titleId = useUniqId(); - const descriptionId = useUniqId(); + const stableKey = typeof title === 'string' ? title : (title?.text ?? ''); + const titleId = `pc-basic-card-title-${stableKey}`; + const descriptionId = `pc-basic-card-desc-${stableKey}`; const areControlsInFooter = controlPosition === 'footer'; const theme = useTheme(); const themedIcon = getThemedValue(icon, theme); diff --git a/src/sub-blocks/Content/Content.tsx b/src/sub-blocks/Content/Content.tsx index a0ef522bc2..251bfd7265 100644 --- a/src/sub-blocks/Content/Content.tsx +++ b/src/sub-blocks/Content/Content.tsx @@ -2,7 +2,11 @@ import {useUniqId} from '@gravity-ui/uikit'; -import {Buttons, ContentList, Links, Title, YFMWrapper} from '../../components'; +import Buttons from '../../components/Buttons/Buttons'; +import ContentList from '../../components/ContentList/ContentList'; +import Links from '../../components/Links/Links'; +import Title from '../../components/Title/Title'; +import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {Col} from '../../grid'; import { ClassNameProps, diff --git a/src/sub-blocks/ImageCard/ImageCard.tsx b/src/sub-blocks/ImageCard/ImageCard.tsx index 7b51057fef..7761ea46ca 100644 --- a/src/sub-blocks/ImageCard/ImageCard.tsx +++ b/src/sub-blocks/ImageCard/ImageCard.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {Link, useUniqId} from '@gravity-ui/uikit'; -import {Image} from '../../components'; +import Image from '../../components/Image/Image'; import {getMediaImage} from '../../components/Media/Image/utils'; import {useTheme} from '../../context/theme'; import {GridColumnSizesType} from '../../grid'; diff --git a/src/sub-blocks/LayoutItem/LayoutItem.tsx b/src/sub-blocks/LayoutItem/LayoutItem.tsx index 0295e7adce..df1cb1bb40 100644 --- a/src/sub-blocks/LayoutItem/LayoutItem.tsx +++ b/src/sub-blocks/LayoutItem/LayoutItem.tsx @@ -1,10 +1,7 @@ -'use client'; - -import * as React from 'react'; - -import {useUniqId} from '@gravity-ui/uikit'; - -import {FullscreenMedia, IconWrapper, Media, MetaInfo} from '../../components'; +import FullscreenMedia from '../../components/FullscreenMedia/FullscreenMedia'; +import IconWrapper from '../../components/IconWrapper/IconWrapper'; +import Media from '../../components/Media/Media'; +import MetaInfo from '../../components/MetaInfo/MetaInfo'; import {useTheme} from '../../context/theme'; import {ContentBlockProps, LayoutItemProps} from '../../models'; import {block, getThemedValue} from '../../utils'; @@ -29,7 +26,7 @@ const LayoutItem = ({ analyticsEvents, controlPosition = 'content', }: LayoutItemProps) => { - const normalizedLinks = React.useMemo(() => getLayoutItemLinks(links), [links]); + const normalizedLinks = getLayoutItemLinks(links); const areControlsInFooter = controlPosition === 'footer'; const theme = useTheme(); const themedIcon = getThemedValue(icon, theme); @@ -41,7 +38,7 @@ const LayoutItem = ({ size: content.size || 's', colSizes: {all: 12, md: 12}, }; - const titleId = useUniqId(); + const titleId = `pc-layout-item-title-${typeof content.title === 'string' ? content.title : (content.title?.text ?? '')}`; const renderMedia = () => { if (!media) { return null; diff --git a/src/sub-blocks/MediaCard/MediaCard.tsx b/src/sub-blocks/MediaCard/MediaCard.tsx index e74bb3f14e..6fc58f1bfc 100644 --- a/src/sub-blocks/MediaCard/MediaCard.tsx +++ b/src/sub-blocks/MediaCard/MediaCard.tsx @@ -1,4 +1,5 @@ -import {CardBase, Media} from '../../components'; +import CardBase from '../../components/CardBase/CardBase'; +import Media from '../../components/Media/Media'; import {MediaCardProps} from '../../models'; import {block} from '../../utils'; diff --git a/src/sub-blocks/PriceCard/PriceCard.tsx b/src/sub-blocks/PriceCard/PriceCard.tsx index 5380800bfb..d5b220710c 100644 --- a/src/sub-blocks/PriceCard/PriceCard.tsx +++ b/src/sub-blocks/PriceCard/PriceCard.tsx @@ -1,6 +1,11 @@ import {Check} from '@gravity-ui/icons'; -import {BackgroundImage, Buttons, CardBase, ContentList, Links, YFMWrapper} from '../../components'; +import BackgroundImage from '../../components/BackgroundImage/BackgroundImage'; +import Buttons from '../../components/Buttons/Buttons'; +import CardBase from '../../components/CardBase/CardBase'; +import ContentList from '../../components/ContentList/ContentList'; +import Links from '../../components/Links/Links'; +import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {PriceCardProps} from '../../models'; import {block} from '../../utils'; diff --git a/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx b/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx index 42ca8ad2d3..148598cb81 100644 --- a/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx +++ b/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import chunk from 'lodash/chunk'; -import {CardBase} from '../../../components'; +import CardBase from '../../../components/CardBase/CardBase'; import {BREAKPOINTS} from '../../../constants'; import {Col, Grid, GridColumnSize, Row} from '../../../grid'; import { diff --git a/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx b/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx index 2a5a1a408b..49eb92a211 100644 --- a/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {Label, LabelProps} from '@gravity-ui/uikit'; -import {YFMWrapper} from '../../../components'; +import YFMWrapper from '../../../components/YFMWrapper/YFMWrapper'; import {StylesContext} from '../../../context/stylesContext'; import { PriceDescriptionColor, diff --git a/src/sub-blocks/PriceDetailed/PriceDetails/Details/List.tsx b/src/sub-blocks/PriceDetailed/PriceDetails/Details/List.tsx index dbd0e91581..c8fd22d77d 100644 --- a/src/sub-blocks/PriceDetailed/PriceDetails/Details/List.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDetails/Details/List.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import {YFMWrapper} from '../../../../components'; +import YFMWrapper from '../../../../components/YFMWrapper/YFMWrapper'; import {PriceDetailsListProps, TextSize} from '../../../../models'; import {block} from '../../../../utils'; diff --git a/src/sub-blocks/PriceDetailed/PriceDetails/Details/Settings.tsx b/src/sub-blocks/PriceDetailed/PriceDetails/Details/Settings.tsx index 88a5b44dfe..9fb027b41c 100644 --- a/src/sub-blocks/PriceDetailed/PriceDetails/Details/Settings.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDetails/Details/Settings.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import {YFMWrapper} from '../../../../components'; +import YFMWrapper from '../../../../components/YFMWrapper/YFMWrapper'; import {PriceDetailsSettingsProps, TextSize} from '../../../../models'; import {block} from '../../../../utils'; diff --git a/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx b/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx index de1fa81100..fee7bd0c89 100644 --- a/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx @@ -2,7 +2,8 @@ import * as React from 'react'; -import {Foldable, ToggleArrow} from '../../../components'; +import Foldable from '../../../components/Foldable/Foldable'; +import ToggleArrow from '../../../components/ToggleArrow/ToggleArrow'; import { PriceDetailsListProps, PriceDetailsProps, diff --git a/src/sub-blocks/PriceDetailed/SeparatePriceDetailed/SeparatePriceDetailed.tsx b/src/sub-blocks/PriceDetailed/SeparatePriceDetailed/SeparatePriceDetailed.tsx index 3611159baf..ae2cc3371f 100644 --- a/src/sub-blocks/PriceDetailed/SeparatePriceDetailed/SeparatePriceDetailed.tsx +++ b/src/sub-blocks/PriceDetailed/SeparatePriceDetailed/SeparatePriceDetailed.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import {CardBase} from '../../../components'; +import CardBase from '../../../components/CardBase/CardBase'; import { CardBorder, PriceDescriptionProps, diff --git a/src/sub-blocks/Quote/Quote.tsx b/src/sub-blocks/Quote/Quote.tsx index 3149981f32..0c09e35803 100644 --- a/src/sub-blocks/Quote/Quote.tsx +++ b/src/sub-blocks/Quote/Quote.tsx @@ -2,8 +2,11 @@ import * as React from 'react'; -import {Author, Button, Image, YFMWrapper} from '../../components'; +import Author from '../../components/Author/Author'; +import Button from '../../components/Button/Button'; +import Image from '../../components/Image/Image'; import {getMediaImage} from '../../components/Media/Image/utils'; +import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {useTheme} from '../../context/theme'; import {useAnalytics} from '../../hooks'; import {AuthorItem, AuthorType, DefaultEventNames, QuoteProps, QuoteType} from '../../models'; From 82978e4494c3e7ec6967948d2ace17285c38346f Mon Sep 17 00:00:00 2001 From: Krotov Petr <83280920+KrotovPetr@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:36:19 +0300 Subject: [PATCH 3/5] fix: remove comments --- .agents/docs/rsc-support-audit.md | 490 ------------------ package.json | 49 ++ .../locationContext/locationContext.ts | 1 - 3 files changed, 49 insertions(+), 491 deletions(-) delete mode 100644 .agents/docs/rsc-support-audit.md diff --git a/.agents/docs/rsc-support-audit.md b/.agents/docs/rsc-support-audit.md deleted file mode 100644 index d99b25efab..0000000000 --- a/.agents/docs/rsc-support-audit.md +++ /dev/null @@ -1,490 +0,0 @@ -# RSC support — аудит и спайк сборки (S1 + S2) - -> Подготовка `@gravity-ui/page-constructor` к работе с React Server Components (Next.js 14 App Router, React 18). -> Документ фиксирует результаты двух исследовательских задач: **S1** — классификация модулей (client vs server-safe), **S2** — проверка, что директива `'use client'` переживает сборку. - -## Контекст - -React 18 **как версия уже поддержан** — в `peerDependencies` стоит -`react: "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"`. - -Реальная задача — **совместимость с RSC**: сейчас в `src/` **ноль** директив `'use client'`, а библиотека построена на `createContext` (17 модулей), хуках (108 файлов) и обработчиках событий. Любой импорт `PageConstructor` в серверный компонент падает, потому что React по умолчанию считает каждый модуль без `'use client'` серверным. - -**Цель:** расставить границы `'use client'` так, чтобы серверный компонент мог импортировать `PageConstructor`/`PageConstructorProvider` «из коробки», при этом `/server` остаётся серверным, а `models`/`schema`/`utils` — импортируемыми из серверного кода. - ---- - -## S2 — Вердикт по сборке ✅ - -**`'use client'` переживает сборку без изменений в тулинге. Фикс не требуется.** - -TypeScript распознаёт ведущий строковый литерал `'use client';` как **directive prologue** и сохраняет его первой инструкцией модуля во всём выводе — даже поднимая выше авто-инжектнутого `react/jsx-runtime` import и выше `"use strict"` в CJS. Кастомные трансформеры (`transformScssImports`, `transformLocalModules`) и шаги `replace`/sourcemaps в gulp директиву не трогают. - -### Доказательства (первые строки артефактов) - -```js -// build/esm/context/theme/ThemeContext.js -'use client'; -import * as React from 'react'; -import {DEFAULT_THEME} from '../../components/constants.js'; -``` - -```js -// build/cjs/context/theme/ThemeContext.js -'use client'; -'use strict'; -Object.defineProperty(exports, '__esModule', {value: true}); -``` - -```js -// build/esm/components/Button/Button.js -// (директива остаётся выше авто-инжектнутого jsx-runtime import) -'use client'; -import {jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'; -import * as React from 'react'; -``` - -### Ключевые находки - -- **esm:** `'use client'` — строка 1, до всех импортов. ✅ -- **cjs:** `'use client'` — строка 1, выше инжектнутого `"use strict"` и всех `require()`. ✅ Это ровно то, что требует RSC. -- **esm vs cjs:** различие только в том, что cjs добавляет `"use strict"` второй директивой; в обоих `'use client'` остаётся первой. Для RSC разницы в поведении нет. - -### Команда и время - -- Быстрый путь: `npx gulp compile-to-esm compile-to-cjs` (только TS-компиляция, параллельно) — **~10 c**. -- Полный `npm run build:client` дополнительно делает clean, бандл swiper, копирование деклараций/json и sass — на `.js`-директиву не влияет. - -### Рекомендации при массовой расстановке - -1. Директива должна быть **голым строковым литералом на первой строке** (до любого импорта). Лицензионный коммент-баннер сверху не мешает (комментарии — не инструкции), но любой код/импорт до директивы её сломает. -2. «Ремни безопасности» не нужны, но при желании `createTypescriptProject` даёт хук `customTransformers` (уже используется для `before`/`afterDeclarations`) — туда можно добавить `before`-трансформер, инжектящий directive prologue. Эмпирически это избыточно. - ---- - -## S1 — Вердикт по классификации модулей - -Критерий: «использует ли **сам модуль** клиентскую фичу — хуки, `createContext`, ``, события, браузерные глобалы, интерактивные виджеты, классовые компоненты?». Модуль без таких фич импорт-безопасен из серверного компонента, даже если рендерит клиентских детей. - -Область: исключены `__tests__`, `__stories__`, `src/stories/`, `*.scss`, `src/widget/`. - -### Сводка - -| Вердикт | Кол-во (≈) | Где | -| -------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **CLIENT** (нужен `'use client'`) | ~190 | весь `src/context`, весь `src/hooks`, весь `src/editor`, `src/customization`, и ~119 из ~194 impl-файлов в `blocks`/`sub-blocks`/`components`/`containers`/`grid`/`navigation` | -| **SERVER-SAFE** (импорт без директивы) | ~110 | весь `src/models`, `src/schema`, `src/text-transform`, `src/server.ts`, `src/constants.ts`, весь `src/utils`, все `i18n`-кейсеты, все per-component `schema.ts`, плюс ~75 презентационных/барел/util-файлов | -| **BORDERLINE** (помечены) | 6 групп | `utils/common.ts`, `utils/hubspot.ts`, value-импорты uikit в `models`, корневые барелы `index.ts` + `constructor-items.ts`, side-effect импорты в `i18n` | - -### Разбор по директориям - -| Директория | Вердикт | Заметки | -| --------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `src/context/*` | **CLIENT** (без исключений) | каждый вызывает `createContext` и/или экспортит хуки; `theme/withTheme.tsx` — классовый компонент | -| `src/hooks/*` | **CLIENT** (blanket) | все зовут хуки/браузерные глобалы; `useMetrika.ts` и `index.ts` — ре-экспорты, но осмысленны только внутри клиента | -| `src/editor/*` | **CLIENT** (blanket) | Monaco, `@gravity-ui/dynamic-forms`, store, `ErrorBoundary` (class). Отдельный вход `./editor`, не для сервера | -| `src/models/*` | **SERVER-SAFE** (blanket) | чистые типы/энамы/guards. См. риск #2 про value-импорты uikit | -| `src/schema/*` | **SERVER-SAFE** (blanket) | чистый JSON Schema, включая каждый per-block/-component `schema.ts` | -| `src/text-transform/*` | **SERVER-SAFE** (blanket) | изоморфно: `@diplodoc/transform`, `sanitize-html`, `typograf`, `lodash` | -| `src/server.ts` | **SERVER-SAFE / server-only** | единственная строка `export * from './text-transform'`; держать серверным | -| `src/constants.ts` | **SERVER-SAFE** | плоские константы (`BREAKPOINTS` и т.п.) | -| `src/utils/*` | **SERVER-SAFE** | все импорт-безопасны; `common.ts` (`loadScript` трогает `document`) и `hubspot.ts` (`window`) — только внутри тел функций | -| `src/grid/*` | **в основном SERVER-SAFE** | `Grid`, `Col`, `Break`, `types`, `utils` — server-safe. **Исключение: `Row/Row.tsx` = CLIENT** (хуки) | -| `src/customization/BlockDecoration.tsx` | **CLIENT** | `React.useContext(InnerContext)` | -| `src/blocks/*` | **преимущественно CLIENT** | server-safe листья: `Banner/Banner.tsx`, `Map/Map.tsx`, `Tabs/TabsTextContent`, `Footer/.../DisclaimerFloor`, + чистые `*/utils.ts`, `*/models.ts`, `blocks/validators.ts`, барел | -| `src/sub-blocks/*` | **смешанно** | см. списки ниже | -| `src/components/*` | **смешанно** | см. списки ниже | -| `src/containers/*` | **CLIENT-ядро** | server-safe только `ConstructorRow/ConstructorRow.tsx` и барелы | -| `src/navigation/*` | **преимущественно CLIENT** | server-safe: `NavigationList`, `NavigationPopup`, `SocialIcon`, `Standalone`, `containers/Layout/Layout.tsx`, `utils.ts`, `models.ts`, `schema`, барелы | - -### Server-safe модули (можно импортировать в серверный компонент уже сегодня) - -- **Целые директории:** `src/models/**`, `src/schema/**` (вкл. каждый `schema.ts`), `src/text-transform/**`. -- **Отдельные файлы:** `src/server.ts`, `src/constants.ts`, весь `src/utils/**` (13 файлов), все `i18n/index.ts` кейсеты. -- **Презентационные листья** (импорт-безопасны, рендерят клиентских детей): - - grid: `Grid.tsx`, `Col.tsx`, `Break.tsx`; - - blocks: `Banner/Banner.tsx`, `Map/Map.tsx`, `Tabs/TabsTextContent/TabsTextContent.tsx`; - - sub-blocks: `Content`, `Divider`, `MediaCard`, `PriceCard`, `PriceDetailed` (+ `SeparatePriceDetailed`, `PriceDetails/Details/List`, `Settings`); - - components: `Anchor`, `Author`, `BackgroundImage`, `BlockBase`, `Buttons`, `ContentList`, `Icon`, `IconWrapper`, `Links`, `MetaInfo`, `Title/Title.tsx`, `ToggleArrow`, `UnpublishedLabel`, `YFMWrapper`, `Media/Iframe/Iframe.tsx`, `Media/FullscreenVideo/FullscreenVideo.tsx`, `MediaBase/MediaBaseContent.tsx`; - - containers: `.../ConstructorRow/ConstructorRow.tsx`; - - navigation: `NavigationList`, `NavigationPopup`, `SocialIcon`, `Standalone`, `containers/Layout/Layout.tsx`. - -> **Оговорка:** эти листья импорт-безопасны на сервере, но в рантайме почти всегда живут внутри клиентского поддерева `PageConstructor` и полагаются на провайдеры — на практике они рендерятся ниже клиентской границы. - -### Must be client (по зонам) - -- **Все контексты** — `src/context/**` (`createContext` + хуки). -- **Все хуки** — `src/hooks/**`. -- **Провайдер/движок** — `containers/PageConstructor/Provider.tsx`, `PageConstructor.tsx`, `ConstructorBlock/Blocks/Item/Loadable`, `Loadable/Loadable.tsx`. -- **Интерактивные виджеты** — `blocks/Slider` (Swiper), `blocks/SliderOld` (react-slick), `components/ReactPlayer/*`, `components/Media/Video`, `components/VideoBlock`, `components/Map/*` (+ `YMap/*`) и `context/mapsContext`, `sub-blocks/HubspotForm/*`, `components/YandexForm`, `components/InnerForm` (dynamic-forms), весь `src/editor/**` (Monaco). -- **Компоненты с хуками/событиями/браузерными API** — CLIENT-списки в `blocks`, `sub-blocks`, `components`, `navigation` + `customization/BlockDecoration.tsx`. -- **Классовые компоненты** — `context/theme/withTheme.tsx`, `components/Table/Table.tsx`, `components/OutsideClick`, `components/OverflowScroller`, `components/FullWidthBackground`, editor `ErrorBoundary`. -- **Рендерят `` (client, несмотря на отсутствие хуков)** — `containers/PageConstructor/Provider.tsx`, `containers/.../ConstructorItem.tsx`, `ConstructorLoadable.tsx`, `navigation/.../NavigationItem.tsx`, `navigation/.../NavigationButton/NavigationButton.tsx`, `grid/Row/Row.tsx`. - -#### Client-листья: sub-blocks - -`BackgroundCard`, `BannerCard`, `BasicCard`, `HubspotForm/*`, `ImageCard`, `LayoutItem/LayoutItem.tsx`, `Quote/Quote.tsx`, `PriceDetailed/CombinedPriceDetailed`, `PriceDetailed/PriceDescription`, `PriceDetailed/PriceDetails/PriceDetails.tsx`. - -#### Client-листья: components - -`AnimateBlock`, `BackLink`, `BackgroundMedia`, `BalancedMasonry`, `Button`, `ButtonTabs`, `DefaultVideo`, `FileLink`, `Foldable`, `FullWidthBackground` (class), `FullscreenImage`, `FullscreenMedia`, `HTML`, `HeaderBreadcrumbs`, `Image/Image.tsx`, `ImageBase`, `InnerForm`, `Link`, `Map/*` (GoogleMap, YandexMap, YMap), `Media/DataLens`, `Media/Media.tsx`, `Media/Image/Image.tsx`, `Media/Video/Video.tsx`, `MediaBase/MediaBase.tsx`, `OutsideClick` (class), `OverflowScroller` (class), `ReactPlayer/*`, `Table/Table.tsx` (class), `Title/TitleItem.tsx`, `VideoBlock`, `YandexForm`, `RouterLink`. - -### Риски / сюрпризы - -1. **Client-без-хуков легко пропустить.** `Provider.tsx`, `NavigationButton.tsx`, `ConstructorItem.tsx`, `grid/Row/Row.tsx` не используют хуки, но обязаны быть клиентскими, потому что `` нельзя создать в серверном компоненте. Grep «есть хук → client» их **пропустит** — нужен доп. критерий «рендерит `.Provider`». -2. **`models` тянут `@gravity-ui/uikit` через value-import.** `models/constructor-items/common.ts` и `blocks.ts` делают `import {ButtonView, IconData, ButtonProps, ButtonSize} from '@gravity-ui/uikit'` (не `import type`). Это только типы, но без `import type` бандлер может затащить клиентский uikit в server-safe граф `models`. **Фикс:** перевести на `import type`. (`constructor-items/common.ts` также делает `import * as icons from '@gravity-ui/icons'` — чистые данные, безопасно.) -3. **Корневые барелы смешанные.** `src/index.ts` ре-экспортит контексты/PageConstructor/blocks (client) вместе с `models`/`schema`/`utils`/`constants` (server-safe); `src/constructor-items.ts` мапит типы блоков на клиентские компоненты. Импорт «из корня» в серверном компоненте транзитивно тянет клиент. **Директиву на барелы не ставить;** отдельно решить — добавлять ли subpath-экспорты (`./models`, `./utils`), т.к. сейчас в `exports` их нет. -4. **`server.ts` чистый** — ре-экспортит только `text-transform` (изоморфно). Держать изолированным. -5. **`utils/common.ts` и `utils/hubspot.ts`** трогают `document`/`window` только внутри тел функций — импорт-безопасны, но по сути клиентские рантайм-хелперы. Не «повышать» их до общих серверных утилит. -6. **`i18n`-кейсеты выполняют side-effect при импорте** (`addComponentKeysets`). Изоморфно и безопасно, но регистрация исполняется и на сервере — это не нулевой по стоимости импорт типов. - ---- - -## Итог и следующий шаг - -- **Тулинг сборки готов** (S2): достаточно ставить `'use client';` первой строкой исходника. -- **Карта границ есть** (S1): помечаем директивой только настоящие клиентские модули (вкл. рендерящие `` без хуков), чистые слои оставляем server-safe. Это канонический RSC-паттерн и даёт «работает из коробки». -- **Перед массовой правкой** — решить две развилки: (а) гранулярность (листья vs под-барелы; рекомендуется листья); (б) добавлять ли subpath-экспорты для серверных модулей. Плюс точечный фикс `import type` в `models` (риск #2). - -Следующие задачи плана: **S3/S4** — расстановка `'use client'`; **S5** — editor + проверка чистоты `/server`; **S6** — eslint-правило + build-assert; **S7** — Next.js 14 пример; **S8** — доки/релиз. - ---- - -## План развития (S3–S9) - -Ниже — продолжение плана после исследовательских S1/S2. Стратегия по умолчанию: **точечная граница** (`'use client'` только на настоящих клиентских листьях + на модулях, рендерящих ``), барелы и чистые слои — без директивы. - -### Фазы - -| Фаза | Что | Статус | -| --------------------------- | ------------------------------------------------------------------------------------------- | --------------- | -| **0. Спайк сборки** | Доказать, что `'use client'` переживает gulp-компиляцию (esm+cjs) и остаётся первой строкой | ✅ сделано (S2) | -| **1. Карта границ** | Классифицировать каждый модуль client vs server-safe | ✅ сделано (S1) | -| **2. Расстановка директив** | `'use client'` по списку: ядро + периферия | S3, S4 | -| **3. Защита пайплайна** | Eslint-правило + build-assert тест на артефакты | S6 | -| **4. Пример + верификация** | Next.js 14 App Router: серверный компонент рендерит `PageConstructor` — критерий успеха | S7 | -| **5. Доки и релиз** | README/migration, changelog, semver (minor/feat) | S8 | -| **6. blog-constructor** | Бамп зависимости + то же самое в отдельном репозитории | S9 | - -### Субагенты - -Порядок: **S3/S4** — основная правка (в worktree, чтобы не мешать друг другу и main). **S5–S8** — после расстановки. **S9** — отдельный репозиторий. - -| # | Субагент | Задача | Зависит от | -| ------ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| **S3** | `general-purpose` (worktree) | **Ядро.** `'use client'` в `Provider.tsx`, `PageConstructor.tsx`, всём `src/context/*`, `src/hooks/*`, `src/containers/*`, `src/grid/Row/Row.tsx`. Прогнать `typecheck`. | S1, S2 | -| **S4** | `general-purpose` (worktree) | **Периферия.** `'use client'` в клиентских листьях `blocks`, `sub-blocks`, `components`, `navigation/*` (по спискам «Must be client» выше). Чистые презентационные листья не трогать. | S1, S2 | -| **S5** | `general-purpose` | **Editor + чистота `server`.** Директива на границе `/editor`; проверить, что `src/server.ts` и весь `/server`-граф остались **без** директив и не тянут клиентские модули. Точечный фикс `import type` в `models` (риск #2). | S1 | -| **S6** | `test-writer` | **Guardrails.** Eslint-правило (обязательность директивы на клиентских зонах, запрет на `src/server.ts`/чистых утилях) + jest/скрипт-ассерт, грепающий `build/esm` и `build/cjs` на директиву в ключевых файлах. | S2, S3, S4 | -| **S7** | `general-purpose` | **Next.js 14 пример + верификация.** App-Router приложение: серверный компонент рендерит `PageConstructor`, контент — через server action. Подтвердить рендер без ошибок RSC. | S3, S4 | -| **S8** | `doc-writer` | **Доки/релиз.** Раздел про RSC в README, migration-заметка, changelog. | S3, S4, S7 | -| **S9** | отдельный репозиторий | **blog-constructor.** Бамп `page-constructor` + `'use client'` для собственных компонентов blog-constructor + свой Next-пример. Из этого воркспейса недоступно — параллельный поток. | S3–S8 | - -### Открытые развилки (решить перед S3/S4) - -1. **Гранулярность границы:** листья (рекомендуется) vs под-барелы (`blocks/index.ts` и т.п.). Листья = канонический RSC-паттерн, лучший tree-shaking; под-барелы проще, но затягивают презентационные листья в клиентский граф. -2. **Subpath-экспорты для серверных модулей:** добавлять ли `./models`, `./utils`, `./schema-ts` в `package.json#exports`, чтобы серверные компоненты импортировали типы/утилиты без пересечения клиентской границы. Сейчас их нет — импорт «из корня» тянет клиент. - -### Риски - -- **Client-без-хуков** (`Provider.tsx`, `NavigationButton`, `ConstructorItem`, `grid/Row/Row.tsx`) — grep по хукам их пропустит; в S3/S4 использовать доп. критерий «рендерит ``». -- **CJS-артефакт:** `'use client'` в CJS безвреден (Next смотрит на ESM), но проверить в S6. -- **Загружаемые блоки** (`Loadable`, `Map`, формы, `React.lazy`) — убедиться, что граница не рвёт динамическую загрузку (S3/S7). -- **`SSRContext`/`useMount`** — логика «isServer» должна остаться консистентной внутри клиентской границы. - ---- - -## План работ — 2 этапа (актуальный, с разбивкой на субагентов) - -> Реконсиляция S-плана под целевую двухэтапную рамку: -> **Этап 1 = «встраивается в Next.js без костылей»** (бывшие S3–S8) — обязательный, разблокирует тикет. -> **Этап 2 = «сдвигаем компоненты в RSC, где реально»** (бывшая Фаза 7) — опциональная оптимизация, требует согласования инварианта #8. -> Списки конкретных модулей не дублируются — берутся из разделов **S1** («Must be client», «Server-safe модули») и **Фаза 7** («Карта контекстов»). - -### Этап 1 — граница `'use client'`, работает из коробки - -**Критерий приёмки этапа:** на smoke-проекте `page-constructor-next-rsc` `npm run build` проходит **без** `Failed to collect page data`; консюмер не пишет ни одной обёртки. Стратегия — точечная граница (листья + модули, рендерящие ``), барелы и чистые слои без директивы. - -| Шаг | Субагент | Задача | Зависит от | Критерий шага | -| -------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | --------------------------------------------------------------------------------------------- | -| **1.1 Ядро** | `general-purpose` (worktree A) | `'use client'` на `context/**`, `hooks/**`, `containers/**`, `grid/Row/Row.tsx`, `Provider.tsx`, `PageConstructor.tsx`. Критерий отбора: хук **или** рендер ``. Прогнать `typecheck` | S1, S2 | E1 уходит; `typecheck` зелёный | -| **1.2 Периферия** | `general-purpose` (worktree B) | `'use client'` на клиентских листьях `blocks`/`sub-blocks`/`components`/`navigation` (по client-спискам S1). Чистые презентационные листья не трогать | S1, S2 | сборка не падает на блоках | -| **1.3 Editor + чистота `/server` + exports** | `general-purpose` | Директива на входе `./editor`; проверить, что `server.ts` и весь `/server`-граф **без** директив; фикс `import type` в `models/constructor-items/{common,blocks}.ts` (риск #2); добавить `./models`, `./utils` в `package.json#exports` (развилка 2 — решена «да») | S1 | grep `/server`-графа чист; `models` без value-import uikit; новые subpath-экспорты резолвятся | -| **1.4 Guardrails** | `test-writer` | Eslint-правило (обязательность директивы в клиентских зонах + запрет на `server.ts`/чистых утилях) + build-assert тест: греп `build/esm`+`build/cjs` на директиву в ключевых файлах | 1.1, 1.2, S2 | тест краснеет, если директиву сняли/срезали | -| **1.5 Verify** | `general-purpose` | Пересобрать `npm pack` → поставить tarball в smoke-проект → `next build` (цикл ретеста из раздела «Верификационный проект») | 1.1–1.3 | build проходит, `/` рендерится на сервере | -| **1.6 Доки/релиз** | `doc-writer` | Раздел RSC в README + migration-заметка + changelog (**minor / feat**) | 1.5 | релиз-нота готова | - -**Фан-аут Этапа 1:** шаги **1.1 и 1.2 запускаются параллельно** в разных worktree — они правят непересекающиеся директории (ядро vs периферия). 1.3 может идти параллельно 1.1/1.2 (директории `editor`/`server`/`models` disjoint), но фикс `import type` в `models` должен приземлиться до 1.5. 1.4 — только после 1.1+1.2 (нужны расставленные директивы для ассертов). 1.5 сводит все worktree воедино. 1.6 — после зелёного 1.5. - -### Этап 2 — рост серверной доли (инверсия `context → props`) - -**Критерий приёмки этапа:** First Load JS страницы из статических блоков падает (`next build` до/после), интерактив цел (острова), нет hydration mismatch, `playwright` зелёный. -**Механика (без костылей):** ставим на Рычаг 2 (инверсия `context → props`), изоморфный шим (Рычаг 1) — не используем. Читаем server-резолвимые контексты один раз на клиентской границе, раздаём пропами; листья становятся server. - -| Шаг | Субагент | Задача | Зависит от | -| --------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| **2.0 Baseline** | `general-purpose` | Зафиксировать First Load JS на странице из статических блоков (`next build`) | Этап 1 зарелижен | -| **2.1 Отбор кандидатов** | `Explore` | Статичные листья, читающие только server-резолвимые контексты (`theme`/`locale`/`isMobile`/`ssr`/`projectSettings` — колонка «да» карты контекстов). Кандидаты: `Content`, `Title`, `Banner`, карточки | 2.0 | -| **2.2–2.4 Конверсия блока** | `general-purpose` (worktree, **один агент на блок**) | Ввести server-shell (резолв контекстов на границе); в листе убрать `useTheme`/`useContext`, принять `theme`/`locale` пропами, снять `'use client'`; `getThemedValue(x, theme)` без изменений. Проверить: интерактив-острова целы, нет mismatch, JS упал | 2.1 | -| **2.5 Итерация/замер** | `general-purpose` | Свести замеры по блокам, остановиться там, где выгода < риска; островные контексты (`image`/`forms`/`maps`/`analytics`/`windowWidth`/`animate`) не трогать | 2.2–2.4 | - -**Фан-аут Этапа 2:** блоки независимы → шаги **2.2–2.4 фанаутятся по одному агенту на блок** (worktree на блок, чтобы правки не конфликтовали), после каждого — свой замер и `playwright`. Пайплайн: `отбор → [конверсия+verify]×N блоков → сводный замер`. - -### Граф зависимостей - -``` -ЭТАП 1 (обязательный) - ┌─ 1.1 Ядро ─────┐ - │ ├─► 1.4 Guardrails ─┐ - ├─ 1.2 Периферия ┘ ├─► 1.5 Verify ─► 1.6 Доки/релиз - └─ 1.3 Editor+server+exports ────────┘ - (параллельно; import type в models — до 1.5) - -ЭТАП 2 (опциональный, требует ок по инварианту #8) - 2.0 Baseline ─► 2.1 Отбор ─► [2.2–2.4 Конверсия+verify] × N блоков ─► 2.5 Замер -``` - -### Развилки к закрытию - -1. **Subpath-экспорты `./models`/`./utils`** — решено «да», исполняется в шаге 1.3 (обязательно для Этапа 2, полезно уже на Этапе 1). -2. **Локальное ослабление инварианта #8** (`contexts over prop drilling` на **листовом** слое) — согласовать до старта 2.x. Без «ок» Этап 2 не делаем; Этап 1 инвариант не задевает. - -### Явно вне scope - -- Этап 1: никаких шимов/форк-реализаций/переноса в server — только границы. Граница ложится на `PageConstructor` как единый client-остров. -- Этап 2: не трогаем интерактивные блоки; не «дошимиваем» контексты, которым нужно рантайм-значение (тема после переключения в браузере, ширина окна); `react-server` export condition — опционально и только для честно форкаемых чисто-данных модулей. - ---- - -## Верификационный проект (Next 14) и baseline-ошибки - -Для S7 создан отдельный smoke-test проект **вне** репозитория библиотеки, чтобы не задевать её lint/build-globs. - -### Расположение и стек - -- **Путь:** `/Users/peterkrotov/page-constructor-next-rsc` (sibling к `page-constructor-1`). -- **Стек:** Next.js **14.2.35** (App Router, TypeScript, без Tailwind/ESLint), React **18.3.1**. -- **Как поставлена библиотека:** локальная сборка через `npm pack` (а не npm-версия), чтобы проект тестировал наши правки. Установлен tarball `gravity-ui-page-constructor-8.13.2.tgz` + peer-зависимости `@gravity-ui/uikit@^7`, `@diplodoc/transform@^4`. -- **Файлы:** - - `app/page.tsx` — **серверный компонент** (без `use client`), рендерит `PageConstructorProvider` + `PageConstructor` с одним `header-block`. Это и есть проверяемый сценарий. - - `app/layout.tsx` — подключает стили `@gravity-ui/uikit/styles/*` + `@gravity-ui/page-constructor/styles/styles.css`, ставит класс `g-root g-root_theme_light`. - -### Baseline (до S3/S4) — воспроизведённая ошибка ❌ - -Команда: `cd /Users/peterkrotov/page-constructor-next-rsc && npm run build` - -Компиляция проходит (`✓ Compiled successfully`), падение — на этапе `Collecting page data`: - -``` -TypeError: ef.createContext is not a function - at 62866 (.next/server/app/page.js …) - … -Build error occurred -Error: Failed to collect page data for / -``` - -**Интерпретация:** это ровно проблема из тикета. При рендере страницы-серверного-компонента модули `src/context/*` вызывают `React.createContext`, которого нет в серверном рантайме React (RSC-сборка не экспортирует `createContext`). Барел `@gravity-ui/page-constructor` без `'use client'` затягивает контексты в серверный граф → падение. - -> Важно: ошибка приходит **на этапе `Collecting page data`** (server prerender), а не при компиляции. Значит блокирует именно отсутствие клиентской границы, а не типы/бандлинг. - -### Целевое состояние (после S3/S4) — ожидаемо ✅ - -После того как библиотека получит границы `'use client'`, цикл ретеста: - -```sh -# 1) пересобрать и переупаковать библиотеку -cd /Users/peterkrotov/page-constructor-1 -npm run build:client && npm run build:server && npm pack -# 2) переустановить tarball в smoke-test проект -cd /Users/peterkrotov/page-constructor-next-rsc -npm i /Users/peterkrotov/page-constructor-1/gravity-ui-page-constructor-8.13.2.tgz -# 3) собрать заново — ожидаем успешный prerender '/' -npm run build -``` - -Критерий успеха S7: `npm run build` проходит без `Failed to collect page data`, страница `/` рендерится на сервере. - -### Реестр блокирующих ошибок - -| # | Ошибка | Где всплывает | Причина | Статус / фикс | -| --- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| E1 | `TypeError: .createContext is not a function` → `Failed to collect page data for /` | `next build`, этап Collecting page data (server prerender) | контекст-модули без `'use client'` попадают в **серверный граф** и вычисляют `createContext` на eval | ✅ **Решена.** Граница `'use client'` (обёртка ИЛИ директивы S3/S4) → контексты исполняются в клиентском бандле | -| E2 | `ReferenceError: document is not defined` | `next build`, этап Generating static pages (**SSR** пререндер) | модуль в клиентском бандле трогает `document`/`window` **на верхнем уровне**; `PageConstructor` тянет `blockMap` из `constructor-items.ts`, который статически импортит **все** блоки → в бандл попадают Share (`github-buttons`), Slider (`swiper`), Map-загрузчики и т.п. | ⚠️ **Обойдена** через `dynamic(..., {ssr:false})`. Правильный фикс — SSR-safety: гард top-level доступа к браузеру в библиотеке / ленивая загрузка тяжёлых блоков | - -### Эксперимент: обёртка vs директивы (проверено в Next-проекте) - -**E1 чинится границей `'use client'` — двумя способами:** - -| Подход | Что делает | Плюсы | Минусы | -| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| **A. Обёртка** (`app/pc-client.tsx` с `'use client'`, ре-экспорт/`dynamic`) | одна клиентская граница на стороне консюмера или отдельным входом библиотеки | 1 файл, «из коробки» без правки 190 модулей | **всё** поддерево становится client; главный барел остаётся серверным только для типов | -| **B. Директивы S3/S4** | `'use client'` на настоящих клиентских модулях | главный барел server-importable (типы/schema/utils), задел под острова (Фаза 7) | много файлов (скриптуется) | - -> Важно: **E2 не зависит от выбора A/B.** Next всё равно SSR-ит клиентские компоненты при пререндере, поэтому top-level доступ к `document`/`window` падает и при обёртке, и при директивах. Значит для «из коробки **с SSR**» библиотеке в любом случае нужна SSR-safety (гард browser-доступа) ИЛИ потребитель отключает SSR через `ssr:false`. - -**Достигнутое состояние (обёртка + `ssr:false`):** `next build` зелёный, страница `/` — серверный компонент, пререндерится статикой, конструктор рендерится на клиенте. Файлы примера: `app/page.tsx` (RSC), `app/PageView.tsx` (`'use client'` композиция), `app/pc-client.tsx` (`dynamic`, `ssr:false`). - -### Следующие блокеры (ожидаемые) - -- После включения SSR (без `ssr:false`) E2 нужно закрыть в библиотеке: найти top-level `document`/`window` (кандидаты — `github-buttons`, `swiper`, `react-slick`, загрузчики карт) и обернуть в `typeof window !== 'undefined'` или ленивую загрузку. -- Возможны E3+ (другие браузерные API при SSR, `React.lazy`/`Loadable`) — фиксируем сюда же по мере всплытия. - ---- - -## Как растить серверную долю (Фаза 7, опциональная оптимизация) - -> Отдельная тема **после** того, как «из коробки» заведётся (S3–S7). Цель — перевести часть компонентов из client в server, чтобы отдавать больше HTML с сервера и грузить меньше JS. Ниже — что реально возможно, потолок каждого подхода и карта, где двигать границу. - -### Жёсткое ограничение (почему контекст нельзя «дошимить» до конца) - -Контекст в RSC — **клиентская механика**. Ошибка E1 (`createContext is not a function`) прилетает **на eval модуля**, а не на рендере: в серверной сборке React `createContext` = `undefined`, а `useContext` не видит стек провайдеров (их на сервере нет). Отсюда: - -- Хелпер **может** сделать модуль контекста import-safe и вернуть **дефолт**. -- Хелпер **не может** вернуть «живое» значение провайдера на сервере (переключённая юзером тема, ширина окна, `isMobile` по `matchMedia`) — этого значения на сервере физически нет. - -Вывод: шим спасает только компоненты, которым достаточно дефолта/статики, а не рантайм-значения. - -### Рычаг 1 — изоморфный context-хелпер (guard + server default) - -Обёртка: на клиенте — настоящий контекст, на сервере — пассивная заглушка с дефолтом. - -```ts -// src/utils/isomorphicContext.ts (server-safe, БЕЗ 'use client') -import * as React from 'react'; - -const isServer = typeof React.createContext !== 'function'; - -export function createIsomorphicContext(defaultValue: T) { - if (isServer) { - // сервер: не трогаем React-контекст, отдаём пассивную заглушку - const ctx = {Provider: ({children}: any) => children, _default: defaultValue}; - return {Context: ctx as any, useValue: () => defaultValue}; - } - const Context = React.createContext(defaultValue); - return {Context, useValue: () => React.useContext(Context)}; -} -``` - -- **Даёт:** компонент, читающий контекст только ради дефолта, становится server-safe. -- **Не даёт:** компоненту, которому нужно настоящее значение провайдера, всё равно быть client. -- **Итог:** сам по себе сдвигает границу мало; полезен в связке с рычагом 2. - -### Рычаг 2 (сильный) — инверсия `context → props` + острова - -Читать контексты **один раз на клиентской границе** и прокидывать разрезолвленные значения вниз пропами → листья становятся чистыми `(props) => JSX` и переезжают в server. Интерактив (`Slider`, `Map`, формы, видео) остаётся «островами» client. - -В page-constructor это идеально ложится на `getThemedValue` — она **уже чистая**, берёт тему аргументом: - -```tsx -// сейчас (client): завязка на контекст -const theme = useTheme(); // useContext → client -const media = getThemedValue(props.media, theme); - -// целевое (server): тема приходит пропом от клиентской границы -function Card({media, theme}: Props) { - // без useContext - const resolved = getThemedValue(media, theme); // остаётся чистой -} -``` - -Тогда `Content`, `Title`, `Banner`, карточки становятся серверными; в бандл уезжает меньше JS. - -### Карта контекстов: что резолвится на серверной границе - -Ключ — разделить контексты на **конфиг, известный на сервере**, и **client-онли рантайм**: - -| Контекст | Резолвится на сервере? | Источник значения | -| ---------------------------- | ---------------------- | ---------------------------- | -| `theme` | да | cookie / проп консюмера | -| `localeContext` | да | заголовки / роут | -| `mobileContext` (`isMobile`) | да | User-Agent / headers | -| `ssrContext` | да | по определению | -| `projectSettingsContext` | да | статический конфиг консюмера | -| `imageContext` | нет | нужен loader/handlers | -| `formsContext` | нет | нужны handlers | -| `mapsContext` | нет | ключи + браузерный API | -| `analyticsContext` | нет | handlers/браузер | -| `windowWidthContext` | нет | `resize` / `matchMedia` | -| `animateContext` | нет | скролл в браузере | - -Всё из колонки «да» консюмер уже имеет на серверной границе → эти значения передаются пропами, и с потребляющих их листьев снимается клиентская метка. Всё «нет» — остаётся островами. - -### Рекомендация и границы применения - -- **Не делать это в S3/S4.** Там задача — просто расставить `'use client'` и разблокировать тикет минимальным риском (граница ляжет на `PageConstructor`). -- **Фаза 7 (после S7):** ввести рычаг 2 точечно — server-shell для статических блоков (`Header`, `Banner`, `Content`) с темой/locale через пропы, интерактив — острова. Начать с блоков без интерактива (см. server-safe листья в разделе S1). -- **Связь с инвариантом:** это **локальное** ослабление правила «contexts over prop drilling» из `architecture.md` — только на **листовом** слое. Кросс-катящий рантайм-конфиг остаётся в контекстах на клиентской границе; презентационные листья переводятся на пропы. Осознанный точечный компромисс, а не отмена инварианта. - -### Метрика успеха Фазы 7 - -- Доля server-компонентов в дереве `PageConstructor` растёт (меньше `'use client'`-модулей в критическом пути статического блока). -- Размер клиентского JS для страницы из статических блоков падает (замер `next build` → First Load JS до/после). -- Ни один блок не теряет интерактив (острова остаются client). - ---- - -## Учебный трек — как изучить тему (пошагово) - -> Куратор-план для погружения в RSC под нашу задачу. Порядок модулей = порядок изучения: от ментальной модели → к директивам → к композиции → к специфике **авторов библиотек** (наш случай) → к верификации. Каждый урок привязан к фазе плана (S1–S9) и/или к конкретной находке аудита выше. -> -> Легенда приоритета: ⭐ обязательно · 📘 углубление · 🛠 практика/эталон конфигурации. - -### Модуль 0 — Ментальная модель RSC - -| # | Урок (цель) | Ссылки | Зачем нам | -| ------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| 0.1 ⭐ | Что такое Server Components: две среды (server/client), что «RSC ≠ SSR» | [react.dev · Server Components](https://react.dev/reference/rsc/server-components) | База для всего документа; объясняет, почему модуль без `'use client'` считается серверным (см. «Контекст») | -| 0.2 ⭐ | Целостная картина: props, ре-рендеры, связка с Suspense/Streaming SSR | [Josh Comeau · Making Sense of RSC](https://www.joshwcomeau.com/react/server-components/) | Лучшее «человеческое» объяснение модели; снимает путаницу RSC vs SSR | -| 0.3 📘 | RSC «с нуля»: как устроен payload, сериализация client-компонентов | [Dan Abramov · RSC From Scratch](https://github.com/reactwg/server-components/discussions/5) | Понимание, почему `'use client'` — это граница графа модулей, а не «флаг компонента» | - -### Модуль 1 — Директивы `'use client'` / `'use server'` - -| # | Урок (цель) | Ссылки | Зачем нам | -| ------ | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -| 1.1 ⭐ | `'use client'`: семантика границы, транзитивность импортов, правила синтаксиса (первая строка, кавычки) | [react.dev · 'use client'](https://react.dev/reference/rsc/use-client) | Прямо обосновывает вывод **S2** (директива = голый литерал первой строкой) и стратегию «границы на листьях» | -| 1.2 📘 | Обзор всех директив разом | [react.dev · Directives](https://react.dev/reference/rsc/directives) | Быстрый референс, чтобы не путать `'use client'` и `'use server'` | -| 1.3 📘 | `'use server'` и Server Functions (это НЕ «пометка серверного компонента») | [react.dev · 'use server'](https://react.dev/reference/rsc/use-server) · [Server Functions](https://react.dev/reference/rsc/server-functions) | Развеивает частую ошибку «для сервера нужен `use server`»; пригодится в примере S7 (контент через server action) | - -### Модуль 2 — Композиция client ↔ server (Next.js App Router) - -| # | Урок (цель) | Ссылки | Зачем нам | -| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| 2.1 ⭐ | Server и Client Components в App Router: серверный может рендерить клиентского ребёнка, но не наоборот | [Next.js · Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components) | Наш целевой сценарий: серверная `page.tsx` рендерит `PageConstructor` (S7) | -| 2.2 ⭐ | Паттерны композиции: props должны быть сериализуемы; Server Components как `children`/props клиентского компонента; «граница каскадит по импортам, не по props» | [Next.js 14 · Composition Patterns](https://nextjs.org/docs/14/app/building-your-application/rendering/composition-patterns) | Фундамент для **Рычага 2** (инверсия `context → props`, острова) из Фазы 7 | -| 2.3 🛠 | Минимизация клиентского бандла: `'use client'` на узких интерактивных листьях, а не на крупных поддеревьях | тот же раздел, блок «Reducing bundle size» в [Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components) | Обоснование развилки «листья vs под-барелы» (рекомендация — листья) | - -### Модуль 3 — Контекст в RSC (наш E1) - -| # | Урок (цель) | Ссылки | Зачем нам | -| ------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -| 3.1 ⭐ | Почему `createContext` недоступен в серверном компоненте — официальная страница ошибки | [Next.js · createContext in a Server Component](https://nextjs.org/docs/messages/context-in-server-component) | Дословно наш **E1** (`createContext is not a function` → `Failed to collect page data`) | -| 3.2 📘 | Как контекст работает в React (что именно ломается на сервере) | [react.dev · createContext](https://react.dev/reference/react/createContext) | Объясняет «жёсткое ограничение» из Фазы 7: шим отдаёт только дефолт, не рантайм-значение провайдера | -| 3.3 ⭐ | Оборачивание сторонних провайдеров в client-компонент (root layout + `{children}`) | раздел «Context providers» в [Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components) | Паттерн для `PageConstructorProvider`; закрывает E1 через S3 | - -### Модуль 4 — Авторам библиотек (ядро нашей задачи) - -| # | Урок (цель) | Ссылки | Зачем нам | -| ------ | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| 4.1 ⭐ | Полный гайд «как поддержать RSC в своей библиотеке»: где ставить `'use client'`, риск срезания директивы бандлером, banner-инъекция | [Bekk · Support RSC in your library](https://www.bekk.christmas/post/2023/19/keep-up-with-react-server-components-how-to-support-it-in-your-library) | Наиболее близкий к нашей задаче материал; сверить с выводом S2 (у нас gulp/tsc директиву НЕ срезает) | -| 4.2 📘 | RFC 0227 «Server Module Conventions» — первоисточник семантики директив и границ | [reactjs/rfcs · 0227](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md) | Первоисточник правила «помечай только точки, потребляемые сервером напрямую» → наша стратегия «точечная граница» | -| 4.3 🛠 | Бандлинг библиотеки с RSC на Rollup: `preserveModules`, banner только на client-чанк, `terser {compress:{directives:false}}` | [DEV · RSC + Client Components with Rollup](https://dev.to/mryechkin/react-server-components-and-client-components-with-rollup-3c05) | Эталон на случай, если S6 покажет, что директиву где-то срезает; у нас пока не требуется | -| 4.4 📘 | Дискуссия мейнтейнеров Next: как паковать server-пакеты с client-компонентами | [vercel/next.js · Discussion #62231](https://github.com/vercel/next.js/discussions/62231) | Кейсы граничных случаев (смешанные чанки, subpath-экспорты) → развилка про `./models`/`./utils` | -| 4.5 📘 | `react-server` export condition: когда `'use client'` мало и нужна форк-реализация модуля | [react.dev · 'use client' (раздел про bundler requirements)](https://react.dev/reference/rsc/use-client) · кейс [lucide-icons/lucide#4200](https://github.com/lucide-icons/lucide/issues/4200) | Альтернатива **Рычагу 1**: server-форк модуля контекста через `exports["react-server"]` вместо изоморфного шима (риск #2, `models` + uikit) | - -### Модуль 5 — Верификация и отладка - -| # | Урок (цель) | Ссылки | Зачем нам | -| ------ | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | -| 5.1 🛠 | Как ретестить: `next build` → этап «Collecting page data» ловит серверные ошибки эвала модуля | [Next.js · Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components) | Наш критерий успеха S7 (сборка без `Failed to collect page data`) | -| 5.2 📘 | Индекс ошибок Next.js (быстрый лукап вторичных падений после S3) | [Next.js · context-in-server-component](https://nextjs.org/docs/messages/context-in-server-component) | При появлении E2/E3 (хуки в блоках, `window` при импорте, `React.lazy`) — искать по этому индексу | - -### Маппинг уроков на фазы плана - -| Фаза / субагент | Обязательные уроки перед стартом | -| ------------------------------------------------------------- | --------------------------------- | -| Понимание модели (все) | 0.1, 0.2, 1.1 | -| **S3/S4** — расстановка `'use client'` | 1.1, 2.1, 2.3, 3.1, 3.3, 4.1, 4.2 | -| **S5** — editor + чистота `/server`, `import type` в `models` | 4.2, 4.5 | -| **S6** — guardrails (eslint + build-assert) | 1.1, 4.1, 4.3 | -| **S7** — Next.js 14 пример | 2.1, 2.2, 3.1, 5.1 | -| **Фаза 7** — рост серверной доли (`context → props`, острова) | 2.2, 3.2, 4.5 | diff --git a/package.json b/package.json index 65e2176164..c38b030201 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,11 @@ "require": "./server/index.js", "import": "./server/index.js" }, + "./server-context": { + "types": "./build/esm/server-context.d.ts", + "require": "./build/cjs/server-context.js", + "import": "./build/esm/server-context.js" + }, "./models": { "types": "./build/esm/models/index.d.ts", "require": "./build/cjs/models/index.js", @@ -39,6 +44,41 @@ "require": "./build/cjs/utils/index.js", "import": "./build/esm/utils/index.js" }, + "./grid": { + "types": "./build/esm/grid/index.d.ts", + "require": "./build/cjs/grid/index.js", + "import": "./build/esm/grid/index.js" + }, + "./grid/*": { + "types": "./build/esm/grid/*.d.ts", + "require": "./build/cjs/grid/*.js", + "import": "./build/esm/grid/*.js" + }, + "./components": { + "types": "./build/esm/components/index.d.ts", + "require": "./build/cjs/components/index.js", + "import": "./build/esm/components/index.js" + }, + "./components/*": { + "types": "./build/esm/components/*.d.ts", + "require": "./build/cjs/components/*.js", + "import": "./build/esm/components/*.js" + }, + "./sub-blocks/*": { + "types": "./build/esm/sub-blocks/*.d.ts", + "require": "./build/cjs/sub-blocks/*.js", + "import": "./build/esm/sub-blocks/*.js" + }, + "./blocks/*": { + "types": "./build/esm/blocks/*.d.ts", + "require": "./build/cjs/blocks/*.js", + "import": "./build/esm/blocks/*.js" + }, + "./navigation/*": { + "types": "./build/esm/navigation/*.d.ts", + "require": "./build/cjs/navigation/*.js", + "import": "./build/esm/navigation/*.js" + }, "./styles/*": "./styles/*", "./widget/*": "./widget/*", "./schema/*": "./schema/*" @@ -57,11 +97,20 @@ "server": [ "./server/index.d.ts" ], + "server-context": [ + "./build/esm/server-context.d.ts" + ], "models": [ "./build/esm/models/index.d.ts" ], "utils": [ "./build/esm/utils/index.d.ts" + ], + "grid": [ + "./build/esm/grid/index.d.ts" + ], + "components": [ + "./build/esm/components/index.d.ts" ] } }, diff --git a/src/context/locationContext/locationContext.ts b/src/context/locationContext/locationContext.ts index 40db96c2a3..5f3069f1a1 100644 --- a/src/context/locationContext/locationContext.ts +++ b/src/context/locationContext/locationContext.ts @@ -30,7 +30,6 @@ export type LocationContextProps = { export const initialLocation: LocationContextProps = {}; -// Изоморфный контекст (без 'use client') — см. context/theme/ThemeContext. const isRscServer = typeof React.createContext !== 'function'; export const LocationContext = ( From 900efc0c02dc3645439d8a331b2bb61bd332bfde Mon Sep 17 00:00:00 2001 From: Krotov Petr <83280920+KrotovPetr@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:46:04 +0300 Subject: [PATCH 4/5] feat: revert server context workaround --- src/blocks/ContentLayout/ContentLayout.tsx | 7 ++---- src/blocks/FilterBlock/FilterBlock.tsx | 7 ++---- src/blocks/FoldableList/FoldableList.tsx | 4 +-- .../FoldableListBlockItem.tsx | 6 +---- src/blocks/Footer/hooks/useLogoImageProps.ts | 24 ++++++++++-------- src/blocks/Form/Form.tsx | 7 ++---- src/blocks/Header/Header.tsx | 6 +---- src/blocks/HeaderSlider/HeaderSlider.tsx | 8 +++--- src/blocks/Hero/Hero.tsx | 8 ++---- src/blocks/Icons/Icons.tsx | 5 +--- .../QuestionBlockItem/QuestionBlockItem.tsx | 6 +---- src/blocks/Questions/Questions.tsx | 4 +-- src/blocks/Share/Share.tsx | 4 +-- src/blocks/Table/Table.tsx | 5 +--- src/blocks/Tabs/TabContent/TabContent.tsx | 5 +--- .../BackgroundMedia/BackgroundMedia.tsx | 6 +++-- .../FullscreenImage/FullscreenImage.tsx | 4 +-- src/components/InnerForm/InnerForm.tsx | 6 ++--- src/components/Media/DataLens/DataLens.tsx | 6 +++-- src/components/Media/Image/Image.tsx | 4 +-- src/components/RouterLink/RouterLink.tsx | 4 +-- src/context/localeContext/index.ts | 2 -- src/context/localeContext/localeContext.ts | 8 +----- src/context/localeContext/serverLocale.ts | 24 ------------------ src/context/localeContext/useLocale.ts | 15 ----------- src/context/locationContext/index.ts | 2 -- .../locationContext/locationContext.ts | 8 +----- src/context/locationContext/serverLocation.ts | 24 ------------------ src/context/locationContext/useLocation.ts | 15 ----------- src/context/mobileContext/MobileContext.ts | 6 +---- src/context/mobileContext/index.ts | 2 -- src/context/mobileContext/serverMobile.ts | 22 ---------------- src/context/mobileContext/useMobile.ts | 15 ----------- src/context/theme/ThemeContext.ts | 6 +---- src/context/theme/index.ts | 1 - src/context/theme/serverTheme.ts | 25 ------------------- src/context/theme/useTheme.ts | 10 ++------ src/navigation/components/Logo/Logo.tsx | 6 ++--- .../MobileMenuButton/MobileMenuButton.tsx | 4 +-- .../ContentWrapper/ContentWrapper.tsx | 4 +-- .../NavigationButton/NavigationButton.tsx | 5 +--- .../NavigationDropdown/NavigationDropdown.tsx | 4 +-- .../NavigationLink/NavigationLink.tsx | 4 +-- src/server-context.ts | 4 --- .../BackgroundCard/BackgroundCard.tsx | 7 +++--- src/sub-blocks/BasicCard/BasicCard.tsx | 10 ++++---- src/sub-blocks/Content/Content.tsx | 8 +----- src/sub-blocks/ImageCard/ImageCard.tsx | 4 +-- src/sub-blocks/LayoutItem/LayoutItem.tsx | 13 +++++----- .../CombinedPriceDetailed.tsx | 4 +-- .../PriceDescription/PriceDescription.tsx | 4 +-- .../PriceDetails/PriceDetails.tsx | 5 +--- src/sub-blocks/Quote/Quote.tsx | 7 +----- 53 files changed, 86 insertions(+), 328 deletions(-) delete mode 100644 src/context/localeContext/serverLocale.ts delete mode 100644 src/context/localeContext/useLocale.ts delete mode 100644 src/context/locationContext/serverLocation.ts delete mode 100644 src/context/locationContext/useLocation.ts delete mode 100644 src/context/mobileContext/serverMobile.ts delete mode 100644 src/context/mobileContext/useMobile.ts delete mode 100644 src/context/theme/serverTheme.ts delete mode 100644 src/server-context.ts diff --git a/src/blocks/ContentLayout/ContentLayout.tsx b/src/blocks/ContentLayout/ContentLayout.tsx index 13e8277c04..a6cfd666c6 100644 --- a/src/blocks/ContentLayout/ContentLayout.tsx +++ b/src/blocks/ContentLayout/ContentLayout.tsx @@ -1,15 +1,12 @@ -'use client'; - import * as React from 'react'; -import BackgroundImage from '../../components/BackgroundImage/BackgroundImage'; -import FileLink from '../../components/FileLink/FileLink'; +import {BackgroundImage, FileLink} from '../../components'; import {BREAKPOINTS} from '../../constants'; import {useTheme} from '../../context/theme'; import {useWindowWidth} from '../../context/windowWidthContext'; import {Col} from '../../grid'; import {ContentLayoutBlockProps, ContentSize, ContentTextSize} from '../../models'; -import Content from '../../sub-blocks/Content/Content'; +import {Content} from '../../sub-blocks'; import {block, getThemedValue} from '../../utils'; import './ContentLayout.scss'; diff --git a/src/blocks/FilterBlock/FilterBlock.tsx b/src/blocks/FilterBlock/FilterBlock.tsx index 41af837827..8eb4a81ed4 100644 --- a/src/blocks/FilterBlock/FilterBlock.tsx +++ b/src/blocks/FilterBlock/FilterBlock.tsx @@ -1,16 +1,13 @@ -'use client'; - import * as React from 'react'; -import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; +import {CardLayoutBlock} from '..'; +import {AnimateBlock, Title} from '../../components'; import ButtonTabs, {ButtonTabsItemProps} from '../../components/ButtonTabs/ButtonTabs'; -import Title from '../../components/Title/Title'; import {ConstructorItem} from '../../containers/PageConstructor/components/ConstructorItem'; import {Col, Row} from '../../grid'; import {useAnalytics} from '../../hooks'; import {FilterBlockProps, FilterItem} from '../../models'; import {block, getBlockKey} from '../../utils'; -import CardLayoutBlock from '../CardLayout/CardLayout'; import {i18n} from './i18n'; diff --git a/src/blocks/FoldableList/FoldableList.tsx b/src/blocks/FoldableList/FoldableList.tsx index e46a2b61e0..86dfafd465 100644 --- a/src/blocks/FoldableList/FoldableList.tsx +++ b/src/blocks/FoldableList/FoldableList.tsx @@ -1,10 +1,8 @@ -'use client'; - import * as React from 'react'; import {Col, Row} from '../../grid'; import {FoldableListProps} from '../../models'; -import Content from '../../sub-blocks/Content/Content'; +import {Content} from '../../sub-blocks'; import {block} from '../../utils'; import {FoldableListBlockItem} from './FoldableListBlockItem/FoldableListBlockItem'; diff --git a/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx b/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx index e4e1b517f7..97fb3319a2 100644 --- a/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx +++ b/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx @@ -1,11 +1,7 @@ -'use client'; - import {useActionHandlers} from '@gravity-ui/uikit'; -import Foldable from '../../../components/Foldable/Foldable'; +import {Foldable, ToggleArrow, YFMWrapper} from '../../../components'; import Link from '../../../components/Link/Link'; -import ToggleArrow from '../../../components/ToggleArrow/ToggleArrow'; -import YFMWrapper from '../../../components/YFMWrapper/YFMWrapper'; import {FoldableListBlockItemProps} from '../../../models'; import {block} from '../../../utils'; diff --git a/src/blocks/Footer/hooks/useLogoImageProps.ts b/src/blocks/Footer/hooks/useLogoImageProps.ts index 1e17734a0a..a06790efb7 100644 --- a/src/blocks/Footer/hooks/useLogoImageProps.ts +++ b/src/blocks/Footer/hooks/useLogoImageProps.ts @@ -1,19 +1,23 @@ +import * as React from 'react'; + import {getMediaImage} from '../../../components/Media/Image/utils'; import type {ImageProps as ModelImageProps} from '../../../models'; export function useLogoImageProps( logoImage?: ModelImageProps | null, ): Record | null { - if (!logoImage) { - return null; - } + return React.useMemo(() => { + if (!logoImage) { + return null; + } - const resolved = getMediaImage(logoImage); - if (typeof resolved === 'string') { - return {src: resolved}; - } + const resolved = getMediaImage(logoImage); + if (typeof resolved === 'string') { + return {src: resolved}; + } - return resolved && typeof resolved === 'object' - ? (resolved as unknown as Record) - : null; + return resolved && typeof resolved === 'object' + ? (resolved as unknown as Record) + : null; + }, [logoImage]); } diff --git a/src/blocks/Form/Form.tsx b/src/blocks/Form/Form.tsx index b07d878600..c5c20dae49 100644 --- a/src/blocks/Form/Form.tsx +++ b/src/blocks/Form/Form.tsx @@ -1,10 +1,7 @@ -'use client'; - import * as React from 'react'; -import BackgroundImage from '../../components/BackgroundImage/BackgroundImage'; +import {BackgroundImage, Title} from '../../components'; import InnerForm from '../../components/InnerForm/InnerForm'; -import Title from '../../components/Title/Title'; import {MobileContext} from '../../context/mobileContext'; import {useTheme} from '../../context/theme'; import {Col, Grid, GridAlignItems, GridColumnSize, Row} from '../../grid'; @@ -16,7 +13,7 @@ import { isHubspotDataForm, isYandexDataForm, } from '../../models'; -import Content from '../../sub-blocks/Content/Content'; +import {Content} from '../../sub-blocks'; import {block, getThemedValue} from '../../utils'; import {hasBackgroundCSS} from './utils'; diff --git a/src/blocks/Header/Header.tsx b/src/blocks/Header/Header.tsx index 9fbdadf673..ccb9369c83 100644 --- a/src/blocks/Header/Header.tsx +++ b/src/blocks/Header/Header.tsx @@ -1,15 +1,11 @@ -'use client'; - /* eslint-disable complexity */ import * as React from 'react'; import {useUniqId} from '@gravity-ui/uikit'; -import Button from '../../components/Button/Button'; +import {Button, Media, RouterLink} from '../../components'; import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs/HeaderBreadcrumbs'; import {getMediaImage} from '../../components/Media/Image/utils'; -import Media from '../../components/Media/Media'; -import RouterLink from '../../components/RouterLink/RouterLink'; import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {BREAKPOINTS} from '../../constants'; import {useTheme} from '../../context/theme'; diff --git a/src/blocks/HeaderSlider/HeaderSlider.tsx b/src/blocks/HeaderSlider/HeaderSlider.tsx index 5948ea0981..443f76dc97 100644 --- a/src/blocks/HeaderSlider/HeaderSlider.tsx +++ b/src/blocks/HeaderSlider/HeaderSlider.tsx @@ -1,5 +1,7 @@ -import SliderBlock from '../../blocks/Slider/Slider'; -import {useMobile} from '../../context/mobileContext'; +import * as React from 'react'; + +import {SliderBlock} from '../../blocks'; +import {MobileContext} from '../../context/mobileContext'; import {HeaderSliderBlockProps, SliderType} from '../../models'; import {block} from '../../utils'; import Header from '../Header/Header'; @@ -9,7 +11,7 @@ import './HeaderSlider.scss'; const b = block('header-slider-block'); export const HeaderSliderBlock = ({items, arrows, ...props}: HeaderSliderBlockProps) => { - const isMobile = useMobile(); + const isMobile = React.useContext(MobileContext); const showArrows = isMobile ? false : arrows; return ( diff --git a/src/blocks/Hero/Hero.tsx b/src/blocks/Hero/Hero.tsx index e4e9d7fa95..7b0da2c601 100644 --- a/src/blocks/Hero/Hero.tsx +++ b/src/blocks/Hero/Hero.tsx @@ -1,16 +1,12 @@ -'use client'; - import * as React from 'react'; -import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs/HeaderBreadcrumbs'; -import Media from '../../components/Media/Media'; -import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; +import {HeaderBreadcrumbs, Media, YFMWrapper} from '../../components'; import {BREAKPOINTS} from '../../constants'; import {useTheme} from '../../context/theme'; import {useWindowWidth} from '../../context/windowWidthContext'; import {Grid} from '../../grid'; import {ButtonProps, HeroBlockProps, Theme} from '../../models'; -import Content from '../../sub-blocks/Content/Content'; +import {Content} from '../../sub-blocks'; import {block, getQaAttrubutes, getThemedValue} from '../../utils'; import {useContainerAspectRatio} from './hooks'; diff --git a/src/blocks/Icons/Icons.tsx b/src/blocks/Icons/Icons.tsx index 93bad77ab8..e6ce8c667d 100644 --- a/src/blocks/Icons/Icons.tsx +++ b/src/blocks/Icons/Icons.tsx @@ -1,9 +1,6 @@ -'use client'; - import * as React from 'react'; -import Image from '../../components/Image/Image'; -import Title from '../../components/Title/Title'; +import {Image, Title} from '../../components'; import {LocationContext} from '../../context/locationContext'; import {useTheme} from '../../context/theme'; import {useAnalytics} from '../../hooks'; diff --git a/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx b/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx index 9706e54d82..c30db81f0f 100644 --- a/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx +++ b/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx @@ -1,11 +1,7 @@ -'use client'; - import {useActionHandlers} from '@gravity-ui/uikit'; -import Foldable from '../../../components/Foldable/Foldable'; +import {Foldable, ToggleArrow, YFMWrapper} from '../../../components'; import Link from '../../../components/Link/Link'; -import ToggleArrow from '../../../components/ToggleArrow/ToggleArrow'; -import YFMWrapper from '../../../components/YFMWrapper/YFMWrapper'; import {QuestionBlockItemProps} from '../../../models'; import {block} from '../../../utils'; diff --git a/src/blocks/Questions/Questions.tsx b/src/blocks/Questions/Questions.tsx index 71b4fc0a8d..a0160a5df0 100644 --- a/src/blocks/Questions/Questions.tsx +++ b/src/blocks/Questions/Questions.tsx @@ -1,10 +1,8 @@ -'use client'; - import * as React from 'react'; import {Col, Row} from '../../grid'; import {QuestionsProps} from '../../models'; -import Content from '../../sub-blocks/Content/Content'; +import {Content} from '../../sub-blocks'; import {block} from '../../utils'; import {sanitizeMicrodata} from '../../utils/microdata'; diff --git a/src/blocks/Share/Share.tsx b/src/blocks/Share/Share.tsx index a799fa3fa6..0c17d7dd88 100644 --- a/src/blocks/Share/Share.tsx +++ b/src/blocks/Share/Share.tsx @@ -1,10 +1,8 @@ -'use client'; - import * as React from 'react'; import {Button, Icon} from '@gravity-ui/uikit'; -import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; +import {YFMWrapper} from '../../components'; import {LocationContext} from '../../context/locationContext'; import {useAnalytics} from '../../hooks'; import {Facebook} from '../../icons/Facebook'; diff --git a/src/blocks/Table/Table.tsx b/src/blocks/Table/Table.tsx index 80fd45fb0e..b292709574 100644 --- a/src/blocks/Table/Table.tsx +++ b/src/blocks/Table/Table.tsx @@ -1,9 +1,6 @@ -'use client'; - import * as React from 'react'; -import Table from '../../components/Table/Table'; -import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; +import {Table, YFMWrapper} from '../../components'; import {Col, Grid, GridColumnSize, Row} from '../../grid'; import {TableBlockProps} from '../../models'; import {block} from '../../utils'; diff --git a/src/blocks/Tabs/TabContent/TabContent.tsx b/src/blocks/Tabs/TabContent/TabContent.tsx index 7e13dd88ba..bb503316f6 100644 --- a/src/blocks/Tabs/TabContent/TabContent.tsx +++ b/src/blocks/Tabs/TabContent/TabContent.tsx @@ -1,14 +1,11 @@ -'use client'; - import * as React from 'react'; import {useUniqId} from '@gravity-ui/uikit'; -import FullscreenImage from '../../../components/FullscreenImage/FullscreenImage'; +import {FullscreenImage, YFMWrapper} from '../../../components'; import {getMediaImage} from '../../../components/Media/Image/utils'; import Media from '../../../components/Media/Media'; import {getHeight} from '../../../components/VideoBlock/VideoBlock'; -import YFMWrapper from '../../../components/YFMWrapper/YFMWrapper'; import {ProjectSettingsContext} from '../../../context/projectSettingsContext'; import {useTheme} from '../../../context/theme'; import {Col, GridColumnOrderClasses, Row} from '../../../grid'; diff --git a/src/components/BackgroundMedia/BackgroundMedia.tsx b/src/components/BackgroundMedia/BackgroundMedia.tsx index d74d0ed2f1..b676147cc0 100644 --- a/src/components/BackgroundMedia/BackgroundMedia.tsx +++ b/src/components/BackgroundMedia/BackgroundMedia.tsx @@ -1,4 +1,6 @@ -import {useMobile} from '../../context/mobileContext'; +import * as React from 'react'; + +import {MobileContext} from '../../context/mobileContext'; import {BackgroundMediaProps} from '../../models'; import {block, getQaAttrubutes} from '../../utils'; import AnimateBlock from '../AnimateBlock/AnimateBlock'; @@ -19,7 +21,7 @@ const BackgroundMedia = ({ qa, ...props }: BackgroundMediaProps) => { - const isMobile = useMobile(); + const isMobile = React.useContext(MobileContext); const qaAttributes = getQaAttrubutes(qa, 'media'); return ( diff --git a/src/components/FullscreenImage/FullscreenImage.tsx b/src/components/FullscreenImage/FullscreenImage.tsx index a30f66444f..7fb6c4c689 100644 --- a/src/components/FullscreenImage/FullscreenImage.tsx +++ b/src/components/FullscreenImage/FullscreenImage.tsx @@ -1,11 +1,9 @@ -'use client'; - import * as React from 'react'; import {ChevronsExpandUpRight, Xmark} from '@gravity-ui/icons'; import {Icon, Modal} from '@gravity-ui/uikit'; -import SliderBlock from '../../blocks/Slider/Slider'; +import {SliderBlock} from '../../blocks'; import {ImageProps as ModelImageProps, SliderType} from '../../models'; import {block} from '../../utils'; import Image, {ImageProps} from '../Image/Image'; diff --git a/src/components/InnerForm/InnerForm.tsx b/src/components/InnerForm/InnerForm.tsx index c04a113daf..d9c5249424 100644 --- a/src/components/InnerForm/InnerForm.tsx +++ b/src/components/InnerForm/InnerForm.tsx @@ -1,7 +1,6 @@ -'use client'; - import * as React from 'react'; +import {YandexForm} from '..'; import { FormsContext, HubspotFormsContextProps, @@ -9,9 +8,8 @@ import { } from '../../context/formsContext/FormsContext'; import {useTheme} from '../../context/theme'; import {FormBlockData, isHubspotDataForm, isYandexDataForm} from '../../models'; -import HubspotForm from '../../sub-blocks/HubspotForm'; +import {HubspotForm} from '../../sub-blocks'; import {getThemedValue} from '../../utils'; -import YandexForm from '../YandexForm/YandexForm'; interface InnerFormProps { formData: FormBlockData; diff --git a/src/components/Media/DataLens/DataLens.tsx b/src/components/Media/DataLens/DataLens.tsx index 3592aed501..25a1b59175 100644 --- a/src/components/Media/DataLens/DataLens.tsx +++ b/src/components/Media/DataLens/DataLens.tsx @@ -1,4 +1,6 @@ -import {useLocale} from '../../../context/localeContext'; +import * as React from 'react'; + +import {LocaleContext} from '../../../context/localeContext'; import {MediaComponentDataLensProps} from '../../../models'; import {block} from '../../../utils'; @@ -12,7 +14,7 @@ const b = block('media-component-data-lens'); const DataLens = (props: MediaComponentDataLensProps) => { const {dataLens} = props; const dataLensData = unifyDataLensToObject(dataLens); - const locale = useLocale(); + const locale = React.useContext(LocaleContext); return dataLens ? (
diff --git a/src/components/Media/Image/Image.tsx b/src/components/Media/Image/Image.tsx index f6417adfc6..896dc2d97b 100644 --- a/src/components/Media/Image/Image.tsx +++ b/src/components/Media/Image/Image.tsx @@ -1,11 +1,9 @@ -'use client'; - import * as React from 'react'; import {Interpolation, animated, config, useSpring} from '@react-spring/web'; import debounce from 'lodash/debounce'; -import SliderBlock from '../../../blocks/Slider/Slider'; +import {SliderBlock} from '../../../blocks'; import {ImageProps, MediaComponentImageProps, QAProps, SliderType} from '../../../models'; import {block, getQaAttrubutes} from '../../../utils'; import BackgroundImage from '../../BackgroundImage/BackgroundImage'; diff --git a/src/components/RouterLink/RouterLink.tsx b/src/components/RouterLink/RouterLink.tsx index fc9b783e46..ba80ca4aec 100644 --- a/src/components/RouterLink/RouterLink.tsx +++ b/src/components/RouterLink/RouterLink.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import {useLocation} from '../../context/locationContext'; +import {LocationContext} from '../../context/locationContext'; export interface RouterLinkProps { href: string; @@ -8,7 +8,7 @@ export interface RouterLinkProps { } const RouterLink = ({href, children}: React.PropsWithChildren) => { - const {Link} = useLocation(); + const {Link} = React.useContext(LocationContext); return Link ? {children} : {children}; }; diff --git a/src/context/localeContext/index.ts b/src/context/localeContext/index.ts index ebfd0af8af..c80490a205 100644 --- a/src/context/localeContext/index.ts +++ b/src/context/localeContext/index.ts @@ -1,3 +1 @@ export * from './localeContext'; -export * from './useLocale'; -export * from './serverLocale'; diff --git a/src/context/localeContext/localeContext.ts b/src/context/localeContext/localeContext.ts index 8e7ee91d62..138a503a85 100644 --- a/src/context/localeContext/localeContext.ts +++ b/src/context/localeContext/localeContext.ts @@ -7,10 +7,4 @@ export type LocaleContextProps = { tld?: string; }; -export const initialLocale: LocaleContextProps = {lang: Lang.Ru, tld: '.ru'}; - -const isRscServer = typeof React.createContext !== 'function'; - -export const LocaleContext = ( - isRscServer ? null : React.createContext(initialLocale) -) as React.Context; +export const LocaleContext = React.createContext({lang: Lang.Ru, tld: '.ru'}); diff --git a/src/context/localeContext/serverLocale.ts b/src/context/localeContext/serverLocale.ts deleted file mode 100644 index d763721913..0000000000 --- a/src/context/localeContext/serverLocale.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react'; - -import {LocaleContextProps, initialLocale} from './localeContext'; - -type Store = {current: LocaleContextProps}; - -const createStore = (): (() => Store) => { - const cache = (React as unknown as {cache?: (fn: () => T) => () => T}).cache; - if (typeof cache === 'function') { - return cache(() => ({current: initialLocale})); - } - const singleton: Store = {current: initialLocale}; - return () => singleton; -}; - -const store = createStore(); - -export function getServerLocale(): LocaleContextProps { - return store().current; -} - -export function setServerLocale(locale: LocaleContextProps): void { - store().current = locale; -} diff --git a/src/context/localeContext/useLocale.ts b/src/context/localeContext/useLocale.ts deleted file mode 100644 index 972e8da601..0000000000 --- a/src/context/localeContext/useLocale.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; - -import {LocaleContext, LocaleContextProps} from './localeContext'; -import {getServerLocale} from './serverLocale'; - -const isRscServer = typeof React.createContext !== 'function'; - -export function useLocale(): LocaleContextProps { - if (isRscServer) { - return getServerLocale(); - } - - // eslint-disable-next-line react-hooks/rules-of-hooks - return React.useContext(LocaleContext); -} diff --git a/src/context/locationContext/index.ts b/src/context/locationContext/index.ts index b2bdb6ab27..edb62d44d4 100644 --- a/src/context/locationContext/index.ts +++ b/src/context/locationContext/index.ts @@ -1,3 +1 @@ export * from './locationContext'; -export * from './useLocation'; -export * from './serverLocation'; diff --git a/src/context/locationContext/locationContext.ts b/src/context/locationContext/locationContext.ts index 5f3069f1a1..9b40725ce3 100644 --- a/src/context/locationContext/locationContext.ts +++ b/src/context/locationContext/locationContext.ts @@ -28,10 +28,4 @@ export type LocationContextProps = { asPath?: string; }; -export const initialLocation: LocationContextProps = {}; - -const isRscServer = typeof React.createContext !== 'function'; - -export const LocationContext = ( - isRscServer ? null : React.createContext(initialLocation) -) as React.Context; +export const LocationContext = React.createContext({}); diff --git a/src/context/locationContext/serverLocation.ts b/src/context/locationContext/serverLocation.ts deleted file mode 100644 index bfed9a7105..0000000000 --- a/src/context/locationContext/serverLocation.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react'; - -import {LocationContextProps, initialLocation} from './locationContext'; - -type Store = {current: LocationContextProps}; - -const createStore = (): (() => Store) => { - const cache = (React as unknown as {cache?: (fn: () => T) => () => T}).cache; - if (typeof cache === 'function') { - return cache(() => ({current: initialLocation})); - } - const singleton: Store = {current: initialLocation}; - return () => singleton; -}; - -const store = createStore(); - -export function getServerLocation(): LocationContextProps { - return store().current; -} - -export function setServerLocation(location: LocationContextProps): void { - store().current = location; -} diff --git a/src/context/locationContext/useLocation.ts b/src/context/locationContext/useLocation.ts deleted file mode 100644 index 3bba84156e..0000000000 --- a/src/context/locationContext/useLocation.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; - -import {LocationContext, LocationContextProps} from './locationContext'; -import {getServerLocation} from './serverLocation'; - -const isRscServer = typeof React.createContext !== 'function'; - -export function useLocation(): LocationContextProps { - if (isRscServer) { - return getServerLocation(); - } - - // eslint-disable-next-line react-hooks/rules-of-hooks - return React.useContext(LocationContext); -} diff --git a/src/context/mobileContext/MobileContext.ts b/src/context/mobileContext/MobileContext.ts index 5028af5355..154ab58277 100644 --- a/src/context/mobileContext/MobileContext.ts +++ b/src/context/mobileContext/MobileContext.ts @@ -1,7 +1,3 @@ import * as React from 'react'; -const isRscServer = typeof React.createContext !== 'function'; - -export const MobileContext = ( - isRscServer ? null : React.createContext(false) -) as React.Context; +export const MobileContext = React.createContext(false); diff --git a/src/context/mobileContext/index.ts b/src/context/mobileContext/index.ts index d9b10abe46..c9a921327a 100644 --- a/src/context/mobileContext/index.ts +++ b/src/context/mobileContext/index.ts @@ -1,3 +1 @@ export * from './MobileContext'; -export * from './useMobile'; -export * from './serverMobile'; diff --git a/src/context/mobileContext/serverMobile.ts b/src/context/mobileContext/serverMobile.ts deleted file mode 100644 index 5c761ae61d..0000000000 --- a/src/context/mobileContext/serverMobile.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; - -type Store = {current: boolean}; - -const createStore = (): (() => Store) => { - const cache = (React as unknown as {cache?: (fn: () => T) => () => T}).cache; - if (typeof cache === 'function') { - return cache(() => ({current: false})); - } - const singleton: Store = {current: false}; - return () => singleton; -}; - -const store = createStore(); - -export function getServerMobile(): boolean { - return store().current; -} - -export function setServerMobile(isMobile: boolean): void { - store().current = isMobile; -} diff --git a/src/context/mobileContext/useMobile.ts b/src/context/mobileContext/useMobile.ts deleted file mode 100644 index d551d357a8..0000000000 --- a/src/context/mobileContext/useMobile.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; - -import {MobileContext} from './MobileContext'; -import {getServerMobile} from './serverMobile'; - -const isRscServer = typeof React.createContext !== 'function'; - -export function useMobile(): boolean { - if (isRscServer) { - return getServerMobile(); - } - - // eslint-disable-next-line react-hooks/rules-of-hooks - return React.useContext(MobileContext); -} diff --git a/src/context/theme/ThemeContext.ts b/src/context/theme/ThemeContext.ts index 54888659af..52d2ae1964 100644 --- a/src/context/theme/ThemeContext.ts +++ b/src/context/theme/ThemeContext.ts @@ -11,8 +11,4 @@ export const initialValue: ThemeContextProps = { theme: DEFAULT_THEME, }; -const isRscServer = typeof React.createContext !== 'function'; - -export const ThemeContext = ( - isRscServer ? null : React.createContext(initialValue) -) as React.Context; +export const ThemeContext = React.createContext(initialValue); diff --git a/src/context/theme/index.ts b/src/context/theme/index.ts index 40a954e48a..cb7dcc6635 100644 --- a/src/context/theme/index.ts +++ b/src/context/theme/index.ts @@ -1,4 +1,3 @@ export * from './ThemeContext'; export * from './useTheme'; -export * from './serverTheme'; export * from './withTheme'; diff --git a/src/context/theme/serverTheme.ts b/src/context/theme/serverTheme.ts deleted file mode 100644 index 89daff2da5..0000000000 --- a/src/context/theme/serverTheme.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; - -import {DEFAULT_THEME} from '../../components/constants'; -import type {Theme} from '../../models'; - -type ThemeStore = {current: Theme}; - -const createStore = (): (() => ThemeStore) => { - const cache = (React as unknown as {cache?: (fn: () => T) => () => T}).cache; - if (typeof cache === 'function') { - return cache(() => ({current: DEFAULT_THEME})); - } - const singleton: ThemeStore = {current: DEFAULT_THEME}; - return () => singleton; -}; - -const store = createStore(); - -export function getServerTheme(): Theme { - return store().current; -} - -export function setServerTheme(theme: Theme): void { - store().current = theme; -} diff --git a/src/context/theme/useTheme.ts b/src/context/theme/useTheme.ts index fe56266bc1..72436bf0d1 100644 --- a/src/context/theme/useTheme.ts +++ b/src/context/theme/useTheme.ts @@ -1,15 +1,9 @@ import * as React from 'react'; import {ThemeContext, ThemeContextProps} from './ThemeContext'; -import {getServerTheme} from './serverTheme'; - -const isRscServer = typeof React.createContext !== 'function'; export function useTheme(): ThemeContextProps['theme'] { - if (isRscServer) { - return getServerTheme(); - } + const {theme} = React.useContext(ThemeContext); - // eslint-disable-next-line react-hooks/rules-of-hooks - return React.useContext(ThemeContext).theme; + return theme; } diff --git a/src/navigation/components/Logo/Logo.tsx b/src/navigation/components/Logo/Logo.tsx index b1e6ffbc4a..c6e474f909 100644 --- a/src/navigation/components/Logo/Logo.tsx +++ b/src/navigation/components/Logo/Logo.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; -import Image from '../../../components/Image/Image'; +import {Image} from '../../../components'; import {getMediaImage} from '../../../components/Media/Image/utils'; import RouterLink from '../../../components/RouterLink/RouterLink'; -import {useLocation} from '../../../context/locationContext'; +import {LocationContext} from '../../../context/locationContext'; import {useTheme} from '../../../context/theme'; import {ThemedNavigationLogoData} from '../../../models'; import {block, getLinkProps, getThemedValue} from '../../../utils'; @@ -21,7 +21,7 @@ export type LogoProps = ThemedNavigationLogoData & { export const Logo = ({alt = i18n('image-alt'), ...restProps}: LogoProps) => { const props: LogoProps = {...restProps, alt}; - const {hostname, Link} = useLocation(); + const {hostname, Link} = React.useContext(LocationContext); const theme = useTheme(); const themedLogoProps = getThemedValue(props, theme) || props; const imageData = getMediaImage(themedLogoProps.icon || props.icon); diff --git a/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx b/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx index 9ce86e30ce..3d912276b3 100644 --- a/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx +++ b/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx @@ -1,10 +1,8 @@ -'use client'; - import * as React from 'react'; import {Bars, Xmark} from '@gravity-ui/icons'; -import Control from '../../../components/Control/Control'; +import {Control} from '../../../components'; import {block} from '../../../utils'; import {MobileMenuButtonProps} from '../../models'; diff --git a/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx b/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx index f0a2cdc104..a89f01f164 100644 --- a/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx +++ b/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx @@ -1,8 +1,6 @@ -'use client'; - import * as React from 'react'; -import Image from '../../../../../components/Image/Image'; +import {Image} from '../../../../../components'; import {ImageProps} from '../../../../../models'; import {block} from '../../../../../utils'; diff --git a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx index 3341b86542..0eded43742 100644 --- a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx @@ -1,7 +1,4 @@ -'use client'; - -import Button from '../../../../../components/Button/Button'; -import RouterLink from '../../../../../components/RouterLink/RouterLink'; +import {Button, RouterLink} from '../../../../../components'; import {BlockIdContext} from '../../../../../context/blockIdContext'; import {ButtonProps} from '../../../../../models'; import {block} from '../../../../../utils'; diff --git a/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx b/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx index cda699567e..e6d4813fb1 100644 --- a/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx @@ -1,9 +1,7 @@ -'use client'; - import * as React from 'react'; +import {ToggleArrow} from '../../../../../components'; import {getMediaImage} from '../../../../../components/Media/Image/utils'; -import ToggleArrow from '../../../../../components/ToggleArrow/ToggleArrow'; import {NavigationDropdownItem} from '../../../../../models'; import {block} from '../../../../../utils'; import {NavigationItemProps} from '../../../../models'; diff --git a/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx b/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx index 78858d7ca4..e47665c29a 100644 --- a/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx @@ -1,9 +1,7 @@ -'use client'; - import * as React from 'react'; +import {RouterLink} from '../../../../../components'; import {getMediaImage} from '../../../../../components/Media/Image/utils'; -import RouterLink from '../../../../../components/RouterLink/RouterLink'; import {LocationContext} from '../../../../../context/locationContext'; import {useAnalytics} from '../../../../../hooks'; import {NavigationArrow} from '../../../../../icons'; diff --git a/src/server-context.ts b/src/server-context.ts deleted file mode 100644 index b7e7ea8905..0000000000 --- a/src/server-context.ts +++ /dev/null @@ -1,4 +0,0 @@ -export {getServerTheme, setServerTheme} from './context/theme/serverTheme'; -export {getServerMobile, setServerMobile} from './context/mobileContext/serverMobile'; -export {getServerLocale, setServerLocale} from './context/localeContext/serverLocale'; -export {getServerLocation, setServerLocation} from './context/locationContext/serverLocation'; diff --git a/src/sub-blocks/BackgroundCard/BackgroundCard.tsx b/src/sub-blocks/BackgroundCard/BackgroundCard.tsx index 35aab957fd..209cd9b4a8 100644 --- a/src/sub-blocks/BackgroundCard/BackgroundCard.tsx +++ b/src/sub-blocks/BackgroundCard/BackgroundCard.tsx @@ -1,5 +1,6 @@ -import BackgroundImage from '../../components/BackgroundImage/BackgroundImage'; -import CardBase from '../../components/CardBase/CardBase'; +import {useUniqId} from '@gravity-ui/uikit'; + +import {BackgroundImage, CardBase} from '../../components/'; import {useTheme} from '../../context/theme'; import {BackgroundCardProps} from '../../models'; import {block, getThemedValue} from '../../utils'; @@ -30,7 +31,7 @@ const BackgroundCard = (props: BackgroundCardProps) => { size = 's', } = props; - const titleId = `pc-background-card-title-${typeof title === 'string' ? title : (title?.text ?? '')}`; + const titleId = useUniqId(); const theme = useTheme(); const hasBackgroundColor = backgroundColor || cardTheme !== 'default'; diff --git a/src/sub-blocks/BasicCard/BasicCard.tsx b/src/sub-blocks/BasicCard/BasicCard.tsx index 1172359690..9125cb6dcc 100644 --- a/src/sub-blocks/BasicCard/BasicCard.tsx +++ b/src/sub-blocks/BasicCard/BasicCard.tsx @@ -1,5 +1,6 @@ -import CardBase from '../../components/CardBase/CardBase'; -import IconWrapper from '../../components/IconWrapper/IconWrapper'; +import {useUniqId} from '@gravity-ui/uikit'; + +import {CardBase, IconWrapper} from '../../components'; import {useTheme} from '../../context/theme'; import {BasicCardProps} from '../../models'; import {IconPosition} from '../../models/constructor-items/sub-blocks'; @@ -27,9 +28,8 @@ const BasicCard = (props: BasicCardProps) => { hoverBackgroundColor, ...cardParams } = props; - const stableKey = typeof title === 'string' ? title : (title?.text ?? ''); - const titleId = `pc-basic-card-title-${stableKey}`; - const descriptionId = `pc-basic-card-desc-${stableKey}`; + const titleId = useUniqId(); + const descriptionId = useUniqId(); const areControlsInFooter = controlPosition === 'footer'; const theme = useTheme(); const themedIcon = getThemedValue(icon, theme); diff --git a/src/sub-blocks/Content/Content.tsx b/src/sub-blocks/Content/Content.tsx index 251bfd7265..9b9238c24e 100644 --- a/src/sub-blocks/Content/Content.tsx +++ b/src/sub-blocks/Content/Content.tsx @@ -1,12 +1,6 @@ -'use client'; - import {useUniqId} from '@gravity-ui/uikit'; -import Buttons from '../../components/Buttons/Buttons'; -import ContentList from '../../components/ContentList/ContentList'; -import Links from '../../components/Links/Links'; -import Title from '../../components/Title/Title'; -import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; +import {Buttons, ContentList, Links, Title, YFMWrapper} from '../../components'; import {Col} from '../../grid'; import { ClassNameProps, diff --git a/src/sub-blocks/ImageCard/ImageCard.tsx b/src/sub-blocks/ImageCard/ImageCard.tsx index 7761ea46ca..e47285f181 100644 --- a/src/sub-blocks/ImageCard/ImageCard.tsx +++ b/src/sub-blocks/ImageCard/ImageCard.tsx @@ -1,10 +1,8 @@ -'use client'; - import * as React from 'react'; import {Link, useUniqId} from '@gravity-ui/uikit'; -import Image from '../../components/Image/Image'; +import {Image} from '../../components'; import {getMediaImage} from '../../components/Media/Image/utils'; import {useTheme} from '../../context/theme'; import {GridColumnSizesType} from '../../grid'; diff --git a/src/sub-blocks/LayoutItem/LayoutItem.tsx b/src/sub-blocks/LayoutItem/LayoutItem.tsx index df1cb1bb40..8bd78976a7 100644 --- a/src/sub-blocks/LayoutItem/LayoutItem.tsx +++ b/src/sub-blocks/LayoutItem/LayoutItem.tsx @@ -1,7 +1,8 @@ -import FullscreenMedia from '../../components/FullscreenMedia/FullscreenMedia'; -import IconWrapper from '../../components/IconWrapper/IconWrapper'; -import Media from '../../components/Media/Media'; -import MetaInfo from '../../components/MetaInfo/MetaInfo'; +import * as React from 'react'; + +import {useUniqId} from '@gravity-ui/uikit'; + +import {FullscreenMedia, IconWrapper, Media, MetaInfo} from '../../components'; import {useTheme} from '../../context/theme'; import {ContentBlockProps, LayoutItemProps} from '../../models'; import {block, getThemedValue} from '../../utils'; @@ -26,7 +27,7 @@ const LayoutItem = ({ analyticsEvents, controlPosition = 'content', }: LayoutItemProps) => { - const normalizedLinks = getLayoutItemLinks(links); + const normalizedLinks = React.useMemo(() => getLayoutItemLinks(links), [links]); const areControlsInFooter = controlPosition === 'footer'; const theme = useTheme(); const themedIcon = getThemedValue(icon, theme); @@ -38,7 +39,7 @@ const LayoutItem = ({ size: content.size || 's', colSizes: {all: 12, md: 12}, }; - const titleId = `pc-layout-item-title-${typeof content.title === 'string' ? content.title : (content.title?.text ?? '')}`; + const titleId = useUniqId(); const renderMedia = () => { if (!media) { return null; diff --git a/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx b/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx index 148598cb81..787a3668ce 100644 --- a/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx +++ b/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx @@ -1,10 +1,8 @@ -'use client'; - import * as React from 'react'; import chunk from 'lodash/chunk'; -import CardBase from '../../../components/CardBase/CardBase'; +import {CardBase} from '../../../components'; import {BREAKPOINTS} from '../../../constants'; import {Col, Grid, GridColumnSize, Row} from '../../../grid'; import { diff --git a/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx b/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx index 49eb92a211..5e56fbf63a 100644 --- a/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx @@ -1,10 +1,8 @@ -'use client'; - import * as React from 'react'; import {Label, LabelProps} from '@gravity-ui/uikit'; -import YFMWrapper from '../../../components/YFMWrapper/YFMWrapper'; +import {YFMWrapper} from '../../../components'; import {StylesContext} from '../../../context/stylesContext'; import { PriceDescriptionColor, diff --git a/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx b/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx index fee7bd0c89..dac4d2a6e6 100644 --- a/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx @@ -1,9 +1,6 @@ -'use client'; - import * as React from 'react'; -import Foldable from '../../../components/Foldable/Foldable'; -import ToggleArrow from '../../../components/ToggleArrow/ToggleArrow'; +import {Foldable, ToggleArrow} from '../../../components'; import { PriceDetailsListProps, PriceDetailsProps, diff --git a/src/sub-blocks/Quote/Quote.tsx b/src/sub-blocks/Quote/Quote.tsx index 0c09e35803..38a65cfeb2 100644 --- a/src/sub-blocks/Quote/Quote.tsx +++ b/src/sub-blocks/Quote/Quote.tsx @@ -1,12 +1,7 @@ -'use client'; - import * as React from 'react'; -import Author from '../../components/Author/Author'; -import Button from '../../components/Button/Button'; -import Image from '../../components/Image/Image'; +import {Author, Button, Image, YFMWrapper} from '../../components'; import {getMediaImage} from '../../components/Media/Image/utils'; -import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {useTheme} from '../../context/theme'; import {useAnalytics} from '../../hooks'; import {AuthorItem, AuthorType, DefaultEventNames, QuoteProps, QuoteType} from '../../models'; From 40a2893f16073153ff6679c1e07c1875694f4d88 Mon Sep 17 00:00:00 2001 From: Krotov Petr <83280920+KrotovPetr@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:46:12 +0300 Subject: [PATCH 5/5] feat: revert server context workaround --- package.json | 8 -------- src/blocks/ContentLayout/ContentLayout.tsx | 2 ++ src/blocks/FilterBlock/FilterBlock.tsx | 2 ++ src/blocks/FoldableList/FoldableList.tsx | 2 ++ .../FoldableListBlockItem/FoldableListBlockItem.tsx | 2 ++ .../Footer/components/CopyrightFloor/CopyrightFloor.tsx | 6 +----- src/blocks/Form/Form.tsx | 2 ++ src/blocks/Header/Header.tsx | 2 ++ src/blocks/Hero/Hero.tsx | 2 ++ src/blocks/Icons/Icons.tsx | 2 ++ .../Questions/QuestionBlockItem/QuestionBlockItem.tsx | 2 ++ src/blocks/Questions/Questions.tsx | 2 ++ src/blocks/Share/Share.tsx | 2 ++ src/blocks/Table/Table.tsx | 2 ++ src/blocks/Tabs/TabContent/TabContent.tsx | 2 ++ src/components/FullscreenImage/FullscreenImage.tsx | 2 ++ src/components/InnerForm/InnerForm.tsx | 2 ++ src/components/Media/Image/Image.tsx | 2 ++ .../components/MobileMenuButton/MobileMenuButton.tsx | 2 ++ .../components/ContentWrapper/ContentWrapper.tsx | 2 ++ .../components/NavigationButton/NavigationButton.tsx | 2 ++ .../components/NavigationDropdown/NavigationDropdown.tsx | 2 ++ .../components/NavigationLink/NavigationLink.tsx | 2 ++ src/sub-blocks/Content/Content.tsx | 2 ++ src/sub-blocks/ImageCard/ImageCard.tsx | 2 ++ .../CombinedPriceDetailed/CombinedPriceDetailed.tsx | 2 ++ .../PriceDetailed/PriceDescription/PriceDescription.tsx | 2 ++ .../PriceDetailed/PriceDetails/PriceDetails.tsx | 2 ++ src/sub-blocks/Quote/Quote.tsx | 2 ++ 29 files changed, 55 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index c38b030201..137cff4a5e 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,6 @@ "require": "./server/index.js", "import": "./server/index.js" }, - "./server-context": { - "types": "./build/esm/server-context.d.ts", - "require": "./build/cjs/server-context.js", - "import": "./build/esm/server-context.js" - }, "./models": { "types": "./build/esm/models/index.d.ts", "require": "./build/cjs/models/index.js", @@ -97,9 +92,6 @@ "server": [ "./server/index.d.ts" ], - "server-context": [ - "./build/esm/server-context.d.ts" - ], "models": [ "./build/esm/models/index.d.ts" ], diff --git a/src/blocks/ContentLayout/ContentLayout.tsx b/src/blocks/ContentLayout/ContentLayout.tsx index a6cfd666c6..645850c2e9 100644 --- a/src/blocks/ContentLayout/ContentLayout.tsx +++ b/src/blocks/ContentLayout/ContentLayout.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BackgroundImage, FileLink} from '../../components'; diff --git a/src/blocks/FilterBlock/FilterBlock.tsx b/src/blocks/FilterBlock/FilterBlock.tsx index 8eb4a81ed4..30f1d52a0d 100644 --- a/src/blocks/FilterBlock/FilterBlock.tsx +++ b/src/blocks/FilterBlock/FilterBlock.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {CardLayoutBlock} from '..'; diff --git a/src/blocks/FoldableList/FoldableList.tsx b/src/blocks/FoldableList/FoldableList.tsx index 86dfafd465..4f406347fa 100644 --- a/src/blocks/FoldableList/FoldableList.tsx +++ b/src/blocks/FoldableList/FoldableList.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Col, Row} from '../../grid'; diff --git a/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx b/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx index 97fb3319a2..d42913cb15 100644 --- a/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx +++ b/src/blocks/FoldableList/FoldableListBlockItem/FoldableListBlockItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useActionHandlers} from '@gravity-ui/uikit'; import {Foldable, ToggleArrow, YFMWrapper} from '../../../components'; diff --git a/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx b/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx index 00bace2091..1b116a12cc 100644 --- a/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx +++ b/src/blocks/Footer/components/CopyrightFloor/CopyrightFloor.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {DropdownMenu, DropdownMenuItem, Link, useResizeObserver} from '@gravity-ui/uikit'; -import Image from '../../../../components/Image/Image'; +import {Image} from '../../../../components'; import {BREAKPOINTS} from '../../../../constants'; import {SSRContext} from '../../../../context/ssrContext'; import {useTheme} from '../../../../context/theme'; @@ -196,10 +196,6 @@ export const CopyrightFloor = ({copyright}: CopyrightFloorProps) => { updateSideColumnWidth(); }, [updateSideColumnWidth]); - React.useEffect(() => { - updateFloorSize(); - }, [updateFloorSize]); - useResizeObserver({ ref: documentElementRef, onResize: updateFloorSize, diff --git a/src/blocks/Form/Form.tsx b/src/blocks/Form/Form.tsx index c5c20dae49..7e0902fb8d 100644 --- a/src/blocks/Form/Form.tsx +++ b/src/blocks/Form/Form.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {BackgroundImage, Title} from '../../components'; diff --git a/src/blocks/Header/Header.tsx b/src/blocks/Header/Header.tsx index ccb9369c83..dc0cd089ef 100644 --- a/src/blocks/Header/Header.tsx +++ b/src/blocks/Header/Header.tsx @@ -1,3 +1,5 @@ +'use client'; + /* eslint-disable complexity */ import * as React from 'react'; diff --git a/src/blocks/Hero/Hero.tsx b/src/blocks/Hero/Hero.tsx index 7b0da2c601..9bda58ff0a 100644 --- a/src/blocks/Hero/Hero.tsx +++ b/src/blocks/Hero/Hero.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {HeaderBreadcrumbs, Media, YFMWrapper} from '../../components'; diff --git a/src/blocks/Icons/Icons.tsx b/src/blocks/Icons/Icons.tsx index e6ce8c667d..30c0180dc1 100644 --- a/src/blocks/Icons/Icons.tsx +++ b/src/blocks/Icons/Icons.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Image, Title} from '../../components'; diff --git a/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx b/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx index c30db81f0f..2478a22569 100644 --- a/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx +++ b/src/blocks/Questions/QuestionBlockItem/QuestionBlockItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useActionHandlers} from '@gravity-ui/uikit'; import {Foldable, ToggleArrow, YFMWrapper} from '../../../components'; diff --git a/src/blocks/Questions/Questions.tsx b/src/blocks/Questions/Questions.tsx index a0160a5df0..2069b3058b 100644 --- a/src/blocks/Questions/Questions.tsx +++ b/src/blocks/Questions/Questions.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Col, Row} from '../../grid'; diff --git a/src/blocks/Share/Share.tsx b/src/blocks/Share/Share.tsx index 0c17d7dd88..d908414730 100644 --- a/src/blocks/Share/Share.tsx +++ b/src/blocks/Share/Share.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Button, Icon} from '@gravity-ui/uikit'; diff --git a/src/blocks/Table/Table.tsx b/src/blocks/Table/Table.tsx index b292709574..acd9e77d77 100644 --- a/src/blocks/Table/Table.tsx +++ b/src/blocks/Table/Table.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Table, YFMWrapper} from '../../components'; diff --git a/src/blocks/Tabs/TabContent/TabContent.tsx b/src/blocks/Tabs/TabContent/TabContent.tsx index bb503316f6..624fceb83a 100644 --- a/src/blocks/Tabs/TabContent/TabContent.tsx +++ b/src/blocks/Tabs/TabContent/TabContent.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {useUniqId} from '@gravity-ui/uikit'; diff --git a/src/components/FullscreenImage/FullscreenImage.tsx b/src/components/FullscreenImage/FullscreenImage.tsx index 7fb6c4c689..51a36bd14a 100644 --- a/src/components/FullscreenImage/FullscreenImage.tsx +++ b/src/components/FullscreenImage/FullscreenImage.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ChevronsExpandUpRight, Xmark} from '@gravity-ui/icons'; diff --git a/src/components/InnerForm/InnerForm.tsx b/src/components/InnerForm/InnerForm.tsx index d9c5249424..e6b7afee86 100644 --- a/src/components/InnerForm/InnerForm.tsx +++ b/src/components/InnerForm/InnerForm.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {YandexForm} from '..'; diff --git a/src/components/Media/Image/Image.tsx b/src/components/Media/Image/Image.tsx index 896dc2d97b..1f1b64bd94 100644 --- a/src/components/Media/Image/Image.tsx +++ b/src/components/Media/Image/Image.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Interpolation, animated, config, useSpring} from '@react-spring/web'; diff --git a/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx b/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx index 3d912276b3..a262afd813 100644 --- a/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx +++ b/src/navigation/components/MobileMenuButton/MobileMenuButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Bars, Xmark} from '@gravity-ui/icons'; diff --git a/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx b/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx index a89f01f164..e493086408 100644 --- a/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx +++ b/src/navigation/components/NavigationItem/components/ContentWrapper/ContentWrapper.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Image} from '../../../../../components'; diff --git a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx index 0eded43742..79ae8ce704 100644 --- a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import {Button, RouterLink} from '../../../../../components'; import {BlockIdContext} from '../../../../../context/blockIdContext'; import {ButtonProps} from '../../../../../models'; diff --git a/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx b/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx index e6d4813fb1..68682db865 100644 --- a/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationDropdown/NavigationDropdown.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {ToggleArrow} from '../../../../../components'; diff --git a/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx b/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx index e47665c29a..50913927e3 100644 --- a/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationLink/NavigationLink.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {RouterLink} from '../../../../../components'; diff --git a/src/sub-blocks/Content/Content.tsx b/src/sub-blocks/Content/Content.tsx index 9b9238c24e..a0ef522bc2 100644 --- a/src/sub-blocks/Content/Content.tsx +++ b/src/sub-blocks/Content/Content.tsx @@ -1,3 +1,5 @@ +'use client'; + import {useUniqId} from '@gravity-ui/uikit'; import {Buttons, ContentList, Links, Title, YFMWrapper} from '../../components'; diff --git a/src/sub-blocks/ImageCard/ImageCard.tsx b/src/sub-blocks/ImageCard/ImageCard.tsx index e47285f181..7b51057fef 100644 --- a/src/sub-blocks/ImageCard/ImageCard.tsx +++ b/src/sub-blocks/ImageCard/ImageCard.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Link, useUniqId} from '@gravity-ui/uikit'; diff --git a/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx b/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx index 787a3668ce..42ca8ad2d3 100644 --- a/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx +++ b/src/sub-blocks/PriceDetailed/CombinedPriceDetailed/CombinedPriceDetailed.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import chunk from 'lodash/chunk'; diff --git a/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx b/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx index 5e56fbf63a..2a5a1a408b 100644 --- a/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDescription/PriceDescription.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Label, LabelProps} from '@gravity-ui/uikit'; diff --git a/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx b/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx index dac4d2a6e6..de1fa81100 100644 --- a/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDetails/PriceDetails.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Foldable, ToggleArrow} from '../../../components'; diff --git a/src/sub-blocks/Quote/Quote.tsx b/src/sub-blocks/Quote/Quote.tsx index 38a65cfeb2..3149981f32 100644 --- a/src/sub-blocks/Quote/Quote.tsx +++ b/src/sub-blocks/Quote/Quote.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import {Author, Button, Image, YFMWrapper} from '../../components';