Highlight the rail entry whose tool is open (#7723)

This commit is contained in:
Reece Browne
2026-08-28 13:02:47 +00:00
committed by GitHub
parent 1c055f3d18
commit d3708c1e63
5 changed files with 46 additions and 0 deletions
@@ -22,6 +22,7 @@ export interface QuickNavHostBridgeProps {
requestNavigation?: (go: () => void) => void;
onGoToDefaultState?: () => void;
onSelectTool?: (toolId: ToolId) => void;
activeTool?: ToolId | null;
/** Merged over the reasons worked out here, for what only the app can see. */
toolReasons?: QuickNavToolReasons;
}
@@ -34,6 +35,7 @@ export function QuickNavHostBridge({
onOpenSettings,
requestNavigation,
onSelectTool,
activeTool = null,
onGoToDefaultState,
toolReasons,
}: QuickNavHostBridgeProps) {
@@ -59,6 +61,7 @@ export function QuickNavHostBridge({
signingBadge,
portalAccess,
readerMode,
activeTool,
notificationsOpen,
toolReasons: mergedToolReasons,
},
@@ -48,6 +48,8 @@ export function QuickNavRailHost() {
else go(route);
};
const openingTool = (id: ToolId) => ({ current: host?.activeTool === id });
const unusable = (id: ToolId) => {
const reason = host?.toolReasons?.[id];
return { disabled: Boolean(reason), reason };
@@ -135,6 +137,7 @@ export function QuickNavRailHost() {
icon: (
<LocalIcon icon="rebase-outline-rounded" width={SIZE} height={SIZE} />
),
...openingTool("automate"),
...unusable("automate"),
onClick: () => openTool("automate", "/automate"),
},
@@ -146,6 +149,7 @@ export function QuickNavRailHost() {
),
badge: host?.signingBadge,
badgeTone: "warning",
...openingTool("sharedSign"),
...unusable("sharedSign"),
onClick: () => openTool("sharedSign", "/shared-sign"),
},
@@ -13,6 +13,7 @@ function Probe({ onRead }: { onRead: (value: unknown) => void }) {
appMounted: host?.appMounted,
chromeless: host?.chromeless,
identity: host?.identity,
activeTool: host?.activeTool,
openSettings: Boolean(host?.actions.current?.openSettings),
});
return null;
@@ -26,6 +27,11 @@ function App() {
return null;
}
function AppWithTool({ tool }: { tool: "automate" | null }) {
useRegisterQuickNavHost({ activeTool: tool }, {});
return null;
}
function LoginRoute() {
useSuppressQuickNavRail();
return null;
@@ -71,6 +77,31 @@ describe("QuickNavHostContext", () => {
expect(after.openSettings).toBe(false);
});
it("clears the open tool when the next app registers without one", () => {
let latest: Record<string, unknown> = {};
const view = render(
<QuickNavHostProvider>
<Probe
onRead={(value) => (latest = value as Record<string, unknown>)}
/>
<AppWithTool tool="automate" />
</QuickNavHostProvider>,
);
expect(latest.activeTool).toBe("automate");
act(() => {
view.rerender(
<QuickNavHostProvider>
<Probe
onRead={(value) => (latest = value as Record<string, unknown>)}
/>
<AppWithTool tool={null} />
</QuickNavHostProvider>,
);
});
expect(latest.activeTool).toBe(null);
});
it("hides the bar while a route with no app chrome is on screen", () => {
// appMounted is sticky, so it can't answer "is an app on screen now".
const { view, read } = setup();
@@ -24,6 +24,7 @@ export interface QuickNavHostData {
signingBadge: number;
portalAccess: boolean;
readerMode: boolean;
activeTool: ToolId | null;
/** The app owns the panel; the rail's bell only reports its state. */
notificationsOpen: boolean;
/** Translated; absent means usable. */
@@ -61,6 +62,7 @@ const EMPTY_DATA: QuickNavHostData = {
signingBadge: 0,
portalAccess: false,
readerMode: false,
activeTool: null,
notificationsOpen: false,
hasSettings: false,
};
@@ -90,6 +92,7 @@ export function QuickNavHostProvider({ children }: { children: ReactNode }) {
merged.signingBadge === prev.signingBadge &&
merged.portalAccess === prev.portalAccess &&
merged.readerMode === prev.readerMode &&
merged.activeTool === prev.activeTool &&
merged.notificationsOpen === prev.notificationsOpen &&
merged.hasSettings === prev.hasSettings &&
merged.identity?.displayName === prev.identity?.displayName &&
@@ -143,6 +146,7 @@ export function useRegisterQuickNavHost(
signingBadge,
portalAccess,
readerMode,
activeTool,
notificationsOpen,
toolReasons,
} = data;
@@ -155,6 +159,8 @@ export function useRegisterQuickNavHost(
signingBadge: signingBadge ?? 0,
portalAccess: portalAccess ?? false,
readerMode: readerMode ?? false,
// Cleared, not omitted as toolReasons is: a stale tool marks an entry.
activeTool: activeTool ?? null,
notificationsOpen: notificationsOpen ?? false,
// Omitted when unknown, so the last answer survives a re-fetch.
...(toolReasons ? { toolReasons } : {}),
@@ -168,6 +174,7 @@ export function useRegisterQuickNavHost(
signingBadge,
portalAccess,
readerMode,
activeTool,
notificationsOpen,
toolReasons,
hasSettings,
@@ -525,6 +525,7 @@ export default function HomePage() {
onSetReaderMode={setReaderMode}
onGoToDefaultState={goToDefaultState}
onSelectTool={handleToolSelect}
activeTool={selectedToolKey}
toolReasons={quickNavToolReasons}
/>
<FilesPageProvider>