@adlas/create-app 1.0.52 → 1.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/docs/FIGMA_TO_CODE_GUIDE.md +490 -177
|
@@ -11,19 +11,25 @@ This document provides comprehensive guidelines for converting Figma designs to
|
|
|
11
11
|
**❌ NEVER use screenshots** - Screenshots lose design data, measurements, and context
|
|
12
12
|
|
|
13
13
|
**✅ ALWAYS use Figma MCP server with node links**:
|
|
14
|
+
|
|
14
15
|
```typescript
|
|
15
16
|
// Use get_design_context tool with node-id from Figma URL
|
|
16
17
|
// Example URL: https://figma.com/design/:fileKey/:fileName?node-id=8486-1580
|
|
17
18
|
// Extract node-id: 8486-1580 → use as: "8486:1580"
|
|
18
19
|
|
|
19
|
-
mcp__figma-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
mcp__figma -
|
|
21
|
+
dev -
|
|
22
|
+
mode -
|
|
23
|
+
mcp -
|
|
24
|
+
server__get_design_context({
|
|
25
|
+
nodeId: "8486:1580",
|
|
26
|
+
clientLanguages: "typescript",
|
|
27
|
+
clientFrameworks: "react,nextjs",
|
|
28
|
+
});
|
|
24
29
|
```
|
|
25
30
|
|
|
26
31
|
**Benefits of using Figma MCP**:
|
|
32
|
+
|
|
27
33
|
- ✅ Get exact measurements, colors, and spacing
|
|
28
34
|
- ✅ Access design tokens and styles
|
|
29
35
|
- ✅ See component structure and hierarchy
|
|
@@ -54,6 +60,7 @@ mcp__figma-dev-mode-mcp-server__get_design_context({
|
|
|
54
60
|
### CRITICAL: Component Styles Must Be Configured, Not Inline
|
|
55
61
|
|
|
56
62
|
**❌ WRONG - Inline component styles**:
|
|
63
|
+
|
|
57
64
|
```tsx
|
|
58
65
|
// DON'T: Apply component-specific styles inline via className
|
|
59
66
|
<Button className="h-10 rounded-full bg-[#19ffa3] px-4 text-sm text-black">
|
|
@@ -70,6 +77,7 @@ mcp__figma-dev-mode-mcp-server__get_design_context({
|
|
|
70
77
|
```
|
|
71
78
|
|
|
72
79
|
**✅ CORRECT - Use props and configured variants**:
|
|
80
|
+
|
|
73
81
|
```tsx
|
|
74
82
|
// Step 1: Configure variants in component wrapper
|
|
75
83
|
// File: src/components/ui/Button.tsx
|
|
@@ -117,6 +125,7 @@ import { Button } from '@/components/ui';
|
|
|
117
125
|
```
|
|
118
126
|
|
|
119
127
|
**Component Style Rules**:
|
|
128
|
+
|
|
120
129
|
- ✅ **Configure in wrapper**: All component-specific styles (height, padding, colors, text size)
|
|
121
130
|
- ✅ **Set defaultVariants**: Define defaults in wrapper so you don't repeat them in usage
|
|
122
131
|
- ✅ **Use theme colors**: `bg-primary`, `bg-secondary`, `bg-success`, `bg-danger`, `bg-warning` from hero.ts
|
|
@@ -129,6 +138,7 @@ import { Button } from '@/components/ui';
|
|
|
129
138
|
- ❌ **NEVER repeat defaultVariants**: Don't specify `size="md"` if `md` is already the default
|
|
130
139
|
|
|
131
140
|
**Color Usage Rules**:
|
|
141
|
+
|
|
132
142
|
```tsx
|
|
133
143
|
// ✅ CORRECT - Use theme colors from hero.ts or globals.css
|
|
134
144
|
color: {
|
|
@@ -150,6 +160,7 @@ secondary, success, danger, warning, default, foreground, background
|
|
|
150
160
|
```
|
|
151
161
|
|
|
152
162
|
**What goes where**:
|
|
163
|
+
|
|
153
164
|
```tsx
|
|
154
165
|
// In Button.tsx configuration:
|
|
155
166
|
const Button = extendVariants(HerouiButton, {
|
|
@@ -194,6 +205,7 @@ import { Icon } from '@/components/ui';
|
|
|
194
205
|
### Data Must Be Mapped from Constants
|
|
195
206
|
|
|
196
207
|
**❌ WRONG - Hardcoded data**:
|
|
208
|
+
|
|
197
209
|
```tsx
|
|
198
210
|
<DropdownMenu>
|
|
199
211
|
<DropdownItem key="option1">Option 1</DropdownItem>
|
|
@@ -203,16 +215,17 @@ import { Icon } from '@/components/ui';
|
|
|
203
215
|
```
|
|
204
216
|
|
|
205
217
|
**✅ CORRECT - Map from constants**:
|
|
218
|
+
|
|
206
219
|
```tsx
|
|
207
220
|
// File: src/config/menuOptions.ts
|
|
208
221
|
export const productMenuItems = [
|
|
209
|
-
{ key:
|
|
210
|
-
{ key:
|
|
211
|
-
{ key:
|
|
222
|
+
{ key: "analytics", labelKey: "product.analytics", href: "/analytics" },
|
|
223
|
+
{ key: "insights", labelKey: "product.insights", href: "/insights" },
|
|
224
|
+
{ key: "reports", labelKey: "product.reports", href: "/reports" },
|
|
212
225
|
];
|
|
213
226
|
|
|
214
227
|
// Usage:
|
|
215
|
-
import { productMenuItems } from
|
|
228
|
+
import { productMenuItems } from "@/config/menuOptions";
|
|
216
229
|
|
|
217
230
|
<DropdownMenu>
|
|
218
231
|
{productMenuItems.map((item) => (
|
|
@@ -220,7 +233,7 @@ import { productMenuItems } from '@/config/menuOptions';
|
|
|
220
233
|
{t(item.labelKey)}
|
|
221
234
|
</DropdownItem>
|
|
222
235
|
))}
|
|
223
|
-
</DropdownMenu
|
|
236
|
+
</DropdownMenu>;
|
|
224
237
|
```
|
|
225
238
|
|
|
226
239
|
---
|
|
@@ -228,11 +241,14 @@ import { productMenuItems } from '@/config/menuOptions';
|
|
|
228
241
|
## 🎨 Component Priority & Usage Strategy
|
|
229
242
|
|
|
230
243
|
### 1. Check HeroUI First
|
|
244
|
+
|
|
231
245
|
Before creating ANY component, check if HeroUI provides it:
|
|
246
|
+
|
|
232
247
|
- **HeroUI Documentation**: https://heroui.com/docs/components
|
|
233
248
|
- **HeroUI Figma Kit**: https://www.figma.com/design/kFGcjHsNKZx7zh2NxEJXYt/HeroUI-Figma-Kit--Community---Community-
|
|
234
249
|
|
|
235
250
|
### 2. Component Selection Flow
|
|
251
|
+
|
|
236
252
|
```
|
|
237
253
|
1. Does HeroUI have this component?
|
|
238
254
|
→ YES: Use HeroUI component (via @/components/ui wrapper if exists)
|
|
@@ -248,6 +264,7 @@ Before creating ANY component, check if HeroUI provides it:
|
|
|
248
264
|
```
|
|
249
265
|
|
|
250
266
|
### 3. Example Decision Tree
|
|
267
|
+
|
|
251
268
|
```tsx
|
|
252
269
|
// Need a Button?
|
|
253
270
|
import { Button } from '@/components/ui'; // ✅ Use HeroUI Button wrapper
|
|
@@ -266,10 +283,12 @@ import { Input } from '@/components/ui'; // ✅ Use HeroUI Input wrapper
|
|
|
266
283
|
## 📁 Project Structure
|
|
267
284
|
|
|
268
285
|
### Pages Location
|
|
286
|
+
|
|
269
287
|
- **With i18n**: `src/app/[locale]/(route-group)/page.tsx`
|
|
270
288
|
- **Without i18n**: `src/app/(route-group)/page.tsx`
|
|
271
289
|
|
|
272
290
|
### Components Location
|
|
291
|
+
|
|
273
292
|
```
|
|
274
293
|
src/components/
|
|
275
294
|
├── ui/ # Reusable UI components (HeroUI wrappers)
|
|
@@ -279,6 +298,7 @@ src/components/
|
|
|
279
298
|
```
|
|
280
299
|
|
|
281
300
|
### Styling
|
|
301
|
+
|
|
282
302
|
- **Global styles**: `src/styles/globals.css`
|
|
283
303
|
- **Theme config**: `src/styles/hero.ts`
|
|
284
304
|
- **Approach**: Tailwind CSS utility classes ONLY
|
|
@@ -291,6 +311,7 @@ src/components/
|
|
|
291
311
|
### Navigation Components
|
|
292
312
|
|
|
293
313
|
#### Navbar (HeroUI)
|
|
314
|
+
|
|
294
315
|
**CRITICAL**: Always use HeroUI Navbar component for navigation bars:
|
|
295
316
|
|
|
296
317
|
```tsx
|
|
@@ -302,8 +323,8 @@ import {
|
|
|
302
323
|
NavbarMenu,
|
|
303
324
|
NavbarMenuItem,
|
|
304
325
|
NavbarMenuToggle,
|
|
305
|
-
} from
|
|
306
|
-
import { Button } from
|
|
326
|
+
} from "@heroui/react";
|
|
327
|
+
import { Button } from "@/components/ui";
|
|
307
328
|
|
|
308
329
|
<Navbar maxWidth="full" className="bg-black border-b-2">
|
|
309
330
|
{/* Logo */}
|
|
@@ -314,14 +335,16 @@ import { Button } from '@/components/ui';
|
|
|
314
335
|
{/* Desktop Menu */}
|
|
315
336
|
<NavbarContent className="hidden lg:flex" justify="center">
|
|
316
337
|
<NavbarItem>
|
|
317
|
-
<Button variant="light">Menu Item</Button>
|
|
338
|
+
<Button variant="light">Menu Item</Button>{" "}
|
|
339
|
+
{/* size="md" omitted - it's default */}
|
|
318
340
|
</NavbarItem>
|
|
319
341
|
</NavbarContent>
|
|
320
342
|
|
|
321
343
|
{/* CTA */}
|
|
322
344
|
<NavbarContent className="hidden lg:flex" justify="end">
|
|
323
345
|
<NavbarItem>
|
|
324
|
-
<Button color="primary">CTA</Button>
|
|
346
|
+
<Button color="primary">CTA</Button>{" "}
|
|
347
|
+
{/* size="md" omitted - it's default */}
|
|
325
348
|
</NavbarItem>
|
|
326
349
|
</NavbarContent>
|
|
327
350
|
|
|
@@ -331,13 +354,16 @@ import { Button } from '@/components/ui';
|
|
|
331
354
|
{/* Mobile Menu */}
|
|
332
355
|
<NavbarMenu>
|
|
333
356
|
<NavbarMenuItem>
|
|
334
|
-
<Button variant="light" fullWidth>
|
|
357
|
+
<Button variant="light" fullWidth>
|
|
358
|
+
Menu Item
|
|
359
|
+
</Button>
|
|
335
360
|
</NavbarMenuItem>
|
|
336
361
|
</NavbarMenu>
|
|
337
|
-
</Navbar
|
|
362
|
+
</Navbar>;
|
|
338
363
|
```
|
|
339
364
|
|
|
340
365
|
**Key Points**:
|
|
366
|
+
|
|
341
367
|
- ✅ Use `Navbar` from `@heroui/react`
|
|
342
368
|
- ✅ Use `Button` from `@/components/ui` for menu items
|
|
343
369
|
- ✅ Use props (`color`, `size`, `variant`) NOT className for styles
|
|
@@ -345,48 +371,128 @@ import { Button } from '@/components/ui';
|
|
|
345
371
|
- ❌ DON'T use custom `<header>` or `<nav>` tags
|
|
346
372
|
|
|
347
373
|
#### Dropdown Menus in Navbar
|
|
374
|
+
|
|
348
375
|
**CRITICAL**: Always use HeroUI Dropdown components for dropdown menus:
|
|
349
376
|
|
|
377
|
+
⚠️ **IMPORTANT - Dropdown Styling Must Match Figma Design Exactly**:
|
|
378
|
+
|
|
379
|
+
HeroUI Dropdown components have default styles that often DON'T match Figma designs. You MUST:
|
|
380
|
+
|
|
381
|
+
1. ✅ Get design context from Figma using `get_design_context` tool
|
|
382
|
+
2. ✅ Check exact colors, spacing, layout, borders from the design
|
|
383
|
+
3. ✅ Use `classNames` prop to override default styles
|
|
384
|
+
4. ✅ Pay attention to: background color, text color, borders, spacing, grid layout
|
|
385
|
+
5. ❌ Don't assume default HeroUI styles will match the design
|
|
386
|
+
|
|
387
|
+
**Common Dropdown Styling Issues**:
|
|
388
|
+
|
|
389
|
+
- Default dropdown has light background, design might use dark background
|
|
390
|
+
- Default text color might be wrong (e.g., white text on white background)
|
|
391
|
+
- Default spacing (gaps, padding) rarely matches Figma exactly
|
|
392
|
+
- Grid layout for multi-column dropdowns needs explicit configuration
|
|
393
|
+
- Section headings need specific color/size overrides
|
|
394
|
+
|
|
350
395
|
```tsx
|
|
351
396
|
import {
|
|
352
397
|
Dropdown,
|
|
353
398
|
DropdownTrigger,
|
|
354
399
|
DropdownMenu,
|
|
355
400
|
DropdownItem,
|
|
401
|
+
DropdownSection, // For grouped items
|
|
356
402
|
} from '@heroui/react';
|
|
357
403
|
import { Button, Icon } from '@/components/ui';
|
|
358
404
|
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
<
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
405
|
+
// ❌ WRONG - Using defaults without checking Figma design
|
|
406
|
+
<Dropdown>
|
|
407
|
+
<DropdownTrigger>
|
|
408
|
+
<Button variant="light" endContent={<Icon name="ChevronDownIcon" />}>
|
|
409
|
+
{t(item.labelKey)}
|
|
410
|
+
</Button>
|
|
411
|
+
</DropdownTrigger>
|
|
412
|
+
<DropdownMenu>
|
|
413
|
+
{item.items.map(subItem => (
|
|
414
|
+
<DropdownItem key={subItem.key} href={subItem.href}>
|
|
415
|
+
{t(subItem.labelKey)}
|
|
416
|
+
</DropdownItem>
|
|
417
|
+
))}
|
|
418
|
+
</DropdownMenu>
|
|
419
|
+
</Dropdown>
|
|
420
|
+
|
|
421
|
+
// ✅ CORRECT - Matching exact Figma design with classNames
|
|
422
|
+
<Dropdown
|
|
423
|
+
classNames={{
|
|
424
|
+
// Match Figma background, border, border-radius, padding
|
|
425
|
+
content: 'bg-[#0A0A0A] border border-[#1F1F1F] rounded-2xl p-8 min-w-[650px]',
|
|
426
|
+
}}
|
|
427
|
+
>
|
|
428
|
+
<DropdownTrigger>
|
|
429
|
+
<Button
|
|
430
|
+
variant="light"
|
|
431
|
+
endContent={<Icon name="ChevronDownIcon" className="size-4 text-white" />}
|
|
432
|
+
className="gap-1 text-base font-normal text-white"
|
|
433
|
+
>
|
|
434
|
+
{t(item.labelKey)}
|
|
435
|
+
</Button>
|
|
436
|
+
</DropdownTrigger>
|
|
437
|
+
<DropdownMenu
|
|
438
|
+
aria-label={t(item.labelKey)}
|
|
439
|
+
className="grid grid-cols-2 gap-8 p-0" // Two-column layout with exact spacing
|
|
440
|
+
itemClasses={{
|
|
441
|
+
base: 'rounded-none p-0 data-[hover=true]:bg-transparent',
|
|
442
|
+
title: 'text-lg font-semibold text-white', // Match Figma text color
|
|
443
|
+
description: 'text-sm text-[#999999] leading-relaxed',
|
|
444
|
+
}}
|
|
445
|
+
>
|
|
446
|
+
<DropdownSection
|
|
447
|
+
title="Core Features"
|
|
448
|
+
showDivider={false}
|
|
449
|
+
classNames={{
|
|
450
|
+
heading: 'text-xs font-medium text-[#666666] uppercase tracking-wider mb-6 px-0',
|
|
451
|
+
group: 'flex flex-col gap-8', // Match exact gap from Figma
|
|
452
|
+
}}
|
|
453
|
+
>
|
|
454
|
+
{coreItems.map(subItem => (
|
|
455
|
+
<DropdownItem
|
|
456
|
+
key={subItem.key}
|
|
457
|
+
description={t(subItem.descriptionKey)}
|
|
458
|
+
href={subItem.href}
|
|
459
|
+
classNames={{
|
|
460
|
+
base: 'flex gap-4 p-0 rounded-none data-[hover=true]:bg-transparent',
|
|
461
|
+
wrapper: 'flex flex-col gap-1',
|
|
462
|
+
}}
|
|
463
|
+
startContent={
|
|
464
|
+
subItem.iconName ? (
|
|
465
|
+
<div className="flex size-12 shrink-0 items-center justify-center rounded-lg border border-primary/40 bg-[#0D1F18] p-3">
|
|
466
|
+
<Icon name={subItem.iconName} className="size-6 text-primary" />
|
|
467
|
+
</div>
|
|
468
|
+
) : null
|
|
469
|
+
}
|
|
470
|
+
>
|
|
373
471
|
{t(subItem.labelKey)}
|
|
374
472
|
</DropdownItem>
|
|
375
473
|
))}
|
|
376
|
-
</
|
|
377
|
-
</
|
|
378
|
-
|
|
379
|
-
<Button variant="light">
|
|
380
|
-
{t(item.labelKey)}
|
|
381
|
-
</Button>
|
|
382
|
-
)}
|
|
474
|
+
</DropdownSection>
|
|
475
|
+
</DropdownMenu>
|
|
476
|
+
</Dropdown>
|
|
383
477
|
```
|
|
384
478
|
|
|
479
|
+
**Dropdown Styling Checklist**:
|
|
480
|
+
|
|
481
|
+
- [ ] Check Figma design context for exact colors (background, text, borders)
|
|
482
|
+
- [ ] Verify spacing values (gap between items, padding, margins)
|
|
483
|
+
- [ ] Confirm layout structure (single column, two columns, grid)
|
|
484
|
+
- [ ] Match border radius, border width, and border colors
|
|
485
|
+
- [ ] Override default hover states if needed (`data-[hover=true]:bg-transparent`)
|
|
486
|
+
- [ ] Check section heading styles (color, size, spacing)
|
|
487
|
+
- [ ] Verify icon sizes and colors match design
|
|
488
|
+
- [ ] Test with actual content to ensure layout matches Figma
|
|
489
|
+
|
|
385
490
|
**Example Menu Configuration** (`src/config/menuItems.ts`):
|
|
491
|
+
|
|
386
492
|
```tsx
|
|
387
493
|
export type MenuItem = {
|
|
388
494
|
key: string;
|
|
389
|
-
labelKey:
|
|
495
|
+
labelKey: "product" | "useCases" | "resources" | "company" | "pricing";
|
|
390
496
|
href?: string;
|
|
391
497
|
hasDropdown?: boolean;
|
|
392
498
|
items?: Array<{
|
|
@@ -398,24 +504,37 @@ export type MenuItem = {
|
|
|
398
504
|
|
|
399
505
|
export const navbarMenuItems: MenuItem[] = [
|
|
400
506
|
{
|
|
401
|
-
key:
|
|
402
|
-
labelKey:
|
|
507
|
+
key: "product",
|
|
508
|
+
labelKey: "product",
|
|
403
509
|
hasDropdown: true,
|
|
404
510
|
items: [
|
|
405
|
-
{
|
|
406
|
-
|
|
407
|
-
|
|
511
|
+
{
|
|
512
|
+
key: "analytics",
|
|
513
|
+
labelKey: "navbar.analytics",
|
|
514
|
+
href: NAVIGATION_URLS.ROOT,
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
key: "dashboard",
|
|
518
|
+
labelKey: "navbar.dashboard",
|
|
519
|
+
href: NAVIGATION_URLS.ROOT,
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
key: "reports",
|
|
523
|
+
labelKey: "navbar.reports",
|
|
524
|
+
href: NAVIGATION_URLS.ROOT,
|
|
525
|
+
},
|
|
408
526
|
],
|
|
409
527
|
},
|
|
410
528
|
{
|
|
411
|
-
key:
|
|
412
|
-
labelKey:
|
|
529
|
+
key: "pricing",
|
|
530
|
+
labelKey: "pricing",
|
|
413
531
|
href: NAVIGATION_URLS.ROOT,
|
|
414
532
|
},
|
|
415
533
|
];
|
|
416
534
|
```
|
|
417
535
|
|
|
418
536
|
**Key Points**:
|
|
537
|
+
|
|
419
538
|
- ✅ Always check `items && items.length > 0` before rendering dropdown
|
|
420
539
|
- ✅ Use `Icon` component with proper name (e.g., `ChevronDownIcon`)
|
|
421
540
|
- ✅ Use `endContent` prop for icons in buttons
|
|
@@ -425,9 +544,11 @@ export const navbarMenuItems: MenuItem[] = [
|
|
|
425
544
|
- ❌ DON'T render dropdown without checking array exists
|
|
426
545
|
|
|
427
546
|
### Form Components
|
|
547
|
+
|
|
428
548
|
All located in `src/components/ui/`:
|
|
429
549
|
|
|
430
550
|
#### Input Fields
|
|
551
|
+
|
|
431
552
|
```tsx
|
|
432
553
|
import { Input, PasswordInput, NumberInput, Textarea } from '@/components/ui';
|
|
433
554
|
|
|
@@ -463,6 +584,7 @@ import { Input, PasswordInput, NumberInput, Textarea } from '@/components/ui';
|
|
|
463
584
|
```
|
|
464
585
|
|
|
465
586
|
#### Selection Components
|
|
587
|
+
|
|
466
588
|
```tsx
|
|
467
589
|
import { Select, RadioGroup, Checkbox, Autocomplete } from '@/components/ui';
|
|
468
590
|
|
|
@@ -495,16 +617,18 @@ import { Select, RadioGroup, Checkbox, Autocomplete } from '@/components/ui';
|
|
|
495
617
|
```
|
|
496
618
|
|
|
497
619
|
#### Date & Time
|
|
620
|
+
|
|
498
621
|
```tsx
|
|
499
|
-
import { DatePicker } from
|
|
622
|
+
import { DatePicker } from "@/components/ui";
|
|
500
623
|
|
|
501
624
|
<DatePicker
|
|
502
625
|
label="Select Date"
|
|
503
626
|
placeholderValue={new CalendarDate(2024, 1, 1)}
|
|
504
|
-
|
|
627
|
+
/>;
|
|
505
628
|
```
|
|
506
629
|
|
|
507
630
|
#### Buttons
|
|
631
|
+
|
|
508
632
|
```tsx
|
|
509
633
|
import { Button } from '@/components/ui';
|
|
510
634
|
|
|
@@ -522,6 +646,7 @@ import { Button } from '@/components/ui';
|
|
|
522
646
|
```
|
|
523
647
|
|
|
524
648
|
#### Other Components
|
|
649
|
+
|
|
525
650
|
```tsx
|
|
526
651
|
import { Modal, Chip, Breadcrumbs, Tabs, Icon } from '@/components/ui';
|
|
527
652
|
|
|
@@ -561,23 +686,27 @@ import { Modal, Chip, Breadcrumbs, Tabs, Icon } from '@/components/ui';
|
|
|
561
686
|
**CRITICAL WORKFLOW**: Icons must follow this exact process:
|
|
562
687
|
|
|
563
688
|
#### Step 1: Export SVG from Figma
|
|
689
|
+
|
|
564
690
|
1. Select the icon in Figma design
|
|
565
691
|
2. Export as SVG (not PNG/JPG)
|
|
566
692
|
3. Download the SVG file
|
|
567
693
|
|
|
568
694
|
#### Step 2: Place in Icons Folder
|
|
695
|
+
|
|
569
696
|
```bash
|
|
570
697
|
# Place SVG file in src/assets/icons/
|
|
571
698
|
mv downloaded-icon.svg src/assets/icons/icon-name.svg
|
|
572
699
|
```
|
|
573
700
|
|
|
574
701
|
#### Step 3: Generate React Components
|
|
702
|
+
|
|
575
703
|
```bash
|
|
576
704
|
# Run the icon generation script
|
|
577
705
|
pnpm generate-icons
|
|
578
706
|
```
|
|
579
707
|
|
|
580
708
|
This command will:
|
|
709
|
+
|
|
581
710
|
- Convert all SVG files in `src/assets/icons/` to React components
|
|
582
711
|
- Place them in `src/components/icons/`
|
|
583
712
|
- **Automatically add "Icon" suffix** to all component names (configured in `svgr.config.mjs`)
|
|
@@ -585,9 +714,35 @@ This command will:
|
|
|
585
714
|
- Update `src/components/icons/index.ts` with exports
|
|
586
715
|
|
|
587
716
|
**Icon Naming Convention**:
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
717
|
+
|
|
718
|
+
⚠️ **IMPORTANT - Icon Naming Inconsistency**:
|
|
719
|
+
The `pnpm generate-icons` script has inconsistent behavior regarding the "Icon" suffix:
|
|
720
|
+
|
|
721
|
+
- Some generated files have "Icon" suffix: `ChevronDownIcon.tsx`, `CloseIcon.tsx`, `EyeIcon.tsx`
|
|
722
|
+
- Some generated files do NOT have "Icon" suffix: `Code.tsx`, `Bag.tsx`, `Magnifier.tsx`, `Stars.tsx`, `WidgetAdd.tsx`
|
|
723
|
+
|
|
724
|
+
**However, the exports in `src/components/icons/index.ts` ALWAYS add "Icon" suffix**:
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
// Even if the file is Code.tsx, the export is:
|
|
728
|
+
export { default as CodeIcon } from "./Code";
|
|
729
|
+
export { default as BagIcon } from "./Bag";
|
|
730
|
+
export { default as MagnifierIcon } from "./Magnifier";
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
**What This Means For You**:
|
|
734
|
+
|
|
735
|
+
1. ✅ **ALWAYS use the exported name WITH "Icon" suffix** when using the Icon component
|
|
736
|
+
2. ✅ Check `src/components/icons/index.ts` to see the actual exported names
|
|
737
|
+
3. ✅ The Icon component requires the exported name (e.g., `"CodeIcon"`, `"BagIcon"`)
|
|
738
|
+
4. ❌ The filename doesn't matter - only the export name matters
|
|
739
|
+
|
|
740
|
+
**Example**:
|
|
741
|
+
|
|
742
|
+
- SVG filename: `code.svg` or `bag.svg`
|
|
743
|
+
- Generated component file: `Code.tsx` or `Bag.tsx` (may or may not have Icon suffix)
|
|
744
|
+
- Exported name: `CodeIcon` or `BagIcon` (ALWAYS has Icon suffix)
|
|
745
|
+
- Usage: `<Icon name="CodeIcon" />` or `<Icon name="BagIcon" />`
|
|
591
746
|
|
|
592
747
|
#### Step 4: Use in Code
|
|
593
748
|
|
|
@@ -603,6 +758,7 @@ import { Icon } from '@/components/ui';
|
|
|
603
758
|
```
|
|
604
759
|
|
|
605
760
|
**❌ WRONG USAGE - DO NOT import individual icon components**:
|
|
761
|
+
|
|
606
762
|
```tsx
|
|
607
763
|
// ❌ WRONG - Don't import and use individual icon components
|
|
608
764
|
import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
@@ -611,6 +767,7 @@ import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
|
611
767
|
```
|
|
612
768
|
|
|
613
769
|
**Available Icons** (auto-generated):
|
|
770
|
+
|
|
614
771
|
- ChevronDownIcon, ChevronRightIcon
|
|
615
772
|
- CloseIcon, MenuIcon
|
|
616
773
|
- EyeIcon, EyeSlashIcon
|
|
@@ -620,11 +777,13 @@ import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
|
620
777
|
- And more...
|
|
621
778
|
|
|
622
779
|
**Icon Names**:
|
|
780
|
+
|
|
623
781
|
- Icon names are PascalCase with "Icon" suffix (e.g., `ChevronDownIcon`)
|
|
624
782
|
- Generated from SVG filename: `chevron-down.svg` → component `ChevronDownIcon`
|
|
625
783
|
- The Icon component uses the exact component name from `@/components/icons`
|
|
626
784
|
|
|
627
785
|
**❌ DO NOT**:
|
|
786
|
+
|
|
628
787
|
- Import individual icon components directly
|
|
629
788
|
- Use kebab-case names (use `ChevronDownIcon` not `chevron-down`)
|
|
630
789
|
- Use Lucide, Font Awesome, or other icon libraries
|
|
@@ -633,6 +792,7 @@ import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
|
633
792
|
- Hardcode SVG paths directly in components
|
|
634
793
|
|
|
635
794
|
**✅ DO**:
|
|
795
|
+
|
|
636
796
|
- Always use `<Icon name="IconName" />` pattern with PascalCase
|
|
637
797
|
- Always export from Figma as SVG
|
|
638
798
|
- Use `pnpm generate-icons` to create components
|
|
@@ -645,6 +805,7 @@ import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
|
645
805
|
#### Step 1: Check Existing Images First
|
|
646
806
|
|
|
647
807
|
**BEFORE implementing or exporting from Figma**:
|
|
808
|
+
|
|
648
809
|
1. Check `public/images/index.ts` to see if the image/logo already exists
|
|
649
810
|
2. Check `public/images/logos/` directory for available logo files
|
|
650
811
|
3. Only export from Figma if the asset doesn't exist
|
|
@@ -656,16 +817,19 @@ ls public/images/logos/
|
|
|
656
817
|
```
|
|
657
818
|
|
|
658
819
|
**Why this matters**:
|
|
820
|
+
|
|
659
821
|
- Avoid duplicate assets
|
|
660
822
|
- User may have already exported the assets
|
|
661
823
|
- Maintain consistency with existing naming conventions
|
|
662
824
|
|
|
663
825
|
#### Step 2: Export from Figma (if needed)
|
|
826
|
+
|
|
664
827
|
1. **For logos**: Export as SVG (preferred) or PNG if source is raster
|
|
665
828
|
2. **For images**: Export as PNG, JPG, or WebP based on image type
|
|
666
829
|
3. Use appropriate resolution (2x or 3x for retina displays)
|
|
667
830
|
|
|
668
831
|
#### Step 3: Place in Public Folder
|
|
832
|
+
|
|
669
833
|
```bash
|
|
670
834
|
# Place logo/image files in public/
|
|
671
835
|
public/
|
|
@@ -691,36 +855,36 @@ public/
|
|
|
691
855
|
*/
|
|
692
856
|
|
|
693
857
|
const LOGOS = {
|
|
694
|
-
PRIMARY_CIRCLE:
|
|
695
|
-
SECONDARY_CIRCLE:
|
|
696
|
-
FULL:
|
|
697
|
-
FULL_BLACK:
|
|
698
|
-
FULL_WHITE:
|
|
699
|
-
ICON:
|
|
858
|
+
PRIMARY_CIRCLE: "/images/logos/logo-circle-primary.svg",
|
|
859
|
+
SECONDARY_CIRCLE: "/images/logos/logo-circle-secondary.svg",
|
|
860
|
+
FULL: "/images/logos/logo-full.svg",
|
|
861
|
+
FULL_BLACK: "/images/logos/logo-full-black.svg",
|
|
862
|
+
FULL_WHITE: "/images/logos/logo-full-white.svg",
|
|
863
|
+
ICON: "/images/logos/logo-icon.svg",
|
|
700
864
|
} as const;
|
|
701
865
|
|
|
702
866
|
const AUTH = {
|
|
703
|
-
LOGIN_BG:
|
|
704
|
-
SIGNUP_BG:
|
|
705
|
-
RESET_PASSWORD:
|
|
867
|
+
LOGIN_BG: "/images/auth/login-background.jpg",
|
|
868
|
+
SIGNUP_BG: "/images/auth/signup-background.jpg",
|
|
869
|
+
RESET_PASSWORD: "/images/auth/reset-password.jpg",
|
|
706
870
|
} as const;
|
|
707
871
|
|
|
708
872
|
const WEBSITE = {
|
|
709
|
-
HOME_HERO:
|
|
710
|
-
HOME_NEWSLETTER:
|
|
711
|
-
ABOUT_HEADER:
|
|
712
|
-
CONTACT_MAP:
|
|
873
|
+
HOME_HERO: "/images/website/home-hero.webp",
|
|
874
|
+
HOME_NEWSLETTER: "/images/website/home-newsletter.webp",
|
|
875
|
+
ABOUT_HEADER: "/images/website/about-header.jpg",
|
|
876
|
+
CONTACT_MAP: "/images/website/contact-map.png",
|
|
713
877
|
} as const;
|
|
714
878
|
|
|
715
879
|
const SERVICES = {
|
|
716
|
-
HEADER:
|
|
717
|
-
FEATURE_1:
|
|
718
|
-
FEATURE_2:
|
|
880
|
+
HEADER: "/images/services/services-header.webp",
|
|
881
|
+
FEATURE_1: "/images/services/feature-1.jpg",
|
|
882
|
+
FEATURE_2: "/images/services/feature-2.jpg",
|
|
719
883
|
} as const;
|
|
720
884
|
|
|
721
885
|
const PRODUCTS = {
|
|
722
|
-
THUMBNAIL_DEFAULT:
|
|
723
|
-
HERO:
|
|
886
|
+
THUMBNAIL_DEFAULT: "/images/products/thumbnail-default.png",
|
|
887
|
+
HERO: "/images/products/hero-banner.jpg",
|
|
724
888
|
} as const;
|
|
725
889
|
|
|
726
890
|
export const IMAGES = {
|
|
@@ -774,12 +938,14 @@ import Image from 'next/image';
|
|
|
774
938
|
```
|
|
775
939
|
|
|
776
940
|
**Preference Order**:
|
|
941
|
+
|
|
777
942
|
1. ✅ **SVG** - Best for logos, icons, and vector graphics (scalable, small file size)
|
|
778
943
|
2. ✅ **WebP** - Modern format for photos (smaller than PNG/JPG)
|
|
779
944
|
3. ✅ **PNG** - For images requiring transparency
|
|
780
945
|
4. ✅ **JPG** - For photos without transparency
|
|
781
946
|
|
|
782
947
|
**❌ DO NOT**:
|
|
948
|
+
|
|
783
949
|
- Put images in `src/` folder
|
|
784
950
|
- Import images as modules unless necessary
|
|
785
951
|
- Use base64 encoded images inline
|
|
@@ -788,6 +954,7 @@ import Image from 'next/image';
|
|
|
788
954
|
- Use hardcoded strings like `src="/images/logos/bina.png"` - Use `src={IMAGES.LOGOS.BINA}` instead
|
|
789
955
|
|
|
790
956
|
**✅ DO**:
|
|
957
|
+
|
|
791
958
|
- Always place in `public/` directory
|
|
792
959
|
- Create centralized image constants file (src/config/images.ts)
|
|
793
960
|
- Organize image paths by category (LOGOS, AUTH, WEBSITE, SERVICES)
|
|
@@ -804,27 +971,30 @@ import Image from 'next/image';
|
|
|
804
971
|
### CRITICAL: Use Theme Colors, NOT Custom Values
|
|
805
972
|
|
|
806
973
|
**Colors are defined in two places**:
|
|
974
|
+
|
|
807
975
|
1. **`src/styles/hero.ts`** - HeroUI theme colors (primary, secondary, etc.)
|
|
808
976
|
2. **`src/styles/globals.css`** - Additional custom theme colors
|
|
809
977
|
|
|
810
978
|
### Available Theme Colors
|
|
811
979
|
|
|
812
980
|
#### From hero.ts (Primary Color Palette):
|
|
981
|
+
|
|
813
982
|
```typescript
|
|
814
983
|
// Primary green shades (defined in hero.ts)
|
|
815
|
-
primary-50
|
|
816
|
-
primary-100
|
|
817
|
-
primary-200
|
|
818
|
-
primary-300
|
|
819
|
-
primary-400
|
|
820
|
-
primary-500
|
|
821
|
-
primary-600
|
|
822
|
-
primary-700
|
|
823
|
-
primary-800
|
|
824
|
-
primary-900
|
|
984
|
+
primary - 50; // #E6FFF0 (lightest)
|
|
985
|
+
primary - 100; // #CBFFE0
|
|
986
|
+
primary - 200; // #AEFFD1
|
|
987
|
+
primary - 300; // #8EFFC2
|
|
988
|
+
primary - 400; // #66FFB2
|
|
989
|
+
primary - 500; // #19FFA3 (default primary)
|
|
990
|
+
primary - 600; // #25C780
|
|
991
|
+
primary - 700; // #26915F
|
|
992
|
+
primary - 800; // #215F40
|
|
993
|
+
primary - 900; // #173123 (darkest)
|
|
825
994
|
```
|
|
826
995
|
|
|
827
996
|
#### HeroUI Default Colors:
|
|
997
|
+
|
|
828
998
|
```tsx
|
|
829
999
|
// Semantic colors (built into HeroUI)
|
|
830
1000
|
bg-primary text-primary border-primary
|
|
@@ -840,6 +1010,7 @@ bg-background text-background border-background
|
|
|
840
1010
|
### Color Usage Examples
|
|
841
1011
|
|
|
842
1012
|
**✅ CORRECT - Use theme colors**:
|
|
1013
|
+
|
|
843
1014
|
```tsx
|
|
844
1015
|
// In component configuration (NO hover states)
|
|
845
1016
|
color: {
|
|
@@ -853,6 +1024,7 @@ className="bg-primary-50 text-primary-900"
|
|
|
853
1024
|
```
|
|
854
1025
|
|
|
855
1026
|
**❌ WRONG - Custom color values or hover states**:
|
|
1027
|
+
|
|
856
1028
|
```tsx
|
|
857
1029
|
// DON'T use hex values or hover states in config
|
|
858
1030
|
color: {
|
|
@@ -872,11 +1044,13 @@ color: {
|
|
|
872
1044
|
### When to Add Custom Colors
|
|
873
1045
|
|
|
874
1046
|
**Only add custom colors to hero.ts if**:
|
|
1047
|
+
|
|
875
1048
|
1. Color is NOT in the existing palette
|
|
876
1049
|
2. Color is used in multiple places (reusable)
|
|
877
1050
|
3. Color is part of the design system
|
|
878
1051
|
|
|
879
1052
|
**Steps to add custom color**:
|
|
1053
|
+
|
|
880
1054
|
```typescript
|
|
881
1055
|
// 1. Add to hero.ts theme
|
|
882
1056
|
export default heroui({
|
|
@@ -885,9 +1059,9 @@ export default heroui({
|
|
|
885
1059
|
colors: {
|
|
886
1060
|
// Add new color palette
|
|
887
1061
|
accent: {
|
|
888
|
-
50:
|
|
889
|
-
500:
|
|
890
|
-
900:
|
|
1062
|
+
50: "#...",
|
|
1063
|
+
500: "#...",
|
|
1064
|
+
900: "#...",
|
|
891
1065
|
},
|
|
892
1066
|
},
|
|
893
1067
|
},
|
|
@@ -895,7 +1069,7 @@ export default heroui({
|
|
|
895
1069
|
});
|
|
896
1070
|
|
|
897
1071
|
// 2. Use in components
|
|
898
|
-
className="bg-accent text-accent-900"
|
|
1072
|
+
className = "bg-accent text-accent-900";
|
|
899
1073
|
```
|
|
900
1074
|
|
|
901
1075
|
---
|
|
@@ -907,21 +1081,23 @@ className="bg-accent text-accent-900"
|
|
|
907
1081
|
**CRITICAL**: Always use closest standard Tailwind class instead of custom arbitrary values
|
|
908
1082
|
|
|
909
1083
|
**Common Conversions**:
|
|
1084
|
+
|
|
910
1085
|
```tsx
|
|
911
1086
|
// ❌ WRONG - Custom arbitrary values
|
|
912
|
-
className="h-[41px] w-[54.6px]"
|
|
913
|
-
className="p-[24px]"
|
|
914
|
-
className="text-[14px]"
|
|
915
|
-
className="gap-[11px]"
|
|
1087
|
+
className = "h-[41px] w-[54.6px]";
|
|
1088
|
+
className = "p-[24px]";
|
|
1089
|
+
className = "text-[14px]";
|
|
1090
|
+
className = "gap-[11px]";
|
|
916
1091
|
|
|
917
1092
|
// ✅ CORRECT - Standard Tailwind classes
|
|
918
|
-
className="h-10 w-14"
|
|
919
|
-
className="p-6"
|
|
920
|
-
className="text-sm"
|
|
921
|
-
className="gap-3"
|
|
1093
|
+
className = "h-10 w-14"; // h-10 = 40px, w-14 = 56px (closest to 54.6px)
|
|
1094
|
+
className = "p-6"; // p-6 = 24px
|
|
1095
|
+
className = "text-sm"; // text-sm = 14px
|
|
1096
|
+
className = "gap-3"; // gap-3 = 12px (closest to 11px)
|
|
922
1097
|
```
|
|
923
1098
|
|
|
924
1099
|
**Tailwind Size Reference**:
|
|
1100
|
+
|
|
925
1101
|
```css
|
|
926
1102
|
/* Spacing Scale (padding, margin, gap, etc.) */
|
|
927
1103
|
0.5 = 2px 4 = 16px 12 = 48px
|
|
@@ -945,11 +1121,13 @@ auto, full, screen, fit, min, max
|
|
|
945
1121
|
```
|
|
946
1122
|
|
|
947
1123
|
**Only use arbitrary values when**:
|
|
1124
|
+
|
|
948
1125
|
- Exact pixel value is required by design system
|
|
949
1126
|
- No standard Tailwind class is close enough
|
|
950
1127
|
- Using design tokens/CSS variables: `bg-[var(--custom-color)]`
|
|
951
1128
|
|
|
952
1129
|
**Examples**:
|
|
1130
|
+
|
|
953
1131
|
```tsx
|
|
954
1132
|
// ✅ Good - Using standard classes
|
|
955
1133
|
<div className="h-10 w-full p-6 text-sm">
|
|
@@ -959,6 +1137,7 @@ auto, full, screen, fit, min, max
|
|
|
959
1137
|
```
|
|
960
1138
|
|
|
961
1139
|
### Responsive Breakpoints (Mobile-First)
|
|
1140
|
+
|
|
962
1141
|
```css
|
|
963
1142
|
/* Mobile */
|
|
964
1143
|
4xs: 320px /* iPhone SE */
|
|
@@ -979,6 +1158,7 @@ xl: 1280px /* MacBook Air */
|
|
|
979
1158
|
```
|
|
980
1159
|
|
|
981
1160
|
### Usage Example
|
|
1161
|
+
|
|
982
1162
|
```tsx
|
|
983
1163
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
984
1164
|
{/* Mobile: 1 column, Tablet: 2 columns, Desktop: 3 columns */}
|
|
@@ -986,7 +1166,9 @@ xl: 1280px /* MacBook Air */
|
|
|
986
1166
|
```
|
|
987
1167
|
|
|
988
1168
|
### HeroUI Color System
|
|
1169
|
+
|
|
989
1170
|
Use these semantic colors from HeroUI theme:
|
|
1171
|
+
|
|
990
1172
|
- `bg-background` - Main background
|
|
991
1173
|
- `bg-foreground` - Text color background
|
|
992
1174
|
- `bg-default` / `bg-default-50` to `bg-default-900` - Gray scale
|
|
@@ -997,6 +1179,7 @@ Use these semantic colors from HeroUI theme:
|
|
|
997
1179
|
- `bg-danger` - Error/danger states
|
|
998
1180
|
|
|
999
1181
|
### Common Patterns
|
|
1182
|
+
|
|
1000
1183
|
```tsx
|
|
1001
1184
|
// Card
|
|
1002
1185
|
<div className="rounded-lg bg-white p-6 shadow-md">
|
|
@@ -1020,19 +1203,20 @@ Use these semantic colors from HeroUI theme:
|
|
|
1020
1203
|
## 📝 Form Implementation with Zod
|
|
1021
1204
|
|
|
1022
1205
|
### Pattern to Follow
|
|
1206
|
+
|
|
1023
1207
|
```tsx
|
|
1024
|
-
|
|
1208
|
+
"use client";
|
|
1025
1209
|
|
|
1026
|
-
import { zodResolver } from
|
|
1027
|
-
import { useForm } from
|
|
1028
|
-
import { z } from
|
|
1210
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
1211
|
+
import { useForm } from "react-hook-form";
|
|
1212
|
+
import { z } from "zod";
|
|
1029
1213
|
|
|
1030
|
-
import { Button, Input } from
|
|
1214
|
+
import { Button, Input } from "@/components/ui";
|
|
1031
1215
|
|
|
1032
1216
|
// 1. Define schema
|
|
1033
1217
|
const schema = z.object({
|
|
1034
|
-
email: z.string().email(
|
|
1035
|
-
password: z.string().min(8,
|
|
1218
|
+
email: z.string().email("Invalid email"),
|
|
1219
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
1036
1220
|
});
|
|
1037
1221
|
|
|
1038
1222
|
type FormData = z.infer<typeof schema>;
|
|
@@ -1059,7 +1243,7 @@ export default function LoginForm() {
|
|
|
1059
1243
|
<Input
|
|
1060
1244
|
label="Email"
|
|
1061
1245
|
type="email"
|
|
1062
|
-
{...register(
|
|
1246
|
+
{...register("email")}
|
|
1063
1247
|
errorMessage={errors.email?.message}
|
|
1064
1248
|
isInvalid={!!errors.email}
|
|
1065
1249
|
/>
|
|
@@ -1067,7 +1251,7 @@ export default function LoginForm() {
|
|
|
1067
1251
|
<Input
|
|
1068
1252
|
label="Password"
|
|
1069
1253
|
type="password"
|
|
1070
|
-
{...register(
|
|
1254
|
+
{...register("password")}
|
|
1071
1255
|
errorMessage={errors.password?.message}
|
|
1072
1256
|
isInvalid={!!errors.password}
|
|
1073
1257
|
/>
|
|
@@ -1089,18 +1273,20 @@ export default function LoginForm() {
|
|
|
1089
1273
|
### Navigation with i18n
|
|
1090
1274
|
|
|
1091
1275
|
**Import Guidelines**:
|
|
1276
|
+
|
|
1092
1277
|
```tsx
|
|
1093
1278
|
// For Link component - use Next.js Link
|
|
1094
|
-
import Link from
|
|
1279
|
+
import Link from "next/link";
|
|
1095
1280
|
|
|
1096
1281
|
// For navigation hooks - use next/navigation
|
|
1097
|
-
import { usePathname, useRouter } from
|
|
1282
|
+
import { usePathname, useRouter } from "next/navigation";
|
|
1098
1283
|
|
|
1099
1284
|
// For navigation URLs - use centralized constants
|
|
1100
|
-
import { NAVIGATION_URLS } from
|
|
1285
|
+
import { NAVIGATION_URLS } from "@/config/navigationUrls";
|
|
1101
1286
|
```
|
|
1102
1287
|
|
|
1103
1288
|
**Navigation Example**:
|
|
1289
|
+
|
|
1104
1290
|
```tsx
|
|
1105
1291
|
import Link from 'next/link';
|
|
1106
1292
|
import { NAVIGATION_URLS } from '@/config/navigationUrls';
|
|
@@ -1117,10 +1303,11 @@ import { Link } from '@heroui/react';
|
|
|
1117
1303
|
```
|
|
1118
1304
|
|
|
1119
1305
|
**Using Navigation Hooks**:
|
|
1306
|
+
|
|
1120
1307
|
```tsx
|
|
1121
|
-
|
|
1308
|
+
"use client";
|
|
1122
1309
|
|
|
1123
|
-
import { usePathname, useRouter } from
|
|
1310
|
+
import { usePathname, useRouter } from "next/navigation";
|
|
1124
1311
|
|
|
1125
1312
|
export function MyComponent() {
|
|
1126
1313
|
const pathname = usePathname();
|
|
@@ -1139,17 +1326,17 @@ export function MyComponent() {
|
|
|
1139
1326
|
**CRITICAL: Specify namespace to avoid TypeScript errors**
|
|
1140
1327
|
|
|
1141
1328
|
```tsx
|
|
1142
|
-
import { useTranslations } from
|
|
1329
|
+
import { useTranslations } from "next-intl";
|
|
1143
1330
|
|
|
1144
1331
|
function MyComponent() {
|
|
1145
1332
|
// ✅ CORRECT - Specify namespace for type safety
|
|
1146
|
-
const t = useTranslations(
|
|
1333
|
+
const t = useTranslations("namespace");
|
|
1147
1334
|
|
|
1148
1335
|
return (
|
|
1149
1336
|
<div>
|
|
1150
1337
|
{/* Now you can use keys directly without type errors */}
|
|
1151
|
-
<h1>{t(
|
|
1152
|
-
<p>{t(
|
|
1338
|
+
<h1>{t("welcome")}</h1>
|
|
1339
|
+
<p>{t("description")}</p>
|
|
1153
1340
|
</div>
|
|
1154
1341
|
);
|
|
1155
1342
|
}
|
|
@@ -1158,42 +1345,141 @@ function MyComponent() {
|
|
|
1158
1345
|
function BadComponent() {
|
|
1159
1346
|
const t = useTranslations(); // Missing namespace
|
|
1160
1347
|
|
|
1161
|
-
const items = [
|
|
1162
|
-
{ key: 'product', labelKey: 'navbar.product' }
|
|
1163
|
-
];
|
|
1348
|
+
const items = [{ key: "product", labelKey: "navbar.product" }];
|
|
1164
1349
|
|
|
1165
1350
|
return (
|
|
1166
1351
|
<div>
|
|
1167
1352
|
{/* TypeScript error: Argument of type 'string' is not assignable */}
|
|
1168
|
-
{items.map(item =>
|
|
1353
|
+
{items.map((item) => (
|
|
1354
|
+
<p key={item.key}>{t(item.labelKey)}</p>
|
|
1355
|
+
))}
|
|
1169
1356
|
</div>
|
|
1170
1357
|
);
|
|
1171
1358
|
}
|
|
1172
1359
|
```
|
|
1173
1360
|
|
|
1174
1361
|
**Type-Safe Translation Pattern:**
|
|
1362
|
+
|
|
1363
|
+
⚠️ **COMMON ERROR - Dynamic Translation Keys with Wrong Namespace**:
|
|
1364
|
+
|
|
1365
|
+
When you have configuration data with translation keys from different namespaces, you'll get TypeScript errors if you try to use a single `useTranslations()` hook:
|
|
1366
|
+
|
|
1367
|
+
```tsx
|
|
1368
|
+
// ❌ WRONG - This causes TypeScript errors
|
|
1369
|
+
const menuItems = [
|
|
1370
|
+
{
|
|
1371
|
+
key: "product",
|
|
1372
|
+
labelKey: "product",
|
|
1373
|
+
items: [
|
|
1374
|
+
{
|
|
1375
|
+
key: "chat",
|
|
1376
|
+
labelKey: "product.chat",
|
|
1377
|
+
descriptionKey: "product.chatDescription",
|
|
1378
|
+
},
|
|
1379
|
+
],
|
|
1380
|
+
},
|
|
1381
|
+
];
|
|
1382
|
+
|
|
1383
|
+
function Navbar() {
|
|
1384
|
+
const tNavbar = useTranslations("navbar"); // Only accepts navbar.* keys
|
|
1385
|
+
const item = menuItems[0];
|
|
1386
|
+
|
|
1387
|
+
return (
|
|
1388
|
+
<div>
|
|
1389
|
+
{/* ❌ ERROR: 'product.chat' is not assignable to navbar keys */}
|
|
1390
|
+
{item.items.map((subItem) => (
|
|
1391
|
+
<div key={subItem.key}>
|
|
1392
|
+
<h3>{tNavbar(subItem.labelKey)}</h3>
|
|
1393
|
+
<p>{tNavbar(subItem.descriptionKey)}</p>
|
|
1394
|
+
</div>
|
|
1395
|
+
))}
|
|
1396
|
+
</div>
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
**✅ SOLUTION - Use Separate Translation Hooks for Each Namespace**:
|
|
1402
|
+
|
|
1175
1403
|
```tsx
|
|
1176
|
-
//
|
|
1404
|
+
// File: src/locales/en.json
|
|
1405
|
+
{
|
|
1406
|
+
"navbar": {
|
|
1407
|
+
"product": "Product",
|
|
1408
|
+
"useCases": "Use cases"
|
|
1409
|
+
},
|
|
1410
|
+
"product": {
|
|
1411
|
+
"chat": "Chat",
|
|
1412
|
+
"chatDescription": "Empower your team with Gen AI"
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// File: src/config/menuItems.ts
|
|
1417
|
+
export type SubMenuItem = {
|
|
1418
|
+
key: string;
|
|
1419
|
+
labelKey: string; // String type because it's from different namespace
|
|
1420
|
+
descriptionKey: string;
|
|
1421
|
+
href: string;
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1177
1424
|
export type MenuItem = {
|
|
1178
1425
|
key: string;
|
|
1179
|
-
|
|
1180
|
-
|
|
1426
|
+
label: string; // Maps to navbar namespace keys
|
|
1427
|
+
items?: SubMenuItem[];
|
|
1181
1428
|
};
|
|
1182
1429
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1430
|
+
export const navbarMenuItems: MenuItem[] = [
|
|
1431
|
+
{
|
|
1432
|
+
key: 'product',
|
|
1433
|
+
label: 'product', // navbar.product
|
|
1434
|
+
items: [
|
|
1435
|
+
{
|
|
1436
|
+
key: 'chat',
|
|
1437
|
+
labelKey: 'chat', // product.chat (different namespace!)
|
|
1438
|
+
descriptionKey: 'chatDescription', // product.chatDescription
|
|
1439
|
+
href: '/chat',
|
|
1440
|
+
},
|
|
1441
|
+
],
|
|
1442
|
+
},
|
|
1443
|
+
];
|
|
1444
|
+
|
|
1445
|
+
// File: Component usage
|
|
1446
|
+
function Navbar() {
|
|
1447
|
+
// ✅ CORRECT - Use separate hooks for each namespace
|
|
1448
|
+
const tNavbar = useTranslations('navbar');
|
|
1449
|
+
const tProduct = useTranslations('product');
|
|
1185
1450
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
)
|
|
1451
|
+
return (
|
|
1452
|
+
<div>
|
|
1453
|
+
{navbarMenuItems.map(item => (
|
|
1454
|
+
<div key={item.key}>
|
|
1455
|
+
{/* Use tNavbar for navbar.* keys */}
|
|
1456
|
+
<button>{tNavbar(item.label)}</button>
|
|
1457
|
+
|
|
1458
|
+
{/* Use tProduct for product.* keys */}
|
|
1459
|
+
{item.items?.map(subItem => (
|
|
1460
|
+
<div key={subItem.key}>
|
|
1461
|
+
<h3>{tProduct(subItem.labelKey)}</h3>
|
|
1462
|
+
<p>{tProduct(subItem.descriptionKey)}</p>
|
|
1463
|
+
</div>
|
|
1464
|
+
))}
|
|
1465
|
+
</div>
|
|
1466
|
+
))}
|
|
1467
|
+
</div>
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1192
1470
|
```
|
|
1193
1471
|
|
|
1472
|
+
**Key Points**:
|
|
1473
|
+
|
|
1474
|
+
1. ✅ Separate translation hooks for each namespace (`tNavbar`, `tProduct`)
|
|
1475
|
+
2. ✅ Use the correct hook for each translation key based on its namespace
|
|
1476
|
+
3. ✅ Keep translation namespaces flat and separate (don't nest `product` inside `navbar`)
|
|
1477
|
+
4. ❌ Don't try to use one translation hook for keys from multiple namespaces
|
|
1478
|
+
|
|
1194
1479
|
### Form labels, placeholders, errors - ALL must use i18n
|
|
1480
|
+
|
|
1195
1481
|
```tsx
|
|
1196
|
-
import { useTranslations } from
|
|
1482
|
+
import { useTranslations } from "next-intl";
|
|
1197
1483
|
|
|
1198
1484
|
function LoginForm() {
|
|
1199
1485
|
const t = useTranslations();
|
|
@@ -1202,9 +1488,11 @@ function LoginForm() {
|
|
|
1202
1488
|
<form>
|
|
1203
1489
|
{/* ✅ CORRECT */}
|
|
1204
1490
|
<Input
|
|
1205
|
-
label={t(
|
|
1206
|
-
placeholder={t(
|
|
1207
|
-
errorMessage={
|
|
1491
|
+
label={t("email")}
|
|
1492
|
+
placeholder={t("enterEmail")}
|
|
1493
|
+
errorMessage={
|
|
1494
|
+
errors.email?.message ? t(errors.email.message) : undefined
|
|
1495
|
+
}
|
|
1208
1496
|
/>
|
|
1209
1497
|
|
|
1210
1498
|
{/* ❌ WRONG - Hardcoded text */}
|
|
@@ -1219,6 +1507,7 @@ function LoginForm() {
|
|
|
1219
1507
|
```
|
|
1220
1508
|
|
|
1221
1509
|
### Navigation URLs Reference
|
|
1510
|
+
|
|
1222
1511
|
```tsx
|
|
1223
1512
|
import { NAVIGATION_URLS } from '@/config/navigationUrls';
|
|
1224
1513
|
|
|
@@ -1243,19 +1532,20 @@ NAVIGATION_URLS.DASHBOARD.INDEX // '/dashboard'
|
|
|
1243
1532
|
## 🎯 Data Fetching
|
|
1244
1533
|
|
|
1245
1534
|
### Using React Query
|
|
1535
|
+
|
|
1246
1536
|
```tsx
|
|
1247
|
-
|
|
1537
|
+
"use client";
|
|
1248
1538
|
|
|
1249
|
-
import { useQuery } from
|
|
1250
|
-
import axios from
|
|
1539
|
+
import { useQuery } from "@tanstack/react-query";
|
|
1540
|
+
import axios from "axios";
|
|
1251
1541
|
|
|
1252
|
-
import { QUERY_KEYS } from
|
|
1542
|
+
import { QUERY_KEYS } from "@/libs/react-query";
|
|
1253
1543
|
|
|
1254
1544
|
export default function ProductList() {
|
|
1255
1545
|
const { data, isLoading, error } = useQuery({
|
|
1256
1546
|
queryKey: QUERY_KEYS.PRODUCTS.LIST,
|
|
1257
1547
|
queryFn: async () => {
|
|
1258
|
-
const response = await axios.get(
|
|
1548
|
+
const response = await axios.get("/api/products");
|
|
1259
1549
|
return response.data;
|
|
1260
1550
|
},
|
|
1261
1551
|
});
|
|
@@ -1278,11 +1568,12 @@ export default function ProductList() {
|
|
|
1278
1568
|
## 📐 Layout Components
|
|
1279
1569
|
|
|
1280
1570
|
### Using Existing Layouts
|
|
1571
|
+
|
|
1281
1572
|
```tsx
|
|
1282
1573
|
// Auth pages use auth layout automatically
|
|
1283
1574
|
// Location: src/app/[locale]/(auth)/login/page.tsx
|
|
1284
1575
|
|
|
1285
|
-
|
|
1576
|
+
"use client";
|
|
1286
1577
|
|
|
1287
1578
|
export default function LoginPage() {
|
|
1288
1579
|
return (
|
|
@@ -1295,11 +1586,14 @@ export default function LoginPage() {
|
|
|
1295
1586
|
```
|
|
1296
1587
|
|
|
1297
1588
|
### Navbar & Footer
|
|
1589
|
+
|
|
1298
1590
|
Already implemented in `src/components/layout/`:
|
|
1591
|
+
|
|
1299
1592
|
- `Navbar.tsx` - Main navigation using HeroUI Navbar
|
|
1300
1593
|
- `Footer.tsx` - Footer component
|
|
1301
1594
|
|
|
1302
1595
|
**CRITICAL**: Navbar uses HeroUI Navbar component:
|
|
1596
|
+
|
|
1303
1597
|
```tsx
|
|
1304
1598
|
import { Navbar } from '@/components/layout';
|
|
1305
1599
|
|
|
@@ -1309,6 +1603,7 @@ import { Navbar } from '@/components/layout';
|
|
|
1309
1603
|
```
|
|
1310
1604
|
|
|
1311
1605
|
**Implementation details** (see `src/components/layout/Navbar.tsx` and `docs/NAVBAR_IMPLEMENTATION.md`):
|
|
1606
|
+
|
|
1312
1607
|
- ✅ Uses HeroUI `Navbar`, `NavbarBrand`, `NavbarContent`, `NavbarItem`, `Dropdown`
|
|
1313
1608
|
- ✅ Uses `Button` from `@/components/ui` with props (`color`, `size`, `variant`)
|
|
1314
1609
|
- ✅ Uses `useTranslations('navbar')` with namespace for type safety
|
|
@@ -1323,6 +1618,7 @@ import { Navbar } from '@/components/layout';
|
|
|
1323
1618
|
## ⚠️ CRITICAL RULES
|
|
1324
1619
|
|
|
1325
1620
|
### ❌ DO NOT
|
|
1621
|
+
|
|
1326
1622
|
1. ❌ **Figma**: Use screenshots to implement design - MUST use Figma MCP server with `get_design_context`
|
|
1327
1623
|
2. ❌ **Figma**: Call `get_screenshot` tool - Design context provides all needed information
|
|
1328
1624
|
3. ❌ **Icons**: Import individual icon components (e.g., `import { ChevronDownIcon }`) - MUST use `<Icon name="ChevronDownIcon" />`
|
|
@@ -1334,28 +1630,29 @@ import { Navbar } from '@/components/layout';
|
|
|
1334
1630
|
9. ❌ **Images**: Use direct path strings anywhere - ALL paths MUST come from `@/config/images`
|
|
1335
1631
|
10. ❌ **Tailwind**: Use arbitrary values when standard classes exist (e.g., `h-[40px]` → use `h-10`)
|
|
1336
1632
|
11. ❌ **Tailwind**: Use custom values like `w-[100%]` (use `w-full`)
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1633
|
+
12. ❌ **Colors**: Use custom color values (e.g., `bg-[#19ffa3]`, `text-[#ff0000]`) - MUST use theme colors from hero.ts
|
|
1634
|
+
13. ❌ **Colors**: Use hex, rgb, or hsl values inline - Use semantic color classes instead
|
|
1635
|
+
14. ❌ **Text**: Hardcode ANY text - ALL text MUST use i18n translation keys
|
|
1636
|
+
15. ❌ **URLs**: Hardcode URLs - ALWAYS use `NAVIGATION_URLS` from config
|
|
1637
|
+
16. ❌ **Data**: Hardcode menu items, dropdown options - MUST map from constants
|
|
1638
|
+
17. ❌ **Components**: Create custom components when HeroUI provides them (e.g., use HeroUI `Navbar` not custom `<header>`)
|
|
1639
|
+
18. ❌ **Components**: Use HTML elements when HeroUI component exists (e.g., `<button>` → use HeroUI `<Button>`)
|
|
1640
|
+
19. ❌ **Styling**: Apply component styles via className - MUST use props (`color`, `size`, `variant`, `radius`)
|
|
1641
|
+
20. ❌ **Styling**: Use className for component styles (e.g., `className="h-10 px-4 bg-primary"`) - Configure in wrapper instead
|
|
1642
|
+
21. ❌ **Styling**: Use CSS modules or styled-components - ONLY Tailwind CSS
|
|
1643
|
+
22. ❌ **Imports**: Import from `@heroui/react` directly (use `@/components/ui` wrappers when available)
|
|
1644
|
+
23. ❌ **Navigation**: Use HeroUI `Link` for internal navigation (use Next.js `Link` from `next/link`)
|
|
1645
|
+
24. ❌ **Navigation**: Import `usePathname`, `useRouter` from wrong source (MUST use `next/navigation`)
|
|
1646
|
+
25. ❌ **TypeScript**: Use `any` type - Use union types and proper typing instead
|
|
1647
|
+
26. ❌ **i18n**: Use `useTranslations()` without namespace - MUST specify namespace for type safety
|
|
1648
|
+
27. ❌ **i18n**: Use dynamic translation keys without proper typing - Define union types for keys
|
|
1649
|
+
28. ❌ **Design**: Ignore responsive design (always implement mobile-first)
|
|
1650
|
+
29. ❌ **Forms**: Skip form validation (always use Zod schemas)
|
|
1651
|
+
30. ❌ **Dropdowns**: Render dropdown items without checking if items array exists/has length
|
|
1652
|
+
31. ❌ **Build**: Run `pnpm build` or `pnpm lint` commands - The user will run these manually
|
|
1357
1653
|
|
|
1358
1654
|
### ✅ DO
|
|
1655
|
+
|
|
1359
1656
|
1. ✅ **Figma**: Use Figma MCP server with `get_design_context` and node-id from Figma URL
|
|
1360
1657
|
2. ✅ **Figma**: Extract node-id from URL (e.g., `node-id=8486-1580` → `"8486:1580"`)
|
|
1361
1658
|
3. ✅ **Icons**: Export SVG from Figma → `src/assets/icons/` → Run `pnpm generate-icons`
|
|
@@ -1369,30 +1666,30 @@ import { Navbar } from '@/components/layout';
|
|
|
1369
1666
|
11. ✅ **Images**: Prefer SVG over PNG when source is vector
|
|
1370
1667
|
12. ✅ **Tailwind**: Use standard classes (`h-10`, `p-6`, `text-sm` instead of arbitrary values)
|
|
1371
1668
|
13. ✅ **Tailwind**: Use size utilities (`size-4`, `size-5`) for icons
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1669
|
+
14. ✅ **Colors**: Use theme colors from hero.ts (`bg-primary`, `text-primary-600`, `bg-success`)
|
|
1670
|
+
15. ✅ **Colors**: Check hero.ts and globals.css for available colors before using custom values
|
|
1671
|
+
16. ✅ **Navigation**: Use Next.js `Link` from `next/link` for internal navigation
|
|
1672
|
+
17. ✅ **Navigation**: Import `usePathname`, `useRouter` from `next/navigation`
|
|
1673
|
+
18. ✅ **Text**: Use i18n translation keys for ALL text (`t('key')`)
|
|
1674
|
+
19. ✅ **URLs**: Use `NAVIGATION_URLS` for ALL navigation links
|
|
1675
|
+
20. ✅ **Data**: Map menu items, dropdown options from constants (never hardcode)
|
|
1676
|
+
21. ✅ **Components**: Use HeroUI components first (e.g., use `Navbar` from `@heroui/react` for navigation)
|
|
1677
|
+
22. ✅ **Components**: Use existing wrappers from `src/components/ui/` when available (e.g., `Button`)
|
|
1678
|
+
23. ✅ **Components**: Configure component styles in wrappers using `extendVariants`
|
|
1679
|
+
24. ✅ **Components**: Set `defaultVariants` in wrapper configuration to avoid repetition
|
|
1680
|
+
25. ✅ **Styling**: Use component props for styles, but ONLY specify non-default values
|
|
1681
|
+
26. ✅ **Styling**: Omit props that match defaultVariants (e.g., don't write `size="md"` if `md` is default)
|
|
1682
|
+
27. ✅ **Styling**: Use className ONLY for layout utilities (`mt-4`, `hidden`, `lg:flex`)
|
|
1683
|
+
28. ✅ **Styling**: Use Tailwind CSS ONLY (utility classes)
|
|
1684
|
+
29. ✅ **TypeScript**: Implement strict typing with proper types (NO `any`)
|
|
1685
|
+
30. ✅ **TypeScript**: Use union types for translation keys (e.g., `labelKey: 'product' | 'useCases'`)
|
|
1686
|
+
31. ✅ **i18n**: Specify namespace in `useTranslations('namespace')` for type safety
|
|
1687
|
+
32. ✅ **i18n**: Define typed keys in config to avoid dynamic string issues
|
|
1688
|
+
33. ✅ **Dropdowns**: Check array exists and has length before mapping (`items && items.length > 0`)
|
|
1689
|
+
34. ✅ **React**: Use `'use client'` directive for interactive components
|
|
1690
|
+
35. ✅ **Design**: Follow mobile-first responsive design
|
|
1691
|
+
36. ✅ **States**: Implement proper loading and error states
|
|
1692
|
+
37. ✅ **Patterns**: Follow existing code patterns and naming conventions
|
|
1396
1693
|
|
|
1397
1694
|
---
|
|
1398
1695
|
|
|
@@ -1401,6 +1698,7 @@ import { Navbar } from '@/components/layout';
|
|
|
1401
1698
|
When converting Figma to code, ensure:
|
|
1402
1699
|
|
|
1403
1700
|
### Icons & Assets
|
|
1701
|
+
|
|
1404
1702
|
- [ ] Exported icons as SVG from Figma
|
|
1405
1703
|
- [ ] Placed SVG files in `src/assets/icons/`
|
|
1406
1704
|
- [ ] Ran `pnpm generate-icons` to create React components
|
|
@@ -1415,6 +1713,7 @@ When converting Figma to code, ensure:
|
|
|
1415
1713
|
- [ ] Referenced via `IMAGES.LOGOS.PRIMARY` (not hardcoded paths)
|
|
1416
1714
|
|
|
1417
1715
|
### Component & Styling
|
|
1716
|
+
|
|
1418
1717
|
- [ ] Checked HeroUI library first for component availability
|
|
1419
1718
|
- [ ] Used HeroUI components via `src/components/ui/` wrappers (e.g., `<Button>` not `<button>`)
|
|
1420
1719
|
- [ ] Configured component styles in wrapper/theme (not inline)
|
|
@@ -1425,6 +1724,7 @@ When converting Figma to code, ensure:
|
|
|
1425
1724
|
- [ ] Mapped all data from constants (no hardcoded menu items/dropdowns)
|
|
1426
1725
|
|
|
1427
1726
|
### Content & Navigation
|
|
1727
|
+
|
|
1428
1728
|
- [ ] ALL text uses i18n translation keys (no hardcoded text)
|
|
1429
1729
|
- [ ] ALL labels use `t('labelKey')`
|
|
1430
1730
|
- [ ] ALL placeholders use `t('placeholderKey')`
|
|
@@ -1433,18 +1733,21 @@ When converting Figma to code, ensure:
|
|
|
1433
1733
|
- [ ] ALL URLs use `NAVIGATION_URLS` from config (no hardcoded URLs)
|
|
1434
1734
|
|
|
1435
1735
|
### Structure & Types
|
|
1736
|
+
|
|
1436
1737
|
- [ ] Page created in correct location (`src/app/[locale]/...`)
|
|
1437
1738
|
- [ ] Added TypeScript types for all data structures
|
|
1438
1739
|
- [ ] Used `'use client'` for interactive components
|
|
1439
1740
|
- [ ] No `any` types used
|
|
1440
1741
|
|
|
1441
1742
|
### Forms & Validation
|
|
1743
|
+
|
|
1442
1744
|
- [ ] Implemented forms with Zod validation schemas
|
|
1443
1745
|
- [ ] Form labels use i18n
|
|
1444
1746
|
- [ ] Form placeholders use i18n
|
|
1445
1747
|
- [ ] Form errors use i18n
|
|
1446
1748
|
|
|
1447
1749
|
### Responsive & States
|
|
1750
|
+
|
|
1448
1751
|
- [ ] Implemented responsive design (mobile-first)
|
|
1449
1752
|
- [ ] Added proper loading states
|
|
1450
1753
|
- [ ] Added proper error states
|
|
@@ -1457,12 +1760,14 @@ When converting Figma to code, ensure:
|
|
|
1457
1760
|
Before finalizing code, verify:
|
|
1458
1761
|
|
|
1459
1762
|
### Icons & Assets
|
|
1763
|
+
|
|
1460
1764
|
1. **Icons workflow** - All icons created via `pnpm generate-icons` (not manual)
|
|
1461
1765
|
2. **Icons source** - SVG files exist in `src/assets/icons/`
|
|
1462
1766
|
3. **Logos location** - All logos/images in `public/` directory (not `src/`)
|
|
1463
1767
|
4. **Image format** - SVG used for logos when possible (not PNG)
|
|
1464
1768
|
|
|
1465
1769
|
### Components & Styling
|
|
1770
|
+
|
|
1466
1771
|
1. **HeroUI components first** - Verify HeroUI library was checked before custom implementation
|
|
1467
1772
|
2. **No direct HeroUI imports** - Only through `@/components/ui`
|
|
1468
1773
|
3. **Component reuse** - Don't duplicate existing components
|
|
@@ -1472,17 +1777,20 @@ Before finalizing code, verify:
|
|
|
1472
1777
|
7. **Map from constants** - All menu items, dropdown options mapped from config files
|
|
1473
1778
|
|
|
1474
1779
|
### i18n & Navigation
|
|
1780
|
+
|
|
1475
1781
|
5. **Zero hardcoded text** - ALL text uses `t('key')`
|
|
1476
1782
|
6. **Zero hardcoded URLs** - ALL links use `NAVIGATION_URLS`
|
|
1477
1783
|
7. **Form fields i18n** - Labels, placeholders, errors all use translation keys
|
|
1478
1784
|
|
|
1479
1785
|
### Styling & Layout
|
|
1786
|
+
|
|
1480
1787
|
8. **Tailwind only** - No CSS modules, no styled-components
|
|
1481
1788
|
9. **HeroUI theme colors** - Use semantic colors, not arbitrary values
|
|
1482
1789
|
10. **Consistent spacing** - Use `space-y-*` and `space-x-*` utilities
|
|
1483
1790
|
11. **Mobile-first** - Base styles for mobile, then `md:`, `lg:`, etc.
|
|
1484
1791
|
|
|
1485
1792
|
### TypeScript & Validation
|
|
1793
|
+
|
|
1486
1794
|
12. **Proper TypeScript** - No `any`, all props typed
|
|
1487
1795
|
13. **Form validation** - Zod schemas for all forms
|
|
1488
1796
|
|
|
@@ -1491,6 +1799,7 @@ Before finalizing code, verify:
|
|
|
1491
1799
|
## 📚 Reference Files
|
|
1492
1800
|
|
|
1493
1801
|
When in doubt, check these example files:
|
|
1802
|
+
|
|
1494
1803
|
- **Form example**: `src/components/ui/form/Form.tsx`
|
|
1495
1804
|
- **Page example**: `src/app/[locale]/(auth)/login/page.tsx`
|
|
1496
1805
|
- **Component example**: `src/components/ui/Input.tsx`
|
|
@@ -1502,6 +1811,7 @@ When in doubt, check these example files:
|
|
|
1502
1811
|
## 🚀 Quick Reference Guide
|
|
1503
1812
|
|
|
1504
1813
|
### Icons Workflow
|
|
1814
|
+
|
|
1505
1815
|
```bash
|
|
1506
1816
|
# 1. Export SVG from Figma
|
|
1507
1817
|
# 2. Place in folder
|
|
@@ -1516,6 +1826,7 @@ import { IconIcon } from '@/components/icons';
|
|
|
1516
1826
|
```
|
|
1517
1827
|
|
|
1518
1828
|
### Logos/Images Workflow
|
|
1829
|
+
|
|
1519
1830
|
```bash
|
|
1520
1831
|
# 1. Export SVG/PNG from Figma
|
|
1521
1832
|
# 2. Place in public with organized folders
|
|
@@ -1548,6 +1859,7 @@ import { IMAGES } from '@/config/images';
|
|
|
1548
1859
|
```
|
|
1549
1860
|
|
|
1550
1861
|
### Tailwind Standard Classes
|
|
1862
|
+
|
|
1551
1863
|
```tsx
|
|
1552
1864
|
// Heights/Widths
|
|
1553
1865
|
h-10 = 40px w-14 = 56px size-5 = 20px
|
|
@@ -1567,6 +1879,7 @@ text-lg = 18px text-3xl = 30px
|
|
|
1567
1879
|
```
|
|
1568
1880
|
|
|
1569
1881
|
### Common Patterns
|
|
1882
|
+
|
|
1570
1883
|
```tsx
|
|
1571
1884
|
// Icons (auto-generated)
|
|
1572
1885
|
import { MenuIcon, CloseIcon } from '@/components/icons';
|