mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
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:
committed by
GitHub
parent
595d2020c9
commit
37331d09e4
@@ -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>
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user