Skip to content

refactor(web): move the IconButton call sites to Opal Button - #14163

Open
raunakab wants to merge 7 commits into
mainfrom
refactor/icon-button-to-opal
Open

refactor(web): move the IconButton call sites to Opal Button#14163
raunakab wants to merge 7 commits into
mainfrom
refactor/icon-button-to-opal

Conversation

@raunakab

@raunakab raunakab commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Description

Moves the IconButton call sites onto Opal's Button. 16 of 21 files are done; the last five are listed below and are deliberately left alone.

Opal Button refuses className by design (InteractiveStatelessProps extends WithoutStyles<...>), so each site had to say what its class was actually for:

  • Prop translation. The boolean flags become Opal's string enums: tertiary/internalprominence, smallsize, transientinteraction, toolTipPositiontooltipSide. action, danger, onHover and href turned out to be unused across all 29 call sites.
  • Hover reveal → Hoverable. Nine of the classes were hidden group-hover/X:flex or the CSS equivalent. Those are now Hoverable.Root + Hoverable.Item, which also reveals on focus-visible — so these actions are keyboard-reachable for the first time.
  • Positioning classes → the parent. shrink-0 moved to a wrapper; ml-2 became the row's own gap-2 pl-2, which also follows the padding-over-margin rule.
  • iconClassName → a wrapped icon component, the idiom FilePickerPopover already used for its spinner.

Two behaviours are worth a look rather than a read:

  1. Hoverable's appear-on-hover is opacity-based, so a hidden item still occupies layout space. ActionLineItem used invisible and matches exactly. AgentCard used hidden, so its card actions now reserve their space instead of reflowing the row on hover. I think that is an improvement, but it is a visual change.
  2. ActionCardHeader's h-6 w-6 maps to size="sm" exactly (--height-line-label is 1.5rem), but Opal's internal padding for that size differs slightly from the old p-2.

Not migrated

Five sites override Opal's own appearance, which it has no prop for. Each needs a design decision, so they still use IconButton:

Site Override
LLMProviderCard hover:bg-transparent — no prominence suppresses the hover background
BaseInputBar (stop) border-[1.5px] border-border-02 — Opal auto-borders only prominence="secondary", at its own width
InputImage w-5! h-5! p-0.5! rounded-04!
ChatPanel rounded-full p-2.5! bg-*! — a round custom-coloured button
AttachmentButton nested inside <button className="attachment-item">; already invalid HTML, and Button preserves it

IconButton.tsx and its stories can be deleted once those five are resolved.

Screenshots + Videos

Button changes were all mechanical. Should be no UI/UX changes here.

Additional Options

  • [Optional] Please cherry-pick this PR to the latest release version.
  • [Optional] Override Linear Check

Summary by cubic

Replaces most IconButton usages with @opal/components Button and migrates hover-only reveals to @opal/core Hoverable for keyboard access. Old behavior used Tailwind hidden/group-hover; new behavior uses Hoverable on hover and focus-visible. Also fixes invisible hover items intercepting clicks by toggling pointer-events in Hoverable CSS.

  • Prop translation: booleans → enums (tertiary/internalprominence, smallsize, transientinteraction, toolTipPositiontooltipSide). Removed unused action, danger, onHover, href; wrapped icons instead of iconClassName; moved positioning classes to parents.
  • Hover behavior: replaced hidden/invisible group-hover with Hoverable. File picker now uses replace-on-hover (extension label ↔ open-file button). Sidebar row menus pin open while their popover is open; they do not render while renaming. ActionLineItem renders the toggle inline or as a hover item; when showing source counts it omits the toggle.
  • Pointer-events fix: Hoverable now sets pointer-events: none when items are hidden (local mode excluded) to prevent invisible triggers from catching clicks (notably in sidebar rows).
  • Visual diffs: AgentCard actions reserve space instead of reflowing on hover; small buttons use Opal padding. ActionCardHeader maps h-6 w-6 to size="sm". The send button wraps its spinner icon to size and spin correctly.

Not migrated

  • LLMProviderCard: suppress hover background.
  • BaseInputBar (stop): 1.5px border not expressible in Opal.
  • InputImage: custom size and padding.
  • ChatPanel: round, custom-colored button.
  • AttachmentButton: nested in another <button>; invalid HTML retained.

Written for commit 0cf0f15. Summary will update on new commits.

Review in cubic

The first eight call sites are a straight prop translation: the boolean
flags become the string enums Opal takes.

  tertiary/internal -> prominence   small -> size
  transient         -> interaction  toolTipPosition -> tooltipSide

Two sites also passed a className, which Opal Button refuses by design.
Both were positioning the button rather than restyling it, so the class
moves to the parent: `shrink-0` to a wrapper, and `ml-2` becomes the row's
own gap and padding.

The sites that override Opal's own sizing, border or hover still use
IconButton and are left for later.
The four card actions hid themselves with `hidden group-hover/AgentCard:flex`,
which Opal Button cannot carry. Hoverable is the sanctioned way to say the
same thing, so the card gets a Hoverable.Root and each action becomes a
Hoverable.Item.

The pin action is the one that is not uniform: a pinned agent shows its pin
at rest and only an unpinned one waits for hover, so only that case is
wrapped. The button itself is declared once and rendered either way.

Hoverable also reveals on focus-visible, so these actions are now reachable
by keyboard, which the hover-only classes never allowed.
Two more rows whose actions were hidden with group-hover classes that Opal
Button cannot carry.

The file row was already a replace-on-hover in all but name: the extension
label at rest, the open-file action on hover. It now says so, and the two
halves no longer have to agree about which class hides which.

The action row keeps its Tailwind group, because the source-count spans
still use it. Its toggle has three states, not two, so the source-count case
now simply does not render the button rather than layering `hidden!` over
the hover classes.
The chat row and the craft session row carried the same class, which encoded
three states at once: pinned open while its popover is open, revealed on
hover otherwise, and never shown while the row is being renamed.

Hoverable.Root takes an `interaction`, so the open-popover case is now the
Root's job. Hover is the Item's default. The renaming case is the one
Hoverable has no word for, so the menu simply is not rendered then.

NameStep was already inside a Hoverable.Item and only needed the button
swapped.
The call site moved to Opal Button in 41f2bea, but this import used a
relative path and so was missed when the others were cleaned up. Nothing
referenced it.
The send button only needed `iconClassName` for its loading spinner, and
wrapping the icon component says the same thing — the pattern the file
picker already uses. Nothing else about it was custom.

The rename button sized itself with `h-6 w-6`, which is exactly what
size="sm" gives (--height-line-label is 1.5rem). Its resting opacity moves
to a wrapper, since that dims the button rather than restyling it.

BaseInputBar keeps IconButton for its stop button, whose 1.5px border has no
Opal equivalent.
@raunakab
raunakab requested a review from a team as a code owner August 22, 2026 00:46
@raunakab
raunakab requested review from jmelahman and nmgarza5 August 22, 2026 00:46
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Full-stack Preview (frontend + backend)

Status Preview Commit Updated
https://0cf0f15-onyx.preview.onyxcorp.dev/ 0cf0f15 2026-08-22 04:29:15 UTC

Sign in with GitHub as an onyx-dot-app member to view it.

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 17 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread web/src/sections/input/BaseInputBar.tsx Outdated
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Status Preview Commit Updated
https://onyx-preview-betds6x14-danswer.vercel.app 0cf0f15 2026-08-22 04:31:27 UTC

@greptile-apps

greptile-apps Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR migrates IconButton call sites to Opal Button and replaces custom hover classes with Hoverable primitives.

  • Translates legacy appearance, size, interaction, and tooltip props.
  • Moves layout-specific classes to wrappers or parent layouts.
  • Adds focus- and touch-accessible hover-reveal behavior.
  • Leaves five appearance-dependent IconButton sites unchanged.

Confidence Score: 4/5

The sidebar hitbox regression should be fixed before merging because hidden menu controls intercept normal row navigation.

Opacity-based hiding leaves both migrated sidebar menu triggers clickable above their navigation overlays, so right-edge row clicks open menus instead of selecting sessions.

Files Needing Attention: web/src/sections/sidebar/ChatButton.tsx; web/src/app/craft/components/SideBar.tsx

Important Files Changed

Filename Overview
web/src/sections/sidebar/ChatButton.tsx Migrates the row menu to Hoverable, but the invisible trigger intercepts clicks intended for chat navigation.
web/src/app/craft/components/SideBar.tsx Applies the same opacity-hidden menu pattern to Craft session rows, including the invisible pointer hitbox.
web/src/sections/agents/AgentCard.tsx Migrates card actions to Opal Button and intentionally reserves their layout space while hidden.
web/src/refresh-components/popovers/ActionsPopover/ActionLineItem.tsx Replaces the disable IconButton with shared Button and Hoverable behavior while retaining click propagation guards.
web/src/refresh-components/popovers/FilePickerPopover.tsx Uses replace-on-hover to swap file-extension text for the file-view action.
web/src/sections/input/BaseInputBar.tsx Migrates the send action and preserves loader animation through a wrapped icon component.
web/src/views/admin/GroupsPage/EditGroupPage.tsx Migrates member-management actions while preserving disabled states, labels, and propagation guards.
Prompt To Fix All With AI
### Issue 1
web/src/sections/sidebar/ChatButton.tsx:412-419
**Hidden menu intercepts navigation**

When a user clicks the right edge of a row while its menu icon is hidden, the opacity-zero trigger remains hit-testable above the row overlay. It opens the actions popover and stops propagation instead of navigating to the selected chat; the same pattern affects Craft session rows.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "refactor(web): move two more IconButtons..." | Re-trigger Greptile

Comment thread web/src/sections/sidebar/ChatButton.tsx
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

🖼️ Visual Regression Report

Project Changed Added Removed Unchanged Report
admin 2 0 0 185 View Report
exclusive 0 0 0 10 ✅ No changes

An `appear-on-hover` item only set `opacity: 0`, so at rest it was invisible
but still hit-testable. In the sidebar that put a transparent popover trigger
above the row's navigation overlay, which is `z-99` to the actions slot's
`z-100`: clicking the right edge of a row opened the menu instead of opening
the chat.

This is why the classes it replaced worked — both `display: none` and
`visibility: hidden` take the element out of hit-testing, and opacity does
not. Every opacity rule now moves `pointer-events` with it.

Local mode is deliberately excluded. It reveals on its own hover, so an item
that could not be hovered could never be revealed.

Also forwards `style` to the send button's spinner. Opal's iconWrapper sizes
icons through `style`, not classes, so dropping it left the loader at its own
default size. LineItem sizes with classes instead, which is why the file
picker's loader does not need this.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant