-
Notifications
You must be signed in to change notification settings - Fork 231
fix: Improve dynamic adjustment of prompt input height #4582
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
Open
jperals
wants to merge
12
commits into
main
Choose a base branch
from
fix/prompt-input-dynamic-height
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+264
−22
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
693addc
fix: Improve dynamic adjustment of prompt input height
jperals 6cd582e
Fix setup for SSR
jperals ddb66de
Better fix
jperals dd14077
Merge branch 'main' into fix/prompt-input-dynamic-height
jperals 4d45abe
Remove type casts
jperals affe6e0
Do not test token mode in react 16
jperals 735ccd7
Add coverage
jperals 13ca1ff
Fix imports
jperals fd17aeb
Merge branch 'main' into fix/prompt-input-dynamic-height
jperals 11ea68b
Fix import
jperals d53b96d
Merge branch 'main' into fix/prompt-input-dynamic-height
jperals 55343d1
Extend comment
jperals File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| import React, { useContext, useState } from 'react'; | ||
|
|
||
| import { FormField, RadioGroup, SpaceBetween } from '~components'; | ||
| import PromptInput, { PromptInputProps } from '~components/prompt-input'; | ||
|
|
||
| import AppContext, { AppContextType } from '../app/app-context'; | ||
| import { SimplePage } from '../app/templates'; | ||
|
|
||
| const longText = | ||
| 'This is a long message that should cause the prompt input to grow in height when the container becomes narrower, because the text wraps to more lines. Adding even more text to ensure wrapping.'; | ||
|
|
||
| const sampleTokens: PromptInputProps.InputToken[] = [ | ||
| { type: 'text', value: longText }, | ||
| { type: 'reference', id: 'ref-1', label: 'Alice', value: 'alice', menuId: 'mentions' }, | ||
| { type: 'text', value: ' and some more text that adds to the wrapping behavior of the component.' }, | ||
| ]; | ||
|
|
||
| const menus: PromptInputProps.MenuDefinition[] = [ | ||
| { | ||
| id: 'mentions', | ||
| trigger: '@', | ||
| options: [ | ||
| { value: 'alice', label: 'Alice' }, | ||
| { value: 'bob', label: 'Bob' }, | ||
| ], | ||
| }, | ||
| ]; | ||
|
|
||
| type PageContext = React.Context<AppContextType<{ inputMode: string; containerWidth: string }>>; | ||
|
|
||
| export default function ContainerResizePage() { | ||
| const { urlParams, setUrlParams } = useContext(AppContext as PageContext); | ||
| const isTokenMode = urlParams.inputMode === 'token'; | ||
| const containerWidth = Number(urlParams.containerWidth) || 400; | ||
|
|
||
| const [tokens, setTokens] = useState<readonly PromptInputProps.InputToken[]>(sampleTokens); | ||
| const [value, setValue] = useState( | ||
| longText + ' Alice and some more text that adds to the wrapping behavior of the component.' | ||
| ); | ||
|
|
||
| return ( | ||
| <SimplePage | ||
| title="Prompt Input - Container Resize" | ||
| subtitle="Tests that the prompt input adjusts height when its container resizes (not just window resize)" | ||
| > | ||
| <SpaceBetween size="m"> | ||
| <SpaceBetween size="l" direction="horizontal"> | ||
| <FormField label="Container width"> | ||
| <RadioGroup | ||
| data-testid="width-radio" | ||
| value={String(containerWidth)} | ||
| onChange={({ detail }) => setUrlParams({ containerWidth: detail.value })} | ||
| items={[ | ||
| { value: '400', label: '400px' }, | ||
| { value: '600', label: '600px' }, | ||
| { value: '800', label: '800px' }, | ||
| { value: '1200', label: '1200px' }, | ||
| ]} | ||
| /> | ||
| </FormField> | ||
|
|
||
| <FormField label="Mode"> | ||
| <RadioGroup | ||
| value={isTokenMode ? 'token' : 'plain'} | ||
| onChange={({ detail }) => setUrlParams({ inputMode: detail.value })} | ||
| items={[ | ||
| { value: 'token', label: 'Token mode' }, | ||
| { value: 'plain', label: 'Plain text' }, | ||
| ]} | ||
| /> | ||
| </FormField> | ||
| </SpaceBetween> | ||
|
|
||
| <div | ||
| data-testid="resizable-container" | ||
| style={{ | ||
| width: containerWidth, | ||
| }} | ||
| > | ||
| <PromptInput | ||
| value={isTokenMode ? undefined : value} | ||
| onChange={event => { | ||
| setValue(event.detail.value); | ||
| if (event.detail.tokens) { | ||
| setTokens(event.detail.tokens); | ||
| } | ||
| }} | ||
| tokens={isTokenMode ? tokens : undefined} | ||
| menus={isTokenMode ? menus : undefined} | ||
| maxRows={10} | ||
| placeholder="Type here..." | ||
| data-testid="prompt-input" | ||
| /> | ||
| </div> | ||
| </SpaceBetween> | ||
| </SimplePage> | ||
| ); | ||
| } |
21 changes: 21 additions & 0 deletions
21
src/internal/hooks/use-width-change/__tests__/use-width-change.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| import { useRef } from 'react'; | ||
|
|
||
| import { renderHook } from '../../../../__tests__/render-hook'; | ||
| import { useWidthChange } from '../index'; | ||
|
|
||
| describe('useWidthChange', () => { | ||
| test('does not throw when ref is null', () => { | ||
| const onWidthChange = jest.fn(); | ||
|
|
||
| expect(() => { | ||
| renderHook(() => { | ||
| const ref = useRef<HTMLElement>(null); | ||
| useWidthChange(ref, onWidthChange); | ||
| }); | ||
| }).not.toThrow(); | ||
|
|
||
| expect(onWidthChange).not.toHaveBeenCalled(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| import { useEffect, useRef } from 'react'; | ||
|
|
||
| /** | ||
| * Observes an element for inline-size (width) changes and calls `onWidthChange` | ||
| * when the width changes. Height-only changes are ignored to prevent infinite | ||
| * loops when the callback adjusts the element's height. | ||
| * | ||
| * Unlike useResizeObserver from the component-toolkit package, it does not cause | ||
| * re-renders when the width changes. | ||
| * | ||
| * @param elementRef - A ref object pointing to the element to observe. | ||
| * @param onWidthChange - Callback fired when the element's width changes. | ||
| */ | ||
| export function useWidthChange(elementRef: React.RefObject<HTMLElement>, onWidthChange: () => void): void { | ||
| const lastWidthRef = useRef(-1); | ||
|
|
||
| useEffect(() => { | ||
| const node = elementRef.current; | ||
| if (!node) { | ||
| return; | ||
| } | ||
| const observer = new ResizeObserver(() => { | ||
| const newWidth = node.getBoundingClientRect().width; | ||
| if (newWidth !== lastWidthRef.current) { | ||
| lastWidthRef.current = newWidth; | ||
| onWidthChange(); | ||
| } | ||
| }); | ||
| observer.observe(node); | ||
| return () => observer.disconnect(); | ||
| }, [elementRef, onWidthChange]); | ||
| } | ||
49 changes: 49 additions & 0 deletions
49
src/prompt-input/__integ__/prompt-input-container-resize.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| import { BasePageObject } from '@cloudscape-design/browser-test-tools/page-objects'; | ||
| import useBrowser from '@cloudscape-design/browser-test-tools/use-browser'; | ||
|
|
||
| import createWrapper from '../../../lib/components/test-utils/selectors/index.js'; | ||
| import { isReact18 } from './utils'; | ||
|
|
||
| const wrapper = createWrapper(); | ||
| const promptInputSelector = wrapper.findPromptInput('[data-testid="prompt-input"]').toSelector(); | ||
|
|
||
| class ContainerResizePage extends BasePageObject { | ||
| async getPromptInputHeight() { | ||
| const { height } = await this.getBoundingBox(promptInputSelector); | ||
| return height; | ||
| } | ||
|
|
||
| async selectWidth(value: string) { | ||
| await this.click(wrapper.findRadioGroup('[data-testid="width-radio"]').findInputByValue(value).toSelector()); | ||
| } | ||
| } | ||
|
|
||
| describe.each(isReact18 ? ['token', 'plain'] : ['plain'])('Prompt Input - Container Resize (mode=%s)', mode => { | ||
| test( | ||
| 'adjusts height when container width changes', | ||
| useBrowser(async browser => { | ||
| const page = new ContainerResizePage(browser); | ||
| await browser.url(`#/prompt-input/container-resize?inputMode=${mode}&containerWidth=400`); | ||
| await page.waitForVisible(promptInputSelector); | ||
|
|
||
| // Start at 400px — text should be wrapping, so height is tall | ||
| const heightAt400 = await page.getPromptInputHeight(); | ||
|
|
||
| // Widen to 1200px — text should unwrap, height should decrease | ||
| await page.selectWidth('1200'); | ||
| await page.pause(500); | ||
| const heightAt1200 = await page.getPromptInputHeight(); | ||
|
|
||
| expect(heightAt1200).toBeLessThan(heightAt400); | ||
|
|
||
| // Narrow back to 400px — height should increase again | ||
| await page.selectWidth('400'); | ||
| await page.pause(500); | ||
| const heightAt400Again = await page.getPromptInputHeight(); | ||
|
|
||
| expect(heightAt400Again).toBeGreaterThan(heightAt1200); | ||
| }) | ||
| ); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| export const isReact18 = process.env.REACT_VERSION === '18'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to use
useResizeObserverinstead ofuseWidthChange?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extended the comment text to explain the difference: