@adlas/create-app 1.0.50 → 1.0.52
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 +291 -227
package/package.json
CHANGED
|
@@ -11,25 +11,19 @@ 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
|
-
|
|
15
14
|
```typescript
|
|
16
15
|
// Use get_design_context tool with node-id from Figma URL
|
|
17
16
|
// Example URL: https://figma.com/design/:fileKey/:fileName?node-id=8486-1580
|
|
18
17
|
// Extract node-id: 8486-1580 → use as: "8486:1580"
|
|
19
18
|
|
|
20
|
-
mcp__figma
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
nodeId: "8486:1580",
|
|
26
|
-
clientLanguages: "typescript",
|
|
27
|
-
clientFrameworks: "react,nextjs",
|
|
28
|
-
});
|
|
19
|
+
mcp__figma-dev-mode-mcp-server__get_design_context({
|
|
20
|
+
nodeId: "8486:1580",
|
|
21
|
+
clientLanguages: "typescript",
|
|
22
|
+
clientFrameworks: "react,nextjs"
|
|
23
|
+
})
|
|
29
24
|
```
|
|
30
25
|
|
|
31
26
|
**Benefits of using Figma MCP**:
|
|
32
|
-
|
|
33
27
|
- ✅ Get exact measurements, colors, and spacing
|
|
34
28
|
- ✅ Access design tokens and styles
|
|
35
29
|
- ✅ See component structure and hierarchy
|
|
@@ -51,6 +45,7 @@ mcp__figma -
|
|
|
51
45
|
9. **Follow project structure** - Place files in correct locations as defined below
|
|
52
46
|
10. **Match coding patterns** - Use the same patterns found in existing code
|
|
53
47
|
11. **Type safety first** - Use TypeScript strictly, no `any` types
|
|
48
|
+
12. **❌ NEVER run build** - Do NOT run `pnpm build` or `pnpm lint` commands. The user will run these manually
|
|
54
49
|
|
|
55
50
|
---
|
|
56
51
|
|
|
@@ -59,7 +54,6 @@ mcp__figma -
|
|
|
59
54
|
### CRITICAL: Component Styles Must Be Configured, Not Inline
|
|
60
55
|
|
|
61
56
|
**❌ WRONG - Inline component styles**:
|
|
62
|
-
|
|
63
57
|
```tsx
|
|
64
58
|
// DON'T: Apply component-specific styles inline via className
|
|
65
59
|
<Button className="h-10 rounded-full bg-[#19ffa3] px-4 text-sm text-black">
|
|
@@ -76,7 +70,6 @@ mcp__figma -
|
|
|
76
70
|
```
|
|
77
71
|
|
|
78
72
|
**✅ CORRECT - Use props and configured variants**:
|
|
79
|
-
|
|
80
73
|
```tsx
|
|
81
74
|
// Step 1: Configure variants in component wrapper
|
|
82
75
|
// File: src/components/ui/Button.tsx
|
|
@@ -86,13 +79,16 @@ export const Button = extendVariants(HeroUIButton, {
|
|
|
86
79
|
variants: {
|
|
87
80
|
color: {
|
|
88
81
|
// ✅ Use theme colors from hero.ts, NOT custom colors
|
|
89
|
-
primary: 'bg-primary text-black',
|
|
82
|
+
primary: 'bg-primary-500 text-black',
|
|
90
83
|
},
|
|
91
84
|
size: {
|
|
92
|
-
sm: 'h-
|
|
85
|
+
sm: 'h-10 px-4 text-sm',
|
|
93
86
|
md: 'h-10 px-4 text-sm',
|
|
94
87
|
lg: 'h-12 px-6 text-base',
|
|
95
88
|
},
|
|
89
|
+
radius: {
|
|
90
|
+
full: 'rounded-full',
|
|
91
|
+
},
|
|
96
92
|
},
|
|
97
93
|
defaultVariants: {
|
|
98
94
|
size: 'md',
|
|
@@ -121,7 +117,6 @@ import { Button } from '@/components/ui';
|
|
|
121
117
|
```
|
|
122
118
|
|
|
123
119
|
**Component Style Rules**:
|
|
124
|
-
|
|
125
120
|
- ✅ **Configure in wrapper**: All component-specific styles (height, padding, colors, text size)
|
|
126
121
|
- ✅ **Set defaultVariants**: Define defaults in wrapper so you don't repeat them in usage
|
|
127
122
|
- ✅ **Use theme colors**: `bg-primary`, `bg-secondary`, `bg-success`, `bg-danger`, `bg-warning` from hero.ts
|
|
@@ -134,7 +129,6 @@ import { Button } from '@/components/ui';
|
|
|
134
129
|
- ❌ **NEVER repeat defaultVariants**: Don't specify `size="md"` if `md` is already the default
|
|
135
130
|
|
|
136
131
|
**Color Usage Rules**:
|
|
137
|
-
|
|
138
132
|
```tsx
|
|
139
133
|
// ✅ CORRECT - Use theme colors from hero.ts or globals.css
|
|
140
134
|
color: {
|
|
@@ -156,7 +150,6 @@ secondary, success, danger, warning, default, foreground, background
|
|
|
156
150
|
```
|
|
157
151
|
|
|
158
152
|
**What goes where**:
|
|
159
|
-
|
|
160
153
|
```tsx
|
|
161
154
|
// In Button.tsx configuration:
|
|
162
155
|
const Button = extendVariants(HerouiButton, {
|
|
@@ -177,16 +170,17 @@ const Button = extendVariants(HerouiButton, {
|
|
|
177
170
|
|
|
178
171
|
// In component usage:
|
|
179
172
|
<Button
|
|
180
|
-
color="primary" // ✅
|
|
181
|
-
|
|
173
|
+
color="primary" // ✅ Configured variant
|
|
174
|
+
size="sm" // ✅ Configured variant (can omit if md is default)
|
|
175
|
+
radius="full" // ✅ Configured radius variant
|
|
182
176
|
className="mt-4 hidden lg:block" // ✅ Layout utilities only
|
|
183
177
|
>
|
|
184
178
|
{t('navbar.bookDemo')} // ✅ i18n text
|
|
185
179
|
</Button>
|
|
186
|
-
// Note:
|
|
180
|
+
// Note: Omit props that match defaultVariants to keep code clean
|
|
187
181
|
|
|
188
182
|
// Using size prop when you need different size:
|
|
189
|
-
<Button color="primary" size="lg"> // ✅ Override default size
|
|
183
|
+
<Button color="primary" size="lg" radius="full"> // ✅ Override default size
|
|
190
184
|
Large Button
|
|
191
185
|
</Button>
|
|
192
186
|
|
|
@@ -200,7 +194,6 @@ import { Icon } from '@/components/ui';
|
|
|
200
194
|
### Data Must Be Mapped from Constants
|
|
201
195
|
|
|
202
196
|
**❌ WRONG - Hardcoded data**:
|
|
203
|
-
|
|
204
197
|
```tsx
|
|
205
198
|
<DropdownMenu>
|
|
206
199
|
<DropdownItem key="option1">Option 1</DropdownItem>
|
|
@@ -210,17 +203,16 @@ import { Icon } from '@/components/ui';
|
|
|
210
203
|
```
|
|
211
204
|
|
|
212
205
|
**✅ CORRECT - Map from constants**:
|
|
213
|
-
|
|
214
206
|
```tsx
|
|
215
207
|
// File: src/config/menuOptions.ts
|
|
216
208
|
export const productMenuItems = [
|
|
217
|
-
{ key:
|
|
218
|
-
{ key:
|
|
219
|
-
{ key:
|
|
209
|
+
{ key: 'analytics', labelKey: 'product.analytics', href: '/analytics' },
|
|
210
|
+
{ key: 'insights', labelKey: 'product.insights', href: '/insights' },
|
|
211
|
+
{ key: 'reports', labelKey: 'product.reports', href: '/reports' },
|
|
220
212
|
];
|
|
221
213
|
|
|
222
214
|
// Usage:
|
|
223
|
-
import { productMenuItems } from
|
|
215
|
+
import { productMenuItems } from '@/config/menuOptions';
|
|
224
216
|
|
|
225
217
|
<DropdownMenu>
|
|
226
218
|
{productMenuItems.map((item) => (
|
|
@@ -228,7 +220,7 @@ import { productMenuItems } from "@/config/menuOptions";
|
|
|
228
220
|
{t(item.labelKey)}
|
|
229
221
|
</DropdownItem>
|
|
230
222
|
))}
|
|
231
|
-
</DropdownMenu
|
|
223
|
+
</DropdownMenu>
|
|
232
224
|
```
|
|
233
225
|
|
|
234
226
|
---
|
|
@@ -236,14 +228,11 @@ import { productMenuItems } from "@/config/menuOptions";
|
|
|
236
228
|
## 🎨 Component Priority & Usage Strategy
|
|
237
229
|
|
|
238
230
|
### 1. Check HeroUI First
|
|
239
|
-
|
|
240
231
|
Before creating ANY component, check if HeroUI provides it:
|
|
241
|
-
|
|
242
232
|
- **HeroUI Documentation**: https://heroui.com/docs/components
|
|
243
233
|
- **HeroUI Figma Kit**: https://www.figma.com/design/kFGcjHsNKZx7zh2NxEJXYt/HeroUI-Figma-Kit--Community---Community-
|
|
244
234
|
|
|
245
235
|
### 2. Component Selection Flow
|
|
246
|
-
|
|
247
236
|
```
|
|
248
237
|
1. Does HeroUI have this component?
|
|
249
238
|
→ YES: Use HeroUI component (via @/components/ui wrapper if exists)
|
|
@@ -259,7 +248,6 @@ Before creating ANY component, check if HeroUI provides it:
|
|
|
259
248
|
```
|
|
260
249
|
|
|
261
250
|
### 3. Example Decision Tree
|
|
262
|
-
|
|
263
251
|
```tsx
|
|
264
252
|
// Need a Button?
|
|
265
253
|
import { Button } from '@/components/ui'; // ✅ Use HeroUI Button wrapper
|
|
@@ -278,12 +266,10 @@ import { Input } from '@/components/ui'; // ✅ Use HeroUI Input wrapper
|
|
|
278
266
|
## 📁 Project Structure
|
|
279
267
|
|
|
280
268
|
### Pages Location
|
|
281
|
-
|
|
282
269
|
- **With i18n**: `src/app/[locale]/(route-group)/page.tsx`
|
|
283
270
|
- **Without i18n**: `src/app/(route-group)/page.tsx`
|
|
284
271
|
|
|
285
272
|
### Components Location
|
|
286
|
-
|
|
287
273
|
```
|
|
288
274
|
src/components/
|
|
289
275
|
├── ui/ # Reusable UI components (HeroUI wrappers)
|
|
@@ -293,7 +279,6 @@ src/components/
|
|
|
293
279
|
```
|
|
294
280
|
|
|
295
281
|
### Styling
|
|
296
|
-
|
|
297
282
|
- **Global styles**: `src/styles/globals.css`
|
|
298
283
|
- **Theme config**: `src/styles/hero.ts`
|
|
299
284
|
- **Approach**: Tailwind CSS utility classes ONLY
|
|
@@ -306,7 +291,6 @@ src/components/
|
|
|
306
291
|
### Navigation Components
|
|
307
292
|
|
|
308
293
|
#### Navbar (HeroUI)
|
|
309
|
-
|
|
310
294
|
**CRITICAL**: Always use HeroUI Navbar component for navigation bars:
|
|
311
295
|
|
|
312
296
|
```tsx
|
|
@@ -318,8 +302,8 @@ import {
|
|
|
318
302
|
NavbarMenu,
|
|
319
303
|
NavbarMenuItem,
|
|
320
304
|
NavbarMenuToggle,
|
|
321
|
-
} from
|
|
322
|
-
import { Button } from
|
|
305
|
+
} from '@heroui/react';
|
|
306
|
+
import { Button } from '@/components/ui';
|
|
323
307
|
|
|
324
308
|
<Navbar maxWidth="full" className="bg-black border-b-2">
|
|
325
309
|
{/* Logo */}
|
|
@@ -330,16 +314,14 @@ import { Button } from "@/components/ui";
|
|
|
330
314
|
{/* Desktop Menu */}
|
|
331
315
|
<NavbarContent className="hidden lg:flex" justify="center">
|
|
332
316
|
<NavbarItem>
|
|
333
|
-
<Button variant="light">Menu Item</Button>{"
|
|
334
|
-
{/* size="md" omitted - it's default */}
|
|
317
|
+
<Button variant="light">Menu Item</Button> {/* size="md" omitted - it's default */}
|
|
335
318
|
</NavbarItem>
|
|
336
319
|
</NavbarContent>
|
|
337
320
|
|
|
338
321
|
{/* CTA */}
|
|
339
322
|
<NavbarContent className="hidden lg:flex" justify="end">
|
|
340
323
|
<NavbarItem>
|
|
341
|
-
<Button color="primary">CTA</Button>{"
|
|
342
|
-
{/* size="md" omitted - it's default */}
|
|
324
|
+
<Button color="primary">CTA</Button> {/* size="md" omitted - it's default */}
|
|
343
325
|
</NavbarItem>
|
|
344
326
|
</NavbarContent>
|
|
345
327
|
|
|
@@ -349,28 +331,103 @@ import { Button } from "@/components/ui";
|
|
|
349
331
|
{/* Mobile Menu */}
|
|
350
332
|
<NavbarMenu>
|
|
351
333
|
<NavbarMenuItem>
|
|
352
|
-
<Button variant="light" fullWidth>
|
|
353
|
-
Menu Item
|
|
354
|
-
</Button>
|
|
334
|
+
<Button variant="light" fullWidth>Menu Item</Button>
|
|
355
335
|
</NavbarMenuItem>
|
|
356
336
|
</NavbarMenu>
|
|
357
|
-
</Navbar
|
|
337
|
+
</Navbar>
|
|
358
338
|
```
|
|
359
339
|
|
|
360
340
|
**Key Points**:
|
|
361
|
-
|
|
362
341
|
- ✅ Use `Navbar` from `@heroui/react`
|
|
363
342
|
- ✅ Use `Button` from `@/components/ui` for menu items
|
|
364
343
|
- ✅ Use props (`color`, `size`, `variant`) NOT className for styles
|
|
365
344
|
- ✅ className ONLY for layout (`hidden`, `lg:flex`, `bg-black`)
|
|
366
345
|
- ❌ DON'T use custom `<header>` or `<nav>` tags
|
|
367
346
|
|
|
368
|
-
|
|
347
|
+
#### Dropdown Menus in Navbar
|
|
348
|
+
**CRITICAL**: Always use HeroUI Dropdown components for dropdown menus:
|
|
369
349
|
|
|
350
|
+
```tsx
|
|
351
|
+
import {
|
|
352
|
+
Dropdown,
|
|
353
|
+
DropdownTrigger,
|
|
354
|
+
DropdownMenu,
|
|
355
|
+
DropdownItem,
|
|
356
|
+
} from '@heroui/react';
|
|
357
|
+
import { Button, Icon } from '@/components/ui';
|
|
358
|
+
|
|
359
|
+
// Dropdown with menu items from config
|
|
360
|
+
{item.hasDropdown && item.items && item.items.length > 0 ? (
|
|
361
|
+
<Dropdown>
|
|
362
|
+
<DropdownTrigger>
|
|
363
|
+
<Button
|
|
364
|
+
variant="light"
|
|
365
|
+
endContent={<Icon name="ChevronDownIcon" className="size-5" />}
|
|
366
|
+
>
|
|
367
|
+
{t(item.labelKey)}
|
|
368
|
+
</Button>
|
|
369
|
+
</DropdownTrigger>
|
|
370
|
+
<DropdownMenu aria-label={t(item.labelKey)}>
|
|
371
|
+
{item.items.map(subItem => (
|
|
372
|
+
<DropdownItem key={subItem.key} href={subItem.href}>
|
|
373
|
+
{t(subItem.labelKey)}
|
|
374
|
+
</DropdownItem>
|
|
375
|
+
))}
|
|
376
|
+
</DropdownMenu>
|
|
377
|
+
</Dropdown>
|
|
378
|
+
) : (
|
|
379
|
+
<Button variant="light">
|
|
380
|
+
{t(item.labelKey)}
|
|
381
|
+
</Button>
|
|
382
|
+
)}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Example Menu Configuration** (`src/config/menuItems.ts`):
|
|
386
|
+
```tsx
|
|
387
|
+
export type MenuItem = {
|
|
388
|
+
key: string;
|
|
389
|
+
labelKey: 'product' | 'useCases' | 'resources' | 'company' | 'pricing';
|
|
390
|
+
href?: string;
|
|
391
|
+
hasDropdown?: boolean;
|
|
392
|
+
items?: Array<{
|
|
393
|
+
key: string;
|
|
394
|
+
labelKey: string;
|
|
395
|
+
href: string;
|
|
396
|
+
}>;
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
export const navbarMenuItems: MenuItem[] = [
|
|
400
|
+
{
|
|
401
|
+
key: 'product',
|
|
402
|
+
labelKey: 'product',
|
|
403
|
+
hasDropdown: true,
|
|
404
|
+
items: [
|
|
405
|
+
{ key: 'analytics', labelKey: 'navbar.analytics', href: NAVIGATION_URLS.ROOT },
|
|
406
|
+
{ key: 'dashboard', labelKey: 'navbar.dashboard', href: NAVIGATION_URLS.ROOT },
|
|
407
|
+
{ key: 'reports', labelKey: 'navbar.reports', href: NAVIGATION_URLS.ROOT },
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
key: 'pricing',
|
|
412
|
+
labelKey: 'pricing',
|
|
413
|
+
href: NAVIGATION_URLS.ROOT,
|
|
414
|
+
},
|
|
415
|
+
];
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Key Points**:
|
|
419
|
+
- ✅ Always check `items && items.length > 0` before rendering dropdown
|
|
420
|
+
- ✅ Use `Icon` component with proper name (e.g., `ChevronDownIcon`)
|
|
421
|
+
- ✅ Use `endContent` prop for icons in buttons
|
|
422
|
+
- ✅ Add `aria-label` to DropdownMenu for accessibility
|
|
423
|
+
- ✅ Map menu items from config constants, never hardcode
|
|
424
|
+
- ✅ Use union types for `labelKey` to ensure type safety
|
|
425
|
+
- ❌ DON'T render dropdown without checking array exists
|
|
426
|
+
|
|
427
|
+
### Form Components
|
|
370
428
|
All located in `src/components/ui/`:
|
|
371
429
|
|
|
372
430
|
#### Input Fields
|
|
373
|
-
|
|
374
431
|
```tsx
|
|
375
432
|
import { Input, PasswordInput, NumberInput, Textarea } from '@/components/ui';
|
|
376
433
|
|
|
@@ -406,7 +463,6 @@ import { Input, PasswordInput, NumberInput, Textarea } from '@/components/ui';
|
|
|
406
463
|
```
|
|
407
464
|
|
|
408
465
|
#### Selection Components
|
|
409
|
-
|
|
410
466
|
```tsx
|
|
411
467
|
import { Select, RadioGroup, Checkbox, Autocomplete } from '@/components/ui';
|
|
412
468
|
|
|
@@ -439,18 +495,16 @@ import { Select, RadioGroup, Checkbox, Autocomplete } from '@/components/ui';
|
|
|
439
495
|
```
|
|
440
496
|
|
|
441
497
|
#### Date & Time
|
|
442
|
-
|
|
443
498
|
```tsx
|
|
444
|
-
import { DatePicker } from
|
|
499
|
+
import { DatePicker } from '@/components/ui';
|
|
445
500
|
|
|
446
501
|
<DatePicker
|
|
447
502
|
label="Select Date"
|
|
448
503
|
placeholderValue={new CalendarDate(2024, 1, 1)}
|
|
449
|
-
|
|
504
|
+
/>
|
|
450
505
|
```
|
|
451
506
|
|
|
452
507
|
#### Buttons
|
|
453
|
-
|
|
454
508
|
```tsx
|
|
455
509
|
import { Button } from '@/components/ui';
|
|
456
510
|
|
|
@@ -468,7 +522,6 @@ import { Button } from '@/components/ui';
|
|
|
468
522
|
```
|
|
469
523
|
|
|
470
524
|
#### Other Components
|
|
471
|
-
|
|
472
525
|
```tsx
|
|
473
526
|
import { Modal, Chip, Breadcrumbs, Tabs, Icon } from '@/components/ui';
|
|
474
527
|
|
|
@@ -508,32 +561,34 @@ import { Modal, Chip, Breadcrumbs, Tabs, Icon } from '@/components/ui';
|
|
|
508
561
|
**CRITICAL WORKFLOW**: Icons must follow this exact process:
|
|
509
562
|
|
|
510
563
|
#### Step 1: Export SVG from Figma
|
|
511
|
-
|
|
512
564
|
1. Select the icon in Figma design
|
|
513
565
|
2. Export as SVG (not PNG/JPG)
|
|
514
566
|
3. Download the SVG file
|
|
515
567
|
|
|
516
568
|
#### Step 2: Place in Icons Folder
|
|
517
|
-
|
|
518
569
|
```bash
|
|
519
570
|
# Place SVG file in src/assets/icons/
|
|
520
571
|
mv downloaded-icon.svg src/assets/icons/icon-name.svg
|
|
521
572
|
```
|
|
522
573
|
|
|
523
574
|
#### Step 3: Generate React Components
|
|
524
|
-
|
|
525
575
|
```bash
|
|
526
576
|
# Run the icon generation script
|
|
527
577
|
pnpm generate-icons
|
|
528
578
|
```
|
|
529
579
|
|
|
530
580
|
This command will:
|
|
531
|
-
|
|
532
581
|
- Convert all SVG files in `src/assets/icons/` to React components
|
|
533
582
|
- Place them in `src/components/icons/`
|
|
583
|
+
- **Automatically add "Icon" suffix** to all component names (configured in `svgr.config.mjs`)
|
|
534
584
|
- Auto-format and lint the generated code
|
|
535
585
|
- Update `src/components/icons/index.ts` with exports
|
|
536
586
|
|
|
587
|
+
**Icon Naming Convention**:
|
|
588
|
+
- SVG filename: `chevron-down.svg` or `chevron-down-icon.svg`
|
|
589
|
+
- Generated component: `ChevronDownIcon.tsx` (Icon suffix added automatically)
|
|
590
|
+
- The svgr config ensures ALL icons end with "Icon" suffix regardless of SVG filename
|
|
591
|
+
|
|
537
592
|
#### Step 4: Use in Code
|
|
538
593
|
|
|
539
594
|
**CORRECT USAGE**: Use the `Icon` component with `name` prop (PascalCase):
|
|
@@ -548,7 +603,6 @@ import { Icon } from '@/components/ui';
|
|
|
548
603
|
```
|
|
549
604
|
|
|
550
605
|
**❌ WRONG USAGE - DO NOT import individual icon components**:
|
|
551
|
-
|
|
552
606
|
```tsx
|
|
553
607
|
// ❌ WRONG - Don't import and use individual icon components
|
|
554
608
|
import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
@@ -557,7 +611,6 @@ import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
|
557
611
|
```
|
|
558
612
|
|
|
559
613
|
**Available Icons** (auto-generated):
|
|
560
|
-
|
|
561
614
|
- ChevronDownIcon, ChevronRightIcon
|
|
562
615
|
- CloseIcon, MenuIcon
|
|
563
616
|
- EyeIcon, EyeSlashIcon
|
|
@@ -567,13 +620,11 @@ import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
|
567
620
|
- And more...
|
|
568
621
|
|
|
569
622
|
**Icon Names**:
|
|
570
|
-
|
|
571
623
|
- Icon names are PascalCase with "Icon" suffix (e.g., `ChevronDownIcon`)
|
|
572
624
|
- Generated from SVG filename: `chevron-down.svg` → component `ChevronDownIcon`
|
|
573
625
|
- The Icon component uses the exact component name from `@/components/icons`
|
|
574
626
|
|
|
575
627
|
**❌ DO NOT**:
|
|
576
|
-
|
|
577
628
|
- Import individual icon components directly
|
|
578
629
|
- Use kebab-case names (use `ChevronDownIcon` not `chevron-down`)
|
|
579
630
|
- Use Lucide, Font Awesome, or other icon libraries
|
|
@@ -582,7 +633,6 @@ import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
|
582
633
|
- Hardcode SVG paths directly in components
|
|
583
634
|
|
|
584
635
|
**✅ DO**:
|
|
585
|
-
|
|
586
636
|
- Always use `<Icon name="IconName" />` pattern with PascalCase
|
|
587
637
|
- Always export from Figma as SVG
|
|
588
638
|
- Use `pnpm generate-icons` to create components
|
|
@@ -595,7 +645,6 @@ import { ChevronDownIcon, CloseIcon } from '@/components/icons';
|
|
|
595
645
|
#### Step 1: Check Existing Images First
|
|
596
646
|
|
|
597
647
|
**BEFORE implementing or exporting from Figma**:
|
|
598
|
-
|
|
599
648
|
1. Check `public/images/index.ts` to see if the image/logo already exists
|
|
600
649
|
2. Check `public/images/logos/` directory for available logo files
|
|
601
650
|
3. Only export from Figma if the asset doesn't exist
|
|
@@ -607,19 +656,16 @@ ls public/images/logos/
|
|
|
607
656
|
```
|
|
608
657
|
|
|
609
658
|
**Why this matters**:
|
|
610
|
-
|
|
611
659
|
- Avoid duplicate assets
|
|
612
660
|
- User may have already exported the assets
|
|
613
661
|
- Maintain consistency with existing naming conventions
|
|
614
662
|
|
|
615
663
|
#### Step 2: Export from Figma (if needed)
|
|
616
|
-
|
|
617
664
|
1. **For logos**: Export as SVG (preferred) or PNG if source is raster
|
|
618
665
|
2. **For images**: Export as PNG, JPG, or WebP based on image type
|
|
619
666
|
3. Use appropriate resolution (2x or 3x for retina displays)
|
|
620
667
|
|
|
621
668
|
#### Step 3: Place in Public Folder
|
|
622
|
-
|
|
623
669
|
```bash
|
|
624
670
|
# Place logo/image files in public/
|
|
625
671
|
public/
|
|
@@ -645,36 +691,36 @@ public/
|
|
|
645
691
|
*/
|
|
646
692
|
|
|
647
693
|
const LOGOS = {
|
|
648
|
-
PRIMARY_CIRCLE:
|
|
649
|
-
SECONDARY_CIRCLE:
|
|
650
|
-
FULL:
|
|
651
|
-
FULL_BLACK:
|
|
652
|
-
FULL_WHITE:
|
|
653
|
-
ICON:
|
|
694
|
+
PRIMARY_CIRCLE: '/images/logos/logo-circle-primary.svg',
|
|
695
|
+
SECONDARY_CIRCLE: '/images/logos/logo-circle-secondary.svg',
|
|
696
|
+
FULL: '/images/logos/logo-full.svg',
|
|
697
|
+
FULL_BLACK: '/images/logos/logo-full-black.svg',
|
|
698
|
+
FULL_WHITE: '/images/logos/logo-full-white.svg',
|
|
699
|
+
ICON: '/images/logos/logo-icon.svg',
|
|
654
700
|
} as const;
|
|
655
701
|
|
|
656
702
|
const AUTH = {
|
|
657
|
-
LOGIN_BG:
|
|
658
|
-
SIGNUP_BG:
|
|
659
|
-
RESET_PASSWORD:
|
|
703
|
+
LOGIN_BG: '/images/auth/login-background.jpg',
|
|
704
|
+
SIGNUP_BG: '/images/auth/signup-background.jpg',
|
|
705
|
+
RESET_PASSWORD: '/images/auth/reset-password.jpg',
|
|
660
706
|
} as const;
|
|
661
707
|
|
|
662
708
|
const WEBSITE = {
|
|
663
|
-
HOME_HERO:
|
|
664
|
-
HOME_NEWSLETTER:
|
|
665
|
-
ABOUT_HEADER:
|
|
666
|
-
CONTACT_MAP:
|
|
709
|
+
HOME_HERO: '/images/website/home-hero.webp',
|
|
710
|
+
HOME_NEWSLETTER: '/images/website/home-newsletter.webp',
|
|
711
|
+
ABOUT_HEADER: '/images/website/about-header.jpg',
|
|
712
|
+
CONTACT_MAP: '/images/website/contact-map.png',
|
|
667
713
|
} as const;
|
|
668
714
|
|
|
669
715
|
const SERVICES = {
|
|
670
|
-
HEADER:
|
|
671
|
-
FEATURE_1:
|
|
672
|
-
FEATURE_2:
|
|
716
|
+
HEADER: '/images/services/services-header.webp',
|
|
717
|
+
FEATURE_1: '/images/services/feature-1.jpg',
|
|
718
|
+
FEATURE_2: '/images/services/feature-2.jpg',
|
|
673
719
|
} as const;
|
|
674
720
|
|
|
675
721
|
const PRODUCTS = {
|
|
676
|
-
THUMBNAIL_DEFAULT:
|
|
677
|
-
HERO:
|
|
722
|
+
THUMBNAIL_DEFAULT: '/images/products/thumbnail-default.png',
|
|
723
|
+
HERO: '/images/products/hero-banner.jpg',
|
|
678
724
|
} as const;
|
|
679
725
|
|
|
680
726
|
export const IMAGES = {
|
|
@@ -688,18 +734,28 @@ export const IMAGES = {
|
|
|
688
734
|
|
|
689
735
|
#### Step 5: Reference in Code
|
|
690
736
|
|
|
737
|
+
**CRITICAL: ALWAYS import and use IMAGES constant - NEVER hardcode paths**
|
|
738
|
+
|
|
691
739
|
```tsx
|
|
692
|
-
// Import from centralized constants
|
|
740
|
+
// ✅ CORRECT - Import from centralized constants
|
|
693
741
|
import { IMAGES } from '@/config/images';
|
|
694
742
|
import Image from 'next/image';
|
|
695
743
|
|
|
696
744
|
// Using Next.js Image component (recommended)
|
|
697
745
|
<Image
|
|
698
|
-
src={IMAGES.LOGOS.
|
|
699
|
-
alt="
|
|
700
|
-
width={
|
|
701
|
-
height={
|
|
702
|
-
|
|
746
|
+
src={IMAGES.LOGOS.BINA} // ✅ Use constant
|
|
747
|
+
alt="BINA"
|
|
748
|
+
width={54}
|
|
749
|
+
height={41}
|
|
750
|
+
className="h-10 w-auto"
|
|
751
|
+
/>
|
|
752
|
+
|
|
753
|
+
// ❌ WRONG - Hardcoded path
|
|
754
|
+
<Image
|
|
755
|
+
src="/images/logos/bina.png" // ❌ Don't hardcode
|
|
756
|
+
alt="BINA"
|
|
757
|
+
width={54}
|
|
758
|
+
height={41}
|
|
703
759
|
/>
|
|
704
760
|
|
|
705
761
|
// Using standard img tag (for SVGs with CSS control)
|
|
@@ -718,22 +774,20 @@ import Image from 'next/image';
|
|
|
718
774
|
```
|
|
719
775
|
|
|
720
776
|
**Preference Order**:
|
|
721
|
-
|
|
722
777
|
1. ✅ **SVG** - Best for logos, icons, and vector graphics (scalable, small file size)
|
|
723
778
|
2. ✅ **WebP** - Modern format for photos (smaller than PNG/JPG)
|
|
724
779
|
3. ✅ **PNG** - For images requiring transparency
|
|
725
780
|
4. ✅ **JPG** - For photos without transparency
|
|
726
781
|
|
|
727
782
|
**❌ DO NOT**:
|
|
728
|
-
|
|
729
783
|
- Put images in `src/` folder
|
|
730
784
|
- Import images as modules unless necessary
|
|
731
785
|
- Use base64 encoded images inline
|
|
732
786
|
- Forget to optimize images before adding
|
|
733
|
-
- Hardcode image paths directly in components
|
|
787
|
+
- **CRITICAL**: Hardcode image paths directly in components - MUST use `IMAGES` constant from `@/config/images`
|
|
788
|
+
- Use hardcoded strings like `src="/images/logos/bina.png"` - Use `src={IMAGES.LOGOS.BINA}` instead
|
|
734
789
|
|
|
735
790
|
**✅ DO**:
|
|
736
|
-
|
|
737
791
|
- Always place in `public/` directory
|
|
738
792
|
- Create centralized image constants file (src/config/images.ts)
|
|
739
793
|
- Organize image paths by category (LOGOS, AUTH, WEBSITE, SERVICES)
|
|
@@ -750,30 +804,27 @@ import Image from 'next/image';
|
|
|
750
804
|
### CRITICAL: Use Theme Colors, NOT Custom Values
|
|
751
805
|
|
|
752
806
|
**Colors are defined in two places**:
|
|
753
|
-
|
|
754
807
|
1. **`src/styles/hero.ts`** - HeroUI theme colors (primary, secondary, etc.)
|
|
755
808
|
2. **`src/styles/globals.css`** - Additional custom theme colors
|
|
756
809
|
|
|
757
810
|
### Available Theme Colors
|
|
758
811
|
|
|
759
812
|
#### From hero.ts (Primary Color Palette):
|
|
760
|
-
|
|
761
813
|
```typescript
|
|
762
814
|
// Primary green shades (defined in hero.ts)
|
|
763
|
-
primary
|
|
764
|
-
primary
|
|
765
|
-
primary
|
|
766
|
-
primary
|
|
767
|
-
primary
|
|
768
|
-
primary
|
|
769
|
-
primary
|
|
770
|
-
primary
|
|
771
|
-
primary
|
|
772
|
-
primary
|
|
815
|
+
primary-50 // #E6FFF0 (lightest)
|
|
816
|
+
primary-100 // #CBFFE0
|
|
817
|
+
primary-200 // #AEFFD1
|
|
818
|
+
primary-300 // #8EFFC2
|
|
819
|
+
primary-400 // #66FFB2
|
|
820
|
+
primary-500 // #19FFA3 (default primary)
|
|
821
|
+
primary-600 // #25C780
|
|
822
|
+
primary-700 // #26915F
|
|
823
|
+
primary-800 // #215F40
|
|
824
|
+
primary-900 // #173123 (darkest)
|
|
773
825
|
```
|
|
774
826
|
|
|
775
827
|
#### HeroUI Default Colors:
|
|
776
|
-
|
|
777
828
|
```tsx
|
|
778
829
|
// Semantic colors (built into HeroUI)
|
|
779
830
|
bg-primary text-primary border-primary
|
|
@@ -789,7 +840,6 @@ bg-background text-background border-background
|
|
|
789
840
|
### Color Usage Examples
|
|
790
841
|
|
|
791
842
|
**✅ CORRECT - Use theme colors**:
|
|
792
|
-
|
|
793
843
|
```tsx
|
|
794
844
|
// In component configuration (NO hover states)
|
|
795
845
|
color: {
|
|
@@ -803,7 +853,6 @@ className="bg-primary-50 text-primary-900"
|
|
|
803
853
|
```
|
|
804
854
|
|
|
805
855
|
**❌ WRONG - Custom color values or hover states**:
|
|
806
|
-
|
|
807
856
|
```tsx
|
|
808
857
|
// DON'T use hex values or hover states in config
|
|
809
858
|
color: {
|
|
@@ -823,13 +872,11 @@ color: {
|
|
|
823
872
|
### When to Add Custom Colors
|
|
824
873
|
|
|
825
874
|
**Only add custom colors to hero.ts if**:
|
|
826
|
-
|
|
827
875
|
1. Color is NOT in the existing palette
|
|
828
876
|
2. Color is used in multiple places (reusable)
|
|
829
877
|
3. Color is part of the design system
|
|
830
878
|
|
|
831
879
|
**Steps to add custom color**:
|
|
832
|
-
|
|
833
880
|
```typescript
|
|
834
881
|
// 1. Add to hero.ts theme
|
|
835
882
|
export default heroui({
|
|
@@ -838,9 +885,9 @@ export default heroui({
|
|
|
838
885
|
colors: {
|
|
839
886
|
// Add new color palette
|
|
840
887
|
accent: {
|
|
841
|
-
50:
|
|
842
|
-
500:
|
|
843
|
-
900:
|
|
888
|
+
50: '#...',
|
|
889
|
+
500: '#...',
|
|
890
|
+
900: '#...',
|
|
844
891
|
},
|
|
845
892
|
},
|
|
846
893
|
},
|
|
@@ -848,7 +895,7 @@ export default heroui({
|
|
|
848
895
|
});
|
|
849
896
|
|
|
850
897
|
// 2. Use in components
|
|
851
|
-
className
|
|
898
|
+
className="bg-accent text-accent-900"
|
|
852
899
|
```
|
|
853
900
|
|
|
854
901
|
---
|
|
@@ -860,23 +907,21 @@ className = "bg-accent text-accent-900";
|
|
|
860
907
|
**CRITICAL**: Always use closest standard Tailwind class instead of custom arbitrary values
|
|
861
908
|
|
|
862
909
|
**Common Conversions**:
|
|
863
|
-
|
|
864
910
|
```tsx
|
|
865
911
|
// ❌ WRONG - Custom arbitrary values
|
|
866
|
-
className
|
|
867
|
-
className
|
|
868
|
-
className
|
|
869
|
-
className
|
|
912
|
+
className="h-[41px] w-[54.6px]"
|
|
913
|
+
className="p-[24px]"
|
|
914
|
+
className="text-[14px]"
|
|
915
|
+
className="gap-[11px]"
|
|
870
916
|
|
|
871
917
|
// ✅ CORRECT - Standard Tailwind classes
|
|
872
|
-
className
|
|
873
|
-
className
|
|
874
|
-
className
|
|
875
|
-
className
|
|
918
|
+
className="h-10 w-14" // h-10 = 40px, w-14 = 56px (closest to 54.6px)
|
|
919
|
+
className="p-6" // p-6 = 24px
|
|
920
|
+
className="text-sm" // text-sm = 14px
|
|
921
|
+
className="gap-3" // gap-3 = 12px (closest to 11px)
|
|
876
922
|
```
|
|
877
923
|
|
|
878
924
|
**Tailwind Size Reference**:
|
|
879
|
-
|
|
880
925
|
```css
|
|
881
926
|
/* Spacing Scale (padding, margin, gap, etc.) */
|
|
882
927
|
0.5 = 2px 4 = 16px 12 = 48px
|
|
@@ -900,13 +945,11 @@ auto, full, screen, fit, min, max
|
|
|
900
945
|
```
|
|
901
946
|
|
|
902
947
|
**Only use arbitrary values when**:
|
|
903
|
-
|
|
904
948
|
- Exact pixel value is required by design system
|
|
905
949
|
- No standard Tailwind class is close enough
|
|
906
950
|
- Using design tokens/CSS variables: `bg-[var(--custom-color)]`
|
|
907
951
|
|
|
908
952
|
**Examples**:
|
|
909
|
-
|
|
910
953
|
```tsx
|
|
911
954
|
// ✅ Good - Using standard classes
|
|
912
955
|
<div className="h-10 w-full p-6 text-sm">
|
|
@@ -916,7 +959,6 @@ auto, full, screen, fit, min, max
|
|
|
916
959
|
```
|
|
917
960
|
|
|
918
961
|
### Responsive Breakpoints (Mobile-First)
|
|
919
|
-
|
|
920
962
|
```css
|
|
921
963
|
/* Mobile */
|
|
922
964
|
4xs: 320px /* iPhone SE */
|
|
@@ -937,7 +979,6 @@ xl: 1280px /* MacBook Air */
|
|
|
937
979
|
```
|
|
938
980
|
|
|
939
981
|
### Usage Example
|
|
940
|
-
|
|
941
982
|
```tsx
|
|
942
983
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
943
984
|
{/* Mobile: 1 column, Tablet: 2 columns, Desktop: 3 columns */}
|
|
@@ -945,9 +986,7 @@ xl: 1280px /* MacBook Air */
|
|
|
945
986
|
```
|
|
946
987
|
|
|
947
988
|
### HeroUI Color System
|
|
948
|
-
|
|
949
989
|
Use these semantic colors from HeroUI theme:
|
|
950
|
-
|
|
951
990
|
- `bg-background` - Main background
|
|
952
991
|
- `bg-foreground` - Text color background
|
|
953
992
|
- `bg-default` / `bg-default-50` to `bg-default-900` - Gray scale
|
|
@@ -958,7 +997,6 @@ Use these semantic colors from HeroUI theme:
|
|
|
958
997
|
- `bg-danger` - Error/danger states
|
|
959
998
|
|
|
960
999
|
### Common Patterns
|
|
961
|
-
|
|
962
1000
|
```tsx
|
|
963
1001
|
// Card
|
|
964
1002
|
<div className="rounded-lg bg-white p-6 shadow-md">
|
|
@@ -982,20 +1020,19 @@ Use these semantic colors from HeroUI theme:
|
|
|
982
1020
|
## 📝 Form Implementation with Zod
|
|
983
1021
|
|
|
984
1022
|
### Pattern to Follow
|
|
985
|
-
|
|
986
1023
|
```tsx
|
|
987
|
-
|
|
1024
|
+
'use client';
|
|
988
1025
|
|
|
989
|
-
import { zodResolver } from
|
|
990
|
-
import { useForm } from
|
|
991
|
-
import { z } from
|
|
1026
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
1027
|
+
import { useForm } from 'react-hook-form';
|
|
1028
|
+
import { z } from 'zod';
|
|
992
1029
|
|
|
993
|
-
import { Button, Input } from
|
|
1030
|
+
import { Button, Input } from '@/components/ui';
|
|
994
1031
|
|
|
995
1032
|
// 1. Define schema
|
|
996
1033
|
const schema = z.object({
|
|
997
|
-
email: z.string().email(
|
|
998
|
-
password: z.string().min(8,
|
|
1034
|
+
email: z.string().email('Invalid email'),
|
|
1035
|
+
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
999
1036
|
});
|
|
1000
1037
|
|
|
1001
1038
|
type FormData = z.infer<typeof schema>;
|
|
@@ -1022,7 +1059,7 @@ export default function LoginForm() {
|
|
|
1022
1059
|
<Input
|
|
1023
1060
|
label="Email"
|
|
1024
1061
|
type="email"
|
|
1025
|
-
{...register(
|
|
1062
|
+
{...register('email')}
|
|
1026
1063
|
errorMessage={errors.email?.message}
|
|
1027
1064
|
isInvalid={!!errors.email}
|
|
1028
1065
|
/>
|
|
@@ -1030,7 +1067,7 @@ export default function LoginForm() {
|
|
|
1030
1067
|
<Input
|
|
1031
1068
|
label="Password"
|
|
1032
1069
|
type="password"
|
|
1033
|
-
{...register(
|
|
1070
|
+
{...register('password')}
|
|
1034
1071
|
errorMessage={errors.password?.message}
|
|
1035
1072
|
isInvalid={!!errors.password}
|
|
1036
1073
|
/>
|
|
@@ -1052,20 +1089,18 @@ export default function LoginForm() {
|
|
|
1052
1089
|
### Navigation with i18n
|
|
1053
1090
|
|
|
1054
1091
|
**Import Guidelines**:
|
|
1055
|
-
|
|
1056
1092
|
```tsx
|
|
1057
1093
|
// For Link component - use Next.js Link
|
|
1058
|
-
import Link from
|
|
1094
|
+
import Link from 'next/link';
|
|
1059
1095
|
|
|
1060
1096
|
// For navigation hooks - use next/navigation
|
|
1061
|
-
import { usePathname, useRouter } from
|
|
1097
|
+
import { usePathname, useRouter } from 'next/navigation';
|
|
1062
1098
|
|
|
1063
1099
|
// For navigation URLs - use centralized constants
|
|
1064
|
-
import { NAVIGATION_URLS } from
|
|
1100
|
+
import { NAVIGATION_URLS } from '@/config/navigationUrls';
|
|
1065
1101
|
```
|
|
1066
1102
|
|
|
1067
1103
|
**Navigation Example**:
|
|
1068
|
-
|
|
1069
1104
|
```tsx
|
|
1070
1105
|
import Link from 'next/link';
|
|
1071
1106
|
import { NAVIGATION_URLS } from '@/config/navigationUrls';
|
|
@@ -1082,11 +1117,10 @@ import { Link } from '@heroui/react';
|
|
|
1082
1117
|
```
|
|
1083
1118
|
|
|
1084
1119
|
**Using Navigation Hooks**:
|
|
1085
|
-
|
|
1086
1120
|
```tsx
|
|
1087
|
-
|
|
1121
|
+
'use client';
|
|
1088
1122
|
|
|
1089
|
-
import { usePathname, useRouter } from
|
|
1123
|
+
import { usePathname, useRouter } from 'next/navigation';
|
|
1090
1124
|
|
|
1091
1125
|
export function MyComponent() {
|
|
1092
1126
|
const pathname = usePathname();
|
|
@@ -1102,29 +1136,64 @@ export function MyComponent() {
|
|
|
1102
1136
|
|
|
1103
1137
|
### Translations for ALL text
|
|
1104
1138
|
|
|
1139
|
+
**CRITICAL: Specify namespace to avoid TypeScript errors**
|
|
1140
|
+
|
|
1105
1141
|
```tsx
|
|
1106
|
-
import { useTranslations } from
|
|
1142
|
+
import { useTranslations } from 'next-intl';
|
|
1107
1143
|
|
|
1108
1144
|
function MyComponent() {
|
|
1109
|
-
|
|
1145
|
+
// ✅ CORRECT - Specify namespace for type safety
|
|
1146
|
+
const t = useTranslations('namespace');
|
|
1110
1147
|
|
|
1111
1148
|
return (
|
|
1112
1149
|
<div>
|
|
1113
|
-
{/*
|
|
1114
|
-
<h1>{t(
|
|
1115
|
-
<p>{t(
|
|
1150
|
+
{/* Now you can use keys directly without type errors */}
|
|
1151
|
+
<h1>{t('welcome')}</h1>
|
|
1152
|
+
<p>{t('description')}</p>
|
|
1153
|
+
</div>
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1116
1156
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1157
|
+
// ❌ WRONG - No namespace causes TypeScript errors with dynamic keys
|
|
1158
|
+
function BadComponent() {
|
|
1159
|
+
const t = useTranslations(); // Missing namespace
|
|
1160
|
+
|
|
1161
|
+
const items = [
|
|
1162
|
+
{ key: 'product', labelKey: 'navbar.product' }
|
|
1163
|
+
];
|
|
1164
|
+
|
|
1165
|
+
return (
|
|
1166
|
+
<div>
|
|
1167
|
+
{/* TypeScript error: Argument of type 'string' is not assignable */}
|
|
1168
|
+
{items.map(item => <p key={item.key}>{t(item.labelKey)}</p>)}
|
|
1119
1169
|
</div>
|
|
1120
1170
|
);
|
|
1121
1171
|
}
|
|
1122
1172
|
```
|
|
1123
1173
|
|
|
1124
|
-
|
|
1174
|
+
**Type-Safe Translation Pattern:**
|
|
1175
|
+
```tsx
|
|
1176
|
+
// 1. Define translation key types in config
|
|
1177
|
+
export type MenuItem = {
|
|
1178
|
+
key: string;
|
|
1179
|
+
labelKey: 'product' | 'useCases' | 'resources'; // Union type of valid keys
|
|
1180
|
+
href?: string;
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
// 2. Use with namespace
|
|
1184
|
+
const t = useTranslations('navbar');
|
|
1185
|
+
|
|
1186
|
+
// 3. Access keys directly (type-safe)
|
|
1187
|
+
{menuItems.map(item => (
|
|
1188
|
+
<Button key={item.key}>
|
|
1189
|
+
{t(item.labelKey)} {/* No TypeScript error */}
|
|
1190
|
+
</Button>
|
|
1191
|
+
))}
|
|
1192
|
+
```
|
|
1125
1193
|
|
|
1194
|
+
### Form labels, placeholders, errors - ALL must use i18n
|
|
1126
1195
|
```tsx
|
|
1127
|
-
import { useTranslations } from
|
|
1196
|
+
import { useTranslations } from 'next-intl';
|
|
1128
1197
|
|
|
1129
1198
|
function LoginForm() {
|
|
1130
1199
|
const t = useTranslations();
|
|
@@ -1133,11 +1202,9 @@ function LoginForm() {
|
|
|
1133
1202
|
<form>
|
|
1134
1203
|
{/* ✅ CORRECT */}
|
|
1135
1204
|
<Input
|
|
1136
|
-
label={t(
|
|
1137
|
-
placeholder={t(
|
|
1138
|
-
errorMessage={
|
|
1139
|
-
errors.email?.message ? t(errors.email.message) : undefined
|
|
1140
|
-
}
|
|
1205
|
+
label={t('email')}
|
|
1206
|
+
placeholder={t('enterEmail')}
|
|
1207
|
+
errorMessage={errors.email?.message ? t(errors.email.message) : undefined}
|
|
1141
1208
|
/>
|
|
1142
1209
|
|
|
1143
1210
|
{/* ❌ WRONG - Hardcoded text */}
|
|
@@ -1152,7 +1219,6 @@ function LoginForm() {
|
|
|
1152
1219
|
```
|
|
1153
1220
|
|
|
1154
1221
|
### Navigation URLs Reference
|
|
1155
|
-
|
|
1156
1222
|
```tsx
|
|
1157
1223
|
import { NAVIGATION_URLS } from '@/config/navigationUrls';
|
|
1158
1224
|
|
|
@@ -1177,20 +1243,19 @@ NAVIGATION_URLS.DASHBOARD.INDEX // '/dashboard'
|
|
|
1177
1243
|
## 🎯 Data Fetching
|
|
1178
1244
|
|
|
1179
1245
|
### Using React Query
|
|
1180
|
-
|
|
1181
1246
|
```tsx
|
|
1182
|
-
|
|
1247
|
+
'use client';
|
|
1183
1248
|
|
|
1184
|
-
import { useQuery } from
|
|
1185
|
-
import axios from
|
|
1249
|
+
import { useQuery } from '@tanstack/react-query';
|
|
1250
|
+
import axios from 'axios';
|
|
1186
1251
|
|
|
1187
|
-
import { QUERY_KEYS } from
|
|
1252
|
+
import { QUERY_KEYS } from '@/libs/react-query';
|
|
1188
1253
|
|
|
1189
1254
|
export default function ProductList() {
|
|
1190
1255
|
const { data, isLoading, error } = useQuery({
|
|
1191
1256
|
queryKey: QUERY_KEYS.PRODUCTS.LIST,
|
|
1192
1257
|
queryFn: async () => {
|
|
1193
|
-
const response = await axios.get(
|
|
1258
|
+
const response = await axios.get('/api/products');
|
|
1194
1259
|
return response.data;
|
|
1195
1260
|
},
|
|
1196
1261
|
});
|
|
@@ -1213,12 +1278,11 @@ export default function ProductList() {
|
|
|
1213
1278
|
## 📐 Layout Components
|
|
1214
1279
|
|
|
1215
1280
|
### Using Existing Layouts
|
|
1216
|
-
|
|
1217
1281
|
```tsx
|
|
1218
1282
|
// Auth pages use auth layout automatically
|
|
1219
1283
|
// Location: src/app/[locale]/(auth)/login/page.tsx
|
|
1220
1284
|
|
|
1221
|
-
|
|
1285
|
+
'use client';
|
|
1222
1286
|
|
|
1223
1287
|
export default function LoginPage() {
|
|
1224
1288
|
return (
|
|
@@ -1231,14 +1295,11 @@ export default function LoginPage() {
|
|
|
1231
1295
|
```
|
|
1232
1296
|
|
|
1233
1297
|
### Navbar & Footer
|
|
1234
|
-
|
|
1235
1298
|
Already implemented in `src/components/layout/`:
|
|
1236
|
-
|
|
1237
1299
|
- `Navbar.tsx` - Main navigation using HeroUI Navbar
|
|
1238
1300
|
- `Footer.tsx` - Footer component
|
|
1239
1301
|
|
|
1240
1302
|
**CRITICAL**: Navbar uses HeroUI Navbar component:
|
|
1241
|
-
|
|
1242
1303
|
```tsx
|
|
1243
1304
|
import { Navbar } from '@/components/layout';
|
|
1244
1305
|
|
|
@@ -1247,29 +1308,32 @@ import { Navbar } from '@/components/layout';
|
|
|
1247
1308
|
<main>{children}</main>
|
|
1248
1309
|
```
|
|
1249
1310
|
|
|
1250
|
-
**Implementation details** (see `src/components/layout/Navbar.tsx`):
|
|
1251
|
-
|
|
1252
|
-
- ✅ Uses HeroUI `Navbar`, `NavbarBrand`, `NavbarContent`, `NavbarItem`
|
|
1311
|
+
**Implementation details** (see `src/components/layout/Navbar.tsx` and `docs/NAVBAR_IMPLEMENTATION.md`):
|
|
1312
|
+
- ✅ Uses HeroUI `Navbar`, `NavbarBrand`, `NavbarContent`, `NavbarItem`, `Dropdown`
|
|
1253
1313
|
- ✅ Uses `Button` from `@/components/ui` with props (`color`, `size`, `variant`)
|
|
1314
|
+
- ✅ Uses `useTranslations('navbar')` with namespace for type safety
|
|
1315
|
+
- ✅ Menu items mapped from `src/config/menuItems.ts` with typed `labelKey`
|
|
1316
|
+
- ✅ Dropdown menus with HeroUI `Dropdown`, `DropdownTrigger`, `DropdownMenu`
|
|
1254
1317
|
- ✅ All styles via props, NO className for component styles
|
|
1255
1318
|
- ✅ className ONLY for layout utilities
|
|
1319
|
+
- ✅ NO `any` types - uses union types for translation keys
|
|
1256
1320
|
|
|
1257
1321
|
---
|
|
1258
1322
|
|
|
1259
1323
|
## ⚠️ CRITICAL RULES
|
|
1260
1324
|
|
|
1261
1325
|
### ❌ DO NOT
|
|
1262
|
-
|
|
1263
1326
|
1. ❌ **Figma**: Use screenshots to implement design - MUST use Figma MCP server with `get_design_context`
|
|
1264
1327
|
2. ❌ **Figma**: Call `get_screenshot` tool - Design context provides all needed information
|
|
1265
1328
|
3. ❌ **Icons**: Import individual icon components (e.g., `import { ChevronDownIcon }`) - MUST use `<Icon name="ChevronDownIcon" />`
|
|
1266
1329
|
4. ❌ **Icons**: Create manual icon components - MUST use `pnpm generate-icons` workflow
|
|
1267
1330
|
5. ❌ **Icons**: Use non-project icons (Lucide, Font Awesome, etc.)
|
|
1268
|
-
6. ❌ **
|
|
1269
|
-
7. ❌ **
|
|
1270
|
-
8. ❌ **
|
|
1271
|
-
9. ❌ **
|
|
1272
|
-
10. ❌ **Tailwind**: Use
|
|
1331
|
+
6. ❌ **Images**: Put logos/images in `src/` folder - MUST go in `public/`
|
|
1332
|
+
7. ❌ **Images**: Import images as modules unless necessary
|
|
1333
|
+
8. ❌ **Images**: **CRITICAL** - Hardcode image/video paths (e.g., `src="/images/logos/bina.png"`) - MUST use `IMAGES` constant
|
|
1334
|
+
9. ❌ **Images**: Use direct path strings anywhere - ALL paths MUST come from `@/config/images`
|
|
1335
|
+
10. ❌ **Tailwind**: Use arbitrary values when standard classes exist (e.g., `h-[40px]` → use `h-10`)
|
|
1336
|
+
11. ❌ **Tailwind**: Use custom values like `w-[100%]` (use `w-full`)
|
|
1273
1337
|
11. ❌ **Colors**: Use custom color values (e.g., `bg-[#19ffa3]`, `text-[#ff0000]`) - MUST use theme colors from hero.ts
|
|
1274
1338
|
12. ❌ **Colors**: Use hex, rgb, or hsl values inline - Use semantic color classes instead
|
|
1275
1339
|
13. ❌ **Text**: Hardcode ANY text - ALL text MUST use i18n translation keys
|
|
@@ -1283,24 +1347,28 @@ import { Navbar } from '@/components/layout';
|
|
|
1283
1347
|
21. ❌ **Imports**: Import from `@heroui/react` directly (use `@/components/ui` wrappers when available)
|
|
1284
1348
|
22. ❌ **Navigation**: Use HeroUI `Link` for internal navigation (use Next.js `Link` from `next/link`)
|
|
1285
1349
|
23. ❌ **Navigation**: Import `usePathname`, `useRouter` from wrong source (MUST use `next/navigation`)
|
|
1286
|
-
24. ❌ **TypeScript**: Use `any` type
|
|
1287
|
-
25. ❌ **
|
|
1288
|
-
26. ❌ **
|
|
1350
|
+
24. ❌ **TypeScript**: Use `any` type - Use union types and proper typing instead
|
|
1351
|
+
25. ❌ **i18n**: Use `useTranslations()` without namespace - MUST specify namespace for type safety
|
|
1352
|
+
26. ❌ **i18n**: Use dynamic translation keys without proper typing - Define union types for keys
|
|
1353
|
+
27. ❌ **Design**: Ignore responsive design (always implement mobile-first)
|
|
1354
|
+
28. ❌ **Forms**: Skip form validation (always use Zod schemas)
|
|
1355
|
+
29. ❌ **Dropdowns**: Render dropdown items without checking if items array exists/has length
|
|
1356
|
+
30. ❌ **Build**: Run `pnpm build` or `pnpm lint` commands - The user will run these manually
|
|
1289
1357
|
|
|
1290
1358
|
### ✅ DO
|
|
1291
|
-
|
|
1292
1359
|
1. ✅ **Figma**: Use Figma MCP server with `get_design_context` and node-id from Figma URL
|
|
1293
1360
|
2. ✅ **Figma**: Extract node-id from URL (e.g., `node-id=8486-1580` → `"8486:1580"`)
|
|
1294
1361
|
3. ✅ **Icons**: Export SVG from Figma → `src/assets/icons/` → Run `pnpm generate-icons`
|
|
1295
1362
|
4. ✅ **Icons**: Use `<Icon name="IconName" />` pattern with PascalCase - NEVER import individual icon components
|
|
1296
|
-
5. ✅ **
|
|
1297
|
-
6. ✅ **
|
|
1298
|
-
7. ✅ **
|
|
1299
|
-
8. ✅ **
|
|
1300
|
-
9. ✅ **
|
|
1301
|
-
10. ✅ **
|
|
1302
|
-
11. ✅ **
|
|
1303
|
-
12. ✅ **Tailwind**: Use
|
|
1363
|
+
5. ✅ **Images**: Check `public/images/index.ts` BEFORE exporting from Figma
|
|
1364
|
+
6. ✅ **Images**: Check `public/images/logos/` directory for existing assets
|
|
1365
|
+
7. ✅ **Images**: Export SVG/PNG from Figma ONLY if not existing → Place in `public/images/` with organized folders
|
|
1366
|
+
8. ✅ **Images**: **CRITICAL** - Create/update `src/config/images.ts` with ALL image paths
|
|
1367
|
+
9. ✅ **Images**: **ALWAYS** import from `@/config/images` and use `IMAGES.LOGOS.BINA` (NEVER hardcode)
|
|
1368
|
+
10. ✅ **Images**: Add new images to centralized config immediately
|
|
1369
|
+
11. ✅ **Images**: Prefer SVG over PNG when source is vector
|
|
1370
|
+
12. ✅ **Tailwind**: Use standard classes (`h-10`, `p-6`, `text-sm` instead of arbitrary values)
|
|
1371
|
+
13. ✅ **Tailwind**: Use size utilities (`size-4`, `size-5`) for icons
|
|
1304
1372
|
13. ✅ **Colors**: Use theme colors from hero.ts (`bg-primary`, `text-primary-600`, `bg-success`)
|
|
1305
1373
|
14. ✅ **Colors**: Check hero.ts and globals.css for available colors before using custom values
|
|
1306
1374
|
15. ✅ **Navigation**: Use Next.js `Link` from `next/link` for internal navigation
|
|
@@ -1316,11 +1384,15 @@ import { Navbar } from '@/components/layout';
|
|
|
1316
1384
|
25. ✅ **Styling**: Omit props that match defaultVariants (e.g., don't write `size="md"` if `md` is default)
|
|
1317
1385
|
26. ✅ **Styling**: Use className ONLY for layout utilities (`mt-4`, `hidden`, `lg:flex`)
|
|
1318
1386
|
27. ✅ **Styling**: Use Tailwind CSS ONLY (utility classes)
|
|
1319
|
-
28. ✅ **TypeScript**: Implement strict typing with proper types
|
|
1320
|
-
29. ✅ **
|
|
1321
|
-
30. ✅ **
|
|
1322
|
-
31. ✅ **
|
|
1323
|
-
32. ✅ **
|
|
1387
|
+
28. ✅ **TypeScript**: Implement strict typing with proper types (NO `any`)
|
|
1388
|
+
29. ✅ **TypeScript**: Use union types for translation keys (e.g., `labelKey: 'product' | 'useCases'`)
|
|
1389
|
+
30. ✅ **i18n**: Specify namespace in `useTranslations('namespace')` for type safety
|
|
1390
|
+
31. ✅ **i18n**: Define typed keys in config to avoid dynamic string issues
|
|
1391
|
+
32. ✅ **Dropdowns**: Check array exists and has length before mapping (`items && items.length > 0`)
|
|
1392
|
+
33. ✅ **React**: Use `'use client'` directive for interactive components
|
|
1393
|
+
34. ✅ **Design**: Follow mobile-first responsive design
|
|
1394
|
+
35. ✅ **States**: Implement proper loading and error states
|
|
1395
|
+
36. ✅ **Patterns**: Follow existing code patterns and naming conventions
|
|
1324
1396
|
|
|
1325
1397
|
---
|
|
1326
1398
|
|
|
@@ -1329,7 +1401,6 @@ import { Navbar } from '@/components/layout';
|
|
|
1329
1401
|
When converting Figma to code, ensure:
|
|
1330
1402
|
|
|
1331
1403
|
### Icons & Assets
|
|
1332
|
-
|
|
1333
1404
|
- [ ] Exported icons as SVG from Figma
|
|
1334
1405
|
- [ ] Placed SVG files in `src/assets/icons/`
|
|
1335
1406
|
- [ ] Ran `pnpm generate-icons` to create React components
|
|
@@ -1344,7 +1415,6 @@ When converting Figma to code, ensure:
|
|
|
1344
1415
|
- [ ] Referenced via `IMAGES.LOGOS.PRIMARY` (not hardcoded paths)
|
|
1345
1416
|
|
|
1346
1417
|
### Component & Styling
|
|
1347
|
-
|
|
1348
1418
|
- [ ] Checked HeroUI library first for component availability
|
|
1349
1419
|
- [ ] Used HeroUI components via `src/components/ui/` wrappers (e.g., `<Button>` not `<button>`)
|
|
1350
1420
|
- [ ] Configured component styles in wrapper/theme (not inline)
|
|
@@ -1355,7 +1425,6 @@ When converting Figma to code, ensure:
|
|
|
1355
1425
|
- [ ] Mapped all data from constants (no hardcoded menu items/dropdowns)
|
|
1356
1426
|
|
|
1357
1427
|
### Content & Navigation
|
|
1358
|
-
|
|
1359
1428
|
- [ ] ALL text uses i18n translation keys (no hardcoded text)
|
|
1360
1429
|
- [ ] ALL labels use `t('labelKey')`
|
|
1361
1430
|
- [ ] ALL placeholders use `t('placeholderKey')`
|
|
@@ -1364,21 +1433,18 @@ When converting Figma to code, ensure:
|
|
|
1364
1433
|
- [ ] ALL URLs use `NAVIGATION_URLS` from config (no hardcoded URLs)
|
|
1365
1434
|
|
|
1366
1435
|
### Structure & Types
|
|
1367
|
-
|
|
1368
1436
|
- [ ] Page created in correct location (`src/app/[locale]/...`)
|
|
1369
1437
|
- [ ] Added TypeScript types for all data structures
|
|
1370
1438
|
- [ ] Used `'use client'` for interactive components
|
|
1371
1439
|
- [ ] No `any` types used
|
|
1372
1440
|
|
|
1373
1441
|
### Forms & Validation
|
|
1374
|
-
|
|
1375
1442
|
- [ ] Implemented forms with Zod validation schemas
|
|
1376
1443
|
- [ ] Form labels use i18n
|
|
1377
1444
|
- [ ] Form placeholders use i18n
|
|
1378
1445
|
- [ ] Form errors use i18n
|
|
1379
1446
|
|
|
1380
1447
|
### Responsive & States
|
|
1381
|
-
|
|
1382
1448
|
- [ ] Implemented responsive design (mobile-first)
|
|
1383
1449
|
- [ ] Added proper loading states
|
|
1384
1450
|
- [ ] Added proper error states
|
|
@@ -1391,14 +1457,12 @@ When converting Figma to code, ensure:
|
|
|
1391
1457
|
Before finalizing code, verify:
|
|
1392
1458
|
|
|
1393
1459
|
### Icons & Assets
|
|
1394
|
-
|
|
1395
1460
|
1. **Icons workflow** - All icons created via `pnpm generate-icons` (not manual)
|
|
1396
1461
|
2. **Icons source** - SVG files exist in `src/assets/icons/`
|
|
1397
1462
|
3. **Logos location** - All logos/images in `public/` directory (not `src/`)
|
|
1398
1463
|
4. **Image format** - SVG used for logos when possible (not PNG)
|
|
1399
1464
|
|
|
1400
1465
|
### Components & Styling
|
|
1401
|
-
|
|
1402
1466
|
1. **HeroUI components first** - Verify HeroUI library was checked before custom implementation
|
|
1403
1467
|
2. **No direct HeroUI imports** - Only through `@/components/ui`
|
|
1404
1468
|
3. **Component reuse** - Don't duplicate existing components
|
|
@@ -1408,20 +1472,17 @@ Before finalizing code, verify:
|
|
|
1408
1472
|
7. **Map from constants** - All menu items, dropdown options mapped from config files
|
|
1409
1473
|
|
|
1410
1474
|
### i18n & Navigation
|
|
1411
|
-
|
|
1412
1475
|
5. **Zero hardcoded text** - ALL text uses `t('key')`
|
|
1413
1476
|
6. **Zero hardcoded URLs** - ALL links use `NAVIGATION_URLS`
|
|
1414
1477
|
7. **Form fields i18n** - Labels, placeholders, errors all use translation keys
|
|
1415
1478
|
|
|
1416
1479
|
### Styling & Layout
|
|
1417
|
-
|
|
1418
1480
|
8. **Tailwind only** - No CSS modules, no styled-components
|
|
1419
1481
|
9. **HeroUI theme colors** - Use semantic colors, not arbitrary values
|
|
1420
1482
|
10. **Consistent spacing** - Use `space-y-*` and `space-x-*` utilities
|
|
1421
1483
|
11. **Mobile-first** - Base styles for mobile, then `md:`, `lg:`, etc.
|
|
1422
1484
|
|
|
1423
1485
|
### TypeScript & Validation
|
|
1424
|
-
|
|
1425
1486
|
12. **Proper TypeScript** - No `any`, all props typed
|
|
1426
1487
|
13. **Form validation** - Zod schemas for all forms
|
|
1427
1488
|
|
|
@@ -1430,7 +1491,6 @@ Before finalizing code, verify:
|
|
|
1430
1491
|
## 📚 Reference Files
|
|
1431
1492
|
|
|
1432
1493
|
When in doubt, check these example files:
|
|
1433
|
-
|
|
1434
1494
|
- **Form example**: `src/components/ui/form/Form.tsx`
|
|
1435
1495
|
- **Page example**: `src/app/[locale]/(auth)/login/page.tsx`
|
|
1436
1496
|
- **Component example**: `src/components/ui/Input.tsx`
|
|
@@ -1442,7 +1502,6 @@ When in doubt, check these example files:
|
|
|
1442
1502
|
## 🚀 Quick Reference Guide
|
|
1443
1503
|
|
|
1444
1504
|
### Icons Workflow
|
|
1445
|
-
|
|
1446
1505
|
```bash
|
|
1447
1506
|
# 1. Export SVG from Figma
|
|
1448
1507
|
# 2. Place in folder
|
|
@@ -1457,7 +1516,6 @@ import { IconIcon } from '@/components/icons';
|
|
|
1457
1516
|
```
|
|
1458
1517
|
|
|
1459
1518
|
### Logos/Images Workflow
|
|
1460
|
-
|
|
1461
1519
|
```bash
|
|
1462
1520
|
# 1. Export SVG/PNG from Figma
|
|
1463
1521
|
# 2. Place in public with organized folders
|
|
@@ -1479,15 +1537,17 @@ export const IMAGES = {
|
|
|
1479
1537
|
WEBSITE,
|
|
1480
1538
|
} as const;
|
|
1481
1539
|
|
|
1482
|
-
# 4. Use in code
|
|
1540
|
+
# 4. Use in code - ALWAYS use IMAGES constant
|
|
1483
1541
|
import { IMAGES } from '@/config/images';
|
|
1484
|
-
|
|
1485
|
-
#
|
|
1486
|
-
<Image src={IMAGES.LOGOS.
|
|
1542
|
+
|
|
1543
|
+
# ✅ CORRECT
|
|
1544
|
+
<Image src={IMAGES.LOGOS.BINA} alt="Logo" width={54} height={41} />
|
|
1545
|
+
|
|
1546
|
+
# ❌ WRONG - Hardcoded path
|
|
1547
|
+
<Image src="/images/logos/bina.png" alt="Logo" width={54} height={41} />
|
|
1487
1548
|
```
|
|
1488
1549
|
|
|
1489
1550
|
### Tailwind Standard Classes
|
|
1490
|
-
|
|
1491
1551
|
```tsx
|
|
1492
1552
|
// Heights/Widths
|
|
1493
1553
|
h-10 = 40px w-14 = 56px size-5 = 20px
|
|
@@ -1507,15 +1567,19 @@ text-lg = 18px text-3xl = 30px
|
|
|
1507
1567
|
```
|
|
1508
1568
|
|
|
1509
1569
|
### Common Patterns
|
|
1510
|
-
|
|
1511
1570
|
```tsx
|
|
1512
1571
|
// Icons (auto-generated)
|
|
1513
1572
|
import { MenuIcon, CloseIcon } from '@/components/icons';
|
|
1514
1573
|
<MenuIcon className="size-6 text-white" />
|
|
1515
1574
|
|
|
1516
|
-
// Logos (
|
|
1575
|
+
// Images & Logos (ALWAYS use IMAGES constant - NEVER hardcode)
|
|
1517
1576
|
import { IMAGES } from '@/config/images';
|
|
1518
|
-
|
|
1577
|
+
|
|
1578
|
+
// ✅ CORRECT
|
|
1579
|
+
<Image src={IMAGES.LOGOS.BINA} alt="Logo" width={54} height={41} />
|
|
1580
|
+
|
|
1581
|
+
// ❌ WRONG
|
|
1582
|
+
<Image src="/images/logos/bina.png" alt="Logo" />
|
|
1519
1583
|
|
|
1520
1584
|
// Navigation (Next.js Link + constants)
|
|
1521
1585
|
import Link from 'next/link';
|