feat(web): unify panel loading states with grid spinner (#1142)
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { GridSpinner } from "@/components/grid-spinner";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type PanelLoadingStateProps = {
|
||||
label: string;
|
||||
/** Override the outer container layout (e.g. absolute overlay for the terminal). */
|
||||
className?: string;
|
||||
testId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared panel loading placeholder: a grid spinner above a label, matching the
|
||||
* terminal's "Connecting terminal..." look. Used by the file browser, terminal,
|
||||
* and agent preview panels so loading states stay visually consistent.
|
||||
*/
|
||||
export function PanelLoadingState({ label, className, testId }: PanelLoadingStateProps) {
|
||||
return (
|
||||
<div
|
||||
data-testid={testId}
|
||||
className={cn("flex h-full w-full items-start justify-center pt-12", className)}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-3 text-muted-foreground">
|
||||
<GridSpinner />
|
||||
<span className="text-sm">{label}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { IconLoader2 } from "@tabler/icons-react";
|
||||
import { PanelLoadingState } from "@/components/panel-loading-state";
|
||||
import type { FileTreeNode } from "@/lib/types/backend";
|
||||
|
||||
type RenderSessionOrLoadStateInput = {
|
||||
@@ -31,18 +31,10 @@ export function renderSessionOrLoadState({
|
||||
);
|
||||
}
|
||||
if ((loadState === "loading" || isLoadingTree) && !tree) {
|
||||
return <div className="p-4 text-sm text-muted-foreground">Loading files...</div>;
|
||||
return <PanelLoadingState label="Loading files..." />;
|
||||
}
|
||||
if (loadState === "waiting") {
|
||||
return (
|
||||
<div
|
||||
data-testid="file-tree-waiting"
|
||||
className="p-4 text-sm text-muted-foreground flex items-center gap-2"
|
||||
>
|
||||
<IconLoader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Preparing workspace...
|
||||
</div>
|
||||
);
|
||||
return <PanelLoadingState testId="file-tree-waiting" label="Preparing workspace..." />;
|
||||
}
|
||||
if (loadState === "manual") {
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FitAddon } from "@xterm/addon-fit";
|
||||
import { AttachAddon } from "@xterm/addon-attach";
|
||||
import { WebglAddon } from "@xterm/addon-webgl";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
import { GridSpinner } from "@/components/grid-spinner";
|
||||
import { PanelLoadingState } from "@/components/panel-loading-state";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import { useSession } from "@/hooks/domains/session/use-session";
|
||||
import { useSessionAgentctl } from "@/hooks/domains/session/use-session-agentctl";
|
||||
@@ -277,17 +277,11 @@ export function PassthroughTerminal(props: PassthroughTerminalProps) {
|
||||
</div>
|
||||
<TerminalSearchBar search={search} />
|
||||
{!isConnected && (
|
||||
<div
|
||||
data-testid="passthrough-loading"
|
||||
className="absolute inset-0 flex items-start justify-center pt-12 bg-background"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-3 text-muted-foreground">
|
||||
<GridSpinner />
|
||||
<span className="text-sm">
|
||||
{mode === "agent" ? "Preparing workspace..." : "Connecting terminal..."}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<PanelLoadingState
|
||||
testId="passthrough-loading"
|
||||
className="absolute inset-0 bg-background"
|
||||
label={mode === "agent" ? "Preparing workspace..." : "Connecting terminal..."}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { IconLoader2 } from "@tabler/icons-react";
|
||||
import { Button } from "@kandev/ui/button";
|
||||
import { AgentLogo } from "@/components/agent-logo";
|
||||
import { GridSpinner } from "@/components/grid-spinner";
|
||||
import { PanelLoadingState } from "@/components/panel-loading-state";
|
||||
import { SessionTabs, type SessionTab } from "@/components/session-tabs";
|
||||
import { useAppStore } from "@/components/state-provider";
|
||||
import { useToast } from "@/components/toast-provider";
|
||||
@@ -177,15 +177,7 @@ function RunningSpinner() {
|
||||
}
|
||||
|
||||
function PreviewLoadingState({ label }: { label: string }) {
|
||||
return (
|
||||
<div
|
||||
className="flex h-full items-center justify-center gap-2 text-sm text-muted-foreground"
|
||||
data-testid="preview-loading-state"
|
||||
>
|
||||
<IconLoader2 className="h-4 w-4 animate-spin" />
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
return <PanelLoadingState testId="preview-loading-state" label={label} />;
|
||||
}
|
||||
|
||||
function PreviewEmptyState() {
|
||||
|
||||
Reference in New Issue
Block a user