Files
2026-05-27 23:29:53 +01:00

10 KiB

name, description
name description
web-artifacts-builder Build production-ready React 19 + TypeScript + Tailwind CSS components and pages using Suaveplan's UI library, Vite bundling, and shadcn/ui primitives

Web Artifacts Builder for Suaveplan

Build React 19 components, pages, and interactive artifacts within the Suaveplan monorepo. All artifacts use TypeScript, Tailwind CSS, shadcn/ui from @suaveplan/react-ui, and Vite for bundling. No external UI libraries when Suaveplan equivalents exist.

Stack

Layer Technology
Framework React 19 (Server Components, Actions, useActionState)
Language TypeScript 5.x (strict mode)
Styling Tailwind CSS with cn() from @suaveplan/react-ui
UI Primitives shadcn/ui via @suaveplan/react-ui
Bundler Vite (NOT Parcel, NOT webpack)
Testing Vitest (unit) + Playwright (E2E)
Linting Biome (NOT ESLint)
Stories Storybook 8
Package Manager Bun with workspace protocol

Available Suaveplan Packages

Always use these instead of external alternatives:

Need Suaveplan Package Do NOT Use
Buttons, Cards, Dialogs, Inputs @suaveplan/react-ui Material UI, Chakra UI, Ant Design
Forms with validation @suaveplan/react-forms React Hook Form (standalone)
Data grids/tables @suaveplan/react-data-table AG Grid, TanStack Table (standalone)
Notifications/toasts @suaveplan/react-notifications react-hot-toast, sonner
Timeline/Gantt views @suaveplan/react-timeline vis-timeline
Authentication UI @suaveplan/react-auth custom auth forms
Media players @suaveplan/react-media react-player
Internationalization @suaveplan/react-translate react-i18next (standalone)
CSS class merging cn() from @suaveplan/react-ui clsx, classnames (standalone)
Charts/visualization @suaveplan/react-charts recharts (standalone)
Drive/file sharing UI @suaveplan/drive-react-sharing custom file dialogs
Virtualized lists @suaveplan/react-virtual react-window, react-virtuoso

Component File Structure

Every component follows this five-file pattern inside its directory:

src/components/<component-name>/
  <component-name>.tsx          # Implementation (max 250 lines)
  <component-name>.test.tsx     # Unit tests (max 500 lines, 95%+ coverage)
  <component-name>.stories.tsx  # Storybook stories
  <component-name>.types.ts    # TypeScript interfaces and types
  <component-name>.md          # Documentation (200+ words, simple + advanced examples)

Implementation File Pattern

// src/components/status-badge/status-badge.tsx
import * as React from 'react';
import { cva } from 'class-variance-authority';
import { cn } from '@suaveplan/react-ui';
import type { StatusBadgeProps } from './status-badge.types';

const statusBadgeVariants = cva(
  'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors',
  {
    variants: {
      status: {
        active: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
        pending: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
        inactive: 'bg-muted text-muted-foreground',
      },
    },
    defaultVariants: { status: 'active' },
  }
);

export const StatusBadge = React.forwardRef<HTMLSpanElement, StatusBadgeProps>(
  ({ className, status, label, ...props }, ref) => {
    return (
      <span
        ref={ref}
        data-testid="sp-status-badge"
        role="status"
        aria-label={`Status: ${label ?? status}`}
        className={cn(statusBadgeVariants({ status }), className)}
        {...props}
      >
        {label ?? status}
      </span>
    );
  }
);

StatusBadge.displayName = 'StatusBadge';

Types File Pattern

// src/components/status-badge/status-badge.types.ts
import type { VariantProps } from 'class-variance-authority';
import type { statusBadgeVariants } from './status-badge';

export interface StatusBadgeProps
  extends React.HTMLAttributes<HTMLSpanElement>,
    VariantProps<typeof statusBadgeVariants> {
  /** Display label (defaults to status name) */
  label?: string;
}

Stories File Pattern

// src/components/status-badge/status-badge.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { StatusBadge } from './status-badge';

const meta: Meta<typeof StatusBadge> = {
  title: 'Components/StatusBadge',
  component: StatusBadge,
  tags: ['autodocs'],
  argTypes: {
    status: {
      control: 'select',
      options: ['active', 'pending', 'inactive'],
    },
  },
};
export default meta;

type Story = StoryObj<typeof StatusBadge>;

export const Active: Story = { args: { status: 'active' } };
export const Pending: Story = { args: { status: 'pending', label: 'Awaiting Review' } };
export const Inactive: Story = { args: { status: 'inactive' } };

export const AllStatuses: Story = {
  render: () => (
    <div className="flex gap-2">
      <StatusBadge status="active" />
      <StatusBadge status="pending" />
      <StatusBadge status="inactive" />
    </div>
  ),
};

Test File Pattern

// src/components/status-badge/status-badge.test.tsx
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { StatusBadge } from './status-badge';

describe('StatusBadge', () => {
  it('renders with active status', () => {
    render(<StatusBadge status="active" />);
    expect(screen.getByTestId('sp-status-badge')).toBeInTheDocument();
    expect(screen.getByRole('status')).toHaveTextContent('active');
  });

  it('uses custom label when provided', () => {
    render(<StatusBadge status="pending" label="In Review" />);
    expect(screen.getByRole('status')).toHaveTextContent('In Review');
  });

  it('applies custom className via cn()', () => {
    render(<StatusBadge status="active" className="ml-4" />);
    expect(screen.getByTestId('sp-status-badge')).toHaveClass('ml-4');
  });

  it('forwards ref', () => {
    const ref = { current: null as HTMLSpanElement | null };
    render(<StatusBadge ref={ref} status="active" />);
    expect(ref.current).toBeInstanceOf(HTMLSpanElement);
  });

  it('has accessible aria-label', () => {
    render(<StatusBadge status="active" />);
    expect(screen.getByRole('status')).toHaveAttribute('aria-label', 'Status: active');
  });
});

Documentation File Pattern

Follow .claude/templates/module-docs.md template with these requirements:

  • Minimum 200 words
  • Include a "Basic Example" section (simple import + render)
  • Include an "Advanced Example" section (composition, custom styling, controlled state)
  • Document all props in an API Reference section
  • Explain design decisions
# StatusBadge

## Purpose
A visual indicator component that displays the current status of an entity...

## Usage

### Basic Example
\`\`\`typescript
import { StatusBadge } from '@suaveplan/react-<package>';
<StatusBadge status="active" />
\`\`\`

### Advanced Example
\`\`\`typescript
import { StatusBadge } from '@suaveplan/react-<package>';
import { cn } from '@suaveplan/react-ui';
// Composed with conditional logic and custom styling...
\`\`\`

## API Reference
...

Styling Rules

Tailwind CSS + cn() Utility

Always use cn() from @suaveplan/react-ui for conditional class merging:

import { cn } from '@suaveplan/react-ui';

// Correct: cn() for merging
<div className={cn('flex items-center', isActive && 'bg-primary', className)} />

// Wrong: template literals for conditional classes
<div className={`flex items-center ${isActive ? 'bg-primary' : ''}`} />

Dark Mode

Use Tailwind's dark: variant. Shadcn/ui CSS variables automatically adapt:

<div className="bg-background text-foreground dark:bg-background dark:text-foreground">
  <p className="text-muted-foreground">Uses CSS variables, works in both modes</p>
</div>

Shadcn/UI CSS Variables

Reference these semantic tokens instead of raw Tailwind colors:

  • background / foreground -- page background and default text
  • primary / primary-foreground -- primary actions
  • secondary / secondary-foreground -- secondary actions
  • muted / muted-foreground -- subdued UI elements
  • accent / accent-foreground -- highlights and hover states
  • destructive / destructive-foreground -- errors and deletions
  • border -- default border color
  • ring -- focus ring color
  • card / card-foreground -- card surfaces

Build Configuration

Suaveplan browser packages use Vite for building:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';

export default defineConfig({
  plugins: [react(), dts({ rollupTypes: true })],
  build: {
    lib: {
      entry: 'src/index.ts',
      formats: ['es'],
      fileName: 'index',
    },
    rollupOptions: {
      external: ['react', 'react-dom', 'react/jsx-runtime'],
    },
  },
});

Package.json Requirements

{
  "name": "@suaveplan/react-<name>",
  "type": "module",
  "sideEffects": false,
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  },
  "dependencies": {
    "@suaveplan/react-ui": "workspace:*"
  },
  "peerDependencies": {
    "react": "19.0.0",
    "react-dom": "19.0.0"
  }
}

Constraints

  • Max 250 lines per implementation file -- split into sub-components if larger
  • Max 500 lines per test file -- split by feature area
  • 95% minimum test coverage -- verified via bun run test:coverage
  • No external UI libraries when a Suaveplan package covers the need
  • No Parcel -- Vite is the bundler for all browser packages
  • No tsc compilation -- Vite handles transforms, TypeScript is for type-checking only
  • No mocks -- test with real implementations
  • Biome linting -- not ESLint
  • React 19 -- use modern patterns (useActionState, useOptimistic, Server Components where applicable)
  • Accessibility -- ARIA labels, keyboard navigation, focus management on all interactive elements
  • data-testid attributes must use sp- prefix on all Suaveplan components
  • forwardRef -- all leaf components must forward refs
  • Documentation -- every .tsx implementation file must have a matching .md file