Adaptive
@cloud-ru/ds-adaptive несёт через React Context текущую раскладку экрана — layoutType (mobile / tablet / desktopSmall / desktop). AdaptiveProvider ставится один раз в корне приложения, а адаптивные компоненты дизайн-системы (Droplist, Modal, Drawer, Dropdown и др.) сами читают раскладку из контекста и переключают поверхность: на mobile обычно открывается BottomSheet, иначе — десктопный popover / modal.
Полное руководство по модели — в паттерне Адаптивность — руководство.
Когда использовать
- Приложение использует адаптивные компоненты дизайн-системы и должно переключать их между mobile и desktop поверхностями. Провайдер ставится один раз в корне.
- Раскладка считается в корне через
useAdaptiveBootstrap()(user-agent + media-query) и раздаётся вложенным компонентам без проброса пропа. - Микрофронт получает раскладку из реактивного стора хост-приложения — провайдер подключается через
store(адаптер собираетсяcreateAdaptiveStore). - Серверный рендеринг: раскладка вычисляется из user-agent запроса через подпуть
@cloud-ru/ds-adaptive/ssr(чистые функции без React Context).
Точечно зафиксировать платформу для поддерева можно вложенным <AdaptiveProvider layoutType=…> или HOC withLayoutType(Component, …) — он затеняет внешний контекст. Пропа layoutType у самих компонентов нет: форс идёт только через контекст.
Установка
pnpm add @cloud-ru/ds-adaptive
import { AdaptiveProvider, withLayoutType, useAdaptiveBootstrap, useAdaptiveLayout, isMobileLayout, LAYOUT_TYPE } from '@cloud-ru/ds-adaptive'
Примеры использования
AdaptiveProvider в корне
useAdaptiveLayout(): desktop
import { AdaptiveProvider, isMobileLayout, LAYOUT_TYPE, LayoutType, useAdaptiveLayout } from '@cloud-ru/ds-adaptive';
import { SegmentControl } from '@cloud-ru/ds-segment-control';
import { Tag } from '@cloud-ru/ds-tag';
import { Typography } from '@cloud-ru/ds-typography';
import { Flex } from '@cloud-ru/ds-uikit-product-flex';
import { useState } from 'react';
const LAYOUT_ITEMS = Object.values(LAYOUT_TYPE).map(value => ({ value, label: value }));
// Потребитель берёт раскладку из AdaptiveProvider через useAdaptiveLayout() — без пропа и обёрток.
// Так же ведут себя Adaptive*-компоненты внутри (на mobile уходят в BottomSheet).
function LayoutSurface() {
const { layoutType } = useAdaptiveLayout();
const mobile = isMobileLayout(layoutType);
return (
<Flex gap='2m' align='center' wrap>
<Tag
appearance={mobile ? 'blue' : 'green'}
label={mobile ? 'Мобильная ветка → BottomSheet' : 'Десктопная ветка'}
/>
<Typography variant='body' size='s'>
useAdaptiveLayout(): {layoutType}
</Typography>
</Flex>
);
}
export function ProviderBasic() {
const [layoutType, setLayoutType] = useState<LayoutType>(LAYOUT_TYPE.Desktop);
return (
<AdaptiveProvider layoutType={layoutType}>
<Flex direction='column' gap='2m' align='flex-start'>
<SegmentControl
items={LAYOUT_ITEMS}
value={layoutType}
onChange={value => setLayoutType(value as LayoutType)}
/>
<LayoutSurface />
</Flex>
</AdaptiveProvider>
);
}Форс раскладки в поддереве
из контекста:
mobileфорс поддерева на desktop:
desktopimport { AdaptiveProvider, isMobileLayout, LAYOUT_TYPE, LayoutType, useAdaptiveLayout } from '@cloud-ru/ds-adaptive';
import { SegmentControl } from '@cloud-ru/ds-segment-control';
import { Tag } from '@cloud-ru/ds-tag';
import { Typography } from '@cloud-ru/ds-typography';
import { Flex } from '@cloud-ru/ds-uikit-product-flex';
import { useState } from 'react';
const CONTEXT_ITEMS = [
{ value: LAYOUT_TYPE.Mobile, label: 'mobile' },
{ value: LAYOUT_TYPE.Desktop, label: 'desktop' },
];
// Раскладка из общего контекста (AdaptiveProvider выше) — следует за переключателем.
function FromContext() {
const { layoutType } = useAdaptiveLayout();
return (
<Flex gap='1m' align='center'>
<Typography variant='body' size='s'>
из контекста:
</Typography>
<Tag appearance={isMobileLayout(layoutType) ? 'blue' : 'green'} label={layoutType} />
</Flex>
);
}
// Форс раскладки в поддереве — вложенный `<AdaptiveProvider layoutType='desktop'>` затеняет внешний
// контекст (то же делает HOC `withLayoutType(Component, 'desktop')` на module-scope). Эта ветка
// остаётся desktop при любом значении переключателя — пропа `layoutType` у компонента нет.
function ForcedDesktop() {
const { layoutType } = useAdaptiveLayout();
return (
<Flex gap='1m' align='center'>
<Typography variant='body' size='s'>
форс поддерева на desktop:
</Typography>
<Tag appearance='green' label={layoutType} />
</Flex>
);
}
export function LayoutTypeOverride() {
const [context, setContext] = useState<LayoutType>(LAYOUT_TYPE.Mobile);
return (
<AdaptiveProvider layoutType={context}>
<Flex direction='column' gap='2m' align='flex-start'>
<SegmentControl items={CONTEXT_ITEMS} value={context} onChange={value => setContext(value as LayoutType)} />
<FromContext />
<AdaptiveProvider layoutType={LAYOUT_TYPE.Desktop}>
<ForcedDesktop />
</AdaptiveProvider>
</Flex>
</AdaptiveProvider>
);
}Свои брейкпоинты приложения
Порог mobile опущен до 480 px. Сузьте окно до этой ширины, чтобы раскладка стала мобильной.
layoutType: desktopimport { AdaptiveProvider, isMobileLayout, useAdaptiveBootstrap, useAdaptiveLayout } from '@cloud-ru/ds-adaptive';
import { Tag } from '@cloud-ru/ds-tag';
import { Typography } from '@cloud-ru/ds-typography';
import { Flex } from '@cloud-ru/ds-uikit-product-flex';
// Раскладка приходит из контекста; Tag перекрашивается, когда ширина окна пересекает порог.
function LayoutSurface() {
const { layoutType } = useAdaptiveLayout();
const mobile = isMobileLayout(layoutType);
return <Tag appearance={mobile ? 'blue' : 'green'} label={`layoutType: ${layoutType}`} />;
}
export function CustomBreakpoints() {
// Брейкпоинты переопределяются на уровне приложения: mobile-порог опущен с 767 до 480 px.
// useAdaptiveBootstrap() читает ширину окна в корне приложения и передаёт результат в AdaptiveProvider.
const { layoutType } = useAdaptiveBootstrap({ breakpoints: { mobile: 480 } });
return (
<AdaptiveProvider layoutType={layoutType}>
<Flex direction='column' gap='2m' align='flex-start'>
<Typography variant='body' size='s'>
Порог mobile опущен до 480 px. Сузьте окно до этой ширины, чтобы раскладка стала мобильной.
</Typography>
<LayoutSurface />
</Flex>
</AdaptiveProvider>
);
}Раскладки и брейкпоинты
layoutType выбирается по ширине окна (max-width в px). Дефолтные пороги:
| layoutType | Порог | Назначение |
|---|---|---|
mobile | ≤ 767 px | Телефон — единственный тир, уходящий в mobile-ветку (BottomSheet). |
tablet | ≤ 1023 px | Планшет — десктопная ветка. |
desktopSmall | ≤ 1279 px | Узкий десктоп — десктопная ветка. |
desktop | ≤ 1439 px | Десктоп — десктопная ветка. SSR-baseline. |
Ширина ≥ 1440 px (large) достижима только через useAdaptiveMatchMedia() — в layoutType она не маппится. Пороги переопределяются на уровне приложения через useAdaptiveBootstrap({ breakpoints }).
На сервере и до монтирования раскладка равна desktop (DEFAULT_LAYOUT_TYPE) — это baseline без доступа к вьюпорту, а не «всегда desktop». На сервере её можно выбрать по request-User-Agent (см. рецепт «Сервер» ниже). Зафиксировать раскладку для поддерева можно вложенным <AdaptiveProvider layoutType=…> или withLayoutType(...) — он затеняет внешний контекст.
Подписка на раскладку
Раскладку вычисляют один раз в корне и раздают через AdaptiveProvider; компоненты её только читают. Утилиты:
Реактивный корень (CSR). useAdaptiveBootstrap() вычисляет layoutType по user-agent + media-query и сам переподписывается на изменения вьюпорта (resize / поворот). Результат скармливается провайдеру:
import { AdaptiveProvider, useAdaptiveBootstrap } from '@cloud-ru/ds-adaptive'
function Root({ app }) {
const { layoutType } = useAdaptiveBootstrap() // ре-вычисляется при смене ширины окна
return <AdaptiveProvider layoutType={layoutType}>{app}</AdaptiveProvider>
}
Чтение в компоненте. useAdaptiveLayout() берёт раскладку из ближайшего AdaptiveProvider и ре-рендерит потребителя при её смене:
import { isMobileLayout, useAdaptiveLayout } from '@cloud-ru/ds-adaptive'
const { layoutType } = useAdaptiveLayout()
if (isMobileLayout(layoutType)) { /* mobile-ветка */ }
Произвольные media-query. Для условий вне layoutType (например, тир large ≥ 1440 px) — useAdaptiveMatchMedia().
Сервер (SSR по User-Agent). На сервере navigator недоступен, поэтому useAdaptiveBootstrap отдаёт desktop. Чтобы убрать flip при гидрации — резолвьте раскладку из request-UA через getAdaptive из серверобезопасного входа @cloud-ru/ds-adaptive/ssr (без React-импортов) и передайте статикой:
import { getAdaptive, INITIAL_ADAPTIVE_QUERIES_VALUE } from '@cloud-ru/ds-adaptive/ssr'
const { layoutType } = getAdaptive(INITIAL_ADAPTIVE_QUERIES_VALUE, headers().get('user-agent'))
// <AdaptiveProvider layoutType={layoutType}>{children}</AdaptiveProvider>
Props
Types
AdaptiveProviderProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
children | string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined | — | yes | |
layoutType | "desktop" | "desktopSmall" | "mobile" | "tablet" | — | no | Статичная раскладка (SSR — значение на запрос, либо `useAdaptiveBootstrap()` в корне CSR). Реактивный источник — через `store`. |
store | AdaptiveStore | — | no | Внешний реактивный стор раскладки; приоритетнее `layoutType`, обновляет подписчиков без перерендера провайдера. |
Unions
Types
AdaptiveProviderProps
AdaptiveStore
Unions
LayoutType
Смотри также
- Адаптивность — руководство — полная модель адаптивных компонентов
@ds. - Оформление — тема, бренд, плотность — модель провайдера темы.
- Локализация — строки в пакетах — модель провайдера локали.
- Порталы — корневой DOM-узел — модель провайдера портала.
- Locale — рантайм локализации.
- PortalContext — корневой DOM-узел для порталов.