fix(frontend): design system feedback (#10253)

## Changes 🏗️

I had a catch-up yesterday with Olivia, we agreed to implement these
fixes on our 👶🏽 component library

### 1. Update button loading states

<img width="600" alt="Screenshot 2025-06-27 at 15 13 12"
src="https://github.com/user-attachments/assets/a9ec8d0b-5f2c-4675-ae38-41ce81a3d699"
/>

When `loading`, all buttons will have a grey background and white text,
except if it is the `ghost` variant.

### 2. Update new border radius tokens

<img width="300" alt="Screenshot 2025-06-27 at 15 15 46"
src="https://github.com/user-attachments/assets/9cc7ea52-420c-4d61-b682-0cffe1843ad8"
/>

Updated the `border-radius` scale to the one in Figma.
[Reference](https://www.figma.com/design/nO9NFynNuicLtkiwvOxrbz/AutoGPT-Design-System?node-id=634-8255&t=hGgDUzLoLdSqpJIe-1).

### 3. Add `secondary` link variant

<img width="319" alt="Screenshot 2025-06-27 at 15 13 02"
src="https://github.com/user-attachments/assets/dc307d32-2f35-4110-bc7e-0ef6dd3d78e3"
/>

We have 2 types of links, `primary` ( _without underline but shows on
hover_ ) and `secondary` ( _with underline_ )

## 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, stories look good

---------

Co-authored-by: Abhimanyu Yadav <122007096+Abhi1992002@users.noreply.github.com>
This commit is contained in:
Ubbe
2025-06-30 13:56:55 +04:00
committed by GitHub
parent f3202fa776
commit a0a7129081
6 changed files with 260 additions and 191 deletions

View File

@@ -94,26 +94,48 @@ export const Ghost: Story = {
},
};
export const Link: Story = {
args: {
variant: "link",
children: "Add to library",
},
};
// Loading states
export const Loading: Story = {
args: {
loading: true,
children: "Processing...",
children: "Saving...",
},
parameters: {
docs: {
description: {
story:
"Use contextual loading text that reflects the action being performed (e.g., 'Computing...', 'Processing...', 'Saving...', 'Uploading...', 'Deleting...')",
},
},
},
};
export const LoadingSecondary: Story = {
export const LoadingGhost: Story = {
args: {
variant: "secondary",
variant: "ghost",
loading: true,
children: "Loading...",
children: "Fetching data...",
},
parameters: {
docs: {
description: {
story:
"Always show contextual loading text that describes what's happening. Avoid generic 'Loading...' text when possible.",
},
},
},
};
// Contextual loading examples
export const ContextualLoadingExamples: Story = {
render: renderContextualLoadingExamples,
parameters: {
docs: {
description: {
story:
"Examples of contextual loading text. Always use specific action-based text rather than generic 'Loading...' to give users clear feedback about what's happening.",
},
},
},
};
@@ -163,6 +185,65 @@ export const AllVariants: Story = {
};
// Render functions as function declarations
function renderContextualLoadingExamples() {
return (
<div className="space-y-6">
<div>
<h3 className="mb-4 text-base font-medium text-neutral-900">
Good Examples - Contextual Loading Text
</h3>
<div className="flex flex-wrap gap-4">
<Button variant="primary" loading>
Saving...
</Button>
<Button variant="primary" loading>
Computing...
</Button>
<Button variant="primary" loading>
Processing...
</Button>
<Button variant="primary" loading>
Uploading...
</Button>
<Button variant="destructive" loading>
Deleting...
</Button>
<Button variant="secondary" loading>
Generating...
</Button>
<Button variant="ghost" loading>
Fetching data...
</Button>
<Button variant="outline" loading>
Analyzing...
</Button>
</div>
</div>
<div>
<h3 className="mb-4 text-base font-medium text-red-600">
Avoid - Generic Loading Text
</h3>
<div className="flex flex-wrap gap-4">
<Button variant="primary" loading disabled>
Loading...
</Button>
<Button variant="secondary" loading disabled>
Please wait...
</Button>
<Button variant="outline" loading disabled>
Working...
</Button>
</div>
<p className="mt-2 text-sm text-neutral-600">
These examples are disabled to show what NOT to do. Use specific
action-based text instead.
</p>
</div>
</div>
);
}
function renderSmallButtons() {
return (
<div className="flex flex-wrap gap-4">
@@ -405,6 +486,9 @@ function renderAllVariants() {
<Button variant="secondary" size="small">
Save
</Button>
<Button variant="secondary" size="small" loading>
Loading
</Button>
<Button variant="secondary" size="small" disabled>
Disabled
</Button>
@@ -427,6 +511,9 @@ function renderAllVariants() {
<Button variant="destructive" size="small">
Save
</Button>
<Button variant="destructive" size="small" loading>
Loading
</Button>
<Button variant="destructive" size="small" disabled>
Disabled
</Button>
@@ -449,6 +536,9 @@ function renderAllVariants() {
<Button variant="outline" size="small">
Save
</Button>
<Button variant="outline" size="small" loading>
Loading
</Button>
<Button variant="outline" size="small" disabled>
Disabled
</Button>
@@ -471,6 +561,9 @@ function renderAllVariants() {
<Button variant="ghost" size="small">
Save
</Button>
<Button variant="ghost" size="small" loading>
Loading
</Button>
<Button variant="ghost" size="small" disabled>
Disabled
</Button>
@@ -492,17 +585,6 @@ function renderAllVariants() {
Other button types
</h2>
<div className="flex gap-20">
{/* Link */}
<div className="flex flex-col gap-5">
<div className="font-['Geist'] text-base font-medium text-zinc-800">
Link
</div>
<div className="flex flex-col gap-8">
<Button variant="link">Add to library</Button>
</div>
</div>
{/* Icon */}
<div className="flex flex-col gap-5">
<div className="font-['Geist'] text-base font-medium text-zinc-800">
Icon

View File

@@ -19,7 +19,6 @@ const extendedButtonVariants = cva(
"bg-transparent border-zinc-700 text-black hover:bg-zinc-100 hover:border-zinc-700 rounded-full disabled:border-zinc-200 disabled:text-zinc-200 disabled:opacity-1",
ghost:
"bg-transparent border-transparent text-black hover:bg-zinc-50 hover:border-zinc-50 rounded-full disabled:text-zinc-200 disabled:opacity-1",
link: "bg-transparent border-transparent text-black hover:underline rounded-none p-0 h-auto min-w-auto disabled:opacity-1",
icon: "bg-white text-black border border-zinc-600 hover:bg-zinc-100 rounded-[96px] disabled:opacity-1",
},
size: {
@@ -57,6 +56,30 @@ function Button({
}: ButtonProps) {
const isDisabled = disabled;
if (loading) {
return variant === "ghost" ? (
<button
className={cn(
extendedButtonVariants({ variant, size, className }),
"pointer-events-none",
)}
>
<CircleNotchIcon className="h-4 w-4 animate-spin" weight="bold" />
{children}
</button>
) : (
<button
className={cn(
extendedButtonVariants({ variant: "primary", size, className }),
"pointer-events-none border-zinc-500 bg-zinc-500 text-white",
)}
>
<CircleNotchIcon className="h-4 w-4 animate-spin" weight="bold" />
{children}
</button>
);
}
return (
<button
className={cn(

View File

@@ -20,6 +20,12 @@ const meta: Meta<typeof Link> = {
control: "text",
description: "The URL or path to link to",
},
variant: {
control: "select",
options: ["primary", "secondary"],
description:
"Link style variant - primary shows underline on hover, secondary always shows underline",
},
isExternal: {
control: "boolean",
description: "Whether this is an external link (opens in new tab)",
@@ -36,6 +42,7 @@ const meta: Meta<typeof Link> = {
args: {
href: "/example",
children: "Add to library",
variant: "primary",
isExternal: false,
},
};
@@ -43,6 +50,26 @@ const meta: Meta<typeof Link> = {
export default meta;
type Story = StoryObj<typeof meta>;
// Primary variant (default - underline on hover)
export const Primary: Story = {
args: {
href: "/library",
children: "Add to library",
variant: "primary",
isExternal: false,
},
};
// Secondary variant (always underlined)
export const Secondary: Story = {
args: {
href: "/library",
children: "Add to library",
variant: "secondary",
isExternal: false,
},
};
// Basic internal link
export const Internal: Story = {
args: {
@@ -74,115 +101,22 @@ export const WithIcon: Story = {
},
};
// Different link types
export const LinkTypes: Story = {
render: renderLinkTypes,
};
// Links in context
export const InContext: Story = {
render: renderInContext,
// Variant comparison
export const AllVariants: Story = {
render: renderVariantComparison,
};
// Render functions as function declarations
function renderLinkTypes() {
function renderVariantComparison() {
return (
<div className="space-y-6">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Internal Links</h3>
<div className="flex flex-wrap gap-4">
<Link href="/dashboard">Dashboard</Link>
<Link href="/settings">Settings</Link>
<Link href="/profile">Profile</Link>
<Link href="/library">Add to library</Link>
</div>
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold">External Links</h3>
<div className="flex flex-wrap gap-4">
<Link
href="https://github.com/Significant-Gravitas/AutoGPT"
isExternal
>
GitHub Repository
</Link>
<Link href="https://docs.autogpt.net" isExternal>
Documentation
</Link>
<Link href="https://discord.gg/autogpt" isExternal>
Discord Community
</Link>
</div>
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold">Links with Icons</h3>
<div className="flex flex-wrap gap-4">
<Link href="https://docs.autogpt.net" isExternal>
<span className="inline-flex items-center gap-1">
Documentation <ExternalLink className="h-3 w-3" />
</span>
</Link>
<Link href="https://github.com" isExternal>
<span className="inline-flex items-center gap-1">
GitHub <ExternalLink className="h-3 w-3" />
</span>
</Link>
</div>
</div>
</div>
);
}
function renderInContext() {
return (
<div className="max-w-2xl space-y-6">
<div className="space-y-4">
<h3 className="text-lg font-semibold">In Paragraph Text</h3>
<p className="text-sm text-gray-600">
This is a paragraph with an{" "}
<Link href="/internal-page">internal link</Link> and an{" "}
<Link href="https://example.com" isExternal>
external link
</Link>{" "}
to demonstrate how links appear in flowing text. The styling is
consistent with our design system.
</p>
</div>
<div className="space-y-4">
<h3 className="text-lg font-semibold">In Navigation</h3>
<nav className="flex space-x-6">
<Link href="/dashboard">Dashboard</Link>
<Link href="/agents">Agents</Link>
<Link href="/library">Library</Link>
<Link href="/settings">Settings</Link>
</nav>
</div>
<div className="space-y-4">
<h3 className="text-lg font-semibold">In Cards</h3>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2 rounded-lg border p-4">
<h4 className="font-medium">Agent Template</h4>
<p className="text-sm text-gray-600">
A powerful automation template for your workflow.
</p>
<Link href="/templates/agent">Add to library</Link>
</div>
<div className="space-y-2 rounded-lg border p-4">
<h4 className="font-medium">External Resource</h4>
<p className="text-sm text-gray-600">
Learn more about this topic from our documentation.
</p>
<Link href="https://docs.autogpt.net/guides" isExternal>
<span className="inline-flex items-center gap-1">
Read Guide <ExternalLink className="h-3 w-3" />
</span>
</Link>
</div>
</div>
<div className="space-y-4">
<div className="flex flex-wrap gap-4">
<Link href="/dashboard" variant="primary">
Primary link
</Link>
<Link href="/settings" variant="secondary">
Secondary link
</Link>
</div>
</div>
);

View File

@@ -7,17 +7,26 @@ interface LinkProps {
children: React.ReactNode;
className?: string;
isExternal?: boolean;
variant?: "primary" | "secondary";
}
const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
{ href, children, className, isExternal = false, ...props },
{
href,
children,
className,
isExternal = false,
variant = "primary",
...props
},
ref,
) {
const linkClasses = cn(
// Base styles from Figma
"font-['Geist'] text-sm font-medium leading-[22px] text-[var(--AutoGPT-Text-text-black,#141414)]",
// Hover state
"hover:underline",
// Variant-specific underline styles
variant === "primary" && "hover:underline",
variant === "secondary" && "underline",
// Focus state for accessibility
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded-sm",
className,

View File

@@ -13,52 +13,56 @@ const meta: Meta = {
export default meta;
// Border radius scale data with rem and px values
// https://tailwindcss.com/docs/border-radius
// Border radius scale data based on Figma design tokens
// Custom naming convention: xs, s, m, l, xl, 2xl, full
const borderRadiusScale = [
{ name: "none", value: "0px", rem: "0rem", px: "0px", class: "rounded-none" },
{
name: "sm",
value: "0.125rem",
rem: "0.125rem",
px: "2px",
class: "rounded-sm",
},
{
name: "DEFAULT",
name: "xs",
value: "0.25rem",
rem: "0.25rem",
px: "4px",
class: "rounded",
class: "rounded-xs",
description: "Extra small - for subtle rounding",
},
{
name: "md",
value: "0.375rem",
rem: "0.375rem",
px: "6px",
class: "rounded-md",
},
{
name: "lg",
name: "s",
value: "0.5rem",
rem: "0.5rem",
px: "8px",
class: "rounded-lg",
class: "rounded-s",
description: "Small - for cards and containers",
},
{
name: "xl",
name: "m",
value: "0.75rem",
rem: "0.75rem",
px: "12px",
class: "rounded-xl",
class: "rounded-m",
description: "Medium - for buttons and inputs",
},
{ name: "2xl", value: "1rem", rem: "1rem", px: "16px", class: "rounded-2xl" },
{
name: "3xl",
name: "l",
value: "1rem",
rem: "1rem",
px: "16px",
class: "rounded-l",
description: "Large - for panels and modals",
},
{
name: "xl",
value: "1.25rem",
rem: "1.25rem",
px: "20px",
class: "rounded-xl",
description: "Extra large - for hero sections",
},
{
name: "2xl",
value: "1.5rem",
rem: "1.5rem",
px: "24px",
class: "rounded-3xl",
class: "rounded-2xl",
description: "2X large - for major containers",
},
{
name: "full",
@@ -66,6 +70,7 @@ const borderRadiusScale = [
rem: "9999px",
px: "9999px",
class: "rounded-full",
description: "Full - for pill buttons and circular elements",
},
];
@@ -79,9 +84,10 @@ export function AllVariants() {
Border Radius
</Text>
<Text variant="large" className="text-zinc-600">
Our border radius system uses a consistent scale to create visual
hierarchy and maintain design consistency across all components.
From subtle rounded corners to fully circular elements.
Our border radius system uses a simplified naming convention (xs, s,
m, l, xl, 2xl, full) based on our Figma design tokens. This creates
visual hierarchy and maintains design consistency across all
components.
</Text>
</div>
@@ -119,18 +125,21 @@ export function AllVariants() {
Directional Classes
</Text>
<Text variant="body" className="mb-2 text-zinc-600">
Apply radius to specific corners or sides
Apply radius to specific corners or sides using our design
tokens
</Text>
<div className="space-y-1 font-mono text-sm text-zinc-800">
<div>rounded-t-lg top corners</div>
<div>rounded-r-lg right corners</div>
<div>rounded-b-lg bottom corners</div>
<div>rounded-l-lg left corners</div>
<div>rounded-t-m top corners</div>
<div>rounded-r-m right corners</div>
<div>rounded-b-m bottom corners</div>
<div>rounded-l-m left corners</div>
</div>
</div>
<Text variant="body" className="mb-4 text-zinc-600">
We follow Tailwind CSS border radius system, which provides a
comprehensive set of radius values for different use cases.
We use a custom border radius system based on our Figma design
tokens, with simplified naming (xs, s, m, l, xl, 2xl, full) that
provides consistent radius values optimized for our design
system.
</Text>
</div>
</div>
@@ -174,11 +183,12 @@ export function AllVariants() {
variant="h2"
className="mb-2 text-xl font-semibold text-zinc-800"
>
Complete Border Radius Scale
Design System Border Radius Tokens
</Text>
<Text variant="body" className="mb-6 text-zinc-600">
All available border radius values in our design system. Each value
can be applied to all corners or specific corners/sides.
All border radius values from our Figma design tokens. Each value
can be applied to all corners or specific corners/sides using our
simplified naming convention.
</Text>
</div>
@@ -221,28 +231,31 @@ export function AllVariants() {
</div>
<StoryCode
code={`// Border radius examples
<div className="rounded-none">No rounding (0px)</div>
<div className="rounded-sm">Small rounding (2px)</div>
<div className="rounded">Default rounding (4px)</div>
<div className="rounded-md">Medium rounding (6px)</div>
<div className="rounded-lg">Large rounding (8px)</div>
<div className="rounded-xl">Extra large rounding (12px)</div>
<div className="rounded-2xl">2X large rounding (16px)</div>
<div className="rounded-3xl">3X large rounding (24px)</div>
<div className="rounded-full">Fully rounded (circular)</div>
code={`// Border radius examples - Design System Tokens
<div className="rounded-xs">Extra small rounding (4px)</div>
<div className="rounded-s">Small rounding (8px)</div>
<div className="rounded-m">Medium rounding (12px)</div>
<div className="rounded-l">Large rounding (16px)</div>
<div className="rounded-xl">Extra large rounding (20px)</div>
<div className="rounded-2xl">2X large rounding (24px)</div>
<div className="rounded-full">Pill buttons (circular)</div>
// Directional rounding
<div className="rounded-t-lg">Top corners only</div>
<div className="rounded-r-lg">Right corners only</div>
<div className="rounded-b-lg">Bottom corners only</div>
<div className="rounded-l-lg">Left corners only</div>
// Directional rounding (works with all sizes)
<div className="rounded-t-m">Top corners only</div>
<div className="rounded-r-m">Right corners only</div>
<div className="rounded-b-m">Bottom corners only</div>
<div className="rounded-l-m">Left corners only</div>
// Individual corners
<div className="rounded-tl-lg">Top-left corner</div>
<div className="rounded-tr-lg">Top-right corner</div>
<div className="rounded-bl-lg">Bottom-left corner</div>
<div className="rounded-br-lg">Bottom-right corner</div>`}
<div className="rounded-tl-m">Top-left corner</div>
<div className="rounded-tr-m">Top-right corner</div>
<div className="rounded-bl-m">Bottom-left corner</div>
<div className="rounded-br-m">Bottom-right corner</div>
// Usage recommendations
<button className="rounded-full">Pill Button</button>
<div className="rounded-m">Card Container</div>
<input className="rounded-s">Input Field</input>`}
/>
</div>
</div>

View File

@@ -116,11 +116,19 @@ const config = {
96: "24rem",
},
borderRadius: {
// Design system border radius tokens from Figma
xs: "0.25rem", // 4px
s: "0.5rem", // 8px
m: "0.75rem", // 12px
l: "1rem", // 16px
xl: "1.25rem", // 20px
"2xl": "1.5rem", // 24px
full: "9999px", // For pill buttons
// Legacy values - kept for backward compatibility
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
// Add a full radius for pill-shaped buttons
full: "9999px",
},
boxShadow: {
subtle: "0px 1px 2px 0px rgba(0,0,0,0.05)",