Merge pull request #1587 from AnishSarkar22/fix/ci-ui-changes

feat(playground): improve API Playground navigation and sidebar UX
This commit is contained in:
Anish Sarkar 2026-07-08 10:31:35 +05:30 committed by GitHub
commit 7faa58a953
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 301 additions and 99 deletions

View file

@ -91,12 +91,13 @@ export function ApiReference({
<div>
<h2 className="text-base font-semibold">API reference</h2>
<p className="mt-1 text-sm text-muted-foreground">
Create an API key, enable API access for this workspace, then use the examples below to call this endpoint.
Create an API key, enable API access for this workspace, then use the examples below to
call this endpoint.
</p>
</div>
<Tabs defaultValue="curl">
<TabsList className="h-auto flex-wrap">
<TabsList className="flex h-auto w-full flex-nowrap justify-start overflow-x-auto overflow-y-hidden">
{snippets.map((snippet) => (
<TabsTrigger key={snippet.id} value={snippet.id}>
{snippet.label}

View file

@ -1,13 +1,6 @@
"use client";
import {
Check,
Copy,
Hash,
Info,
Coins,
Timer,
} from "lucide-react";
import { Check, Coins, Copy, Hash, Info, Timer } from "lucide-react";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
@ -99,9 +92,9 @@ function EndpointCopyButton({ endpoint }: { endpoint: string }) {
variant="ghost"
size="sm"
onClick={handleCopy}
className="h-auto justify-start gap-2 rounded bg-muted/40 px-2 py-1 font-mono text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
className="h-auto max-w-full items-start justify-start gap-2 whitespace-normal rounded bg-muted/40 px-2 py-1 font-mono text-xs text-muted-foreground hover:bg-muted hover:text-foreground sm:whitespace-nowrap"
>
<code>{endpoint}</code>
<code className="min-w-0 break-all text-left sm:break-normal">{endpoint}</code>
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
<span className="sr-only">{copied ? "Copied endpoint" : "Copy endpoint"}</span>
</Button>
@ -243,8 +236,8 @@ export function PlaygroundRunner({ workspaceId, platform, verb }: PlaygroundRunn
className="font-medium text-foreground underline-offset-4 hover:underline"
>
Read docs
</Link>
{" "}for more info.
</Link>{" "}
for more info.
</>
) : null}
</p>
@ -280,17 +273,12 @@ export function PlaygroundRunner({ workspaceId, platform, verb }: PlaygroundRunn
</Button>
)}
</div>
</div>
<div className="space-y-3">
<h2 className="text-sm font-medium text-muted-foreground">Output</h2>
{isRunning ? (
<RunProgressPanel
latest={run.latest}
events={run.events}
elapsedMs={run.elapsedMs}
/>
<RunProgressPanel latest={run.latest} events={run.events} elapsedMs={run.elapsedMs} />
) : run.status === "cancelled" ? (
<div className="flex h-64 items-center justify-center rounded-md border border-border/60 px-4 text-center text-sm text-muted-foreground">
Run cancelled.

View file

@ -1,15 +1,14 @@
"use client";
import { History, LayoutGrid } from "lucide-react";
import { useSelectedLayoutSegments } from "next/navigation";
import type React from "react";
import { useMemo } from "react";
import {
type RoutedSectionGroup,
type RoutedSectionItem,
getPlaygroundNavGroups,
getPlaygroundNavItems,
getPlaygroundSelectedLabel,
RoutedSectionShell,
} from "@/components/layout";
import { PLAYGROUND_PLATFORMS } from "@/lib/playground/catalog";
interface PlaygroundLayoutShellProps {
workspaceId: string;
@ -20,41 +19,8 @@ export function PlaygroundLayoutShell({ workspaceId, children }: PlaygroundLayou
const segments = useSelectedLayoutSegments();
const base = `/dashboard/${workspaceId}/playground`;
const topLevelItems = useMemo<RoutedSectionItem[]>(
() => [
{
value: "overview",
label: "Overview",
href: base,
icon: <LayoutGrid className="h-4 w-4" />,
},
{
value: "runs",
label: "API Runs",
href: `${base}/runs`,
icon: <History className="h-4 w-4" />,
},
],
[base]
);
const providerGroups = useMemo<RoutedSectionGroup[]>(
() =>
PLAYGROUND_PLATFORMS.map((platform) => {
const Icon = platform.icon;
return {
value: platform.id,
label: platform.label,
icon: <Icon className="h-4 w-4 shrink-0" />,
items: platform.verbs.map((verb) => ({
value: `${platform.id}/${verb.verb}`,
label: verb.label,
href: `${base}/${platform.id}/${verb.verb}`,
})),
};
}),
[base]
);
const topLevelItems = useMemo(() => getPlaygroundNavItems(base), [base]);
const providerGroups = useMemo(() => getPlaygroundNavGroups(base), [base]);
const activeValue =
segments.length >= 2
@ -63,7 +29,7 @@ export function PlaygroundLayoutShell({ workspaceId, children }: PlaygroundLayou
? segments[0]
: "overview";
const selectedLabel = getSelectedLabel(activeValue, topLevelItems, providerGroups);
const selectedLabel = getPlaygroundSelectedLabel(activeValue, topLevelItems, providerGroups);
return (
<RoutedSectionShell
@ -73,23 +39,9 @@ export function PlaygroundLayoutShell({ workspaceId, children }: PlaygroundLayou
activeValue={activeValue}
selectedLabel={selectedLabel}
mobileNav="drawer"
desktopNav={false}
>
{children}
</RoutedSectionShell>
);
}
function getSelectedLabel(
activeValue: string,
items: RoutedSectionItem[],
groups: RoutedSectionGroup[]
): string {
const topLevelItem = items.find((item) => item.value === activeValue);
if (topLevelItem) return topLevelItem.label;
const group = groups.find((item) => item.items.some((child) => child.value === activeValue));
const child = group?.items.find((item) => item.value === activeValue);
if (group && child) return `${group.label}: ${child.label}`;
return "API Playground";
}