This directory contains comprehensive documentation for all custom React components used in the TechDiary application (excluding shadcn/ui components).
Custom components are reusable UI elements that encapsulate specific functionality and design patterns. They provide consistent interfaces, behavior, and styling throughout the application.
- ArticleCard - Article preview cards for feeds and lists
- ArticleEditor - Comprehensive markdown editor with auto-save
- UserInformationCard - User profile display with interactions
- AppImage - Optimized image component with Cloudinary integration
- ImageDropzoneWithCropper - File upload with cropping capabilities
- ResourceReaction - Emoji reaction system for articles and comments (UI implementation of ResourceReactionable)
- ResourceBookmark - Bookmark button for articles and comments (UI implementation of ResourceBookmarkable)
- ResourceReactionable - Render prop for reaction functionality with optimistic updates
- ResourceBookmarkable - Render prop for bookmark functionality with state management
- Navbar - Main application navigation with search and user actions
- ThemeSwitcher - Dark/light theme toggle component
- LanguageSwitcher - Bengali/English language toggle
- BaseLayout - Base application layout wrapper
- HomepageLayout - Homepage-specific layout structure
- CommonProviders - Application-wide context providers
- SessionProvider - User session management
- I18nProvider - Internationalization context
- DiscordWidget - Discord community integration
- SocialLinksWidget - Social media links display
- LatestUsers - Recent user registrations display
- ImportantLinksWidget - Curated links section
Components that form the primary user interface:
- ArticleCard, ArticleEditor, UserInformationCard
- Navbar, navigation components
- Layout wrappers and providers
Components handling images and file uploads:
- AppImage with Cloudinary optimization
- ImageDropzoneWithCropper for uploads
- File management utilities
Components providing user interactions:
- ResourceReaction for emoji reactions (UI implementation of ResourceReactionable)
- ResourceBookmark for saving content (UI implementation of ResourceBookmarkable)
- Comment system components
Components that provide data and logic via render props pattern:
- ResourceReactionable for reaction functionality
- ResourceBookmarkable for bookmark functionality
- Flexible UI rendering with complete control
Helper components for specific functions:
- VisibilitySensor for scroll detection
- Toast notifications
- Social login cards
- Icon components
| Component | Purpose | Key Features |
|---|---|---|
ArticleCard |
Article preview | Author info, reactions, bookmarking |
ArticleEditor |
Content creation | Markdown editing, auto-save, preview |
AppImage |
Optimized images | Cloudinary integration, lazy loading |
ImageDropzoneWithCropper |
File uploads | Drag-and-drop, cropping, cloud storage |
UserInformationCard |
User profiles | Bio, social info, follow/edit actions |
ResourceReaction |
Emoji reactions UI | Pre-built UI using ResourceReactionable |
ResourceBookmark |
Bookmark button UI | Pre-built UI using ResourceBookmarkable |
ResourceReactionable |
Reaction logic | Render props, optimistic updates, auth |
ResourceBookmarkable |
Bookmark logic | Render props, state sync, optimistic UI |
interface ComponentProps {
// Required props
id: string;
title: string;
// Optional props with defaults
variant?: "primary" | "secondary";
disabled?: boolean;
// Callback functions
onAction?: (data: any) => void;
// Children and composition
children?: React.ReactNode;
}function CustomComponent() {
// Translation and i18n
const { _t } = useTranslation();
// Authentication
const session = useSession();
// UI state management
const [isOpen, { toggle, close }] = useToggle();
// API integration
const { data, isLoading } = useQuery({...});
return (/* JSX */);
}// Loading states
if (isLoading) return <SkeletonLoader />;
// Error states
if (error) return <ErrorMessage error={error} />;
// Authentication-based rendering
{session ? (
<AuthenticatedContent />
) : (
<LoginPrompt />
)}
// Feature flags and permissions
{hasPermission && <PrivilegedAction />}const handleAction = useCallback((event: Event) => {
// Prevent defaults
event.preventDefault();
// Validation
if (!isValid) return;
// Business logic
performAction();
// Callbacks
onAction?.(result);
}, [dependencies]);// Render props component for reusable logic
function DataProvider({ render, ...props }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// Data fetching logic
useEffect(() => {
fetchData().then(setData).finally(() => setLoading(false));
}, []);
return render({ data, loading, refetch: fetchData });
}
// Usage with complete UI control
<DataProvider
render={({ data, loading }) => (
loading ? <Spinner /> : <CustomDataDisplay data={data} />
)}
/>
// Render props for resource interactions
<ResourceReactionable
resource_type="ARTICLE"
resource_id={articleId}
render={({ reactions, toggle }) => (
<div className="custom-reactions">
{reactions.map(reaction => (
<button key={reaction.type} onClick={() => toggle(reaction.type)}>
{reaction.emoji} {reaction.count}
</button>
))}
</div>
)}
/>// Container patterns
"flex items-center justify-between"
"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
"max-w-4xl mx-auto p-4"
// Interactive states
"hover:bg-primary/20 transition-colors duration-200"
"focus:outline-none focus:ring-2 focus:ring-primary"
"disabled:opacity-50 disabled:cursor-not-allowed"
// Responsive design
"hidden md:block"
"text-sm md:text-base"
"p-2 md:p-4"// Base classes
const baseClasses = "component-base-styles";
// Variant classes
const variantClasses = {
primary: "primary-variant-styles",
secondary: "secondary-variant-styles"
};
// State classes
const stateClasses = clsx(baseClasses, {
[variantClasses[variant]]: variant,
"active-state": isActive,
"disabled-state": disabled
});// Data fetching
const { data, isLoading, error } = useQuery({
queryKey: ['resource', resourceId],
queryFn: () => fetchResource(resourceId)
});
// Mutations
const mutation = useMutation({
mutationFn: updateResource,
onSuccess: () => queryClient.invalidateQueries(['resource'])
});// Global atoms
const [session, setSession] = useAtom(sessionAtom);
const [theme, setTheme] = useAtom(themeAtom);
const [language, setLanguage] = useAtom(languageAtom);const form = useForm({
defaultValues: initialData,
resolver: zodResolver(validationSchema)
});
const onSubmit = form.handleSubmit((data) => {
// Handle form submission
});// Use proper semantic elements
<article>
<header>
<h1>{title}</h1>
</header>
<main>{content}</main>
<footer>{metadata}</footer>
</article>
// Navigation structures
<nav aria-label="Main navigation">
<ul role="list">
<li><a href="/home">Home</a></li>
</ul>
</nav>// Interactive elements
<button
aria-label={`Delete ${itemName}`}
aria-describedby="delete-description"
onClick={handleDelete}
>
Delete
</button>
// Dynamic content
<div
role="status"
aria-live="polite"
aria-label="Loading status"
>
{isLoading ? "Loading..." : "Content loaded"}
</div>// Focus trapping in modals
const focusRef = useRef<HTMLElement>(null);
useEffect(() => {
if (isOpen && focusRef.current) {
focusRef.current.focus();
}
}, [isOpen]);
// Skip links for navigation
<a href="#main-content" className="sr-only focus:not-sr-only">
Skip to main content
</a>// Memoization for expensive components
const ExpensiveComponent = React.memo(({ data }) => {
return <ComplexVisualization data={data} />;
});
// Callback memoization
const handleClick = useCallback((id: string) => {
onItemClick(id);
}, [onItemClick]);
// Value memoization
const processedData = useMemo(() => {
return processLargeDataset(rawData);
}, [rawData]);// Lazy loading for large components
const HeavyComponent = lazy(() => import('./HeavyComponent'));
// Skeleton loading states
{isLoading ? (
<SkeletonLoader />
) : (
<ActualContent data={data} />
)}
// Progressive enhancement
<Suspense fallback={<Spinner />}>
<LazyComponent />
</Suspense>// Proper image sizing
<AppImage
imageSource={source}
width={800}
height={600}
sizes="(max-width: 768px) 100vw, 50vw"
alt="Descriptive alt text"
/>
// Lazy loading implementation
loading="lazy"
placeholder="blur"
blurDataURL={blurredVersion}// Basic rendering tests
test('renders component with required props', () => {
render(<Component title="Test" />);
expect(screen.getByText('Test')).toBeInTheDocument();
});
// Interaction testing
test('handles user interactions', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalled();
});
// Accessibility testing
test('is accessible', async () => {
const { container } = render(<Component />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});// API integration
test('fetches and displays data', async () => {
mockAPI.get('/api/data').mockResolvedValue({ data: testData });
render(<DataComponent />);
await waitFor(() => {
expect(screen.getByText(testData.title)).toBeInTheDocument();
});
});function Component() {
const { _t } = useTranslation();
return (
<div>
<h1>{_t("Welcome to TechDiary")}</h1>
<p>{_t("User count: $", [userCount])}</p>
<button>{_t("Get Started")}</button>
</div>
);
}// Date formatting
const { lang } = useTranslation();
const formattedDate = formattedTime(date, lang);
// Number formatting
const formattedNumber = new Intl.NumberFormat(lang).format(number);
// Currency formatting
const formattedPrice = new Intl.NumberFormat(lang, {
style: 'currency',
currency: 'USD'
}).format(price);class ComponentErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Component error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return this.props.children;
}
}// Network error handling
{error ? (
<div className="error-state">
<p>Failed to load content</p>
<button onClick={retry}>Try Again</button>
</div>
) : (
<MainContent />
)}
// Feature detection
{supportsFeature ? (
<EnhancedComponent />
) : (
<BasicComponent />
)}- Component Structure
// ComponentName.tsx
interface ComponentNameProps {
// Define props interface
}
const ComponentName: React.FC<ComponentNameProps> = ({
prop1,
prop2
}) => {
// Hooks
// Event handlers
// Render logic
return (
// JSX
);
};
export default ComponentName;- Documentation Template
# ComponentName
## Overview
Brief description of the component's purpose.
## Props
Interface definition with descriptions.
## Usage Examples
Practical examples of component usage.
## Features
Key functionality and capabilities.
## Best Practices
Usage recommendations and patterns.- Testing Requirements
- Unit tests for all props and states
- Integration tests for user interactions
- Accessibility tests
- Visual regression tests (if applicable)
- Component follows established patterns
- Props interface is properly typed
- Accessibility requirements met
- Performance optimizations applied
- Error handling implemented
- Documentation updated
- Tests written and passing
- Internationalization considered
src/components/
├── ComponentName.tsx # Main component
├── ComponentName.test.tsx # Tests
├── ComponentName.stories.tsx # Storybook stories
├── hooks/ # Component-specific hooks
├── utils/ # Component utilities
└── types/ # Type definitions
- Components: PascalCase (e.g.,
ArticleCard) - Props: camelCase with descriptive names
- Event handlers:
onActionpattern (e.g.,onUploadComplete) - Boolean props:
isorhasprefix (e.g.,isLoading,hasError)
// Default export for main component
export default ComponentName;
// Named exports for types and utilities
export type { ComponentNameProps };
export { componentUtility };- Hooks Documentation - Custom hooks used in components
- Styling Guide - CSS and design system guidelines
- API Integration - Server state management patterns
- Testing Guide - Component testing strategies