#!/bin/bash # React Component Scaffold Generator # Generates a production-ready React component with tests, Storybook, and CSS modules set -e COMPONENT_NAME="${1}" if [ -z "$COMPONENT_NAME" ]; then echo "Usage: $0 ComponentName" echo "Example: $0 Button" exit 1 fi # Convert to kebab-case for file names KEBAB_NAME=$(echo "$COMPONENT_NAME" | sed 's/\([A-Z]\)/-\1/g' | sed 's/^-//' | tr '[:upper:]' '[:lower:]') echo "🎨 Creating React component: $COMPONENT_NAME ($KEBAB_NAME)" # Create directory structure mkdir -p "src/components/$COMPONENT_NAME" cd "src/components/$COMPONENT_NAME" # Create component file cat > "$COMPONENT_NAME.tsx" < void; } /** * $COMPONENT_NAME component * * A reusable component for... * * @example * \`\`\`tsx * <$COMPONENT_NAME onClick={() => console.log('clicked')}> * Click me * * \`\`\` */ export const $COMPONENT_NAME: React.FC<${COMPONENT_NAME}Props> = ({ children, className = '', disabled = false, onClick, }) => { const handleClick = () => { if (!disabled && onClick) { onClick(); } }; return (
{children}
); }; $COMPONENT_NAME.displayName = '$COMPONENT_NAME'; EOF # Create CSS module cat > "$COMPONENT_NAME.module.css" < "index.ts" < "$COMPONENT_NAME.test.tsx" < { it('renders children correctly', () => { render(<$COMPONENT_NAME>Test Content); expect(screen.getByText('Test Content')).toBeInTheDocument(); }); it('calls onClick when clicked', () => { const handleClick = vi.fn(); render(<$COMPONENT_NAME onClick={handleClick}>Click me); fireEvent.click(screen.getByText('Click me')); expect(handleClick).toHaveBeenCalledTimes(1); }); it('does not call onClick when disabled', () => { const handleClick = vi.fn(); render( <$COMPONENT_NAME onClick={handleClick} disabled> Click me ); fireEvent.click(screen.getByText('Click me')); expect(handleClick).not.toHaveBeenCalled(); }); it('applies custom className', () => { const { container } = render( <$COMPONENT_NAME className="custom-class">Test ); const element = container.firstChild; expect(element).toHaveClass('custom-class'); }); it('has correct accessibility attributes', () => { render(<$COMPONENT_NAME>Test); const element = screen.getByRole('button'); expect(element).toHaveAttribute('tabIndex', '0'); expect(element).toHaveAttribute('aria-disabled', 'false'); }); it('has correct accessibility attributes when disabled', () => { render(<$COMPONENT_NAME disabled>Test); const element = screen.getByRole('button'); expect(element).toHaveAttribute('tabIndex', '-1'); expect(element).toHaveAttribute('aria-disabled', 'true'); }); }); EOF # Create Storybook story cat > "$COMPONENT_NAME.stories.tsx" <; export default meta; type Story = StoryObj; export const Default: Story = { args: { children: 'Default $COMPONENT_NAME', }, }; export const Disabled: Story = { args: { children: 'Disabled $COMPONENT_NAME', disabled: true, }, }; export const WithClick: Story = { args: { children: 'Click me', onClick: () => console.log('Clicked!'), }, }; export const CustomClass: Story = { args: { children: 'Custom Styled', className: 'custom-component-class', }, }; EOF # Create README cat > "README.md" < console.log('clicked')}> Click me ); } \`\`\` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | \`children\` | \`React.ReactNode\` | - | The content to display | | \`className\` | \`string\` | \`''\` | Additional CSS class names | | \`disabled\` | \`boolean\` | \`false\` | Whether the component is disabled | | \`onClick\` | \`() => void\` | - | Click handler | ## Testing \`\`\`bash npm test -- $COMPONENT_NAME.test.tsx \`\`\` ## Storybook \`\`\`bash npm run storybook # View at http://localhost:6006/?path=/story/components-${KEBAB_NAME} \`\`\` ## Accessibility - Uses semantic HTML with \`role="button"\` - Keyboard accessible with \`tabIndex\` - Screen reader friendly with \`aria-disabled\` - Focus visible with outline ## CSS Variables Customize the component by overriding CSS variables: \`\`\`css .$KEBAB_NAME { --bg-color: #fff; --bg-hover-color: #f5f5f5; --border-color: #ccc; --border-hover-color: #999; --text-color: #333; --focus-color: #0066ff; } \`\`\` EOF cd ../../.. echo "✅ React component created: src/components/$COMPONENT_NAME" echo "" echo "Files created:" echo " - $COMPONENT_NAME.tsx (component)" echo " - $COMPONENT_NAME.module.css (styles)" echo " - $COMPONENT_NAME.test.tsx (tests)" echo " - $COMPONENT_NAME.stories.tsx (Storybook)" echo " - index.ts (exports)" echo " - README.md (documentation)" echo "" echo "Next steps:" echo " 1. Import: import { $COMPONENT_NAME } from './components/$COMPONENT_NAME';" echo " 2. Test: npm test -- $COMPONENT_NAME.test.tsx" echo " 3. View in Storybook: npm run storybook"