-
Notifications
You must be signed in to change notification settings - Fork 201
feat: add render prop #9918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: add render prop #9918
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,68 @@ | ||
| import { createRef } from 'react'; | ||
| import { render, screen } from '@testing-library/react'; | ||
| import { baselineComponent } from '../../testing/utils'; | ||
| import { RootComponent } from './RootComponent'; | ||
|
|
||
| describe('RootComponent', () => { | ||
| baselineComponent(RootComponent); | ||
|
|
||
| describe('render prop', () => { | ||
| it('uses render instead of Component', () => { | ||
| render( | ||
| <RootComponent | ||
| Component="div" | ||
| render={({ getRootRef, ...props }) => <span data-testid="rendered" {...props} />} | ||
| />, | ||
| ); | ||
|
|
||
| const node = screen.getByTestId('rendered'); | ||
| expect(node.tagName).toBe('SPAN'); | ||
| }); | ||
|
|
||
| it('passes resolved className and style to render', () => { | ||
| render( | ||
| <RootComponent | ||
| className="customClassName" | ||
| baseClassName="baseClassName" | ||
| style={{ color: 'red' }} | ||
| baseStyle={{ backgroundColor: 'blue' }} | ||
| render={({ getRootRef, ...props }) => <div data-testid="rendered" {...props} />} | ||
| />, | ||
| ); | ||
|
|
||
| const node = screen.getByTestId('rendered'); | ||
| expect(node.classList.contains('customClassName')).toBe(true); | ||
| expect(node.classList.contains('baseClassName')).toBe(true); | ||
| expect(node.style.color).toBe('red'); | ||
| expect(node.style.backgroundColor).toBe('blue'); | ||
| }); | ||
|
|
||
| it('passes rest props to render', () => { | ||
| render( | ||
| <RootComponent | ||
| id="custom-id" | ||
| aria-label="label" | ||
| render={({ getRootRef, ...props }) => <div data-testid="rendered" {...props} />} | ||
| />, | ||
| ); | ||
|
|
||
| const node = screen.getByTestId('rendered'); | ||
| expect(node.id).toBe('custom-id'); | ||
| expect(node.getAttribute('aria-label')).toBe('label'); | ||
| }); | ||
|
|
||
| it('forwards getRootRef through render', () => { | ||
| const ref = createRef<HTMLDivElement>(); | ||
| render( | ||
| <RootComponent | ||
| getRootRef={ref} | ||
| render={({ getRootRef, ...props }) => ( | ||
| <div data-testid="rendered" ref={getRootRef} {...props} /> | ||
| )} | ||
| />, | ||
| ); | ||
|
|
||
| expect(ref.current).toBe(screen.getByTestId('rendered')); | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,13 @@ | ||
| import type * as React from 'react'; | ||
| import { classNames } from '@vkontakte/vkjs'; | ||
| import { mergeStyle } from '../../helpers/mergeStyle'; | ||
| import type { HasComponent, HasRootRef } from '../../types'; | ||
| import type { HasComponent, HasRender, HasRootRef } from '../../types'; | ||
| import styles from './RootComponent.module.css'; | ||
|
|
||
| export interface RootComponentProps<T> | ||
| extends React.AllHTMLAttributes<T>, | ||
| HasRootRef<T>, | ||
| HasComponent {} | ||
| export type RootComponentProps<T> = React.AllHTMLAttributes<T> & | ||
| HasRootRef<T> & | ||
| HasComponent & | ||
|
inomdzhon marked this conversation as resolved.
|
||
| HasRender<T>; | ||
|
|
||
| export interface RootComponentExtendProps { | ||
| /** | ||
|
|
@@ -24,6 +24,19 @@ export interface RootComponentInternalProps<T> | |
| extends RootComponentProps<T>, | ||
| RootComponentExtendProps {} | ||
|
|
||
| interface RenderRootComponentProps<T> extends Omit<RootComponentInternalProps<T>, 'render'> { | ||
| /** | ||
| * Функция кастомного рендера. См. `RootComponentProps['render']`. | ||
| */ | ||
| render: NonNullable<RootComponentProps<T>['render']>; | ||
| } | ||
|
|
||
| const RenderRootComponent = <T,>({ | ||
| render, | ||
| getRootRef, | ||
| ...restProps | ||
| }: RenderRootComponentProps<T>): React.ReactNode => render({ ...restProps, getRootRef }); | ||
|
|
||
| /** | ||
| * Базовый корневой компонент. | ||
| */ | ||
|
|
@@ -34,17 +47,23 @@ export const RootComponent = <T,>({ | |
| baseStyle, | ||
| style, | ||
| getRootRef, | ||
| render, | ||
| ...restProps | ||
| }: RootComponentInternalProps<T>): React.ReactNode => ( | ||
| <Component | ||
| ref={getRootRef} | ||
| className={classNames( | ||
| }: RootComponentInternalProps<T>): React.ReactNode => { | ||
| const resolvedProps = { | ||
| className: classNames( | ||
| className, | ||
| baseClassName, | ||
| styles.host, | ||
| restProps.hidden === true && styles.hidden, | ||
| )} | ||
| style={mergeStyle(baseStyle, style)} | ||
| {...restProps} | ||
| /> | ||
| ); | ||
| ), | ||
| style: mergeStyle(baseStyle, style), | ||
| ...restProps, | ||
| }; | ||
|
|
||
| if (render) { | ||
| return <RenderRootComponent render={render} getRootRef={getRootRef} {...resolvedProps} />; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А точно нужно дополнительный узел в дереве создавать? Нельзя просто?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Нельзя, так как ругается компилятор
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Нашёл несколько моментов, которые стоит поправить перед мержем:
|
||
| } | ||
|
|
||
| return <Component ref={getRootRef} {...resolvedProps} />; | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.