@manufac/sculptor 1.0.0
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/.github/workflows/feedback.yml +50 -0
- package/.github/workflows/inline.yml +119 -0
- package/.github/workflows/insights.yml +160 -0
- package/.husky/pre-commit +2 -0
- package/.prettierrc.mjs +2 -0
- package/Readme.md +1 -0
- package/dist/claude/feedback.yml +50 -0
- package/dist/claude/inline.yml +119 -0
- package/dist/claude/insights.yml +160 -0
- package/dist/guidelines/MantineCore.md +110 -0
- package/dist/guidelines/MantineForm.md +172 -0
- package/dist/guidelines/MantineHooks.md +230 -0
- package/dist/guidelines/React.md +502 -0
- package/dist/guidelines/Typescript.md +53 -0
- package/dist/guidelines/TypescriptNamingConventions.md +117 -0
- package/dist/guidelines/Zod.md +175 -0
- package/dist/tsc/index.js +11 -0
- package/dist/tsc/utils.js +116 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/utils.d.ts +2 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/eslint.config.mjs +8 -0
- package/package.json +43 -0
- package/src/claude/feedback.yml +50 -0
- package/src/claude/inline.yml +119 -0
- package/src/claude/insights.yml +160 -0
- package/src/guidelines/MantineCore.md +110 -0
- package/src/guidelines/MantineForm.md +172 -0
- package/src/guidelines/MantineHooks.md +230 -0
- package/src/guidelines/React.md +502 -0
- package/src/guidelines/Typescript.md +53 -0
- package/src/guidelines/TypescriptNamingConventions.md +117 -0
- package/src/guidelines/Zod.md +175 -0
- package/src/index.ts +14 -0
- package/src/utils.ts +142 -0
- package/tsconfig.json +113 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
### React Coding Guidelines
|
|
2
|
+
|
|
3
|
+
#### Component Structure
|
|
4
|
+
|
|
5
|
+
1. **Component Naming**
|
|
6
|
+
- Each component name should be PascalCase.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
// Correct
|
|
10
|
+
function UserProfile(): JSX.Element {
|
|
11
|
+
return <div>Profile</div>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Incorrect
|
|
15
|
+
function userProfile(): JSX.Element {
|
|
16
|
+
return <div>Profile</div>;
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
2. **Component Declaration**
|
|
21
|
+
- Each component should be declared using the `function` keyword.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// Correct
|
|
25
|
+
function Button(): JSX.Element {
|
|
26
|
+
return <button>Click</button>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Incorrect - arrow function
|
|
30
|
+
const Button = (): JSX.Element => {
|
|
31
|
+
return <button>Click</button>;
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. **Return Type**
|
|
36
|
+
- Each component should have an explicit return type of `JSX.Element`.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
function UserCard(): JSX.Element {
|
|
40
|
+
return <div>User Card</div>;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
4. **Props Destructuring**
|
|
45
|
+
- Props should be destructured in the component parameters if less than 3.
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
interface UserCardProps {
|
|
51
|
+
name: string;
|
|
52
|
+
email: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Correct
|
|
56
|
+
function UserCard({ name, email }: UserCardProps): JSX.Element {
|
|
57
|
+
return (
|
|
58
|
+
<div>
|
|
59
|
+
<Text>{name}</Text>
|
|
60
|
+
<Text>{email}</Text>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Incorrect
|
|
66
|
+
function UserCard(props: UserCardProps): JSX.Element {
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
<Text>{props.name}</Text>
|
|
70
|
+
<Text>{props.email}</Text>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
5. **Unused Props**
|
|
77
|
+
- Avoid passing props that are not used inside the component.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
interface ButtonProps {
|
|
81
|
+
label: string;
|
|
82
|
+
onClick: MouseEventHandler<HTMLButtonElement>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Correct - only pass what's needed
|
|
86
|
+
<Button label="Submit" onClick={handleClick} />
|
|
87
|
+
|
|
88
|
+
// Incorrect - passing unused prop
|
|
89
|
+
<Button label="Submit" onClick={handleClick} unused={someValue} />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Type Safety
|
|
93
|
+
|
|
94
|
+
6. **Avoid Wide Types**
|
|
95
|
+
- Avoid using wide types like `ReactNode` or `ReactElement` unless absolutely necessary.
|
|
96
|
+
- Only use wide types when the component genuinely accepts any valid React children.
|
|
97
|
+
- Always add a comment explaining why the wide type is necessary.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
interface CardProps {
|
|
101
|
+
// ReactNode is necessary here because this component accepts any valid React children
|
|
102
|
+
// including strings, numbers, elements, fragments, and portals
|
|
103
|
+
children: ReactNode;
|
|
104
|
+
title: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function Card({ children, title }: CardProps): JSX.Element {
|
|
108
|
+
return (
|
|
109
|
+
<div>
|
|
110
|
+
<Title>{title}</Title>
|
|
111
|
+
{children}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
7. **Handler Type Safety**
|
|
118
|
+
- Each `onChange` handler, `onClick` handler, and other event handlers should have appropriately declared types.
|
|
119
|
+
- Use component-specific prop types or React's event handler types.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import type { MouseEventHandler } from 'react';
|
|
123
|
+
import type { TextInputProps } from '@mantine/core';
|
|
124
|
+
|
|
125
|
+
interface FormProps {
|
|
126
|
+
onSubmit: MouseEventHandler<HTMLButtonElement>;
|
|
127
|
+
onChange: TextInputProps["onChange"];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function Form({ onSubmit, onChange }: FormProps): JSX.Element {
|
|
131
|
+
return (
|
|
132
|
+
<form>
|
|
133
|
+
<TextInput onChange={onChange} />
|
|
134
|
+
<Button onClick={onSubmit}>Submit</Button>
|
|
135
|
+
</form>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Code Organization
|
|
141
|
+
|
|
142
|
+
8. **Handler Functions**
|
|
143
|
+
- All handlers declared inside the component should be arrow functions.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
function UserForm(): JSX.Element {
|
|
147
|
+
// Correct - arrow function
|
|
148
|
+
const handleSubmit: MouseEventHandler<HTMLButtonElement> = (event) => {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
// Handle submit
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Incorrect - function declaration
|
|
154
|
+
function handleSubmit(event: MouseEvent<HTMLButtonElement>): void {
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return <Button onClick={handleSubmit}>Submit</Button>;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
9. **Utility Functions**
|
|
163
|
+
- Extract business logic, calculations, and data transformations into utility functions.
|
|
164
|
+
- Move utility functions outside the component into a `utils.ts` file.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// utils.ts
|
|
168
|
+
export function calculateTotal(items: CartItem[]): number {
|
|
169
|
+
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function formatCurrency(amount: number): string {
|
|
173
|
+
return new Intl.NumberFormat('en-US', {
|
|
174
|
+
style: 'currency',
|
|
175
|
+
currency: 'USD',
|
|
176
|
+
}).format(amount);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// CartSummary/index.tsx
|
|
180
|
+
import { calculateTotal, formatCurrency } from './utils';
|
|
181
|
+
|
|
182
|
+
function CartSummary({ items }: CartSummaryProps): JSX.Element {
|
|
183
|
+
const total = calculateTotal(items);
|
|
184
|
+
|
|
185
|
+
return <Text>{formatCurrency(total)}</Text>;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
10. **File Naming Convention**
|
|
190
|
+
- All component file names should follow the pattern `ComponentName/index.tsx`.
|
|
191
|
+
- If a file does not contain any component, its extension should not be `.tsx`.
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
src/
|
|
195
|
+
components/
|
|
196
|
+
UserProfile/
|
|
197
|
+
index.tsx // Component file
|
|
198
|
+
utils.ts // Utility functions (no JSX)
|
|
199
|
+
types.ts // Type definitions (no JSX)
|
|
200
|
+
styles.module.css
|
|
201
|
+
Button/
|
|
202
|
+
index.tsx
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Data Fetching & Mutations
|
|
206
|
+
|
|
207
|
+
11. **TanStack Query**
|
|
208
|
+
- Use TanStack Query for all data fetching and mutations.
|
|
209
|
+
- Use `useQuery` for fetching data.
|
|
210
|
+
- Use `useMutation` for creating, updating, or deleting data.
|
|
211
|
+
- Define query keys in a separate constants file for reusability.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
// queryKeys.ts
|
|
215
|
+
export const queryKeys = {
|
|
216
|
+
users: {
|
|
217
|
+
all: ['users'] as const,
|
|
218
|
+
detail: (id: string) => ['users', id] as const,
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// useGetUser.ts
|
|
223
|
+
import { useQuery } from '@tanstack/react-query';
|
|
224
|
+
import { queryKeys } from './queryKeys';
|
|
225
|
+
|
|
226
|
+
interface User {
|
|
227
|
+
id: string;
|
|
228
|
+
name: string;
|
|
229
|
+
email: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function fetchUser(userId: string): Promise<User> {
|
|
233
|
+
return fetch(`/api/users/${userId}`).then((res) => res.json());
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function useGetUser(userId: string) {
|
|
237
|
+
return useQuery({
|
|
238
|
+
queryKey: queryKeys.users.detail(userId),
|
|
239
|
+
queryFn: () => fetchUser(userId),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// useUpdateUser.ts
|
|
244
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
245
|
+
import { queryKeys } from './queryKeys';
|
|
246
|
+
|
|
247
|
+
interface UpdateUserParams {
|
|
248
|
+
userId: string;
|
|
249
|
+
data: Partial<User>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function updateUser(params: UpdateUserParams): Promise<User> {
|
|
253
|
+
return fetch(`/api/users/${params.userId}`, {
|
|
254
|
+
method: 'PATCH',
|
|
255
|
+
body: JSON.stringify(params.data),
|
|
256
|
+
}).then((res) => res.json());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function useUpdateUser() {
|
|
260
|
+
const queryClient = useQueryClient();
|
|
261
|
+
|
|
262
|
+
return useMutation({
|
|
263
|
+
mutationFn: updateUser,
|
|
264
|
+
onSuccess: (data) => {
|
|
265
|
+
queryClient.invalidateQueries({
|
|
266
|
+
queryKey: queryKeys.users.detail(data.id),
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// UserProfile/index.tsx
|
|
273
|
+
function UserProfile({ userId }: UserProfileProps): JSX.Element {
|
|
274
|
+
const { data: user, isPending } = useGetUser(userId);
|
|
275
|
+
const { mutate: updateUser } = useUpdateUser();
|
|
276
|
+
|
|
277
|
+
const handleUpdate: MouseEventHandler<HTMLButtonElement> = () => {
|
|
278
|
+
updateUser({ userId, data: { name: 'New Name' } });
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
if (isPending) {
|
|
282
|
+
return <Loader />;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<div>
|
|
287
|
+
<Text>{user?.name}</Text>
|
|
288
|
+
<Button onClick={handleUpdate}>Update</Button>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### Modern React Patterns
|
|
295
|
+
|
|
296
|
+
12. **React 19 Best Practices**
|
|
297
|
+
- Follow React 19 best practices from https://react.dev/blog/2024/12/05/react-19
|
|
298
|
+
- Use React 19 Actions for form submissions and mutations when not using TanStack Query.
|
|
299
|
+
- Prefer the `use` hook for reading promises and context.
|
|
300
|
+
- Utilize ref callbacks for DOM measurements.
|
|
301
|
+
- Use `useOptimistic` for optimistic UI updates.
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
// Using Actions for form submission (when not using TanStack Query)
|
|
305
|
+
import { useActionState } from 'react';
|
|
306
|
+
|
|
307
|
+
async function submitForm(prevState: FormState, formData: FormData): Promise<FormState> {
|
|
308
|
+
const name = formData.get('name') as string;
|
|
309
|
+
// Process form data
|
|
310
|
+
return { success: true, message: 'Form submitted' };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function ContactForm(): JSX.Element {
|
|
314
|
+
const [state, formAction, isPending] = useActionState(submitForm, {
|
|
315
|
+
success: false,
|
|
316
|
+
message: '',
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
<form action={formAction}>
|
|
321
|
+
<TextInput name="name" />
|
|
322
|
+
<Button type="submit" loading={isPending}>
|
|
323
|
+
Submit
|
|
324
|
+
</Button>
|
|
325
|
+
{state.message !== '' ? <Text>{state.message}</Text> : null}
|
|
326
|
+
</form>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
13. **Escape hatches**
|
|
332
|
+
- Refer https://react.dev/learn/escape-hatches for detecting escape hatches.
|
|
333
|
+
- Any usage of an escape hatch must include a comment explaining:
|
|
334
|
+
- Why it is necessary
|
|
335
|
+
- Why a declarative alternative was not possible
|
|
336
|
+
- What risk it introduces
|
|
337
|
+
|
|
338
|
+
#### Performance Optimization
|
|
339
|
+
|
|
340
|
+
14. **Memoization**
|
|
341
|
+
- Use `memo` to prevent unnecessary re-renders of components that receive the same props.
|
|
342
|
+
- Use `useMemo` for expensive calculations.
|
|
343
|
+
- Use `useCallback` for functions passed as props to memoized components.
|
|
344
|
+
- Always add comments explaining why memoization is necessary.
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
import { memo, useMemo, useCallback } from 'react';
|
|
348
|
+
|
|
349
|
+
interface ExpensiveListProps {
|
|
350
|
+
items: Item[];
|
|
351
|
+
onItemClick: (id: string) => void;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Memoized to prevent re-renders when parent re-renders
|
|
355
|
+
// but items and onItemClick remain the same
|
|
356
|
+
export const ExpensiveList = memo(function ExpensiveList({
|
|
357
|
+
items,
|
|
358
|
+
onItemClick,
|
|
359
|
+
}: ExpensiveListProps): JSX.Element {
|
|
360
|
+
// Expensive calculation memoized to avoid recalculation on every render
|
|
361
|
+
const sortedItems = useMemo(() => {
|
|
362
|
+
return [...items].sort((a, b) => a.priority - b.priority);
|
|
363
|
+
}, [items]);
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<Stack>
|
|
367
|
+
{sortedItems.map((item) => (
|
|
368
|
+
<ItemCard key={item.id} item={item} onClick={onItemClick} />
|
|
369
|
+
))}
|
|
370
|
+
</Stack>
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
function ItemList(): JSX.Element {
|
|
375
|
+
const { data: items } = useGetItems();
|
|
376
|
+
|
|
377
|
+
// Memoized because it's passed to a memoized component
|
|
378
|
+
const handleItemClick = useCallback((id: string) => {
|
|
379
|
+
console.log('Item clicked:', id);
|
|
380
|
+
}, []);
|
|
381
|
+
|
|
382
|
+
return <ExpensiveList items={items ?? []} onItemClick={handleItemClick} />;
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
15. **List Rendering**
|
|
387
|
+
- Always provide a unique, stable `key` prop when rendering lists.
|
|
388
|
+
- Use unique identifiers (IDs) as keys, not array indices.
|
|
389
|
+
- Add a comment if array index must be used as a key.
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
// Correct - using unique ID
|
|
393
|
+
function UserList({ users }: UserListProps): JSX.Element {
|
|
394
|
+
return (
|
|
395
|
+
<Stack>
|
|
396
|
+
{users.map((user) => (
|
|
397
|
+
<UserCard key={user.id} user={user} />
|
|
398
|
+
))}
|
|
399
|
+
</Stack>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Incorrect - using array index
|
|
404
|
+
function UserList({ users }: UserListProps): JSX.Element {
|
|
405
|
+
return (
|
|
406
|
+
<Stack>
|
|
407
|
+
{users.map((user, index) => (
|
|
408
|
+
<UserCard key={index} user={user} />
|
|
409
|
+
))}
|
|
410
|
+
</Stack>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Acceptable with comment - when items have no unique identifier
|
|
415
|
+
function StaticList({ items }: StaticListProps): JSX.Element {
|
|
416
|
+
return (
|
|
417
|
+
<Stack>
|
|
418
|
+
{items.map((item, index) => (
|
|
419
|
+
// Index used as key because items are static and have no unique identifier
|
|
420
|
+
<Text key={index}>{item}</Text>
|
|
421
|
+
))}
|
|
422
|
+
</Stack>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### Conditional Rendering
|
|
428
|
+
|
|
429
|
+
16. **Conditional Rendering Patterns**
|
|
430
|
+
- Use explicit `if` statements for complex conditions or when performing side effects.
|
|
431
|
+
- Use ternary operators for simple conditional rendering.
|
|
432
|
+
- Use logical `&&` operator only for boolean conditions, never for truthy/falsy checks.
|
|
433
|
+
- Always ensure the left side of `&&` evaluates to a boolean.
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
function UserProfile({ user }: UserProfileProps): JSX.Element {
|
|
437
|
+
// Correct - explicit boolean check with &&
|
|
438
|
+
return (
|
|
439
|
+
<Stack>
|
|
440
|
+
{user.isActive === true && <Badge>Active</Badge>}
|
|
441
|
+
|
|
442
|
+
{/* Correct - ternary for simple either/or */}
|
|
443
|
+
{user.isPremium === true ? <PremiumBadge /> : <FreeBadge />}
|
|
444
|
+
</Stack>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function ProductList({ products }: ProductListProps): JSX.Element {
|
|
449
|
+
// Incorrect - truthy check with && can render 0
|
|
450
|
+
// {products.length && <Text>Products available</Text>}
|
|
451
|
+
|
|
452
|
+
// Correct - explicit boolean check
|
|
453
|
+
if (products.length === 0) {
|
|
454
|
+
return <Text>No products available</Text>;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
<Stack>
|
|
459
|
+
{products.map((product) => (
|
|
460
|
+
<ProductCard key={product.id} product={product} />
|
|
461
|
+
))}
|
|
462
|
+
</Stack>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
#### Custom Hooks
|
|
468
|
+
|
|
469
|
+
17. **Custom Hook Conventions**
|
|
470
|
+
- Custom hooks must start with `use` prefix.
|
|
471
|
+
- Extract reusable logic into custom hooks.
|
|
472
|
+
- Custom hooks should have explicit return types.
|
|
473
|
+
- Place custom hooks in a `hooks` directory.
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
// hooks/useLocalStorage.ts
|
|
477
|
+
import { useState, useEffect } from "react";
|
|
478
|
+
|
|
479
|
+
interface UseLocalStorageReturn<T> {
|
|
480
|
+
value: T;
|
|
481
|
+
setValue: (value: T) => void;
|
|
482
|
+
removeValue: () => void;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function useLocalStorage<T>(key: string, initialValue: T): UseLocalStorageReturn<T> {
|
|
486
|
+
const [value, setValue] = useState<T>(() => {
|
|
487
|
+
const item = window.localStorage.getItem(key);
|
|
488
|
+
return item !== null ? JSON.parse(item) : initialValue;
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
493
|
+
}, [key, value]);
|
|
494
|
+
|
|
495
|
+
const removeValue = (): void => {
|
|
496
|
+
window.localStorage.removeItem(key);
|
|
497
|
+
setValue(initialValue);
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
return { value, setValue, removeValue };
|
|
501
|
+
}
|
|
502
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
### Coding conventions guidelines
|
|
2
|
+
|
|
3
|
+
1. All top-level and named functions must be declared using a `function` declaration.
|
|
4
|
+
2. Arrow functions are permitted only for callbacks and inline anonymous functions.
|
|
5
|
+
3. Function expression is not allowed.
|
|
6
|
+
4. All functions must explicitly declare their return type.
|
|
7
|
+
5. If a function has more than 2 parameters, require a single object parameter typed using an explicit interface or type alias.
|
|
8
|
+
6. Do not use short-circuiting operators (`&&`, `||`) **as a substitute for control-flow or side-effect execution** (e.g. conditionally calling functions or executing statements).
|
|
9
|
+
|
|
10
|
+
- Short-circuiting operators **are allowed** in pure boolean expressions, conditions, return values, and assignments.
|
|
11
|
+
- Use explicit `if` statements or a ternary operator when performing branching logic or side effects.
|
|
12
|
+
|
|
13
|
+
5. Condition checks must evaluate to boolean values:
|
|
14
|
+
- For boolean variables, use direct checks (e.g. `if (isValid)`).
|
|
15
|
+
- For non-boolean expressions, use explicit comparisons (e.g. `array.length === 0`).
|
|
16
|
+
6. Use `array.at(0)` to access the element of an array.
|
|
17
|
+
7. For derived objects, use `as const satisfies`:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
interface XYZ {
|
|
21
|
+
name: string;
|
|
22
|
+
email: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const XYZFormNames = {
|
|
26
|
+
name: "name",
|
|
27
|
+
email: "email",
|
|
28
|
+
} as const satisfies Record<keyof XYZ, keyof XYZ>;
|
|
29
|
+
|
|
30
|
+
const XYZFormLabels = {
|
|
31
|
+
name: "Full Name",
|
|
32
|
+
email: "Email Address",
|
|
33
|
+
} as const satisfies Record<keyof XYZ, string>;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
8. For un-derived objects use type declaration.
|
|
37
|
+
Example:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const User = {
|
|
41
|
+
name: "abc",
|
|
42
|
+
email: "xyz",
|
|
43
|
+
}; // This is incorrect
|
|
44
|
+
|
|
45
|
+
interface User {
|
|
46
|
+
name: string;
|
|
47
|
+
email: string;
|
|
48
|
+
}
|
|
49
|
+
const UserDetails: User = {
|
|
50
|
+
name: "abc",
|
|
51
|
+
email: "xyz",
|
|
52
|
+
}; // This is correct.
|
|
53
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
### Variable naming guidelines
|
|
2
|
+
|
|
3
|
+
- Identify project domain using project codebase.
|
|
4
|
+
- Use domain-driven development guidelines regarding naming conventions.
|
|
5
|
+
- Abbreviated/acronym in the variable names should be all in uppercase.
|
|
6
|
+
example:
|
|
7
|
+
- testURL is preferred instead of testUrl
|
|
8
|
+
- chartID is preferred instead of chartId
|
|
9
|
+
- userIDs is preferred instead of userIds
|
|
10
|
+
- httpURL is preferred instead of httpUrl
|
|
11
|
+
- Check for spelling errors in the variable names.
|
|
12
|
+
- If a compound word exists as a single dictionary word, it should not have camel casing. When uncertain if a compound word is a single dictionary entry, consult standard dictionaries (Merriam-Webster, Oxford)
|
|
13
|
+
example:
|
|
14
|
+
- subtitle is correct while subTitle is incorrect
|
|
15
|
+
- database is correct while dataBase is incorrect
|
|
16
|
+
- username is correct while userName is incorrect
|
|
17
|
+
- When a compound dictionary word contains an acronym, the acronym rule takes precedence.
|
|
18
|
+
example:
|
|
19
|
+
- databaseURL is correct while databaseUrl is incorrect
|
|
20
|
+
- Global variables should be PascalCase except for global variables that are arrow functions or higher order functions, which should be camelCase.
|
|
21
|
+
example:
|
|
22
|
+
- `const UserConfig = {...}` (PascalCase for regular global variable)
|
|
23
|
+
- `const fetchData = () => {...}` (camelCase for global arrow function)
|
|
24
|
+
- `const withAuth = (component) => {...}` (camelCase for global HOF)
|
|
25
|
+
- Variable names should be in camelCase or PascalCase. No other format is accepted.
|
|
26
|
+
- Use camelCase for local variables and function parameters
|
|
27
|
+
- Use PascalCase for classes, components, and global variables but functions names should not use pascal case.
|
|
28
|
+
- Snake cases or screaming snake case are not accepted. We are intentionally avoiding the snake case naming for constants even though it is preferred by JS conventions.
|
|
29
|
+
example:
|
|
30
|
+
- `const MAX_RETRY_COUNT = 5` is incorrect while `const MaxRetryCount = 5` is correct
|
|
31
|
+
- `const user_id = response.user_id` is incorrect while `const userID = response.user_id` is correct (follows camelCase with uppercase acronym rule)
|
|
32
|
+
- When mapping API responses with nested objects, transform all levels from snake_case to camelCase.
|
|
33
|
+
example:
|
|
34
|
+
- `const user = { userID: response.user_id, profileData: response.profile_data }` is correct
|
|
35
|
+
- Kebab case values are allowed only in object values.
|
|
36
|
+
example:
|
|
37
|
+
- `const styles = { className: "user-profile" }` (kebab-case string value is OK)
|
|
38
|
+
- `const config = { "user-id": 123 }` (kebab-case as object key is incorrect)
|
|
39
|
+
- Use only named imports and exports.
|
|
40
|
+
- All boolean variables should start with a helping verb: `is`, `has`, `can`, or `should`.
|
|
41
|
+
example:
|
|
42
|
+
- isNull, isValid, isLoading
|
|
43
|
+
- hasPermission, hasError
|
|
44
|
+
- canEdit, canDelete
|
|
45
|
+
- shouldUpdate, shouldRender
|
|
46
|
+
- Negative boolean conditions should be avoided unless necessary for code readability. When used, they must be accompanied by a comment explaining the positive context.
|
|
47
|
+
example:
|
|
48
|
+
- ❌ `const isInvalid = checkValidation()` (avoid negative naming)
|
|
49
|
+
- ✅ `const isValid = checkValidation()` (use positive naming with ! operator when needed)
|
|
50
|
+
- ✅ `const isDisabled = true // Indicates the button cannot be clicked` (acceptable with explanatory comment)
|
|
51
|
+
|
|
52
|
+
### Types naming conventions
|
|
53
|
+
|
|
54
|
+
- Interface names should be PascalCase and they should not start with `I`.
|
|
55
|
+
example:
|
|
56
|
+
- IUser is invalid while User is valid interface name
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// ❌ Invalid
|
|
60
|
+
interface IUser {
|
|
61
|
+
name: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ✅ Valid
|
|
65
|
+
interface User {
|
|
66
|
+
name: string;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- Type alias names should be PascalCase.
|
|
71
|
+
example:
|
|
72
|
+
- `type UserRole = string | number` (PascalCase type name)
|
|
73
|
+
- Avoid type aliases unless necessary for union types or mapped types. Prefer interfaces for object shapes.
|
|
74
|
+
example:
|
|
75
|
+
- `type UserRole = string | number` (acceptable for union types)
|
|
76
|
+
- `type User = { name: string }` (avoid, use interface instead)
|
|
77
|
+
- `interface User { name: string }` (preferred)
|
|
78
|
+
- Union types are allowed when used with:
|
|
79
|
+
- Primitive types
|
|
80
|
+
- `type UserRole = string | number` (PascalCase type name)
|
|
81
|
+
- Complex types (interfaces, objects)
|
|
82
|
+
```ts
|
|
83
|
+
interface User {
|
|
84
|
+
email: string;
|
|
85
|
+
role: string;
|
|
86
|
+
}
|
|
87
|
+
type Actor = string | User;
|
|
88
|
+
```
|
|
89
|
+
- Enums should not be used. Use union types with const objects instead.
|
|
90
|
+
example:
|
|
91
|
+
- ❌ `enum UserRole { Admin, Editor, Viewer }`
|
|
92
|
+
- Predefined / Finite Domain Values
|
|
93
|
+
- If a value is predefined and limited (e.g. roles like admin, user, editor):
|
|
94
|
+
- You must create a const dictionary
|
|
95
|
+
- The dictionary name must be PascalCase
|
|
96
|
+
- Use as const
|
|
97
|
+
- Derive the type using keyof typeof
|
|
98
|
+
|
|
99
|
+
```TS
|
|
100
|
+
const UserRoles = {
|
|
101
|
+
Admin: 'admin',
|
|
102
|
+
Editor: 'editor',
|
|
103
|
+
Viewer: 'viewer',
|
|
104
|
+
} as const
|
|
105
|
+
|
|
106
|
+
type UserRole = (typeof UserRoles)[keyof typeof UserRoles]
|
|
107
|
+
|
|
108
|
+
interface User {
|
|
109
|
+
email: string
|
|
110
|
+
role: UserRole
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### File naming conventions
|
|
115
|
+
|
|
116
|
+
1. All `*.ts` file name should be kebab cased.
|
|
117
|
+
2. All `*.md` file name should be Pascal case.
|