From dce956ffadbccbd276a959bcc08500d1b3f5aa69 Mon Sep 17 00:00:00 2001 From: Chris Wilton-Magras Date: Mon, 25 Mar 2024 14:05:01 +0000 Subject: [PATCH] 783 details summary buttons hover and redesign (#869) --------- Co-authored-by: dhinrichs-scottlogic <125262707+dhinrichs-scottlogic@users.noreply.github.com> Co-authored-by: Peter Marsh <118171430+pmarsh-scottlogic@users.noreply.github.com> --- frontend/src/App.css | 11 ++ frontend/src/Theme.css | 1 + .../src/components/ChatBox/ChatBoxInput.css | 10 -- .../src/components/ChatBox/ChatBoxInput.tsx | 2 - .../components/ControlPanel/ControlPanel.css | 39 ++++-- .../components/ControlPanel/ControlPanel.tsx | 72 +++++----- .../DefenceBox/DefenceConfiguration.css | 4 +- .../DefenceConfigurationRadioButton.css | 1 - .../DefenceBox/DefenceMechanism.css | 130 +++++------------- .../DefenceBox/DefenceMechanism.tsx | 69 ++++------ .../PromptEnclosureDefenceMechanism.tsx | 8 +- .../DocumentViewer/DocumentViewButton.css | 2 +- .../ModelBox/ModelConfigurationSlider.css | 38 +---- .../ModelBox/ModelConfigurationSlider.tsx | 13 +- .../components/ModelBox/ModelSelection.css | 10 ++ .../ThemedButtons/DetailElement.css | 43 ++++++ .../ThemedButtons/DetailElement.test.tsx | 52 +++++++ .../ThemedButtons/DetailElement.tsx | 47 +++++++ .../ThemedInput/ThemedNumberInput.css | 4 +- .../components/ThemedInput/ThemedTextArea.tsx | 32 ++++- 20 files changed, 331 insertions(+), 257 deletions(-) delete mode 100644 frontend/src/components/ChatBox/ChatBoxInput.css create mode 100644 frontend/src/components/ThemedButtons/DetailElement.css create mode 100644 frontend/src/components/ThemedButtons/DetailElement.test.tsx create mode 100644 frontend/src/components/ThemedButtons/DetailElement.tsx diff --git a/frontend/src/App.css b/frontend/src/App.css index 87ad143a6..4a2d55bae 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -69,6 +69,17 @@ hr { border-bottom: transparent; } +.visually-hidden { + position: absolute; + overflow: hidden; + clip: rect(0, 0, 0, 0); + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + border: 0; +} + @media (width < 62.5rem) { .dialog-modal { max-height: 95%; diff --git a/frontend/src/Theme.css b/frontend/src/Theme.css index 81a32d35c..b4b3db3ac 100644 --- a/frontend/src/Theme.css +++ b/frontend/src/Theme.css @@ -61,6 +61,7 @@ /* control panel */ --control-header-background-colour: #313131; + --control-header-background-colour-hover: #414141; --control-body-background-colour: #3a3a3a; --control-config-border: var(--chat-info-text-colour); diff --git a/frontend/src/components/ChatBox/ChatBoxInput.css b/frontend/src/components/ChatBox/ChatBoxInput.css deleted file mode 100644 index da1403a59..000000000 --- a/frontend/src/components/ChatBox/ChatBoxInput.css +++ /dev/null @@ -1,10 +0,0 @@ -.visually-hidden { - position: absolute; - overflow: hidden; - clip: rect(0, 0, 0, 0); - width: 1px; - height: 1px; - margin: -1px; - padding: 0; - border: 0; -} diff --git a/frontend/src/components/ChatBox/ChatBoxInput.tsx b/frontend/src/components/ChatBox/ChatBoxInput.tsx index 6d86a7338..68e73bdac 100644 --- a/frontend/src/components/ChatBox/ChatBoxInput.tsx +++ b/frontend/src/components/ChatBox/ChatBoxInput.tsx @@ -2,8 +2,6 @@ import { KeyboardEvent } from 'react'; import ThemedTextArea from '@src/components/ThemedInput/ThemedTextArea'; -import './ChatBoxInput.css'; - function ChatBoxInput({ content, onContentChanged, diff --git a/frontend/src/components/ControlPanel/ControlPanel.css b/frontend/src/components/ControlPanel/ControlPanel.css index 0a0a7b9ab..59ca2cef0 100644 --- a/frontend/src/components/ControlPanel/ControlPanel.css +++ b/frontend/src/components/ControlPanel/ControlPanel.css @@ -1,4 +1,7 @@ .control-panel { + display: flex; + flex-direction: column; + gap: 1rem; overflow-y: auto; height: 100%; padding: 1.875rem; @@ -12,26 +15,38 @@ background: transparent; } -.control-panel .control-collapsible-section { - margin-bottom: 0.75rem; +.control-panel .config-container { border-radius: 0.5rem; - background-color: var(--control-body-background-colour); + background-color: var(--control-header-background-colour); +} + +.control-panel .config-container fieldset { + margin: 0; + padding: 0.25rem 0.75rem 0.75rem; + border: 0.25rem solid var(--control-config-border); + border-radius: 0.5rem; +} + +.control-panel .config-container fieldset legend { + padding: 0.25rem 0.375rem; } -.control-panel .control-collapsible-section-header { +.control-panel .config-container > .details-button { + width: 100%; padding: 0.75rem; + border: none; border-radius: 0.5rem; background-color: var(--control-header-background-colour); - cursor: default; + color: inherit; + font-size: 1rem; + text-align: start; } -.control-panel .control-collapsible-section-header:focus-visible { - border-radius: 0; +.control-panel .config-container > .details-button:hover, +.control-panel .config-container > .details-button:focus-visible { + background-color: var(--control-header-background-colour-hover); } -.control-panel - .control-collapsible-section[open] - .control-collapsible-section-header { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; +.control-panel .config-container > .details-content { + margin: 0; } diff --git a/frontend/src/components/ControlPanel/ControlPanel.tsx b/frontend/src/components/ControlPanel/ControlPanel.tsx index 9719bcd25..e1849a29a 100644 --- a/frontend/src/components/ControlPanel/ControlPanel.tsx +++ b/frontend/src/components/ControlPanel/ControlPanel.tsx @@ -2,6 +2,7 @@ import { DEFENCES_HIDDEN_LEVEL3_IDS, MODEL_DEFENCES } from '@src/Defences'; import DefenceBox from '@src/components/DefenceBox/DefenceBox'; import DocumentViewButton from '@src/components/DocumentViewer/DocumentViewButton'; import ModelBox from '@src/components/ModelBox/ModelBox'; +import DetailElement from '@src/components/ThemedButtons/DetailElement'; import { ChatModel } from '@src/models/chat'; import { DEFENCE_ID, @@ -68,44 +69,41 @@ function ControlPanel({ {(currentLevel === LEVEL_NAMES.LEVEL_3 || currentLevel === LEVEL_NAMES.SANDBOX) && ( <> -

ScottBrew System Access

-
- - Defence Configuration - - -
- -
- - Model Configuration - - - - {/* only show model box in sandbox mode */} - {showConfigurations && ( - + ScottBrewBot Security Configuration + +
+ + + +
+
+ + - )} -
+ {currentLevel === LEVEL_NAMES.SANDBOX && ( + + )} + + )} diff --git a/frontend/src/components/DefenceBox/DefenceConfiguration.css b/frontend/src/components/DefenceBox/DefenceConfiguration.css index fe5fb1f82..8a82b1f03 100644 --- a/frontend/src/components/DefenceBox/DefenceConfiguration.css +++ b/frontend/src/components/DefenceBox/DefenceConfiguration.css @@ -1,5 +1,7 @@ .defence-configuration { - width: 100%; + display: flex; + flex-direction: column; + gap: 0.25rem; } .defence-configuration .header { diff --git a/frontend/src/components/DefenceBox/DefenceConfigurationRadioButton.css b/frontend/src/components/DefenceBox/DefenceConfigurationRadioButton.css index 2b26fc1dd..b679c7222 100644 --- a/frontend/src/components/DefenceBox/DefenceConfigurationRadioButton.css +++ b/frontend/src/components/DefenceBox/DefenceConfigurationRadioButton.css @@ -6,7 +6,6 @@ } .defence-radio-button input { - margin-bottom: 0.5rem; opacity: 0; } diff --git a/frontend/src/components/DefenceBox/DefenceMechanism.css b/frontend/src/components/DefenceBox/DefenceMechanism.css index 491e8682f..e50146a91 100644 --- a/frontend/src/components/DefenceBox/DefenceMechanism.css +++ b/frontend/src/components/DefenceBox/DefenceMechanism.css @@ -1,123 +1,64 @@ -.defence-mechanism-fieldset { - min-height: fit-content; - margin: 0; - border: 0.25rem solid var(--control-config-border); - border-radius: 0.5rem; -} - -.defence-mechanism-legend { - padding: 0.3em; -} - -.defence-mechanism-summary { - width: fit-content; - padding: 0.3125rem; - border: 0.25rem outset var(--control-config-border); - border-radius: 0.25rem; - background-color: var(--main-scrollbar-colour); -} - -.defence-mechanism > summary { - font-size: 1rem; -} - -.defence-mechanism-fieldset details > summary { - list-style: none; -} - -.defence-mechanism-fieldset details > summary::-webkit-details-marker { - display: none; -} - -.defence-mechanism-fieldset details[open] > summary { - border-style: inset; - background-color: var(--control-body-background-colour); -} - -.defence-mechanism-form { +.defence-mechanism-fieldset .defence-mechanism-form { position: absolute; - right: 1em; -} - -.defence-mechanism { - cursor: default; -} - -.defence-mechanism .info-box :first-child { - margin-top: 0; -} - -.defence-mechanism .info-box { - padding: 0.5em; - font-size: 0.875rem; -} - -.defence-mechanism .info-box .validation-text { - font-weight: 600; - font-size: inherit; + right: 1rem; + display: flex; + align-items: center; + height: 2.5rem; } -.defence-mechanism .defence-radio-buttons { +.defence-mechanism-fieldset .defence-radio-buttons { display: flex; flex-direction: column; - gap: 0.5rem; - padding: 0.75rem 0; -} - -.defence-mechanism .prompt-enclosure-configuration-area { - padding: 0; + gap: 0.875rem; } /* adapted from: https://adrianroselli.com/2019/03/under-engineered-toggles.html */ -.toggles [type='checkbox'] { - position: absolute; - top: auto; - overflow: hidden; - clip: rect(1px, 1px, 1px, 1px); - width: 1px; - height: 1px; - white-space: nowrap; +.toggles *, +.toggles ::before, +.toggles ::after { + box-sizing: border-box; } .toggles [type='checkbox'] + label { position: relative; display: block; max-width: fit-content; - padding-right: 3em; + padding-right: 3rem; text-align: right; } +.toggles [type='checkbox']:focus + label, +.toggles [type='checkbox']:hover + label { + color: var(--main-text-accent-colour); +} + .toggles [type='checkbox'] + label::before, .toggles [type='checkbox'] + label::after { content: ''; position: absolute; - height: 0.9em; - transition: all 0.25s ease; -} - -.toggles [type='checkbox']:focus + label, -.toggles [type='checkbox']:hover + label { - color: var(--main-text-accent-colour); } .toggles [type='checkbox'] + label::before { - top: 0.1em; + top: 0; right: 0; - width: 1.9em; - border: 0.15em solid var(--main-toggle-off-border-colour); - border-radius: 1.1em; - background: var(--main-toggle-off-colour); + width: 2.125rem; + height: 1.25rem; + border: 0.125rem solid var(--main-toggle-off-border-colour); + border-radius: 1rem; + background-color: var(--main-toggle-off-colour); } .toggles [type='checkbox'] + label::after { - top: 0.17em; - right: 0.95em; - width: 1em; - border: 0.05em solid var(--main-toggle-off-border-colour); + top: 0.125rem; + right: 1rem; + width: 1rem; + height: 1rem; + border: 0.0625rem solid var(--main-toggle-off-border-colour); border-radius: 50%; - background: var(--main-text-colour); + background-color: var(--main-text-colour); background-position: center center; + transition: right 0.3s ease 0.2s; } .toggles [type='checkbox']:focus + label::before, @@ -134,19 +75,18 @@ background-repeat: no-repeat; } -.toggles [type='checkbox']:checked + label::after { - right: 0.1em; +.toggles [type='checkbox']:checked + label::before { border-color: var(--main-toggle-on-border-colour); - color: var(--main-toggle-on-border-colour); + background-color: var(--main-toggle-on-border-colour); } -.toggles [type='checkbox']:checked + label::before { +.toggles [type='checkbox']:checked + label::after { + right: 0.125rem; border-color: var(--main-toggle-on-border-colour); - background-color: var(--main-toggle-on-border-colour); + color: var(--main-toggle-on-border-colour); } @media screen and (prefers-reduced-motion: reduce) { - .toggles [type='checkbox'] + label::before, .toggles [type='checkbox'] + label::after { transition: none; } diff --git a/frontend/src/components/DefenceBox/DefenceMechanism.tsx b/frontend/src/components/DefenceBox/DefenceMechanism.tsx index b9e321827..17b6cec87 100644 --- a/frontend/src/components/DefenceBox/DefenceMechanism.tsx +++ b/frontend/src/components/DefenceBox/DefenceMechanism.tsx @@ -1,5 +1,4 @@ -import { useState } from 'react'; - +import DetailElement from '@src/components/ThemedButtons/DetailElement'; import { DEFENCE_ID, DefenceConfigItem, @@ -33,8 +32,6 @@ function DefenceMechanism({ config: DefenceConfigItem[] ) => Promise; }) { - const [configKey, setConfigKey] = useState(0); - function resetConfigurationValue( defence: Defence, configItemId: DEFENCE_CONFIG_ITEM_ID @@ -64,7 +61,7 @@ function DefenceMechanism({
{ @@ -79,43 +76,33 @@ function DefenceMechanism({
)} -
{ - // re-render the configuration component when detail is toggled - // this is to resize the textarea when detail is expanded - setConfigKey(configKey + 1); - }} - > - Details -
+ {defenceDetail.id !== DEFENCE_ID.PROMPT_ENCLOSURE ? ( + defenceDetail.config.map((config) => ( + +

{defenceDetail.info}

+ {showConfigurations && ( + + )} +
+ )) + ) : ( +

{defenceDetail.info}

- - {defenceDetail.id !== DEFENCE_ID.PROMPT_ENCLOSURE ? ( - showConfigurations && - defenceDetail.config.map((config) => { - return ( - - ); - }) - ) : ( - - )} -
-
+ + + )} ); } diff --git a/frontend/src/components/DefenceBox/PromptEnclosureDefenceMechanism.tsx b/frontend/src/components/DefenceBox/PromptEnclosureDefenceMechanism.tsx index ce28201a6..3a7348873 100644 --- a/frontend/src/components/DefenceBox/PromptEnclosureDefenceMechanism.tsx +++ b/frontend/src/components/DefenceBox/PromptEnclosureDefenceMechanism.tsx @@ -51,7 +51,7 @@ function PromptEnclosureDefenceMechanism({ } return ( -
+ <>
{selectedDefence && ( -
+ <>

{selectedDefence.info}

{showConfigurations && selectedDefence.config.map((config, index) => ( @@ -87,9 +87,9 @@ function PromptEnclosureDefenceMechanism({ resetConfigurationValue={resetConfigurationValue} /> ))} -
+ )} -
+ ); } diff --git a/frontend/src/components/DocumentViewer/DocumentViewButton.css b/frontend/src/components/DocumentViewer/DocumentViewButton.css index 7708b8397..ead7b2dc6 100644 --- a/frontend/src/components/DocumentViewer/DocumentViewButton.css +++ b/frontend/src/components/DocumentViewer/DocumentViewButton.css @@ -1,7 +1,7 @@ .document-view-button-wrapper { display: flex; + flex-shrink: 0; height: 2.5rem; - padding-top: 0.5rem; } /* Button fills all available width */ diff --git a/frontend/src/components/ModelBox/ModelConfigurationSlider.css b/frontend/src/components/ModelBox/ModelConfigurationSlider.css index c90585d08..a01ff3940 100644 --- a/frontend/src/components/ModelBox/ModelConfigurationSlider.css +++ b/frontend/src/components/ModelBox/ModelConfigurationSlider.css @@ -1,43 +1,7 @@ .model-config-slider-fieldset { display: flex; flex-direction: column; - margin: 0; - border: 0.25rem solid var(--control-config-border); - border-radius: 0.5rem; -} - -.model-selection-fieldset { - min-height: fit-content; - margin: 0; - border: 0.25rem solid var(--control-config-border); - border-radius: 0.5rem; -} - -.model-config-slider-fieldset legend { - padding: 0.3125rem; -} - -.model-config-slider-fieldset details > summary { - width: fit-content; - padding: 0.3125rem; - border: 0.25rem outset var(--control-config-border); - border-radius: 0.25rem; - background-color: var(--main-scrollbar-colour); - list-style: none; -} - -.model-config-slider-fieldset details[open] summary { - border-style: inset; - background-color: var(--control-body-background-colour); -} - -.model-config-slider-fieldset .info-text { - padding: 0.5rem; - font-size: 0.875rem; -} - -.model-config-slider-fieldset details[open] .info-text { - display: block; + gap: 0.5rem; } .model-config-slider-fieldset .config-slider { diff --git a/frontend/src/components/ModelBox/ModelConfigurationSlider.tsx b/frontend/src/components/ModelBox/ModelConfigurationSlider.tsx index e679ecad3..30826ef93 100644 --- a/frontend/src/components/ModelBox/ModelConfigurationSlider.tsx +++ b/frontend/src/components/ModelBox/ModelConfigurationSlider.tsx @@ -1,6 +1,7 @@ import { Slider } from '@mui/material'; -import { SyntheticEvent, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; +import DetailElement from '@src/components/ThemedButtons/DetailElement'; import { CustomChatModelConfiguration, MODEL_CONFIG } from '@src/models/chat'; import './ModelConfigurationSlider.css'; @@ -14,10 +15,7 @@ function ModelConfigurationSlider({ }) { const [value, setValue] = useState(config.value); - function handleValueChange( - _: Event | SyntheticEvent, - value: number | number[] - ) { + function handleValueChange(_: Event, value: number | number[]) { const val = Array.isArray(value) ? value[0] : value; setValue(val); } @@ -50,10 +48,9 @@ function ModelConfigurationSlider({ }} />
-
- Details +
{config.info}
-
+ ); } diff --git a/frontend/src/components/ModelBox/ModelSelection.css b/frontend/src/components/ModelBox/ModelSelection.css index e370c1bf1..8c5ca0726 100644 --- a/frontend/src/components/ModelBox/ModelSelection.css +++ b/frontend/src/components/ModelBox/ModelSelection.css @@ -3,6 +3,13 @@ flex-direction: column; } +.model-selection-box .model-selection-fieldset { + display: flex; + flex-direction: column; + gap: 0.5rem; + min-height: fit-content; +} + .model-selection-box .model-selection-row { display: flex; flex-wrap: wrap; @@ -25,6 +32,9 @@ .model-selection-box .model-selection-row select { flex: 1 1 auto; padding: 0.5rem; + border-width: 0.0625rem; + border-radius: 0.25rem; + outline-offset: 0.125rem; } .model-selection-box .model-selection-info { diff --git a/frontend/src/components/ThemedButtons/DetailElement.css b/frontend/src/components/ThemedButtons/DetailElement.css new file mode 100644 index 000000000..afd9e6c96 --- /dev/null +++ b/frontend/src/components/ThemedButtons/DetailElement.css @@ -0,0 +1,43 @@ +.details-button { + width: fit-content; + height: auto; + padding: 0.3125rem; + border: 0.25rem outset var(--control-config-border); + border-radius: 0.25rem; + background-color: var(--main-scrollbar-colour); + color: var(--main-text-colour); + font-size: 1rem; + cursor: default; +} + +.details-button:hover { + background-color: var(--main-scrollbar-hover-colour); +} + +.details-button[aria-expanded='true'] { + border-style: inset; + background-color: var(--control-body-background-colour); +} + +.details-button[aria-expanded='true']:hover { + background-color: var(--main-scrollbar-hover-colour); +} + +.details-button .button-arrow-icon { + display: inline-block; + width: 1.5rem; + text-align: center; +} + +.details-content { + margin-top: 0.5rem; + font-size: 0.875rem; +} + +.details-content > :first-child { + margin-top: 0; +} + +.details-content > :last-child { + margin-bottom: 0; +} diff --git a/frontend/src/components/ThemedButtons/DetailElement.test.tsx b/frontend/src/components/ThemedButtons/DetailElement.test.tsx new file mode 100644 index 000000000..86c64e1b2 --- /dev/null +++ b/frontend/src/components/ThemedButtons/DetailElement.test.tsx @@ -0,0 +1,52 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ReactNode } from 'react'; +import { describe, expect, test } from 'vitest'; + +import DetailElement from './DetailElement'; + +function renderComponent(content: ReactNode, buttonText?: string) { + const user = userEvent.setup(); + render( + + {content} + + ); + + return { user }; +} + +function detailButton(name = 'Details') { + return screen.getByRole('button', { name }); +} + +describe('DetailElement component tests', () => { + test('Renders collapsed initially with Details button by default', () => { + const textContent = 'Well hello there'; + renderComponent(<>{textContent}); + + const button = detailButton(); + expect(button).toBeInTheDocument(); + expect(button).toHaveAttribute('aria-expanded', 'false'); + expect(screen.getByText(textContent)).not.toBeVisible(); + }); + + test('Expands on clicking Details button', async () => { + const textContent = 'Well hello there'; + const { user } = renderComponent(<>{textContent}); + + const button = detailButton(); + await user.click(button); + + expect(button).toHaveAttribute('aria-expanded', 'true'); + expect(screen.getByText(textContent)).toBeVisible(); + }); + + test('Button can have a custom name', () => { + const buttonText = + 'This is my button, there are many others like it but this one is mine'; + renderComponent(<>without my button i am nothing, buttonText); + + expect(detailButton(buttonText)).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/components/ThemedButtons/DetailElement.tsx b/frontend/src/components/ThemedButtons/DetailElement.tsx new file mode 100644 index 000000000..25fbadb7e --- /dev/null +++ b/frontend/src/components/ThemedButtons/DetailElement.tsx @@ -0,0 +1,47 @@ +import { PropsWithChildren, useCallback, useState } from 'react'; + +import './DetailElement.css'; + +export interface DetailElementProps extends PropsWithChildren { + useIcon: boolean; + buttonText?: string; +} + +function DetailElement({ + useIcon, + buttonText = 'Details', + children, +}: DetailElementProps) { + const [isExpanded, setIsExpanded] = useState(false); + + const toggleState = useCallback(() => { + setIsExpanded((expanded) => !expanded); + }, []); + + return ( + <> + +
+ {children} +
+ + ); +} + +export default DetailElement; diff --git a/frontend/src/components/ThemedInput/ThemedNumberInput.css b/frontend/src/components/ThemedInput/ThemedNumberInput.css index 0613e59b9..aead0cfc8 100644 --- a/frontend/src/components/ThemedInput/ThemedNumberInput.css +++ b/frontend/src/components/ThemedInput/ThemedNumberInput.css @@ -5,7 +5,9 @@ appearance: none; } -/* hide arrows in firefox */ .themed-number-input { + outline-offset: 0.125rem; + + /* hide arrows in firefox */ appearance: textfield; } diff --git a/frontend/src/components/ThemedInput/ThemedTextArea.tsx b/frontend/src/components/ThemedInput/ThemedTextArea.tsx index fb989b6e1..948541150 100644 --- a/frontend/src/components/ThemedInput/ThemedTextArea.tsx +++ b/frontend/src/components/ThemedInput/ThemedTextArea.tsx @@ -1,5 +1,5 @@ import { clsx } from 'clsx'; -import { KeyboardEvent, useEffect, useRef } from 'react'; +import { ChangeEvent, KeyboardEvent, useEffect, useRef } from 'react'; import useIsOverflow from '@src/hooks/useIsOverflow'; @@ -86,13 +86,31 @@ function ThemedTextArea({ }) { const textareaRef = useRef(null); + const resizeObserverRef = useRef( + new ResizeObserver((entries) => { + entries.forEach((entry) => { + // If component was hidden in DOM when mounted (e.g. display: none) then + // we must trigger resize when it first becomes visible + if (entry.contentBoxSize[0].blockSize === 0) { + resizeInput(entry.target as HTMLTextAreaElement, maxLines); + } + }); + }) + ); + useEffect(() => { - if (textareaRef.current) { - resizeInput(textareaRef.current, maxLines); - } + textareaRef.current && + resizeObserverRef.current.observe(textareaRef.current); + return () => { + resizeObserverRef.current.disconnect(); + }; + }, []); + + useEffect(() => { + textareaRef.current && resizeInput(textareaRef.current, maxLines); }, [content]); - function inputChange(event: React.ChangeEvent) { + function inputChange(event: ChangeEvent) { onContentChanged(event.target.value); } @@ -112,6 +130,8 @@ function ThemedTextArea({ return (