app: make integrations list scrollable (#17977)

This commit is contained in:
Parth Sareen
2026-08-24 17:46:45 -07:00
committed by GitHub
parent e2e82903fa
commit 60d83f8b0e
2 changed files with 9 additions and 113 deletions
+6 -12
View File
@@ -9,7 +9,6 @@ import {
default as Onboarding,
RunOllamaScreen,
shouldShowClaudeConnectedIntro,
terminalRowsForWindowHeight,
WelcomeScreen,
} from "./Onboarding";
import {
@@ -50,12 +49,6 @@ describe("Onboarding", () => {
expect(html).not.toContain("Skip");
});
it("shows more terminal integrations as the window gets taller", () => {
expect(terminalRowsForWindowHeight(400)).toBe(1);
expect(terminalRowsForWindowHeight(660)).toBe(4);
expect(terminalRowsForWindowHeight(960)).toBe(8);
});
it("renders the apps screen without browser platform globals", () => {
vi.stubGlobal("navigator", undefined);
try {
@@ -365,7 +358,7 @@ describe("Onboarding", () => {
expect(html).not.toContain("Sign up");
});
it("groups disconnected Claude with applications and terminal separately", () => {
it("groups disconnected Claude with a scrollable terminal list", () => {
const integrations: IntegrationStatuses = [
{
id: "claude-desktop",
@@ -463,10 +456,11 @@ describe("Onboarding", () => {
expect(html).not.toContain("ChatGPT");
expect(html).toContain("OpenCode");
expect(html).toContain("Terminal");
expect(html).toContain('aria-label="Show more apps"');
expect(html).toContain('aria-expanded="false"');
expect(html).toContain("grid-rows-[0fr]");
expect(html).not.toContain("Collapse");
expect(html).toContain("overflow-y-auto");
expect(html).not.toContain('aria-label="Show more apps"');
expect(html).not.toContain("aria-expanded");
expect(html).not.toContain("grid-rows-[0fr]");
expect(html).not.toContain("inert");
expect(html).toContain("/launch-icons/claude.svg");
expect(html).toContain("/launch-icons/claude-code.svg");
expect(html).not.toContain("<table");
+3 -101
View File
@@ -31,11 +31,7 @@ import {
ShieldCheckIcon,
Square2StackIcon,
} from "@heroicons/react/24/outline";
import {
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "@heroicons/react/20/solid";
import { CheckIcon } from "@heroicons/react/20/solid";
import {
useCallback,
useEffect,
@@ -54,10 +50,6 @@ type ClaudeConnectPhase =
| "launching"
| "disconnecting";
const MINIMUM_APP_WINDOW_HEIGHT = 660;
const TERMINAL_ROW_HEIGHT_WITH_GAP = 80;
const TERMINAL_LIST_RESERVED_HEIGHT = 296;
export function shouldShowClaudeConnectedIntro(status: ClaudeDesktopStatus) {
return status.connected && !status.startFailed && !status.used;
}
@@ -82,15 +74,6 @@ function getClaudeConnectionSummary() {
return getStatus?.() ?? Promise.resolve(null);
}
export function terminalRowsForWindowHeight(height: number): number {
return Math.max(
1,
Math.floor(
(height - TERMINAL_LIST_RESERVED_HEIGHT) / TERMINAL_ROW_HEIGHT_WITH_GAP,
),
);
}
interface ScreenProps {
isSigningIn: boolean;
signInError: string | null;
@@ -455,15 +438,6 @@ export function ConnectAppsScreen({
const screenMounted = useRef(true);
const [integrationStatuses, setIntegrationStatuses] =
useState<IntegrationStatuses | null>(initialIntegrations ?? null);
const [showAllIntegrations, setShowAllIntegrations] = useState(false);
const [collapsedIntegrationCount, setCollapsedIntegrationCount] = useState(
() =>
terminalRowsForWindowHeight(
typeof window === "undefined"
? MINIMUM_APP_WINDOW_HEIGHT
: window.innerHeight,
),
);
const [statusError, setStatusError] = useState(false);
useEffect(() => {
@@ -473,19 +447,6 @@ export function ConnectAppsScreen({
};
}, []);
useEffect(() => {
const updateCollapsedIntegrationCount = () => {
setCollapsedIntegrationCount(
terminalRowsForWindowHeight(window.innerHeight),
);
};
window.addEventListener("resize", updateCollapsedIntegrationCount);
return () => {
window.removeEventListener("resize", updateCollapsedIntegrationCount);
};
}, []);
useEffect(() => {
if (!copiedCommand) return;
@@ -939,15 +900,6 @@ export function ConnectAppsScreen({
const claudeInstalled =
claudeStatus?.installed ?? claudeIntegration?.installed ?? false;
const isConnectingClaude = claudePhase !== "idle";
const initialLaunchIntegrations = launchIntegrations.slice(
0,
collapsedIntegrationCount,
);
const additionalLaunchIntegrations = launchIntegrations.slice(
collapsedIntegrationCount,
);
const canToggleIntegrations =
launchIntegrations.length > collapsedIntegrationCount;
const claudeStatusLabel =
claudePhase === "installing"
? "Downloading…"
@@ -1102,60 +1054,10 @@ export function ConnectAppsScreen({
>
Terminal
</h2>
<div className="mt-2 overflow-hidden bg-white">
<div className="mt-2 bg-white">
<div className="space-y-2">
{initialLaunchIntegrations.map(launchIntegrationRow)}
{launchIntegrations.map(launchIntegrationRow)}
</div>
{canToggleIntegrations && (
<div
aria-hidden={!showAllIntegrations}
inert={!showAllIntegrations}
className={`grid transition-[grid-template-rows] ease-in-out motion-reduce:duration-0 ${
showAllIntegrations
? "duration-[750ms]"
: "duration-[825ms]"
} ${
showAllIntegrations
? "grid-rows-[1fr]"
: "grid-rows-[0fr]"
}`}
>
<div className="min-h-0 overflow-hidden">
<div className="space-y-2 pt-2">
{additionalLaunchIntegrations.map(
launchIntegrationRow,
)}
</div>
</div>
</div>
)}
{canToggleIntegrations && (
<button
type="button"
aria-label={
showAllIntegrations
? "Collapse apps"
: "Show more apps"
}
aria-expanded={showAllIntegrations}
className="mx-auto mt-3 flex h-10 w-10 items-center justify-center rounded-full text-neutral-600 transition-colors hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500"
onClick={() =>
setShowAllIntegrations((current) => !current)
}
>
{showAllIntegrations ? (
<ChevronUpIcon
aria-hidden="true"
className="h-5 w-5"
/>
) : (
<ChevronDownIcon
aria-hidden="true"
className="h-5 w-5"
/>
)}
</button>
)}
</div>
</section>
)}