feat(frontend): add Badge component (#10244)

## Changes 🏗️

<img width="800" alt="Screenshot 2025-06-25 at 20 34 38"
src="https://github.com/user-attachments/assets/bfc90504-85b6-4178-9ace-2aa4d14f16b0"
/>
<br /><br />

- To match what is on the AutoGPT design system
- Unit tests commented because they depend on:
https://github.com/Significant-Gravitas/AutoGPT/pull/10243

## Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Run Storybook locally, Badge stories look good
This commit is contained in:
Ubbe
2025-06-26 09:21:20 +04:00
committed by GitHub
parent 443995d79a
commit 0b37263092
3 changed files with 193 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { Badge } from "./Badge";
const meta: Meta<typeof Badge> = {
title: "Atoms/Badge",
tags: ["autodocs"],
component: Badge,
parameters: {
layout: "centered",
docs: {
description: {
component:
"Badge component for displaying status information with different variants for success, error, and info states.",
},
},
},
argTypes: {
variant: {
control: "select",
options: ["success", "error", "info"],
description: "Badge variant that determines color scheme",
},
children: {
control: "text",
description: "Badge content",
},
className: {
control: "text",
description: "Additional CSS classes",
},
},
args: {
variant: "success",
children: "Success",
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Success: Story = {
args: {
variant: "success",
children: "Success",
},
};
export const Error: Story = {
args: {
variant: "error",
children: "Failed",
},
};
export const Info: Story = {
args: {
variant: "info",
children: "Stopped",
},
};
export const AllVariants: Story = {
render: renderAllVariants,
};
function renderAllVariants() {
return (
<div className="flex flex-wrap gap-4">
<Badge variant="success">Success</Badge>
<Badge variant="error">Failed</Badge>
<Badge variant="info">Stopped</Badge>
<Badge variant="info">Running</Badge>
<Badge variant="success">Completed</Badge>
<Badge variant="error">Error</Badge>
</div>
);
}

View File

@@ -0,0 +1,81 @@
// import { render, screen } from "@testing-library/react";
// import { describe, expect, it } from "vitest";
// import { Badge } from "./Badge";
// describe("Badge Component", () => {
// it("renders badge with content", () => {
// render(<Badge variant="success">Success</Badge>);
// expect(screen.getByText("Success")).toBeInTheDocument();
// });
// it("applies correct variant styles", () => {
// const { rerender } = render(<Badge variant="success">Success</Badge>);
// let badge = screen.getByText("Success");
// expect(badge).toHaveClass("bg-green-100", "text-green-800");
// rerender(<Badge variant="error">Error</Badge>);
// badge = screen.getByText("Error");
// expect(badge).toHaveClass("bg-red-100", "text-red-800");
// rerender(<Badge variant="info">Info</Badge>);
// badge = screen.getByText("Info");
// expect(badge).toHaveClass("bg-slate-100", "text-slate-800");
// });
// it("applies custom className", () => {
// render(
// <Badge variant="success" className="custom-class">
// Success
// </Badge>,
// );
// const badge = screen.getByText("Success");
// expect(badge).toHaveClass("custom-class");
// });
// it("renders as span element", () => {
// render(<Badge variant="success">Success</Badge>);
// const badge = screen.getByText("Success");
// expect(badge.tagName).toBe("SPAN");
// });
// it("renders children correctly", () => {
// render(
// <Badge variant="success">
// <span>Custom</span> Content
// </Badge>,
// );
// expect(screen.getByText("Custom")).toBeInTheDocument();
// expect(screen.getByText("Content")).toBeInTheDocument();
// });
// it("supports all badge variants", () => {
// const variants = ["success", "error", "info"] as const;
// variants.forEach((variant) => {
// const { unmount } = render(
// <Badge variant={variant} data-testid={`badge-${variant}`}>
// {variant}
// </Badge>,
// );
// expect(screen.getByTestId(`badge-${variant}`)).toBeInTheDocument();
// unmount();
// });
// });
// it("handles long text content", () => {
// render(
// <Badge variant="info">
// Very long text that should be handled properly by the component
// </Badge>,
// );
// const badge = screen.getByText(/Very long text/);
// expect(badge).toBeInTheDocument();
// expect(badge).toHaveClass("overflow-hidden", "text-ellipsis");
// });
// });

View File

@@ -0,0 +1,35 @@
import { cn } from "@/lib/utils";
type BadgeVariant = "success" | "error" | "info";
interface BadgeProps {
variant: BadgeVariant;
children: React.ReactNode;
className?: string;
}
const badgeVariants: Record<BadgeVariant, string> = {
success: "bg-green-100 text-green-800",
error: "bg-red-100 text-red-800",
info: "bg-slate-100 text-slate-800",
};
export function Badge({ variant, children, className }: BadgeProps) {
return (
<span
className={cn(
// Base styles from Figma
"inline-flex items-center gap-2 rounded-[45px] px-[9px] py-[3px]",
// Text styles
"font-['Geist'] text-xs font-medium leading-5",
// Text overflow handling
"overflow-hidden text-ellipsis",
// Variant styles
badgeVariants[variant],
className,
)}
>
{children}
</span>
);
}