Install
openclaw skills install @heroinyan-stack/react-component-generatorGenerates accessible, TypeScript-typed React components matching your detected design system and UI libraries like shadcn/ui, MUI, Chakra, with Storybook sto...
openclaw skills install @heroinyan-stack/react-component-generatorGenerate production-ready React components that match your design system. Auto-detects your UI library (shadcn/ui, MUI, Chakra, Tailwind), generates accessible, TypeScript-typed components with proper variants, forwardRef, and Storybook stories.
AI-generated React components often ignore design systems, lack accessibility, miss TypeScript types, and don't follow React 19 patterns. This skill reads your project's existing components and design tokens to generate components that look like they belong in your codebase.
Activate when the user:
Scan the project for:
cn() utility, variant patternsBased on detected patterns, generate a complete component:
"use client";
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { Loader2, ChevronDown } from "lucide-react";
// ===== Variant Definitions =====
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-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
// ===== Props Interface =====
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
loading?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
}
// ===== Component =====
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, loading, leftIcon, rightIcon, children, disabled, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
disabled={disabled || loading}
aria-busy={loading}
{...props}
>
{loading && <Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />}
{!loading && leftIcon && <span className="inline-flex">{leftIcon}</span>}
{children}
{!loading && rightIcon && <span className="inline-flex">{rightIcon}</span>}
</button>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants };
export type { ButtonProps };
// Generated with: sorting, filtering, pagination, row selection
export function DataTable<TData, TValue>({
columns,
data,
pagination,
}: DataTableProps<TData, TValue>) {
// ... full implementation with TanStack Table
}
// Generated with: label, error display, react-hook-form integration
export function FormField<TFieldValues extends FieldValues>({
name,
label,
description,
required,
render,
}: FormFieldProps<TFieldValues>) {
// ... full implementation with accessibility
}
// Generated with: focus trap, escape to close, portal, animation
export function Modal({ trigger, children, title, description }: ModalProps) {
// ... Radix UI Dialog with framer-motion animation
}
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
const meta = {
title: "UI/Button",
component: Button,
parameters: { layout: "centered" },
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["default", "destructive", "outline", "secondary", "ghost", "link"],
},
size: { control: "select", options: ["default", "sm", "lg", "icon"] },
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = { args: { children: "Button" } };
export const Destructive: Story = { args: { children: "Delete", variant: "destructive" } };
export const Loading: Story = { args: { children: "Saving...", loading: true } };
export const WithIcon: Story = {
args: { children: "Download", leftIcon: <DownloadIcon /> },
};
export const Disabled: Story = { args: { children: "Disabled", disabled: true } };
import { render, screen, fireEvent } from "@testing-library/react";
import { Button } from "./Button";
describe("Button", () => {
it("renders children correctly", () => {
render(<Button>Click me</Button>);
expect(screen.getByRole("button", { name: /click me/i })).toBeInTheDocument();
});
it("handles onClick events", () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByRole("button"));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it("disables when loading is true", () => {
render(<Button loading>Saving</Button>);
expect(screen.getByRole("button")).toBeDisabled();
expect(screen.getByRole("button")).toHaveAttribute("aria-busy", "true");
});
it("applies variant classes correctly", () => {
render(<Button variant="destructive">Delete</Button>);
expect(screen.getByRole("button")).toHaveClass("bg-destructive");
});
});
forwardRef (React 19 compatible)any)className prop for customizationcva (class-variance-authority) pattern@/components, @/lib/utils)