Skip to content

feat: add keyboard shortcuts and tooltips to booking slideover buttons#27428

Merged
dhairyashiil merged 26 commits intomainfrom
devin/1769773070-booking-slideover-keyboard-shortcuts
Feb 19, 2026
Merged

feat: add keyboard shortcuts and tooltips to booking slideover buttons#27428
dhairyashiil merged 26 commits intomainfrom
devin/1769773070-booking-slideover-keyboard-shortcuts

Conversation

@PeerRich
Copy link
Member

@PeerRich PeerRich commented Jan 30, 2026

What does this PR do?

Adds keyboard shortcuts and tooltips to the booking details slideover navigation buttons:

  • Up Arrow (↑): Navigate to previous booking
  • Down Arrow (↓): Navigate to next booking
  • ESC: Close slideover (already existed, now has tooltip)
  • Enter: Join meeting (opens conferencing link)

Each button now displays a tooltip showing the keyboard shortcut on hover.

Keyboard shortcuts are automatically disabled when focus is inside a Radix overlay (dialog, dropdown, popover, etc.) so they don't interfere with overlay interactions like BookingActionsDropdown.

Changes

  • bookingSheetKeyboardHandler.ts (new): Extracted keyboard handler logic into a testable utility. Exports isEditableTarget, checkSheetActive, and createBookingSheetKeydownHandler.
  • bookingSheetKeyboardHandler.test.ts (new): 25 unit tests covering editable target detection, sheet active state, arrow key propagation behavior (including at list boundaries), Enter key handling, overlay suppression, and unhandled key passthrough.
  • booking-sheet-keyboard.e2e.ts (new): 6 Playwright E2E tests covering arrow navigation, boundary conditions (first/last booking), dropdown suppression at boundaries, Escape to close, and rapid keypresses.
  • BookingDetailsSheet.tsx: Uses the extracted utility for keyboard handling. Wrapped navigation and close buttons with <Tooltip>. Added isSheetActive() check using Radix portal detection. Updated onInteractOutside to check both [data-booking-list-item] and [data-booking-calendar-event] selectors. Added data-testid="booking-sheet-title" for stable E2E selectors.
  • JoinMeetingButton.tsx: Converted to forwardRef to support ref from parent. Added optional showTooltip prop that wraps button in <Tooltip>.
  • Event.tsx (weekly calendar): Added data-booking-uid attribute and data-booking-calendar-event attribute to calendar event buttons so the sheet stays open when clicking between events.
  • common.json: Added 4 i18n keys: close_shortcut, previous_shortcut, next_shortcut, join_shortcut.

Calendar view fix

The sheet's onInteractOutside handler previously only checked for [data-booking-list-item], which meant clicking between calendar events would close and reopen the sheet. Fixed by:

  1. Adding data-booking-uid and data-booking-calendar-event to calendar event buttons in Event.tsx
  2. Checking both [data-booking-list-item] and [data-booking-calendar-event] in the onInteractOutside handler — this covers both list and calendar views

Arrow key / actions dropdown fix

Pressing Arrow Up/Down was opening the BookingActionsDropdown alongside (or instead of) navigating bookings. Root cause: Radix's DropdownMenuTrigger natively opens on ArrowDown keypress. The keyboard handler needed to always consume arrow key events when the sheet is active — both preventDefault() and stopPropagation() — regardless of whether navigation is possible. Previously, these calls were inside the if (canGoNext/canGoPrev) guard, so at the first or last booking the event would leak through to Radix.

Fixed by moving e.preventDefault() and e.stopPropagation() outside the navigation guard for ArrowUp/ArrowDown. Navigation still only triggers when possible, but the event is always consumed.

Overlay suppression approach

Uses Radix portal detection ([data-radix-portal]) to determine whether the focused element is inside an overlay. The checkSheetActive() logic:
if no element has focus, or focus is inside the sheet content, or focus is not inside any Radix portal → shortcuts work. Otherwise (focus is in a Radix portal that isn't the sheet) → shortcuts suppressed.

Capture phase and stopPropagation

The keyboard listener is registered in the capture phase (addEventListener("keydown", handler, true)). This ensures shortcuts intercept events before they reach and activate any focused button. ArrowUp/ArrowDown always call stopPropagation() when the sheet is active (even at list boundaries). Enter calls stopPropagation() only when a join link is present.

Human Review Checklist

Items worth verifying manually:

  • Arrow keys at list boundaries: Navigate to the first booking → press Arrow Up → should do nothing but NOT open the actions dropdown. Same for last booking + Arrow Down.
  • Calendar view: keyboard navigation: In calendar view, click an event to open the sheet → press Arrow Down/Up → should navigate between bookings without opening the actions dropdown.
  • Booking list view: keyboard navigation: Same test in list view.
  • Test with BookingActionsDropdown open: pressing Enter should NOT join the meeting, and Arrow Up/Down should NOT navigate bookings.
  • Test with any dialog open (e.g. reschedule, cancel, reassign): shortcuts should be suppressed.
  • After closing an overlay, shortcuts should work again immediately.
  • Enter key side-effect fix: Open the sheet, press Enter → should open the conference link in a new tab but should NOT open the BookingActionsDropdown.
  • Verify arrow keys can't scroll the page when the sheet is open (this is intentional — arrows are consumed by the sheet).

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - no docs changes needed.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. Navigate to the /bookings page
  2. Click on a booking to open the slideover panel
  3. Verify tooltips appear when hovering over navigation buttons
  4. Test keyboard shortcuts:
    • Press Up Arrow to navigate to previous booking
    • Press Down Arrow to navigate to next booking
    • Press ESC to close the slideover
    • Press Enter to open the meeting link (when Join button is visible)
  5. Test at list boundaries:
    • Navigate to the first booking → press Arrow Up → nothing should happen, dropdown should NOT open
    • Navigate to the last booking → press Arrow Down → nothing should happen, dropdown should NOT open
  6. Test overlay suppression:
    • Open the actions dropdown (three-dot menu) → press Arrow Up/Down → should NOT navigate bookings
    • Open any dialog (reschedule, cancel, etc.) → press Enter → should NOT join meeting
    • Close the overlay → shortcuts should work again
  7. Test calendar view:
    • Switch to calendar view
    • Click a calendar event to open the sheet
    • Press Arrow Up/Down → should navigate bookings WITHOUT opening the actions dropdown
    • Test at first/last booking in calendar view as well

Checklist

  • My code follows the style guidelines of this project
  • I have checked if my changes generate no new warnings

Link to Devin run: https://app.devin.ai/sessions/c89d946fc36b4c4b9921a095317deee0
Requested by: @eunjae-lee

Previous Devin session: https://app.devin.ai/sessions/9870c25d9ff84a7d9ede1cde6f03f246
Originally requested by: @PeerRich

@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@PeerRich PeerRich requested a review from eunjae-lee January 30, 2026 13:39
@keithwillcode keithwillcode added the core area: core, team members only label Feb 3, 2026
@github-actions
Copy link
Contributor

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

@github-actions github-actions bot added the Stale label Feb 11, 2026
@PeerRich PeerRich marked this pull request as ready for review February 11, 2026 21:06
@github-actions
Copy link
Contributor

Devin AI is completing this stale PR

This PR by @PeerRich has been marked as stale. A Devin session has been created to complete the remaining work.

View Devin Session


Devin will review the PR, address any feedback, and push updates to complete this PR.

- Removed duplicate import of useBookingLocation from non-existent
  @calcom/web/modules/bookings/hooks/useBookingLocation path
- Fixed import ordering to satisfy biome organizeImports rules
- Removed unnecessary code comment
- Original feature by @PeerRich via Devin AI

Co-Authored-By: unknown <>
@devin-ai-integration
Copy link
Contributor

Completed stale PR

I've reviewed and fixed the following issues in this PR:

Bug fix:

  • Removed duplicate useBookingLocation import — the PR had added an import from @calcom/web/modules/bookings/hooks/useBookingLocation (a path that doesn't exist), while the correct import from @calcom/features/bookings/hooks was already present. This would have caused a build error.

Code quality:

  • Fixed import ordering to satisfy biome's organizeImports rules
  • Removed an unnecessary code comment

Verification:

  • Lint passes with 0 errors (only pre-existing warnings)
  • Type-check failures are pre-existing and unrelated to this PR (packages/embeds/embed-react/inline.tsx)

The original feature (keyboard shortcuts + tooltips for booking slideover buttons) by @PeerRich is complete and functional as-is. The changes I made are purely correctness fixes.


Devin session

@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

E2E results are ready!

@github-actions github-actions bot removed the Stale label Feb 12, 2026
Copy link
Member

@dhairyashiil dhairyashiil left a comment

Choose a reason for hiding this comment

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

Screen.Recording.2026-02-12.at.12.04.20.PM.mov

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/web/modules/bookings/components/BookingDetailsSheet.tsx">

<violation number="1" location="apps/web/modules/bookings/components/BookingDetailsSheet.tsx:726">
P2: The global Enter shortcut hijacks keyboard interaction for non-input controls. Pressing Enter on another button/link/select inside the sheet will be prevented and will always open the join link. Consider ignoring Enter when the active element is any interactive control (button, select, link, etc.) or when the event is already handled.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-actions
Copy link
Contributor

Devin AI is addressing Cubic AI's review feedback

A Devin session has been created to address the issues identified by Cubic AI.

View Devin Session

@devin-ai-integration
Copy link
Contributor

Reviewed the Cubic AI feedback on this PR. The single issue identified (global Enter shortcut hijacking keyboard interaction for non-input controls) has a confidence score of 7/10, which is below the 9/10 threshold for automated fixes. Skipping this change.

For context: the existing code already guards the Enter shortcut behind joinButtonRef.current existence and excludes input/textarea/contenteditable elements. The concern about other interactive controls (buttons, links, selects) is valid but lower confidence — a human reviewer should evaluate whether the current guard is sufficient for the actual UI controls present in the sheet.

@dhairyashiil
Copy link
Member

fixed join button tooltip hover and enter click works now

Screen.Recording.2026-02-12.at.12.30.52.PM.mov

devin-ai-integration bot and others added 5 commits February 13, 2026 09:42
…g during booking navigation

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
…first/last booking

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@PeerRich PeerRich marked this pull request as ready for review February 13, 2026 10:54
@github-actions
Copy link
Contributor

Devin AI is resolving merge conflicts

This PR has merge conflicts with the main branch. The existing Devin session has been notified to resolve them.

View Devin Session

Devin will:

  1. Merge the latest main into this branch
  2. Resolve any conflicts intelligently
  3. Run lint/type checks to ensure validity
  4. Push the resolved changes

If you prefer to resolve conflicts manually, you can close the Devin session and handle it yourself.

devin-ai-integration bot and others added 2 commits February 13, 2026 10:56
Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/web/playwright/booking-sheet-keyboard.e2e.ts">

<violation number="1" location="apps/web/playwright/booking-sheet-keyboard.e2e.ts:121">
P2: Rule violated: **E2E Tests Best Practices**

Rule 1 (E2E Tests Best Practices) requires avoiding text locators and using data-testid selectors. These new tests rely on `text=Booking ...` locators, which are brittle and violate the guideline.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-actions
Copy link
Contributor

Devin AI is addressing Cubic AI's review feedback

New feedback has been sent to the existing Devin session.

View Devin Session

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@devin-ai-integration
Copy link
Contributor

Addressed the Cubic AI feedback: replaced all text= locators with data-testid selectors in the E2E tests.

Changes:

  • Added data-testid="booking-sheet-title" to the booking title <span> in BookingDetailsSheet.tsx
  • Updated all 6 E2E tests to use sheet.getByTestId("booking-sheet-title") with .toHaveText() assertions instead of brittle text=Booking N locators

All 6 E2E tests pass locally.


✅ Pushed commit a85ef547df

@github-actions
Copy link
Contributor

Devin AI is resolving merge conflicts

This PR has merge conflicts with the main branch. A Devin session has been created to automatically resolve them.

View Devin Session

Devin will:

  1. Merge the latest main into this branch
  2. Resolve any conflicts intelligently
  3. Run lint/type checks to ensure validity
  4. Push the resolved changes

If you prefer to resolve conflicts manually, you can close the Devin session and handle it yourself.

dhairyashiil and others added 2 commits February 19, 2026 21:39
… with useCallback

In BookingDetailsSheet.tsx, all three handler functions were plain arrow functions recreated on every render, causing the useEffect to tear down and re-attach the document keydown listener unnecessarily. Wrapped all three in useCallback with proper dependency arrays (the Zustand store functions they call).
Fix 2 — data-booking-list-item verified (no change needed)
Confirmed that data-booking-list-item is rendered on BookingListItem.tsx and data-booking-calendar-event is rendered on Event.tsx. The onInteractOutside handler in the final merged state correctly checks both selectors. No code change required.
Fix 3 — Removed dead code from JoinMeetingButton
Reverted JoinMeetingButton back to a plain function component:
Removed forwardRef wrapping (no caller passes a ref)
Removed showTooltip prop (unused — tooltip is handled by the parent BookingDetailsSheet)
Removed ref prop from the inner Button
Removed unused Tod forwardRef imports
@dhairyashiil dhairyashiil enabled auto-merge (squash) February 19, 2026 17:02
@dhairyashiil dhairyashiil merged commit 60b6086 into main Feb 19, 2026
46 checks passed
@dhairyashiil dhairyashiil deleted the devin/1769773070-booking-slideover-keyboard-shortcuts branch February 19, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants