fix(composer): make composer icon-button tooltips click-through#1272
Draft
tellaho wants to merge 3 commits into
Draft
fix(composer): make composer icon-button tooltips click-through#1272tellaho wants to merge 3 commits into
tellaho wants to merge 3 commits into
Conversation
Toolbar tooltips rendered with pointer-events:auto, so while visible they sat over the message textarea and swallowed clicks meant for the editor beneath. Fix the click-through on the composer surface only — where labels are known-short — rather than promising it app-wide for every tooltip. Add ComposerIconButton (desktop/src/features/messages/ui): a forwardRef component owning the Tooltip -> Trigger -> Button -> Content shape that bakes pointer-events-none onto the tooltip content (the floating popup) only. The trigger keeps pointer/focus so focus-to-show still works (WCAG content-on-hover-or-focus). Because the button owns its tooltip, the override can't be forgotten and future composer icon buttons inherit it. Swap the 5 main-toolbar icon buttons (formatting toggle/close, mention, attach, spoiler) to it. The formatting sub-toolbar (Bold/Italic/lists/Quote) maps over raw <button>s with custom active-state styling, not the shared Button, so bake pointer-events-none onto its single TooltipContent there instead of restructuring to ComposerIconButton — keeping the override in one obvious place for that row. Shared tooltip.tsx is untouched. Adds composer-scoped e2e cases asserting both surfaces' tooltips are visible but click-through, and registers the spec in the smoke project. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34h <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2987fc4 to
80c7633
Compare
Collaborator
Author
|
moving down to draft because it's not behaving as expected |
The click-through fix (pointer-events-none) stopped the popup from swallowing clicks but left Radix Tooltips hover-to-persist safe bridge intact, so the cursor could slide off the trigger onto the popup, keep it alive, and select its text. Set disableHoverableContent on the composer Tooltip Roots (ComposerIconButton + the FormattingToolbar map) so these label tooltips dismiss the instant the pointer leaves the trigger. Scoped to the composer Roots only — the shared TooltipProvider and tooltip.tsx keep their app-wide defaults, and the trigger keeps its hover/focus-to-show lifecycle (WCAG content-on-hover-or-focus). Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…ntent disableHoverableContent + pointer-events-none on the inner TooltipContent still let the tooltip camp: Radix wraps content in a positioned [data-radix-popper-content-wrapper] DIV it styles with pointer-events:auto and never exposes to props. That wrapper overlaps the trigger, so a real cursor sliding off the trigger lands on it, persists the tooltip, and can select text. The inner pointer-events-none was one level too shallow. Tag the composer TooltipContent with data-composer-tooltip and add a scoped globals.css rule that reaches the wrapper via :has(> [data-composer-tooltip]), setting pointer-events:none + user-select:none on the actual camp surface. Scoped to composer tooltips only — shared tooltip.tsx and the app-wide TooltipProvider untouched; trigger keeps hover/focus-to-show (WCAG content-on-hover-or-focus). Inner pointer-events-none/select-none kept as belt-and-suspenders. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Category: fix
User Impact: Tooltips on the message composer's toolbar buttons no longer block clicks into the controls or the text field underneath them.
Problem: Radix/shadcn
TooltipContentrenders as a hoverable Portal popup withpointer-events: auto. When a composer toolbar tooltip is visible, the popup sits on top of the message textarea and intercepts the mouse, so clicking into the field while the tooltip shows fails. Reported on the composer's formatting/action buttons.Solution: Make the fix localized to the composer rather than app-wide. A new
ComposerIconButtoncomponent owns the fullTooltip → Trigger → Button → Contentshape and bakespointer-events-noneonto the tooltip content (popup) only — never the trigger. Because the button owns its own tooltip, every current and future composer icon button inherits the click-through behavior and the override can't be forgotten. The sharedtooltip.tsxis left untouched. The formatting sub-toolbar takes a matching content-only knob (its buttons are raw<button>s with custom active-state styling, so wrapping them was too heavy).This satisfies the WCAG content-on-hover-or-focus constraints:
pointer-events-noneonly affects the floating popup, so trigger focus-to-show (screen-magnification accommodation) and the hover/show lifecycle are unchanged. The callerclassNameis merged last via tailwind-merge so the override stays adjustable. Composer labels are succinct and non-interactive.File changes
desktop/src/features/messages/ui/ComposerIconButton.tsx (new)
forwardRefcomponent owningTooltip → Trigger → Button → Content. Bakescn("pointer-events-none", tooltipClassName)onto the tooltip content only; trigger/button untouched. Defaultssize="icon",type="button". Doc comment covers the three WCAG content-on-hover-or-focus constraints.desktop/src/features/messages/ui/MessageComposerToolbar.tsx
Swapped the 6 composer icon buttons (formatting toggle ×2, close, mention, attach, spoiler) to
ComposerIconButton. Send stays a raw submitButton(no tooltip).desktop/src/features/messages/ui/FormattingToolbar.tsx
Sub-toolbar (Bold/Italic/Strikethrough/Code/Link/lists/Quote)
TooltipContenttakes the content-onlypointer-events-noneknob, with an on-site comment explaining the content-knob path for these raw active-state buttons.desktop/playwright.config.ts
Registered the composer-scoped e2e spec in the
smokeproject (it was previously dangling).desktop/e2e/screenshot-tooltip-pointer-events.spec.ts (new)
e2e cases for both surfaces: assert tooltip visible AND
pointerEvents === 'none', and confirm typed text lands in the editor beneath the visible tooltip.Reproduction Steps
Screenshots/Demos
Click-through confirmed during review — tooltip renders over the textarea and the field receives input through it:
Follow-up: dismiss composer tooltips when the cursor leaves the trigger (commit
c2a3b64f)Problem (gap 2): With click-through fixed, the composer tooltips still persisted when you slid the cursor off the trigger onto the popup — you could camp on a tooltip and even select its text. Expected behavior: mouse off the trigger and the tooltip dismisses immediately. Root cause is Radix Tooltip's hoverable-content "safe bridge", which is on by default and deliberately keeps the popup alive as the cursor moves from trigger toward content. This is a separate lever from the
pointer-events-noneclick-through fix above, which holds unchanged.Solution: Set
disableHoverableContenton the composerTooltipRoot in both composer surfaces —ComposerIconButton.tsxandFormattingToolbar.tsx. Root-level overrides the app-wideTooltipProviderdefault, so the change is genuinely scoped to the composer: the sharedtooltip.tsxand the app-wide provider are untouched, and there's no app-wide behavior change. The trigger keeps its normal hover/focus-to-show lifecycle, so WCAG content-on-hover-or-focus still holds. On-site comments explain the knob.Verification:
biome check .,tsc --noEmit, andpnpm buildclean; rebuilt bundle showsdisableHoverableContent:!0exactly twice (one per surface). A behavioral spec confirms both surfaces: hover trigger → tooltip shows → move cursor onto the popup → it dismisses instead of camping. The existing click-through tests still pass — no regression.