Editor: Polish real-time collaboration presence UI and move Avatar to editor package#75652
Editor: Polish real-time collaboration presence UI and move Avatar to editor package#75652dabowman wants to merge 40 commits intoWordPress:trunkfrom
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
ciampo
left a comment
There was a problem hiding this comment.
Thank you for working on this! I left some initial comments.
In general, I'd prefer smaller PRs that focused on single tasks — for example, one PR that moves the code to the editor package, and one or more follow-up PRs that apply the rest of the changes (as grouped under the "How" section in the PR description)
packages/editor/src/components/collaborators-presence/avatar/component.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-presence/avatar/component.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/overlay.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/overlay.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/overlay.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/overlay.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/overlay.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/overlay.tsx
Outdated
Show resolved
Hide resolved
|
Thanks for the ping. In general I ihave some trust that this can move forward on a dev heavy side while it's still relatively exotic, but if you need any visual reviews, are you able to provide screenshots? It can be a bit tricky to review this particular feature. |
|
@jasmussen here's a screenshot of the current state of the UI with a bunch of collaborators in a session. You can also see the same colors being used between notes and the collab components.
|
|
@ciampo I tried to make that big css string a bit more friendly. I split it into two dedicated files. One for the avatar component and one for the rest of the overlay styling. I also added a file of variables that maps to the scss variables we'd like to reference but can't. That way the css is at least pulling what it can from a shared file that we can map to the system and try to keep in sync. The core issue is that we can't really add a lot of styles to the iframe without putting stuff in the block-editor package or adding some plumbing to allow us to enqueue styles from here to get compiled with the rest of the block-editor styles. It's a bit janky but hopefully this makes it a bit more sustainable until folks have time to build out a more permanent solution. |
|
Looks good! |
There was a problem hiding this comment.
Pull request overview
This PR refines the real-time collaboration (RTC) presence UI in the editor (presence button + list popover + block highlight labels) and relocates the RTC-specific Avatar/AvatarGroup components from @wordpress/components private APIs into @wordpress/editor, including updating iframe-injected overlay styles.
Changes:
- Move
AvatarandAvatarGroupintopackages/editorand update consumers to import them directly (removingunlock( componentsPrivateApis )usage). - Polish presence button + collaborators list popover styling and update avatar rendering API (
variant="badge",dimmed). - Extend the overlay to render avatar badge labels for whole-block selections, with updated iframe CSS injection.
Reviewed changes
Copilot reviewed 20 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/editor/src/style.scss | Includes new Avatar/AvatarGroup SCSS in editor bundle. |
| packages/editor/src/components/collaborators-presence/styles/collaborators-presence.scss | Updates presence button container/pressed styling behavior. |
| packages/editor/src/components/collaborators-presence/styles/collaborators-list.scss | Restyles collaborators list popover (layout/typography/hover/focus). |
| packages/editor/src/components/collaborators-presence/list.tsx | Switches to editor-owned Avatar and uses dimmed state. |
| packages/editor/src/components/collaborators-presence/index.tsx | Switches to editor-owned Avatar/AvatarGroup and adjusts max avatars. |
| packages/editor/src/components/collaborators-presence/avatar/types.ts | Updates Avatar public props (variant, dimmed) and IconType import. |
| packages/editor/src/components/collaborators-presence/avatar/styles.scss | Adds editor-scoped Avatar styles + badge + dimmed styling. |
| packages/editor/src/components/collaborators-presence/avatar/index.ts | Re-exports Avatar component and types. |
| packages/editor/src/components/collaborators-presence/avatar/component.tsx | New Avatar implementation (contrast-aware badge text via colord). |
| packages/editor/src/components/collaborators-presence/avatar-group/types.ts | Defines AvatarGroup props in editor package. |
| packages/editor/src/components/collaborators-presence/avatar-group/styles.scss | Adds editor-scoped AvatarGroup styles (overlap + overflow). |
| packages/editor/src/components/collaborators-presence/avatar-group/index.ts | Re-exports AvatarGroup component and types. |
| packages/editor/src/components/collaborators-presence/avatar-group/component.tsx | New AvatarGroup implementation for editor package. |
| packages/editor/src/components/collaborators-overlay/use-block-highlighting.ts | Refactors highlighting hook to return highlight label render data. |
| packages/editor/src/components/collaborators-overlay/overlay.tsx | Injects new iframe CSS strings and renders block highlight avatar labels. |
| packages/editor/src/components/collaborators-overlay/overlay-iframe-styles.ts | New overlay positioning CSS for iframe injection. |
| packages/editor/src/components/collaborators-overlay/collaborator-styles.ts | Centralizes compiled design-token constants used in injected CSS. |
| packages/editor/src/components/collaborators-overlay/avatar-iframe-styles.ts | New injected CSS for editor-owned Avatar inside iframe. |
| packages/editor/src/components/collab-sidebar/utils.js | Updates deterministic avatar border color palette. |
| packages/editor/package.json | Adds colord dependency for contrast-aware color logic. |
| packages/components/src/style.scss | Removes Avatar/AvatarGroup SCSS from components bundle. |
| packages/components/src/private-apis.ts | Removes Avatar/AvatarGroup from components private API surface. |
| packages/components/src/avatar/component.tsx | Removes components-owned Avatar implementation. |
| package-lock.json | Locks new colord dependency. |
Comments suppressed due to low confidence (1)
packages/editor/src/components/collaborators-presence/avatar-group/component.tsx:38
- The overflow indicator’s
aria-labelstring ("${overflowCount} more") is not localized. Since this is user-facing for screen readers, it should use@wordpress/i18n(and ideally include context like “more collaborators”).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/editor/src/components/collaborators-presence/avatar/component.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/overlay.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/use-block-highlighting.ts
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/use-block-highlighting.ts
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/use-block-highlighting.ts
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-presence/styles/collaborators-list.scss
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-presence/avatar/component.tsx
Show resolved
Hide resolved
packages/editor/src/components/collaborators-presence/avatar/component.tsx
Outdated
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/avatar-iframe-styles.ts
Show resolved
Hide resolved
packages/editor/src/components/collaborators-overlay/use-block-highlighting.ts
Show resolved
Hide resolved
936299d to
3f6d791
Compare
| // Combine both delayed rerenders so layout changes recompute everything. | ||
| const rerenderAfterDelay = useCallback( () => { | ||
| const cleanupCursors = rerenderCursorsAfterDelay(); | ||
| const cleanupHighlights = rerenderHighlightsAfterDelay(); | ||
| return () => { | ||
| cleanupCursors(); | ||
| cleanupHighlights(); | ||
| }; | ||
| }, [ rerenderCursorsAfterDelay, rerenderHighlightsAfterDelay ] ); | ||
|
|
||
| // Detect layout changes on overlay (e.g. turning on "Show Template") and window | ||
| // resizes, and re-render the cursors. | ||
| const resizeObserverRef = useResizeObserver( rerenderCursorsAfterDelay ); | ||
| useEffect( rerenderCursorsAfterDelay, [ rerenderCursorsAfterDelay ] ); | ||
| // resizes, and re-render the cursors and block highlights. | ||
| const resizeObserverRef = useResizeObserver( rerenderAfterDelay ); | ||
| useEffect( rerenderAfterDelay, [ rerenderAfterDelay ] ); |
There was a problem hiding this comment.
Is it necessary to run the rerenderAfterDelay when it changes? Having a useEffect where the function is also the dependency seems strange.
There was a problem hiding this comment.
that makes sense, I refactored.
packages/editor/src/components/collaborators-overlay/use-block-highlighting.ts
Show resolved
Hide resolved
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The free-form `status` string generated `is-{status}` CSS modifier
classes intended for ad-hoc external styling — a pattern incompatible
with CSS modules. Replace with a `dimmed` boolean that bakes the
visual dimming behavior directly into the component.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace inline `opacity: 0.5` on the list item button with the Avatar component's `dimmed` prop, which provides proper desaturation and luminosity blending instead of a blunt container opacity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `avatar-colors.ts` with an independent color palette for the overlay and presence components, decoupled from collab-sidebar/utils. Update all four consumers to import from the new module. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…trast Use colord's WCAG AA readability check to set the badge name color to black or white based on the borderColor luminance. Also update the overlay's compiled Avatar styles to read the new custom property. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Describe when each size should be used rather than raw pixel values, following the same pattern as Button's size prop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aligns with the pattern used by Button, Popover, and other
components in the library. The CSS class changes from `has-badge`
to `is-badge` to match the `is-{variant}` convention.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Renders a small Avatar badge at the top-left of each highlighted block, positioned $grid-unit-10 above the outline, so users can see who selected a block at a glance and hover to reveal the collaborator's name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused avatar background color exports and arrays - Fix JSDoc on getAvatarBorderColor to match actual return - Refactor useBlockHighlighting: useMemo → useCallback, type userStates as PostEditorAwarenessState[] to eliminate any casts, use Set for O(1) lookups in unhighlight loop - Update inline style comment to reference correct SCSS source path and note intentionally omitted dimmed/status-indicator styles - Remove whitespace in collaborator count span - Remove stories and tests (moved to add/avatar-component branch) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…erlay Replaces the separate avatar-colors.ts palette with the existing getAvatarBorderColor in collab-sidebar/utils.js, updated to use the WordPress.org Design Library colors agreed on with the design team. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Renames class names and custom properties to follow the editor package convention now that Avatar lives in the editor package. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Avatar isn't restyled — it's positioned as a label for block highlights. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace hardcoded #000 with #1e1e1e ($gray-900) in badge text contrast check to match the design system's text color - Replace --wp-components-color-accent with --wp-admin-theme-color in overlay inline styles (components package variable not available in iframe) - Add token comments (e.g. /* $font-size-medium */) to hardcoded values in the overlay inline styles for maintainability - Update top-level comment to reference editor package instead of wp-components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract the monolithic CSS string from overlay.tsx into three focused files with clear responsibilities: - collaborator-styles.ts: compiled design tokens from @wordpress/base-styles, used as the single source of truth for values that can't be imported as Sass inside the editor canvas iframe. - avatar-iframe-styles.ts: Avatar component CSS (mirrors avatar/styles.scss) using token constants instead of hardcoded values with comments. - overlay-iframe-styles.ts: overlay layout, cursors, block highlights, and animations. Adds z-index layering so cursor lines always render below avatar labels across users. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Safari < 17 does not support `overflow: clip`. Add `overflow: hidden` before `overflow: clip` as a fallback, matching the established pattern used by the Cover block. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap the colord().isReadable() call in useMemo so it only recomputes when borderColor changes instead of on every render. Also guard role="img" and aria-label to only be set when name is provided, avoiding an unlabeled image role. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move all DOM mutations into useEffect, replacing the previous useCallback + useMemo approach. Add a recomputeToken state variable that rerenderHighlightsAfterDelay bumps via setTimeout, keeping the delayed rerender pure and stable across renders. Add cleanup effect that removes is-collaborator-selected classes and --collaborator-outline-color properties from block elements on unmount. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded #3858e9 with rgba(var(--wp-admin-theme-color--rgb)) for hover and active states, matching the established pattern in edit-site and dataviews. Make active slightly darker (0.08) than hover (0.04). Fix focus-visible fallback to use #3858e9. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add inline comments identifying each hex color in the AVATAR_BORDER_COLORS array for easier reference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use sprintf and _n from @wordpress/i18n for the overflow count aria-label so it is translatable and properly pluralized. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace useMemo wrapping a function factory with useCallback, which is the idiomatic React pattern for memoizing callback functions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When multiple collaborators select the same block, only the first one in the array gets the outline and avatar label. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace useMemo(() => () => {}) with useEffect + recomputeToken state,
matching the pattern already used in useBlockHighlighting. This makes
rerenderCursorsAfterDelay a stable useCallback with empty dependencies,
reducing unnecessary effect re-runs from useResizeObserver.
Also fix getComputedStyle to use the iframe's defaultView instead of the
parent window, which is correct for cross-frame style resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copies `highlightedBlockIds.current` into a local variable at the top of the effect so the cleanup closure always references the same Set instance, fixing the react-hooks/exhaustive-deps warning. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap resolveSelection() calls in try/catch blocks in both useBlockHighlighting and useRenderCursors hooks. The underlying createAbsolutePositionFromRelativePosition can throw when Yjs document positions become stale after edits. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Safari does not resolve url() values inside CSS custom properties, causing avatar gravatar images to silently fail and show only the blue background-color fallback. Set backgroundImage as an inline style directly on the .editor-avatar__image span, bypassing the custom property. The --editor-avatar-url custom property is kept for the dimmed state's ::before pseudo-element. Skip the inline style when dimmed to avoid conflicting with the dimmed state's background-image: none rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Re-implement tests for Avatar (34 tests) and AvatarGroup (11 tests) that were previously moved to the add/avatar-component branch. Updated for the editor package: class prefix (editor-avatar), custom property names (--editor-avatar-*), i18n overflow labels, and new inline backgroundImage Safari fix coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n in Avatar Switches the Avatar component from CSS background-image to a real <img> element with a useImageLoadingStatus preloader hook. This eliminates the Safari bug where url() in CSS custom properties silently fails (including the unfixable dimmed state which used a ::before pseudo-element), and adds proper image load/error detection so broken URLs gracefully fall back to initials instead of showing empty colored circles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The side-channel `new Image()` preloader is blocked by Safari's Intelligent Tracking Prevention for third-party domains like Gravatar, causing avatars to silently fall back to initials. Replaces the preloader with native `<img onLoad/onError>` events — the `<img>` is always in the DOM (when src is truthy) at opacity 0, becoming visible via CSS when the load event fires. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use $gray-900 instead of $black for status indicator color, keep useImageLoadingStatus as a private import, and add a test for the src-change reset path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the intermediate `rerenderAfterDelay` useCallback wrapper and inline the logic directly into useResizeObserver and useEffect. This eliminates the unusual pattern of a function being both the effect callback and its own dependency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3dcf828 to
4e314cb
Compare


What?
Polishes the real-time collaboration (RTC) presence UI — the collaborator button, list popover, and block highlight labels — and moves the
AvatarandAvatarGroupcomponents from@wordpress/componentsprivate APIs into the@wordpress/editorpackage where they are consumed.Why?
Follow-up to #75595. That PR introduced the collaborator overlay with cursor and block highlight rendering. This PR refines the visual design of the presence UI to match the Figma spec and addresses component ownership:
@wordpress/componentsas a private API adds unnecessary coupling and prevents the editor package from owning its own styling (e.g.$components-color-accentis restricted to the components package by stylelint).How?
Avatar component changes (
badge→variant,status→dimmed)badgeboolean prop withvariant: 'badge'enum (extensible for future variants)statusstring prop withdimmedboolean (simpler API for the single use case)colord— automatically switches to dark text whenborderColoris lightbackground-colorfrom root to.is-badgeso non-badge avatars don't show a colored background behind the white ringMove to editor package
AvatarandAvatarGroupfrompackages/components/src/avatar{,-group}/topackages/editor/src/components/collaborators-presence/avatar{,-group}/@wordpress/componentsprivate APIs (private-apis.ts) andstyle.scssimport Avatar from './avatar') instead of usingunlock()colordto editorpackage.jsonvar(--wp-admin-theme-color, #3858e9)instead of$components-color-accent(which is restricted by stylelint outside the components package)Props & Omit<React.HTMLAttributes<HTMLDivElement>, keyof Props>instead ofWordPressComponentProps(not publicly exported)Presence button polish
$gray-100, hover/pressed$gray-200); innerButtonstays transparent in all statesAvatarGroup max={4}to match designmargin-inline-endfor right paddingCollaborators list popover
$grid-unit-15 $grid-unit-20paddingcloseSmallicon at 24px$font-size-medium/$font-weight-mediumrgba(#3858e9, 0.04)Block highlight avatar labels
variant="badge",size="small") at its top-left corner8pxabove the outline viatransform: translateY(calc(-100% - 8px))useBlockHighlightingrefactored to returnBlockHighlightData[](same pattern asuseRenderCursors)Overlay inline styles
COLLABORATORS_OVERLAY_STYLESto stay in sync with the SCSS sourceTesting Instructions
+Noverflow count when > 4 users. Check hover/pressed background states match surrounding toolbar items.Keyboard testing
Screenshots or screencast