diff --git a/packages/react-ui-validations/.storybook/preview.tsx b/packages/react-ui-validations/.storybook/preview.tsx index 3ae668b4f80..3884dc2222c 100644 --- a/packages/react-ui-validations/.storybook/preview.tsx +++ b/packages/react-ui-validations/.storybook/preview.tsx @@ -9,6 +9,7 @@ import styled from 'styled-components'; import { HandThumbDownIcon } from '@skbkontur/icons/icons/HandThumbDownIcon'; import { HandThumbUpIcon } from '@skbkontur/icons/icons/HandThumbUpIcon'; import ThumbUpIcon from '@skbkontur/react-icons/ThumbUp'; +import { FixedSizeList as List } from 'react-window'; import * as Validations from '../src/index'; import * as ReactUI from '../../react-ui/index'; @@ -100,6 +101,7 @@ addons.setConfig({ HandThumbUpIcon, ThumbUpIcon, SpaceFiller, + List, }, decorators: [ThemeDecorator, FeatureFlagsDecorator], } as LiveConfig, diff --git a/packages/react-ui-validations/docs/Pages_NEW/Api.mdx b/packages/react-ui-validations/docs/Pages_NEW/Api.mdx index f3bd2dc5d75..5e957c34708 100644 --- a/packages/react-ui-validations/docs/Pages_NEW/Api.mdx +++ b/packages/react-ui-validations/docs/Pages_NEW/Api.mdx @@ -155,3 +155,43 @@ type RenderErrorMessage = ### `data-tid?: string` Позволяет задать кастомный `data-tid`, для доступа к содержимому ошибок. + +## ValidationListWrapper + +Обертка для виртуализированного списка, осуществляет поиск валидаций в "неотрендеренных" контролах. + +### `children: React.Node` + +Дочерний компонент должен быть ровно один. ValidationListWrapper контролирует его поведение путём передачи +prop-ов ref и data-tid. Для работы с компонентом используется +[React.cloneElement()](https://facebook.github.io/react/docs/react-api.html#cloneelement); + +### `validationInfos: ?ValidationReader` + +Результат возвращаемый из функции createValidator передает валидации в ValidationListWrapper. Если на любом уровне, вложенных в объект значений есть невалидное + +- список считается невалидным. + +### `onValidation?: (index: number | null, validation: Nullable) => void` + +Колбек для получения индекса невалидной строки и сообщения о невалидности + +### `level?: ValidationLevel` + +См. [Документацию](https://ui.gitlab-pages.kontur.host/storybook-documentation/?path=/docs/react_ui_validations_displaying-validation-level--docs) + +### `behaviour?: ValidationBehaviour` + +См. [Документацию](https://ui.gitlab-pages.kontur.host/storybook-documentation/?path=/docs/react_ui_validations_displaying-validation-type--docs) + +### `independent?: boolean` + +См. [Документацию](https://ui.gitlab-pages.kontur.host/storybook-documentation/?path=/docs/react_ui_validations_validator-independent--docs) + +### `scrollToElement?: (index: number) => void` + +Колбек, принимающий индекс невалидного элемента для возможности прокрутки к невалидному неотрендеренному элементу + +### `data-tid?: string` + +Позволяет задать кастомный `data-tid`, для доступа к содержимому ошибок. diff --git a/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx new file mode 100644 index 00000000000..491440a0175 --- /dev/null +++ b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx @@ -0,0 +1,119 @@ +import { Meta, Story } from '@skbkontur/react-ui/typings/stories'; +import React from 'react'; +import { Button } from '@skbkontur/react-ui/components/Button'; +import { FixedSizeList as List } from 'react-window'; +import { Token } from '@skbkontur/react-ui'; + +import { createValidator, ValidationContainer, ValidationListWrapper, ValidationWrapper } from '../../../../src'; + +export default { + title: 'Examples/Virtualized List example', + parameters: { creevey: { skip: true } }, +} as Meta; + +interface Data { + title: string; + value: { + subtitle: string; + value: number; + }; +} + +export const VirtualizedListExample: Story = () => { + const containerRef = React.useRef(null); + const listRef = React.useRef(null); + const [firstInvalidRow, setFirstInvalidRow] = React.useState(null); + + const data: Data[] = new Array(100).fill(0).map((_, i) => ({ + title: `title ${i}`, + value: { subtitle: `subtitle ${i}`, value: i }, + })); + const values = { + array: data, + }; + + const validator = createValidator<{ array: Data[] }>((b) => { + b.prop( + (x) => x.array, + (b) => { + b.array( + (x) => x, + (b) => { + b.prop( + (x) => x.value, + (b) => { + b.prop( + (x) => x.subtitle, + (b) => { + b.invalid(() => true, 'invlid sub'); + }, + ); + b.prop( + (x) => x.value, + (b) => { + b.invalid((x) => x > 40, 'invalid', 'immediate'); + }, + ); + }, + ); + }, + ); + }, + ); + }); + + const validationRules = validator(values); + const [isValid, setIsValid] = React.useState(true); + const handleValidate = async () => { + const result = await containerRef.current?.validate(); + setIsValid(!!result); + }; + + return ( + +
+
+ {firstInvalidRow ? `first invalid row is ${firstInvalidRow}` : null} + x.array)} + onValidation={(index) => setFirstInvalidRow(index)} + scrollToElement={(index) => { + listRef.current?.scrollToItem(index, 'center'); + }} + behaviour="submit" + > + { + return ( +
+
строка {data[index].value.value}
+
+ {data[index].title} + x.array) + .getNodeByIndex(index) + .getNode((x) => x.value.value) + .get()} + > + {data[index].value.subtitle} + +
+
+ ); + }} + /> +
+
+ + isValid {isValid.toString()} +
+
+ ); +}; diff --git a/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.mdx b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.mdx new file mode 100644 index 00000000000..71ede197b4c --- /dev/null +++ b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.mdx @@ -0,0 +1,13 @@ +import { Story } from '@storybook/blocks'; +import * as DocsStories from './VirtualizedList.docs.stories.tsx'; +import { Meta } from '../../../../.storybook/Meta'; + + + +# Валидация массива + +В примере показана валидация с виртуализированным списком + +### Пример + + diff --git a/packages/react-ui-validations/package.json b/packages/react-ui-validations/package.json index 6bad5179c50..91afbc6dd08 100644 --- a/packages/react-ui-validations/package.json +++ b/packages/react-ui-validations/package.json @@ -75,10 +75,10 @@ "@skbkontur/storybook-addon-live-examples": "0.0.11", "@storybook/addon-a11y": "7.6.18", "@storybook/addon-controls": "7.6.18", + "@storybook/addon-docs": "7.6.18", "@storybook/addon-essentials": "7.6.18", "@storybook/addon-interactions": "^7.6.18", "@storybook/addon-links": "7.6.18", - "@storybook/addon-docs": "7.6.18", "@storybook/blocks": "7.6.18", "@storybook/react": "7.6.18", "@storybook/react-webpack5": "7.6.18", @@ -91,6 +91,7 @@ "@types/react-dom": "18.3.0", "@types/react-helmet": "^6.1.11", "@types/react-syntax-highlighter": "^15.5.13", + "@types/react-window": "1.8.8", "@types/styled-components": "^5.1.34", "@types/warning": "^3.0.0", "babel-loader": "^9.1.3", @@ -118,6 +119,7 @@ "react-hot-loader": "^4.13.1", "react-router-dom": "^6.23.1", "react-syntax-highlighter": "15.5.0", + "react-window": "1.8.10", "rimraf": "^5.0.7", "rollup": "^4.18.0", "rollup-plugin-typescript2": "^0.36.0", diff --git a/packages/react-ui-validations/src/ValidationContextWrapper.tsx b/packages/react-ui-validations/src/ValidationContextWrapper.tsx index 18e7c3ea95f..71fbd2c9ae7 100644 --- a/packages/react-ui-validations/src/ValidationContextWrapper.tsx +++ b/packages/react-ui-validations/src/ValidationContextWrapper.tsx @@ -4,6 +4,7 @@ import { ValidationWrapperInternal } from './ValidationWrapperInternal'; import type { ScrollOffset, ValidateArgumentType } from './ValidationContainer'; import { isNullable } from './utils/isNullable'; import { FocusMode } from './FocusMode'; +import { ValidationListWrapperInternal } from './ValidationListWrapperInternal'; export interface ValidationContextSettings { scrollOffset: ScrollOffset; @@ -20,16 +21,20 @@ export interface ValidationContextWrapperProps { export interface ValidationContextType { register: (wrapper: ValidationWrapperInternal) => void; + registerVirtual: (reader: ValidationListWrapperInternal) => void; unregister: (wrapper: ValidationWrapperInternal) => void; + unregisterVirtual: (reader: ValidationListWrapperInternal) => void; instanceProcessBlur: (wrapper: ValidationWrapperInternal) => void; - onValidationUpdated: (wrapper: ValidationWrapperInternal, isValid: boolean) => void; + onValidationUpdated: (wrapper: ValidationWrapperInternal | ValidationListWrapperInternal, isValid: boolean) => void; getSettings: () => ValidationContextSettings; isAnyWrapperInChangingMode: () => boolean; } export const ValidationContext = React.createContext({ register: () => undefined, + registerVirtual: () => undefined, unregister: () => undefined, + unregisterVirtual: () => undefined, instanceProcessBlur: () => undefined, onValidationUpdated: () => undefined, getSettings: () => ({ @@ -43,6 +48,7 @@ ValidationContext.displayName = 'ValidationContext'; export class ValidationContextWrapper extends React.Component { public childWrappers: ValidationWrapperInternal[] = []; + public virtualWrappers: ValidationListWrapperInternal[] = []; public getSettings(): ValidationContextSettings { let scrollOffset: ScrollOffset = {}; @@ -67,27 +73,43 @@ export class ValidationContextWrapper extends React.Component x !== instance && !x.isIndependent())) { wrapper.processBlur(); } } - public onValidationUpdated(wrapper: ValidationWrapperInternal, isValid?: boolean) { + public onValidationUpdated(wrapper: ValidationWrapperInternal | ValidationListWrapperInternal, isValid?: boolean) { const { onValidationUpdated } = this.props; if (onValidationUpdated) { - const isValidResult = !this.childWrappers.find((x) => { - if (x === wrapper) { - return !isValid; - } - return x.hasError(); - }); - onValidationUpdated(isValidResult); + const isValidResult = + !this.childWrappers.find((x) => { + if (x === wrapper) { + return !isValid; + } + return x.hasError(); + }) || + this.virtualWrappers.find((x) => { + if (x === wrapper) { + return !isValid; + } + return x.hasError(); + }); + onValidationUpdated(!!isValidResult); } } @@ -95,6 +117,14 @@ export class ValidationContextWrapper extends React.Component x.isChanging); } + public onVirtualValidationRemoved() { + const { onValidationUpdated } = this.props; + if (onValidationUpdated) { + const isValidResult = !this.virtualWrappers.find((x) => x.hasError()); + onValidationUpdated(isValidResult); + } + } + public onValidationRemoved() { const { onValidationUpdated } = this.props; if (onValidationUpdated) { @@ -103,8 +133,8 @@ export class ValidationContextWrapper extends React.Component ({ + public getChildWrappersSortedByPosition(): Array { + const wrappersWithPosition = [...this.childWrappers, ...this.virtualWrappers].map((x) => ({ target: x, position: x.getControlPosition(), })); @@ -132,7 +162,10 @@ export class ValidationContextWrapper extends React.Component { const focusMode = ValidationContextWrapper.getFocusMode(withoutFocusOrValidationSettings); - await Promise.all(this.childWrappers.map((x) => x.processSubmit())); + await Promise.all([ + ...this.childWrappers.map((x) => x.processSubmit()), + ...this.virtualWrappers.map((x) => x.processSubmit()), + ]); const childrenWrappersSortedByPosition = this.getChildWrappersSortedByPosition(); const firstError = childrenWrappersSortedByPosition.find((x) => x.hasError()); diff --git a/packages/react-ui-validations/src/ValidationListWrapper.tsx b/packages/react-ui-validations/src/ValidationListWrapper.tsx new file mode 100644 index 00000000000..8311c38f1b9 --- /dev/null +++ b/packages/react-ui-validations/src/ValidationListWrapper.tsx @@ -0,0 +1,54 @@ +import React from 'react'; + +import { Nullable } from '../typings/Types'; + +import { Validation } from './ValidationWrapperInternal'; +import { + ValidationBehaviour, + ValidationLevel, + ValidationListWrapperInternal, + ValidationListInternalProps, +} from './ValidationListWrapperInternal'; +import { ValidationReader } from './Validations'; + +export interface ValidationListProps extends Pick { + children?: React.ReactElement; + validationInfos: ValidationReader; + onValidation?: (index: number | null, validation: Nullable) => void; + level?: ValidationLevel; + behaviour?: ValidationBehaviour; + independent?: boolean; + scrollToElement?: (index: number) => void; +} + +export class ValidationListWrapper extends React.Component { + public static __KONTUR_REACT_UI__ = 'ValidationListWrapper'; + public static displayName = 'ValidationListWrapper'; + + public render() { + const { + children, + onValidation, + behaviour, + level, + independent, + scrollToElement, + validationInfos, + 'data-tid': datTid, + } = this.props; + + return ( + + {children} + + ); + } +} diff --git a/packages/react-ui-validations/src/ValidationListWrapperInternal.tsx b/packages/react-ui-validations/src/ValidationListWrapperInternal.tsx new file mode 100644 index 00000000000..309f1af828f --- /dev/null +++ b/packages/react-ui-validations/src/ValidationListWrapperInternal.tsx @@ -0,0 +1,194 @@ +import React, { ReactInstance } from 'react'; +import warning from 'warning'; + +import { Nullable } from '../typings/Types'; + +import { getRootNode } from './utils/getRootNode'; +import { isBrowser } from './utils/utils'; +import { smoothScrollIntoView } from './smoothScrollIntoView'; +import { getLevel, getVisibleValidation, isEqual } from './ValidationHelper'; +import { ValidationContext, ValidationContextType } from './ValidationContextWrapper'; +import { Validation } from './ValidationWrapperInternal'; +import { ValidationReader } from './Validations'; + +if (isBrowser && typeof HTMLElement === 'undefined') { + const w = window as any; + w.HTMLElement = w.Element; +} + +export type ValidationBehaviour = 'immediate' | 'lostfocus' | 'submit'; + +export type ValidationLevel = 'error' | 'warning'; + +export interface ValidationListInternalProps { + children?: React.ReactElement; + onValidation?: (index: number | null, validation: Nullable) => void; + validationInfos: ValidationReader; + scrollToElement?: (index: number) => void; + level?: ValidationLevel; + behaviour?: ValidationBehaviour; + independent?: boolean; + 'data-tid'?: string; +} + +interface ValidationListInternalState { + validation: Nullable; +} + +interface Point { + x: number; + y: number; +} + +export class ValidationListWrapperInternal extends React.Component< + ValidationListInternalProps, + ValidationListInternalState +> { + public state: ValidationListInternalState = { + validation: null, + }; + + private child: any; // todo type + private rootNode: Nullable; + + public static contextType = ValidationContext; + public context: ValidationContextType = this.context; + public validationIndex: number | null = null; + private validationInfoTemplate: Validation = { + independent: this.props.independent || false, + message: '', + behaviour: this.props.behaviour || 'submit', + level: this.props.level || 'error', + }; + + public componentDidMount() { + warning( + this.context, + 'ValidationListWrapper should appears as child of ValidationContainer.\n' + + 'https://tech.skbkontur.ru/react-ui-validations/#/getting-started', + ); + if (this.context) { + this.context.registerVirtual(this); + } + + this.validationIndex = this.props.validationInfos.getFirstNodeWithValidation(); + this.applyValidation(this.validationIndex ? this.validationInfoTemplate : null); + } + + public componentWillUnmount() { + this.context.unregisterVirtual(this); + } + + public componentDidUpdate() { + this.validationIndex = this.props.validationInfos.getFirstNodeWithValidation(); + this.applyValidation(this.validationIndex ? this.validationInfoTemplate : null); + } + + public async focus(): Promise { + const htmlElement = this.getRootNode(); + if (htmlElement instanceof HTMLElement) { + const { disableSmoothScroll, scrollOffset } = this.context.getSettings(); + if (this.props.scrollToElement && this.validationIndex) { + this.props.scrollToElement(this.validationIndex); + } + if (!disableSmoothScroll) { + await smoothScrollIntoView(htmlElement, scrollOffset); + } + if (this.child && typeof this.child.focus === 'function') { + this.child.focus(); + } + } + } + + public render() { + const { children, 'data-tid': dataTid } = this.props; + + return children ? ( + React.cloneElement(children, { + ref: this.customRef, + 'data-tid': dataTid, + }) + ) : ( + + ); + } + + private customRef = (instance: Nullable) => { + const { children } = this.props; + + this.setRootNode(instance); + const child = children as any; // todo type or maybe React.Children.only + if (child && child.ref) { + if (typeof child.ref === 'function') { + child.ref(instance); + } + if (Object.prototype.hasOwnProperty.call(child.ref, 'current')) { + child.ref.current = instance; + } + } + this.child = instance; + }; + + private setRootNode = (element: Nullable) => { + this.rootNode = getRootNode(element); + }; + + public getRootNode = () => { + return this.rootNode; + }; + + public getControlPosition(): Nullable { + const htmlElement = this.getRootNode(); + if (htmlElement instanceof HTMLElement) { + const rect = htmlElement.getBoundingClientRect(); + return { x: rect.top, y: rect.left }; + } + return null; + } + + public async processSubmit(): Promise { + return this.setValidation(this.validationIndex ? this.validationInfoTemplate : null); + } + + public hasError(): boolean { + return getLevel(this.state.validation) === 'error'; + } + + public hasWarning(): boolean { + return getLevel(this.state.validation) === 'warning'; + } + + private applyValidation(actual: Nullable) { + const visible = this.getVisibleValidation(actual); + this.setValidation(visible); + } + + private setValidation(validation: Nullable): Promise { + const current = this.state.validation; + + if (current === validation) { + return Promise.resolve(); + } + + return new Promise((resolve) => { + this.setState({ validation }, () => { + if (Boolean(current) !== Boolean(validation)) { + if (this.props.onValidation) { + this.props.onValidation(this.validationIndex, validation); + } + this.context.onValidationUpdated(this, !validation); + } + resolve(); + }); + }); + } + + private getVisibleValidation(actual: Nullable): Nullable { + const visible = this.state.validation; + if (isEqual(visible, actual)) { + return visible; + } + const changing = this.context.isAnyWrapperInChangingMode(); + return getVisibleValidation(visible, actual, changing); + } +} diff --git a/packages/react-ui-validations/src/Validations/ValidationReader.ts b/packages/react-ui-validations/src/Validations/ValidationReader.ts index 0d160eccc4f..765a3cb5545 100644 --- a/packages/react-ui-validations/src/Validations/ValidationReader.ts +++ b/packages/react-ui-validations/src/Validations/ValidationReader.ts @@ -28,6 +28,44 @@ export class ValidationReader { return this.node ? this.node.validation : null; } + private findValueInNestedObject(obj: Nullable>, valuesToFind: string[]) { + let foundKey = null; + if (!obj) { + return null; + } + + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const currentValue = obj[key]; + + if (valuesToFind.includes(currentValue)) { + return key; + } + + if (typeof currentValue === 'object') { + foundKey = this.findValueInNestedObject(currentValue, valuesToFind); + + if (foundKey !== null) { + return key; + } + } + } + } + + return null; + } + + public getFirstNodeWithValidation(level: 'all' | 'error' | 'warning' = 'all'): number | null { + const values = level === 'all' ? ['invalid', 'warning'] : [level]; + if (!this.node) { + return null; + } + if (this.node.validation) { + return 0; + } + return Number(this.findValueInNestedObject(this.node.children, values)); + } + private getReaderInternal(path: string[]): ValidationReader { const node = this.getNodeInternal(path); return new ValidationReader(node, this.tokens); diff --git a/packages/react-ui-validations/src/index.tsx b/packages/react-ui-validations/src/index.tsx index 60961cbe779..65365a8abb2 100644 --- a/packages/react-ui-validations/src/index.tsx +++ b/packages/react-ui-validations/src/index.tsx @@ -16,7 +16,9 @@ import type { ValidationBehaviour, } from './ValidationWrapperInternal'; import { ValidationWrapper } from './ValidationWrapper'; +import { ValidationListWrapper } from './ValidationListWrapper'; import type { ValidationInfo, ValidationWrapperProps } from './ValidationWrapper'; +import type { ValidationListProps } from './ValidationListWrapper'; import type { ValidationContextType, ValidationContextWrapperProps } from './ValidationContextWrapper'; import { ValidationContext, ValidationContextWrapper, ValidationContextSettings } from './ValidationContextWrapper'; import { @@ -32,6 +34,7 @@ export type { ValidationContextType, ValidationContextWrapperProps, ValidationWrapperProps, + ValidationListProps, ValidationTooltipProps, ScrollOffset, ValidationLevel, @@ -54,6 +57,7 @@ export { ValidationContextWrapper, ValidationWrapper as ValidationWrapperV1, ValidationWrapper, + ValidationListWrapper, ValidationTooltip, tooltip, text, diff --git a/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx b/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx new file mode 100644 index 00000000000..2f23215103c --- /dev/null +++ b/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx @@ -0,0 +1,160 @@ +import React from 'react'; +import { Meta } from '@storybook/react'; +import { Button, Token } from '@skbkontur/react-ui'; +import { FixedSizeList as List } from 'react-window'; + +import { + createValidator, + ValidationContainer, + ValidationReader, + ValidationWrapper, + ValidationListWrapper, +} from '../src'; + +interface Props { + data: Data; + index: number; + validationReader: ValidationReader; + style?: React.CSSProperties; +} + +const SimpleElement = ({ data: value, validationReader, style }: Props) => { + return ( +
+
строка {value.value.value}
+
+ {value.title} + x.value.value).get()}> + {value.value.subtitle} + +
+
+ ); +}; + +interface WindowProps { + rowHeight: number; + gap?: number; + data: Data[]; + validationInfos: ValidationReader; +} + +const Window: React.FC = ({ rowHeight, data, validationInfos }) => { + const containerRef = React.useRef(null); + const [firstInvalidRow, setFirstInvalidRow] = React.useState(null); + + return ( +
+ {firstInvalidRow ? `first invalid row is ${firstInvalidRow}` : null} + setFirstInvalidRow(index)} + scrollToElement={(index) => { + containerRef.current?.scrollToItem(index, 'center'); + }} + behaviour="submit" + > + { + return ( + + ); + }} + /> + +
+ ); +}; + +const validator = createValidator<{ template: Data; array: Data[]; secondTemplate: Data }>((b) => { + b.prop( + (x) => x.array, + (b) => { + b.array( + (x) => x, + (b) => { + b.prop( + (x) => x.value, + (b) => { + b.prop( + (x) => x.subtitle, + (b) => { + b.invalid(() => true, 'invlid sub'); + }, + ); + b.prop( + (x) => x.value, + (b) => { + b.invalid((x) => x > 40, 'invalid', 'immediate'); + }, + ); + }, + ); + }, + ); + }, + ); + b.prop( + (x) => x.template, + (b) => { + b.invalid(() => true, 'kek'); + }, + ); +}); + +interface Data { + title: string; + value: { + subtitle: string; + value: number; + }; +} + +export const Default = () => { + const containerRef = React.useRef(null); + const data: Data[] = new Array(100).fill(0).map((_, i) => ({ + title: `title ${i}`, + value: { subtitle: `subtitle ${i}`, value: i }, + })); + const values = { + template: { title: 'titke', value: { subtitle: 'sub', value: 90 } }, + array: data, + secondTemplate: { title: 'titke', value: { subtitle: 'sub', value: 90 } }, + }; + const createValidator = validator(values); + const [isValid, setIsValid] = React.useState(true); + const handleValidate = async () => { + const result = await containerRef.current?.validate(); + setIsValid(!!result); + }; + + return ( + +
+ x.array)} /> + + isValid {isValid.toString()} + x.secondTemplate).get()}> + заглушка + +
+
+ ); +}; + +export default { + title: 'Virtualize Table', + parameters: { creevey: { skip: true } }, +} as Meta; diff --git a/yarn.lock b/yarn.lock index 629e28e927e..79324284c7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6806,6 +6806,13 @@ dependencies: "@types/react" "*" +"@types/react-window@1.8.8": + version "1.8.8" + resolved "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz#c20645414d142364fbe735818e1c1e0a145696e3" + integrity sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q== + dependencies: + "@types/react" "*" + "@types/react@*": version "18.2.75" resolved "https://registry.npmjs.org/@types/react/-/react-18.2.75.tgz#45d18f384939306d35312def1bf532eb38a68562" @@ -15665,6 +15672,11 @@ memfs@^4.6.0: sonic-forest "^1.0.0" tslib "^2.0.0" +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" @@ -18444,6 +18456,14 @@ react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" +react-window@1.8.10: + version "1.8.10" + resolved "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz#9e6b08548316814b443f7002b1cf8fd3a1bdde03" + integrity sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@18.3.1, react@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"