161 lines
3.8 KiB
Markdown
161 lines
3.8 KiB
Markdown
---
|
|
name: react-component
|
|
description: Panduan membuat React component untuk Tour Planner App. Gunakan skill ini ketika membuat komponen baru, baik UI umum maupun komponen spesifik fitur (trip, schedule, packing, budget).
|
|
---
|
|
|
|
# Skill: React Component — Tour Planner App
|
|
|
|
## Aturan Dasar
|
|
|
|
Semua komponen HARUS mengikuti pola berikut:
|
|
- Functional component dengan arrow function
|
|
- Props didestruktur di parameter
|
|
- Menggunakan CSS variables dari design system (lihat `src/index.css`)
|
|
- Menggunakan Framer Motion untuk animasi
|
|
- Menggunakan Lucide React untuk icon
|
|
|
|
## Template Dasar Komponen
|
|
|
|
```jsx
|
|
import { motion } from 'framer-motion';
|
|
import { IconName } from 'lucide-react';
|
|
import styles from './ComponentName.module.css'; // opsional
|
|
|
|
const ComponentName = ({ prop1, prop2, onAction }) => {
|
|
return (
|
|
<motion.div
|
|
className="component-name"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
{/* content */}
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
export default ComponentName;
|
|
```
|
|
|
|
## Komponen UI yang Tersedia
|
|
|
|
### Button
|
|
```jsx
|
|
<Button variant="primary|secondary|ghost|danger" size="sm|md|lg" icon={IconComponent} onClick={fn}>
|
|
Label
|
|
</Button>
|
|
```
|
|
|
|
### Card
|
|
```jsx
|
|
<Card glassmorphism hoverable>
|
|
{/* content */}
|
|
</Card>
|
|
```
|
|
Variants: `glassmorphism` (default), `elevated`, `outlined`
|
|
|
|
### Modal
|
|
```jsx
|
|
<Modal isOpen={bool} onClose={fn} title="Judul Modal">
|
|
{/* content */}
|
|
</Modal>
|
|
```
|
|
|
|
### Badge
|
|
```jsx
|
|
<Badge variant="primary|success|warning|danger">{text}</Badge>
|
|
```
|
|
|
|
### ProgressBar
|
|
```jsx
|
|
<ProgressBar value={0-100} label="Teks" showPercent />
|
|
```
|
|
|
|
## Animasi Standar
|
|
|
|
```javascript
|
|
// Fade in dari bawah (card, item list)
|
|
const fadeInUp = {
|
|
initial: { opacity: 0, y: 20 },
|
|
animate: { opacity: 1, y: 0 },
|
|
transition: { duration: 0.3 }
|
|
};
|
|
|
|
// Stagger anak-anak (list items)
|
|
const containerVariants = {
|
|
animate: { transition: { staggerChildren: 0.08 } }
|
|
};
|
|
|
|
// Scale on hover (interactive card)
|
|
const hoverScale = {
|
|
whileHover: { scale: 1.02 },
|
|
whileTap: { scale: 0.98 }
|
|
};
|
|
```
|
|
|
|
## CSS Classes Tersedia (dari index.css)
|
|
|
|
```
|
|
.glass — glassmorphism card
|
|
.glass-dark — glassmorphism gelap
|
|
.btn-primary — tombol primary
|
|
.btn-secondary — tombol secondary
|
|
.btn-ghost — tombol ghost
|
|
.text-gradient — teks dengan gradient
|
|
.badge — badge dasar
|
|
.input-field — input form
|
|
.section-title — heading section
|
|
```
|
|
|
|
## File Baru Selalu di Folder yang Tepat
|
|
|
|
| Tipe | Lokasi |
|
|
|------|--------|
|
|
| Layout component | `src/components/layout/` |
|
|
| UI generic | `src/components/ui/` |
|
|
| Trip feature | `src/components/trip/` |
|
|
| Schedule feature | `src/components/schedule/` |
|
|
| Packing feature | `src/components/packing/` |
|
|
| Budget feature | `src/components/budget/` |
|
|
| Full page | `src/pages/` |
|
|
|
|
## Contoh: Membuat Activity Item
|
|
|
|
```jsx
|
|
// src/components/schedule/ActivityItem.jsx
|
|
import { motion } from 'framer-motion';
|
|
import { Clock, MapPin, GripVertical, Trash2 } from 'lucide-react';
|
|
|
|
const ActivityItem = ({ activity, onDelete, dragHandleProps }) => {
|
|
return (
|
|
<motion.div
|
|
className="activity-item"
|
|
layout
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: 20 }}
|
|
>
|
|
<div className="activity-drag-handle" {...dragHandleProps}>
|
|
<GripVertical size={16} />
|
|
</div>
|
|
<div className="activity-time">
|
|
<Clock size={14} />
|
|
<span>{activity.time}</span>
|
|
</div>
|
|
<div className="activity-content">
|
|
<h4>{activity.name}</h4>
|
|
<div className="activity-location">
|
|
<MapPin size={12} />
|
|
<span>{activity.location}</span>
|
|
</div>
|
|
</div>
|
|
<button onClick={() => onDelete(activity.id)}>
|
|
<Trash2 size={16} />
|
|
</button>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
export default ActivityItem;
|
|
```
|