Files
aperonight/docs/creating-shadcn-react-components.md
2025-08-28 13:43:05 +02:00

289 lines
6.3 KiB
Markdown

# Creating New Shadcn and React Components
This guide explains how to create new Shadcn (UI) components and React components in this Rails application with React frontend.
## Overview
This project uses:
- **Shadcn/ui** for UI components (built on Radix UI and Tailwind CSS)
- **React** for frontend components
- **Rails** as the backend framework
- **esbuild** for JavaScript bundling
## Directory Structure
```
app/
├── javascript/
│ ├── components/
│ │ └── ui/ # Shadcn components
│ └── controllers/ # React controllers
├── views/
│ └── components/ # Rails view components
└── docs/ # Documentation
```
## Creating Shadcn Components
### 1. Using the Shadcn CLI
The easiest way to add new Shadcn components is using the CLI:
```bash
# Navigate to the project root
cd /home/acid/Documents/aperonight
# Add a new component (example: adding a card)
npx shadcn-ui@latest add card
```
This will:
- Install the component to `app/javascript/components/ui/`
- Update the components.json configuration
- Create the necessary TypeScript/JavaScript files
### 2. Manual Component Creation
If the CLI is not available, create components manually:
#### Create the component file
```bash
# Create a new component (example: button.jsx)
touch app/javascript/components/ui/button.jsx
```
#### Basic component structure
```javascript
// app/javascript/components/ui/button.jsx
import * as React from "react"
import { cn } from "@/lib/utils"
const Button = React.forwardRef(({ className, ...props }, ref) => {
return (
<button
className={cn(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
className
)}
ref={ref}
{...props}
/>
)
})
Button.displayName = "Button"
export { Button }
```
## Creating React Components
### 1. Controller-Based Components
For components that need Rails integration:
#### Create controller file
```bash
# Create a new controller
touch app/javascript/controllers/my_component_controller.js
```
#### Basic controller structure
```javascript
// app/javascript/controllers/my_component_controller.js
import { Controller } from "@hotwired/stimulus"
import React from "react"
import ReactDOM from "react-dom/client"
export default class extends Controller {
static targets = ["container"]
connect() {
const root = ReactDOM.createRoot(this.containerTarget)
root.render(<MyComponent />)
}
}
```
### 2. Standalone React Components
For reusable React components:
#### Create component file
```bash
# Create a new React component
touch app/javascript/components/MyNewComponent.jsx
```
#### Component structure
```javascript
// app/javascript/components/MyNewComponent.jsx
import React from "react"
const MyNewComponent = ({ title, description }) => {
return (
<div className="p-4 border rounded-lg">
<h2 className="text-lg font-semibold">{title}</h2>
<p className="text-gray-600">{description}</p>
</div>
)
}
export default MyNewComponent
```
## Integration Patterns
### 1. Using in Rails Views
To use components in Rails views:
#### Create partial
```erb
<!-- app/views/components/_my_component.html.erb -->
<div data-controller="my-component">
<div data-my-component-target="container"></div>
</div>
```
#### Include in page
```erb
<!-- app/views/pages/home.html.erb -->
<%= render "components/my_component" %>
```
### 2. Direct React Rendering
For pages that are primarily React:
#### Create page component
```javascript
// app/javascript/components/pages/HomePage.jsx
import React from "react"
import { Button } from "@/components/ui/button"
const HomePage = () => {
return (
<div className="container mx-auto">
<h1>Welcome</h1>
<Button>Get Started</Button>
</div>
)
}
export default HomePage
```
## Configuration Updates
### 1. Update components.json
```json
{
"style": "default",
"rsc": false,
"tsx": false,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/assets/stylesheets/application.postcss.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "app/javascript/components",
"utils": "app/javascript/lib/utils"
}
}
```
### 2. Update JavaScript entry point
```javascript
// app/javascript/application.js
import "./components"
import "./controllers"
```
## Naming Conventions
### Shadcn Components
- Use kebab-case for filenames: `button.jsx`, `card.jsx`
- Use PascalCase for exports: `export { Button }`
- Follow Radix UI naming patterns
### React Components
- Use PascalCase for filenames: `MyComponent.jsx`
- Use PascalCase for components: `const MyComponent = () => {}`
- Use camelCase for props: `myProp`, `onClick`
## Testing Components
### 1. Create test file
```bash
# Create test file
touch test/components/my_component_test.rb
```
### 2. Write component test
```javascript
// test/components/my_component_test.jsx
import { render, screen } from "@testing-library/react"
import MyComponent from "../../app/javascript/components/MyComponent"
test("renders component", () => {
render(<MyComponent title="Test" />)
expect(screen.getByText("Test")).toBeInTheDocument()
})
```
## Common Patterns
### 1. Props Pattern
```javascript
// Pass Rails data as props
const MyComponent = ({ user, config }) => {
return <div>{user.name}</div>
}
```
### 2. Event Handling
```javascript
// Handle events from Rails
const MyComponent = ({ onAction }) => {
return <button onClick={onAction}>Click me</button>
}
```
### 3. Styling Integration
```javascript
// Use Tailwind classes
const MyComponent = () => {
return <div className="bg-white dark:bg-gray-800">Content</div>
}
```
## Troubleshooting
### Common Issues
1. **Component not rendering**: Check controller connection
2. **Styling issues**: Verify Tailwind classes
3. **Props not passing**: Check data-controller attributes
4. **Import errors**: Verify alias paths in components.json
### Debug Steps
1. Check browser console for errors
2. Verify component file exists in correct location
3. Check import paths in application.js
4. Verify Rails view includes correct data attributes
## Example created for testing purpose
```html
<!-- Shadcn Button Test -->
<div data-controller="shadcn-test" class="mt-4">
<div data-shadcn-test-target="container"></div>
</div>
```