Skip to content

Moves the mods page onto the new page layout and toolbar (LAZ-947) - #23940

Open
r1chm8 wants to merge 6 commits into
masterfrom
laz-947
Open

Moves the mods page onto the new page layout and toolbar (LAZ-947)#23940
r1chm8 wants to merge 6 commits into
masterfrom
laz-947

Conversation

@r1chm8

@r1chm8 r1chm8 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

https://linear.app/nexus-mods/issue/LAZ-947/implement-redesign-of-mods-page-layout-toolbar-and-table

Page & toolbar — The page renders

Page/PageHeader/PageScroll/PageContent and the new Toolbar instead of MainPage chrome and IconBar; the table gets the edge-to-edge sticky-header treatment the extensions page uses. A new hook adapts the existing mod-icons registrations into toolbar actions — hidden on a false condition, disabled on a string, ordered by registered position, as IconBar showed them. The five entries that were React components become plain actions (Install From File, Check for Updates, Deploy, Purge in the hook; Manage Rules in mod-dependency-manager) and their button components are deleted.

image

Order & icons

The row reads Install From File, Open, History, Check for Updates, Categories, Manage Rules. The "Open …" and "Import From …" actions each fold behind one button again, now via explicit groups rather than IconBar's icon-matching; a menu holding one action renders as that action. Manage Rules gets a rules icon of its own, Install From File a plus, and an unknown icon name shows a puzzle piece.

image

Menu/Button primitives

Menu rows take the same brand a Button does, so an action looks the same on the bar and in the overflow menu it collapses into; danger becomes a real Button brand; nxm-dropdown-item-active is renamed -focus (Headless UI 2 deprecated active, and -active misreads against CSS :active).

Bottom of the page

The multi-selection bar portals out of the table into the page's own bottom section via a new footerContainer prop (other tables keep the footer where it was), freeing the 48px the rows gave up to a placeholder. The two-selection hint becomes UsageAlert, keyed the same as Usage so existing dismissals carry. "Get more mods" stops sticking to the bottom, and the empty state becomes NoResults with a game-named button (or menu, where several sources offer one).

image

Table sizing & details pane

A sticky-header table's container takes flex-auto and spans the columns it overflows into. The details pane a double click opens now sticks to the top/bottom/right of the page scroll rather than standing the full height of the table and sliding out of view on horizontal scroll; PageScroll becomes a size container so it has something to measure.

image

Toolbar labels

The Open and Import buttons get their ... back, appended to the label so the words stay the translated key. Manage Rules now names its unresolved conflicts in the tooltip ("Manage Rules (3 unresolved conflicts)") instead of losing the flash it used to get: an action can carry a notice in its options, which the toolbar brackets after the title. mod-dependency-manager owns the text and now stores the count, not just a boolean.

image

Deliberate differences,

All revisited by the toolbar redesign: Deploy is highlighted by brand colour rather than the flashing class.

image

r1chm8 added 6 commits August 13, 2026 11:20
…947)

The mods page now renders Page/PageHeader/PageScroll/PageContent and the new
Toolbar instead of the legacy MainPage chrome and IconBar, and its table gets
the edge-to-edge sticky-header treatment the extensions page uses.

The toolbar takes actions rather than components, so a new hook adapts the
mod-icons registrations for it — hidden when a condition returns false,
disabled when it returns a string, ordered by the registered position, exactly
as IconBar showed them. Anything that doesn't fit the width collapses into the
overflow menu, which ToolbarGroup already handles.

The five toolbar entries that were React components had no equivalent in the
action shape, so they are now plain actions with their behaviour carried over:
Install From File, Check for Updates, Deploy Mods and Purge Mods in the hook,
Manage Rules as a plain registration in mod-dependency-manager. The button
components they replace are deleted; CheckModVersionsButton stays because it
is still the table's row action.

Deliberate differences, all of which the toolbar redesign revisits:
- Deploy is highlighted via brand colour rather than the flashing class
- Manage Rules loses its unresolved-conflict emphasis
- a string condition greys an action out without showing the explanation

E2E selectors move from the ids those components carried to the data-testid
the toolbar renders.
A sticky-header table scrolls with the page rather than in a pane of its own,
so it carries `height: auto` and stopped at its last row. The details pane is
positioned against that container, which meant double-clicking a row in a
table holding one row opened a pane one row tall.

The container now also takes `flex-auto`, and the mods and extensions pages
lay their scroll content out as full-height flex columns so there is height to
grow into. `flex-auto` rather than `flex-1` keeps the basis on the content, so
a table taller than the page still scrolls it.

Both page-header toolbars take `flex-1 justify-end` for the same sort of
reason: as a flex item a toolbar is sized by its content, and a toolbar sized
by its content can't tell how much room it has, so it kept every action and
ran over the page title instead of collapsing into its overflow menu. Growing
rather than only shrinking is what lets it expand again when space returns.

The mods toolbar moves out of ModList into a component of its own.
The row reads Install From File, Open, History, Check for Updates, Categories,
Manage Rules, which moves Open to position 30 and History to 40. Position is a
single ordering for the row and the overflow menu both, so those two move up the
menu as well.

Open is new here: the five folder actions move out of `mod-icons` into a group of
their own, which the page folds into one button with a menu behind it, so they
take one slot on the row between them. `ToolbarPanelButton` now honours
`panelRole`, so that menu is styled as a dropdown and focuses its own first row
rather than being treated as a panel of settings — it hardcoded the latter.

Icons: Manage Rules asks for a "rules" icon of its own rather than borrowing the
profile transfer "connection" one, Install From File takes a plus, and an icon
name we have no path for now shows a puzzle piece — such a name comes from an
extension — rather than a generic shape.
…ton each (LAZ-947)

IconBar grouped every `mod-icons` action sharing an icon into one dropdown,
labelled with whatever leading words their titles had in common. That is how
the five "Open ..." actions came to sit behind an "Open" button, and the two
"Import From ..." ones behind an "Import" button, without anyone asking for it.
The new toolbar only knew about a hardcoded `mod-icons-open` group, so any
action that had relied on the icon grouping became a button of its own —
Witcher 3's "Open TW3 Documents Folder", and both importers.

An action now lands in one of those menus either by registering into the
menu's group, which is how to ask for it going forward, or by registering
into `mod-icons` under the menu's icon, which is what extensions we don't
ship still do. A menu holding a single action renders as that action rather
than a menu of one, the way IconBar did, so a game supporting only one
importer doesn't grow a button to reach it.
…LAZ-947)

A menu row now takes the same `brand` a Button does, so an action reads the
same on a toolbar as it does in the overflow menu it collapsed into — Deploy
Mods kept its primary colour on the bar and lost it the moment it collapsed.
`IToolbarAction` inherits the brand from `IMenuAction` rather than declaring
its own, so there is one union behind both. A row tints its icon and leaves
the labels an even column of text to read down; `danger` is the exception and
colours the whole row, a destructive action being the one thing in a menu that
should be hard to pick by accident.

`danger` was already a dropdown-item style but no Button brand answered to it,
so it becomes a real brand: the token ramp was there, only the `nxm-button-danger`
block and the union value were missing. `DropdownItem` takes a brand too, which
lets ToolRow's Delete row drop the class it applied by hand.

Also renames `nxm-dropdown-item-active` to `-focus`, along with the props and
state behind it. Headless UI 2 deprecated the `active` render prop in favour of
`focus`, and in a stylesheet `-active` reads against CSS's own `:active`, which
means something else entirely.
…pty state (LAZ-947)

Four things that all belonged to the bottom half of the page.

The table's multi-selection bar moves out of the table and into the page's own
bottom section, above the drop panel. `Table` takes a `footerContainer` and
portals the footer into it, so the bar lays out in the page's flow instead of
absolutely filling the bottom of the table — which also frees the 48px the rows
used to give up to a placeholder keeping them clear of it. A table that passes
no container keeps the footer where it was, which is every other table.

The drop panel gains a wrapper of its own to position against, since the bar is
now a sibling of it inside that section: the collapse toggle is absolute, and
with the `relative` still on the section it would have been positioning against
a box holding both. Collapsing reaches only the dropzone.

The hint standing in for the bar below two selections becomes an `Alert`. It is
still dismissed for good — `UsageAlert` keeps the decision in
`settings.interface.usage` under the same id `Usage` used, so anyone who has
already dismissed it won't see this one either. `Usage` stays for its four other
callers, which use the reopenable `persistent` mode this doesn't offer.

"Get more mods" stops sticking to the bottom of the page as you scroll and sits
after the last row, the page rather than the table being what scrolls now. Its
translucent band goes with the sticky, nothing passing behind it any more.

The empty state becomes `NoResults`, and its way out is a button named for the
game — "Get Skyrim SE mods" rather than "But don't worry, I know a place...",
which moves up to the message. Where more than one source offers somewhere to
browse it becomes a menu, on the design-system `Dropdown` rather than the
bootstrap one the bar still uses. The filter deciding that is now shared with
the bar rather than written out twice.

The details pane a row opens on a double click is positioned against the table
container, and a sticky-header table is as tall as its rows and as wide as its
columns — so the pane stood the full height of the table and slid out of view as
the page scrolled sideways. A layer between the two sticks to the top right of the
page scroll instead and stands as tall as it, which keeps the pane on the part of
the table you can see. The container spans the columns the table overflows into,
sticking only carrying as far as the containing block, and the scroll region
becomes a size container so the layer has something to measure. The pane's
collapse button takes the scrollbar's width as padding, or its icon would sit
behind a horizontal scrollbar.
@r1chm8
r1chm8 requested review from a team as code owners August 13, 2026 13:16
@github-actions

Copy link
Copy Markdown

This PR has conflicts. You need to rebase the PR before it can be merged.

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.

1 participant