Revert "fix(frontend): Consolidate Application Buttons to ShadcN Base Button Component" (#9575)

Reverts Significant-Gravitas/AutoGPT#9570

Some of the styling changes cause issues with existing parts of the
application, and I don't know if partially reverting would cause issues
with the other refactored components/elements.

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
This commit is contained in:
Reinier van der Leer
2025-03-05 16:42:26 +01:00
committed by GitHub
parent 595d2020c9
commit 37331d09e4
14 changed files with 139 additions and 292 deletions

View File

@@ -30,7 +30,7 @@ export default function Error({
again later or contact support if the issue persists.
</p>
<div className="mt-6 flex flex-row justify-center gap-4">
<Button onClick={reset} variant="secondary">
<Button onClick={reset} variant="outline">
Retry
</Button>
<Button>

View File

@@ -4,26 +4,26 @@
@layer base {
:root {
--background: 220 14.29% 95.88%;
--foreground: 240 3.7% 15.88%;
--background: 0 0% 99.6%; /* #FEFEFE */
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 3.7% 15.88%;
--primary: 240 3.7% 15.88%;
--primary-foreground: 0 0% 98.04%;
--secondary: 240 5.88% 90%;
--secondary-foreground: 240 3.7% 15.88%;
--muted: 240 5.88% 90%;
--muted-foreground: 240 5.2% 33.92%;
--accent: 220 13.04% 90.98%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.24% 60.2%;
--destructive-foreground: 0 0% 100%;
--border: 240 5.88% 90%;
--input: 240 4.88% 83.92%;
--ring: 216.92 22.29% 65.69%;
--radius: 1rem;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 262 83% 58%;
--accent-foreground: 0 0% 100%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 85%;
--ring: 240 5.9% 10%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;

View File

@@ -837,7 +837,11 @@ export const CustomNode = React.memo(
data={data.executionResults!.at(-1)?.data || {}}
/>
<div className="flex justify-end">
<Button variant="ghost" onClick={handleOutputClick}>
<Button
variant="ghost"
onClick={handleOutputClick}
className="border border-gray-300"
>
View More
</Button>
</div>

View File

@@ -1,5 +1,6 @@
import React from "react";
import { Clock, LogOut } from "lucide-react";
import React, { useState } from "react";
import { Button } from "./ui/button";
import { Clock, LogOut, ChevronLeft } from "lucide-react";
import { IconPlay, IconSquare } from "@/components/ui/icons";
import {
Tooltip,
@@ -7,7 +8,6 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { FaSpinner } from "react-icons/fa";
import { Button } from "@/components/ui/button";
interface PrimaryActionBarProps {
onClickAgentOutputs: () => void;
@@ -41,7 +41,12 @@ const PrimaryActionBar: React.FC<PrimaryActionBarProps> = ({
<div className={`flex gap-1 md:gap-4`}>
<Tooltip key="ViewOutputs" delayDuration={500}>
<TooltipTrigger asChild>
<Button onClick={onClickAgentOutputs} variant="outline">
<Button
className="flex items-center gap-2"
onClick={onClickAgentOutputs}
size="primary"
variant="outline"
>
<LogOut className="hidden h-5 w-5 md:flex" />
<span className="text-sm font-medium md:text-lg">
Agent Outputs{" "}
@@ -55,7 +60,9 @@ const PrimaryActionBar: React.FC<PrimaryActionBarProps> = ({
<Tooltip key="RunAgent" delayDuration={500}>
<TooltipTrigger asChild>
<Button
className="flex items-center gap-2"
onClick={runButtonOnClick}
size="primary"
style={{
background: isRunning ? "#DF4444" : "#7544DF",
opacity: isDisabled ? 0.5 : 1,
@@ -75,7 +82,9 @@ const PrimaryActionBar: React.FC<PrimaryActionBarProps> = ({
<Tooltip key="ScheduleAgent" delayDuration={500}>
<TooltipTrigger asChild>
<Button
className="flex items-center gap-2"
onClick={onClickScheduleButton}
size="primary"
disabled={isScheduling}
variant="outline"
data-id="primary-action-schedule-agent"

View File

@@ -57,14 +57,23 @@ const TallyPopupSimple = () => {
return (
<div className="fixed bottom-1 right-6 z-50 hidden select-none items-center gap-4 p-3 transition-all duration-300 ease-in-out md:flex">
{show_tutorial && <Button onClick={resetTutorial}>Tutorial</Button>}
{show_tutorial && (
<Button
variant="default"
onClick={resetTutorial}
className="mb-0 h-14 w-28 rounded-2xl bg-[rgba(65,65,64,1)] text-left font-inter text-lg font-medium leading-6"
>
Tutorial
</Button>
)}
<Button
size="icon"
className="h-14 w-14 rounded-full bg-[rgba(65,65,64,1)]"
variant="default"
data-tally-open="3yx2L0"
data-tally-emoji-text="👋"
data-tally-emoji-animation="wave"
>
<QuestionMarkCircledIcon />
<QuestionMarkCircledIcon className="h-14 w-14" />
<span className="sr-only">Reach Out</span>
</Button>
</div>

View File

@@ -2,7 +2,6 @@
import * as React from "react";
import { PublishAgentPopout } from "./composite/PublishAgentPopout";
import { Button } from "@/components/ui/button";
interface BecomeACreatorProps {
title?: string;
description?: string;
@@ -47,7 +46,16 @@ export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
</p>
<PublishAgentPopout
trigger={<Button onClick={handleButtonClick}>{buttonText}</Button>}
trigger={
<button
onClick={handleButtonClick}
className="inline-flex h-[48px] cursor-pointer items-center justify-center rounded-[38px] bg-neutral-800 px-8 py-3 transition-colors hover:bg-neutral-700 dark:bg-neutral-700 dark:hover:bg-neutral-600 md:h-[56px] md:px-10 md:py-4 lg:h-[68px] lg:px-12 lg:py-5"
>
<span className="whitespace-nowrap font-poppins text-base font-medium leading-normal text-neutral-50 md:text-lg md:leading-relaxed lg:text-xl lg:leading-7">
{buttonText}
</span>
</button>
}
/>
</div>
</div>

View File

@@ -5,12 +5,12 @@ import { useState } from "react";
import Image from "next/image";
import { Button } from "./Button";
import { IconPersonFill } from "@/components/ui/icons";
import { CreatorDetails, ProfileDetails } from "@/lib/autogpt-server-api/types";
import { Separator } from "@/components/ui/separator";
import useSupabase from "@/hooks/useSupabase";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { Button } from "@/components/ui/button";
export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => {
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -245,14 +245,21 @@ export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => {
<div className="flex h-[50px] items-center justify-end gap-3 py-8">
<Button
type="button"
variant="secondary"
className="font-circular h-[50px] rounded-[35px] bg-neutral-200 px-6 py-3 text-base font-medium text-neutral-800 transition-colors hover:bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-700 dark:text-neutral-200 dark:hover:border-neutral-600 dark:hover:bg-neutral-600"
onClick={() => {
setProfileData(profile);
}}
>
Cancel
</Button>
<Button type="submit" disabled={isSubmitting} onClick={submitForm}>
<Button
type="submit"
disabled={isSubmitting}
className="font-circular h-[50px] rounded-[35px] bg-neutral-800 px-6 py-3 text-base font-medium text-white transition-colors hover:bg-neutral-900 dark:bg-neutral-200 dark:text-neutral-900 dark:hover:bg-neutral-100"
onClick={submitForm}
>
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
</div>

View File

@@ -19,7 +19,7 @@ export default function AuthButton({
}: Props) {
return (
<Button
className="w-full"
className="mt-2 w-full self-stretch rounded-md bg-slate-900 px-4 py-2"
type={type}
disabled={isLoading || disabled}
onClick={onClick}

View File

@@ -155,6 +155,7 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
data-id="blocks-control-popover-trigger"
data-testid="blocks-control-blocks-button"
name="Blocks"
className="dark:hover:bg-slate-800"
>
<IconToyBrick />
</Button>

View File

@@ -61,6 +61,7 @@ export const ControlPanel = ({
data-id={`control-button-${index}`}
data-testid={`blocks-control-${control.label.toLowerCase()}-button`}
disabled={control.disabled || false}
className="dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
>
{control.icon}
<span className="sr-only">{control.label}</span>

View File

@@ -93,7 +93,7 @@ export const SaveControl = ({
data-testid="blocks-control-save-button"
name="Save"
>
<IconSave />
<IconSave className="dark:text-gray-300" />
</Button>
</PopoverTrigger>
</TooltipTrigger>
@@ -153,6 +153,7 @@ export const SaveControl = ({
</CardContent>
<CardFooter className="flex flex-col items-stretch gap-2">
<Button
className="w-full dark:bg-slate-700 dark:text-slate-100 dark:hover:bg-slate-800"
onClick={handleSave}
data-id="save-control-save-agent"
data-testid="save-control-save-agent-button"

View File

@@ -382,7 +382,7 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
{/* Form Actions */}
<div className="flex justify-end gap-4">
<Button
variant="secondary"
variant="outline"
type="button"
onClick={onCancel}
disabled={form.formState.isSubmitting}

View File

@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "@/components/ui/button";
import { Button } from "./button";
import { userEvent, within, expect } from "@storybook/test";
const meta = {
@@ -23,8 +23,7 @@ const meta = {
},
size: {
control: "select",
options: ["default", "sm", "lg", "icon"],
description: "Button size variants. The 'primary' size is deprecated.",
options: ["default", "sm", "lg", "primary", "icon"],
},
disabled: {
control: "boolean",
@@ -46,32 +45,6 @@ export const Default: Story = {
args: {
children: "Button",
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: /Button/i });
// Test default styling
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("rounded-full"),
);
// Test SVG styling is present
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("[&_svg]:size-4"),
);
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("[&_svg]:shrink-0"),
);
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("[&_svg]:pointer-events-none"),
);
},
};
export const Interactive: Story = {
@@ -84,33 +57,14 @@ export const Interactive: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: /Interactive Button/i });
// Test interaction
await userEvent.click(button);
await expect(button).toHaveFocus();
// Test styling matches the updated component
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("rounded-full"),
);
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("gap-2"),
);
// Test other key button styles
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("inline-flex items-center justify-center"),
);
},
};
export const Variants: Story = {
render: (args) => (
<div className="flex flex-wrap gap-4">
<div className="flex flex-wrap gap-2">
<Button {...args} variant="default">
Default
</Button>
@@ -135,8 +89,6 @@ export const Variants: Story = {
const canvas = within(canvasElement);
const buttons = canvas.getAllByRole("button");
await expect(buttons).toHaveLength(6);
// Test hover states
for (const button of buttons) {
await userEvent.hover(button);
await expect(button).toHaveAttribute(
@@ -144,81 +96,47 @@ export const Variants: Story = {
expect.stringContaining("hover:"),
);
}
// Test rounded-full styling on appropriate variants
const roundedVariants = [
"default",
"destructive",
"outline",
"secondary",
"ghost",
];
for (let i = 0; i < 5; i++) {
await expect(buttons[i]).toHaveAttribute(
"class",
expect.stringContaining("rounded-full"),
);
}
// Link variant should not have rounded-full
await expect(buttons[5]).not.toHaveAttribute(
"class",
expect.stringContaining("rounded-full"),
);
},
};
export const Sizes: Story = {
render: (args) => (
<div className="flex flex-wrap items-center gap-4">
<Button {...args} size="icon">
🚀
</Button>
<div className="flex flex-wrap items-center gap-2">
<Button {...args} size="sm">
Small
</Button>
<Button {...args}>Default</Button>
<Button {...args} size="default">
Default
</Button>
<Button {...args} size="lg">
Large
</Button>
<div className="flex flex-col items-start gap-2 rounded border p-4">
<p className="mb-2 text-xs text-muted-foreground">Deprecated Size:</p>
<Button {...args} size="primary">
Primary (deprecated)
</Button>
</div>
<Button {...args} size="primary">
Primary
</Button>
<Button {...args} size="icon">
🚀
</Button>
</div>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const buttons = canvas.getAllByRole("button");
await expect(buttons).toHaveLength(5);
// Test icon size
const iconButton = canvas.getByRole("button", { name: /🚀/i });
await expect(iconButton).toHaveAttribute(
"class",
expect.stringContaining("h-9 w-9"),
);
// Test specific size classes
const smallButton = canvas.getByRole("button", { name: /Small/i });
await expect(smallButton).toHaveAttribute(
"class",
expect.stringContaining("h-8"),
);
const defaultButton = canvas.getByRole("button", { name: /Default/i });
await expect(defaultButton).toHaveAttribute(
"class",
expect.stringContaining("h-9"),
);
const largeButton = canvas.getByRole("button", { name: /Large/i });
await expect(largeButton).toHaveAttribute(
"class",
expect.stringContaining("h-10"),
);
const sizes = ["sm", "default", "lg", "primary", "icon"];
const sizeClasses = [
"h-8 rounded-md px-3 text-xs",
"h-9 px-4 py-2",
"h-10 rounded-md px-8",
"md:h-14 md:w-44 rounded-2xl h-10 w-28",
"h-9 w-9",
];
buttons.forEach(async (button, index) => {
await expect(button).toHaveAttribute(
"class",
expect.stringContaining(sizeClasses[index]),
);
});
},
};
@@ -231,101 +149,39 @@ export const Disabled: Story = {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: /Disabled Button/i });
await expect(button).toBeDisabled();
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("disabled:pointer-events-none"),
);
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("disabled:opacity-50"),
);
await expect(button).toHaveStyle("pointer-events: none");
await expect(button).not.toHaveFocus();
},
};
export const WithIcon: Story = {
render: () => (
<div className="flex flex-col gap-4">
<div className="flex gap-4">
<Button>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg>
Icon Left
</Button>
<Button>
Icon Right
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg>
</Button>
</div>
<div>
<p className="mb-2 text-sm text-muted-foreground">
Icon with automatic gap spacing:
</p>
<Button>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg>
Button with Icon
</Button>
</div>
</div>
),
args: {
children: (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg>
Button with Icon
</>
),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const buttons = canvas.getAllByRole("button");
const icons = canvasElement.querySelectorAll("svg");
// Test that SVGs are present
await expect(icons.length).toBeGreaterThan(0);
// Test for gap-2 class for spacing
await expect(buttons[0]).toHaveAttribute(
"class",
expect.stringContaining("gap-2"),
);
// Test SVG styling from buttonVariants
await expect(buttons[0]).toHaveAttribute(
"class",
expect.stringContaining("[&_svg]:size-4"),
);
await expect(buttons[0]).toHaveAttribute(
"class",
expect.stringContaining("[&_svg]:shrink-0"),
);
await expect(buttons[0]).toHaveAttribute(
"class",
expect.stringContaining("[&_svg]:pointer-events-none"),
);
const button = canvas.getByRole("button", { name: /Button with Icon/i });
const icon = button.querySelector("svg");
await expect(icon).toBeInTheDocument();
await expect(button).toHaveTextContent("Button with Icon");
},
};
@@ -337,8 +193,10 @@ export const LoadingState: Story = {
render: (args) => (
<Button {...args}>
<svg
className="animate-spin"
className="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
@@ -357,56 +215,5 @@ export const LoadingState: Story = {
await expect(button).toBeDisabled();
const spinner = button.querySelector("svg");
await expect(spinner).toHaveClass("animate-spin");
// Test SVG styling from buttonVariants
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("[&_svg]:size-4"),
);
},
};
export const RoundedStyles: Story = {
render: () => (
<div className="flex flex-col gap-6">
<div>
<p className="mb-2 text-sm text-muted-foreground">
Default variants have rounded-full style:
</p>
<div className="flex gap-4">
<Button variant="default">Default</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
</div>
</div>
<div>
<p className="mb-2 text-sm text-muted-foreground">
Link variant maintains its original style:
</p>
<div className="flex gap-4">
<Button variant="link">Link</Button>
</div>
</div>
</div>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const buttons = canvas.getAllByRole("button");
// Test rounded-full on first 5 buttons
for (let i = 0; i < 5; i++) {
await expect(buttons[i]).toHaveAttribute(
"class",
expect.stringContaining("rounded-full"),
);
}
// Test that link variant doesn't have rounded-full
await expect(buttons[5]).not.toHaveAttribute(
"class",
expect.stringContaining("rounded-full"),
);
},
};

View File

@@ -5,28 +5,28 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-neutral-300",
{
variants: {
variant: {
default:
"bg-primary/90 text-primary-foreground shadow hover:bg-primary rounded-full",
"bg-neutral-900 text-neutral-50 shadow hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90",
destructive:
"bg-destructive/90 text-destructive-foreground shadow-sm hover:bg-destructive rounded-full",
"bg-red-500 text-neutral-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground rounded-full",
"border border-neutral-200 bg-white shadow-sm hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
secondary:
"bg-secondary/90 text-secondary-foreground shadow-sm hover:bg-secondary rounded-full",
ghost: "hover:bg-accent hover:text-accent-foreground rounded-full",
link: "text-primary underline-offset-4 hover:underline",
"bg-neutral-100 text-neutral-900 shadow-sm hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
ghost:
"hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
/** @deprecated Use default size with custom classes instead */
primary: "md:h-14 md:w-44 rounded-2xl h-10 w-28",
icon: "h-9 w-9",
},
},
defaultVariants: {