mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-05 16:04:43 +00:00
fix(app): keep the setup window mounted when the theme changes
The shell's appearance boundary remounts its dialog hosts whenever a theme color changes, so picking a theme in setup, or a system light/dark switch, replaced the open setup window and replayed its fade. Setup draws colors through CSS variables and themed props, which update in place, and changes no font size, so it now sits outside that remount. The dialog enter fade is skipped with reduced motion, and its style is typed for CI, which typechecks without the generated expo-env.d.ts.
This commit is contained in:
@@ -120,6 +120,58 @@ test("a new user goes from the loading screen straight into setup", async ({ pag
|
||||
}
|
||||
});
|
||||
|
||||
test("with reduced motion, setup appears without fading in", async ({ page }) => {
|
||||
const host = await startSetupHost("reduced-motion");
|
||||
|
||||
try {
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await recordFirstLaunch(page);
|
||||
await openAppAsNewUser(page, host);
|
||||
await expectSetupStep(page, "Welcome to Paseo");
|
||||
|
||||
expect((await readFirstLaunch(page)).setupOpacityOnArrival).toBe(1);
|
||||
} finally {
|
||||
await host.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("changing the theme keeps the setup window in place on every step", async ({ page }) => {
|
||||
const host = await startSetupHost("theme-change");
|
||||
|
||||
try {
|
||||
await openAppAsNewUser(page, host);
|
||||
await expectSetupStep(page, "Welcome to Paseo");
|
||||
const setupWindow = await setupModal(page).elementHandle();
|
||||
|
||||
for (const title of [
|
||||
"Welcome to Paseo",
|
||||
"Your agents",
|
||||
"Add your projects",
|
||||
"Let agents use Paseo",
|
||||
"Take your agents with you",
|
||||
]) {
|
||||
if (title !== "Welcome to Paseo") await continueSetup(page);
|
||||
await expectSetupStep(page, title);
|
||||
const heading = setupModal(page).getByRole("heading", { name: title });
|
||||
const headingNode = await heading.elementHandle();
|
||||
const lightColor = await heading.evaluate((node) => getComputedStyle(node).color);
|
||||
await page.emulateMedia({ colorScheme: "dark" });
|
||||
await expect(heading).not.toHaveCSS("color", lightColor);
|
||||
await page.emulateMedia({ colorScheme: "light" });
|
||||
await expect(heading).toHaveCSS("color", lightColor);
|
||||
expect(await setupWindow?.evaluate((node) => node.isConnected), title).toBe(true);
|
||||
expect(await headingNode?.evaluate((node) => node.isConnected), title).toBe(true);
|
||||
}
|
||||
|
||||
await goToSetupStep(page, "Appearance");
|
||||
await pickTheme(page, "Midnight");
|
||||
await pickTheme(page, "Light");
|
||||
expect(await setupWindow?.evaluate((node) => node.isConnected)).toBe(true);
|
||||
} finally {
|
||||
await host.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("the loading screen stays up while the setup host loads its workspaces", async ({ page }) => {
|
||||
const host = await startSetupHost("slow-workspaces");
|
||||
const gate = await installDaemonWebSocketGate(page, { port: host.port });
|
||||
|
||||
@@ -558,7 +558,6 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
|
||||
<WorkspacePinShortcutHandler />
|
||||
<WorkspaceRenameHost />
|
||||
<CommandCenter />
|
||||
<OnboardingHost />
|
||||
<AddProjectFlowHost />
|
||||
<HostChooserModal />
|
||||
<HostConfirmationSheet />
|
||||
@@ -569,6 +568,10 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
|
||||
<ChangelogHost />
|
||||
<QuittingOverlay />
|
||||
</AppearanceStyleBoundary>
|
||||
{/* Setup changes the theme while it is open, so it stays out of the remount above. Its
|
||||
colors are CSS variables and themed props, which update in place, and setup changes no
|
||||
font size. */}
|
||||
<OnboardingHost />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
@@ -237,21 +237,23 @@ const WEB_ENTER_ANIMATION_NAME = "paseo-modal-enter";
|
||||
|
||||
// The desktop card fades in; the dim lands at once, so the screen behind is never seen undimmed
|
||||
// while the dialog arrives. A CSS animation runs from the card's first paint, so a dialog that opens
|
||||
// on its own, like first-run setup, never pops in. Closing fades the whole overlay.
|
||||
// on its own, like first-run setup, never pops in. Closing fades the whole overlay. With reduced
|
||||
// motion the keyframes are never defined, so the card appears at once.
|
||||
function ensureWebEnterKeyframes() {
|
||||
if (!isWeb || typeof document === "undefined") return;
|
||||
if (document.getElementById(WEB_ENTER_ANIMATION_NAME)) return;
|
||||
const style = document.createElement("style");
|
||||
style.id = WEB_ENTER_ANIMATION_NAME;
|
||||
style.textContent = `@keyframes ${WEB_ENTER_ANIMATION_NAME} { from { opacity: 0; } to { opacity: 1; } }`;
|
||||
style.textContent = `@media (prefers-reduced-motion: no-preference) { @keyframes ${WEB_ENTER_ANIMATION_NAME} { from { opacity: 0; } to { opacity: 1; } } }`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
ensureWebEnterKeyframes();
|
||||
// The CSS animation props are not part of ViewStyle without the generated expo-env.d.ts.
|
||||
const WEB_ENTER_STYLE = {
|
||||
animationName: WEB_ENTER_ANIMATION_NAME,
|
||||
animationDuration: `${WEB_ENTER_DURATION_MS}ms`,
|
||||
animationTimingFunction: "ease-out",
|
||||
};
|
||||
} as unknown as ViewStyle;
|
||||
|
||||
/** The compact sheet's surface: Gorhom's `backgroundComponent` for every bottom sheet. */
|
||||
export function SheetBackground({ style }: BottomSheetBackgroundProps) {
|
||||
|
||||
Reference in New Issue
Block a user