Add Agent table

This commit is contained in:
SwiftyOS
2024-10-22 10:57:11 +02:00
parent 8b03477d2d
commit 88767a84d1
7 changed files with 440 additions and 71 deletions

View File

@@ -0,0 +1,81 @@
import type { Meta, StoryObj } from "@storybook/react";
import { AgentTable } from "./AgentTable";
import { AgentTableRowProps } from "./AgentTableRow";
import { userEvent, within, expect } from "@storybook/test";
const meta: Meta<typeof AgentTable> = {
title: "AGPT UI/Agent Table",
component: AgentTable,
tags: ["autodocs"],
};
export default meta;
type Story = StoryObj<typeof AgentTable>;
const sampleAgents: AgentTableRowProps[] = [
{
agentName: "Super Coder",
description: "An AI agent that writes clean, efficient code",
imageSrc:
"https://ddz4ak4pa3d19.cloudfront.net/cache/53/b2/53b2bc7d7900f0e1e60bf64ebf38032d.jpg",
dateSubmitted: "2023-05-15",
status: "Active",
runs: 1500,
rating: 4.8,
onEdit: () => console.log("Edit Super Coder"),
},
{
agentName: "Data Analyzer",
description: "Processes and analyzes large datasets with ease",
imageSrc:
"https://ddz4ak4pa3d19.cloudfront.net/cache/40/f7/40f7bc97c952f8df0f9c88d29defe8d4.jpg",
dateSubmitted: "2023-05-10",
status: "Active",
runs: 1200,
rating: 4.5,
onEdit: () => console.log("Edit Data Analyzer"),
},
{
agentName: "UI Designer",
description: "Creates beautiful and intuitive user interfaces",
imageSrc:
"https://ddz4ak4pa3d19.cloudfront.net/cache/14/9e/149ebb9014aa8c0097e72ed89845af0e.jpg",
dateSubmitted: "2023-05-05",
status: "Inactive",
runs: 800,
rating: 4.2,
onEdit: () => console.log("Edit UI Designer"),
},
];
export const Default: Story = {
args: {
agents: sampleAgents,
},
};
export const EmptyTable: Story = {
args: {
agents: [],
},
};
// Tests
export const InteractionTest: Story = {
...Default,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const editButtons = await canvas.findAllByText("Edit");
await userEvent.click(editButtons[0]);
// You would typically assert something here, but console.log is used in the mocked function
},
};
export const EmptyTableTest: Story = {
...EmptyTable,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const emptyMessage = canvas.getByText("No agents found");
expect(emptyMessage).toBeTruthy();
},
};

View File

@@ -0,0 +1,70 @@
// AgentTable.tsx
import * as React from "react";
import { AgentTableRow, AgentTableRowProps } from "./AgentTableRow";
import { AgentTableCard } from "./AgentTableCard";
export interface AgentTableProps {
agents: AgentTableRowProps[];
}
export const AgentTable: React.FC<AgentTableProps> = ({ agents }) => {
return (
<div className="mx-auto w-full max-w-[1095px]">
{/* Table for medium and larger screens */}
<div className="hidden md:block">
<table className="w-full">
<thead>
<tr className="border-b border-[#d9d9d9]">
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
Agent
</th>
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
Date submitted
</th>
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
Status
</th>
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
Runs
</th>
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
Reviews
</th>
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
Actions
</th>
</tr>
</thead>
<tbody>
{agents.length > 0 ? (
agents.map((agent, index) => (
<AgentTableRow key={index} {...agent} />
))
) : (
<tr>
<td
colSpan={6}
className="font-['PP Neue Montreal TT'] py-4 text-center text-base text-[#282828]"
>
No agents available. Create your first agent to get started!
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Cards for small screens */}
<div className="block md:hidden">
{agents.length > 0 ? (
agents.map((agent, index) => (
<AgentTableCard key={index} {...agent} />
))
) : (
<div className="font-['PP Neue Montreal TT'] py-4 text-center text-base text-[#707070]">
No agents available. Create your first agent to get started!
</div>
)}
</div>
</div>
);
};

View File

@@ -0,0 +1,64 @@
import type { Meta, StoryObj } from "@storybook/react";
import { AgentTableCard } from "./AgentTableCard";
import { userEvent, within, expect } from "@storybook/test";
const meta: Meta<typeof AgentTableCard> = {
title: "AGPT UI/Agent Table Card",
component: AgentTableCard,
tags: ["autodocs"],
};
export default meta;
type Story = StoryObj<typeof AgentTableCard>;
export const Default: Story = {
args: {
agentName: "Super Coder",
description: "An AI agent that writes clean, efficient code",
imageSrc:
"https://ddz4ak4pa3d19.cloudfront.net/cache/53/b2/53b2bc7d7900f0e1e60bf64ebf38032d.jpg",
dateSubmitted: "2023-05-15",
status: "Active",
runs: 1500,
rating: 4.8,
onEdit: () => console.log("Edit Super Coder"),
},
};
export const NoRating: Story = {
args: {
...Default.args,
rating: undefined,
},
};
export const NoRuns: Story = {
args: {
...Default.args,
runs: undefined,
},
};
export const InactiveAgent: Story = {
args: {
...Default.args,
status: "Inactive",
},
};
export const LongDescription: Story = {
args: {
...Default.args,
description:
"This is a very long description that should wrap to multiple lines. It contains detailed information about the agent and its capabilities.",
},
};
export const InteractionTest: Story = {
...Default,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const editButton = canvas.getByText("Edit");
await userEvent.click(editButton);
},
};

View File

@@ -0,0 +1,68 @@
// AgentCard.tsx
import * as React from "react";
import Image from "next/image";
import { Button } from "./Button";
import { IconStarFilled, IconEdit } from "../ui/icons";
import { Card } from "../ui/card";
import { AgentTableRowProps } from "./AgentTableRow";
export const AgentTableCard: React.FC<AgentTableRowProps> = ({
agentName,
description,
imageSrc,
dateSubmitted,
status,
runs,
rating,
onEdit,
}) => {
return (
<Card className="mb-4 p-4">
<div className="flex">
<div className="relative mr-4 h-20 w-20 overflow-hidden rounded-xl">
<Image
src={imageSrc}
alt={agentName}
layout="fill"
objectFit="cover"
/>
</div>
<div className="flex-1">
<h3 className="mb-2 font-neue text-lg font-medium tracking-tight text-[#272727]">
{agentName}
</h3>
<p className="mb-2 font-neue text-sm leading-tight tracking-tight text-[#282828]">
{description}
</p>
<div className="mb-2 font-neue text-sm leading-tight tracking-tight text-[#282828]">
<strong>Date submitted:</strong> {dateSubmitted}
</div>
<div className="mb-2 font-neue text-sm leading-tight tracking-tight text-[#282828]">
<strong>Status:</strong> {status}
</div>
{runs !== undefined && (
<div className="mb-2 font-neue text-sm leading-tight tracking-tight text-[#282828]">
<strong>Runs:</strong> {runs.toLocaleString()}
</div>
)}
{rating !== undefined && (
<div className="mb-2 flex items-center font-neue text-sm leading-tight tracking-tight text-[#282828]">
<strong>Rating:</strong>
<span className="ml-2">{rating.toFixed(1)}</span>
<IconStarFilled className="ml-1" />
</div>
)}
<Button
variant="outline"
size="sm"
className="mt-2 flex items-center gap-1"
onClick={onEdit}
>
<IconEdit />
<span>Edit</span>
</Button>
</div>
</div>
</Card>
);
};

View File

@@ -0,0 +1,79 @@
import * as React from "react";
import Image from "next/image";
import { Button } from "./Button";
import { IconStarFilled, IconEdit } from "../ui/icons";
export interface AgentTableRowProps {
agentName: string;
description: string;
imageSrc: string;
dateSubmitted: string;
status: string;
runs?: number;
rating?: number;
onEdit: () => void;
}
export const AgentTableRow: React.FC<AgentTableRowProps> = ({
agentName,
description,
imageSrc,
dateSubmitted,
status,
runs,
rating,
onEdit,
}) => {
return (
<tr className="border-b border-[#d9d9d9] py-4">
<td className="flex items-center">
<div className="relative my-4 mr-4 h-20 w-20 overflow-hidden rounded-xl sm:h-20 sm:w-[125px]">
<Image
src={imageSrc}
alt={agentName}
layout="fill"
objectFit="cover"
/>
</div>
<div className="max-w-[293px]">
<h3 className="mb-2 font-neue text-lg font-medium tracking-tight text-[#272727]">
{agentName}
</h3>
<p className="font-neue text-sm leading-tight tracking-tight text-[#282828]">
{description}
</p>
</div>
</td>
<td className="font-neue text-base leading-[21px] tracking-tight text-[#282828]">
{dateSubmitted}
</td>
<td className="font-neue text-base leading-[21px] tracking-tight text-[#282828]">
{status}
</td>
<td className="font-neue text-base leading-[21px] tracking-tight text-[#282828]">
{runs !== undefined ? runs.toLocaleString() : ""}
</td>
<td>
{rating !== undefined && (
<div className="flex items-center">
<span className="mr-2 font-neue text-base font-medium tracking-tight text-[#272727]">
{rating.toFixed(1)}
</span>
<IconStarFilled />
</div>
)}
</td>
<td>
<Button
variant="outline"
size="sm"
className="flex items-center gap-1"
onClick={onEdit}
>
<IconEdit />
<span className="font-neue text-sm">Edit</span>
</Button>
</td>
</tr>
);
};

View File

@@ -18,9 +18,7 @@ type Story = StoryObj<typeof meta>;
const defaultLinkGroups = [
{
links: [
{ text: "Integrations", href: "/integrations" },
],
links: [{ text: "Integrations", href: "/integrations" }],
},
{
links: [
@@ -67,8 +65,14 @@ export const LongLinkTexts: Story = {
linkGroups: [
{
links: [
{ text: "This is a very long link text that might wrap", href: "/long-link-1" },
{ text: "Another extremely long link text for testing purposes", href: "/long-link-2" },
{
text: "This is a very long link text that might wrap",
href: "/long-link-1",
},
{
text: "Another extremely long link text for testing purposes",
href: "/long-link-2",
},
],
},
...defaultLinkGroups,

View File

@@ -5,78 +5,81 @@ import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Menu } from "lucide-react";
interface SidebarLinkGroup {
links: {
text: string;
href: string;
}[];
links: {
text: string;
href: string;
}[];
}
interface SidebarProps {
linkGroups: SidebarLinkGroup[];
linkGroups: SidebarLinkGroup[];
}
export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
return (
<>
<Sheet>
<SheetTrigger asChild>
<button aria-label="Open sidebar menu" className="md:hidden fixed top-1/2 left-0 border border-neutral-500 bg-neutral-200 rounded-r-xl p-1">
<Menu className="h-6 w-6" />
<span className="sr-only">Open sidebar menu</span>
</button>
</SheetTrigger>
<SheetContent side="left" className="w-[280px] sm:w-[280px] p-0">
<div className="h-full bg-neutral-100">
<div className="p-6 flex flex-col justify-start items-start gap-[30px]">
<h2 className="text-neutral-900 text-xl font-medium font-neue leading-7 tracking-tight">
Creator Dashboard
</h2>
<Separator className="self-stretch" />
{linkGroups.map((group, groupIndex) => (
<React.Fragment key={groupIndex}>
{group.links.map((link, linkIndex) => (
<Link
key={linkIndex}
href={link.href}
className="self-stretch text-neutral-500 text-xl font-normal font-neue leading-7 tracking-tight hover:text-neutral-700"
>
{link.text}
</Link>
))}
{groupIndex < linkGroups.length - 1 && (
<Separator className="self-stretch" />
)}
</React.Fragment>
))}
</div>
</div>
</SheetContent>
</Sheet>
<div className="hidden md:block w-[280px] h-[934px] relative">
<div className="w-full h-full absolute left-0 top-0 bg-neutral-100" />
<div className="w-[210px] absolute left-10 top-[63px] flex flex-col justify-start items-start gap-[30px]">
<h2 className="self-stretch text-neutral-900 text-xl font-normal font-neue leading-7 tracking-tight hover:text-neutral-700">
Creator Dashboard
</h2>
return (
<>
<Sheet>
<SheetTrigger asChild>
<button
aria-label="Open sidebar menu"
className="fixed left-0 top-1/2 rounded-r-xl border border-neutral-500 bg-neutral-200 p-1 md:hidden"
>
<Menu className="h-6 w-6" />
<span className="sr-only">Open sidebar menu</span>
</button>
</SheetTrigger>
<SheetContent side="left" className="w-[280px] p-0 sm:w-[280px]">
<div className="h-full bg-neutral-100">
<div className="flex flex-col items-start justify-start gap-[30px] p-6">
<h2 className="font-neue text-xl font-medium leading-7 tracking-tight text-neutral-900">
Creator Dashboard
</h2>
<Separator className="self-stretch" />
{linkGroups.map((group, groupIndex) => (
<React.Fragment key={groupIndex}>
{group.links.map((link, linkIndex) => (
<Link
key={linkIndex}
href={link.href}
className="self-stretch font-neue text-xl font-normal leading-7 tracking-tight text-neutral-500 hover:text-neutral-700"
>
{link.text}
</Link>
))}
{groupIndex < linkGroups.length - 1 && (
<Separator className="self-stretch" />
{linkGroups.map((group, groupIndex) => (
<React.Fragment key={groupIndex}>
{group.links.map((link, linkIndex) => (
<Link
key={linkIndex}
href={link.href}
className="self-stretch text-neutral-600 text-xl font-normal font-neue leading-7 tracking-tight hover:text-neutral-700"
>
{link.text}
</Link>
))}
{groupIndex < linkGroups.length - 1 && (
<Separator className="self-stretch" />
)}
</React.Fragment>
))}
</div>
)}
</React.Fragment>
))}
</div>
</>
);
</div>
</SheetContent>
</Sheet>
<div className="relative hidden h-[934px] w-[280px] md:block">
<div className="absolute left-0 top-0 h-full w-full bg-neutral-100" />
<div className="absolute left-10 top-[63px] flex w-[210px] flex-col items-start justify-start gap-[30px]">
<h2 className="self-stretch font-neue text-xl font-normal leading-7 tracking-tight text-neutral-900 hover:text-neutral-700">
Creator Dashboard
</h2>
<Separator className="self-stretch" />
{linkGroups.map((group, groupIndex) => (
<React.Fragment key={groupIndex}>
{group.links.map((link, linkIndex) => (
<Link
key={linkIndex}
href={link.href}
className="self-stretch font-neue text-xl font-normal leading-7 tracking-tight text-neutral-600 hover:text-neutral-700"
>
{link.text}
</Link>
))}
{groupIndex < linkGroups.length - 1 && (
<Separator className="self-stretch" />
)}
</React.Fragment>
))}
</div>
</div>
</>
);
};