10 KiB
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 textprimary/primary-foreground-- primary actionssecondary/secondary-foreground-- secondary actionsmuted/muted-foreground-- subdued UI elementsaccent/accent-foreground-- highlights and hover statesdestructive/destructive-foreground-- errors and deletionsborder-- default border colorring-- focus ring colorcard/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-testidattributes must usesp-prefix on all Suaveplan componentsforwardRef-- all leaf components must forward refs- Documentation -- every
.tsximplementation file must have a matching.mdfile