FN-5765: move settings version check from header to footer

Relocate Settings version/update UI into the modal footer to reduce header crowding.

- Remove the version/update-check control block from the Settings modal header.
- Add a dedicated footer version container and keep the update-check action/result rendering there.
- Update Settings modal CSS for the new footer layout and mobile wrapping behavior.
- Extend mobile tests to verify footer placement and update-check button clickability.

Files changed:
 packages/dashboard/app/components/SettingsModal.css    | 25 +++++++--
 packages/dashboard/app/components/SettingsModal.tsx    | 64 +++++++++++-----------
 packages/dashboard/app/components/__tests__/settings-mobile.test.tsx | 27 ++++++++-
 3 files changed, 77 insertions(+), 39 deletions(-)

Fusion-Task-Id: FN-5765

Fusion-Task-Lineage: 97923f08-e787-4274-a4ce-8b615b909468
This commit is contained in:
gsxdsm
2026-05-31 00:01:55 -07:00
parent 0ffe7f0604
commit ec92434f4c
3 changed files with 77 additions and 39 deletions

View File

@@ -41,11 +41,6 @@
min-width: 0; min-width: 0;
} }
.settings-update-check {
flex-wrap: wrap;
row-gap: var(--space-xs);
}
.settings-header-actions { .settings-header-actions {
margin-left: auto; margin-left: auto;
margin-right: var(--space-sm); margin-right: var(--space-sm);
@@ -164,10 +159,30 @@
font-weight: 500; font-weight: 500;
} }
.settings-modal-footer-version {
display: flex;
align-items: center;
margin-right: var(--space-sm);
min-width: 0;
}
.settings-update-check { .settings-update-check {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-sm); gap: var(--space-sm);
min-width: 0;
}
@media (max-width: 768px) {
.settings-modal-footer-version {
flex: 1 1 100%;
margin-right: 0;
}
.settings-update-check {
flex-wrap: wrap;
row-gap: var(--space-xs);
}
} }
.settings-version-check-btn { .settings-version-check-btn {

View File

@@ -7443,37 +7443,6 @@ export function SettingsModal({
<div className="modal-header"> <div className="modal-header">
<div className="settings-modal-heading"> <div className="settings-modal-heading">
<h3>Settings</h3> <h3>Settings</h3>
<div className="settings-update-check">
{appVersion && (
<button
type="button"
className="settings-version-check-btn"
onClick={() => {
void handleCheckForUpdates();
}}
disabled={updateCheckLoading}
aria-label="Check for updates"
title="Check for updates"
>
<span className="settings-modal-version">Version {appVersion}</span>
<RefreshCw size={12} className={updateCheckLoading ? "spinning" : undefined} />
</button>
)}
{updateCheckResult && (
<span
aria-live="polite"
className={`settings-update-result ${
updateCheckResult.error
? "settings-update-result--error"
: updateCheckResult.updateAvailable
? "settings-update-result--available"
: "settings-update-result--up-to-date"
}`}
>
{renderUpdateCheckResultContent()}
</span>
)}
</div>
</div> </div>
<div className="settings-header-actions"> <div className="settings-header-actions">
{form.showGitHubStarButton !== false && ( {form.showGitHubStarButton !== false && (
@@ -7588,6 +7557,39 @@ export function SettingsModal({
</div> </div>
)} )}
<div className="modal-actions"> <div className="modal-actions">
<div className="settings-modal-footer-version">
<div className="settings-update-check">
{appVersion && (
<button
type="button"
className="settings-version-check-btn"
onClick={() => {
void handleCheckForUpdates();
}}
disabled={updateCheckLoading}
aria-label="Check for updates"
title="Check for updates"
>
<span className="settings-modal-version">Version {appVersion}</span>
<RefreshCw size={12} className={updateCheckLoading ? "spinning" : undefined} />
</button>
)}
{updateCheckResult && (
<span
aria-live="polite"
className={`settings-update-result ${
updateCheckResult.error
? "settings-update-result--error"
: updateCheckResult.updateAvailable
? "settings-update-result--available"
: "settings-update-result--up-to-date"
}`}
>
{renderUpdateCheckResultContent()}
</span>
)}
</div>
</div>
<div className="modal-actions-left"> <div className="modal-actions-left">
<button <button
type="button" type="button"

View File

@@ -2,7 +2,7 @@ import fs from "node:fs";
import { loadAllAppCss } from "../../test/cssFixture"; import { loadAllAppCss } from "../../test/cssFixture";
import path from "node:path"; import path from "node:path";
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, waitFor } from "@testing-library/react"; import { render, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { SettingsModal } from "../SettingsModal"; import { SettingsModal } from "../SettingsModal";
import type { Settings } from "@fusion/core"; import type { Settings } from "@fusion/core";
@@ -192,10 +192,31 @@ describe("SettingsModal mobile adaptations", () => {
it("renders the app version label in mobile layout", async () => { it("renders the app version label in mobile layout", async () => {
mockSettingsViewport(true); mockSettingsViewport(true);
const { findByText } = render(<SettingsModal onClose={vi.fn()} addToast={vi.fn()} />); const { findByText, container } = render(<SettingsModal onClose={vi.fn()} addToast={vi.fn()} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled()); await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
expect(await findByText("Version 1.2.3")).toBeTruthy(); const version = await findByText("Version 1.2.3");
const modalActions = container.querySelector(".modal-actions");
const modalHeader = container.querySelector(".modal-header");
expect(version).toBeTruthy();
expect(modalActions?.contains(version)).toBe(true);
expect(modalHeader?.contains(version)).toBe(false);
});
it("keeps update-check button clickable from the footer", async () => {
mockSettingsViewport(true);
const user = userEvent.setup();
const { container } = render(<SettingsModal onClose={vi.fn()} addToast={vi.fn()} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
const modalActions = container.querySelector(".modal-actions");
expect(modalActions).toBeTruthy();
const updateButton = within(modalActions as HTMLElement).getByRole("button", { name: "Check for updates" });
await user.click(updateButton);
expect(updateButton).toBeTruthy();
}); });
it("excludes research sections from mobile picker when researchView is disabled", async () => { it("excludes research sections from mobile picker when researchView is disabled", async () => {