History

Fix toolkit loading skeleton

Rhys Sullivan committed 4 months ago

Commit 07cbee21 parent 2836ce22 files · +149 −45Browse files

Unified · Split

2 files
  1. e2e/selfhost/toolkits-ui.test.ts +3 −0
  2. packages/plugins/toolkits/src/page.tsx +146 −45
e2e/selfhost/toolkits-ui.test.ts+3−0
@@ -56,6 +56,9 @@ scenario( · 55 unmodified lines
5656 await page.getByRole("heading", { name: "Toolkits" }).waitFor();
5757 await page.getByRole("heading", { name: "Workspace" }).waitFor();
5858 await page.getByRole("heading", { name: "Personal" }).waitFor();
59+ await page.getByRole("button", { name: "Add workspace toolkit" }).waitFor();
60+ await page.getByRole("button", { name: "Add personal toolkit" }).waitFor();
61+ expect(await page.locator('main [data-slot="skeleton"]').count()).toBe(0);
5962 expect(await page.getByLabel("New toolkit").count()).toBe(0);
6063 });
6164
packages/plugins/toolkits/src/page.tsx+146−45
@@ -1114,15 +1114,110 @@ function ToolkitWorkspace(props: { · 1113 unmodified lines
11141114 );
11151115 }
11161116
1117−function ToolkitsPageSkeleton() {
1117+function ToolkitTileSkeleton(props: { index: number }) {
11181118 return (
1119− <div className="flex min-h-0 flex-1 overflow-hidden">
1120− <div className="flex-1 p-4">
1121− <Skeleton className="h-8 w-full" />
1122− <div className="grid grid-cols-1 gap-3 pt-4 sm:grid-cols-2 md:grid-cols-3">
1123− {Array.from({ length: 6 }).map((_, index) => (
1124− <Skeleton key={index} className="h-36 w-full rounded-md" />
1125− ))}
1119+ <div
1120+ className="flex min-h-36 min-w-0 flex-col justify-between rounded-md border border-border/60 bg-card/35 p-3"
1121+ style={toolkitCardStyle}
1122+ aria-hidden="true"
1123+ >
1124+ <div className="flex min-w-0 items-start gap-3">
1125+ <Skeleton className="size-9 shrink-0 rounded-md" />
1126+ <div className="min-w-0 flex-1 space-y-2 pt-0.5">
1127+ <Skeleton className="h-4" style={{ width: `${52 + ((props.index * 17) % 28)}%` }} />
1128+ <Skeleton className="h-3" style={{ width: `${34 + ((props.index * 11) % 24)}%` }} />
1129+ </div>
1130+ </div>
1131+ <div className="space-y-2">
1132+ <Skeleton className="h-3 w-24" />
1133+ <div className="flex min-w-0 items-center gap-1.5">
1134+ <Skeleton className="h-5 w-14 rounded-full" />
1135+ <Skeleton className="h-5 w-20 rounded-full" />
1136+ </div>
1137+ </div>
1138+ </div>
1139+ );
1140+}
1141+
1142+function ToolkitSectionSkeleton(props: { title: string; offset: number }) {
1143+ return (
1144+ <section className="space-y-3" aria-label={`Loading ${props.title.toLowerCase()} toolkits`}>
1145+ <div className="border-b border-border/60 pb-2">
1146+ <h2 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
1147+ {props.title}
1148+ </h2>
1149+ </div>
1150+
1151+ <div className="grid content-start grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3">
1152+ {Array.from({ length: 3 }).map((_, index) => (
1153+ <ToolkitTileSkeleton key={index} index={props.offset + index} />
1154+ ))}
1155+ </div>
1156+ </section>
1157+ );
1158+}
1159+
1160+function ToolkitGridSkeleton() {
1161+ return (
1162+ <div className="min-h-0 flex-1 overflow-y-auto">
1163+ <div className="w-full space-y-7 px-4 py-4" style={toolkitGridContainerStyle}>
1164+ <ToolkitSectionSkeleton title="Workspace" offset={0} />
1165+ <ToolkitSectionSkeleton title="Personal" offset={3} />
1166+ </div>
1167+ </div>
1168+ );
1169+}
1170+
1171+function ToolkitDetailSkeleton() {
1172+ return (
1173+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
1174+ <div className="shrink-0 border-b border-border/60 bg-background/95 px-4 py-3">
1175+ <Skeleton className="mb-3 h-7 w-20 rounded-md" />
1176+ <div className="space-y-2">
1177+ <div className="flex min-w-0 items-center gap-2">
1178+ <Skeleton className="h-4 w-40" />
1179+ <Skeleton className="h-5 w-16 rounded-full" />
1180+ <Skeleton className="h-3 w-12" />
1181+ </div>
1182+ <Skeleton className="h-6 w-full max-w-md" />
1183+ </div>
1184+ </div>
1185+
1186+ <div className="flex min-h-0 flex-1 overflow-hidden">
1187+ <div
1188+ className="flex min-h-0 shrink-0 flex-col border-r border-border/60"
1189+ style={toolkitToolTreeStyle}
1190+ >
1191+ <div className="border-b border-border/60 px-3 py-3">
1192+ <div className="flex items-center justify-between gap-3">
1193+ <div className="space-y-2">
1194+ <Skeleton className="h-3 w-28" />
1195+ <Skeleton className="h-3 w-14" />
1196+ </div>
1197+ <Skeleton className="h-8 w-16 rounded-md" />
1198+ </div>
1199+ </div>
1200+ <div className="space-y-1 p-3">
1201+ {Array.from({ length: 9 }).map((_, index) => (
1202+ <div key={index} className="flex items-center gap-2 rounded-md px-2 py-1.5">
1203+ <Skeleton className="size-4 shrink-0 rounded" />
1204+ <Skeleton className="h-3.5" style={{ width: `${48 + ((index * 13) % 38)}%` }} />
1205+ </div>
1206+ ))}
1207+ </div>
1208+ </div>
1209+ <div className="flex min-w-0 flex-1 flex-col gap-5 overflow-hidden p-6">
1210+ <div className="space-y-2">
1211+ <Skeleton className="h-5 w-52" />
1212+ <Skeleton className="h-4 w-full max-w-lg" />
1213+ <Skeleton className="h-4 w-full max-w-sm" />
1214+ </div>
1215+ <div className="space-y-3">
1216+ <Skeleton className="h-4 w-24" />
1217+ <Skeleton className="h-24 w-full rounded-md" />
1218+ <Skeleton className="h-4 w-28" />
1219+ <Skeleton className="h-24 w-full rounded-md" />
1220+ </div>
11261221 </div>
11271222 </div>
11281223 </div>
@@ -1193,7 +1288,7 @@ function ToolkitDetailView(props: { · 64 unmodified lines
11931288 return <div className="p-6 text-sm text-destructive">Failed to load toolkit</div>;
11941289 }
11951290 if (!AsyncResult.isSuccess(policies) || !AsyncResult.isSuccess(connections)) {
1196− return <ToolkitsPageSkeleton />;
1291+ return <ToolkitDetailSkeleton />;
11971292 }
11981293
11991294 return (
@@ -1234,6 +1329,10 @@ export function ToolkitsPage(props: PluginPageProps) { · 34 unmodified lines
12341329 const integrationRows = AsyncResult.isSuccess(integrations)
12351330 ? (integrations.value as readonly Integration[])
12361331 : [];
1332+ const toolkitsReady = AsyncResult.isSuccess(toolkits);
1333+ const toolkitsFailed = AsyncResult.isFailure(toolkits);
1334+ const toolsReady = AsyncResult.isSuccess(tools);
1335+ const toolsFailed = AsyncResult.isFailure(tools);
12371336 const navigateToIndex = () =>
12381337 navigate({
12391338 to: "/{-$orgSlug}/plugins/$pluginId/$",
@@ -1270,46 +1369,48 @@ export function ToolkitsPage(props: PluginPageProps) { · 30 unmodified lines
12701369 </div>
12711370 ) : null}
12721371
1273− {!AsyncResult.isSuccess(toolkits) || !AsyncResult.isSuccess(tools) ? (
1274− AsyncResult.isFailure(toolkits) || AsyncResult.isFailure(tools) ? (
1372+ {!toolkitsReady ? (
1373+ toolkitsFailed ? (
12751374 <div className="p-6 text-sm text-destructive">Failed to load toolkits</div>
12761375 ) : (
1277− <ToolkitsPageSkeleton />
1376+ <ToolkitGridSkeleton />
1377+ )
1378+ ) : selectedToolkit ? (
1379+ toolsFailed ? (
1380+ <div className="p-6 text-sm text-destructive">Failed to load toolkit tools</div>
1381+ ) : !toolsReady ? (
1382+ <ToolkitDetailSkeleton />
1383+ ) : (
1384+ <ToolkitDetailView
1385+ toolkit={selectedToolkit}
1386+ tools={toolRows}
1387+ integrations={integrationRows}
1388+ integrationPlugins={integrationPlugins}
1389+ orgSlug={params.orgSlug}
1390+ onBack={() => void navigateToIndex()}
1391+ onRemoveToolkit={removeToolkitHandler}
1392+ />
12781393 )
1394+ ) : selectedToolkitSlug !== null ? (
1395+ <div className="flex min-h-0 flex-1 flex-col p-6">
1396+ <Button
1397+ type="button"
1398+ variant="ghost"
1399+ size="xs"
1400+ onClick={() => void navigateToIndex()}
1401+ className="mb-4 w-fit -ml-1 text-muted-foreground"
1402+ >
1403+ <ArrowLeftIcon className="size-3.5" />
1404+ Toolkits
1405+ </Button>
1406+ <div className="text-sm text-muted-foreground">Toolkit not found</div>
1407+ </div>
12791408 ) : (
1280− <>
1281− {selectedToolkit ? (
1282− <ToolkitDetailView
1283− toolkit={selectedToolkit}
1284− tools={toolRows}
1285− integrations={integrationRows}
1286− integrationPlugins={integrationPlugins}
1287− orgSlug={params.orgSlug}
1288− onBack={() => void navigateToIndex()}
1289− onRemoveToolkit={removeToolkitHandler}
1290− />
1291− ) : selectedToolkitSlug !== null ? (
1292− <div className="flex min-h-0 flex-1 flex-col p-6">
1293− <Button
1294− type="button"
1295− variant="ghost"
1296− size="xs"
1297− onClick={() => void navigateToIndex()}
1298− className="mb-4 w-fit -ml-1 text-muted-foreground"
1299− >
1300− <ArrowLeftIcon className="size-3.5" />
1301− Toolkits
1302− </Button>
1303− <div className="text-sm text-muted-foreground">Toolkit not found</div>
1304− </div>
1305− ) : (
1306− <ToolkitGrid
1307− pluginId={props.pluginId}
1308− toolkits={toolkitRows}
1309− onCreate={createToolkitHandler}
1310− />
1311− )}
1312− </>
1409+ <ToolkitGrid
1410+ pluginId={props.pluginId}
1411+ toolkits={toolkitRows}
1412+ onCreate={createToolkitHandler}
1413+ />
13131414 )}
13141415 </div>
13151416 );