refactor(web): replace raw button and input elements with Opal

Merged
RO
2d ago
+7-15

Description

This is the second of four cleanup PRs on top of #16234, which is merged (stack: #16240 icons, #16241 raw elements, #16242 refresh components, #16243 Text). This PR replaces raw <button>, <input>, <select> and <textarea> elements with Opal components where Opal has a faithful replacement.

The result is small on purpose. Most of the value of this PR is the list of gaps it found. Of 58 raw-element sites in the baseline, only 2 have an Opal replacement that keeps the look and the behavior. The other 56 are kept, and each one is listed below with the reason. Most of them show a missing Opal component, not a careless raw element.

Changes

  1. web/src/app/admin/bots/SlackBotUpdateForm.tsx: the native checkbox becomes Opal InputCheckbox. It is imported as OpalInputCheckbox, because the file already has a local helper named InputCheckbox.
    • onChange passes straight through, because Opal forwards it to its hidden input, so e.target.checked still works.
    • Opal's checkbox takes no className, so the old me-2 my-auto becomes items-center gap-2 on the parent label.
    • Visual change: the box is 16px, not the old 14px native box.
  2. web/src/app/ee/admin/performance/custom-analytics/CustomAnalyticsUpdateForm.tsx: the hand-styled password input becomes InputTypeIn type="password", with <Spacer rem={0.25} /> in place of mt-1. This matches the Opal InputTextArea that the same form already uses.
    • I did not use InputPasswordTypeIn, because it adds a show/hide toggle that the old field did not have.

Both files still have other violations, so they stay in the baseline.

Sites kept, grouped by the Opal gap they show

Gap in OpalSitesWhere
Button takes only a string label. No button primitive takes rich children (icons, avatars, images, layout)25craft: CompactionMarker, CraftToolCard, ApprovalCard (2), ThinkingCard, TodoListCard, CraftToolGroup, TaskBody, ConnectDataBanner, BuildWelcome, AgentSwitcher, OpencodeDebugLogs. Other: SlackChannelConfigFormFields (agent chip), SourceTag, SettingsPage and nrf SettingsPanel (background tiles), documents/sets/page (aria-disabled keeps the tooltip working), ProjectFolderButton, LLMStep, CodeBlock, FileCard (2), ChatDocumentDisplay, FileTile, ScheduleEditor
No hidden file input opened by its own trigger7UserFilesModal, InlineFileManagement, AppearanceThemeSettings, UserLibraryModal, AgentEditorPage, CraftInputBar, FilePickerPopover
No react-dropzone integration (getInputProps() owns the input)3SkillBundlePicker, SkillFilesPicker, ProjectContextPanel
InputTextArea always has a border and a 48px minimum height. There is no borderless or auto-sizing variant4PasteTilePopover, HumanMessage (resizes itself through a ref), the standard-answer search box
LinkButton/TextButton are text-03. There is no link-colored or action-colored variant. TextButton renders a <p>, so it cannot sit inside prose6RequestNewVerificationEmail, LoginPage, the 3 "view all" toggles in SlackChannelConfigFormFields, the inline citation in MemoizedTextComponents
No segmented control, and SelectButton cannot match custom pills4ScheduleEditor weekday toggles, InstructionsDisplayModeToggle, SuggestedPrompts (2)
No icon button without chrome (an icon-only Button adds a box and a hover background)3the X in SuggestedPrompts, the X in refresh Tag, the &times; in standard-answer
No inline rename input with no chrome1ButtonRenaming
No slider1SettingsPage range input
Hidden input passed through from props1table-layouts.tsx
refresh-components that are button primitives themselves3BigButton, SquareButton, refresh SelectButton. Their callers should migrate, not their internals

Suggested follow-ups

These are the Opal additions that would unblock most of the remaining sites, in order of impact:

  1. A button primitive that takes rich children (Interactive already does the work inside Opal; app code needs a public wrapper). This unblocks 25 sites.
  2. An upload trigger that wraps a hidden <input type="file"> and a dropzone-friendly input. This unblocks 10 sites.
  3. A borderless, auto-sizing InputTextArea variant. This unblocks 4 sites.
  4. Link-colored LinkButton/TextButton variants and an inline (span) mode. This unblocks 6 sites.
  5. A segmented control, an icon button without chrome, and a slider.

How Has This Been Tested?

  • oxlint: the 2 changed files have no react(forbid-elements) diagnostics. The full run has 0 errors.
  • bun run types:check: no type errors.
  • pre-commit (oxfmt, oxlint) passes on the changed files.

Additional Options

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

Summary by CodeRabbit

  • Style
    • The Slack bot settings checkbox and custom analytics password field now use the app’s standard form controls.
    • The checkbox is vertically aligned with its label, uses consistent spacing, and retains its accessible label.
  • Form Behavior
    • The checkbox continues to reflect and update its checked state, and custom analytics form submission behavior is unchanged.