[BpkChatbotInput]Bpk component chatbox input#4279
[BpkChatbotInput]Bpk component chatbox input#4279GC Zhu (gc-skyscanner) wants to merge 21 commits intomainfrom
Conversation
…operty (#4265) * add Claude SDD spec for BpkButton * add notes for the definition of themeable * Make corner radius configurable via bpk-themeable * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update specs/001-bpkbutton-baseline/spec.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * solve comment * [CLOV-1327] Address Copilot review comments - Fix Object.keys → Object.values in BpkButton-test.tsx forEach loop so tests iterate over kebab-case type values (e.g. primary-on-dark) that match actual BEM class names, not camelCase keys - Remove unused ButtonType import (no longer needed after removing cast) - Update plan.md: replace "snapshot regeneration" with accurate description of explicit assertion approach; expand Files changed list to include test, snapshot deletion, and spec documentation artefacts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * remove the BpkButton spec * add storybook example for the themed boder radius for BpkButton * imporve BpkButton unit test --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Bumps the artifacts-actions group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/download-artifact` from 7.0.0 to 8.0.0 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](actions/download-artifact@37930b1...70fc10c) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: artifacts-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new bpk-component-chatbot-input package to Backpack, providing a chat-style input with a default single-line mode and a composer (textarea) mode, plus supporting hooks, styling, tests, and Storybook examples.
Changes:
- Introduces
BpkChatbotInputwith default + composer modes and a send button/loading state. - Adds supporting hooks (
useChatbotInput,useChatbotInputManager,useTextAreaAutoResize,useInputHandlers) and component subparts (InputField,TextAreaField,SendButton). - Adds unit tests, accessibility tests, snapshots, and Storybook examples for the new component.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/bpk-component-chatbot-input/src/types.ts | Defines shared prop types for input field variants. |
| packages/bpk-component-chatbot-input/src/hooks/useTextAreaAutoResize.ts | Implements textarea measurement + auto-resize logic. |
| packages/bpk-component-chatbot-input/src/hooks/useTextAreaAutoResize-test.tsx | Tests textarea auto-resize behavior and scrolling behavior. |
| packages/bpk-component-chatbot-input/src/hooks/useInputHandlers.ts | Centralizes click/touch/change handlers for input controls. |
| packages/bpk-component-chatbot-input/src/hooks/useInputHandlers-test.tsx | Tests handler behavior for click/touch/change. |
| packages/bpk-component-chatbot-input/src/hooks/useChatbotInputManager.ts | Adds state management for chatbot input value and submit/autoclear behavior. |
| packages/bpk-component-chatbot-input/src/hooks/useChatbotInputManager-test.tsx | Tests input manager state transitions and submission logic. |
| packages/bpk-component-chatbot-input/src/hooks/useChatbotInput.ts | Provides UI state (focused/disabled/overlimit/etc) and input props for the component. |
| packages/bpk-component-chatbot-input/src/hooks/useChatbotInput-test.tsx | Tests derived state and key-handling behavior for the hook. |
| packages/bpk-component-chatbot-input/src/hooks/index.ts | Barrel exports for hooks used by the component. |
| packages/bpk-component-chatbot-input/src/constants.ts | Declares input type constants and default max character limit. |
| packages/bpk-component-chatbot-input/src/accessibility-test.tsx | Adds jest-axe coverage for default/composer/loading renders. |
| packages/bpk-component-chatbot-input/src/snapshots/BpkChatbotInput-test.tsx.snap | Stores render snapshots for default and composer variants. |
| packages/bpk-component-chatbot-input/src/TextAreaField/TextAreaField.tsx | Implements the composer textarea UI. |
| packages/bpk-component-chatbot-input/src/TextAreaField/TextAreaField.module.scss | Styles for composer textarea container/field. |
| packages/bpk-component-chatbot-input/src/TextAreaField/TextAreaField-test.tsx | Tests textarea field rendering, events, and axe checks. |
| packages/bpk-component-chatbot-input/src/SendButton/SendButton.tsx | Adds send button with icon + loading state behavior. |
| packages/bpk-component-chatbot-input/src/SendButton/SendButton-test.tsx | Tests send button render/click/disabled + axe. |
| packages/bpk-component-chatbot-input/src/LoadingButton/LoadingButton.tsx | Adds a loading button implementation (currently not referenced). |
| packages/bpk-component-chatbot-input/src/LoadingButton/LoadingButton.module.scss | Styles for the loading button. |
| packages/bpk-component-chatbot-input/src/InputField/InputField.tsx | Implements default single-line input UI. |
| packages/bpk-component-chatbot-input/src/InputField/InputField.module.scss | Styles for the single-line input field. |
| packages/bpk-component-chatbot-input/src/InputField/InputField-test.tsx | Tests input field rendering, events, and axe checks. |
| packages/bpk-component-chatbot-input/src/BpkChatbotInput.tsx | Main component wiring modes, hooks, and send button. |
| packages/bpk-component-chatbot-input/src/BpkChatbotInput.module.scss | Styles for default/composer container variants. |
| packages/bpk-component-chatbot-input/src/BpkChatbotInput-test.tsx | Component tests for variants, loading, and keyboard interactions. |
| packages/bpk-component-chatbot-input/index.ts | Public entrypoint exports for the new package. |
| packages/bpk-component-chatbot-input/README.md | Package documentation and usage instructions. |
| examples/bpk-component-chatbot-input/stories.tsx | Storybook story registration for the new component. |
| examples/bpk-component-chatbot-input/examples.tsx | Interactive examples for default/composer/loading states. |
| examples/bpk-component-chatbot-input/examples.module.scss | Example layout styling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| export const LINE_HEIGHT = 24; | ||
| export const MIN_INPUT_HEIGHT = LINE_HEIGHT; | ||
| export const MAX_INPUT_HEIGHT_PHASE_1 = LINE_HEIGHT * 4; | ||
| export const MAX_INPUT_HEIGHT_PHASE_2 = LINE_HEIGHT * 5; | ||
| export const MIN_CONTAINER_HEIGHT = 24; | ||
| export const MAX_CONTAINER_HEIGHT = 96; | ||
| export const PARENT_PADDING_TOP = 16; |
There was a problem hiding this comment.
packages/bpk-component-chatbot-input/src/TextAreaField/TextAreaField.module.scss
Show resolved
Hide resolved
packages/bpk-component-chatbot-input/src/hooks/useTextAreaAutoResize-test.tsx
Show resolved
Hide resolved
packages/bpk-component-chatbot-input/src/LoadingButton/LoadingButton.tsx
Outdated
Show resolved
Hide resolved
packages/bpk-component-chatbot-input/src/TextAreaField/TextAreaField.tsx
Show resolved
Hide resolved
packages/bpk-component-chatbot-input/src/hooks/useInputHandlers.ts
Outdated
Show resolved
Hide resolved
packages/bpk-component-chatbot-input/src/hooks/useChatbotInput.ts
Outdated
Show resolved
Hide resolved
packages/bpk-component-chatbot-input/src/TextAreaField/TextAreaField.tsx
Outdated
Show resolved
Hide resolved
|
Visit https://backpack.github.io/storybook-prs/4279 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4279 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4279 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4279 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4279 to see this build running in a browser. |
| const measureElementRef = useRef<HTMLTextAreaElement | null>(null); | ||
| const previousValueRef = useRef<string>(''); | ||
| const shouldScrollRef = useRef<boolean>(false); | ||
| const isInitialRenderRef = useRef<boolean>(true); |
There was a problem hiding this comment.
Why useRef for all four?(same implementation with carhire's)
All four values share one thing in common: changes to them don't need to trigger a UI re-render.
| ref | reason |
|---|---|
| measureElementRef | Stores a DOM node — the classic use case for useRef |
| previousValueRef | A pure computation helper, only read/written inside effects, never affects render output |
| shouldScrollRef | A cross-effect signal flag — using useState would cause unnecessary re-renders or even an infinite loop |
| isInitialRenderRef | A one-time flag that flips once over the component's lifetime, has no impact on any UI |
Of the values in this hook, only dimensions uses useState — because those values are applied directly as inline styles and must trigger a re-render to take effect visually.
|
|
||
| const textarea = ref.current; | ||
| const computedStyle = getComputedStyle(textarea); | ||
| const tempTextarea = document.createElement('textarea'); |
There was a problem hiding this comment.
The reason why use tempTextArea here(Same implemetation with Car hire's)
The real textarea is for display — its height is locked. The hidden textarea is for measurement — its height is free to expand. By mirroring the same content and styles in the hidden element, we can measure the true height the content needs, then apply that height back to the real textarea.
|
Visit https://backpack.github.io/storybook-prs/4279 to see this build running in a browser. |
| document.body.appendChild(tempTextarea); | ||
| measureElementRef.current = tempTextarea; | ||
|
|
||
| const resizeObserver = new ResizeObserver((entries) => { |
There was a problem hiding this comment.
Add ResizeObserver here(different from carhire, the original implementation)
Reason is:
The height calculation fails when the container width changes
previously recalculates the height only when the value changes ⬇️
However,
measureEl.style.width depends on textarea.offsetWidth. If the user does not input any new content but only the width of the container changes due to window scaling or layout alterations, the width will remain the old value and the calculation of the number of lines will be incorrect.
for example:
The user entered three lines of text, and the window narrowed → it actually became five lines
But value remains unchanged → Effect 2 is not triggered → height is still displayed in 3 lines ❌
|
Visit https://backpack.github.io/storybook-prs/4279 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4279 to see this build running in a browser. |
| width: 100%; | ||
| align-items: center; | ||
| transition: | ||
| height 0.3s cubic-bezier(0.4, 0, 0.2, 1), |
| transition: | ||
| border-color tokens.$bpk-duration-sm ease, | ||
| box-shadow tokens.$bpk-duration-sm ease; | ||
| border: 1.5 * tokens.$bpk-one-pixel-rem solid tokens.$bpk-line-day; |


Remember to include the following changes:
[Clover-123][BpkButton] Updating the colourREADME.md(If you have created a new component)README.md