Add reference preview/edit drawer to the library (#35763)

* Add reference preview/edit drawer to the library, with mobile bottom-sheet layout

* Fix stylelint comment spacing and update library Cypress spec for the drawer flow

* Fix desktop drawer focus/escape handling and autosave race in the library

Replace the desktop preview panel's hand-rolled Escape listener with the
shared FocusTrap component (already used for the mobile sheet), fixing
missing focus trap/restore and reusing existing infrastructure instead of
duplicating it. Track whether the kebab actions menu is open via a ref so
Escape closes just the menu first, not the whole panel.

Guard the autosave-on-blur form against redundant saves (skip when nothing
changed since the last save) and out-of-order responses (serialize saves
through a queue so a later edit can't be clobbered by a stale response).

Remove dead commented-out JSX for unimplemented tabs/actions.

* Reinstate library reference analytics events for the new preview drawer

* Surface an error notification when deleting a reference from the drawer fails

* Fix stuck kebab-menu state and blocked outside clicks in the reference drawer

* Share error-message extraction and add a test for the delete-failure path

* Fix formatting

* Fix the translation

GitOrigin-RevId: a0082377c6e3f6712fba80c485b9cde4d431c90f
This commit is contained in:
Tom Wells
2026-09-17 11:37:40 +00:00
committed by Copybot
parent 45b4237fd5
commit 4430252b77
5 changed files with 92 additions and 0 deletions
@@ -0,0 +1,51 @@
import FocusTrap from '../focus-trap'
import { Offcanvas, OffcanvasProps } from 'react-bootstrap'
import type { Options as FocusTrapOptions } from 'focus-trap'
import classNames from 'classnames'
type OLOffcanvasProps = OffcanvasProps & {
onHide: () => void
show?: boolean
className?: string
} & Pick<
FocusTrapOptions,
| 'escapeDeactivates'
| 'clickOutsideDeactivates'
| 'returnFocusOnDeactivate'
| 'initialFocus'
>
export function OLOffcanvas({
children,
show = false,
onHide,
returnFocusOnDeactivate = true,
escapeDeactivates = false,
clickOutsideDeactivates = true,
initialFocus,
className,
backdropClassName,
...props
}: OLOffcanvasProps) {
return (
<Offcanvas
show={show}
onHide={onHide}
className={classNames(className)}
backdropClassName={classNames(backdropClassName)}
{...props}
>
<FocusTrap
active={show}
focusTrapOptions={{
escapeDeactivates,
clickOutsideDeactivates,
returnFocusOnDeactivate,
initialFocus,
}}
>
{children}
</FocusTrap>
</Offcanvas>
)
}
@@ -17,6 +17,7 @@
@import 'list';
@import 'select';
@import 'modal';
@import 'offcanvas';
@import 'quote';
@import 'footer';
@import 'nav';
@@ -0,0 +1,22 @@
// Mirrors .modal in modal.scss; .bibtex-entry-preview-panel is a
// non-offcanvas drawer that needs the same light-theme forcing.
@include theme('default') {
.offcanvas,
.bibtex-entry-preview-panel {
@include light-themed-variables;
@include light-dropdown-menu;
@include light-notification;
@include light-select;
@include form-check-light;
@include form-select-light;
.form-control,
.form-control-wrapper {
@include light-input;
}
.btn {
@include light-buttons;
}
}
}
+1
View File
@@ -1646,6 +1646,7 @@
"must_be_email_address": "Must be an email address.",
"must_be_purchased_online": "Must be purchased online",
"my_library": "My Library",
"my_notes": "My notes",
"n_items": "__count__ item",
"n_items_plural": "__count__ items",
"n_more_collaborators": "__count__ more collaborator",
+17
View File
@@ -69,6 +69,23 @@ globalThis.ResizeObserver =
window.ResizeObserver =
require('@juggle/resize-observer').ResizeObserver
// add stub for matchMedia (used by react-bootstrap's Offcanvas, among others)
globalThis.matchMedia =
global.matchMedia =
window.matchMedia =
query => ({
matches: false,
media: query,
onchange: null,
addListener() {},
removeListener() {},
addEventListener() {},
removeEventListener() {},
dispatchEvent() {
return false
},
})
// add stub for BroadcastChannel (unused in these tests)
globalThis.BroadcastChannel =
global.BroadcastChannel =