diff --git a/packages/vkui/src/components/Box/Box.tsx b/packages/vkui/src/components/Box/Box.tsx index 75effe340d9..3b3c0d3ed5a 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,45 @@ 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 { + /** + * Компонент для рендера или массив компонентов для композиции. + * + * Можно передать один компонент или массив компонентов. При передаче массива + * компоненты будут вложены друг в друга справа налево, начиная с `div`. + * + * @example + * // Один компонент + * + * + * @example + * // Массив компонентов — рендер: Wrapper → Link → div + * + */ + Component?: BoxComponent | undefined; /** * Возможность задать css-свойство `display`. */ @@ -24,12 +65,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 + // eslint-disable-next-line react/display-name -- используется defineComponentDisplayNames +> = 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 ; + }, +); + +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 new file mode 100644 index 00000000000..7a160cd2873 --- /dev/null +++ b/packages/vkui/src/components/OnboardingTooltip/OnboardingTooltipFixedContainer.tsx @@ -0,0 +1,19 @@ +'use client'; + +import * as React from 'react'; +import { defineComponentDisplayNames } from '../../lib/react/defineComponentDisplayNames'; +import type { HasComponent } from '../../types'; +import { OnboardingTooltipContainer } from './OnboardingTooltipContainer'; + +type OnboardingTooltipFixedContainerProps = React.HTMLAttributes & HasComponent; + +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/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..3da3a8f9e40 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 { @@ -171,3 +169,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.tsx b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx index d6b2eab4dd2..c986760c0fb 100644 --- a/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx +++ b/packages/vkui/src/components/PanelHeaderContext/PanelHeaderContext.tsx @@ -9,7 +9,9 @@ import { type SizeTypeValues, ViewWidth, type ViewWidthType } from '../../lib/ad 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 styles from './PanelHeaderContext.module.css'; function getViewWidthClassName( @@ -43,6 +45,8 @@ export interface PanelHeaderContextProps extends HTMLAttributesWithRootRef
{ @@ -108,6 +115,6 @@ export const PanelHeaderContext = ({ >
{children}
- +
); };