Skip to content

Restore styling and behaviour lost in the GButton adoption sweep - #23464

Draft
itisAliRH wants to merge 37 commits into
galaxyproject:devfrom
itisAliRH:fix/gbutton-polish
Draft

Restore styling and behaviour lost in the GButton adoption sweep#23464
itisAliRH wants to merge 37 commits into
galaxyproject:devfrom
itisAliRH:fix/gbutton-polish

Conversation

@itisAliRH

Copy link
Copy Markdown
Member

Follow-up to #22688.
That PR converts the last BButton call sites to GButton, and the component-level fixes it needs are already on its own branch. This PR includes the remaining call-site fixes, mostly styling regressions exposed by the conversion, plus three small behavior fixes.

All the styling regressions have the same cause. The converted buttons no longer carry .btn, and GButton's scoped rules (.g-button.g-…[data-v-…], specificity 0,3,0 and up) outrank the global helper classes those call sites still rely on (0,1,0): .inline-icon-button (client/src/style/scss/ui.scss:427), .btn-transparent-background (client/src/style/scss/base.scss:289), .ui-link and .toggle-link. The visible effect is that brand-blue borderless icons render as grey boxes or bordered chips, and a couple of hover states paint a solid fill where they used to be bare links. Each call site is fixed by passing props that express what the helper used to do (transparent, icon-only, color="blue"), or by raising the scoped selector so it deterministically outranks GButton's own rule, rather than relying on bundle order.

Three behavior fixes ride along. HistoryCounter.test.ts and JobOutputs.test.js had been weakened to .findComponent(GButton).vm.$emit("click"), which no longer covers the template's @click wiring or GButton's disabled guard and would pass with the @click deleted; both are back to mounting for real and clicking the button. NewUserConfirmation gains an if (!termsRead.value) return; guard in submit(), because GButton renders aria-disabled rather than native disabled, so an Enter-submit on the form never reaches the button handler and the disabled state alone no longer gates account creation. And CollectionDisplay's import button was href="#" with a @click that had no .prevent; bootstrap-vue's BLink special-cased a bare # href; GButton does not, so importing a collection also pushed a history entry and scrolled the page to the top.

One change is worth calling out because the obvious fix would have been wrong. GCard's indicator buttons needed transparent color="blue", but hard-coding those props on the element would have silently killed indicator.variant for callers that set it (useHistoryCardIndicators.ts:75 passes variant: "danger"), because Vue 2's v-bind="obj" never overwrites an explicitly declared attribute. Instead, the indicator's default variant changed from outline-secondary to link, which variantToColor already maps to { transparent: true, color: "blue" }, so caller-supplied variants continue to work.

Depends on #22688. This branch is stacked on it, so the diff currently includes that PR's component fixes; it will be rebased onto dev once #22688 merges, which drops them out.

How to test the changes?

(Select all options that apply)

  • I've included appropriate automated tests.
  • This is a refactoring of components with existing test coverage.
  • Instructions for manual testing are as follows:
    1. Run cd client && pnpm vitest run src/components/History/CurrentHistory/HistoryCounter.test.ts src/components/JobInformation/JobOutputs.test.js and confirm both pass — they now mount the component and click the real button rather than emitting click on a stub.
    2. Open /workflows/list with at least one published workflow and one imported from a TRS server or a URL. The published globe, TRS shield, and URL-import indicators must be borderless brand-blue glyphs, not grey- or white-bordered chips.
    3. Open a history's sharing page (/histories/sharing?id=…). The Edit URL pencil, the Done check, and the Copy button must be borderless blue icons, not full grey bordered buttons.
    4. As an admin, create a broadcast with two or more action links (/admin/notifications/create_new_broadcast), then view it. The left/right arrows must be borderless, and the dismiss cross must be borderless with a red hover rather than a grey filled square.
    5. On any item with tags, hover over the × on a tag pill: it must stay bare over the tag's coloured background instead of taking a grey hover box.
    6. Open a tool that has tutorial recommendations and click the category toggles. They must render as brand-blue inline links on hover, not grey inline-flex buttons.
    7. On an item with more tags than fit, hover "N more…" and "Fewer tags": they must stay bare links rather than painting a solid blue pill.
    8. In a workflow report or page, click the import button on a history dataset collection element. The collection must be imported without the page also navigating to # (no new history entry, no scroll to top).
    9. On the OIDC new-user confirmation form, press Enter without ticking the terms checkbox. No account may be created.

License

  • I agree to license these and all my past contributions to the core galaxy codebase under the MIT license.

dannon and others added 30 commits September 3, 2026 17:28
Replaces BButton imports and tags with GButton in seven components
that use only static variant/size strings. No behavior change --
GButton renders the same <button> with equivalent styling. Part of
the bootstrap-vue elimination sweep.
Replaces BButton imports and tags with GButton across thirteen
Workflow editor, list, run, published, invocation, and metrics
components that use only static variant/size strings. No behavior
change. Part of the bootstrap-vue elimination sweep.
Replaces BButton imports and tags with GButton across thirteen
History components (archiving, content/dataset actions, navigation,
list, layout, multi-view) that use only static variant/size strings.
No behavior change. Part of the bootstrap-vue elimination sweep.
Replaces BButton with GButton across two Dataset components and
five Libraries/LibraryFolder components using only static
variant/size strings. FolderDetails (uses v-b-modal directive)
deferred to a follow-up commit. No behavior change.
Replaces BButton with GButton in two admin form components and
three ConfigTemplates components using only static variant/size
strings. DataManagerJobs (uses dynamic :variant) deferred to a
follow-up. No behavior change.
Replaces BButton with GButton across ten components using only
static variant/size strings: Markdown rendering, Sharing dialog
and embeds, SelectionDialog, SchemaOrg creator editor, and
TagsMultiselect. CellButton (dynamic) deferred. No behavior change.
…c usages

Replaces BButton with GButton across thirteen components in Tool,
ToolsList, Panels, Notifications, PageDisplay, User Notifications,
InteractiveTools, JobInformation, and Collections/sheet that use
only static variant/size strings. JobOutputs.vue's block prop
becomes class="w-100". Dynamic-variant components deferred. No
behavior change.
Five icon-only buttons converted from variant="link" in the
previous Panels/Interactive/Collections sweep were missing the
transparent prop. Without it GButton falls back to grey background
and border, which differs visually from BButton's link variant.
This restores the no-background, no-border look.
Replaces BButton with GButton in eight components originally
miscategorized as dynamic-variant -- their :variant= bindings
were on BAlert/BBadge rather than BButton, so the BButtons
themselves use static variant strings only. Covers Login,
admin/DataManager, Tool/User credentials, Upload/RulesInput,
Workflow run, CustomBuilds, and Grid/GridList sort headers.
Adds client/src/components/BaseComponents/variantToColor.ts as a
shared helper for translating bootstrap-vue variant strings to
GButton color/outline/transparent attributes, plus a sizeToGSize
helper for size translation. Uses it across eight components that
bind :variant= dynamically: CellButton, BroadcastContainer,
ObjectStoreSelectButton, CompositeBox, RulesInput, ServiceCredentials,
GCard, HistoryCounter, and MultipleViewItem. Static buttons in the
same files use direct prop translation. No behavior change.
Test updates:
- swap btn-primary / btn-outline-primary class selectors for
  g-button g-blue / g-blue g-outline (DatasetCopy)
- swap "active" pressed-state class for "g-pressed" (FormData)
- swap "btn" class check for "g-button" (FilterMenu)
- swap "disabled" attribute check for "aria-disabled" -- GButton
  drops the native disabled attribute in favor of aria-disabled
  to keep the click handler reachable by screen readers (FilesDialog)
- swap shallow-mount BButton stub names for GButton
  (RoleForm, PageForm, ConfigureHeader, ExternalRegistration)
- mount instead of shallowMount where shallow stubs swallowed
  click events (SharingIndicators, SelectionStatus, HistoryCounter
  partial via direct findComponent emit)
- inject a test router into LibraryDataset so the GButton-rendered
  router-link can resolve

Production fixes uncovered by the test sweep:
- CellButton: GButton does not forward template listeners through
  v-bind="$attrs", so add .native modifier on @mouseleave/@blur
  to keep the auto-blur-on-mouseleave behavior working
- LibraryDataset: convert the named-route :to object to a path
  template literal since GButton.to is typed as string and the
  object form was triggering a Vue prop type warning
Two safety fixes to GButton itself, motivated by the wider BButton
adoption sweep. First, default the rendered `<button>` element to
`type="button"` when no explicit type is provided via $attrs. Without
this, any GButton inside a `<form>` silently defaults to type="submit"
and starts submitting the form on click -- a regression Bootstrap's
BButton avoided by setting type="button" itself. Explicit
`type="submit"` / `type="reset"` from callers still wins via Vue's
later-binding precedence on `v-bind="$attrs"`.

Second, add `.g-pressed` styling for solid (non-outline, non-transparent)
GButtons. Pressed state previously only rendered for outline and
transparent variants, so solid buttons with `:pressed` had no visual
indication. Grey solid pressed now uses --color-grey-400 background and
--color-grey-600 border, matching the existing hover/active direction;
colored solid pressed uses the darker --color-*-700 / --color-*-900
pair.
Cleanup of direct b-button / BButton usages that the earlier static
and dynamic sweeps missed -- mostly lowercase `<b-button>` forms (the
prior commits only matched the PascalCase tag) across markdown and
page editors, library permissions pages, API key management, schema.org
forms, user-facing pages, tool shed install actions, workflow
attributes/run/storage, and the export ActionButton. Standard prop
translations apply: variant=primary -> color=blue, variant=link ->
transparent, size=sm -> size=small, etc.

WorkflowAttributes' Readme toggle button keeps its :pressed binding;
the matching solid-pressed styling lives in the prior GButton commit.
Replaces BButtonGroup wrappers with GButtonGroup across the components
whose children were converted to GButton in this branch. Bootstrap's
.btn-group child styling targets .btn classes, so leaving GButton
children inside a BButtonGroup wrapper produces ungrouped, individually-
rounded buttons -- a visible regression. GButtonGroup already exists
with the equivalent corner-merge selectors targeting .g-button.

A few of these files (ZoomControl, FormColumnDefinitions,
FormRecordFieldDefinitions, Workflow/Editor/Index) also still had
lowercase <b-button> children inside their wrappers; those get
converted to GButton in the same change since wrapper migration
required it.

The remaining BButtonGroup in WorkflowInvocationMetrics.vue is left as
BButtonGroup intentionally -- it wraps BDropdown triggers (still .btn-
based), not GButtons, so the .btn-group child styling still applies.

Also updates client/src/components/Workflow/Editor/Comments/_buttonGroup.scss
to target the new .g-button.g-outline.g-blue and .g-pressed class
combinations that GButton renders instead of Bootstrap's
.btn-outline-primary / .active.
The previous `:type="baseComponent === 'button' && !$attrs.type ? 'button' : undefined"`
relied on `v-bind="$attrs"` to re-apply caller-provided `type="submit"` /
`type="reset"`. In Vue 2 that does not work: explicit attribute bindings
take precedence over `v-bind="object"` regardless of source order, so the
explicit `:type="undefined"` won and the rendered button had no type
attribute at all. The form-button HTML default still made it act as submit
at runtime, but CSS selectors like `button[type='submit']` stopped matching
-- breaking tests in LoginForm, RegisterForm, and ChangePassword that find
their submit button by attribute.

Computing the type directly via `$attrs.type ?? 'button'` makes the
explicit binding render whichever value is right, no longer dependent on
`v-bind="$attrs"` precedence.
The FormDataContextButtons wrapper switched from BButtonGroup (which
rendered `.btn-group`) to GButtonGroup (which renders `.g-button-group`)
as part of the wider button-group migration. Three find selectors in the
FormData test were still scoping by `.btn-group`, which now matches
nothing in the rendered output.
The branch's BButton variant="link" -> GButton transparent translation
consistently dropped the link-color part of Bootstrap's link variant.
GButton's transparent (no color) renders as --color-grey-700 (#35373f,
near-black), while variant="link" originally rendered in Bootstrap's
link blue (Galaxy's --color-blue-600 / #25537b navy). The plan's
translation table flagged this -- "transparent (no color, or color=blue
if styled blue)" -- but every conversion picked the no-color option.

Audit: pre-existing GButton callers on upstream/dev (Tool/Buttons,
PairedElementView, FormDrilldownOption, etc.) follow the convention
`<GButton transparent color="blue">` for styled-blue text/link buttons
and `<GButton transparent>` (no color) for icon-only buttons matching
surrounding text. This commit applies that convention retroactively.

Static call-sites (visible text content, was variant="link"): adds
color="blue" to the sort-header in Grid/GridList, the display-btn
showing sub-item counts in ContentItem, both nav buttons in
CollectionNavigation, the three filter buttons (active/deleted/hidden)
in HistoryCounter, the "History" button in HistoryNavigation, "Select
All" in SelectionStatus, both Library permissions "Go back" buttons,
and the Simple Form toggle in WorkflowRunForm.

Dynamic helper: variantToColor("link") now returns
{ transparent: true, color: "blue" } so HistoryCounter's refresh button
keeps its navy color in the link state (and falls back to red via the
existing danger branch). HistoryCounter.test.ts assertions updated to
match.

Icon-only conversions (ContentOptions toolbar, DatasetActions, Sharing
indicators, Tag delete, workflow move/delete buttons, page editor
controls, modal close X's, etc.) are intentionally left as transparent
without color -- those were always meant to inherit the surrounding
text color and the no-color render is correct for them.
These files were added to dev after the original GButton migration was
written, so rebasing left them still on bootstrap-vue's BButton. Convert
them to GButton following the same variant->color mapping the rest of the
PR uses (link -> transparent blue, outline-primary -> blue outline,
success -> green, outline-danger -> red outline, sm -> small).

ProposalDiffView and SectionPatchView's tests were shallow-mounting, which
worked with BButton (functional, native listeners) but not with GButton,
which emits click from its own root handler. Mount those two for real, and
check aria-disabled instead of the native disabled attribute since that's
how GButton signals disabled state.
SelectPreferredStore.test.ts finds the modal's Ok button with
".g-button.g-blue". That was unambiguous while GCard's action buttons were
still BButtons, but converting them means variant="outline-primary" now
renders .g-button.g-blue too, so find() returned a card action instead and
the click never reached the modal. Scope the selector to
.g-modal-confirm-buttons, which is the footer GModal renders.
The comment explains why the refresh button needs a logged-in user to
render. HistoryCounter switched to GButtonGroup in this PR, so the comment
was naming a component the file no longer uses.
A native disabled button dispatches no click event at all. GButton and
GLink deliberately render `aria-disabled` instead of the native attribute
so a disabled control stays hoverable for its `disabledTitle` tooltip, but
the JS guard in `onClick` only called `preventDefault()`. The native click
therefore kept bubbling: clickable ancestors (history content rows, GCard
roots, sidebar list rows) still reacted to clicks on disabled buttons, and
any `.stop`/`.prevent` modifier a caller put on the component never ran,
because those modifiers are attached to the component listener that is
skipped when disabled.

Call `stopPropagation()` alongside `preventDefault()` so a disabled
control behaves like a native one.
When `to` is set the root element becomes a RouterLink. Vue 2 treats
`@click` on a component as a component listener, and vue-router 3 neither
emits a `click` event nor merges `$listeners`, so the `onClick` handler
never ran and every caller-supplied `@click` was silently dead. Live
casualties include GCard's primary and secondary actions, the tool list
upload action and SourceOptionCard's select emit.

Bind `@click.native` alongside the existing `@click`. Vue 2 ignores
`nativeOn` on a plain element root, and binds it to the rendered anchor for
a component root, so exactly one of the two listeners fires in every case.
Both collapse to a single `@click` once the components run on Vue 3.

Also refresh the `useClickableElement` docstring, which justified the plain
`button` root for the disabled state with the now-fixed listener gap.
The root rendered a bare `div`, so the `aria-label` callers pass (for
instance MultiviewPanel's pin-recent group) was not exposed to assistive
technology; BButtonGroup rendered `role="group"`.

The group also lacked Bootstrap's `.btn-group > .btn { flex: 1 1 auto }`,
so a group stretched with `w-100` left its buttons at content width.
Bootstrap's `.btn` centred its label with `text-align: center`, which has
no effect on the `inline-flex` box `.g-button` uses. Full-width buttons
("Show N more outputs", the workflow readme toggle) rendered left-aligned,
and the fixed 1.5rem glyph buttons in the workflow comment toolbars sat at
flex-start inside their square.
Bootstrap's radius and z-index rules for an input group only ever matched
`.btn`, so a GButton in an `.input-group-append` kept its own rounded left
corners and rendered a visible seam against the adjoining input, and its
focus ring was painted under the control instead of over it. Add the
mirror-image rules for `.input-group-prepend` while we are here.
`&.g-pressed:not(.g-outline):not(.g-transparent)` is more specific than the
sibling `&:hover, &:focus-visible` block, so a pressed solid button lost all
hover and keyboard-focus feedback and read as inert. Restate the feedback
inside the pressed blocks, one step darker than the pressed background.
The rename pencil, the bookmark star and the card indicators carry
`.inline-icon-button`, a global helper that painted them brand blue and
borderless. That helper is a single class selector, so it now loses to
GButton's scoped variant rules and the buttons render grey with a grey
hover fill.

Say it with props instead: `transparent icon-only color="blue"` on the
pencil and star, and a `link` default variant for indicators, which
`variantToColor` maps to the same transparent blue while still honouring an
explicit `indicator.variant`.
The Edit URL / Done / Copy buttons kept the `.inline-icon-button` helper
but gained no GButton props in the sweep, so they went from borderless
brand-blue glyphs to full grey bordered buttons: the helper is a single
class selector and loses to GButton's scoped variant rules.
The paging arrows relied on `.inline-icon-button` for their brand-blue
borderless look and now render as grey bordered buttons on the coloured
banner. The dismiss cross has the same problem, and its scoped red hover
rule additionally loses to GButton's more specific transparent-hover rule,
so hovering painted a grey box instead of turning the cross red.

Give the arrows the transparent blue treatment as props, and raise the
dismiss button's scoped selectors above GButton's own.
The published-globe, TRS-shield and URL-import indicators are styled by
`.inline-icon-button`, which now loses to GButton's scoped variant rules,
so they render as solid grey bordered buttons in the workflow list instead
of borderless brand-blue glyphs.
`.inline-icon-button` used to override Bootstrap's `.btn-primary` on the
key glyph, marking it out as an indicator next to the solid blue clone,
remove and recommend actions. It now loses to GButton's scoped rules and
renders as a solid blue box, so state the transparent look as props.
`.btn-transparent-background` only ever targeted `.btn`, so the delete
cross on a tag now paints GButton's grey transparent hover box over the
tag's own colour. Match `.g-button` too, forcing the hover and active
colours past GButton's more specific scoped rules.
`.ui-link` makes a button read as a link, but its single-class `color` and
`display` declarations lose to GButton's scoped rules, so both togglers
render as grey inline-flex buttons. State the link look as props instead.

`transparent color="blue"` alone is not enough: GButton's transparent-blue
hover repaints the label near-white for a solid blue fill that `.ui-link`
suppresses with `background: none !important`, leaving the toggles
invisible on hover. Restate the link colour and underline in a scoped rule
specific enough to out-rank it.
`.stateless-tags .toggle-link:hover` loses to GButton's scoped
transparent-blue hover rule, so "N more..." and "Fewer tags" paint a solid
blue pill; the `padding: 0` rule ties with GButton's size padding and is
decided by bundle order. Raise the scoped selector above both.
Both tests dropped to `.vm.$emit("click")` on a GButton stub, which skips
the template's `@click` binding and GButton's own click guard entirely --
they would keep passing with the `@click` deleted. Render the button for
real and trigger a DOM click instead, as ProposalDiffView.test.ts does.
The confirm button no longer renders a native `disabled` attribute, so it
stays focusable and keyboard activatable and remains eligible as the
form's default button. An implicit form submission bypasses the button's
click handler entirely, so check the terms checkbox in `submit()` as well.
BootstrapVue's BLink special-cased an `href` of `#` and swallowed the
navigation. GButton has no such guard, so importing a collection also
pushed a history entry and scrolled the page to the top. The element is a
button already, so the placeholder href can simply go.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants