[CLOV-1347] Add BpkChatBubble component#4315
[CLOV-1347] Add BpkChatBubble component#4315Richard-Shen (RichardSyq) wants to merge 7 commits intomainfrom
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove role="presentation" from chat bubble div (WCAG SC 1.3.1 / 4.1.2) - Guard showFeedback rendering to bot type only - Fix systemPosition CSS classes to apply only to bot type (not retry) - Remove hardcoded English default for retryLabel - Remove non-descriptive aria-label default for suggestionAriaLabel - Replace raw :hover with utils.bpk-hover mixin (touch device guard) - Replace magic 0.2s/0.22s with tokens.$bpk-duration-sm - Replace $bpk-text-secondary-day with $bpk-line-day in box-shadow - Replace font-size: inherit with typography.bpk-body-default mixin - Add tests for showFeedback not rendering on user/retry types - Regenerate snapshots Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Visit https://backpack.github.io/storybook-prs/4315 to see this build running in a browser. |
1 similar comment
|
Visit https://backpack.github.io/storybook-prs/4315 to see this build running in a browser. |
Move gap declaration before @include to satisfy order/order rule Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Visit https://backpack.github.io/storybook-prs/4315 to see this build running in a browser. |
- Use bpk-canvas-contrast-day background so white bot bubbles are visible - Rewrite MixedExample as single container for proper chat flow layout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Visit https://backpack.github.io/storybook-prs/4315 to see this build running in a browser. |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Visit https://backpack.github.io/storybook-prs/4315 to see this build running in a browser. |
There was a problem hiding this comment.
Pull request overview
Adds a new official Backpack web component package, bpk-component-chat-bubble, migrated from the car hire homepage implementation with a simplified, app-agnostic API.
Changes:
- Introduces
BpkChatBubblesupportinguser,bot,retry, andsuggestionvariants with sequencing + animation delay support. - Adds styling via new SCSS module (BEM modifiers, RTL, entrance animation, sequencing corner radii).
- Adds unit tests, accessibility (jest-axe) tests, Storybook examples, and snapshots.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/bpk-component-chat-bubble/src/BpkChatBubble.tsx | New component implementation and props API. |
| packages/bpk-component-chat-bubble/src/BpkChatBubble.module.scss | Visual styling, RTL, animations, and sequencing modifiers. |
| packages/bpk-component-chat-bubble/src/BpkChatBubble-test.tsx | Unit test suite for rendering and interactions. |
| packages/bpk-component-chat-bubble/src/accessibility-test.tsx | jest-axe coverage for all variants. |
| packages/bpk-component-chat-bubble/src/snapshots/BpkChatBubble-test.tsx.snap | Snapshots for rendered variants. |
| packages/bpk-component-chat-bubble/index.ts | Package entrypoint + type exports. |
| packages/bpk-component-chat-bubble/README.md | Public documentation (installation/usage/props). |
| examples/bpk-component-chat-bubble/stories.tsx | Storybook stories registration. |
| examples/bpk-component-chat-bubble/examples.tsx | Story implementations for each variant/sequence. |
| examples/bpk-component-chat-bubble/examples.module.scss | Example layout styling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| cubic-bezier(0.2, 0, 0, 1) forwards; | ||
| animation-delay: var(--anim-delay, 0ms); | ||
|
|
||
| @media (prefers-reduced-motion: reduce) { |
There was a problem hiding this comment.
In the prefers-reduced-motion branch you swap to an opacity-only keyframe, but the base transform: translateY(...) scale(...) remains. This means reduced-motion users may see the bubble stay permanently shifted/scaled. Consider also resetting transform to the final state (e.g. translateY(0) scale(1) / none) within the reduced-motion media query.
| @media (prefers-reduced-motion: reduce) { | |
| @media (prefers-reduced-motion: reduce) { | |
| transform: translateY(0) scale(1); |
| align-items: center; | ||
| gap: tokens.bpk-spacing-base(); | ||
|
|
||
| > span { |
There was a problem hiding this comment.
.bpk-chat-bubble__thumbs > span { ... } doesn’t match the current DOM (thumb buttons render as <button> elements, not wrapped in spans), so this rule is dead code. Consider removing it or updating the selector to target the actual child element if a style override is still needed.
| > span { | |
| > button { |
| | retryLabel | string | false | 'Try again' | | ||
| | onSuggestionClick | () => void | false | - | | ||
| | suggestionAriaLabel | string | false | 'suggestion' | |
There was a problem hiding this comment.
The README lists default values for retryLabel and suggestionAriaLabel, but the implementation doesn’t currently provide defaults for either prop. Please align the documentation with the actual component behavior (or add defaults in the component to match the README).
| | retryLabel | string | false | 'Try again' | | |
| | onSuggestionClick | () => void | false | - | | |
| | suggestionAriaLabel | string | false | 'suggestion' | | |
| | retryLabel | string | false | - | | |
| | onSuggestionClick | () => void | false | - | | |
| | suggestionAriaLabel | string | false | - | |
| export const BotBubbleExample = () => ( | ||
| <div className={getClassName('bpk-chat-bubble-examples')}> | ||
| <BpkChatBubble type="bot"> | ||
| {"Hey! I'm your car hire assistant. Feel free to ask me anything about renting a car, and I\u0027ll put my thinking cap on."} |
There was a problem hiding this comment.
BotBubbleExample contains a JSX string literal with a \u0027 escape sequence, which makes the example hard to read and is inconsistent with the rest of the examples using '. Consider rewriting this as normal JSX text (or a regular string) and escaping apostrophes consistently.
| {"Hey! I'm your car hire assistant. Feel free to ask me anything about renting a car, and I\u0027ll put my thinking cap on."} | |
| Hey! I'm your car hire assistant. Feel free to ask me anything about renting a car, and I'll put my thinking cap on. |
| const v = Math.min(ms, max); | ||
| return Math.round(v / 50) * 50; |
There was a problem hiding this comment.
clampAndSnap only clamps the upper bound. If a consumer passes a negative animationDelay (or NaN), the computed CSS variable can become a negative/invalid animation-delay, which contradicts the documented 0–300ms contract. Consider clamping to a minimum of 0 (and handling non-finite numbers) before snapping.
| const v = Math.min(ms, max); | |
| return Math.round(v / 50) * 50; | |
| const safeMs = Number.isFinite(ms) ? ms : 0; | |
| const clamped = Math.min(Math.max(safeMs, 0), max); | |
| return Math.round(clamped / 50) * 50; |
| isBot && systemPosition === 'first' && 'bpk-chat-bubble--system-first', | ||
| isBot && systemPosition === 'middle' && 'bpk-chat-bubble--system-middle', | ||
| isBot && systemPosition === 'last' && 'bpk-chat-bubble--system-last', |
There was a problem hiding this comment.
systemPosition classes are only applied when type === 'bot', but the SCSS modifiers (bpk-chat-bubble--system-*) aren’t bot-specific and a retry bubble is also rendered left-aligned like a system message. If retry bubbles should participate in sequencing, consider applying systemPosition modifiers for retry as well (or rename the prop to reflect bot-only behavior).
| isBot && systemPosition === 'first' && 'bpk-chat-bubble--system-first', | |
| isBot && systemPosition === 'middle' && 'bpk-chat-bubble--system-middle', | |
| isBot && systemPosition === 'last' && 'bpk-chat-bubble--system-last', | |
| (isBot || isRetry) && systemPosition === 'first' && 'bpk-chat-bubble--system-first', | |
| (isBot || isRetry) && systemPosition === 'middle' && 'bpk-chat-bubble--system-middle', | |
| (isBot || isRetry) && systemPosition === 'last' && 'bpk-chat-bubble--system-last', |
| onClick={onRetry} | ||
| disabled={retryDisabled} | ||
| > | ||
| {retryLabel} |
There was a problem hiding this comment.
When type="retry" and onRetry is provided, the retry button renders {retryLabel} but retryLabel is optional and has no default. This can produce an empty button with no accessible name. Consider defaulting retryLabel (e.g. to "Try again") and/or making it required whenever onRetry is set.
| {retryLabel} | |
| {retryLabel || 'Try again'} |
|
Visit https://backpack.github.io/storybook-prs/4315 to see this build running in a browser. |
| return Math.round(v / 50) * 50; | ||
| }; | ||
|
|
||
| export type ChatBubbleType = 'user' | 'bot' | 'retry' | 'suggestion'; |
There was a problem hiding this comment.
Can we rename the suggestion one to match what is in Figma (ButtonBubble)
There was a problem hiding this comment.
We also need a 'carsButtonBubble' as they have a custom shadow hover. Can you work with Adam to understand hover on the button bubble?
Summary
Bubblechat component fromcarhire-homepage'sunstable_backpack/Bubble/into a new official Backpack component:bpk-component-chat-bubblechildren: ReactNodefor contentNew Component:
BpkChatBubbleSupports four bubble types:
user— right-aligned user message bubblebot— left-aligned bot/assistant message bubbleretry— retry action bubblesuggestion— suggestion chip bubbleKey Changes
useChatbotTracking,IntersectionObserverWrapper,useI18n, andformatChatMessagechildrendirectly instead of aChatBubbledata objectJira
https://skyscanner.atlassian.net/browse/CLOV-1347
🤖 Generated with Claude Code