This guide provides a step-by-step approach to implementing new features in the Prompt Helper Chrome Extension, following the established patterns and architecture.
The extension follows a React-based architecture with:
- React 18 with TypeScript for the UI
- Vite for build tooling and development
- Tailwind CSS + Shadcn/UI for styling
- Chrome Extension Manifest V3 for browser integration
- Framer Motion for animations
- React Hook Form + Zod for form validation
src/
├── components/ # React components
│ ├── ui/ # Reusable UI components (Shadcn/UI)
│ ├── PromptSidebar.tsx # Main sidebar component
│ ├── PromptCard.tsx # Individual prompt display
│ ├── PromptForm.tsx # Add/edit prompt form
│ └── DragDropContext.tsx # Drag & drop functionality
├── hooks/ # Custom React hooks
├── lib/ # Utility functions and services
│ ├── storage.ts # Chrome storage abstraction
│ └── utils.ts # General utilities
├── types/ # TypeScript type definitions
└── App.tsx # Main application component
If your feature requires new data structures, add them to src/types/:
// src/types/yourFeature.ts
export interface YourFeatureType {
id: string;
name: string;
// ... other properties
}If your feature needs data persistence, extend src/lib/storage.ts:
// In src/lib/storage.ts
export const storageUtils = {
// ... existing methods
async saveYourFeatureData(data: YourFeatureType[]): Promise<void> {
try {
if (typeof chrome !== "undefined" && chrome.storage) {
await chrome.storage.local.set({ yourFeatureKey: data });
} else {
localStorage.setItem("yourFeatureKey", JSON.stringify(data));
}
} catch (error) {
console.error("Error saving your feature data:", error);
localStorage.setItem("yourFeatureKey", JSON.stringify(data));
}
},
async getYourFeatureData(): Promise<YourFeatureType[]> {
// Similar pattern to existing methods
},
};For complex state management, create custom hooks in src/hooks/:
// src/hooks/useYourFeature.ts
import { useState, useEffect } from "react";
import { storageUtils } from "@/lib/storage";
export const useYourFeature = () => {
const [data, setData] = useState<YourFeatureType[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
setLoading(true);
try {
const loadedData = await storageUtils.getYourFeatureData();
setData(loadedData);
} catch (error) {
console.error("Failed to load data:", error);
} finally {
setLoading(false);
}
};
return { data, loading, loadData };
};Follow the established component patterns:
// src/components/YourFeatureComponent.tsx
import { useState } from "react";
import { motion } from "framer-motion";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
interface YourFeatureComponentProps {
// Define props
}
export function YourFeatureComponent({}: YourFeatureComponentProps) {
const [state, setState] = useState();
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="w-full"
>
<Card>
<CardHeader>
<CardTitle>Your Feature</CardTitle>
</CardHeader>
<CardContent>{/* Your component content */}</CardContent>
</Card>
</motion.div>
);
}Add your feature to the main sidebar or create new navigation:
// In src/components/PromptSidebar.tsx
import { YourFeatureComponent } from "./YourFeatureComponent";
// Add state for your feature
const [showYourFeature, setShowYourFeature] = useState(false);
// Add UI elements in the appropriate section
<Button onClick={() => setShowYourFeature(true)} variant="ghost" size="sm">
Your Feature
</Button>;
// Conditionally render your component
{
showYourFeature && (
<YourFeatureComponent onClose={() => setShowYourFeature(false)} />
);
}Use Zod for schema validation:
import * as z from "zod";
const yourFeatureSchema = z.object({
name: z.string().min(1, "Name is required"),
description: z.string().max(500, "Description too long"),
});
type YourFeatureFormData = z.infer<typeof yourFeatureSchema>;Use Framer Motion consistently:
import { motion, AnimatePresence } from "framer-motion";
<AnimatePresence>
{condition && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
>
{/* Content */}
</motion.div>
)}
</AnimatePresence>;- Follow the existing color scheme with CSS variables
- Use
bg-background,text-foreground,text-muted-foreground - Apply consistent spacing:
p-3,gap-2,space-y-2
- Cards:
w-fullwith responsive max-widths - Buttons: Use size variants (
sm,default) - Icons: Typically
h-4 w-4orh-5 w-5
- All components automatically support dark mode via Tailwind's
dark:prefix - Use semantic color classes that adapt to theme
If your feature needs new permissions, update manifest.json:
{
"permissions": ["storage", "sidePanel", "newPermission"]
}For background functionality, extend src/background.ts:
// Handle your feature's background events
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "YOUR_FEATURE_ACTION") {
// Handle the action
sendResponse({ success: true });
}
});- Run
npm run buildto build the extension - Load the unpacked extension in Chrome
- Test all user interactions
- Verify data persistence across browser sessions
- Test in both light and dark modes
- Run
npm run lintto check for issues - Ensure TypeScript compilation passes
- Follow existing naming conventions
- Types defined in
src/types/ - Storage functions implemented (if needed)
- Custom hooks created (if needed)
- UI components follow established patterns
- Framer Motion animations added
- Form validation with Zod (if applicable)
- Integration with main sidebar
- Dark mode support verified
- Chrome extension permissions updated (if needed)
- Manual testing completed
- Code linting passes
After implementing your feature:
- Test thoroughly in development
- Build the extension:
npm run build - Test the built version
- Update version in
manifest.jsonif needed - Create distribution package
- Follow existing patterns - Look at similar components for guidance
- Keep components small - Break complex features into smaller components
- Use TypeScript strictly - Define proper types for all data
- Handle errors gracefully - Always provide fallbacks
- Maintain accessibility - Use proper ARIA labels and semantic HTML
- Optimize performance - Use React.memo() for expensive components
- Document complex logic - Add comments for non-obvious code
const [data, setData] = useState<Type[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>("");try {
await someAsyncOperation();
} catch (error) {
console.error("Operation failed:", error);
setError("User-friendly error message");
}interface ComponentProps {
data: Type[];
onAction: (item: Type) => void;
className?: string;
}This guide should help you implement features that integrate seamlessly with the existing codebase while maintaining code quality and user experience standards.