Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions packages/vkui/src/components/Box/Box.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -12,7 +15,45 @@ const displayClassNames = {
'contents': styles.displayContents,
};

export interface BoxProps extends RootComponentProps<HTMLElement>, LayoutProps {
type BoxComponent = RootComponentProps<HTMLElement>['Component'] | React.ElementType[];

function composeComponents(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется слишком мощная затея + пересекается #9918

component: BoxComponent | undefined,
): RootComponentProps<HTMLElement>['Component'] {
if (!Array.isArray(component)) {
return component;
}

if (component.length === 0) {
return undefined;
}

return component.reduceRight<React.ElementType>(
(InnerComponent, WrapperComponent) =>
// eslint-disable-next-line react/display-name -- динамическая композиция обёрток
React.forwardRef<HTMLElement, RootComponentProps<HTMLElement>>((props, ref) => (
<WrapperComponent {...props} ref={ref} Component={InnerComponent} />
)),
'div',
);
}

export interface BoxProps extends Omit<RootComponentProps<HTMLElement>, 'Component'>, LayoutProps {
/**
* Компонент для рендера или массив компонентов для композиции.
*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пустой комментарий

* Можно передать один компонент или массив компонентов. При передаче массива
* компоненты будут вложены друг в друга справа налево, начиная с `div`.
*
* @example
* // Один компонент
* <Box Component="section" />
*
* @example
* // Массив компонентов — рендер: Wrapper → Link → div
* <Box Component={[Wrapper, Link]} />
*/
Component?: BoxComponent | undefined;
/**
* Возможность задать css-свойство `display`.
*/
Expand All @@ -24,12 +65,21 @@ export interface BoxProps extends RootComponentProps<HTMLElement>, 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 (
<RootComponent
{...resolvedProps}
Component={Component}
baseClassName={resolvedProps.className}
baseStyle={resolvedProps.style}
className={classNames(className, display && displayClassNames[display])}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use client';

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';

type SplitColWidthWrapperProps = React.HTMLAttributes<HTMLElement> & HasComponent;

export const SplitColWidthWrapper: React.ForwardRefExoticComponent<
React.PropsWithoutRef<SplitColWidthWrapperProps> & React.RefAttributes<HTMLElement>
// eslint-disable-next-line react/display-name -- используется defineComponentDisplayNames
> = React.forwardRef<HTMLElement, SplitColWidthWrapperProps>(
({ Component = 'div', style, ...restProps }, forwardedRef) => {
const platform = usePlatform();
const { colRef } = React.useContext(SplitColContext);
const [width, setWidth] = React.useState<string | undefined>(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 <Component {...restProps} ref={forwardedRef} style={{ width, ...style }} />;
},
);

if (process.env.NODE_ENV !== 'production') {
defineComponentDisplayNames(SplitColWidthWrapper, 'SplitColWidthWrapper');
}
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> & HasComponent;

export const OnboardingTooltipFixedContainer: React.ForwardRefExoticComponent<
React.PropsWithoutRef<OnboardingTooltipFixedContainerProps> & React.RefAttributes<HTMLDivElement>
// eslint-disable-next-line react/display-name -- используется defineComponentDisplayNames
> = React.forwardRef<HTMLDivElement, OnboardingTooltipFixedContainerProps>((props, ref) => (
<OnboardingTooltipContainer {...props} fixed ref={ref} />
));

if (process.env.NODE_ENV !== 'production') {
defineComponentDisplayNames(OnboardingTooltipFixedContainer, 'OnboardingTooltipFixedContainer');
}
12 changes: 8 additions & 4 deletions packages/vkui/src/components/PanelHeader/PanelHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -201,15 +202,18 @@ export const PanelHeader = ({
getRootRef={isFixed ? getRootRef : getRef}
>
{isFixed ? (
<FixedLayout
<Box
Component={SplitColWidthWrapper}
className={classNames(styles.fixed, 'vkuiInternalPanelHeader__fixed')}
vertical="top"
position="fixed"
insetBlockStart={0}
inlineSize="100%"
getRootRef={getRef}
>
<PanelHeaderIn before={before} after={after} typographyProps={typographyProps}>
{children}
</PanelHeaderIn>
</FixedLayout>
</Box>
) : (
<PanelHeaderIn before={before} after={after} typographyProps={typographyProps}>
{children}
Expand Down
Comment thread
SevereCloud marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 */
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -43,6 +45,8 @@ export interface PanelHeaderContextProps extends HTMLAttributesWithRootRef<HTMLD
onClose: VoidFunction;
}

const ComponentDecorators = [SplitColWidthWrapper, OnboardingTooltipFixedContainer];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Есть кейсы когда для PanelHeaderContext нужен OnboardingTooltipFixedContainer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сложно сказать. Я в целом не особо вижу смысл в компоненте OnboardingTooltipContainer. Так сделал скорее, чтобы старое поведение сохранить и ничего не сломать


/**
* @see https://vkui.io/components/panel-header-context
*/
Expand Down Expand Up @@ -82,16 +86,19 @@ export const PanelHeaderContext = ({
}

return (
<FixedLayout
{...restProps}
<Box
Component={ComponentDecorators}
className={classNames(
styles.host,
platform === 'ios' && styles.ios,
opened ? styles.opened : styles.closing,
getViewWidthClassName(viewWidth, legacySizeX),
className,
)}
vertical="top"
position="fixed"
inlineSize="100%"
insetBlockStart={0}
{...restProps}
>
<div
onClick={(event) => {
Expand All @@ -108,6 +115,6 @@ export const PanelHeaderContext = ({
>
<div className={styles.content}>{children}</div>
</div>
</FixedLayout>
</Box>
);
};
Loading