From 5547445f1778f208f231df5837673e24fa8426fd Mon Sep 17 00:00:00 2001 From: "e.mukhametkhanov" Date: Fri, 24 Apr 2026 17:53:23 +0300 Subject: [PATCH 1/4] feat(PanelHeader,PanelHeaderContext): remove FixedLayout using --- packages/vkui/src/components/Box/Box.tsx | 43 ++++++++++- .../FixedLayout/SplitColWidthWrapper.tsx | 41 ++++++++++ .../OnboardingTooltipFixedContainer.tsx | 13 ++++ .../components/PanelHeader/PanelHeader.tsx | 12 ++- .../PanelHeaderContext.module.css | 15 ++-- .../PanelHeaderContext.test.tsx | 15 ++-- .../PanelHeaderContext/PanelHeaderContext.tsx | 74 +++++++++++++------ 7 files changed, 174 insertions(+), 39 deletions(-) create mode 100644 packages/vkui/src/components/FixedLayout/SplitColWidthWrapper.tsx create mode 100644 packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipFixedContainer.tsx diff --git a/packages/vkui/src/components/Box/Box.tsx b/packages/vkui/src/components/Box/Box.tsx index 75effe340d9..cffbbd58b41 100644 --- a/packages/vkui/src/components/Box/Box.tsx +++ b/packages/vkui/src/components/Box/Box.tsx @@ -1,3 +1,6 @@ +'use client'; + +import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; import { resolveLayoutProps } from '../../lib/layouts'; import type { LayoutProps } from '../../lib/layouts/types'; @@ -12,7 +15,34 @@ const displayClassNames = { 'contents': styles.displayContents, }; -export interface BoxProps extends RootComponentProps, LayoutProps { +type BoxComponent = RootComponentProps['Component'] | React.ElementType[]; + +function composeComponents( + component: BoxComponent | undefined, +): RootComponentProps['Component'] { + if (!Array.isArray(component)) { + return component; + } + + if (component.length === 0) { + return undefined; + } + + return component.reduceRight( + (InnerComponent, WrapperComponent) => + // eslint-disable-next-line react/display-name -- динамическая композиция обёрток + React.forwardRef>((props, ref) => ( + + )), + 'div', + ); +} + +export interface BoxProps extends Omit, 'Component'>, LayoutProps { + /** + * + */ + Component?: BoxComponent | undefined; /** * Возможность задать css-свойство `display`. */ @@ -24,12 +54,21 @@ export interface BoxProps extends RootComponentProps, LayoutProps { * * @since 7.9.0 */ -export const Box = ({ className, style, display, ...restProps }: BoxProps) => { +export const Box = ({ + className, + style, + display, + Component: ComponentProp, + ...restProps +}: BoxProps) => { const resolvedProps = resolveLayoutProps(restProps); + const Component = React.useMemo(() => composeComponents(ComponentProp), [ComponentProp]); + return ( & HasComponent; + +export const SplitColWidthWrapper: React.ForwardRefExoticComponent< + React.PropsWithoutRef & React.RefAttributes +> = React.forwardRef( + ({ Component = 'div', style, ...restProps }, forwardedRef) => { + const platform = usePlatform(); + const { colRef } = React.useContext(SplitColContext); + const [width, setWidth] = React.useState(undefined); + + const doResize = React.useCallback(() => { + if (!colRef?.current) { + setWidth(undefined); + return; + } + + const computedStyle = getComputedStyle(colRef.current); + + setWidth( + `${ + colRef.current.clientWidth - + parseFloat(computedStyle.paddingLeft || '0') - + parseFloat(computedStyle.paddingRight || '0') + }px`, + ); + }, [colRef]); + + React.useEffect(doResize, [doResize, platform]); + useResizeObserver(colRef, doResize); + + return ; + }, +); diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipFixedContainer.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipFixedContainer.tsx new file mode 100644 index 00000000000..7b46a313a09 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipFixedContainer.tsx @@ -0,0 +1,13 @@ +'use client'; + +import * as React from 'react'; +import type { HasComponent } from '../../types'; +import { OnboardingTooltipContainer } from './OnboardingTooltipContainer'; + +type OnboardingTooltipFixedContainerProps = React.HTMLAttributes & HasComponent; + +export const OnboardingTooltipFixedContainer: React.ForwardRefExoticComponent< + React.PropsWithoutRef & React.RefAttributes +> = React.forwardRef((props, ref) => ( + +)); diff --git a/packages/vkui/src/components/PanelHeader/PanelHeader.tsx b/packages/vkui/src/components/PanelHeader/PanelHeader.tsx index cb1f3b9fe10..adb0ad60ee4 100644 --- a/packages/vkui/src/components/PanelHeader/PanelHeader.tsx +++ b/packages/vkui/src/components/PanelHeader/PanelHeader.tsx @@ -16,8 +16,9 @@ import type { HasRef, HTMLAttributesWithRootRef, } from '../../types'; +import { Box } from '../Box/Box'; import { useConfigProvider } from '../ConfigProvider/ConfigProviderContext'; -import { FixedLayout } from '../FixedLayout/FixedLayout'; +import { SplitColWidthWrapper } from '../FixedLayout/SplitColWidthWrapper'; import { OnboardingTooltipContainer } from '../OnboardingTooltip/OnboardingTooltipContainer'; import { RootComponent } from '../RootComponent/RootComponent'; import { Separator } from '../Separator/Separator'; @@ -201,15 +202,18 @@ export const PanelHeader = ({ getRootRef={isFixed ? getRootRef : getRef} > {isFixed ? ( - {children} - + ) : ( {children} diff --git a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.module.css b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.module.css index ba8ddf16ee1..94226afe4da 100644 --- a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.module.css +++ b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.module.css @@ -2,8 +2,6 @@ --vkui_internal--PanelHeaderContext__fade_display: none; z-index: var(--vkui_internal--z_index_panel_header_context); - inline-size: 100%; - block-size: auto; } .viewWidthSmallTabletMinus { @@ -21,9 +19,6 @@ } .in { - position: absolute; - inset-inline-start: 0; - z-index: 1; box-sizing: border-box; inline-size: 100%; padding: 8px; @@ -171,3 +166,13 @@ opacity: 0; } } + +/* stylelint-disable selector-max-universal, selector-pseudo-class-disallowed-list */ +:global(.vkuiInternalPanelHeader) ~ .host, +:global(.vkuiInternalPanelHeader) ~ * .host { + inset-block-start: calc( + var(--vkui_internal--panel_header_height) + + var(--vkui_internal--safe_area_inset_top) + ); +} +/* stylelint-enable selector-max-universal, selector-pseudo-class-disallowed-list */ diff --git a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx index 703fd562336..ff9b3b5d081 100644 --- a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx +++ b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx @@ -1,10 +1,9 @@ -import { render, screen } from '@testing-library/react'; +import {fireEvent, render, screen} from '@testing-library/react'; import { noop } from '@vkontakte/vkjs'; import { ViewWidth } from '../../lib/adaptivity'; import { baselineComponent, - userEvent, - waitCSSKeyframesAnimation, + waitForFloatingPosition, withFakeTimers, } from '../../testing/utils'; import { AdaptivityProvider } from '../AdaptivityProvider/AdaptivityProvider'; @@ -32,7 +31,7 @@ describe('PanelHeaderContext', () => { , ); - await userEvent.click(document.body); + fireEvent.click(document.body); expect(onClose).toHaveBeenCalledTimes(1); }), ); @@ -42,7 +41,7 @@ describe('PanelHeaderContext', () => { withFakeTimers(async () => { const onClose = vi.fn(); render(); - await userEvent.click( + fireEvent.click( document.querySelector(`.${panelHeaderContextStyles.fade}`) as Element, ); expect(onClose).toHaveBeenCalledTimes(1); @@ -58,7 +57,7 @@ describe('PanelHeaderContext', () => {
, ); - await userEvent.click(screen.getByTestId('xxx')); + fireEvent.click(screen.getByTestId('xxx')); expect(onClose).not.toHaveBeenCalled(); }), ); @@ -69,12 +68,14 @@ describe('PanelHeaderContext', () => {
, ); + expect(screen.queryByTestId('xxx')).not.toBeNull(); result.rerender(
, ); - await waitCSSKeyframesAnimation(result.getByTestId('content')); + + await waitForFloatingPosition(); expect(screen.queryByTestId('xxx')).toBeNull(); }); }); diff --git a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx index d6b2eab4dd2..68c8b3f4041 100644 --- a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx +++ b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx @@ -6,10 +6,12 @@ import { useAdaptivity } from '../../hooks/useAdaptivity'; import { useGlobalOnClickOutside } from '../../hooks/useGlobalOnClickOutside'; import { usePlatform } from '../../hooks/usePlatform'; import { type SizeTypeValues, ViewWidth, type ViewWidthType } from '../../lib/adaptivity'; -import { useCSSKeyframesAnimationController } from '../../lib/animation'; import type { HTMLAttributesWithRootRef } from '../../types'; import { useScrollLock } from '../AppRoot/ScrollContext'; -import { FixedLayout } from '../FixedLayout/FixedLayout'; +import { Box } from '../Box/Box'; +import { SplitColWidthWrapper } from '../FixedLayout/SplitColWidthWrapper'; +import { OnboardingTooltipFixedContainer } from '../OnboardingTooltip/OnboardingTooltipFixedContainer'; +import { Popover } from '../Popover/Popover'; import styles from './PanelHeaderContext.module.css'; function getViewWidthClassName( @@ -43,6 +45,8 @@ export interface PanelHeaderContextProps extends HTMLAttributesWithRootRef { const platform = usePlatform(); const { sizeX: legacySizeX, viewWidth = 'none' } = useAdaptivity(); - const elementRef = React.useRef(null); - const [animationState, animationHandlers] = useCSSKeyframesAnimationController( - opened ? 'enter' : 'exit', - undefined, - true, - ); - const visible = animationState !== 'exited'; + const [visible, setVisible] = React.useState(opened); + const [prevOpened, setPrevOpened] = React.useState(opened); + const anchorRef = React.useRef(null); + const popoverRef = React.useRef(null); - useScrollLock(platform !== 'vkcom' && visible); + if (prevOpened !== opened) { + if (opened) { + setVisible(true); + } + setPrevOpened(opened); + } const handleGlobalOnClickOutside = React.useCallback( (event: MouseEvent) => { @@ -75,15 +81,21 @@ export const PanelHeaderContext = ({ [opened, onClose], ); - useGlobalOnClickOutside(handleGlobalOnClickOutside, visible ? elementRef : null); + useScrollLock(platform !== 'vkcom' && visible); + + useGlobalOnClickOutside( + handleGlobalOnClickOutside, + visible ? anchorRef : null, + visible ? popoverRef : null, + ); if (!visible) { return null; } return ( -
{ @@ -100,14 +115,31 @@ export const PanelHeaderContext = ({ }} className={styles.fade} /> -
{children}
} + sameWidth + usePortal={false} + zIndex={1} + noStyling className={styles.in} - ref={elementRef} - {...animationHandlers} + offsetByMainAxis={0} + onShownChanged={(shown) => { + if (!shown) { + setVisible(false); + } + }} + getRootRef={popoverRef} > -
{children}
-
-
+ + + ); }; From 84be8fbf094b36dab013c6c3418d16ff10e1e3e9 Mon Sep 17 00:00:00 2001 From: "e.mukhametkhanov" Date: Mon, 27 Apr 2026 18:00:31 +0300 Subject: [PATCH 2/4] fix: fix formatting --- .../components/FixedLayout/SplitColWidthWrapper.tsx | 6 ++++++ .../OnboardingTooltipFixedContainer.tsx | 6 ++++++ .../PanelHeaderContext/PanelHeaderContext.test.tsx | 12 +++--------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/vkui/src/components/FixedLayout/SplitColWidthWrapper.tsx b/packages/vkui/src/components/FixedLayout/SplitColWidthWrapper.tsx index f5c9c445a6b..908c9cc5890 100644 --- a/packages/vkui/src/components/FixedLayout/SplitColWidthWrapper.tsx +++ b/packages/vkui/src/components/FixedLayout/SplitColWidthWrapper.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { usePlatform } from '../../hooks/usePlatform'; import { useResizeObserver } from '../../hooks/useResizeObserver'; +import { defineComponentDisplayNames } from '../../lib/react/defineComponentDisplayNames'; import type { HasComponent } from '../../types'; import { SplitColContext } from '../SplitCol/SplitColContext'; @@ -10,6 +11,7 @@ type SplitColWidthWrapperProps = React.HTMLAttributes & HasComponen export const SplitColWidthWrapper: React.ForwardRefExoticComponent< React.PropsWithoutRef & React.RefAttributes + // eslint-disable-next-line react/display-name -- используется defineComponentDisplayNames > = React.forwardRef( ({ Component = 'div', style, ...restProps }, forwardedRef) => { const platform = usePlatform(); @@ -39,3 +41,7 @@ export const SplitColWidthWrapper: React.ForwardRefExoticComponent< return ; }, ); + +if (process.env.NODE_ENV !== 'production') { + defineComponentDisplayNames(SplitColWidthWrapper, 'SplitColWidthWrapper'); +} diff --git a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipFixedContainer.tsx b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipFixedContainer.tsx index 7b46a313a09..7a160cd2873 100644 --- a/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipFixedContainer.tsx +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipFixedContainer.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; +import { defineComponentDisplayNames } from '../../lib/react/defineComponentDisplayNames'; import type { HasComponent } from '../../types'; import { OnboardingTooltipContainer } from './OnboardingTooltipContainer'; @@ -8,6 +9,11 @@ type OnboardingTooltipFixedContainerProps = React.HTMLAttributes export const OnboardingTooltipFixedContainer: React.ForwardRefExoticComponent< React.PropsWithoutRef & React.RefAttributes + // eslint-disable-next-line react/display-name -- используется defineComponentDisplayNames > = React.forwardRef((props, ref) => ( )); + +if (process.env.NODE_ENV !== 'production') { + defineComponentDisplayNames(OnboardingTooltipFixedContainer, 'OnboardingTooltipFixedContainer'); +} diff --git a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx index ff9b3b5d081..81735afb8df 100644 --- a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx +++ b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx @@ -1,11 +1,7 @@ -import {fireEvent, render, screen} from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { noop } from '@vkontakte/vkjs'; import { ViewWidth } from '../../lib/adaptivity'; -import { - baselineComponent, - waitForFloatingPosition, - withFakeTimers, -} from '../../testing/utils'; +import { baselineComponent, waitForFloatingPosition, withFakeTimers } from '../../testing/utils'; import { AdaptivityProvider } from '../AdaptivityProvider/AdaptivityProvider'; import { PanelHeaderContext } from './PanelHeaderContext'; import panelHeaderContextStyles from './PanelHeaderContext.module.css'; @@ -41,9 +37,7 @@ describe('PanelHeaderContext', () => { withFakeTimers(async () => { const onClose = vi.fn(); render(); - fireEvent.click( - document.querySelector(`.${panelHeaderContextStyles.fade}`) as Element, - ); + fireEvent.click(document.querySelector(`.${panelHeaderContextStyles.fade}`) as Element); expect(onClose).toHaveBeenCalledTimes(1); }), ); From 8049d1418aecd85614b09e530fa399d6ef1bc804 Mon Sep 17 00:00:00 2001 From: "e.mukhametkhanov" Date: Mon, 25 May 2026 11:52:38 +0300 Subject: [PATCH 3/4] fix(PanelHeaderContext): revert Popover usage --- .../PanelHeaderContext.module.css | 3 + .../PanelHeaderContext.test.tsx | 21 ++++--- .../PanelHeaderContext/PanelHeaderContext.tsx | 57 ++++++------------- 3 files changed, 32 insertions(+), 49 deletions(-) diff --git a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.module.css b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.module.css index 94226afe4da..3da3a8f9e40 100644 --- a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.module.css +++ b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.module.css @@ -19,6 +19,9 @@ } .in { + position: absolute; + inset-inline-start: 0; + z-index: 1; box-sizing: border-box; inline-size: 100%; padding: 8px; diff --git a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx index 81735afb8df..703fd562336 100644 --- a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx +++ b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.test.tsx @@ -1,7 +1,12 @@ -import { fireEvent, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { noop } from '@vkontakte/vkjs'; import { ViewWidth } from '../../lib/adaptivity'; -import { baselineComponent, waitForFloatingPosition, withFakeTimers } from '../../testing/utils'; +import { + baselineComponent, + userEvent, + waitCSSKeyframesAnimation, + withFakeTimers, +} from '../../testing/utils'; import { AdaptivityProvider } from '../AdaptivityProvider/AdaptivityProvider'; import { PanelHeaderContext } from './PanelHeaderContext'; import panelHeaderContextStyles from './PanelHeaderContext.module.css'; @@ -27,7 +32,7 @@ describe('PanelHeaderContext', () => { , ); - fireEvent.click(document.body); + await userEvent.click(document.body); expect(onClose).toHaveBeenCalledTimes(1); }), ); @@ -37,7 +42,9 @@ describe('PanelHeaderContext', () => { withFakeTimers(async () => { const onClose = vi.fn(); render(); - fireEvent.click(document.querySelector(`.${panelHeaderContextStyles.fade}`) as Element); + await userEvent.click( + document.querySelector(`.${panelHeaderContextStyles.fade}`) as Element, + ); expect(onClose).toHaveBeenCalledTimes(1); }), ); @@ -51,7 +58,7 @@ describe('PanelHeaderContext', () => {
, ); - fireEvent.click(screen.getByTestId('xxx')); + await userEvent.click(screen.getByTestId('xxx')); expect(onClose).not.toHaveBeenCalled(); }), ); @@ -62,14 +69,12 @@ describe('PanelHeaderContext', () => {
, ); - expect(screen.queryByTestId('xxx')).not.toBeNull(); result.rerender(
, ); - - await waitForFloatingPosition(); + await waitCSSKeyframesAnimation(result.getByTestId('content')); expect(screen.queryByTestId('xxx')).toBeNull(); }); }); diff --git a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx index 68c8b3f4041..c986760c0fb 100644 --- a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx +++ b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx @@ -6,12 +6,12 @@ import { useAdaptivity } from '../../hooks/useAdaptivity'; import { useGlobalOnClickOutside } from '../../hooks/useGlobalOnClickOutside'; import { usePlatform } from '../../hooks/usePlatform'; import { type SizeTypeValues, ViewWidth, type ViewWidthType } from '../../lib/adaptivity'; +import { useCSSKeyframesAnimationController } from '../../lib/animation'; import type { HTMLAttributesWithRootRef } from '../../types'; import { useScrollLock } from '../AppRoot/ScrollContext'; import { Box } from '../Box/Box'; import { SplitColWidthWrapper } from '../FixedLayout/SplitColWidthWrapper'; import { OnboardingTooltipFixedContainer } from '../OnboardingTooltip/OnboardingTooltipFixedContainer'; -import { Popover } from '../Popover/Popover'; import styles from './PanelHeaderContext.module.css'; function getViewWidthClassName( @@ -59,17 +59,15 @@ export const PanelHeaderContext = ({ }: PanelHeaderContextProps): React.ReactNode => { const platform = usePlatform(); const { sizeX: legacySizeX, viewWidth = 'none' } = useAdaptivity(); - const [visible, setVisible] = React.useState(opened); - const [prevOpened, setPrevOpened] = React.useState(opened); - const anchorRef = React.useRef(null); - const popoverRef = React.useRef(null); + const elementRef = React.useRef(null); + const [animationState, animationHandlers] = useCSSKeyframesAnimationController( + opened ? 'enter' : 'exit', + undefined, + true, + ); + const visible = animationState !== 'exited'; - if (prevOpened !== opened) { - if (opened) { - setVisible(true); - } - setPrevOpened(opened); - } + useScrollLock(platform !== 'vkcom' && visible); const handleGlobalOnClickOutside = React.useCallback( (event: MouseEvent) => { @@ -81,13 +79,7 @@ export const PanelHeaderContext = ({ [opened, onClose], ); - useScrollLock(platform !== 'vkcom' && visible); - - useGlobalOnClickOutside( - handleGlobalOnClickOutside, - visible ? anchorRef : null, - visible ? popoverRef : null, - ); + useGlobalOnClickOutside(handleGlobalOnClickOutside, visible ? elementRef : null); if (!visible) { return null; @@ -115,31 +107,14 @@ export const PanelHeaderContext = ({ }} className={styles.fade} /> - {children}
} - sameWidth - usePortal={false} - zIndex={1} - noStyling +
{ - if (!shown) { - setVisible(false); - } - }} - getRootRef={popoverRef} + ref={elementRef} + {...animationHandlers} > - - +
{children}
+
); }; From cc7ba10a2bb1d418227c7d3c538978b23fde7f30 Mon Sep 17 00:00:00 2001 From: "e.mukhametkhanov" Date: Mon, 25 May 2026 11:57:39 +0300 Subject: [PATCH 4/4] fix: update JSDoc --- packages/vkui/src/components/Box/Box.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/vkui/src/components/Box/Box.tsx b/packages/vkui/src/components/Box/Box.tsx index cffbbd58b41..3b3c0d3ed5a 100644 --- a/packages/vkui/src/components/Box/Box.tsx +++ b/packages/vkui/src/components/Box/Box.tsx @@ -40,7 +40,18 @@ function composeComponents( export interface BoxProps extends Omit, 'Component'>, LayoutProps { /** + * Компонент для рендера или массив компонентов для композиции. * + * Можно передать один компонент или массив компонентов. При передаче массива + * компоненты будут вложены друг в друга справа налево, начиная с `div`. + * + * @example + * // Один компонент + * + * + * @example + * // Массив компонентов — рендер: Wrapper → Link → div + * */ Component?: BoxComponent | undefined; /**