Skip to content

Context Menu Stories and Implementation - #4913

Open
Yanko Mirov (tranculent) wants to merge 10 commits into
mainfrom
CAPY-2348/context-menu-component
Open

Context Menu Stories and Implementation#4913
Yanko Mirov (tranculent) wants to merge 10 commits into
mainfrom
CAPY-2348/context-menu-component

Conversation

@tranculent

@tranculent Yanko Mirov (tranculent) commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new BpkContextMenu compound component wrapping Ark UI's Menu primitive, with accessible keyboard navigation, focus management, ARIA semantics, and floating positioning out of the box.

For Backpack reviewers

https://backpack.github.io/storybook-prs/4913/?path=/docs/bpk-component-context-menu--documentation

New component: BpkContextMenu

Namespaced compound API:

Part Purpose
.Root State container — open state, keyboard navigation, selection dispatch
.Trigger Generic trigger. Renders a plain <button> by default; asChild merges props onto a custom element
.SaveTrigger Pre-styled circular heart-icon trigger for the save-to-list pattern. No consumer styling required
.Content Floating menu surface, portalled to escape overflow: hidden ancestors
.Item Selectable row — supports endIcon, destructive variant, and onClick
.ItemGroup Visual grouping container for related items
.Separator Horizontal rule between groups
.TriggerItem Row that opens a nested sub-menu

Architecture decisions

  • BpkContextMenu.SaveTrigger owns the heart-button visual entirely within the context menu package — consumers get the correct visual without depending on BpkSaveButton as an Ark asChild trigger, avoiding coupling between the two components
  • Focus ring suppression on trigger close is handled by useMenuTriggerFocusGuard — a shared hook used by both Trigger and SaveTrigger. It tracks pointer vs keyboard interactions to suppress the :focus-visible ring after pointer-initiated close while preserving it for keyboard navigation
  • unmountOnExit on Menu.Root ensures the menu is removed from the DOM after the close animation, preventing the invisible element from blocking pointer events

BpkSaveButton — no functional changes

The initial draft of this PR composed BpkSaveButton as an Ark asChild trigger, which required adding forwardRef, onClick, hoverEffect, and focus-ring overrides to BpkSaveButton. That approach was abandoned in favour of BpkContextMenu.SaveTrigger. BpkSaveButton is unchanged from its pre-PR state.

ESLint: Menu.Trigger and Menu.TriggerItem added to the forbid-component-props allowlist.

Storybook + unit + accessibility tests included.

Consumer usage

// When the user has saved lists — show the context menu
<BpkContextMenu.Root onSelect={({ value }) => handleSelect(value)}>
  <BpkContextMenu.SaveTrigger aria-label="Save to trip" />
  <BpkContextMenu.Content>
    <BpkContextMenu.ItemGroup>
      {trips.map(trip => (
        <BpkContextMenu.Item key={trip.id} value={trip.id}>
          {trip.name}
        </BpkContextMenu.Item>
      ))}
    </BpkContextMenu.ItemGroup>
    <BpkContextMenu.Separator />
    <BpkContextMenu.ItemGroup>
      <BpkContextMenu.Item value="new-trip" endIcon={<PlusIcon />}>
        Plan a new trip
      </BpkContextMenu.Item>
    </BpkContextMenu.ItemGroup>
  </BpkContextMenu.Content>
</BpkContextMenu.Root>

// When the user has no saved lists — plain BpkSaveButton, unchanged
<BpkSaveButton
  checked={isSaved}
  accessibilityLabel={isSaved ? 'Remove' : 'Save'}
  onCheckedChange={handleToggle}
/>
image image image
  • Component README.md
  • Tests
  • Accessibility tests
  • Storybook examples

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

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

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

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

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

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

@tranculent
Yanko Mirov (tranculent) marked this pull request as ready for review July 24, 2026 14:22
Copilot AI review requested due to automatic review settings July 24, 2026 14:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds a new BpkContextMenu compound component wrapping Ark UI’s Menu, plus small additive changes to BpkSaveButton to support composition as an asChild menu trigger.

Changes:

  • Introduces bpk-component-context-menu with a namespaced API (Root, Trigger, Content, Item, ItemGroup, Separator, TriggerItem) and SCSS styling.
  • Adds Storybook stories and unit + a11y tests for the new context menu.
  • Updates BpkSaveButton to use forwardRef, adds onClick and hoverEffect props, adjusts prop spread order, and tweaks focus styling when used as a menu trigger.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/backpack-web/src/bpk-component-context-menu/src/common-types.ts Adds shared public types and item variant constants for the context menu.
packages/backpack-web/src/bpk-component-context-menu/src/accessibility-test.tsx Adds axe-based accessibility coverage for open/closed states.
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenuTrigger.tsx Implements the trigger wrapper with optional asChild composition.
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenuSubTriggerItem.tsx Adds submenu trigger item wrapper (Menu.TriggerItem).
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenuSeparator.tsx Adds separator wrapper (Menu.Separator).
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenuRoot.tsx Adds root wrapper (Menu.Root) and wires selection/open callbacks.
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenuItemGroup.tsx Adds item group wrapper for layout/dividers.
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenuItem.tsx Adds menu item wrapper with variant, endIcon, and disabled support.
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenuContent.tsx Adds portalled content/positioner wrappers.
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenu.tsx Defines the namespaced API surface for the component.
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenu.stories.tsx Provides Storybook examples and Percy-friendly visual stories.
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenu.module.scss Adds styles for trigger, content surface, items, and animations.
packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenu-test.tsx Adds unit tests for rendering, selection, variants, and slots.
packages/backpack-web/src/bpk-component-context-menu/index.ts Exposes the component, constants, and selected public types.
packages/backpack-web/src/bpk-component-context-menu/README.md Documents installation/usage/parts and accessibility notes.
packages/backpack-web/src/bpk-component-card-button/src/BpkSaveButton.tsx Adds forwardRef, onClick, hoverEffect, and changes prop spreading.
packages/backpack-web/src/bpk-component-card-button/src/BpkSaveButton.module.scss Adjusts focus styling for menu-trigger usage and adds “no-hover” modifiers.
.eslintrc Allow-lists Ark Menu.* components for className prop usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/backpack-web/src/bpk-component-context-menu/README.md Outdated
Comment thread packages/backpack-web/src/bpk-component-context-menu/index.ts
Comment thread packages/backpack-web/src/bpk-component-card-button/src/BpkSaveButton.module.scss Outdated
Comment thread packages/backpack-web/src/bpk-component-context-menu/README.md
Comment thread packages/backpack-web/src/bpk-component-card-button/src/BpkSaveButton.tsx Outdated
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

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

@tranculent Yanko Mirov (tranculent) added the patch Patch production bug label Jul 27, 2026
Comment thread packages/backpack-web/src/bpk-component-card-button/src/BpkSaveButton.tsx Outdated
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

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

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

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

Comment thread packages/backpack-web/src/bpk-component-context-menu/src/BpkContextMenuItem.tsx Outdated
Comment thread packages/backpack-web/src/bpk-component-context-menu/index.ts Outdated
@toveadamsson

Copy link
Copy Markdown
Contributor

Regarding the long list UI, is it intentional that Plan a new trip and Quick Save aren't visible initially?
Screenshot 2026-07-27 at 14 00 19

@toveadamsson

Copy link
Copy Markdown
Contributor

Can we include documents in the descriptions that will be useful for external review, as to why certain decisions were made and the figma design as well 🙏

@tranculent

Copy link
Copy Markdown
Contributor Author

Regarding the long list UI, is it intentional that Plan a new trip and Quick Save aren't visible initially? Screenshot 2026-07-27 at 14 00 19

this is just a story but the way this Ark UI component is exposed to consumers is it lets them craft the menu as they wish. So technically it is possible to just render the Plan a new trip and quick save block on top without any issues. Also, this hasn't been confirmed by design yet.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

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

flex-direction: column;
border-radius: tokens.$bpk-border-radius-lg;
outline: none;
background-color: var(--bpk-canvas, #{tokens.$bpk-canvas-day});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

--bpk-canvas does not exist, it should be --bpk-canvas-default

Suggested change
background-color: var(--bpk-canvas, #{tokens.$bpk-canvas-day});
background-color: var(--bpk-canvas-default, #{tokens.$bpk-canvas-day});

border: 0;
block-size: 0;
border-block-start: tokens.$bpk-border-size-sm solid
var(--bpk-line, #{tokens.$bpk-line-day});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

--bpk-line does not exist, can you double check?
I think it's --bpk-other-line-default, but not 100% sure

@Supremeyh Ezreal Yang (Supremeyh) added minor Non breaking change and removed patch Patch production bug labels Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor Non breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants