Skip to content

Commit fb45a9f

Browse files
committed
Add ResourceReaction, ResourceReactionable, and UserInformationCard components with detailed documentation
- Implement ResourceReaction component for interactive emoji reactions on articles and comments. - Create ResourceReactionable as a render-prop component for managing reaction logic with optimistic updates and authentication. - Develop UserInformationCard to display user profiles with interactive elements and data fetching. - Include usage examples, props details, and features for each component in their respective documentation.
1 parent 76f8faa commit fb45a9f

File tree

9 files changed

+3829
-0
lines changed

9 files changed

+3829
-0
lines changed

docs/components/AppImage.md

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
# AppImage Component
2+
3+
A wrapper component around Next.js Image that provides Cloudinary integration with automatic optimization and blur placeholders.
4+
5+
## Location
6+
`src/components/AppImage.tsx`
7+
8+
## Overview
9+
AppImage is an optimized image component that handles Cloudinary transformations, automatic format selection, quality optimization, and blur placeholders for better user experience.
10+
11+
## Props
12+
13+
```typescript
14+
interface AppImageProps {
15+
alt?: string;
16+
sizes?: string;
17+
width?: number | `${number}` | undefined;
18+
height?: number | `${number}` | undefined;
19+
imageSource?: IServerFile;
20+
}
21+
```
22+
23+
### Props Details
24+
- `alt`: Alternative text for accessibility
25+
- `sizes`: Responsive image sizes (Next.js Image prop)
26+
- `width`: Image width (Next.js Image prop)
27+
- `height`: Image height (Next.js Image prop)
28+
- `imageSource`: Server file object containing provider and key information
29+
30+
## IServerFile Interface
31+
```typescript
32+
interface IServerFile {
33+
provider: "cloudinary" | "r2" | string;
34+
key: string;
35+
}
36+
```
37+
38+
## Usage Examples
39+
40+
### Basic Usage
41+
```typescript
42+
import AppImage from '@/components/AppImage';
43+
44+
function ArticleCover() {
45+
const imageSource = {
46+
provider: "cloudinary",
47+
key: "articles/my-article-cover"
48+
};
49+
50+
return (
51+
<AppImage
52+
imageSource={imageSource}
53+
alt="Article cover image"
54+
width={800}
55+
height={400}
56+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
57+
/>
58+
);
59+
}
60+
```
61+
62+
### With Different Providers
63+
```typescript
64+
// Cloudinary image
65+
const cloudinaryImage = {
66+
provider: "cloudinary",
67+
key: "profile-photos/user-avatar"
68+
};
69+
70+
// R2/Other provider image
71+
const r2Image = {
72+
provider: "r2",
73+
key: "https://example.com/image.jpg"
74+
};
75+
76+
return (
77+
<div>
78+
<AppImage imageSource={cloudinaryImage} alt="User avatar" />
79+
<AppImage imageSource={r2Image} alt="External image" />
80+
</div>
81+
);
82+
```
83+
84+
### Responsive Image Grid
85+
```typescript
86+
function ImageGallery({ images }: { images: IServerFile[] }) {
87+
return (
88+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
89+
{images.map((image, index) => (
90+
<AppImage
91+
key={index}
92+
imageSource={image}
93+
alt={`Gallery image ${index + 1}`}
94+
width={300}
95+
height={300}
96+
sizes="(max-width: 768px) 50vw, (max-width: 1200px) 33vw, 25vw"
97+
/>
98+
))}
99+
</div>
100+
);
101+
}
102+
```
103+
104+
## Features
105+
106+
### Cloudinary Integration
107+
- **Automatic format**: Selects optimal format (WebP, AVIF, etc.)
108+
- **Quality optimization**: Auto quality based on content
109+
- **URL generation**: Constructs optimized Cloudinary URLs
110+
- **Blur placeholder**: Generates blurred version for loading states
111+
112+
### Performance Optimizations
113+
- **Lazy loading**: Images load only when needed
114+
- **Responsive sizing**: Proper sizes attribute for responsive images
115+
- **Format selection**: Automatic modern format selection
116+
- **Quality adjustment**: Optimal quality for file size balance
117+
118+
### Fallback Handling
119+
- **Provider fallback**: Handles non-Cloudinary providers
120+
- **Default placeholder**: Falls back to local placeholder image
121+
- **Error handling**: Graceful degradation for missing images
122+
123+
## Cloudinary Transformations
124+
125+
### Applied Automatically
126+
```typescript
127+
// Quality optimization
128+
.quality("auto")
129+
130+
// Format optimization
131+
.format("auto")
132+
133+
// Blur placeholder
134+
.effect(blur(100000))
135+
```
136+
137+
### Generated URLs
138+
```typescript
139+
// Original image
140+
"https://res.cloudinary.com/techdiary-dev/image/upload/q_auto,f_auto/v1/path/to/image"
141+
142+
// Blur placeholder
143+
"https://res.cloudinary.com/techdiary-dev/image/upload/q_auto,f_auto,e_blur:100000/v1/path/to/image"
144+
```
145+
146+
## Provider Support
147+
148+
### Cloudinary Provider
149+
- Full transformation support
150+
- Automatic optimization
151+
- Blur placeholder generation
152+
- Format and quality selection
153+
154+
### Other Providers (R2, Direct URLs)
155+
- Direct URL passthrough
156+
- Default placeholder image
157+
- No transformations applied
158+
- Basic Next.js Image functionality
159+
160+
## Implementation Details
161+
162+
### Cloudinary Setup
163+
```typescript
164+
const cld = new Cloudinary({
165+
cloud: { cloudName: "techdiary-dev" },
166+
});
167+
```
168+
169+
### URL Construction
170+
```typescript
171+
// Main image URL
172+
const imageUrl = cld
173+
.image(imageSource.key)
174+
.quality("auto")
175+
.format("auto")
176+
.toURL();
177+
178+
// Blur placeholder URL
179+
const blurUrl = cld
180+
.image(imageSource.key)
181+
.quality("auto")
182+
.format("auto")
183+
.effect(blur(100000))
184+
.toURL();
185+
```
186+
187+
## Best Practices
188+
189+
### Sizing and Responsive
190+
```typescript
191+
// Provide proper dimensions
192+
<AppImage
193+
width={800}
194+
height={600}
195+
sizes="(max-width: 768px) 100vw, 50vw"
196+
/>
197+
198+
// Use aspect ratio classes for responsive design
199+
<div className="aspect-video">
200+
<AppImage imageSource={image} alt="Video thumbnail" />
201+
</div>
202+
```
203+
204+
### Accessibility
205+
```typescript
206+
// Always provide meaningful alt text
207+
<AppImage
208+
imageSource={profilePhoto}
209+
alt={`Profile photo of ${user.name}`}
210+
/>
211+
212+
// Use empty alt for decorative images
213+
<AppImage
214+
imageSource={decorativeImage}
215+
alt=""
216+
role="presentation"
217+
/>
218+
```
219+
220+
### Performance
221+
```typescript
222+
// Use appropriate sizes for responsive images
223+
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
224+
225+
// Preload important images
226+
<link
227+
rel="preload"
228+
as="image"
229+
href={generateImageUrl(imageSource)}
230+
/>
231+
```
232+
233+
## Common Use Cases
234+
235+
### Article Cover Images
236+
```typescript
237+
function ArticleCover({ coverImage }: { coverImage: IServerFile }) {
238+
return (
239+
<div className="aspect-video overflow-hidden rounded-lg">
240+
<AppImage
241+
imageSource={coverImage}
242+
alt="Article cover"
243+
width={1200}
244+
height={630}
245+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 70vw"
246+
/>
247+
</div>
248+
);
249+
}
250+
```
251+
252+
### User Avatars
253+
```typescript
254+
function Avatar({ profilePhoto, userName }: {
255+
profilePhoto: IServerFile;
256+
userName: string;
257+
}) {
258+
return (
259+
<div className="w-10 h-10 rounded-full overflow-hidden">
260+
<AppImage
261+
imageSource={profilePhoto}
262+
alt={`${userName}'s avatar`}
263+
width={40}
264+
height={40}
265+
/>
266+
</div>
267+
);
268+
}
269+
```
270+
271+
### Image Galleries
272+
```typescript
273+
function Gallery({ images }: { images: IServerFile[] }) {
274+
return (
275+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
276+
{images.map((image, index) => (
277+
<AppImage
278+
key={index}
279+
imageSource={image}
280+
alt={`Gallery image ${index + 1}`}
281+
width={400}
282+
height={300}
283+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
284+
/>
285+
))}
286+
</div>
287+
);
288+
}
289+
```
290+
291+
## Error Handling
292+
293+
The component gracefully handles:
294+
- Missing imageSource prop
295+
- Invalid Cloudinary keys
296+
- Network errors
297+
- Unsupported image formats
298+
299+
## Environment Configuration
300+
301+
Ensure Cloudinary is properly configured:
302+
```typescript
303+
// Environment variables
304+
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=techdiary-dev
305+
CLOUDINARY_API_KEY=your_api_key
306+
CLOUDINARY_API_SECRET=your_api_secret
307+
```
308+
309+
## Related Components
310+
311+
- **ImageDropzoneWithCropper**: For image uploads with editing
312+
- **Next.js Image**: Base component being wrapped
313+
- **Cloudinary SDK**: For URL generation and transformations

0 commit comments

Comments
 (0)