@blastlabs/utils 1.19.0 → 1.21.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/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export * from './form';
3
3
  export * from './string';
4
4
  export * from './number';
5
5
  export * from './mock';
6
+ export * from './routes';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAGvB,cAAc,UAAU,CAAC;AAGzB,cAAc,UAAU,CAAC;AAGzB,cAAc,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAGvB,cAAc,UAAU,CAAC;AAGzB,cAAc,UAAU,CAAC;AAGzB,cAAc,QAAQ,CAAC;AAYvB,cAAc,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -15,3 +15,4 @@ export * from './mock';
15
15
  // export * from './array';
16
16
  // Object utilities (placeholder for future)
17
17
  // export * from './object';
18
+ export * from './routes';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 인증 관련 라우트 경로
3
+ */
4
+ export declare const AUTH_ROUTES: {
5
+ readonly ROOT: "/auth";
6
+ readonly LOGIN: string;
7
+ readonly REGISTER: string;
8
+ readonly FORGOT_PASSWORD: string;
9
+ readonly RESET_PASSWORD: string;
10
+ readonly VERIFY_EMAIL: string;
11
+ readonly VERIFY_OTP: string;
12
+ };
13
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,eAAO,MAAM,WAAW;;;;;;;;CAQd,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 인증 관련 라우트 경로
3
+ */
4
+ import { createRoute } from './utils/helpers';
5
+ const AUTH_BASE = '/auth';
6
+ export const AUTH_ROUTES = {
7
+ ROOT: AUTH_BASE,
8
+ LOGIN: createRoute(AUTH_BASE, '/login'),
9
+ REGISTER: createRoute(AUTH_BASE, '/register'),
10
+ FORGOT_PASSWORD: createRoute(AUTH_BASE, '/forgot-password'),
11
+ RESET_PASSWORD: createRoute(AUTH_BASE, '/reset-password'),
12
+ VERIFY_EMAIL: createRoute(AUTH_BASE, '/verify-email'),
13
+ VERIFY_OTP: createRoute(AUTH_BASE, '/verify-otp'),
14
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 기본 라우트 경로
3
+ */
4
+ export declare const BASE_ROUTES: {
5
+ readonly HOME: "/";
6
+ };
7
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/routes/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,WAAW;;CAEd,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 기본 라우트 경로
3
+ */
4
+ export const BASE_ROUTES = {
5
+ HOME: '/',
6
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 애플리케이션 라우트 경로 상수
3
+ * Next.js App Router 기준으로 작성되었습니다.
4
+ *
5
+ * 사용 예시:
6
+ * - ROUTE_PATH.HOME
7
+ * - ROUTE_PATH.AUTH.LOGIN
8
+ * - withParam(ROUTE_PATH.USERS.DETAIL, { id: '123' })
9
+ */
10
+ /**
11
+ * 라우트 경로 상수 객체
12
+ */
13
+ export declare const ROUTE_PATH: {
14
+ readonly AUTH: {
15
+ readonly ROOT: "/auth";
16
+ readonly LOGIN: string;
17
+ readonly REGISTER: string;
18
+ readonly FORGOT_PASSWORD: string;
19
+ readonly RESET_PASSWORD: string;
20
+ readonly VERIFY_EMAIL: string;
21
+ readonly VERIFY_OTP: string;
22
+ };
23
+ readonly POST: {
24
+ readonly ROOT: "/post";
25
+ readonly LIST: "/post";
26
+ readonly CREATE: string;
27
+ readonly DETAIL: string;
28
+ };
29
+ readonly HOME: "/";
30
+ };
31
+ export { withParam, withQuery } from './utils/helpers';
32
+ export type { RoutePath } from './types';
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;GAEG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;CAIb,CAAC;AAGX,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGvD,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 애플리케이션 라우트 경로 상수
3
+ * Next.js App Router 기준으로 작성되었습니다.
4
+ *
5
+ * 사용 예시:
6
+ * - ROUTE_PATH.HOME
7
+ * - ROUTE_PATH.AUTH.LOGIN
8
+ * - withParam(ROUTE_PATH.USERS.DETAIL, { id: '123' })
9
+ */
10
+ import { BASE_ROUTES } from './base';
11
+ import { AUTH_ROUTES } from './auth';
12
+ import { POST_ROUTES } from './post';
13
+ /**
14
+ * 라우트 경로 상수 객체
15
+ */
16
+ export const ROUTE_PATH = {
17
+ ...BASE_ROUTES,
18
+ AUTH: AUTH_ROUTES,
19
+ POST: POST_ROUTES,
20
+ };
21
+ // 유틸리티 함수 재내보내기
22
+ export { withParam, withQuery } from './utils/helpers';
@@ -0,0 +1,7 @@
1
+ export declare const POST_ROUTES: {
2
+ readonly ROOT: "/post";
3
+ readonly LIST: "/post";
4
+ readonly CREATE: string;
5
+ readonly DETAIL: string;
6
+ };
7
+ //# sourceMappingURL=post.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post.d.ts","sourceRoot":"","sources":["../../src/routes/post.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW;;;;;CAKd,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { createRoute } from './utils/helpers';
2
+ const POST_BASE = '/post';
3
+ export const POST_ROUTES = {
4
+ ROOT: POST_BASE,
5
+ LIST: POST_BASE,
6
+ CREATE: createRoute(POST_BASE, '/create'),
7
+ DETAIL: createRoute(POST_BASE, '/[id]'),
8
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 라우트 경로 관련 타입 정의
3
+ */
4
+ import type { ROUTE_PATH } from './index';
5
+ /**
6
+ * 라우트 경로 타입
7
+ */
8
+ export type RoutePath = typeof ROUTE_PATH;
9
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/routes/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 라우트 경로 관련 타입 정의
3
+ */
4
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 사용자 관련 라우트 경로 예시
3
+ *
4
+ * 새로운 도메인 라우트를 추가할 때 이 파일을 참고하세요.
5
+ *
6
+ * 사용 방법:
7
+ * 1. 이 파일을 users.ts로 이름 변경
8
+ * 2. routes/index.ts에서 import하여 ROUTE_PATH에 추가
9
+ */
10
+ export declare const USERS_ROUTES: {
11
+ readonly ROOT: "/users";
12
+ readonly LIST: "/users";
13
+ readonly DETAIL: string;
14
+ readonly CREATE: string;
15
+ readonly EDIT: string;
16
+ readonly SETTINGS: string;
17
+ readonly PROFILE: string;
18
+ };
19
+ //# sourceMappingURL=users.example.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.example.d.ts","sourceRoot":"","sources":["../../src/routes/users.example.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,eAAO,MAAM,YAAY;;;;;;;;CAQf,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 사용자 관련 라우트 경로 예시
3
+ *
4
+ * 새로운 도메인 라우트를 추가할 때 이 파일을 참고하세요.
5
+ *
6
+ * 사용 방법:
7
+ * 1. 이 파일을 users.ts로 이름 변경
8
+ * 2. routes/index.ts에서 import하여 ROUTE_PATH에 추가
9
+ */
10
+ import { createRoute } from './utils/helpers';
11
+ const USERS_BASE = '/users';
12
+ export const USERS_ROUTES = {
13
+ ROOT: USERS_BASE,
14
+ LIST: USERS_BASE,
15
+ DETAIL: createRoute(USERS_BASE, '/[id]'),
16
+ CREATE: createRoute(USERS_BASE, '/create'),
17
+ EDIT: createRoute(USERS_BASE, '/[id]/edit'),
18
+ SETTINGS: createRoute(USERS_BASE, '/[id]/settings'),
19
+ PROFILE: createRoute(USERS_BASE, '/profile'),
20
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 라우트 경로 관련 유틸리티 함수
3
+ */
4
+ /**
5
+ * 동적 라우트 파라미터를 포함한 경로를 생성하는 헬퍼 함수
6
+ * @param path - 기본 경로 (예: '/users/[id]')
7
+ * @param params - 파라미터 객체 (예: { id: '123' })
8
+ * @returns 실제 경로 (예: '/users/123')
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const userPath = withParam('/users/[id]', { id: '123' });
13
+ * // '/users/123'
14
+ * ```
15
+ */
16
+ export declare function withParam<T extends string>(path: T, params: Record<string, string | number>): string;
17
+ /**
18
+ * 쿼리 파라미터를 포함한 경로를 생성하는 헬퍼 함수
19
+ * @param path - 기본 경로
20
+ * @param query - 쿼리 파라미터 객체
21
+ * @returns 쿼리 파라미터가 포함된 경로
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const searchPath = withQuery('/search', { q: 'test', page: 1 });
26
+ * // '/search?q=test&page=1'
27
+ * ```
28
+ */
29
+ export declare function withQuery(path: string, query: Record<string, string | number | boolean | null | undefined>): string;
30
+ /**
31
+ * 라우트 경로를 생성하는 헬퍼 함수
32
+ * @param base - 기본 경로
33
+ * @param path - 추가 경로
34
+ * @returns 합쳐진 경로
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const route = createRoute('/auth', '/login');
39
+ * // '/auth/login'
40
+ * ```
41
+ */
42
+ export declare function createRoute(base: string, path: string): string;
43
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/routes/utils/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EACxC,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GACtC,MAAM,CAMR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,GAClE,MAAM,CASR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9D"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * 라우트 경로 관련 유틸리티 함수
3
+ */
4
+ /**
5
+ * 동적 라우트 파라미터를 포함한 경로를 생성하는 헬퍼 함수
6
+ * @param path - 기본 경로 (예: '/users/[id]')
7
+ * @param params - 파라미터 객체 (예: { id: '123' })
8
+ * @returns 실제 경로 (예: '/users/123')
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const userPath = withParam('/users/[id]', { id: '123' });
13
+ * // '/users/123'
14
+ * ```
15
+ */
16
+ export function withParam(path, params) {
17
+ let result = path;
18
+ for (const [key, value] of Object.entries(params)) {
19
+ result = result.replace(`[${key}]`, String(value));
20
+ }
21
+ return result;
22
+ }
23
+ /**
24
+ * 쿼리 파라미터를 포함한 경로를 생성하는 헬퍼 함수
25
+ * @param path - 기본 경로
26
+ * @param query - 쿼리 파라미터 객체
27
+ * @returns 쿼리 파라미터가 포함된 경로
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const searchPath = withQuery('/search', { q: 'test', page: 1 });
32
+ * // '/search?q=test&page=1'
33
+ * ```
34
+ */
35
+ export function withQuery(path, query) {
36
+ const searchParams = new URLSearchParams();
37
+ for (const [key, value] of Object.entries(query)) {
38
+ if (value !== null && value !== undefined) {
39
+ searchParams.append(key, String(value));
40
+ }
41
+ }
42
+ const queryString = searchParams.toString();
43
+ return queryString ? `${path}?${queryString}` : path;
44
+ }
45
+ /**
46
+ * 라우트 경로를 생성하는 헬퍼 함수
47
+ * @param base - 기본 경로
48
+ * @param path - 추가 경로
49
+ * @returns 합쳐진 경로
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const route = createRoute('/auth', '/login');
54
+ * // '/auth/login'
55
+ * ```
56
+ */
57
+ export function createRoute(base, path) {
58
+ return `${base}${path.startsWith('/') ? path : `/${path}`}`;
59
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=helpers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.test.d.ts","sourceRoot":"","sources":["../../../src/routes/utils/helpers.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,106 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { withParam, withQuery, createRoute } from './helpers';
3
+ describe('routes/utils/helpers', () => {
4
+ describe('withParam', () => {
5
+ it('should replace single parameter', () => {
6
+ expect(withParam('/users/[id]', { id: '123' })).toBe('/users/123');
7
+ expect(withParam('/posts/[postId]', { postId: '456' })).toBe('/posts/456');
8
+ });
9
+ it('should replace multiple parameters', () => {
10
+ expect(withParam('/posts/[postId]/comments/[commentId]', {
11
+ postId: '456',
12
+ commentId: '789',
13
+ })).toBe('/posts/456/comments/789');
14
+ expect(withParam('/users/[userId]/posts/[postId]/comments/[commentId]', {
15
+ userId: '1',
16
+ postId: '2',
17
+ commentId: '3',
18
+ })).toBe('/users/1/posts/2/comments/3');
19
+ });
20
+ it('should handle parameters in any order', () => {
21
+ const path = '/posts/[postId]/comments/[commentId]';
22
+ // 순서대로
23
+ expect(withParam(path, { postId: '456', commentId: '789' })).toBe('/posts/456/comments/789');
24
+ // 반대 순서
25
+ expect(withParam(path, { commentId: '789', postId: '456' })).toBe('/posts/456/comments/789');
26
+ });
27
+ it('should handle nested key names safely', () => {
28
+ // id가 postId의 부분 문자열이지만 정확한 일치만 바꿈
29
+ expect(withParam('/posts/[id]/comments/[postId]', { id: '123', postId: '456' })).toBe('/posts/123/comments/456');
30
+ expect(withParam('/posts/[id]/comments/[postId]', { postId: '456', id: '123' })).toBe('/posts/123/comments/456');
31
+ });
32
+ it('should replace only first occurrence (using replace, not replaceAll)', () => {
33
+ // 현재 구현은 replace()를 사용하므로 첫 번째 매칭만 바뀝니다
34
+ expect(withParam('/[id]/[id]/[id]', { id: '123' })).toBe('/123/[id]/[id]');
35
+ });
36
+ it('should handle numeric values', () => {
37
+ expect(withParam('/users/[id]', { id: 123 })).toBe('/users/123');
38
+ expect(withParam('/posts/[postId]/comments/[commentId]', {
39
+ postId: 1,
40
+ commentId: 2,
41
+ })).toBe('/posts/1/comments/2');
42
+ });
43
+ it('should leave unmatched parameters as is', () => {
44
+ expect(withParam('/users/[id]', { userId: '123' })).toBe('/users/[id]');
45
+ expect(withParam('/posts/[postId]/comments/[commentId]', { id: '123' })).toBe('/posts/[postId]/comments/[commentId]');
46
+ });
47
+ it('should handle values with brackets correctly', () => {
48
+ // 값에 [id]가 있어도 이미 처리된 패턴은 다시 바뀌지 않음
49
+ expect(withParam('/[placeholder]/[actual]', { placeholder: 'fixed', actual: '[id]' })).toBe('/fixed/[id]');
50
+ });
51
+ });
52
+ describe('withQuery', () => {
53
+ it('should add single query parameter', () => {
54
+ expect(withQuery('/search', { q: 'test' })).toBe('/search?q=test');
55
+ expect(withQuery('/users', { page: 1 })).toBe('/users?page=1');
56
+ });
57
+ it('should add multiple query parameters', () => {
58
+ expect(withQuery('/search', { q: 'test', page: 1 })).toBe('/search?q=test&page=1');
59
+ expect(withQuery('/filter', { category: 'tech', sort: 'asc', limit: 10 })).toBe('/filter?category=tech&sort=asc&limit=10');
60
+ });
61
+ it('should handle numeric values', () => {
62
+ expect(withQuery('/users', { page: 1, limit: 20 })).toBe('/users?page=1&limit=20');
63
+ });
64
+ it('should handle boolean values', () => {
65
+ expect(withQuery('/posts', { featured: true, archived: false })).toBe('/posts?featured=true&archived=false');
66
+ });
67
+ it('should filter out null and undefined values', () => {
68
+ expect(withQuery('/search', { q: 'test', page: null, limit: undefined })).toBe('/search?q=test');
69
+ expect(withQuery('/users', { id: undefined })).toBe('/users');
70
+ });
71
+ it('should handle empty object', () => {
72
+ expect(withQuery('/users', {})).toBe('/users');
73
+ });
74
+ it('should append query string (does not merge with existing query)', () => {
75
+ // 현재 구현은 기존 쿼리 스트링을 병합하지 않고 뒤에 붙입니다
76
+ expect(withQuery('/search?existing=1', { new: 'value' })).toBe('/search?existing=1?new=value');
77
+ });
78
+ it('should combine withParam and withQuery', () => {
79
+ const path = withQuery(withParam('/users/[id]', { id: '123' }), { tab: 'profile' });
80
+ expect(path).toBe('/users/123?tab=profile');
81
+ const path2 = withQuery(withParam('/posts/[postId]/comments/[commentId]', {
82
+ postId: '456',
83
+ commentId: '789',
84
+ }), { sort: 'asc', page: 1 });
85
+ expect(path2).toBe('/posts/456/comments/789?sort=asc&page=1');
86
+ });
87
+ });
88
+ describe('createRoute', () => {
89
+ it('should combine base and path', () => {
90
+ expect(createRoute('/auth', '/login')).toBe('/auth/login');
91
+ expect(createRoute('/users', '/create')).toBe('/users/create');
92
+ });
93
+ it('should handle path without leading slash', () => {
94
+ expect(createRoute('/auth', 'login')).toBe('/auth/login');
95
+ expect(createRoute('/users', 'create')).toBe('/users/create');
96
+ });
97
+ it('should handle base with trailing slash', () => {
98
+ expect(createRoute('/auth/', '/login')).toBe('/auth//login');
99
+ expect(createRoute('/auth/', 'login')).toBe('/auth//login');
100
+ });
101
+ it('should handle multiple levels', () => {
102
+ expect(createRoute('/api/v1', '/users')).toBe('/api/v1/users');
103
+ expect(createRoute('/admin', '/settings/profile')).toBe('/admin/settings/profile');
104
+ });
105
+ });
106
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blastlabs/utils",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,34 +21,15 @@ paths:
21
21
  user-profile/
22
22
  use-user-data/
23
23
  button-group.tsx
24
+ user-card.tsx
25
+ login-form.tsx
24
26
 
25
27
  ❌ bad
26
28
  UserProfile/
27
29
  useUserData/
28
30
  buttonGroup.tsx
29
- ```
30
-
31
- ### 예외: React 컴포넌트
32
-
33
- **컴포넌트 파일은 `PascalCase` 사용**
34
-
35
- ```
36
- components/
37
- ✅ ButtonGroup.tsx
38
- ✅ UserProfile.tsx
39
- ✅ FormInput.tsx
40
-
41
- ❌ button-group.tsx
42
- ❌ user-profile.tsx
43
- ```
44
-
45
- **단, 폴더는 `kebab-case`**
46
-
47
- ```
48
- components/
49
- button-group/
50
- ButtonGroup.tsx
51
- ButtonGroup.test.tsx
31
+ UserCard.tsx
32
+ LoginForm.tsx
52
33
  ```
53
34
 
54
35
  ### Hooks
@@ -85,8 +66,8 @@ model/
85
66
  **대상 파일명 + `.test.` + 확장자**
86
67
 
87
68
  ```
88
- ButtonGroup.tsx → ButtonGroup.test.tsx
89
- useUserData.ts → useUserData.test.ts
69
+ button-group.tsx → button-group.test.tsx
70
+ use-user-data.ts → use-user-data.test.ts
90
71
  api.ts → api.test.ts
91
72
  ```
92
73
 
@@ -106,19 +87,20 @@ src/
106
87
  │ │ ├── use-user-query.ts
107
88
  │ │ └── user.test.ts
108
89
  │ └── ui/
109
- │ ├── UserCard.tsx # PascalCase
110
- │ └── UserCard.test.tsx
90
+ │ ├── user-card.tsx
91
+ │ └── user-card.test.tsx
111
92
  ├── features/
112
93
  │ └── auth-login/ # kebab-case
113
94
  │ ├── model/
114
95
  │ ├── api/
115
96
  │ └── ui/
116
- └── LoginForm.tsx # PascalCase
97
+ ├── login-form.tsx
98
+ │ └── login-form.test.tsx
117
99
  ├── shared/
118
100
  │ └── ui/
119
101
  │ └── button/ # kebab-case
120
- │ ├── Button.tsx # PascalCase
121
- │ └── Button.test.tsx
102
+ │ ├── button.tsx
103
+ │ └── button.test.tsx
122
104
  ```
123
105
 
124
106
  ## 변수/함수 네이밍
@@ -206,7 +188,7 @@ enum userRole {
206
188
 
207
189
  ## 중요
208
190
 
209
- - **Always** use kebab-case for files and folders
210
- - **Always** use PascalCase for component files
191
+ - **Always** use kebab-case for all files and folders (including components)
211
192
  - **Always** use camelCase for variables and functions
212
- - **Always** use UPPER_SNAKE_CASE for constants
193
+ - **Always** use UPPER_SNAKE_CASE for constants
194
+ - **Always** use PascalCase for types, interfaces, enums, and React component exports