Skip to content

[CLOV-1347] Add BpkChatBubble component#4315

Open
Richard-Shen (RichardSyq) wants to merge 7 commits intomainfrom
RichardSyq/carhire-to-backpack
Open

[CLOV-1347] Add BpkChatBubble component#4315
Richard-Shen (RichardSyq) wants to merge 7 commits intomainfrom
RichardSyq/carhire-to-backpack

Conversation

@RichardSyq
Copy link
Contributor

Summary

  • Migrated the Bubble chat component from carhire-homepage's unstable_backpack/Bubble/ into a new official Backpack component: bpk-component-chat-bubble
  • Removed all carhire-specific dependencies (tracking hooks, i18n, IntersectionObserver)
  • Simplified API to flat props with children: ReactNode for content

New Component: BpkChatBubble

Supports four bubble types:

  • user — right-aligned user message bubble
  • bot — left-aligned bot/assistant message bubble
  • retry — retry action bubble
  • suggestion — suggestion chip bubble

Key Changes

  • No app-specific dependencies: removed useChatbotTracking, IntersectionObserverWrapper, useI18n, and formatChatMessage
  • Flat props API: consumers pass children directly instead of a ChatBubble data object
  • BEM-compliant SCSS: animation, RTL support, and sequential message corner-radius changes preserved
  • Full test suite: unit tests + accessibility tests
  • Storybook stories: all variants documented

Jira

https://skyscanner.atlassian.net/browse/CLOV-1347

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Richard Shen and others added 2 commits March 24, 2026 15:58
- 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>
@skyscanner-backpack-bot
Copy link

Visit https://backpack.github.io/storybook-prs/4315 to see this build running in a browser.

1 similar comment
@skyscanner-backpack-bot
Copy link

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>
@skyscanner-backpack-bot
Copy link

Visit https://backpack.github.io/storybook-prs/4315 to see this build running in a browser.

@skyscanner-backpack-bot
Copy link

skyscanner-backpack-bot bot commented Mar 24, 2026

Browser support

If this is a visual change, make sure you've tested it in multiple browsers.

Generated by 🚫 dangerJS against abdfde8

- 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>
@skyscanner-backpack-bot
Copy link

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>
@skyscanner-backpack-bot
Copy link

Visit https://backpack.github.io/storybook-prs/4315 to see this build running in a browser.

@RichardSyq Richard-Shen (RichardSyq) marked this pull request as ready for review March 25, 2026 06:55
Copilot AI review requested due to automatic review settings March 25, 2026 06:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 BpkChatBubble supporting user, bot, retry, and suggestion variants 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) {
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
@media (prefers-reduced-motion: reduce) {
@media (prefers-reduced-motion: reduce) {
transform: translateY(0) scale(1);

Copilot uses AI. Check for mistakes.
align-items: center;
gap: tokens.bpk-spacing-base();

> span {
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.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.

Suggested change
> span {
> button {

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +34
| retryLabel | string | false | 'Try again' |
| onSuggestionClick | () => void | false | - |
| suggestionAriaLabel | string | false | 'suggestion' |
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
| retryLabel | string | false | 'Try again' |
| onSuggestionClick | () => void | false | - |
| suggestionAriaLabel | string | false | 'suggestion' |
| retryLabel | string | false | - |
| onSuggestionClick | () => void | false | - |
| suggestionAriaLabel | string | false | - |

Copilot uses AI. Check for mistakes.
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."}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 &apos;. Consider rewriting this as normal JSX text (or a regular string) and escaping apostrophes consistently.

Suggested change
{"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&apos;m your car hire assistant. Feel free to ask me anything about renting a car, and I&apos;ll put my thinking cap on.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +32
const v = Math.min(ms, max);
return Math.round(v / 50) * 50;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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;

Copilot uses AI. Check for mistakes.
Comment on lines +101 to +103
isBot && systemPosition === 'first' && 'bpk-chat-bubble--system-first',
isBot && systemPosition === 'middle' && 'bpk-chat-bubble--system-middle',
isBot && systemPosition === 'last' && 'bpk-chat-bubble--system-last',
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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',

Copilot uses AI. Check for mistakes.
onClick={onRetry}
disabled={retryDisabled}
>
{retryLabel}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
{retryLabel}
{retryLabel || 'Try again'}

Copilot uses AI. Check for mistakes.
@skyscanner-backpack-bot
Copy link

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';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename the suggestion one to match what is in Figma (ButtonBubble)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need a 'carsButtonBubble' as they have a custom shadow hover. Can you work with Adam to understand hover on the button bubble?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai: claude minor Non breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants