diff --git a/cypress/snapshots/b2c/components/TextArea/TextAreaBase.component-test.tsx/plasma-core TextArea -- resize.snap.png b/cypress/snapshots/b2c/components/TextArea/TextAreaBase.component-test.tsx/plasma-core TextArea -- resize.snap.png index 12f9c2f4f..073b636a5 100644 Binary files a/cypress/snapshots/b2c/components/TextArea/TextAreaBase.component-test.tsx/plasma-core TextArea -- resize.snap.png and b/cypress/snapshots/b2c/components/TextArea/TextAreaBase.component-test.tsx/plasma-core TextArea -- resize.snap.png differ diff --git a/cypress/snapshots/ui/components/TextArea/TextArea.component-test.tsx/plasma-core TextArea -- resize.snap.png b/cypress/snapshots/ui/components/TextArea/TextArea.component-test.tsx/plasma-core TextArea -- resize.snap.png index aac3b85fb..c7893cb4a 100644 Binary files a/cypress/snapshots/ui/components/TextArea/TextArea.component-test.tsx/plasma-core TextArea -- resize.snap.png and b/cypress/snapshots/ui/components/TextArea/TextArea.component-test.tsx/plasma-core TextArea -- resize.snap.png differ diff --git a/cypress/snapshots/web/components/TextArea/TextAreaBase.component-test.tsx/plasma-core TextArea -- resize.snap.png b/cypress/snapshots/web/components/TextArea/TextAreaBase.component-test.tsx/plasma-core TextArea -- resize.snap.png index da0300960..3f78bde41 100644 Binary files a/cypress/snapshots/web/components/TextArea/TextAreaBase.component-test.tsx/plasma-core TextArea -- resize.snap.png and b/cypress/snapshots/web/components/TextArea/TextAreaBase.component-test.tsx/plasma-core TextArea -- resize.snap.png differ diff --git a/packages/plasma-b2c/src/components/TextArea/TextArea.tsx b/packages/plasma-b2c/src/components/TextArea/TextArea.tsx index e1088f28f..e3e8c2147 100644 --- a/packages/plasma-b2c/src/components/TextArea/TextArea.tsx +++ b/packages/plasma-b2c/src/components/TextArea/TextArea.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { createRef, useMemo, useState } from 'react'; import styled from 'styled-components'; import { TextFieldRoot, @@ -7,6 +7,7 @@ import { primary, secondary, tertiary, + useResizeObserver, } from '@sberdevices/plasma-core'; import type { TextAreaProps as BaseProps } from '@sberdevices/plasma-core'; @@ -44,6 +45,10 @@ const StyledTextArea = styled(BaseArea)` } `; +const StyledFieldHelpers = styled(FieldHelpers)<{ width: number }>` + width: ${({ width }) => width}px; +`; + /** * Поле ввода многострочного текста. */ @@ -66,10 +71,21 @@ export const TextArea = React.forwardRef( className, ...rest }, - ref, + outerRef, ) => { + const [width, setWidth] = useState(0); + const ref = useMemo(() => (outerRef && 'current' in outerRef ? outerRef : createRef()), [ + outerRef, + ]); + const placeLabel = (label || placeholder) as string | undefined; + useResizeObserver(ref, (currentElement) => { + const { width: elementWidth } = currentElement.getBoundingClientRect(); + + setWidth(elementWidth); + }); + return ( ( aria-describedby={id ? `${id}-helper` : undefined} {...rest} /> - + {leftHelper} {rightHelper} - + ); diff --git a/packages/plasma-core/package-lock.json b/packages/plasma-core/package-lock.json index 32df41e4e..e590d1f6f 100644 --- a/packages/plasma-core/package-lock.json +++ b/packages/plasma-core/package-lock.json @@ -4030,6 +4030,12 @@ "@types/react": "*" } }, + "@types/resize-observer-browser": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz", + "integrity": "sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg==", + "dev": true + }, "@types/styled-components": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.0.tgz", diff --git a/packages/plasma-core/package.json b/packages/plasma-core/package.json index 97ed1288d..60f395a47 100644 --- a/packages/plasma-core/package.json +++ b/packages/plasma-core/package.json @@ -7,18 +7,7 @@ "main": "index.js", "module": "es/index.js", "types": "index.d.ts", - "files": [ - "components", - "hocs", - "hooks", - "mixins", - "tokens", - "types", - "utils", - "index.d.ts", - "index.js", - "es" - ], + "files": ["components", "hocs", "hooks", "mixins", "tokens", "types", "utils", "index.d.ts", "index.js", "es"], "peerDependencies": { "react": ">=16.13.1", "react-dom": ">=16.13.1", @@ -36,6 +25,7 @@ "@types/node": "15.14.9", "@types/react": "16.9.38", "@types/react-dom": "16.9.8", + "@types/resize-observer-browser": "0.1.7", "@types/styled-components": "5.1.0", "babel-loader": "8.2.2", "babel-plugin-annotate-pure-calls": "0.4.0", @@ -63,11 +53,7 @@ "test": "NODE_ICU_DATA=node_modules/full-icu jest", "test:watch": "NODE_ICU_DATA=node_modules/full-icu jest --watch" }, - "contributors": [ - "Vasiliy Loginevskiy", - "Виноградов Антон Александрович", - "Зубаиров Фаниль Асхатович" - ], + "contributors": ["Vasiliy Loginevskiy", "Виноградов Антон Александрович", "Зубаиров Фаниль Асхатович"], "sideEffects": false, "dependencies": { "focus-visible": "5.2.0", diff --git a/packages/plasma-core/src/components/TextArea/TextArea.component-test.tsx b/packages/plasma-core/src/components/TextArea/TextArea.component-test.tsx index 4e2ce8e14..cc29102a4 100644 --- a/packages/plasma-core/src/components/TextArea/TextArea.component-test.tsx +++ b/packages/plasma-core/src/components/TextArea/TextArea.component-test.tsx @@ -87,6 +87,8 @@ describe('plasma-core: TextArea', () => { , ); + cy.root().get('textarea').last().invoke('attr', 'style', 'width: 280px; height: 140px;'); + cy.matchImageSnapshot(); }); diff --git a/packages/plasma-core/src/hooks/index.ts b/packages/plasma-core/src/hooks/index.ts index b1b739e4f..5190a6100 100644 --- a/packages/plasma-core/src/hooks/index.ts +++ b/packages/plasma-core/src/hooks/index.ts @@ -2,3 +2,4 @@ export { useDebouncedFunction } from './useDebouncedFunction'; export { useForkRef } from './useForkRef'; export { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; export { useUniqId } from './useUniqId'; +export { useResizeObserver } from './useResizeObserver'; diff --git a/packages/plasma-core/src/hooks/useResizeObserver.ts b/packages/plasma-core/src/hooks/useResizeObserver.ts new file mode 100644 index 000000000..2ec0f83c8 --- /dev/null +++ b/packages/plasma-core/src/hooks/useResizeObserver.ts @@ -0,0 +1,34 @@ +import { useEffect } from 'react'; + +/** + * Отслеживает изменение размеров переданного элемента + * @param {React.Ref} ref - реф элемента, за которым нужно следить + * @callback callback - функция, которая вызывается при изменении элемента + * @param {Element} element - элемент, размер которого изменился + */ +export const useResizeObserver = ( + ref: React.MutableRefObject, + callback: (element: T) => void, +) => { + useEffect(() => { + /* istanbul ignore if: убираем проверку на рефы из покрытия */ + if (!ref?.current) { + return; + } + + const { current } = ref; + + const resizeObserver = new window.ResizeObserver(() => callback(current)); + + resizeObserver.observe(ref.current); + + return () => { + /* istanbul ignore if: убираем проверку на рефы из покрытия */ + if (!ref?.current) { + return; + } + + resizeObserver.unobserve(ref.current); + }; + }, [ref]); +}; diff --git a/packages/plasma-web/src/components/TextArea/TextArea.tsx b/packages/plasma-web/src/components/TextArea/TextArea.tsx index 870f09c15..d6a7db517 100644 --- a/packages/plasma-web/src/components/TextArea/TextArea.tsx +++ b/packages/plasma-web/src/components/TextArea/TextArea.tsx @@ -1,6 +1,12 @@ -import React, { forwardRef } from 'react'; +import React, { forwardRef, useState, useMemo, createRef } from 'react'; import styled from 'styled-components'; -import { FieldRoot, FieldContent, FieldHelper, TextArea as BaseArea } from '@sberdevices/plasma-core'; +import { + FieldRoot, + FieldContent, + FieldHelper, + TextArea as BaseArea, + useResizeObserver, +} from '@sberdevices/plasma-core'; import type { TextAreaProps as BaseProps } from '@sberdevices/plasma-core'; import { applyInputStyles } from '../Field'; @@ -16,14 +22,35 @@ const StyledTextArea = styled(BaseArea)` ${applyInputStyles} `; +const StyledFieldHelperWrapper = styled.div<{ width: number }>` + position: absolute; + top: 0; + + display: flex; + justify-content: flex-end; + + width: ${({ width }) => width}px; +`; + /** * Поле ввода многострочного текста. */ // eslint-disable-next-line prefer-arrow-callback export const TextArea = forwardRef(function TextArea( { id, disabled, status, label, placeholder, contentRight, helperText, style, className, ...rest }, - ref, + outerRef, ) { + const [width, setWidth] = useState(0); + const ref = useMemo(() => (outerRef && 'current' in outerRef ? outerRef : createRef()), [ + outerRef, + ]); + + useResizeObserver(ref, (currentElement) => { + const { width: elementWidth } = currentElement.getBoundingClientRect(); + + setWidth(elementWidth); + }); + const placeLabel = (label || placeholder) as string | undefined; return ( @@ -44,8 +71,10 @@ export const TextArea = forwardRef(function aria-describedby={id ? `${id}-helpertext` : undefined} {...rest} /> - {contentRight && {contentRight}} {helperText && {helperText}} + + {contentRight && {contentRight}} + ); });