feat(opal): stored-secret password tag, tab keyboard nav, tertiary actions

Merged
RA
3d ago
+940-216

Description

1. Stored secrets in InputPasswordTypeIn

  • New prop existingValue: a stored value from the backend, already masked.
  • While value === existingValue:
    • the field shows the value as a medium blue Tag with the key icon, and the input itself stays empty;
    • the reveal toggle is disabled;
    • Backspace or Delete clears the value;
    • typing or pasting replaces the value with the new text.
  • After the first edit, the field is a normal password field again.
  • To keep the stored secret on submit, the caller compares the submitted value with existingValue.
  • The tag has no tooltip.
  • An unchanged existing value does not block submission: the native required check is off while the tag shows. A readOnly field keeps its existing value, and the caller's onKeyDown runs first and can cancel the deletion.
  • The mask prop and its ✱ overlay are removed. Password fields always use the browser's own dots.

Callers that edit a stored secret now pass existingValue

  • LLM provider modals (APIKeyField, Bedrock, Ollama), SSO provider modal, index settings, web search, voice, tracing, image generation (OpenAI, Azure), OpenAPI OAuth client secret, MCP API token and OAuth secret, MCP user credentials, Craft app user credentials, Slack bot tokens.
  • In each one, the stored masked string is the field's initial value. So the untouched field keeps Formik's dirty false, and a Save gated on dirty stays blocked. Submit logic does not change: an unchanged value still counts as unchanged.
  • SSO used isNonRevealable on edit only to protect the stored value. It uses existingValue now.
  • No change where the form starts empty for a stored secret: the hooks modal (only a placeholder holds the masked key), the Discord bot config and the custom-analytics key.

Raw password inputs move to InputPasswordTypeIn

  • Slack bot tokens, reset password, impersonate (API key, autoComplete="new-password"), custom analytics secret key, plugin connection dialog.

2. Keyboard and tab navigation (from the closed #16238)
These are the Opal parts of the closed Plugins PR. The plugins-specific parts are left out.

  • Tabs:

    • new activationMode. In "manual", the default, arrows move focus and Enter or Space selects.
    • a focus ring shows while the keyboard moves focus;
    • Tab enters the list on the selected tab and the next Tab leaves it, as the ARIA tabs pattern says;
    • Escape leaves the tabs, but only from a tab: a control in rightChildren keeps focus;
    • only a left click focuses a tab.
    • orientation is omitted from the props: Opal tabs are horizontal only.
  • Interactive (stateless): keyboard focus shows the hover state in place of the browser's ring.

  • useGridNavigation: keyboard focus on a control inside a grid item shows again.

  • InputTypeIn clear button: it keeps focus in the field after it clears the value, it has an aria-label, and it sends onChange a real change event.

  • Escape on a control beside a text field (the clear button, the reveal toggle, a chevron, a chip) returns focus to the field, also when a tooltip consumed the key. This covers InputTypeIn and every input built on it, plus TagField. On InputTime and InputDatePicker, which have no single input, it focuses the first segment.

3. Input action buttons

  • The right-side icon buttons use tertiary prominence: the clear buttons of InputTypeIn, TagField, InputTime and InputDatePicker, and the date picker's calendar button.

  • The InputTypeIn and UploadControl clear buttons have tooltips.

  • The tooltip-only clear and calendar buttons of TagField, InputTime and InputDatePicker get an aria-label, so they have an accessible name.

  • The clear button is always an input's last control. InputDatePicker drew it before the calendar button, and TagField (InputTypeInTag, the multi-select) drew it before the dropdown chevron.

  • TagField (InputTypeInTag, the multi-select) uses size="sm" for its clear button, as the other inputs do. It used xs.

Additional Options

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

Summary by cubic

Adds an existingValue prop to InputPasswordTypeIn so forms that edit backend secrets show the saved value as a key tag instead of editable masked text, and polishes Opal tab keyboard navigation, focus handling, and input action buttons.

Stored secrets

  • existingValue marks a stored backend value: while the field's value equals it, the value renders as a key tag, the input stays empty, and the reveal toggle is disabled.
  • Backspace or Delete clears it; typing or pasting replaces it whole. A read-only field keeps the value, the caller's onKeyDown can cancel the deletion, and an unchanged tag doesn't fail a native required check.
  • Secret-editing forms (Slack tokens, SSO, MCP, language-model and image-gen modals, tracing, voice, web search, and more) now pass existingValue; a few raw password inputs move to InputPasswordTypeIn. The mask prop and idle asterisk overlay are removed.
  • E2E specs for OpenAPI OAuth, index settings, and web search now verify the stored value appears as a tag with an empty input.

Keyboard and input actions

  • Tabs defaults to manual activation: arrows move focus, Enter or Space selects, a focus ring tracks keyboard movement, and Escape leaves the tabs. Only a plain left click focuses a tab; a caller's onKeyDown can cancel the Escape.
  • Keyboard focus on Interactive surfaces replaces the browser ring with the hover state; useGridNavigation now shows focus on controls inside grid items.
  • InputTypeIn's clear button sends a real change event, keeps focus in the field after clearing, and gains an aria-label.
  • Escape on an input's side controls (clear, reveal toggle, chevron, chip) returns focus to the field, even when a tooltip consumed the key; on InputTime and InputDatePicker it focuses the first segment.
  • Input action buttons use tertiary prominence, the clear button is always an input's last control, and TagField's clear button is sm like the other inputs'.

Summary by CodeRabbit

  • New Features

    • Password fields display saved credentials as masked, non-revealable tags. Clear them with Backspace or Delete, or replace them by typing or pasting.
    • Tabs use manual activation: arrow keys move focus, while Enter or Space selects.
  • Improvements

    • Credential fields preserve saved values while editing across administrative and configuration forms.
    • Clear controls provide localized labels and tooltips, and return focus to the field.
    • Escape restores focus from date, time, tag, and input controls.
    • Keyboard focus styling is more consistent across interactive controls and grid navigation.